<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet href="https://brandonrozek.com/css/pretty-feed-v3.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Blog | Brandon Rozek</title>
    <link>https://brandonrozek.com/blog/index.json</link>
    <image>
      <title>Blog | Brandon Rozek</title>
      <link>https://brandonrozek.com/blog/index.json</link>
      <url>https://brandonrozek.com/img/avatar.jpg</url>
    </image>
    <description> Brandon Rozek is a Computer Scientist often writing about topics surrounding Linux and Software Development. </description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    
	<atom:link href="https://brandonrozek.com/blog/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>SVG Pixel Art</title>
      <link>https://brandonrozek.com/blog/svg-pixel-art/</link>
      <pubDate>Mon, 19 Feb 2024 10:24:49 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/svg-pixel-art/</guid>
      <description><![CDATA[<p>This year, I participated in the 32-bit Cafe <a href="https://tilde.32bit.cafe/~ribose/events/valentines2024/">Valentine&rsquo;s Day Event</a>. They gave a few ways to participate, and this year I made a little Valentine&rsquo;s day card.</p>
<p><img src="/files/images/blog/20240219105747.png" alt="Image of card that says &amp;ldquo;Happy Valentine&amp;rsquo;s&amp;rdquo; with pixel art of a heart."></p>
<p>For the whole animated version, check out my <a href="http://tilde.club/~brozek/valentines-2024.html">tilde.club page</a>.</p>
<p>The picture above is a PNG screenshot, but for the card itself I wanted to use SVGs as the primary graphics format to create the heart.</p>
<p>If you were to ask me why, I would claim it&rsquo;s because SVGs are flexible when it comes to size. Though if I was to be honest, it&rsquo;s beause SVGs are a plaintext format that I can edit.</p>
<p>Looking around for a tool to create this, I came across a <a href="https://jerosoler.github.io/SvgPixelArt/">SVG Pixel Art project</a> from Jero Soler.</p>
<p><img src="/files/images/blog/20240219110500.png" alt="Screenshot of the pixel art editor"></p>
<p>The editor provides a very familiar experience to Gimp and Photoshop. It includes advance features like layering which I didn&rsquo;t feel the need to use for this quick drawing.</p>
<p>The available colors leave some to be desired. After downloading the SVG, I was able to edit the <code>fill</code> attribute with the hex values of the tacky colors I wanted to use instead.</p>
<p>I recommend trying the website out yourself next time you want to make some pixel art. For me, it&rsquo;s less daunting and more familiar to use than Inkscape, the more prevalent svg editing tool.</p>
]]></description>
      
    </item>
    
    <item>
      <title>Quick Python: Refactoring Exceptions with Context Manager</title>
      <link>https://brandonrozek.com/blog/python-refactoring-exceptions-context-manager/</link>
      <pubDate>Thu, 01 Feb 2024 20:48:21 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/python-refactoring-exceptions-context-manager/</guid>
      <description><![CDATA[<p>I generally find exception syntax a little clunky&hellip;</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> _ <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">5</span>):
</span></span><span style="display:flex;"><span>        sleep(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">except</span> <span style="color:#a6e22e">KeyboardInterrupt</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Awesome task 1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Awesome task 2...</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">pass</span>
</span></span></code></pre></div><p>Especially if you end up capturing the same exceptions and handling it the same way.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> _ <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">5</span>):
</span></span><span style="display:flex;"><span>        sleep(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">except</span> <span style="color:#a6e22e">KeyboardInterrupt</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Awesome task 1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Awesome task 2...</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">pass</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> _ <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">2</span>):
</span></span><span style="display:flex;"><span>        sleep(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">except</span> <span style="color:#a6e22e">KeyboardInterrupt</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Awesome task 1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Awesome task 2...</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">pass</span>
</span></span></code></pre></div><p>One way to make our code more DRY (don&rsquo;t-repeat-yourself) is to make use of Python&rsquo;s context managers.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#a6e22e">@contextmanager</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">handle_sigint</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">yield</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">except</span> <span style="color:#a6e22e">KeyboardInterrupt</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Awesome task 1</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Awesome task 2...</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">pass</span>
</span></span></code></pre></div><p>Using the context manager, everything within the indented block gets executed within the try block.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">with</span> handle_sigint():
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> _ <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">5</span>):
</span></span><span style="display:flex;"><span>        sleep(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> handle_sigint():
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> _ <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">2</span>):
</span></span><span style="display:flex;"><span>        sleep(<span style="color:#ae81ff">1</span>)
</span></span></code></pre></div><p>In fact, we can write this in a generic way to give us an alternative syntax for handling exceptions.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#a6e22e">@contextmanager</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">handle_exception</span>(f, <span style="color:#f92672">*</span>exceptions):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">yield</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">except</span> exceptions <span style="color:#66d9ef">as</span> e:
</span></span><span style="display:flex;"><span>        f(e)
</span></span></code></pre></div><p>For example, let&rsquo;s tell the user that we&rsquo;re explicitly ignoring their exception</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">ignore</span>(e):
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;Ignoring&#34;</span>, e<span style="color:#f92672">.</span>__class__<span style="color:#f92672">.</span>__name__)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> handle_exception(ignore, <span style="color:#a6e22e">NotImplementedError</span>, <span style="color:#a6e22e">KeyboardInterrupt</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> _ <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">5</span>):
</span></span><span style="display:flex;"><span>        sleep(<span style="color:#ae81ff">1</span>)
</span></span></code></pre></div>]]></description>
      <category>Python</category>
      
    </item>
    
    <item>
      <title>Mapping the states I visited</title>
      <link>https://brandonrozek.com/blog/mapping-states-visited/</link>
      <pubDate>Thu, 01 Feb 2024 18:30:00 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/mapping-states-visited/</guid>
      <description><![CDATA[<p><a href="https://hacdias.com/impossible-list/">Henrique&rsquo;s Impossible List</a> shows a list of countries that they&rsquo;ve visited so far and their goals for the future. I personally have not travelled to many countries, but I have visited a several US states!</p>
<p>Instead of presenting the states in a list, I thought it would be very cool to visually show it on a map. Ideally the map would be encoded in a SVG so that:</p>
<ul>
<li>The image will scale regardless of the device size</li>
<li>I can manually edit the source to show the visited regions</li>
</ul>
<p>Now imagine my surprise after searching on <a href="https://kagi.com/">Kagi</a> to come across a SVG in the public domain on <a href="https://commons.wikimedia.org/wiki/File:Blank_US_Map_(states_only).svg">Wikipedia</a>. Even better, the following is shown near the top of the file</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span><span style="color:#75715e">/* Individual states can be colored as follows: */</span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">ks</span><span style="color:#f92672">,</span>.<span style="color:#a6e22e">mt</span><span style="color:#f92672">,</span>.<span style="color:#a6e22e">pa</span> {fill:<span style="color:#ae81ff">#0000FF</span>}
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">ca</span><span style="color:#f92672">,</span>.<span style="color:#a6e22e">de</span> {fill:<span style="color:#ae81ff">#FF0000</span>}
</span></span><span style="display:flex;"><span><span style="color:#75715e">/* In this example, Kansas, Montana and Pennsylvania are colored blue,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">and California and Delaware are colored red.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">Place this code in the empty space below. */</span>
</span></span></code></pre></div><p>Such an elegant way of coloring in the states! Now it&rsquo;s up to me to come up with some categories. As of the time of writing, I decided on:</p>
<ul>
<li>Green: Places I&rsquo;ve lived</li>
<li>Blue: Places I&rsquo;ve visited</li>
<li>Darker Gray: Places I&rsquo;ve driven through</li>
</ul>
<p>Here&rsquo;s how I colored by map:</p>
<p><img src="/files/images/usa_visited.svg" alt="Map of US States colored by whether I&amp;rsquo;ve visited"></p>
<p>For the last couple weeks, I&rsquo;ve been brainstorming new pages for my website and came up with <a href="https://brandonrozek.com/visited">https://brandonrozek.com/visited</a>.</p>
<p>At the time of writing, it only has the colored in states and a list of cities I&rsquo;ve been to. Though I&rsquo;m welcome to any suggestions you might have. Feel free to <a href="/contact">get in touch!</a></p>
]]></description>
      
    </item>
    
    <item>
      <title>Wordguess: A game for the Tildeverse</title>
      <link>https://brandonrozek.com/blog/wordguess/</link>
      <pubDate>Sun, 28 Jan 2024 18:40:00 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/wordguess/</guid>
      <description><![CDATA[<p>The <a href="/blog/tildeverse/">Tildeverse</a> is a collection of online communities that allows people to create user accounts on public unix machines (pubnixes). Often this is to create websites, however, one component that I think deserves additional attention is the creation of games for these communities.</p>
<p>Three games that are featured in the message-of-the-day in Tilde.club are:</p>
<ul>
<li><a href="https://tildegit.org/team/botany">Botany</a>: Plant raising game where players can help water each other&rsquo;s plants</li>
<li><a href="https://github.com/jmdejong/asciifarm">Asciifarm</a>: Multiplayer farming/fighting game</li>
<li><a href="http://crawl.develz.org/">Dungeon Crawl Stone Soup</a>: Rouge-like dungeon adventure</li>
</ul>
<p>These games are designed to be multiplayer, but it doesn&rsquo;t need to in order to be a social game. A large example of this recently, is the game Wordle.</p>
<p>Wordle is designed to be played by oneself. A challenge is issued once a day, and the player gets a score based on their performance. The players can then compare the scores with each other and strive to beat each other on future days.</p>
<p>WordGuess is a Wordle inspired game I made designed to run on a pubnix. It relies on the Linux permission system to authenticate players and keeps a leaderboard showing players scores for each of the days.</p>
<p>Below is how it looks like for a player to SSH into the pubnix and play the game:</p>
<p><img src="/files/images/blog/wordguess.svg" alt="Gameplay of WordGuess"></p>
<p>For those on the <a href="https://tilde.club/">Tilde.club</a> server, it&rsquo;s already setup and ready to play. Run the following command to start:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>python /home/brozek/WordGuess/client.py
</span></span></code></pre></div><p>After playing the game you&rsquo;re welcome to see the scores of others that day:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>python /home/brozek/WordGuess/leaderboard.py
</span></span></code></pre></div><p>If you&rsquo;re not on Tilde.club. You&rsquo;re welcome to set it up yourself on any Linux server or pubnix. The instructions are listed on the <a href="https://github.com/brandon-rozek/wordguess">Github</a>.</p>
]]></description>
      
    </item>
    
    <item>
      <title>Ice Hockey</title>
      <link>https://brandonrozek.com/blog/ice-hockey/</link>
      <pubDate>Thu, 25 Jan 2024 12:15:00 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/ice-hockey/</guid>
      <description><![CDATA[<p><img src="/files/images/blog/20231126210415162.jpg" alt="Action shot of ice hockey players"></p>
<hr>
<p>One of my favorite pastimes of late is watching my university&rsquo;s hockey team. RPI Men&rsquo;s hockey compete in the <a href="https://en.wikipedia.org/wiki/ECAC_Hockey">ECAC hockey league</a> which is a division 1 league under the <a href="https://en.wikipedia.org/wiki/National_Collegiate_Athletic_Association">NCAA</a>. In other words, hockey is a pretty big deal at the university,</p>
<p>Hockey is a full-contact sport with players going back and forth on the ice at high speeds. You can tell it&rsquo;s a strenuous sport because the players only stay on the ice for a shift of on average 45 seconds.</p>
<p>Part of the fun is the camaraderie among the attendees. We have a full set of <a href="http://apbwiki.rpipepbandalumni.org/Category:Vocal_cheers">vocal cheers</a> that we scream when penalties are announced, goals are obtained, and other in-game events. We call them vocal cheers, however, the majority of them consists of yelling that the other team <em>sucks</em>.</p>
<p>I&rsquo;ve attended at least 5 games at this point. I wouldn&rsquo;t say that I&rsquo;m an expert at  the rules of hockey, but I&rsquo;ve picked up on the most common calls.</p>
<hr>
<p>For resets:</p>
<ul>
<li>Icing: When a player shoots a puck before the center line and it passes the goal line on the other side. This resets the game with the puck going to the defending zone of the team that committed the infraction.</li>
<li>Puck out of bounds: When a player unintentionally shoots a puck out of the rink either hitting the audience area or the netting. This resets the game to the closest face off spot from where the puck was hit.</li>
</ul>
<hr>
<p>It&rsquo;s also not hockey without talking about penalties.</p>
<ul>
<li>Boarding: Violently pushing a player onto the walls of the rink</li>
<li>Cross-checking: Hitting an opponent with a stick when both hands are on the stick and no part of the stick is on the ice.</li>
<li>Hooking: Using the stick to &ldquo;hook&rdquo; or slow down an opponent.</li>
<li>Slashing: Swinging the stick at an opponent.</li>
<li>Tripping: Causing an opponent to fall.</li>
<li>Interference: Impeding a player that does not have a puck.</li>
</ul>
<hr>
<p>Most of these penalties (unless injury occurs) result in a 2 minute timeout for the player that committed the infraction. The player does not get replaced, resulting in the team that committed the infraction having one less active player. This time period is marked as a <em>power play</em> for the other team.</p>
<p>There are 3 periods in ice hockey with each period lasting 20 minutes. If there are any special performances at RPI, it typically happens after the first period. For example, we&rsquo;ve had several ice skating performances. After the second period, RPI holds the  &ldquo;chuck the puck&rdquo; competition and the 50/50 raffle. <a href="https://en.wikipedia.org/wiki/Ice_resurfacer">Zambonis</a> come out after these events to resurface the ice.</p>
<p><img src="/files/images/blog/20230212014049350.jpg" alt="Image of Zamboni sponsored by CDATA"></p>
<p>Lastly, we cannot forget about the pep band and our good ol&rsquo; mascot Puckman. They don&rsquo;t come out every  game but it&rsquo;s great when they do.</p>
<p><img src="/files/images/blog/20230212011745325.jpg" alt="Puckman"></p>
]]></description>
      
    </item>
    
    <item>
      <title>Marshalling Python Dataclasses</title>
      <link>https://brandonrozek.com/blog/marshalling-python-dataclasses/</link>
      <pubDate>Sat, 20 Jan 2024 22:52:50 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/marshalling-python-dataclasses/</guid>
      <description><![CDATA[<p>Recently I wanted a way to transfer structured messages between two python applications over a unix domain socket. The cleanest and simplest way I found so far is to make use of the dataclasses and json standard libraries.</p>
<p>We&rsquo;ll consider the following message for the rest of the post:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> dataclasses <span style="color:#f92672">import</span> dataclass
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@dataclass</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">QueryUserMessage</span>:
</span></span><span style="display:flex;"><span>    auth_key: str
</span></span><span style="display:flex;"><span>    username: str
</span></span></code></pre></div><h2 id="marshalling">Marshalling</h2>
<p>Let&rsquo;s say we have a message we want to send:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>message <span style="color:#f92672">=</span> QueryUserMessage(<span style="color:#e6db74">&#34;lkajdfsas&#34;</span>, <span style="color:#e6db74">&#34;brozek&#34;</span>)
</span></span></code></pre></div><p>We first need to get its dictionary representation. Luckily the standard library has us there:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> dataclasses <span style="color:#f92672">import</span> asdict
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>message_dict <span style="color:#f92672">=</span> asdict(message)
</span></span></code></pre></div><p>Then we can use the <code>json</code> module to give us a string representation</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> json
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>message_str <span style="color:#f92672">=</span> json<span style="color:#f92672">.</span>dumps(message_dict)
</span></span></code></pre></div><p>Finally, we can encode it into bytes and send it away:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Default encoding is &#34;utf-8&#34;</span>
</span></span><span style="display:flex;"><span>message_bytes <span style="color:#f92672">=</span> message_str<span style="color:#f92672">.</span>encode()
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Assuming connetion is defined...</span>
</span></span><span style="display:flex;"><span>connection<span style="color:#f92672">.</span>sendall(message_bytes)
</span></span></code></pre></div><p>To make this easier for myself, I create a custom <code>json</code> encoder and a function that uses the connection to send off the message</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">DataclassEncoder</span>(json<span style="color:#f92672">.</span>JSONEncoder):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">default</span>(self, o):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> asdict(o)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">send_message</span>(connection, message_dataclass):
</span></span><span style="display:flex;"><span>    contents <span style="color:#f92672">=</span> json<span style="color:#f92672">.</span>dumps(message_dataclass, cls<span style="color:#f92672">=</span>DataclassEncoder)<span style="color:#f92672">.</span>encode()
</span></span><span style="display:flex;"><span>    connection<span style="color:#f92672">.</span>sendall(contents)
</span></span></code></pre></div><h2 id="un-marshalling">Un-marshalling</h2>
<p>On the other end, let us receive the bytes and decode it into a string:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>MESSAGE_BUFFER_LEN <span style="color:#f92672">=</span> <span style="color:#ae81ff">1024</span>
</span></span><span style="display:flex;"><span>message_bytes <span style="color:#f92672">=</span> connection<span style="color:#f92672">.</span>recv(MESSAGE_BUFFER_LEN)
</span></span><span style="display:flex;"><span>message_str <span style="color:#f92672">=</span> message_bytes<span style="color:#f92672">.</span>decode()
</span></span></code></pre></div><p>We can use the <code>json</code> module to turn it into a Python dictionary</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>message_dict <span style="color:#f92672">=</span> json<span style="color:#f92672">.</span>loads(message_str)
</span></span></code></pre></div><p>In this post, we can make use of the fact that we only have one message class. In other cases, you would either want to rely on some protocol or pass in the message type ahead of time. Therefore, we can pass the fields of the dictionary straight to the constructor.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>message <span style="color:#f92672">=</span> QueryUserMessage(<span style="color:#f92672">**</span>message_dict)
</span></span></code></pre></div><h2 id="conclusion">Conclusion</h2>
<p>In production use cases, we&rsquo;ll need to introduce a gambit of error-handling to capture failures in json de-serialization and class instantiation. I hope, however, that this serves as a good starting point.</p>
<p>Some things to consider:</p>
<ol>
<li>If you have multiple types of messages, maybe including a field in the dictionary that is a string which represents the message type. Both applications can then maintain a map between these strings and the class constructors.</li>
<li>If it&rsquo;s possible to have messages larger than the buffer length, then consider either setting it higher or sending the size of the message beforehand.</li>
<li>Using a standard HTTP library ;)</li>
</ol>
]]></description>
      <category>Python</category>
      
    </item>
    
    <item>
      <title>Ascii Towns in the Tildeverse with Cadastre</title>
      <link>https://brandonrozek.com/blog/tildeverse-ascii-towns-cadastre/</link>
      <pubDate>Wed, 17 Jan 2024 18:14:32 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/tildeverse-ascii-towns-cadastre/</guid>
      <description><![CDATA[<p><a href="https://github.com/jmdejong/cadastre">Cadastre</a> stitches together plots of &ldquo;land&rdquo; that users on a tilde server control. Ultimately creating one giant ascii town.</p>
<p>For example,  consider one user has the following in <code>~/.cadastre/home.txt</code>:</p>
<pre tabindex="0"><code>0 0
####################
#  ________        #
# /~\__\___\       #
# | |  |   | ,,,,  #
#           ,,,,,  #
#    ~~~~~  ,,,,,  #
#    ~~~~~     _   #
#    ~~~~~    (*)  #
# ~deepend     |   #
#                  #
####################
</code></pre><p>While another user has:</p>
<pre tabindex="0"><code>0 1
~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~
~~        ~~~~~~~~~~~~~
~    ~~~      ~~~~~~~~~~
~~~~~~~~~      ~~~    ~
~~~~~~~~~~~         ~~~
~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~
</code></pre><p>Then the stitched together town will be:</p>
<pre tabindex="0"><code>#################### ~~~~~~~~~~~~~~~~~~~~~~~~
#  ________        # ~~~~~~~~~~~~~~~~~~~~~~~
# /~\__\___\       # ~~~~~~~~~~~~~~~~~~~~~~~
# | |  |   | ,,,,  # ~~~~~~~~~~~~~~~~~~~~~~~
#           ,,,,,  # ~~        ~~~~~~~~~~~~~
#    ~~~~~  ,,,,,  # ~    ~~~      ~~~~~~~~~~
#    ~~~~~     _   # ~~~~~~~~~      ~~~    ~
#    ~~~~~    (*)  # ~~~~~~~~~~~         ~~~
# ~deepend     |   # ~~~~~~~~~~~~~~~~~~~~~~~
#                  # ~~~~~~~~~~~~~~~~~~~~~~~
#################### ~~~~~~~~~~~~~~~~~~~~~~~
</code></pre><p>Ascii art is something that always interested me, but I never took the time to make. Cadastre limits us to 24x12 characters which serves as a good starting canvas for myself.</p>
<p>You might be able to tell, but I&rsquo;m a big fan of mountains.</p>
<pre tabindex="0"><code>+=======================+
.    /\                 .
.   /  \  /\            .
.  /    \/  \/\         .
. /      \ _ \ \        .
.     ____//            .
. ___/____/      @@@    .
. ___/         @@@@@@@  .
.             @@@@@@@@@ .
.              @@@@@@@  .
. ~brozek        | |    .
.........................
</code></pre><p>Check out my plot among the village over at <a href="https://tilde.club/~troido/cadastre/town.html">tilde.club</a>. For a more lively instance check out <a href="http://tilde.town/~troido/cadastre/town.html">tilde.town</a>.</p>
<p><em>Thanks to <a href="http://tilde.town/~troido/">~troido</a> for creating this cool piece of software!</em></p>
]]></description>
      
    </item>
    
    <item>
      <title>Python Dataclasses: Derived Fields and Validation</title>
      <link>https://brandonrozek.com/blog/python-dataclasses-derived-fields-validation/</link>
      <pubDate>Mon, 15 Jan 2024 11:02:21 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/python-dataclasses-derived-fields-validation/</guid>
      <description><![CDATA[<p>Python dataclasses provide a simplified way of creating simple classes that hold data.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> dataclasses <span style="color:#f92672">import</span> dataclass
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@dataclass</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Person</span>:
</span></span><span style="display:flex;"><span>    name: str
</span></span><span style="display:flex;"><span>    birth_year: int
</span></span></code></pre></div><p>The above code is equivalent to:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">A</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __init__(name: str, birth_year: int):
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>name <span style="color:#f92672">=</span> name
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>birth_year <span style="color:#f92672">=</span> birth_year
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>__post__init__()
</span></span></code></pre></div><p>Notice the call to <code>__post__init__</code> at the end. We can override that method to do whatever we&rsquo;d like. I have found two great use cases for this.</p>
<h2 id="use-case-1-derived-fields">Use Case 1: Derived Fields</h2>
<p>Straight from the <a href="https://docs.python.org/3/library/dataclasses.html#dataclasses.__post_init__">Python documentation</a>, this use case is for when we want to use some variables to create a new variable.</p>
<p>For example, to compute a new field <code>age</code> from a person&rsquo;s <code>birth_year</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Person</span>:
</span></span><span style="display:flex;"><span>    name: str
</span></span><span style="display:flex;"><span>    birth_year: int
</span></span><span style="display:flex;"><span>    age: int <span style="color:#f92672">=</span> field(init<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">__post_init__</span>(self):
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Assuming the current year is 2024 and their birthday already passed</span>
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>age <span style="color:#f92672">=</span> <span style="color:#ae81ff">2024</span> <span style="color:#f92672">-</span> self<span style="color:#f92672">.</span>birth_year
</span></span></code></pre></div><h2 id="use-case-2-validation">Use Case 2: Validation</h2>
<p>Another use case is to make sure
that the user instantiates the fields
of a data class in a way we expect.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Person</span>:
</span></span><span style="display:flex;"><span>    name: str
</span></span><span style="display:flex;"><span>    birth_year: int
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">__post__init__</span>(self):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">assert</span> self<span style="color:#f92672">.</span>birth_year <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">assert</span> isinstance(self<span style="color:#f92672">.</span>name, str)
</span></span></code></pre></div><p>Nothing is stopping us from combining both of these use cases within the <code>__post_init__</code> method!</p>
]]></description>
      <category>Python</category>
      
    </item>
    
    <item>
      <title>Fun with Telnet</title>
      <link>https://brandonrozek.com/blog/fun-with-telnet/</link>
      <pubDate>Mon, 08 Jan 2024 22:33:42 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/fun-with-telnet/</guid>
      <description><![CDATA[<p>Some time ago I came across a cinematic experience</p>
<pre tabindex="0"><code>.........                @@@@@    @@@@@      ...........           
.........               @     @  @     @     ...........           
 .........                 @@@   @     @      ..........           
  ........               @@      @     @       .........           
   ........             @@@@@@@   @@@@@  th     .......            
     ......           -----------------------    ......            
       .....            C  E  N  T  U  R  Y        ....            
         ....         -----------------------       ...            
           ...        @@@@@ @@@@@ @   @ @@@@@        ..            
            ==          @   @      @ @    @          ==            
          __||__        @   @@@@    @     @        __||__          
         |      |       @   @      @ @    @       |      |         
_________|______|_____  @   @@@@@ @   @   @  _____|______|_________
</code></pre><p>It was a Star Wars inspired animation, brought to you by Telnet!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>telnet towel.blinkenlights.nl <span style="color:#ae81ff">23</span>
</span></span></code></pre></div><p><em>It&rsquo;s worth watching the entire thing.</em></p>
<p>Fast forward a few years, and I&rsquo;ve come across other great <a href="https://www.telnet.org/htm/places.htm">telnet nuggets</a></p>
<table>
<thead>
<tr>
<th>Address</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>freechess.org 5000</td>
<td>Chess <a href="https://www.freechess.org/">[1]</a></td>
</tr>
<tr>
<td>towel.blinkenlights.nl 23</td>
<td>Star Wars asciimation</td>
</tr>
<tr>
<td>mtrek.com 1701</td>
<td>Space combat game (inspired by StarTrek) <a href="http://mtrek.com/">[1]</a></td>
</tr>
<tr>
<td>fibs.com 4321</td>
<td>Backgammon <a href="http://fibs.com/">[1]</a> <a href="https://tinysubversions.com/notes/backgammon-fibs/">[2]</a></td>
</tr>
<tr>
<td>mapscii.me</td>
<td>World map (Can zoom in with mouse) <a href="https://github.com/rastapasta/mapscii">[1]</a></td>
</tr>
<tr>
<td>telehack.com</td>
<td>Arpanet/Usenet Simulation. Includes over 60 text-based games <a href="https://telehack.com/">[1]</a></td>
</tr>
</tbody>
</table>
<p>Remember that telnet communication is all over plaintext, so don&rsquo;t accidentally leak any secrets. Have fun discovering the quirky old world of Telnet!</p>
]]></description>
      
    </item>
    
    <item>
      <title>How to get a RSS Feed of your Wikipedia Watchlist</title>
      <link>https://brandonrozek.com/blog/rss-feed-watchlist-wikipedia/</link>
      <pubDate>Sun, 07 Jan 2024 11:29:01 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/rss-feed-watchlist-wikipedia/</guid>
      <description><![CDATA[<p>Instead of having to manually log in to Wikipedia to look at your watchlist, you can use your RSS feed!</p>
<p><strong>Step 1:</strong> Get your token</p>
<p>Go to your <a href="https://en.wikipedia.org/wiki/Special:Preferences#mw-prefsection-watchlist-token">preferences</a>. Log in if not already.</p>
<p>Then click on the <em>Watchlist</em> tab. Scroll down to <em>Tokens</em>. Click the <em>Manage Tokens</em> button.</p>
<p>Hold on to the token for the next step.</p>
<p><strong>Step 2:</strong> Import the RSS feed to your feed reader.</p>
<p>Direct URL: <a href="https://en.wikipedia.org/w/api.php?action=feedwatchlist&amp;allrev&amp;wlowner=USERNAME&amp;wltoken=TOKEN">https://en.wikipedia.org/w/api.php?action=feedwatchlist&amp;allrev&amp;wlowner=USERNAME&amp;wltoken=TOKEN</a></p>
<p>Replace <code>USERNAME</code> and <code>TOKEN</code> with their respective information.</p>
<p><strong>That&rsquo;s it!</strong> Each new item in the RSS feed will look like the following (feed reader dependent):</p>
<pre tabindex="0"><code>Reinforcement learning
December 31, 2023 at 11:28 PM by OAbot

[[Wikipedia:OABOT|Open access bot]]: arxiv updated in citation with #oabot. (OAbot) 
</code></pre><p>At a glance, you can see:</p>
<p>Line 1: Title of Wikipedia Page Edited</p>
<p>Line 2: Time and Author of Edit (may be username or IP)</p>
<p>Rest: Description of edit.</p>
<p>From here you can check the diffs to make sure that the edit stands.</p>
<h2 id="resources">Resources</h2>
<p><a href="https://m.mediawiki.org/wiki/Help:Watchlist">https://m.mediawiki.org/wiki/Help:Watchlist</a>
<a href="https://en.m.wikipedia.org/wiki/Wikipedia%3aSyndication">https://en.m.wikipedia.org/wiki/Wikipedia%3aSyndication</a></p>
]]></description>
      
    </item>
    
    <item>
      <title>Run a command when a file in a directory changes</title>
      <link>https://brandonrozek.com/blog/command-when-file-in-directory-changes/</link>
      <pubDate>Fri, 05 Jan 2024 16:00:53 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/command-when-file-in-directory-changes/</guid>
      <description><![CDATA[<p>When editing code or adding content to a HTML file for a website, it can be very useful to see the changes right away after you save the file. We&rsquo;ll show a lightweight way of accomplishing this task with <code>entr</code> and a bit of bash scripting.</p>
<p>First if you don&rsquo;t already have it, install <code>entr</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo dnf install entr
</span></span></code></pre></div><p>This terminal application works by monitoring files passed within standard in. Therefore, to run a <code>$command</code>  if any file under some <code>$directory</code> changes run the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find $directory -type f | entr $command
</span></span></code></pre></div><p>An example can be running a build script if anything under the source folder changes:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find src -type f | entr ./build.sh
</span></span></code></pre></div><p>Now this doesn&rsquo;t capture files that get added to the directory. To do this, we need to get <code>entr</code> to monitor the parent folder for any changes. We can do this with the <code>-d</code> flag.</p>
<p>From the man page:</p>
<blockquote>
<p><strong>-d</strong>    Track the directories of regular files provided as input and exit if
a new file is added.</p>
</blockquote>
<p>Since <code>entr</code> exits when a file gets added with this flag, the <a href="https://superuser.com/a/665208">common solution</a> on the internet is to wrap it in a <code>while</code> loop.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ <span style="color:#66d9ef">while</span> sleep 0.1 ; <span style="color:#66d9ef">do</span> find $directory -type f | entr -d $command ; <span style="color:#66d9ef">done</span>
</span></span></code></pre></div><p>This solution works great as a one-liner, but it doesn&rsquo;t let me CTRL-C out when I&rsquo;m finished. Therefore, I wrote a shell script that incorporates this solution while also adding a <code>trap</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>set -o nounset
</span></span><span style="display:flex;"><span>set -o pipefail
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>show_usage<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    echo <span style="color:#e6db74">&#34;Usage: entr-dir [dir] [command]&#34;</span>
</span></span><span style="display:flex;"><span>    exit <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Check argument count</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$#<span style="color:#e6db74">&#34;</span> -lt <span style="color:#ae81ff">2</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    show_usage
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Make sure that the command entr exists</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> ! command -v entr &gt; /dev/null ; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    echo <span style="color:#e6db74">&#34;entr not found. Exiting...&#34;</span>
</span></span><span style="display:flex;"><span>    exit <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>DIR<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span>; shift
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> ! -d <span style="color:#e6db74">&#34;</span>$DIR<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    echo <span style="color:#e6db74">&#34;First argument must be a directory. Exiting...&#34;</span>
</span></span><span style="display:flex;"><span>    exit <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Allow for CTRL-C to exit script</span>
</span></span><span style="display:flex;"><span>trap <span style="color:#e6db74">&#34;exit 0;&#34;</span> SIGINT
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">while</span> sleep 0.1; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>    find <span style="color:#e6db74">&#34;</span>$DIR<span style="color:#e6db74">&#34;</span> -type f | entr -d <span style="color:#e6db74">&#34;</span>$@<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span></code></pre></div><p>I plopped this in <code>~/.local/bin/entr-dir</code>, gave it the <code>+x</code> permission, and now I can easily monitor and build projects when files get changed/added/deleted.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>entr-dir src ./build.sh
</span></span></code></pre></div>]]></description>
      
    </item>
    
    <item>
      <title>Bash Traps: Exit, Error, Sigint</title>
      <link>https://brandonrozek.com/blog/bash-traps-exit-error-sigint/</link>
      <pubDate>Tue, 02 Jan 2024 11:26:06 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/bash-traps-exit-error-sigint/</guid>
      <description><![CDATA[<p>Traps in bash listen for interrupts and signals and performs a bash command in response to it. This post will go over three use cases I&rsquo;ve encountered so far in my bash scripting journey.</p>
<h2 id="sigint"><code>SIGINT</code></h2>
<p>If we have a while loop within a bash script</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#66d9ef">while</span> sleep 0.1; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>  do_something
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span></code></pre></div><p>We probably want to be able to CTRL-C to exit the whole script.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>finish <span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  exit <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>trap finish SIGINT
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">while</span> sleep 0.1; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>  do_something
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span></code></pre></div><h2 id="err"><code>ERR</code></h2>
<p>If we encounter an error in a bash script, one thing we might want to do is dump the environment information and other things that can be useful for debugging.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>set -o errexit
</span></span><span style="display:flex;"><span>set -o nounset
</span></span><span style="display:flex;"><span>set -o pipefail
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print_debug <span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  declare exit_code<span style="color:#f92672">=</span>$?
</span></span><span style="display:flex;"><span>  env &gt;&amp;<span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>  other_debug_info &gt;&amp;<span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>  exit <span style="color:#e6db74">&#34;</span>$exit_code<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>trap print_debug ERR
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>command_that_may_fail
</span></span></code></pre></div><h2 id="exit"><code>EXIT</code></h2>
<p>Like the last one, this function will occur if the script fails. However, this also occurs if the script is successful. It&rsquo;s similar to the <code>finally</code> clause within a <code>try-catch</code> programming paradigm.</p>
<p>This is useful for cleaning up build files, de-provisioning services, etc.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cleanup <span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  rm a.tmp b.tmp
</span></span><span style="display:flex;"><span>  deprovision
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>trap cleanup EXIT
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>long_compile_may_fail
</span></span></code></pre></div><h2 id="other-resources">Other Resources</h2>
<p><a href="http://redsymbol.net/articles/bash-exit-traps/">http://redsymbol.net/articles/bash-exit-traps/</a></p>
<p><a href="https://jakski.github.io/pages/error-handling-in-bash.html">https://jakski.github.io/pages/error-handling-in-bash.html</a></p>
]]></description>
      <category>Bash</category>
      
    </item>
    
    <item>
      <title>No Response Default in Nginx</title>
      <link>https://brandonrozek.com/blog/no-response-default-nginx/</link>
      <pubDate>Mon, 01 Jan 2024 17:21:51 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/no-response-default-nginx/</guid>
      <description><![CDATA[<blockquote>
<p>Welcome to nginx!</p>
</blockquote>
<p>After installing <code>nginx</code> fresh on a server, this classic quote is part of a welcome page that is automatically setup within a default configuration file. On Redhat-based systems this lives under <code>/etc/nginx/conf.d</code> and for Ubuntu-based ones under <code>/etc/nginx/sites-available/</code>.</p>
<p>This page mostly exists to make sure that everything is squared away after installation. However, how can we get rid of the page once we have all the site configurations loaded?</p>
<p>We&rsquo;ll show how to get rid of the default welcome page by changing the <code>default.conf</code> file.</p>
<p>When you type in <code>https://brandonrozek.com</code> in your web browser, your system will first (1) look up the IP (at the time of writing it&rsquo;s <code>173.255.230.230</code>) and then (2) send a HTTP request with the header including <code>Host('brandonrozek.com')</code>. It&rsquo;s the <code>Host</code> header that lets <code>nginx</code> know which content to serve.</p>
<p>So if <code>nginx</code> comes across a <code>Host</code> header that doesn&rsquo;t have a specified configuration for, then it follows some default behavior. This is described under the configuration file with the host <code>default_server</code>.</p>
<p>One good default behavior is to not bother responding to the request. For this purpose, the best HTTP response code is <code>444</code> No Response.</p>
<p>The <code>nginx</code> configuration on the HTTP side looks like the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">server</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#ae81ff">80</span> <span style="color:#e6db74">default_server</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#e6db74">[::]:80</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">server_name</span> <span style="color:#e6db74">_</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">return</span> <span style="color:#ae81ff">444</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Under standard HTTPS, a SSL handshake occurs. Though this seems pointless if all we&rsquo;re doing is returning nothing. Therefore, we can reject the handshake as well.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">server</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#ae81ff">443</span> <span style="color:#e6db74">ssl</span> <span style="color:#e6db74">http2</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#e6db74">[::]:443</span> <span style="color:#e6db74">ssl</span> <span style="color:#e6db74">http2</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ssl_reject_handshake</span> <span style="color:#66d9ef">on</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">server_name</span> <span style="color:#e6db74">_</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">return</span> <span style="color:#ae81ff">444</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>All together, a modified <code>default.conf</code> looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">server</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#ae81ff">80</span> <span style="color:#e6db74">default_server</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#e6db74">[::]:80</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">server_name</span> <span style="color:#e6db74">_</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">return</span> <span style="color:#ae81ff">444</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">server</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#ae81ff">443</span> <span style="color:#e6db74">ssl</span> <span style="color:#e6db74">http2</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#e6db74">[::]:443</span> <span style="color:#e6db74">ssl</span> <span style="color:#e6db74">http2</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ssl_reject_handshake</span> <span style="color:#66d9ef">on</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">server_name</span> <span style="color:#e6db74">_</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">return</span> <span style="color:#ae81ff">444</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div>]]></description>
      <category>Nginx</category>
      
    </item>
    
    <item>
      <title>Switching to Raid 10</title>
      <link>https://brandonrozek.com/blog/switching-raid-10/</link>
      <pubDate>Fri, 29 Dec 2023 15:44:44 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/switching-raid-10/</guid>
      <description><![CDATA[<p>In my living room lies a Dell PowerEdge R430 server which acts as the primary data storage system in our home. The server has four hard drive slots, three of which I had previously filled.</p>
<p>I run <code>btrfs</code> as my primary file system on that server. The file system supports multiple raid levels: RAID 0, RAID 1, RAID 10, RAID 5, and RAID 6. Wikipedia has two great articles describing the <a href="https://en.wikipedia.org/wiki/Standard_RAID_levels">standard RAID levels</a> and <a href="https://en.wikipedia.org/wiki/Nested_RAID_levels">nested RAID levels</a>.</p>
<p>Since at the time I only had 3 drives, it limited me to RAID 0, 1, and 5. There are still <a href="https://btrfs.readthedocs.io/en/latest/btrfs-man5.html#raid56-status-and-recommended-practices">some complications with RAID 5</a> in btrfs, so it leaves me with two options:</p>
<ul>
<li>RAID 0: No data redundancy. Data is split evenly across all disks</li>
<li>RAID 1: Mirroring. Data is present across all disks.</li>
</ul>
<p>Now the data isn&rsquo;t vital to me. I have backups <a href="https://www.raidisnotabackup.com/">elsewhere</a>. However, I would prefer not to deal with the process of restoring terrabytes of data from a backup when a hard drive fails. So at the time, I used RAID 1.</p>
<p>For Christmas, I received another 3.5 inch hard drive for my server. This now unlocks the two other RAID levels 6 and 10. RAID 6 suffers from similar issues as RAID 5. What about RAID 10?</p>
<p>RAID 10 is a nested RAID level because it has both a RAID 1 and RAID 0 setup with it.  Wikipedia has a <a href="https://en.wikipedia.org/wiki/Nested_RAID_levels#/media/File:RAID_10_01.svg">great diagram</a> which I&rsquo;ll show here describing how it&rsquo;s laid out.</p>

<img style="display: block; margin: 0 auto;" src="/files/images/blog/202312291631.png" alt="Diagram describing RAID 10" width=250/>

<p>For four hard drives, we split them into pairs of two. Each pair is configured as RAID 1. Meaning that for a pair (A, B), all the data in A is also in B. Then all of the pairs  (A, B), (C, D) get combined into a RAID 0 configuration. This means that data from one pair (A, B) will not appear in another pair (C, D).</p>
<p>RAID 10, just like RAID 1 protects against a single drive failure. For RAID 1, this is because the data is stored on all the other hard drives. For RAID 10, this is because the data is on the hard drives corresponding pair.</p>
<p>RAID 10, however, may or may not be safe if two hard drives fail. If you&rsquo;re lucky, only a single hard drive from each of the pairs fail. However, if both A and B fail and they&rsquo;re in a (A, B) pair, the data is lost.</p>
<p>The benefit? You get to utilize more of the storage space compared to a RAID 1 configuration. In RAID 1, you can only use up to the smallest disk size you have. This is because the data needs to exist on all the hard drives. For RAID 10, it&rsquo;s dependent on how the drives are paired up. If all the hard drives are the same size, however, then you can use double the space than a corresponding RAID 1 configuration.</p>
<p>Hard drives don&rsquo;t fail every day, in fact it&rsquo;s common for hard drives to <a href="https://www.backblaze.com/blog/hard-drive-life-expectancy/">last beyond 3-5 years</a>. Therefore it makes sense to me to sacrifice the ability for all-but-one hard drives to fail for more storage capacity. Especially since I regularly obtain hard drives many months apart, to avoid the <a href="https://en.wikipedia.org/wiki/Bathtub_curve">bathtub curve</a>.</p>
<p>The steps for adding the hard drive and converting the raid level weren&rsquo;t too involved.</p>
<p>To add a drive:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo btrfs device add /dev/sdX MOUNTPOINT
</span></span></code></pre></div><p>Replacing <code>sdX</code> with the appropriate hard drive reference.</p>
<p>To convert to RAID 10:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo btrfs balance start -dconvert<span style="color:#f92672">=</span>raid10 -mconvert<span style="color:#f92672">=</span>raid10 MOUNTPOINT
</span></span></code></pre></div><p>Depending on the amount of data you have, it can take many hours (sometimes days) to balance a btrfs file system. Therefore, it might make sense to run the above command in a <code>tmux</code> session.</p>
]]></description>
      <category>Storage</category>
      
    </item>
    
    <item>
      <title>Renewing my GPG Keys</title>
      <link>https://brandonrozek.com/blog/renewing-gpg-keys/</link>
      <pubDate>Thu, 28 Dec 2023 11:46:33 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/renewing-gpg-keys/</guid>
      <description><![CDATA[<p>Recently I let my GPG keys expire. I noticed this when I was working on a project, and when I went to <a href="/blog/signingcommits/">automatically sign my commits</a> &ndash; git threw an error at me. Since I was working at the time, I did the not-so-great practice of disabling the signing feature.</p>
<p>Having keys automatically expire is annoying. Though, it does give me a chance to reflect if these keys are still useful to me.  Currently I use GPG keys for:</p>
<ul>
<li>Code signing</li>
<li>Receiving encrypted messages</li>
<li><a href="/blog/decentralized-identity-pgp-keyoxide/">Decentralized Identity</a></li>
</ul>
<p>So to me, having a GPG key is still worth it. Now to go about renewing my keys. This post will show how I go about the renewing process itself and what services I update. Mostly for me in the future.</p>
<h2 id="renewing-my-gpg-key">Renewing my GPG key</h2>
<p>First, find your key</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gpg --list-keys
</span></span></code></pre></div><pre tabindex="0"><code>/home/brandon/.gnupg/pubring.kbx
------------------------------
pub   ed25519 2022-12-14 [SC] [expires: 2023-12-14]
      5F37830BFA46FF7881F47AC78DF79C3DC5FC658A
uid           [ultimate] Brandon Rozek &lt;brozek@brandonrozek.com&gt;
uid           [ultimate] Brandon Rozek &lt;hello@brandonrozek.com&gt;
sub   cv25519 2022-12-14 [E] [expires: 2023-12-14]
sub   dsa2048 2022-12-17 [S] [expires: 2023-12-14]
</code></pre><p>The fingerprint is the line below <code>pub</code> and for me starts with <code>5F37</code>. Let&rsquo;s store that in a variable for easy reference later.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export FPR<span style="color:#f92672">=</span>5F37830BFA46FF7881F47AC78DF79C3DC5FC658A
</span></span></code></pre></div><p>If we want to extend the expiration date to a year from today, we can use the following command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gpg --quick-set-expire $FPR 1y
</span></span></code></pre></div><p>Alternatively, you can specify an exact date with the ISO format <code>YYYY-MM-DD</code> or keep it relative with respect to days <code>d</code>, weeks <code>w</code>, and months <code>m</code>.</p>
<p>When we check the key again, we should see an updated expiration date</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gpg --list-keys
</span></span></code></pre></div><pre tabindex="0"><code>/home/brandon/.gnupg/pubring.kbx
------------------------------
pub   ed25519 2022-12-14 [SC] [expires: 2024-12-28]
      5F37830BFA46FF7881F47AC78DF79C3DC5FC658A
uid           [ultimate] Brandon Rozek &lt;brozek@brandonrozek.com&gt;
uid           [ultimate] Brandon Rozek &lt;hello@brandonrozek.com&gt;
sub   cv25519 2022-12-14 [E] [expires: 2023-12-14]
sub   dsa2048 2022-12-17 [S] [expires: 2023-12-14]
</code></pre><p>Notice that the two subkeys still have the old expiration date. We&rsquo;ll need to update that as well. We&rsquo;ll need to get their fingergrints with the following command</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gpg --list-keys --verbose --with-subkey-fingerprints
</span></span></code></pre></div><pre tabindex="0"><code>gpg: enabled compatibility flags:
gpg: using pgp trust model
/home/rozek/.gnupg/pubring.kbx
------------------------------
pub   ed25519 2022-12-14 [SC] [expires: 2024-12-27]
      5F37830BFA46FF7881F47AC78DF79C3DC5FC658A
uid           [ultimate] Brandon Rozek &lt;brozek@brandonrozek.com&gt;
uid           [ultimate] Brandon Rozek &lt;hello@brandonrozek.com&gt;
sub   cv25519 2022-12-14 [E] [expires: 2023-12-14]
      D502A12A65F9997DAE4609C97DAEAD7BFFA8F9D3
sub   dsa2048 2022-12-17 [S] [expires: 2023-12-14]
      89859D1EDF70D6DC2F6BFFF226E457DA82C9F480
</code></pre><p>Store the fingerprints again for easy reference:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export SFPR1<span style="color:#f92672">=</span>D502A12A65F9997DAE4609C97DAEAD7BFFA8F9D3
</span></span><span style="display:flex;"><span>export SFPR2<span style="color:#f92672">=</span>89859D1EDF70D6DC2F6BFFF226E457DA82C9F480
</span></span></code></pre></div><p>Extend the expiration of the subkeys:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gpg --quick-set-expire $FPR 1y $SFPR1
</span></span><span style="display:flex;"><span>gpg --quick-set-expire $FPR 1y $SFPR2
</span></span></code></pre></div><h2 id="updating-services">Updating Services</h2>
<p>I currently allow for two ways to query my keys: OpenGPG keyserver and WKD. To update my keys on my own WKD keyserver, I followed the steps in my <a href="/blog/decentralized-pgp-keys-wkd">tutorial on WKD</a>.</p>
<p>For OpenGPG, I followed the instructions on their <a href="https://keys.openpgp.org/about/usage">usage page</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gpg --export your_address@example.net | curl -T - https://keys.openpgp.org 
</span></span></code></pre></div><h2 id="references">References</h2>
<p><a href="https://www.gnupg.org/documentation/manuals/gnupg24/gpg.1.html">https://www.gnupg.org/documentation/manuals/gnupg24/gpg.1.html</a></p>
<p><a href="https://whynothugo.nl/journal/2023/07/13/extending-an-expired-gpg-key/">https://whynothugo.nl/journal/2023/07/13/extending-an-expired-gpg-key/</a></p>
<p><a href="https://brandonrozek.com/blog/decentralized-pgp-keys-wkd/">https://brandonrozek.com/blog/decentralized-pgp-keys-wkd/</a></p>
<p><a href="https://keys.openpgp.org/about/usage">https://keys.openpgp.org/about/usage</a></p>
]]></description>
      <category>GPG/PGP</category>
      
    </item>
    
    <item>
      <title>Starbound Server using LXC</title>
      <link>https://brandonrozek.com/blog/starbound-server-lxc/</link>
      <pubDate>Sun, 03 Dec 2023 11:17:10 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/starbound-server-lxc/</guid>
      <description><![CDATA[<p>I recently had a gaming session with old friends and we played the game Starbound. In hopes of continuing our session in the near future, I set up a Starbound server for us all to connect to.</p>
<p>However, since I&rsquo;ve recently learned how to setup LXC containers, my host OS is Fedora, and the <a href="https://developer.valvesoftware.com/wiki/SteamCMD#Ubuntu">steamcmd</a> provides instructions for Debian, Ubuntu, and Arch.  I&rsquo;ve decided to set up a system container using LXC.</p>
<p>Also who knows how secure these game servers are. It&rsquo;s likely a good idea to virtualize them when possible&hellip;</p>
<p>This guide assumes you have a Ubuntu Jammy (22.04) amd64 system container configured and that you&rsquo;re within the root shell of that container. For help with that, check out <a href="/blog/lxc-fedora-38/">my post</a>.</p>
<h2 id="installing-starbound">Installing Starbound</h2>
<p>Since we start off with a root account, let&rsquo;s create another user which we&rsquo;ll call <code>steam</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>adduser steam
</span></span></code></pre></div><p>We&rsquo;ll give <code>sudo</code> privileges to our <code>steam</code> user. You can always remove this privilege after setting it up.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>usermod -aG sudo steam
</span></span></code></pre></div><p>Now let&rsquo;s go into our steam user</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>su steam
</span></span></code></pre></div><p>Before using <code>apt</code>, it&rsquo;s always a good idea to make sure our system is up to date.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt update <span style="color:#f92672">&amp;&amp;</span> sudo apt upgrade
</span></span></code></pre></div><p>Steam requires 32-bit libraries to work properly, so we&rsquo;ll need to setup the multiverse repository and add i386 support.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install software-properties-common
</span></span><span style="display:flex;"><span>sudo add-apt-repository multiverse
</span></span><span style="display:flex;"><span>sudo dpkg --add-architecture i386
</span></span></code></pre></div><p>Finally, we can update our local cache and install <code>steamcmd</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt update
</span></span><span style="display:flex;"><span>sudo apt install steamcmd
</span></span></code></pre></div><p>Now let&rsquo;s open our steam shell.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>steamcmd
</span></span></code></pre></div><p>We should now see a <code>steam&gt;</code> at the beginning of our prompt. We&rsquo;ll tell steam to install Starbound under the Starbound folder of our <code>$HOME</code> directory.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>force_install_dir /home/steam/starbound
</span></span></code></pre></div><p>I&rsquo;m unsure if we need to login or do this anonymously. When I set this up, I&rsquo;ve logged into my steam account. Write to me if you were able to get this to work anonymously.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>login &lt;username&gt; &lt;password&gt;
</span></span></code></pre></div><p>After logging in (it might ask for a 2FA code), we can install Starbound using the following command.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>app_update <span style="color:#ae81ff">211820</span>
</span></span></code></pre></div><p>We can then leave the steam shell.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>quit
</span></span></code></pre></div><p>At this point, we should check to see if the server binary runs as expected.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>/home/steam/starbound/linux/starbound_server
</span></span></code></pre></div><p>If this works, great! I didn&rsquo;t face any issues at this step, but feel free to write in if you did.</p>
<h2 id="configuring-networking">Configuring Networking</h2>
<p>We now have a Starbound server running from within our LXC container. Since we want players outside of the host system to access the Starbound server, we&rsquo;ll need to setup some firewall rules and TCP forwarding.</p>
<p>First we&rsquo;ll need to allow traffic to go into our container network.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ufw allow in on lxdbr0
</span></span><span style="display:flex;"><span>sudo ufw route allow in on lxdbr0
</span></span></code></pre></div><p>On the host, we need to open up the Starbound port</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ufw allow <span style="color:#ae81ff">21025</span>
</span></span></code></pre></div><p>Then we need to route TCP traffic back and forth. For this I used <code>nginx</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo dnf install nginx
</span></span></code></pre></div><p>We need to get the container IP.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>lxc-ls --fancy
</span></span></code></pre></div><p>Add the following to the end of <code>/etc/nginx/nginx.conf</code></p>
<pre tabindex="0"><code>stream {
    upstream starbound {
        server CONTAINERIP:21025;
    }

    server {
        listen 21025;
        proxy_pass starbound;
    }
}
</code></pre><p>Start and enable the nginx service</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl enable --now nginx
</span></span></code></pre></div><p>If you face an error here, it might be because of <code>selinux</code>. Either disable that or configure it to allow for binding of <code>21025</code>.</p>
<p>At this point, you can try to connect to the Starbound server using the game! I didn&rsquo;t face any unexpected issues at this part, but feel free to write in if you did.</p>
<h2 id="locking-down-starbound-optional">Locking down Starbound (Optional)</h2>
<p>This part is optional, but if you don&rsquo;t want any arbitrary person to connect to your Starbound server, you&rsquo;ll need to lock it down.</p>
<p>Inside your container exit the <code>/home/steam/starbound/storage/starbound_server.config</code></p>
<p>Disallow anonymous connections</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#e6db74">&#34;allowAnonymousConnections&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#66d9ef">false</span>
</span></span></code></pre></div><p>Add users with usernames and passwords</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#e6db74">&#34;serverUsers&#34;</span> : <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;user1&#34;</span> : <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;admin&#34;</span> : false,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;password&#34;</span> : <span style="color:#e6db74">&#34;somethinghere&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">}</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;user2&#34;</span> : <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;admin&#34;</span> : false,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;password&#34;</span> : <span style="color:#e6db74">&#34;somethinghere2&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div><p>Keep in mind that we didn&rsquo;t set up TLS so I wouldn&rsquo;t put a password here that gets used anywhere else.</p>
<h2 id="automatically-starting-up-the-starbound-server">Automatically Starting up the Starbound server</h2>
<p>First make sure <a href="/blog/lxc-fedora-38/">the container autostarts on boot</a>.</p>
<p>Then within the container, we can set up a systemd service to start the starbound server.</p>
<p>Edit <code>/etc/systemd/system/starbound.service</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[Unit]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Description</span><span style="color:#f92672">=</span><span style="color:#e6db74">StarboundServer</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">After</span><span style="color:#f92672">=</span><span style="color:#e6db74">network.target</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Service]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">WorkingDirectory</span><span style="color:#f92672">=</span><span style="color:#e6db74">/home/steam/starbound/linux</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">User</span><span style="color:#f92672">=</span><span style="color:#e6db74">steam</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Group</span><span style="color:#f92672">=</span><span style="color:#e6db74">steam</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Type</span><span style="color:#f92672">=</span><span style="color:#e6db74">simple</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStart</span><span style="color:#f92672">=</span><span style="color:#e6db74">/home/steam/starbound/linux/starbound_server</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">RestartSec</span><span style="color:#f92672">=</span><span style="color:#e6db74">15</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Restart</span><span style="color:#f92672">=</span><span style="color:#e6db74">always</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">KillSignal</span><span style="color:#f92672">=</span><span style="color:#e6db74">SIGINT</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Install]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">WantedBy</span><span style="color:#f92672">=</span><span style="color:#e6db74">multi-user.target</span>
</span></span></code></pre></div><p>Then start and enable the service</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl enable --now starbound
</span></span></code></pre></div>]]></description>
      
    </item>
    
    <item>
      <title>Setting up unprivileged containers with LXC on Fedora 38</title>
      <link>https://brandonrozek.com/blog/lxc-fedora-38/</link>
      <pubDate>Sun, 03 Dec 2023 10:21:41 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/lxc-fedora-38/</guid>
      <description><![CDATA[<p>LXC is a containerization technology that allows us to create <em>system containers</em>. We can set it up so that we can SSH into a container and perform many of the same tasks we would on a regular Linux box. I currently have two uses cases for this.</p>
<p>First, these system containers allows me to follow instruction documentation for projects that do not treat docker/podman as a first class distribution method. Maybe the project relies on being able to access <code>systemd</code>, or perhaps I don&rsquo;t want the additional burden of ensuring the upgrade path of a custom docker-based method.</p>
<p>Second, it allows me to provide a lightweight virtualized environment for my friends and family. With this approach, we don&rsquo;t have to set caps on the amount of CPU and memory usage from the start (as opposed to a VM), and instead impose them later if others are violating fair use.</p>
<p>In this guide, we&rsquo;ll focus on setting up unprivileged containers. These are containers that are started up and maintained by a user other than <code>root</code>.</p>
<p>This technology is moving fast. It&rsquo;s likely that this guide will be out of date by the time you&rsquo;re reading this. I do hope, however, that this can serve a as a good jumping off point for other queries.</p>
<h2 id="setup">Setup</h2>
<p>First, let&rsquo;s install <code>lxc</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo dnf install lxc lxc-templates
</span></span></code></pre></div><p>To setup networking for our containers, we&rsquo;ll also need to install <code>dnsmasq</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo dnf install dnsmasq
</span></span></code></pre></div><p>We now need to tell LXC that our user is allowed to create a certain number of network devices on our <code>lxcbr0</code> bridge that LXC configures for us.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>echo <span style="color:#66d9ef">$(</span>id -un<span style="color:#66d9ef">)</span> veth lxcbr0 <span style="color:#ae81ff">50</span> | sudo tee -a /etc/lxc/lxc-usernet
</span></span></code></pre></div><p>I can&rsquo;t imagine myself creating more than 50 system containers, but do adjust that number as you see fit.</p>
<p>The last command we&rsquo;ll need to run as root on the host system is to enable and start the <code>lxc-net</code> service.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl enable --now lxc-net
</span></span></code></pre></div><p>For our user on the host, we need to setup the LXC configuration file. Here, we&rsquo;re going to map the user UID/GIDs into our container.</p>
<p>This set of commands were taken from <a href="https://linuxcontainers.org/lxc/getting-started/">LXC getting started</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mkdir -p ~/.config/lxc
</span></span><span style="display:flex;"><span>cp /etc/lxc/default.conf ~/.config/lxc/default.conf
</span></span><span style="display:flex;"><span>MS_UID<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>grep <span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>id -un<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span> /etc/subuid  | cut -d : -f 2<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>ME_UID<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>grep <span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>id -un<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span> /etc/subuid  | cut -d : -f 3<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>MS_GID<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>grep <span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>id -un<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span> /etc/subgid  | cut -d : -f 2<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>ME_GID<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>grep <span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>id -un<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span> /etc/subgid  | cut -d : -f 3<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;lxc.idmap = u 0 </span>$MS_UID<span style="color:#e6db74"> </span>$ME_UID<span style="color:#e6db74">&#34;</span> &gt;&gt; ~/.config/lxc/default.conf
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;lxc.idmap = g 0 </span>$MS_GID<span style="color:#e6db74"> </span>$ME_GID<span style="color:#e6db74">&#34;</span> &gt;&gt; ~/.config/lxc/default.conf
</span></span></code></pre></div><p>Now we can download and create our container.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>systemd-run --unit<span style="color:#f92672">=</span>my-unit --user --scope -p <span style="color:#e6db74">&#34;Delegate=yes&#34;</span> -- lxc-create -t download -n test
</span></span></code></pre></div><p>At the time of writing, we unfortunately have to wrap the <code>lxc-create</code> command within a <code>systemd-run</code> because of a semi-recent change in how <code>cgroups</code> work.</p>
<p>After running that command, you should see a large table of distributions and questions about which one to choose. Of course this is personal preference, but I selected <code>ubuntu jammy amd64</code>.</p>
<p>Once downloaded, we can then start our container.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>systemd-run --unit<span style="color:#f92672">=</span>my-unit --user --scope -p <span style="color:#e6db74">&#34;Delegate=yes&#34;</span> -- lxc-start test
</span></span></code></pre></div><p>If we see an error message here, then we can add a log file to check for details.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>systemd-run --unit<span style="color:#f92672">=</span>my-unit --user --scope -p <span style="color:#e6db74">&#34;Delegate=yes&#34;</span> -- lxc-start test --logfile<span style="color:#f92672">=</span>~/lxc.log
</span></span></code></pre></div><p>The error I commonly saw when setting this up was:</p>
<pre tabindex="0"><code>lxc-start test 20231130034412.168 ERROR    start - start.c:print_top_failing_dir:99 - Permission denied - Could not access /home/brandon. Please grant it x access, or add an ACL for the container root
</code></pre><p>This means we need to give <code>$MS_UID</code> access to open the <code>/home/brandon</code> folder. Though what it&rsquo;s really trying to do is access <code>/home/brandon/.local/share/lxc</code> for it&rsquo;s <code>rootfs</code> and <code>config</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>setfacl -m u:$MS_UID:x /home/rozek
</span></span></code></pre></div><p>Then try running the <code>lxc-start</code> command from before again. Sometimes when setting this up it worked from here, other times it wanted me to add the <code>+x</code> permission to other folders.</p>
<p>If you run it and don&rsquo;t see a bunch of errors, then it&rsquo;s hopefully a success! Check that it&rsquo;s running:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#f92672">[</span>brandon@host ~<span style="color:#f92672">]</span>$ lxc-ls --fancy
</span></span><span style="display:flex;"><span>NAME      STATE   AUTOSTART GROUPS IPV4       IPV6 UNPRIVILEGED 
</span></span><span style="display:flex;"><span>test      RUNNING <span style="color:#ae81ff">0</span>         -      10.0.3.43  -    true   
</span></span></code></pre></div><p>We can then attach to our container to get a shell within it.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>lxc-attach test
</span></span></code></pre></div><p>If the above command doesn&rsquo;t work, you might need to wrap it in a <code>systemd-run</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>systemd-run --unit<span style="color:#f92672">=</span>my-unit2 --user --scope -p <span style="color:#e6db74">&#34;Delegate=yes&#34;</span> -- lxc-attach test
</span></span></code></pre></div><h2 id="auto-starting-lxc-containers-at-boot">Auto-starting LXC containers at boot</h2>
<p>When we start a container with the <code>systemd-run</code> command, it&rsquo;s tied to that particular terminal session. If we want to start these containers when the machine boots up, we can rely on <code>systemd</code>.</p>
<p>First let&rsquo;s create a service file under <code>~/.config/systemd/user/lxc@.service</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[Unit]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Description</span><span style="color:#f92672">=</span><span style="color:#e6db74">Start LXC container</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">After</span><span style="color:#f92672">=</span><span style="color:#e6db74">lxc-net.service</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Wants</span><span style="color:#f92672">=</span><span style="color:#e6db74">lxc-net.service</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Service]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Type</span><span style="color:#f92672">=</span><span style="color:#e6db74">oneshot</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Delegate</span><span style="color:#f92672">=</span><span style="color:#e6db74">yes</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">RemainAfterExit</span><span style="color:#f92672">=</span><span style="color:#e6db74">yes</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStart</span><span style="color:#f92672">=</span><span style="color:#e6db74">/usr/bin/lxc-start %i</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStop</span><span style="color:#f92672">=</span><span style="color:#e6db74">/usr/bin/lxc-stop %i</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">TimeoutStartSec</span><span style="color:#f92672">=</span><span style="color:#e6db74">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Install]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">WantedBy</span><span style="color:#f92672">=</span><span style="color:#e6db74">default.target</span>
</span></span></code></pre></div><p>Then we can start our test container</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>systemctl --user start lxc@test
</span></span></code></pre></div><p>Enable it on bootup</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>systemctl --user enable lxc@test
</span></span></code></pre></div><p>Since this is a user service, we need to make sure linger is on for it to respect
the enable on boot setting.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo loginctl enable-linger rozek
</span></span></code></pre></div><h2 id="other-resources">Other Resources</h2>
<p><a href="https://linuxcontainers.org/lxc/getting-started/">https://linuxcontainers.org/lxc/getting-started/</a></p>
<p><a href="https://www.redhat.com/sysadmin/exploring-containers-lxc">https://www.redhat.com/sysadmin/exploring-containers-lxc</a></p>
<p><a href="https://wiki.archlinux.org/title/Linux_Containers">https://wiki.archlinux.org/title/Linux_Containers</a></p>
]]></description>
      <category>Containers</category>
      
    </item>
    
    <item>
      <title>Adding badges to my website</title>
      <link>https://brandonrozek.com/blog/website-badges/</link>
      <pubDate>Sun, 26 Nov 2023 22:57:08 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/website-badges/</guid>
      <description><![CDATA[<p>I&rsquo;ve been coming across more <a href="https://neocities.org/">neocities</a> websites recently. It&rsquo;s really cool how many people there hand craft their HTML and CSS to really make a website theirs. One idea I&rsquo;m stealing for my website is adding web badges.</p>
<p><img src="/badges/fedora.gif" alt="Example Badge"></p>
<p>These web badges are often small images that shows a product or message. Historically, it dates back to when Netscape in 1996 made the <a href="https://sillydog.org/netscape/now.html">Netscape Now!</a> badge.</p>
<p>Since then, the Geocities picked it up and standardized many of the web badges to have a pixel size of 88x31. These badges are sometimes animated as GIFs, though they usually don&rsquo;t hold dynamic information.</p>
<p>In recent times, we&rsquo;ve seen badges hold dynamic information such as <a href="https://github.com/badges/shields">code coverage on GitHub</a>. Also, Wikipedia has Userboxes that people can add to their User Pages to show dynamic information about themselves. <a href="https://en.wikipedia.org/wiki/User:RubenSchade/Userboxes">Ruben&rsquo;s User Page</a> showcases many of these.</p>
<p>Though to get started with my own website, I decided to look primarily at the traditional 88x31 style web badges. Looking around the web, there are some webpages that have large listings of web badges:</p>
<p><a href="https://web.badges.world/">https://web.badges.world/</a></p>
<p><a href="https://cyber.dabamos.de/">https://cyber.dabamos.de/</a></p>
<p><a href="https://capstasher.neocities.org/">https://capstasher.neocities.org/</a></p>
<p>From there, we can (1) pick the ones we like (2) download them, and (3) upload them to our webserver. We can then create the <code>img</code> tag in our footer to showcase them.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">img</span> <span style="color:#a6e22e">width</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;88px&#34;</span> <span style="color:#a6e22e">height</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;31px&#34;</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/badges/fedora.gif&#34;</span> /&gt;
</span></span></code></pre></div>]]></description>
      
    </item>
    
    <item>
      <title>Re: On Earbuds</title>
      <link>https://brandonrozek.com/blog/on-earbuds/</link>
      <pubDate>Wed, 22 Nov 2023 13:13:34 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/on-earbuds/</guid>
      <description><![CDATA[<p>In response to <a href="https://ricard.blog/other/my-first-noise-cancelling-earbuds/">Ricard Torres post</a></p>
<p>I also recently bought the Google Pixel buds (August 2023) and agree with you on how magical they are. Beforehand, I primarily used the <a href="https://www.sony.com/et/electronics/headband-headphones/wh-1000xm4">Sony WH-100XM4 headphones</a> for a couple years. While I still believe that those are superior in terms of audio quality, the Google Pixel buds have it beat in terms of convenience. They allow me to have one earbud in to not only enjoy whichever podcast I&rsquo;m listening to at the moment, but also be able to quickly hear my fiance when she wants to strike a conversation with me.</p>
<p>While the Google Pixel buds are my new default. I do believe there are situations when other solutions are appropriate.</p>
<ul>
<li>For video calls, I often use a cheap pair of 2.5mm wired earphones. This is mainly to not include latency introduced by bluetooth.</li>
<li>When I&rsquo;m in an extended noisy non-interactive situation like a airline flight. I find that the Sony WH-100XM4 headphones cancels out more noise which is needed for an enjoyable experience.</li>
</ul>
]]></description>
      
    </item>
    
    <item>
      <title>Mirror Public Repositories</title>
      <link>https://brandonrozek.com/blog/mirror-public-repositories/</link>
      <pubDate>Wed, 22 Nov 2023 12:28:13 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/mirror-public-repositories/</guid>
      <description><![CDATA[<p>Git is designed to be decentralized. However, many people treat it as a centralized solution by depending on services such as GitHub. What we&rsquo;ve seen through the <a href="https://www.zdnet.com/article/riaa-blitz-takes-down-18-github-projects-used-for-downloading-youtube-videos/">youtube-dl debacle</a> is that repositories that we depend on can be taken down.</p>
<p>This isn&rsquo;t to say that GitHub is evil and that we should move to Bitbucket, Gitlab, Source Hut, etc. But this is more of a commentary on what happens when we depend on one service to host our code. Git is designed to be decentralized, we should make use of that fact!</p>
<p>Also, alleged illegal activity is not the only reason repositories are taken down from the Internet. Sometimes, the <a href="https://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/">developer themselves</a> decide to take it down.</p>
<p>As individuals, we can maintain mirrors of repositories we care about. That way if it ever gets removed from a particular service, we&rsquo;re not out of luck.</p>
<p>The simplest way to go about this is to <code>git clone</code> the repositories you care about, and regularly <code>pull</code> through a cron job or systemd timer. Through systemd maybe it&rsquo;ll look like this:</p>
<p><code>/etc/systemd/system/refresh-hugo.service</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[Unit]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Description</span><span style="color:#f92672">=</span><span style="color:#e6db74">Pull latest changes from Hugo</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Service]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Type</span><span style="color:#f92672">=</span><span style="color:#e6db74">oneshot</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">WorkingDirectory</span><span style="color:#f92672">=</span><span style="color:#e6db74">/home/brandonrozek/repo/hugo</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStart</span><span style="color:#f92672">=</span><span style="color:#e6db74">/usr/bin/git pull</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Install]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">WantedBy</span><span style="color:#f92672">=</span><span style="color:#e6db74">multi-user.target</span>
</span></span></code></pre></div><p><code>/etc/systemd/system/refresh-hugo.timer</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[Unit]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Description</span><span style="color:#f92672">=</span><span style="color:#e6db74">Pull latest changes from Hugo every week</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Requires</span><span style="color:#f92672">=</span><span style="color:#e6db74">refresh-hugo.service</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Timer]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Unit</span><span style="color:#f92672">=</span><span style="color:#e6db74">refresh-hugo.service</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">OnCalendar</span><span style="color:#f92672">=</span><span style="color:#e6db74">weekly</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Persistent</span><span style="color:#f92672">=</span><span style="color:#e6db74">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Install]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">WantedBy</span><span style="color:#f92672">=</span><span style="color:#e6db74">timers.target</span>
</span></span></code></pre></div><p>Alternatively, you can use a self-hosted git server instance such as <a href="https://forgejo.org/">Forgejo</a> to set up pull mirrors through the migration tool. As of the time of writing, this is what I currently do with repositories at <a href="https://git.brandonrozek.com/github">https://git.brandonrozek.com/github</a></p>
<p>I do recommend only mirroring/pulling infrequently such as weekly. We don&rsquo;t want to induce unnecessary load on these centralized services.</p>
]]></description>
      <category>Git</category>
      <category>Archive</category>
      
    </item>
    
    <item>
      <title>Changing /etc/hosts based on network connection</title>
      <link>https://brandonrozek.com/blog/etc-hosts-connection/</link>
      <pubDate>Wed, 22 Nov 2023 09:23:09 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/etc-hosts-connection/</guid>
      <description><![CDATA[<p>I use my laptop at home, university, and public locations. The IP address I use to connect to a particular resource changes depending on if I&rsquo;m within the network it&rsquo;s hosted on or a VPN. A common solution is to have a DNS server within the VPN that all clients use. This, however, has a few issues:</p>
<ul>
<li>If the client has multiple VPNs connected, only one DNS server can be set.</li>
<li>There may be more latency using a DNS server within a VPN than using a default one provided by the ISP.</li>
<li>You may not have permission to host the DNS server within the VPN network.</li>
</ul>
<p>To address these set of issues, we&rsquo;ll go over how to change <code>/etc/hosts</code> on the local client machine depending on which network it&rsquo;s connected to.</p>
<p>In this setup, we&rsquo;ll have a default <code>/etc/hosts</code> file. I&rsquo;ll show how to then <em>swap</em> it with one for a particular connection. To do this, we need a way for a script to run when NetworkManager connects or disconnects from a network.</p>
<p>Luckily, <code>NetworkManager-dispatcher</code> handles this for us. To get a complete understanding on how to write scripts for this, reference</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>man <span style="color:#ae81ff">8</span> networkmanager-dispatcher
</span></span></code></pre></div><p>In essence, scripts within <code>/etc/NetworkManger/dispatcher.d/</code> get executed in alphabetical order with two arguments set and a lot of environmental variables.</p>
<p>What we&rsquo;ll care about in our scripts are:</p>
<ul>
<li><code>$1</code> the first argument passed to the script is the interface</li>
<li><code>$2</code> the second argument refers to the <em>event</em> being triggered.
<ul>
<li>Possible options include: pre-up, up, pre-down, down, vpn-up, vpn-pre-up, vpn-pre-down, vpn-down, hostname, dhcp4-change, dhcp6-change, connectivity-change, reapply.</li>
</ul>
</li>
<li><code>$CONNECTION_UUID</code> refers to a particular connection profile in NetworkManager. This is so we know which <code>/etc/hosts</code> file to swap with which connection.</li>
</ul>
<p>Doing some quick in dirty tests, I found the following events were triggered when connecting to a particular network:</p>
<p><code> dhcp4-change -&gt; up -&gt; connectivity-change</code></p>
<p>And, the following events were triggered when disconnecting from a particular network:</p>
<p><code>connectivity-change -&gt; down</code></p>
<p>My first instinct was to use the <code>connectivity-change</code> event, however, the <code>CONNECTION_UUID</code> variable is not set for those. Instead we&rsquo;ll use the <code>up/down</code> events.</p>
<p>For our example, here&rsquo;s what our default <code>/etc/hosts/</code> file will look like:</p>
<pre tabindex="0"><code>127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

10.10.10.3  home-server.brandonrozek.com
10.10.10.4  home-desktop.brandonrozek.com
</code></pre><p>When we&rsquo;re connected to my home network, we&rsquo;ll swap my <code>/etc/hosts/</code> to look like:</p>
<pre tabindex="0"><code>127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

192.168.0.30  home-server.brandonrozek.com
192.168.0.40  home-desktop.brandonrozek.com
</code></pre><p>The following script we&rsquo;ll store at <code>/etc/NetworkManager/dispatcher.d/swap_home.sh</code> which will swap the <code>/etc/hosts</code> file with the one stored at <code>/etc/NetworkManager/hosts.home</code> when I connect to my home network.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env bash
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>interface<span style="color:#f92672">=</span>$1
</span></span><span style="display:flex;"><span>event<span style="color:#f92672">=</span>$2
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> $interface !<span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;wlp0s20f3&#34;</span> <span style="color:#f92672">]]</span> <span style="color:#f92672">||</span> <span style="color:#f92672">[[</span> $event !<span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;up&#34;</span> <span style="color:#f92672">]]</span> <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>        exit <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># This environmental variable is set on UP/DOWN events</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> $CONNECTION_UUID !<span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;901a1b68-e622-4be6-a61f-a8dc999212b3&#34;</span> <span style="color:#f92672">]]</span> <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>        exit <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cp /etc/NetworkManager/hosts.home /etc/hosts
</span></span></code></pre></div><p>In this script, you might have to replace <code>wlp0s20f3</code> to reflect the interface that you&rsquo;re using for connecting to the network. Additionally, you&rsquo;ll have to replace the <code>CONNECTION_UUID</code> with the UUID of the connection you&rsquo;re trying to swap under. You can use <code>nmcli c</code> to show the UUIDs for each of your network connections.</p>
<p>Similarly, when we disconnect from the network, we&rsquo;ll need to set it back to our default <code>/etc/hosts</code> file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env bash
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>interface<span style="color:#f92672">=</span>$1
</span></span><span style="display:flex;"><span>event<span style="color:#f92672">=</span>$2
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> $interface !<span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;wlp0s20f3&#34;</span> <span style="color:#f92672">]]</span> <span style="color:#f92672">||</span> <span style="color:#f92672">[[</span> $event !<span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;down&#34;</span> <span style="color:#f92672">]]</span> <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>        exit <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cp /etc/NetworkManager/hosts.default /etc/hosts
</span></span></code></pre></div>]]></description>
      
    </item>
    
    <item>
      <title>Simple Key-Value Store using Sqlite3</title>
      <link>https://brandonrozek.com/blog/simple-kv-store-sqlite/</link>
      <pubDate>Thu, 09 Nov 2023 22:15:23 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/simple-kv-store-sqlite/</guid>
      <description><![CDATA[<p>A lot of software nowadays are built for scale. You have to setup a Kubernetes cluster and deploy Redis for duplication in order to have a key-value store. Though for many small projects, I feel like it&rsquo;s overkill.</p>
<p>I&rsquo;ll show in this post, that we can have a nice simple<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> key-value store using <code>sqlite3</code>. This gives us the benefit that we don&rsquo;t need to use system resources to run a daemon the entire time and only spin up a process when we need it.</p>
<p>For our key-value store, we&rsquo;re going to use a table with two columns:</p>
<ul>
<li>A key, which we&rsquo;ll call <code>name</code>. This will be a unique <code>TEXT</code> type that has to be set.</li>
<li>The value, which we&rsquo;ll call <code>value</code> (Creative, I know.) For our purposes, this will also be a <code>TEXT</code> type.</li>
</ul>
<p>The SQL to create this table is</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> config(
</span></span><span style="display:flex;"><span>    name TEXT <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span> <span style="color:#66d9ef">UNIQUE</span>,
</span></span><span style="display:flex;"><span>    value TEXT
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><p>Let&rsquo;s say we want to get the value of the key <code>author</code>. This is a <code>SELECT</code> statement away:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> value <span style="color:#66d9ef">FROM</span> config <span style="color:#66d9ef">where</span> name<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;author&#39;</span>;
</span></span></code></pre></div><p>Now let&rsquo;s say that we want to insert a new key into the table.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> config(name, value) <span style="color:#66d9ef">VALUES</span> (<span style="color:#e6db74">&#39;a&#39;</span>, <span style="color:#e6db74">&#39;1&#39;</span>);
</span></span></code></pre></div><p>What about updating?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">UPDATE</span> config <span style="color:#66d9ef">SET</span> value<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;2&#39;</span> <span style="color:#66d9ef">WHERE</span> name<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;a&#39;</span>;
</span></span></code></pre></div><p>The tricky part is if we want to insert if the key does not exist, and update if it does. To handle this we&rsquo;ll need to resolve the <a href="https://www.sqlite.org/lang_conflict.html">conflict</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> config(name, value) <span style="color:#66d9ef">VALUES</span> (<span style="color:#e6db74">&#39;a&#39;</span>, <span style="color:#e6db74">&#39;3&#39;</span>) <span style="color:#66d9ef">ON</span> CONFLICT(name) <span style="color:#66d9ef">DO</span> <span style="color:#66d9ef">UPDATE</span> <span style="color:#66d9ef">SET</span> value<span style="color:#f92672">=</span>excluded.value;
</span></span></code></pre></div><p>Lastly if you want to export the entire key-value store as a CSV:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sqlite3 -header -csv data.db <span style="color:#e6db74">&#34;SELECT * FROM config;&#34;</span>
</span></span></code></pre></div><p>This is nice and all, but it&rsquo;s inconvinient to type out all these SQL commands. Therefore, I wrote two little bash scripts.</p>
<p><strong><code>sqlite3_getkv</code></strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/sh
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>set -o errexit
</span></span><span style="display:flex;"><span>set -o nounset
</span></span><span style="display:flex;"><span>set -o pipefail
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>show_usage<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    echo <span style="color:#e6db74">&#34;Usage: sqlite3_getkv [db_file] [key]&#34;</span>
</span></span><span style="display:flex;"><span>    exit <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Check argument count</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$#<span style="color:#e6db74">&#34;</span> -ne <span style="color:#ae81ff">2</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    show_usage
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Initalize database file is not already</span>
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;CREATE TABLE IF NOT EXISTS config(name TEXT NOT NULL UNIQUE, value TEXT);&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Get value from key</span>
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;SELECT value FROM CONFIG where name=&#39;</span>$2<span style="color:#e6db74">&#39;;&#34;</span>
</span></span></code></pre></div><p><strong><code>ssqlite3_setkv</code></strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/sh
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>set -o errexit
</span></span><span style="display:flex;"><span>set -o nounset
</span></span><span style="display:flex;"><span>set -o pipefail
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>show_usage<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    echo <span style="color:#e6db74">&#34;Usage: sqlite3_setkv [db_file] [key] [value]&#34;</span>
</span></span><span style="display:flex;"><span>    exit <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Check argument count</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$#<span style="color:#e6db74">&#34;</span> -ne <span style="color:#ae81ff">3</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    show_usage
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Initalize database file is not already</span>
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;CREATE TABLE IF NOT EXISTS config(name TEXT NOT NULL UNIQUE, value TEXT);&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Set key-value pair</span>
</span></span><span style="display:flex;"><span>sqlite3 <span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;INSERT INTO config(name, value) VALUES (&#39;</span>$2<span style="color:#e6db74">&#39;, &#39;</span>$3<span style="color:#e6db74">&#39;) ON CONFLICT(name) DO UPDATE SET value=excluded.value;&#34;</span>
</span></span></code></pre></div><p><strong>Example Usage:</strong></p>
<pre tabindex="0"><code>$ ./sqlite3_setkv.sh test.db a 4
$ ./sqlite3_setkv.sh test.db c 5
$ ./sqlite3_getkv.sh test.db a
4
$ ./sqlite3_setkv.sh test.db a 5
$ ./sqlite3_getkv.sh test.db a
5
</code></pre><div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Somehow my idea of easier, simpler, and more maintainable is writing bash scripts.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <category>DB</category>
      
    </item>
    
    <item>
      <title>Transcoding Plex Video in RAM</title>
      <link>https://brandonrozek.com/blog/plex-ram-transcode/</link>
      <pubDate>Thu, 09 Nov 2023 21:58:55 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/plex-ram-transcode/</guid>
      <description><![CDATA[<p>One not so secret trick I recently learned is that you can have Plex transcode video in RAM to speed it up. This is especially useful if like me, your server runs on spinning rust hard drives and you have many GBs of RAM to spare.</p>
<p><strong>Step 1:</strong> Set a transcode location in Plex.</p>
<p>Log into your admin account. Then go to Settings -&gt; Transcoder.</p>
<p>From there select the &ldquo;Show Advanced&rdquo; button and scroll down to the &ldquo;Set Transcoder temporary directory&rdquo;.</p>
<p>I&rsquo;ve set it to <code>/transcode</code> and then hit the Save button.</p>
<p><strong>Step 2: Setup <code>tmpfs</code></strong></p>
<p>We can use <code>tmpfs</code> to setup a filesystem that will be stored in RAM at the transcode directory. If you have Plex installed not in a container, then you can follow the <a href="https://wiki.archlinux.org/title/Tmpfs">Arch Wiki Instructions</a> for setting it up.</p>
<p>I use <code>docker-compose</code> for my setup, so it involved adding some limes to my yaml.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>  <span style="color:#f92672">plex</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">image</span>: <span style="color:#ae81ff">lscr.io/linuxserver/plex:1.32.5</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># ...</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">tmpfs</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">/transcode</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># ...</span>
</span></span></code></pre></div><p>I don&rsquo;t promise massive performance increase by setting up Plex this way, but I do think it makes sense:</p>
<ul>
<li>Improved write speeds for the transcoded files</li>
<li>The transcoded files aren&rsquo;t persistent</li>
</ul>
<p>If you&rsquo;re of a different opinion, let me know.</p>
]]></description>
      
    </item>
    
    <item>
      <title>Figuring out which git repositories are ahead or behind</title>
      <link>https://brandonrozek.com/blog/ahead-behind-git/</link>
      <pubDate>Thu, 09 Nov 2023 21:05:34 -0500</pubDate>
      
      <guid>https://brandonrozek.com/blog/ahead-behind-git/</guid>
      <description><![CDATA[<p>More often than I care to admit, I would pick up to do work on a device only to realize that I&rsquo;m working with an older version of the codebase. I could use the <code>git status</code> command, but the output is verbose and stale if you haven&rsquo;t <code>git fetch/pull</code>&rsquo;d.</p>
<p>I keep the majority of my git repositories in the folder <code>~/repo/</code> on all my devices. Inspired by a recent <a href="https://claytonerrington.com/blog/git-status/">blog post by Clayton Errington</a>, I wanted a way to quickly check within a folder which repositories need updating.  Their blog post has a script written in PowerShell. I decided to write my own bash implementation, and also ignore the bit about modified files since I mostly care about the state of my commits with respect to the <code>origin</code> remote.</p>
<p>Before writing a recursive implementation, let&rsquo;s first discuss how to check the ahead/behind status for a single repository.</p>
<p>First things first, you need to make sure that we have all the references from the remote.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git remote update
</span></span></code></pre></div><p>To print out how many commits the local <code>main</code> branch is ahead of the one located on the <code>origin</code> remote we can use:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git rev-list --count origin/main..main
</span></span></code></pre></div><p>Similarly for checking how many commits the local <code>main</code> branch is behind we can use:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git rev-list --count main..origin/main
</span></span></code></pre></div><p>Instead of looking at the <code>main</code> branch, maybe we can to check whichever branch we&rsquo;re currently at.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>branch<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>git rev-parse --abbrev-ref HEAD<span style="color:#66d9ef">)</span>
</span></span></code></pre></div><p>We can wrap all of this into a nice bash function. We&rsquo;ll additionally check if there is a <code>.git</code> in the current folder as none of the git commands will work without it.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>check_remote<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> -d .git <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    git remote update &gt; /dev/null 2&gt; /dev/null
</span></span><span style="display:flex;"><span>    branch<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>git rev-parse --abbrev-ref HEAD<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>    ahead<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>git rev-list --count origin/$branch..$branch<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>    behind<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>git rev-list --count $branch..origin/$branch<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>    echo <span style="color:#e6db74">&#34;</span>$ahead<span style="color:#e6db74"> commits ahead, </span>$behind<span style="color:#e6db74"> commits behind&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div><p>I currently have 15 repositories in my <code>~/repo</code> folder. Now I can <code>cd</code> into each of them and run this bash function. Or, I can have <code>bash</code> do it for me :)</p>
<p>Let&rsquo;s write a function called <code>process</code> that does just that. We&rsquo;ll pass in a folder as an argument stored in <code>$1</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>process<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> -d <span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">/.git&#34;</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    pushd <span style="color:#e6db74">&#34;</span>$PWD<span style="color:#e6db74">&#34;</span> &gt; /dev/null
</span></span><span style="display:flex;"><span>    cd <span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>    echo -n <span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74"> &#34;</span>
</span></span><span style="display:flex;"><span>    check_remote
</span></span><span style="display:flex;"><span>    popd &gt; /dev/null
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div><p>The <code>pushd</code> command will keep track of the folder that we&rsquo;re currently in. Then we <code>cd</code> into the directory that has <code>.git</code> folder. Print the name of the folder so we can associate the ahead/behind counts, and then run the <code>check_remote</code> function. Lastly we <code>popd</code> back to the folder we started from.</p>
<p>All that&rsquo;s left is to get the list of folders to process:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find . -type d -print0
</span></span></code></pre></div><p>Feed it into a <code>while read</code> loop passing in each folder into the <code>process</code> function.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find . -type d -print0 | <span style="color:#66d9ef">while</span> read -d <span style="color:#e6db74">$&#39;\0&#39;</span> folder
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>  process $folder
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span></code></pre></div><p>All together the script will look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>set -o errexit
</span></span><span style="display:flex;"><span>set -o nounset
</span></span><span style="display:flex;"><span>set -o pipefail
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>show_usage<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  echo <span style="color:#e6db74">&#34;Usage: git-remote-status [-R]&#34;</span>
</span></span><span style="display:flex;"><span>  exit <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>check_remote<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> -d .git <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    git remote update &gt; /dev/null 2&gt; /dev/null
</span></span><span style="display:flex;"><span>    branch<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>git rev-parse --abbrev-ref HEAD<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>    ahead<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>git rev-list --count origin/$branch..$branch<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>    behind<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>git rev-list --count $branch..origin/$branch<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>    echo <span style="color:#e6db74">&#34;</span>$ahead<span style="color:#e6db74"> commits ahead, </span>$behind<span style="color:#e6db74"> commits behind&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$#<span style="color:#e6db74">&#34;</span> -eq <span style="color:#ae81ff">0</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>  check_remote
</span></span><span style="display:flex;"><span>  exit <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span> !<span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;-R&#34;</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span> 
</span></span><span style="display:flex;"><span>  show_usage
</span></span><span style="display:flex;"><span>  exit <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>process<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> -d <span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">/.git&#34;</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    pushd <span style="color:#e6db74">&#34;</span>$PWD<span style="color:#e6db74">&#34;</span> &gt; /dev/null
</span></span><span style="display:flex;"><span>    cd <span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>    echo -n <span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74"> &#34;</span>
</span></span><span style="display:flex;"><span>    check_remote
</span></span><span style="display:flex;"><span>    popd &gt; /dev/null
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>export -f process
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>find . -type d -print0 | <span style="color:#66d9ef">while</span> read -d <span style="color:#e6db74">$&#39;\0&#39;</span> folder
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>  process $folder
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span></code></pre></div><p>This gives us two options. If we pass in no flags, then it&rsquo;ll print out the ahead/behind status for the current folder. If we pass in <code>-R</code>, then we recursively check all the subfolders as well.</p>
<p>Example Output of <code>git-remote-status -R</code>:</p>
<pre tabindex="0"><code>./project1 0 commits ahead, 3 commits behind
./project2 1 commits ahead, 0 commits behind
./project3 1 commits ahead, 2 commits behind
./project4 0 commits ahead, 0 commits behind
./project5 0 commits ahead, 0 commits behind
</code></pre>]]></description>
      <category>Git</category>
      
    </item>
    
    <item>
      <title>Quickly configuring and testing the Mosquitto MQTT broker</title>
      <link>https://brandonrozek.com/blog/test-mqtt/</link>
      <pubDate>Mon, 30 Oct 2023 01:01:57 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/test-mqtt/</guid>
      <description><![CDATA[<p>I&rsquo;ve been playing with Tasmota smart devices recently; and to hook them up with home assistant, they both need to be configured to utilize MQTT. In this post, we&rsquo;ll only focus on the MQTT part. From quickly installing it to making sure publish/subscribe messages work on another machine.</p>
<h2 id="installing-mqtt-broker">Installing MQTT Broker</h2>
<p>On the server you want to install MQTT on:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo dnf install mosquitto
</span></span></code></pre></div><p>We won&rsquo;t go over how to setup TLS or authenticated users. Instead for a quick test, we&rsquo;ll configure it to allow for anonymous connections over any interface.</p>
<p>Add the following lines to <code>/etc/mosquitto/mosquitto.conf</code></p>
<pre tabindex="0"><code>listener 1883
allow_anonymous true
</code></pre><p>Then enable the systemd service to start mosquitto</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl enable --now mosquitto
</span></span></code></pre></div><p>Make sure it&rsquo;s running:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl status mosquitto
</span></span></code></pre></div><h2 id="testing-the-broker">Testing the Broker</h2>
<p>Most tutorials have you test the publish/subscribe on the local machine. Though given we&rsquo;re working with smart devices, we need to make sure it works on another machine first.</p>
<p><strong>Approach 1:</strong> Install the mosquitto tools on that machine. Then you&rsquo;ll need to open two terminals.</p>
<ul>
<li>On terminal 1, subscribe to all messages <code>mosquitto_sub -h [MQTT_BROKER_ADDR] -t &quot;#&quot; </code></li>
<li>On terminal 2, publish a message: <code>mosquitto_pub -h [MQTT_BROKER_ADDR] -t &quot;test&quot;  -m &quot;Hello, World&quot;</code></li>
</ul>
<p>On the first terminal, you should see the string <code>Hello, World</code>.</p>
<p><strong>Approach 2:</strong> Use curl.</p>
<ul>
<li>On terminal 1, subscribe to all messages with <code>curl mqtt://[MQTT_BROKER_ADDR]/%23 --OUTPUT - --trace -</code></li>
<li>On terminal 2, publish a message <code>curl -d 'Hello, World'  mqtt://[MQTT_BROKER_ADDR]/test/</code></li>
</ul>
<p>Similarly, you should see the string <code>Hello, World</code> along with a bunch of debugging information.</p>
]]></description>
      
    </item>
    
    <item>
      <title>Emoji Kitchen: Combinations of Emojis</title>
      <link>https://brandonrozek.com/blog/emoji-kitchen/</link>
      <pubDate>Thu, 19 Oct 2023 23:56:48 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/emoji-kitchen/</guid>
      <description><![CDATA[<p>Clare mentioned to me today that Google keyboard has combination emojis. Intrigued, I checked it out. Not all emojis are supported, but a lot of the facial expression ones are. Select a supported emoji, and you&rsquo;ll be presented with different combination results.</p>
<p><img src="/files/images/blog/20231020000035.jpg" alt="Image of Google Keyboard"></p>
<p>Google calls this <em>Emoji Kitchen</em>. <a href="https://xsalazar.com/">Xavier Salazar</a> created a <a href="https://emojikitchen.dev/">nice website to visualize all the combinations</a>.   These emojis are not part of any text encoding standard, but instead are image files.</p>
<p>Now it&rsquo;s too hard for me to keep track of all these emojis. However, if there&rsquo;s one series that particularly resonates with me. I would say it&rsquo;s the turtle. Just look how awesome he is.</p>
<p><img src="/files/images/blog/turtles.webp" alt="12 Turtle Emojis"></p>
]]></description>
      
    </item>
    
    <item>
      <title>Obtaining a IPv6 Address via Hurricane Electric&#39;s Tunnel Broker Service</title>
      <link>https://brandonrozek.com/blog/obtaining-ipv6-address-hurricane-electric/</link>
      <pubDate>Mon, 09 Oct 2023 22:31:10 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/obtaining-ipv6-address-hurricane-electric/</guid>
      <description><![CDATA[<p>Lately, I have come across a virtual private server that only has a IPv4 address and not a IPv6 address. Since all regional Internet registries have <a href="https://en.wikipedia.org/wiki/IPv4_address_exhaustion">exhausted their IPv4 address pools since 2019</a>, I wanted to find a way to route IPv6 traffic to that server.</p>
<p>Luckily, Hurricane Electric provides a free IPv6 tunnel broker service. The <a href="https://wiki.archlinux.org/title/IPv6_tunnel_broker_setup">Arch Linux Wiki</a> has a great guide on how to set it up. This post will start off with setting up the service, and we&rsquo;ll discuss a couple bits of consideration we need to take to get IPv6 traffic to a webserver running in a docker/podman container.</p>
<p>Hurricane Electric is a Tier 2 Internet service provider. This means that they <a href="https://en.wikipedia.org/wiki/Peering">peer</a> with other Internet service providers to route network traffic with. Since at <a href="https://forums.he.net/index.php?topic=783.0">least 2010</a>, Hurricane Electric has offered a <a href="https://www.tunnelbroker.net/">free IPv6 tunnel</a>.   This gives me confidence that they should at least continue to offer this service in the near future. In order to route IPv6 traffic to your IPv4 server, they make use of the the <a href="https://en.wikipedia.org/wiki/6in4">6in4 tunneling protocol</a>. This encapsulates the IPv6 packet with a IPv4 header.</p>
<p>To get setup, create on account on <a href="https://tunnelbroker.net">https://tunnelbroker.net</a>. After verifying your email<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, login and click on &ldquo;Create Regular Tunnel&rdquo;. From there it will ask you for the IPv4 address of your server. Insert that and then click the tunnel server that is the closest geographically to where the server is located.</p>
<p>Once the tunnel is created, you&rsquo;ll need to take note of four addresses within the &ldquo;IPv6 Tunnel Endpoints&rdquo; section:</p>
<ul>
<li>Server IPv4 Address</li>
<li>Server IPv6 Address</li>
<li>Client IPv4 Address</li>
<li>Client IPv6 Address</li>
</ul>
<p>In terms of configuring the networking on the server, I found the systemd script on the Arch Wiki the most straightforward setup. Within <code>/etc/systemd/system/he-ipv6.service</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[Unit]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Description</span><span style="color:#f92672">=</span><span style="color:#e6db74">he.net IPv6 tunnel</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">After</span><span style="color:#f92672">=</span><span style="color:#e6db74">network.target</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Service]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Type</span><span style="color:#f92672">=</span><span style="color:#e6db74">oneshot</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">RemainAfterExit</span><span style="color:#f92672">=</span><span style="color:#e6db74">yes</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStart</span><span style="color:#f92672">=</span><span style="color:#e6db74">/usr/bin/ip tunnel add he-ipv6 mode sit remote server_IPv4_address local client_IPv4_address ttl 255</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStart</span><span style="color:#f92672">=</span><span style="color:#e6db74">/usr/bin/ip link set he-ipv6 up mtu 1480</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStart</span><span style="color:#f92672">=</span><span style="color:#e6db74">/usr/bin/ip addr add client_IPv6_address dev he-ipv6</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStart</span><span style="color:#f92672">=</span><span style="color:#e6db74">/usr/bin/ip -6 route add ::/0 dev he-ipv6</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStop</span><span style="color:#f92672">=</span><span style="color:#e6db74">/usr/bin/ip -6 route del ::/0 dev he-ipv6</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStop</span><span style="color:#f92672">=</span><span style="color:#e6db74">/usr/bin/ip link set he-ipv6 down</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStop</span><span style="color:#f92672">=</span><span style="color:#e6db74">/usr/bin/ip tunnel del he-ipv6</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Install]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">WantedBy</span><span style="color:#f92672">=</span><span style="color:#e6db74">multi-user.target</span>
</span></span></code></pre></div><p>Then enable/start the service with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl enable --now he-ipv6.service
</span></span></code></pre></div><p>With this, your server should now be IPv6 routable. You can test this by trying to ping your server from another IPv6 enabled device.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ping client_ipv6_address
</span></span></code></pre></div><p>If that works, next you&rsquo;ll have to make sure that <a href="https://brandonrozek.com/blog/podman-nginx-tcpv6-http2-ready/">docker/podman and nginx are configured to accept TCPv6 traffic</a>. Finally, since the tunnel is on a different network interface, you may need to allow routing at the firewall level.</p>
<p>For example, to allow from traffic between the podman and tunnel interfaces:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ufw route allow in on he-ipv6 out on podman1
</span></span><span style="display:flex;"><span>sudo ufw route allow in on podman1 out on he-ipv6
</span></span></code></pre></div><div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>A reader reached out to let me know that Hurricane Electric does not
support @outlook.com or @gmail.com addresses for the email.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <category>Networking</category>
      
    </item>
    
    <item>
      <title>Hiding Answers in Lecture Slides using Latex Beamer</title>
      <link>https://brandonrozek.com/blog/hiding-answers-lecture-slides-latex-beamer/</link>
      <pubDate>Mon, 09 Oct 2023 11:40:36 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/hiding-answers-lecture-slides-latex-beamer/</guid>
      <description><![CDATA[<p>Asking questions is a great way to try to solicitate engagement from students. However, it can feel at odds with trying to provide a fully comprehensive slide deck at times. Similar to having notes <a href="/blog/notes-beamer-latex/">hidden for yourself</a>, we should also have a way to hide answers for the students of the present but not the students of the future.</p>
<p>LaTex beamer has the <code>\invisible</code> command which holds the space for the text but doesn&rsquo;t display it. We can create a new command with an if statement stating whether or not we want to use <code>\invisible</code>.</p>
<p>For this, I called the variable <code>showanswer</code>, but you can call it whatever you like. Put the following code in the preamble of your beamer document.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-latex" data-lang="latex"><span style="display:flex;"><span><span style="color:#66d9ef">\newif\ifshowanswer</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">%\showanswertrue % Uncomment when you want to show the answer
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">\newcommand</span>{<span style="color:#66d9ef">\HideAns</span>}[1]{<span style="color:#66d9ef">\unless\ifshowanswer</span> <span style="color:#66d9ef">\invisible</span>{#1} <span style="color:#66d9ef">\else</span> #1 <span style="color:#66d9ef">\fi</span>}
</span></span></code></pre></div><p>To write <code>if not</code> in LaTex, we need to use <code>\unless\if</code>.</p>
<p>Now let&rsquo;s see this in action. Here is a slide I gave in a recent lecture about Unification:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-latex" data-lang="latex"><span style="display:flex;"><span><span style="color:#66d9ef">\begin</span>{frame}{Examples}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">\begin</span>{enumerate}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\item</span> <span style="color:#e6db74">$</span>f<span style="color:#f92672">(</span>x, y<span style="color:#f92672">)</span> \eq f<span style="color:#f92672">(</span>g<span style="color:#f92672">(</span>x<span style="color:#f92672">)</span>, b<span style="color:#f92672">)</span><span style="color:#e6db74">$</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\begin</span>{itemize}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">\item</span> Answer: <span style="color:#66d9ef">\HideAns</span>{None - Occurs Check}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\end</span>{itemize}
</span></span><span style="display:flex;"><span>		
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\item</span> <span style="color:#e6db74">$</span>f<span style="color:#f92672">(</span>x, a<span style="color:#f92672">)</span> \eq f<span style="color:#f92672">(</span>a, b<span style="color:#f92672">)</span><span style="color:#e6db74">$</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\begin</span>{itemize}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">\item</span> Answer: <span style="color:#66d9ef">\HideAns</span>{None - Function Clash}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\end</span>{itemize}
</span></span><span style="display:flex;"><span>		
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\item</span> <span style="color:#e6db74">$</span>g<span style="color:#f92672">(</span>x<span style="color:#f92672">)</span> \eq g<span style="color:#f92672">(</span>y<span style="color:#f92672">)</span><span style="color:#e6db74">$</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\begin</span>{itemize}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">\item</span> Answer: <span style="color:#66d9ef">\HideAns</span>{<span style="color:#e6db74">$</span>x \mapsto y<span style="color:#e6db74">$</span>}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\end</span>{itemize}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">\end</span>{enumerate}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">\end</span>{frame}
</span></span></code></pre></div><p>The left shows the hidden version, and the right shows the version I can distribute afterwards.</p>
<p><img src="/files/images/blog/20231009120041.png" alt="Screenshot showing example with and without hidden answers"></p>
<p>Minimal Working Example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-latex" data-lang="latex"><span style="display:flex;"><span><span style="color:#66d9ef">\documentclass</span>{beamer}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">\usetheme</span>{Copenhagen}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">\usepackage</span><span style="color:#a6e22e">[utf8]</span>{inputenc}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">\newcommand</span>{<span style="color:#66d9ef">\eq</span>}{<span style="color:#66d9ef">\stackrel</span>{?}{=}}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">\newif\ifshowanswer</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">% \showanswertrue % Uncomment when you want to show the answer
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">\newcommand</span>{<span style="color:#66d9ef">\HideAns</span>}[1]{<span style="color:#66d9ef">\unless\ifshowanswer</span> <span style="color:#66d9ef">\invisible</span>{#1} <span style="color:#66d9ef">\else</span> #1 <span style="color:#66d9ef">\fi</span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">\begin</span>{document}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">\begin</span>{frame}{Examples}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">\begin</span>{enumerate}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\item</span> <span style="color:#e6db74">$</span>f<span style="color:#f92672">(</span>x, y<span style="color:#f92672">)</span> \eq f<span style="color:#f92672">(</span>g<span style="color:#f92672">(</span>x<span style="color:#f92672">)</span>, b<span style="color:#f92672">)</span><span style="color:#e6db74">$</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\begin</span>{itemize}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">\item</span> Answer: <span style="color:#66d9ef">\HideAns</span>{None - Occurs Check}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\end</span>{itemize}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\item</span> <span style="color:#e6db74">$</span>f<span style="color:#f92672">(</span>x, a<span style="color:#f92672">)</span> \eq f<span style="color:#f92672">(</span>a, b<span style="color:#f92672">)</span><span style="color:#e6db74">$</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\begin</span>{itemize}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">\item</span> Answer: <span style="color:#66d9ef">\HideAns</span>{None - Function Clash}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\end</span>{itemize}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\item</span> <span style="color:#e6db74">$</span>g<span style="color:#f92672">(</span>x<span style="color:#f92672">)</span> \eq g<span style="color:#f92672">(</span>y<span style="color:#f92672">)</span><span style="color:#e6db74">$</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\begin</span>{itemize}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">\item</span> Answer: <span style="color:#66d9ef">\HideAns</span>{<span style="color:#e6db74">$</span>x \mapsto y<span style="color:#e6db74">$</span>}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">\end</span>{itemize}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">\end</span>{enumerate}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">\end</span>{frame}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">\end</span>{document}
</span></span></code></pre></div>]]></description>
      <category>LaTex</category>
      
    </item>
    
    <item>
      <title>Use block storage for your application data</title>
      <link>https://brandonrozek.com/blog/block-storage-application-data/</link>
      <pubDate>Mon, 02 Oct 2023 23:40:07 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/block-storage-application-data/</guid>
      <description><![CDATA[<p>One day I was upgrading my server from Fedora 37 to 38 and the thought came to my head.</p>
<blockquote>
<p>What if this installation fails and I need to recreate the server?</p>
</blockquote>
<p>Luckily that didn&rsquo;t come to pass, but the thought was enough to scare me into learning <a href="https://www.ansible.com/">Ansible</a>.</p>
<p>Honestly, I&rsquo;ve been skeptical of it for a while. I thought that having bash automation scripts was good enough and that Ansible will come and go.</p>
<p>Except, that it&rsquo;s been around for the last 11 years&hellip;</p>
<p>Consider me converted! My favorite module is <a href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/lineinfile_module.html"><code>lineinfile</code></a>. It&rsquo;s all too common in Linux for setup instructions to contain &ldquo;edit x line in y file.&rdquo;</p>
<p>Now, I have Playbooks written for configuring my server, but what about the data?</p>
<p>I was chatting with my good friend Stefano Coronado and he suggested using block volumes. Most VPS providers offer this feature and its persistent data storage that can be attached to one or more servers within the same data center.</p>
<p>This of course does not substitute having good backups, but having separation between configuration and data is a great idea.</p>
<p>Lets say that I have block storage mounted on <code>/mnt/storage</code>.</p>
<p>It&rsquo;s easy to point directly to it within docker-compose:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#f92672">database</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">image</span>: <span style="color:#ae81ff">docker.io/mongo:5.0</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">volumes</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">/mnt/storage/database/data/db:/data/db</span>
</span></span></code></pre></div><p>If you&rsquo;re using an application outside of docker and is not easily configurable, we can make use of symlinks.</p>
<p>For example, if an application is expecting configuration to be at <code>/etc/special</code> then:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ln -s /mnt/storage/special /etc/special
</span></span></code></pre></div><p>sets up a symlink at <code>/etc/special</code> to point at <code>/mnt/storage/special</code>.</p>
<p>Learn more on how to specifically setup and mount a block volume depending on your hosting provider:</p>
<ul>
<li><a href="https://www.linode.com/docs/products/storage/block-storage/">Linode</a></li>
<li><a href="https://docs.digitalocean.com/products/volumes/">DigitalOcean</a></li>
<li><a href="https://www.vultr.com/docs/block-storage">Vultr</a></li>
<li><a href="https://aws.amazon.com/ebs/">AWS</a></li>
</ul>
]]></description>
      
    </item>
    
    <item>
      <title>How to set up a private Syncthing network</title>
      <link>https://brandonrozek.com/blog/private-syncthing-network/</link>
      <pubDate>Tue, 19 Sep 2023 22:54:50 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/private-syncthing-network/</guid>
      <description><![CDATA[<p><a href="https://syncthing.net/">Syncthing</a> is a peer to peer file synchronization program that I&rsquo;ve been using for many years. I&rsquo;ve never had any issues with it and I consider it rock solid in stability.</p>
<p>Each device is given a unique identifier, which it can then query a discovery server for its IP address. The Syncthing foundation runs a public discovery server to facilitate this.</p>
<p>Once the IPs are known, if a direct connection can&rsquo;t be established, then Syncthing will make use of a relay. A relay only forwards encrypted packets like a router and is unable to inspect its contents.</p>
<p>The community runs a pool of public relay servers that are configured by default as well.</p>
<p>For privacy reasons, one might not want to make use of these public discovery and relay servers. Also, it may be more performant to run your own relay servers. We&rsquo;ll show how you can configure Syncthing to work without relying on these community services.</p>
<p>We&rsquo;ll go over:</p>
<ul>
<li>How to setup a private relay server</li>
<li>How to configure the clients to disable discovery and use the private relay server.</li>
</ul>
<h3 id="setting-up-the-private-relay-server">Setting up the private relay server</h3>
<p>First we&rsquo;ll need to have <code>strelaysrv</code> installed. On Fedora, this is under the <code>syncthing-tools</code> package.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo dnf install syncthing-tools
</span></span></code></pre></div><p>Then we need to make sure that port 22067 is open to TCP traffic on our firewall. Optionally, strelaysrv can expose a <code>/status</code> HTTP endpoint on port 22070, but I didn&rsquo;t enable it myself.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ufw allow 22067/tcp
</span></span></code></pre></div><p>If applicable, make sure that these are enabled in your cloud firewall.</p>
<p>When <code>strelaysrv</code> runs for the first time, it&rsquo;s going to create a set of public/private keys. We&rsquo;ll store this in <code>/etc/strelaysrv</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mkdir /etc/strelaysrv
</span></span></code></pre></div><p>To run as a daemon on startup, we&rsquo;ll write a systemd service to <code>/etc/systemd/system/strelaysrv.service</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[Unit]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Description</span><span style="color:#f92672">=</span><span style="color:#e6db74">Syncthing Relay Server</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">After</span><span style="color:#f92672">=</span><span style="color:#e6db74">network.target</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Documentation</span><span style="color:#f92672">=</span><span style="color:#e6db74">man:strelaysrv(1)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Service]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStart</span><span style="color:#f92672">=</span><span style="color:#e6db74">/usr/bin/strelaysrv -keys=/etc/strelaysrv -status-srv=&#34;&#34; -token=INSERT_TOKEN_HERE</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Hardening</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">User</span><span style="color:#f92672">=</span><span style="color:#e6db74">brandonrozek</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Group</span><span style="color:#f92672">=</span><span style="color:#e6db74">brandonrozek</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ProtectSystem</span><span style="color:#f92672">=</span><span style="color:#e6db74">strict</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ReadWritePaths</span><span style="color:#f92672">=</span><span style="color:#e6db74">/etc/strelaysrv</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">NoNewPrivileges</span><span style="color:#f92672">=</span><span style="color:#e6db74">true</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">PrivateTmp</span><span style="color:#f92672">=</span><span style="color:#e6db74">true</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">PrivateDevices</span><span style="color:#f92672">=</span><span style="color:#e6db74">true</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ProtectHome</span><span style="color:#f92672">=</span><span style="color:#e6db74">true</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">SystemCallArchitectures</span><span style="color:#f92672">=</span><span style="color:#e6db74">native</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">MemoryDenyWriteExecute</span><span style="color:#f92672">=</span><span style="color:#e6db74">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Install]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">WantedBy</span><span style="color:#f92672">=</span><span style="color:#e6db74">multi-user.target</span>
</span></span></code></pre></div><p>Let&rsquo;s discuss the flags given in <code>ExecStart</code>:</p>
<ul>
<li><code>-keys</code> indicate the location that the public/private keys will be written to or read from</li>
<li><code>-status-srv=&quot;&quot;</code> disables the status endpoint on port 22070. You can remove that flag if you want that feature.</li>
<li><code>-token=&quot;INSERT_TOKEN_HERE&quot;</code> first prevents the server from joining the public pool and requires clients to specify the token in order to use the relay.
<ul>
<li>NOTE:  Replace the token with a random output, such as <code>uuidgen</code>.</li>
</ul>
</li>
</ul>
<p>The hardening section is better left to a future blog post. In the meantime, read the <a href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html">Freedesktop documentation</a> for more information.</p>
<p>Now we can enable the service on boot and start the Syncthing relay service.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl enable --now strelaysrv.service
</span></span></code></pre></div><p>You&rsquo;ll need to note the <code>relay://</code> URL shown at <code>sudo systemctl status strelaysrv</code> for the next section.</p>
<h3 id="client-configuration">Client Configuration</h3>
<p>On the client side, we have to prevent the Syncthing service from listening to traffic from public relays and not rely on the public servers for discovery. These instructions are for the GUI interface on <code>localhost:8384</code>.</p>
<p>Lets say we have a relay at <code>relay:examplerelay.com?id=SOME-ID&amp;token=SOME-TOKEN</code></p>
<p>Change Settings-&gt;Connections-&gt;Sync Protocol Listen from <code>default</code> to:</p>
<pre tabindex="0"><code>relay:examplerelay.com:22067?id=SOME-ID&amp;token=SOME-TOKEN
</code></pre><p>If we want to allow for direct connections, then we&rsquo;ll need to have port <code>22000</code>/UDP open on the client side.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ufw allow 22000/udp
</span></span></code></pre></div><p>Then edit Settings-&gt;Connections-&gt;Sync Protocol Listen to:</p>
<pre tabindex="0"><code>quic://0.0.0.0:22000, relay://examplerelay.com:22067/?id=SOME-ID&amp;token=SOME-TOKEN
</code></pre><p>Uncheck &lsquo;Global Discovery&rsquo; within that menu. Optionally,  uncheck &ldquo;Local Discovery&rdquo; as well.</p>
<p>Local discovery only sends packets within the local network so it&rsquo;s safe to leave it enabled if desired. If you&rsquo;re worried about information leakage in a public WiFi network, then leave it disabled.</p>
<p>Since we disabled the discovery server, how do we find a connection to the different devices? Here, we can make use of the relay, and/or hard code static ip addresses that some devices may have.</p>
<p>For example, say that I have a home-server on the following IP addresses:</p>
<ul>
<li><code>192.168.1.58</code> within my home network</li>
<li><code>10.10.2.7</code> within my Wireguard network.</li>
</ul>
<p>Then under home-server-&gt;Settings-&gt;Advanced-&gt;Addresses, I can set it to:</p>
<pre tabindex="0"><code>quic://192.168.1.58, quic:10.10.2.7, relay://examplerelay.com:22067
</code></pre><p>This will try to establish a direct connection using the local and wireguard IP addresses and fallback to the relay if not possible.</p>
<h3 id="conclusion">Conclusion</h3>
<p>With these configuration changes, Syncthing no longer relies on public discovery and relay servers and instead makes use of your own relay server.</p>
<p>This helps improve privacy and perhaps even efficiency as well! If you run into any troubles during this configuration let me know and I can expand upon this post.</p>
]]></description>
      
    </item>
    
    <item>
      <title>A day in Brooklyn</title>
      <link>https://brandonrozek.com/blog/day-in-brooklyn/</link>
      <pubDate>Tue, 19 Sep 2023 21:56:19 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/day-in-brooklyn/</guid>
      <description><![CDATA[<p>Last month, my brother came to visit me. I was staying at NYC at the time, and took this as a perfect opportunity to explore around. The target on Saturday, Brooklyn.</p>
<p>Beginning from April through Octoboer, <a href="https://www.smorgasburg.com/">Smorgasburg</a> Williamsburg offers a variety of different food stalls starting in the morning. We decided to start the day there with a late breakfast.</p>
<p><img src="/files/images/blog/20230805161016.jpg" alt="Image of many tents and people at Smorgasburg Williamsburg"></p>
<p>We had some <a href="https://www.mycbao.com/">steamed bao buns</a> (bun buns), drank <a href="https://johnsjuice.com/">juice out of a hallowed out pineapple</a>, and ate <a href="https://www.instagram.com/ume.nyc/?hl=en">taiyaki</a>. There wasn&rsquo;t a lot of shaded seating available, so we ended up eating on the grass near a tree.</p>
<p>Afterwards, we walked around Williamsburg and visited a few shops.</p>
<p><img src="/files/images/blog/20230805172226.jpg" alt="Image of a indoor shopping area"></p>
<p>We then took the train to &ldquo;Down Under the <em>Manhattan</em> Bridge Overpass&rdquo; (DUMBO). There we stumbled upon an <a href="https://fadmarket.co/">art fair</a>, and took some pictures on the way to the Brooklyn bridge.</p>
<p><strong>Photo of Manhattan Bridge:</strong></p>
<p><img src="/files/images/blog/20230805194109.jpg" alt=""></p>
<p><strong>Photo of Brooklyn Bridge:</strong></p>
<p><img src="/files/images/blog/20230805194139.jpg" alt=""></p>
<p>We walked all the way to the beginning of the prominade on Tillary street to get onto the Brooklyn bridge. From where we started this resulted in a lot of unnecessary walking. If I was to do this again, I would&rsquo;ve climbed the staircase located in the <a href="https://www.brooklynbridgepark.org/places-to-see/brooklyn-bridge/">underpass of Washington street and Prospect street</a>.</p>
<p><img src="/files/images/blog/20230805205125.jpg" alt="In the middle of the bridge"></p>
<p>After crossing the bridge and reaching our 20k steps for the day, we found the nearest bench in city hall park and looked up places to eat. While doing so, we saw a biker run over the wings of a pigeon. At least for the pigeon, a little girl fed it Pringles afterwards.</p>
<p>We ended up eating at a <a href="http://hometownhotpot.com/">HotPot</a> place in the Manhattan Chinatown nearby. It&rsquo;s harder to justify shared buffet style restaurants like unlimited hot pot or sushi when it&rsquo;s just Clare and I. So how can we let go of that chance? The food turned out great and we&rsquo;re looking out for the next excuse to eat it again.</p>
<p><img src="/files/images/blog/20230805220511.jpg" alt="Photo of Hot Pot meal with boiling soup and meats and veggies on the side"></p>
]]></description>
      <category>Life</category>
      
    </item>
    
    <item>
      <title>Limiting MongoDB Resource Usage within Docker Compose</title>
      <link>https://brandonrozek.com/blog/managing-mongodb-resource-usage-docker-compose/</link>
      <pubDate>Tue, 19 Sep 2023 16:08:41 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/managing-mongodb-resource-usage-docker-compose/</guid>
      <description><![CDATA[<p>One of my web services utilizes MongoDB as the database backend. It lives on one of the smaller sized virtual private servers with only 1GB of RAM and 1 CPU. Every so often I encounter a scenario where MongoDB is taking all of the sytem resources, so I started looking into how to limit it.</p>
<p>First, we can set some limits to container in general within docker-compose.</p>
<p>Example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#f92672">mongo</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">image</span>: <span style="color:#ae81ff">docker.io/mongo:5.0</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">mem_limit</span>: <span style="color:#ae81ff">256m</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">cpus</span>: <span style="color:#ae81ff">0.25</span>
</span></span></code></pre></div><p>This says that the mongo container can only use a maximum of 256MB of memory and can only use up to 25% of one CPU.</p>
<p>To see a detailed list of possible options, check out the <a href="https://docs.docker.com/config/containers/resource_constraints/">docker documentation</a>.</p>
<p>Keep in mind that your memory and CPUs are not virtualized, therefore the container can see all the resources available, it just cannot request them.</p>
<p>This may lead to some problems depending on the codebase. For example, in <a href="https://www.mongodb.com/docs/manual/reference/configuration-options/#mongodb-setting-storage.wiredTiger.engineConfig.cacheSizeGB">MongoDB</a> the WiredTiger internal cache is by default set to 50% of the total amount of RAM minus 1GB.</p>
<p>Luckily we can change this default, by adding a flag within the docker-compose file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yml" data-lang="yml"><span style="display:flex;"><span><span style="color:#f92672">mongo</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">image</span>: <span style="color:#ae81ff">docker.io/mongo:5.0</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">mem_limit</span>: <span style="color:#ae81ff">256m</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">cpus</span>: <span style="color:#ae81ff">0.25</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">command</span>: --<span style="color:#ae81ff">wiredTigerCacheSizeGB 0.25</span>
</span></span></code></pre></div><p>The <code>wiredTigerCacheSizeGB</code> takes values between <code>0.25</code> GB and <code>10000</code> GB.</p>
]]></description>
      <category>Containers</category>
      
    </item>
    
    <item>
      <title>How to Safely Remove a DNF Repository</title>
      <link>https://brandonrozek.com/blog/how-to-safely-remove-dnf-repository/</link>
      <pubDate>Tue, 19 Sep 2023 15:49:30 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/how-to-safely-remove-dnf-repository/</guid>
      <description><![CDATA[<p>After a major upgrade of Fedora or CentOS, we can be left with an outdated third-party vendor repository. Now maybe we went ahead and added the newer version of that repository, but how do we safely remove the old one?</p>
<p>First, we need to identify the repo id that we wish to remove.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dnf repolist
</span></span></code></pre></div><p>Example output:</p>
<pre tabindex="0"><code>repo id                          repo name
fedora                           Fedora 38 - x86_64
fedora-cisco-openh264            Fedora 38 openh264 (From Cisco) - x86_64
fedora-modular                   Fedora Modular 38 - x86_64
updates                          Fedora 38 - x86_64 - Updates
updates-modular                  Fedora Modular 38 - x86_64 - Updates
ancient-repo                     Fedora 1
</code></pre><p>We can remove all the packages from the repo id <code>ancient-repo</code> with the following command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dnf repository-packages ancient-repo remove
</span></span></code></pre></div><p>Use this opportunity to double check that what it&rsquo;s going to remove is okay for your system. Some applications may not have newer versions in the other repositories, so you might have to hunt those down separately.</p>
<p>Also keep in mind, that this command will uninstall any unused dependencies from other repositories as well.</p>
<p>After removing the packages within the deprecated repository, we can remove it from the repo list.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>rm /etc/yum.repos.d/ancient-repo.repo
</span></span></code></pre></div><p>Replacing <code>ancient-repo</code> with your repo id.</p>
]]></description>
      <category>Fedora</category>
      
    </item>
    
    <item>
      <title>Type Checking Javascript Files Using Typescript</title>
      <link>https://brandonrozek.com/blog/type-checking-javascript-files-using-typescript/</link>
      <pubDate>Tue, 19 Sep 2023 15:30:02 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/type-checking-javascript-files-using-typescript/</guid>
      <description><![CDATA[<p>There&rsquo;s a nice and easy way to type check Javascript files without having to set up an entire project or adjust the code. It only takes understanding some of the flags within the Typescript compiler.</p>
<p>To install:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install -g typescript
</span></span></code></pre></div><p>Typecheck a file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>tsc --checkJs --allowJs example.js --noEmit
</span></span></code></pre></div><table>
<thead>
<tr>
<th>Flag</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--checkJs</code></td>
<td>Allow JavaScript files to be a part of your program.</td>
</tr>
<tr>
<td><code>--allowJs</code></td>
<td>Enable error reporting in type-checked JavaScript files</td>
</tr>
<tr>
<td><code>--noEmit</code></td>
<td>Disable emitting files from a compilation.</td>
</tr>
</tbody>
</table>
<p>Since <code>tsc</code> is primarily a compile, it by default produces code output.
We&rsquo;re only concerned with seeing any type errors, so we throw away
the compiled code with the flag <code>--noEmit</code>.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>Now it&rsquo;s likely that we&rsquo;re using features of the standard library or the DOM in our Javascript code. We need to include a couple flags for Typescript to understand them.</p>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--target</code></td>
<td>Set the JavaScript language version for emitted JavaScript and include compatible library declarations.</td>
</tr>
<tr>
<td><code>--lib</code></td>
<td>Specify a set of bundled library declaration files that describe the target runtime environment.</td>
</tr>
</tbody>
</table>
<p>You can see <code>tsc --help</code>for the long list of options. Here&rsquo;s an example for targetting <code>ES2022</code> with the DOM library.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>tsc --checkJs --allowJs --target es2022 --lib es2022,dom example.js --noEmit
</span></span></code></pre></div><div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>In an earlier version of this post, I had <code>--outfile /dev/null</code> instead of <code>--noEmit</code>.
<a href="https://veyndan.com/">Veyndan</a> kindly reached out and showed how the <code>--noEmit</code> flag is
more flexible in allowing for exported classes.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <category>JS</category>
      
    </item>
    
    <item>
      <title>Representing Uncertainty under the Closed World Assumption</title>
      <link>https://brandonrozek.com/blog/representing-uncertainty-closed-world-assumption/</link>
      <pubDate>Mon, 18 Sep 2023 19:01:56 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/representing-uncertainty-closed-world-assumption/</guid>
      <description><![CDATA[<p>The <a href="https://en.wikipedia.org/wiki/Closed-world_assumption">closed world assumption</a> is used in many practical applications of logic (planning, prolog, databases) in order to lower the complexity class of the reasoning algorithms used. This assumes that any predicates that are stated are true and the ones that aren&rsquo;t are false.</p>
<p>For example, given the state: <code>(color shirt1 red) (color shirt2 blue)</code>, we know that <code>(color shirt1 blue)</code> is false solely because it wasn&rsquo;t listed in the state.</p>
<p>This means that for an individual predicate $P$, it can either represent true or false. To represent uncertainty, we need to extend to the use of two predicates instead of one.</p>
<p>This approach is popular within conformant planning citing back to Palacios and Geffner in 2006. Consider our predicate $P$ for example. Instead of that predicate existing imagine we have the predicates. $K P$ and $K \neg P$. Note that $K$ is not a higher-order predicate or operator but is instead part of the predicate name. Then our truth value for $P$ is as follows:</p>
<table>
<thead>
<tr>
<th>$K P$</th>
<th>$K \neg P$</th>
<th>$P$</th>
</tr>
</thead>
<tbody>
<tr>
<td>True</td>
<td>False</td>
<td>True</td>
</tr>
<tr>
<td>False</td>
<td>True</td>
<td>False</td>
</tr>
<tr>
<td>False</td>
<td>False</td>
<td>Unknown</td>
</tr>
<tr>
<td>True</td>
<td>True</td>
<td>Contradiction</td>
</tr>
</tbody>
</table>
<p>We&rsquo;re still in the closed world assumption setting. The intuition behind this compilation is that the agent &ldquo;knows what it knows and knows what it doesn&rsquo;t know.&rdquo; This approach doesn&rsquo;t include any epistemic axions, but may be good enough to get started modeling within some of these systems.</p>
<p>This is one way of encoding uncertainty in some predicate of $P$, but not the only way. An alternative is what I call the <em>discovery encoding</em>, mainly since I haven&rsquo;t seen it in use in literature. If you have a citation with this encoding being used, please share with me.</p>
<table>
<thead>
<tr>
<th>$\mathit{P\_Value}$</th>
<th>$P\_Discovered$</th>
<th>$P$</th>
</tr>
</thead>
<tbody>
<tr>
<td>True</td>
<td>True</td>
<td>True</td>
</tr>
<tr>
<td>False</td>
<td>True</td>
<td>False</td>
</tr>
<tr>
<td>False</td>
<td>False</td>
<td>Unknown</td>
</tr>
<tr>
<td>True</td>
<td>False</td>
<td>Contradiction</td>
</tr>
</tbody>
</table>
<p>This operates very similarly to <a href="https://en.wikipedia.org/wiki/Option_type">optionals</a> within functional programming. The idea is that one shouldn&rsquo;t even consider the valuation of $P\_Value$ until $P\_Discovered$ is true. Since once $P\_Discovered$ is true, then $P\_Value = P$.</p>
<p><strong>References</strong></p>
<p>Palacios, Héctor, and Hector Geffner. &ldquo;Compiling uncertainty away:  Solving conformant planning problems using a classical planner  (sometimes).&rdquo; <em>AAAI</em>. 2006.</p>
]]></description>
      <category>Logic</category>
      
    </item>
    
    <item>
      <title>Obtaining Valid SSL Certificates within an Internal LAN</title>
      <link>https://brandonrozek.com/blog/obtaining-valid-ssl-certificates-internal-lan/</link>
      <pubDate>Mon, 18 Sep 2023 18:26:12 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/obtaining-valid-ssl-certificates-internal-lan/</guid>
      <description><![CDATA[<p>I previously wrote about a quick way to create a <a href="https://brandonrozek.com/blog/internalca/">certificate authority for an internal LAN</a>. This is for scenarios in which we don&rsquo;t want that internal network to have access to the Internet or vice-versa. The main downside of this approach, is that for a computer to trust the SSL certificate created by a machine on that LAN, they need to have the public certificate loaded and trusted on that machine. This can become a pain to manage the larger the internal network grows.</p>
<p>However, if the internal network does have access to the Internet, we can use a different tool. Letsencrypt can issue a valid SSL certificate for your network without being able to directly access your network in question! It is able to do this though the <code>DNS-01</code> challenge.</p>
<p>The way this work is that Letsencrypt asks your client to create a DNS <code>TXT</code> entry containing some special token. The client then either manually, manually with <a href="https://certbot.eff.org/docs/using.html#hooks">hooks</a>, or via a <a href="https://community.letsencrypt.org/t/dns-providers-who-easily-integrate-with-lets-encrypt-dns-validation/86438">plugin</a> edits the zone file on the DNS nameserver to add that entry. The Letsencrypt server then only needs to access that DNS nameserver in order to verify that you own the domain; Issuing your certificate upon success.</p>
<p>This is an example on how to get a certificate for an example internal domain <code>insiderexample.org</code> using the Linode DNS provider.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>certbot certonly <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  --dns-linode <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  --dns-linode-credentials ~/.secrets/certbot/linode.ini <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  -d insiderexample.org
</span></span></code></pre></div>]]></description>
      
    </item>
    
    <item>
      <title>Using the DNS-01 Challenge to obtain a wildcard certificate on Letsecnrypt</title>
      <link>https://brandonrozek.com/blog/dns-01-challenge-wildcard-domain-letsencrypt/</link>
      <pubDate>Mon, 18 Sep 2023 17:42:10 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/dns-01-challenge-wildcard-domain-letsencrypt/</guid>
      <description><![CDATA[<p>Letsencrypt lets anyone get a free SSL certificate in an easily automated way. It verifies that the user is allowed to issue a certificate for that domain by issuing a <em>challenge</em>. Lets say that I want a certificate for <code>exampledomain.com</code>. The defaults for most clients is to use the <code>HTTP-01</code> challenge. This requires that we have a webserver installed on that domain running on port 80. As part of the challenge, the Letsencrypt client will drop a file within the appropriate webserver folder so that it gets served at <code>http://exampledomain.com/.well-known/acme-challenge/&lt;token&gt;</code>. For example on <code>nginx</code> this could be at <code>/var/www/exampledomain.com/.well-known/acme-challenge/&lt;token&gt;</code>.  The Letsencrypt server will then verify that the file exists before issuing the certificate.</p>
<p>This easily works for one domain, but what if we have many sub-domains we want added to the certificate? With the <code>HTTP-01</code> challenge, we need to test each sub-domain individually. Alternatively, we can use the <code>DNS-01</code> challenge to get issued a wildcard certificate. With one wildcard certificate (e.g <code>*.exampledomain.com</code>) we can secure <code>a.exampledomain.com</code>, <code>b.exampledomain.com</code> and many more!</p>
<p>Letsecnrypt verifies that the user is allowed to claim all these subdomains, by seeing if the user has access to the DNS zone file for that domain. The idea is that if the user is able to change the DNS anyways, then the user could&rsquo;ve gone through the process of installing a webserver at that IP. With access to the DNS zone file, the user would have to create a <code>TXT</code> record.</p>
<p>Now this process could be done manually on Certbot by using the <code>--manual</code> flag. However, in the spirit of automation, there are many plugins existent to help streamline the process. This list of <a href="https://community.letsencrypt.org/t/dns-providers-who-easily-integrate-with-lets-encrypt-dns-validation/86438">DNS providers</a> shows which provider supports this feature and in what clients.</p>
<p>If your provider isn&rsquo;t supported on this list, not all hope is lost. Under the manual plugin one can use the  <a href="https://certbot.eff.org/docs/using.html#hooks">hooks</a> <code>--manual-auth-hook</code> and <code>--manual-cleanup-hook</code> to execute external scripts to access the DNS provider&rsquo;s API.</p>
<p>This wiki page for <a href="https://eff-certbot.readthedocs.io/en/stable/using.html#dns-plugins">Certbot</a> shows the list of supported providers and how to structure the command line arguments. For example, I use <code>Nginx</code> as my webserver and Linode as my DNS nameserver.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>certbot certonly <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  --dns-linode <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  --dns-linode-credentials ~/.secrets/certbot/linode.ini <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  -d *.exampledomain.com
</span></span></code></pre></div>]]></description>
      
    </item>
    
    <item>
      <title>Progressive Enhancement of Page Transitions in Hugo with htmx</title>
      <link>https://brandonrozek.com/blog/progressive-enhancement-page-transitions-hugo-htmx/</link>
      <pubDate>Fri, 21 Jul 2023 19:31:43 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/progressive-enhancement-page-transitions-hugo-htmx/</guid>
      <description><![CDATA[<p>Progressive Enhancement is a philosophy that places emphasis on making sure that a website&rsquo;s content is available to everyone. Additional functionality, such as JavaScript interactivity, are viewed as a nicety. This idea became popular around the time period when many news websites would show a blank screen if JavaScript was disabled.</p>
<p>However without JavaScript, transitioning between pages can flash a white screen in between. This can be jarring for websites using a non-white background. Single page applications attempt to resolve this by requesting webpages via JavaScript and dynamically reshuffling the page.</p>
<p>Pjax is a technique designed to marry these two ideas. <a href="https://gander.wustl.edu/~wilson/store/yui/docs/pjax/index.html">The name Pjax stands for <code>pushState</code> + <code>Ajax</code></a>. The idea is that when a person clicks a link, instead of the browser loading an entirely new page, the local  JavaScript would issue an Ajax request. Once the request is fulfilled it would replace hte current screen contents with the request and update the browser&rsquo;s history via a <code>pushState</code>. Browsers that don&rsquo;t support this feature will result in a normal full page load when the link is clicked.</p>
<p>Over the years several JavaScript libraries have popped up to fill this need. There was the classic <a href="https://clarle.github.io/yui3/yui/docs/pjax/"><code>pjax</code></a> library which handled this. Then <a href="https://intercoolerjs.org/"><code>intercooler.js</code></a> came around to allow you to specify Ajax requests via HTML attributes. Finally, <a href="https://htmx.org/"><code>htmx</code></a> extends these HTML attributes beyond Ajax and into CSS transitions and WebSockets.</p>
<p>This post will show how you can use <code>htmx</code> to easily create page transitions in a progressively enhanced way.  The quickest way to achieve this is with an attribute called <code>hx-boost</code>. This can be used by <code>&lt;a&gt;</code> tags in HTML for links and <code>&lt;form&gt;</code> tag for form submissions. By default, these two HTML elements will issue a full page load to a new URL once they are clicked. However with <code>hx-boost</code>, it will replace the full page load with a <code>ajax</code> request and replace the <code>innerHTML</code> of the HTML node&rsquo;s contents with the response.</p>
<p>Another nice feature of <code>htmx</code> is that we don&rsquo;t need to add <code>hx-boost</code> for every HTML node. Instead, this attribute can be <em>inherited</em>. This allows us to put <code>hx-boost=&quot;true&quot;</code> on the body of the document and every form and link will be boosted. If we want to simulate a page transition, instead of replacing the inner HTML of the link or form, we can replace the entire inner HTML of the body of the document using <code>hx-indicator</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">body</span> <span style="color:#a6e22e">hx-boost</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;true&#34;</span> <span style="color:#a6e22e">hx-indicator</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;body&#34;</span>&gt;
</span></span><span style="display:flex;"><span>   
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">body</span>&gt;
</span></span></code></pre></div><p>Though we have to do a little more work if we want a smooth page transition. HTMX uses classes to denote the <a href="https://htmx.org/docs/#request-operations">lifecycle of the request</a>. When an element sends off a request is, the <code>htmx-request</code> class is added to whichever node is spcified in <code>hx-indicator</code> (itself by default).  We can use this to have a simple fade out transition in the beginning of the request.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span>.<span style="color:#a6e22e">htmx-request</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">opacity</span>: <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">transition</span>: <span style="color:#66d9ef">opacity</span> <span style="color:#ae81ff">200</span><span style="color:#66d9ef">ms</span> <span style="color:#66d9ef">ease-in</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Once the AJAX request is satisfied, the <code>htmx-request</code> class is replaced with <code>htmx-swapping</code> to indicate that the content is about to be replaced.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span>.<span style="color:#a6e22e">htmx-swapping</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">opacity</span>: <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><a href="https://htmx.org/attributes/hx-swap/">Unless a swap delay is specified</a>, the content is then quickly replaced which then puts the node in a settled state. This is when we should fade in to see the new content</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span>.<span style="color:#a6e22e">htmx-settling</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">opacity</span>: <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">transition</span>: <span style="color:#66d9ef">opacity</span> <span style="color:#ae81ff">200</span><span style="color:#66d9ef">ms</span> <span style="color:#66d9ef">ease-in</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Using the htmx library, we can have nice page transitions with one line of HTML code, an optional 11 lines of CSS, and zero lines of custom JavaScrpt code. The small transitions are especially nice in low-bandwidth scenarios when a response to an ajax request isn&rsquo;t immediately received. It gives some visual indication that clicking the link still did something.</p>
]]></description>
      
    </item>
    
    <item>
      <title>Proactive Defederation, is it worth it?</title>
      <link>https://brandonrozek.com/blog/proactive-defederation/</link>
      <pubDate>Mon, 10 Jul 2023 07:56:01 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/proactive-defederation/</guid>
      <description><![CDATA[<p>Meta recently launched Threads in an effort to siphon dissatisfied Twitter users. In this process, they are rumored to pursue ActivityPub support. This would allow users on Mastodon and other Fediverse platforms to communicate with people on Threads.</p>
<p>The rumor has been circulating for a few months now, even when Threads was referred to its codename <em>Project 92</em>. As such, many people have already voiced their opinion of this move. A group of moderators even banded together to make a <a href="https://fedipact.online/">website declaring their opposition of Meta joining the Fediverse</a>.</p>
<p>The general argument is that Facebook is <em>bad</em> and <a href="https://seirdy.one/posts/2023/06/20/defederating-p92/#incompatible-values-and-complicity">has a bad track record</a>. Thus, many people from the Mastodon community want to disassociate from Meta. The tool for disassociation on the Fediverse is called <em>de-federation</em>.</p>
<p>The idea behind the Fediverse is that servers can <em>federate</em> with each other. This means that servers can share data (whether its statuses or posts) with other servers as they talk a common language. Admins of each of these servers can select which platforms they want to <em>block</em>. Refusing to display content from blocked servers is called de-federation.</p>
<p>This technique is commonly used to block content from servers that often promote illegal or offensive content. Though it can also be used in cases where servers do not align with the ethics of a community. Since Facebook &ldquo;does not match the moral compass&rdquo; of several Mastodon communities, they seek to de-federate proactively. Before any questionable content appears.</p>
<p>The hope of these admins, is that if enough servers de-federate, it effectively nulls the benefits of adding ActivityPub support. Blocking the millions of users that signed up for Threads from interacting with other Mastodon users.</p>
<p>Of course, there will be some server admins that prioritize free speech and alternative values, but we&rsquo;ll get into that later&hellip;</p>
<p>Instead of going further into the ethics of the situation. I&rsquo;m curious on the potential ramifications of Threads both sucessfully and unsucessfully federating with the majority of the Fediverse.</p>
<h2 id="successful-federation">Successful Federation</h2>
<p>Lets say somehow Threads manages to federate with enough servers, such that the majority of current Mastodon users can interact with Threads users.</p>
<p>In this world, Threads will be by far the largest user instance. This would not be too unexpected, since they have the engineers on staff to build infrastructure that can support millions of people. As a consequence, the majority of content would then be generated via Threads.</p>
<p>This content will likely not follow the cultural norms of the existing Mastodon community. The majority of these cultural norms are built with an accessibility first mindset. Examples of this include: adding captions to photos, title-casing hashtags, adding content warnings to inappropriate or sensitive topics.</p>
<p>In a world where Meta is the largest Fediverse player, they will likely try to push the ActivityPub standards to whatever their priority of the time is. There&rsquo;s a few ways this can play out, they can try to be the majority representation of the board updating the ActivityPub standard. They could also just implement features themselves and bully others to adopt and support the extensions to the standard they craft.</p>
<p>What features do I imagine Meta will want to add? I would imagine any that pertain to advertising and monetization, but we can leave that to your imagination.</p>
<h2 id="failed-federation">Failed Federation</h2>
<p>In this world, enough large instances de-federate from Threads, that most current users of Mastodon wouldn&rsquo;t see content from them.</p>
<p>The ideal in this case is that some sort of status quo is maintained and users of these differing communities do not need to interact with each other.</p>
<p>Though I&rsquo;m not sure if that&rsquo;s the case. There might be some internal resistance from Mastodon users that want to use the Fediverse to keep in touch with their friends and family. Sadly the majority of non-techie friends and family will likely be using Threads or another platform from a major player. This added pressure can either cause the de-federated servers to federate again. Alternatively, it can cause some of these users to migrate over to free speech-instances or create another account on these or on Threads itself.</p>
<p>Unfortunately for many free-speech instances, they often harbor offensive speech. The question then is: does Threads federate with these servers and then have to take many active measures to make sure the most extreme content doesn&rsquo;t surface to its users?</p>
<p>Or does it just block the free-speech instances entirely? If Threads de-federates from the free speech instances, then there wouldn&rsquo;t be enough other instances to federate with making this a failed ActivityPub  attempt.</p>
<p>There could be an attempt to fork the ActivityPub standard, but without some buy-in from the existing community, this is likely to fail.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Historically, this reminds me of when Google Talk/Hangouts supported XMPP. At the time I used Pidgin with the OTR plugin to communicate with friends and family. Sadly they henceforth abandoned XMPP support and I don&rsquo;t use much of that or Hangouts anymore.</p>
<p>Will we see a similar approach from Meta to extend, embrace, and extinguish the ActivityPub protocol?</p>
]]></description>
      
    </item>
    
    <item>
      <title>Adventures in Bird Watching</title>
      <link>https://brandonrozek.com/blog/adventures-in-bird-watching/</link>
      <pubDate>Sat, 08 Jul 2023 11:24:07 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/adventures-in-bird-watching/</guid>
      <description><![CDATA[<p>Recently for the first time I embarked on a bird watching tour. Part of the event listing contained the following:</p>
<blockquote>
<p>Don&rsquo;t forget to bring a pair of binoculars!</p>
</blockquote>
<p>Now I can buy the first pair of binoculars I find in a supermarket. Though, I always enjoy over-analyzing purchasing decisions and this isn&rsquo;t an exception.</p>
<p><strong>What considerations do you need to make when buying binoculars?</strong></p>
<p>Most pairs of binoculars report a NxM measurement.</p>
<ul>
<li>N here denotes the magnification factor</li>
<li>M denotes the diameter of the lens in millimeters</li>
</ul>
<p>This isn&rsquo;t a case where you buy the highest value in each section and call it a day. There are trade-offs:</p>
<ul>
<li>Magnification Factor
<ul>
<li>Higher means you can see farther away.</li>
<li>Lower means you have a greater field of view.</li>
</ul>
</li>
<li>Diameter Length
<ul>
<li>Higher means that more light gets captured which makes the overall image brighter.</li>
<li>Lower means that the binocular weighs less which can be important for long hikes.</li>
</ul>
</li>
</ul>
<p>For a typical bird watching session, you normally don&rsquo;t look at birds that are too far away. In this case, you want to prioritize a greater field of view which can make the viewing feel more stable. The commonly recommended magnification factor is 8.</p>
<p>Binocular weight is generally only a massive concern for people hiking long distances. If the bird watching session is a quick one, then you should prioritize having a brighter picture than a lighter binocular. The commonly recommended diameter length is 42mm.</p>
<p><em>Therefore, the commonly recommended specification for bird-watching binoculars is 8x42.</em></p>
<p>Once you&rsquo;re undergoing the bird watching session, how do you know what bird you&rsquo;re looking at? In the ideal case, you&rsquo;re already an expert and can identify it once you see it. Second best case, you have a guide that can tell you what it is. For the majority of us, however, we will have neither. In this case, we can rely on technology.</p>
<p><strong><a href="https://merlin.allaboutbirds.org/">Merlin</a> is a mobile app that can help identify birds.</strong></p>
<p>Developed by the Cornel lab of Orthnology, Merlin can provide a database of bird photos and sounds, as well as tools to help identify them.</p>
<p>If you remember before a session, you can download localized bird data. This way during the session you can look at photos and sounds of birds that are typically found nearby your location.</p>
<p>Not only do they provide a database, but the app also allows for identification by both visuals and sound. When you ask the app the identify a bird visually, it&rsquo;ll ask for your location, size of bird, colors found on the bird, and its behavior.</p>
<p>Birds often have fairly distinct calls. Merlin can also help identify based on these sounds.</p>
<p>So how did my bird watching tour go? Our group spotted the following birds in New York:</p>
<ul>
<li>Turkey Vulture</li>
<li>Cooper’s Hawk</li>
<li>Red-Shouldered Hawk</li>
<li>Red-tailed Hawk</li>
<li>Red-eyed Vireo</li>
<li>Blue Jay</li>
<li>Tree Swallow</li>
<li>Barn Swallow</li>
<li>Gray Catbird</li>
<li>Wood thrush</li>
<li>Cedar Waxwing</li>
<li>American Goldfinch</li>
<li>Song sparrow</li>
<li>Red-winged blackbird</li>
<li>Common yellowthroat</li>
<li>Yellow Warbler</li>
</ul>
]]></description>
      <category>Life</category>
      
    </item>
    
    <item>
      <title>Photograph the Ordinary Times</title>
      <link>https://brandonrozek.com/blog/photograph-ordinary-times/</link>
      <pubDate>Sat, 17 Jun 2023 22:20:50 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/photograph-ordinary-times/</guid>
      <description><![CDATA[<p>Whenever I go out I enjoy taking photos. Given how easy it is to take photos nowadays, I aim to capture the moment through more candid expressions. Not all of the photos turn out nice or flattering, but there are usually at least a few that help bring me back to that time.</p>
<p>It&rsquo;s easy to remember to take photos when a special event is going on. Perhaps you&rsquo;re visiting a location for the first time, or you are hanging out with people. What&rsquo;s harder to remember is taking photos in locations you constantly go to or around the house.</p>
<p>What is interesting about ordinary times like these? They are a dime a dozen. If I photograph my simple moments everyday, I&rsquo;ll clutter my photo library.</p>
<p>That may be true, but I argue that they at least deserve a spot in your photo library.</p>
<p>I find that my life constantly changes. It may be because I&rsquo;m still young and moving around places. But I believe this is still the case even if you settle down in a location.</p>
<p>For example, I used to go to Spillin&rsquo; the Beans in Troy, NY for breakfast every week. Now they closed down. Since I have taken a couple photos of that location, I can look back and remember my times there.</p>
<p>People come and go in our lives. Friends may move to different locations or kids move out of the house. What we might at one point taken for granted and call ordinary, may be gone the next moment.</p>
<p>We ourselves undergo change over time. Both through age and sometimes in the way we style ourselves.</p>
<p>Therefore, next time you go out and visit a coffee shop or park that you frequent. Snap a photo. Future you will thank you later for the memory.</p>
]]></description>
      
    </item>
    
    <item>
      <title>Rebalancing Portfolios: The buy low sell high of investing</title>
      <link>https://brandonrozek.com/blog/rebalancing-portfolio-buy-low-sell-high/</link>
      <pubDate>Sat, 17 Jun 2023 12:22:37 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/rebalancing-portfolio-buy-low-sell-high/</guid>
      <description><![CDATA[<p>In uncertain financial times, investments can take a temporary hit in value. A non-prudent investor may have their emotions get the better of them and start selling their positions out of fear of a further permanent drop in value. One of the core assumptions in investing is that general market valuations increase over time. Therefore, assuming that your positions are well diversified, you should not assume that valuations are <em>permanently</em> lowered.</p>
<p>How do we know when to buy and sell positions then? One primary goal is to avoiding <em>buying high and selling low</em>. There&rsquo;s a simple procedure we can follow that guarantees the opposite. That procedure is called <em>rebalancing</em>.</p>
<p>The process of rebalancing assumes that you have some fixed goal distribution of your assets. For example, let&rsquo;s say you want your portfolio to consist of 80% stocks and 20% bonds. Over time as valuations fluctuate, your portfolio will fall out of balance. For example, it can be 87% stocks and 13% bonds.</p>
<p>There could be a couple reasons this happened. The valuation of the stock positions could&rsquo;ve risen and/or the valuation of the bond positions could&rsquo;ve fallen. To get the portfolio back to the 80/20 split, we can sell off some of the stock positions and/or buy some more of the bond positions. Given the circumstances that we&rsquo;re rebalancing in, it means we&rsquo;re likely selling the stock positions at its high and/or buying bond positions at its low.</p>
<p>So when should one rebalance? There&rsquo;s not a hard-fast rule and it&rsquo;s dependent on taxation and trading fees, but generally people either follow a schedule (1-4 times a year) or rebalance when their distribution strays by more than say 5%.</p>
]]></description>
      <category>Finance</category>
      
    </item>
    
    <item>
      <title>Diversified Investing using Index Funds</title>
      <link>https://brandonrozek.com/blog/diversified-investments-index-fund/</link>
      <pubDate>Fri, 16 Jun 2023 18:50:50 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/diversified-investments-index-fund/</guid>
      <description><![CDATA[<p>When it comes to investments, there&rsquo;s a common saying:</p>
<blockquote>
<p>Diversification is the only free lunch in investing</p>
</blockquote>
<p>To diversify with individual securities, one would need to buy equities and bonds from many organizations of differing sectors and capital size. For most people this can turn out to be prohibitively expensive.</p>
<p>Mutual funds and ETFs address this problem by pooling together money from multiple parties and purchasing securities based off some stated objective. Depending on the holdings, the fund may be considered well diversified. Some funds don&rsquo;t require minimum investments which makes it easier for people to get started.</p>
<p>Not all funds are created equal however. Before investing in any fund, you should be informed and take a look at their prospectus to get an idea of their objectives and current assets before investing.</p>
<p>One way to characterize a fund is if it has an active or passive investment style. The goal of an active fund is to beat overall market trends. This means that the fund manager is often buying and selling securities to take advantage of price fluctuations. Though since it&rsquo;s neigh impossible to predict market conditions, it is common for active funds to not beat the overall market.</p>
<p>Passive funds take a more buy-and-hold type of mentality. The core assumption is that market valuations increase over time. Therefore if a fund holds a given security over a long period of time, then is likely to result in some positive return.</p>
<p>Though what specific securities do passive funds buy and hold? One common category of passive funds is indexing. This is where the fund holds securities in the same weighted proportion as reported by some index. For example the most commonly known index, the S&amp;P 500, consists of 500 of the largest stocks in the United States weighted by their market value.</p>
<p>For large-cap indexes (those that contains large companies), there is little turnover in the companies within the index. This makes it perfect for passively managed funds. The S&amp;P 500 has a historical turnover rate of 4.4%.</p>
<p>Recall the saying about diversification being a free lunch. What would be considered one of the most diversified funds? If we&rsquo;re looking at solely the US, we can consider the Dow Jones US Total Stock Market Index. This contains a market cap weighting of 4193 companies at this time of writing. That&rsquo;s 8 times larger than the S&amp;P 500.</p>
<p>Therefore, given the assumption that equities on average increase in value over time. A well diversified passive fund can be a great investment. These funds generally have lower expense ratios (fees) as not as much human attention is needed to maintain the portfolio.</p>
<p>A risk that&rsquo;s unique to some index funds is tracking error. A fund may not have enough money to purchase all securities at the reported proportions. Therefore, some will perform statistical techniques to approximate the index. Also since index funds are bound by the index they&rsquo;re following, the performance will be correlated and the fund may not take advantage of price fluctuations or attempt to lower drawdowns.</p>
<p>Disclaimer: I am not an investment analyst and this does not constitute investment advice. This information is what I&rsquo;ve learned recently and I encourage you to read up on these concepts and look at the prospectus before investing.</p>
]]></description>
      <category>Finance</category>
      
    </item>
    
    <item>
      <title>Functional Completeness</title>
      <link>https://brandonrozek.com/blog/functional-completeness/</link>
      <pubDate>Tue, 30 May 2023 07:30:08 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/functional-completeness/</guid>
      <description><![CDATA[<p>You might have heard somewhere that AND as well as NOT are the only two operators you need in propositional logic in order to represent all possible truth tables.</p>
<p>This is often used to simplify circuit design. Instead of having a custom chip for every other operator, it&rsquo;s generally more cost effective to only have two distinct chips but use more of them. But what if we want to find other combinations?</p>
<p>In formal terms, we&rsquo;re looking for other <a href="https://en.wikipedia.org/wiki/Functional_completeness">functional complete</a> sets.</p>
<p>In this post, we&rsquo;ll provide an overview for finding these sets. Finding some examples of functional complete sets along the way.</p>
<hr>
<p>The intuition here is to know that our set $\mathcal{S}$ is functionally complete if we can replicate the other operators using the ones only within $\mathcal{S}$. Let&rsquo;s refresh ourselves in what the complete set of operators are:
$$
\{\mathtt{AND}, \mathtt{OR}, \mathtt{NOT}, \mathtt{IF}, \mathtt{IFF}\}
$$
The first step would normally be to remove one operator from the above set and see if you can derive it using the other operators. For brevity of this post though, we&rsquo;ll skip sets of length four and look directly at sets of length three.</p>
<p><strong>Example 1:</strong> Show that $\mathcal{S} = \{\mathtt{AND}, \mathtt{OR}, \mathtt{NOT}\}$ is functionally complete.</p>
<p>In this example, we&rsquo;re missing both $\mathtt{IF}$ and $\mathtt{IFF}$. Using the operators of $\mathcal{S}$ we can define the missing operators to be (in prefix notation):
$$
\begin{align*}
(\mathtt{IF}~A~B) &amp;\equiv (\mathtt{OR}~(\mathtt{NOT}~A)~B) \\
(\mathtt{IFF}~A~B) &amp;\equiv (\mathtt{AND}~(\mathtt{OR}~A~B) (\mathtt{OR}~(\mathtt{NOT}~A)~ (\mathtt{NOT}~B)))
\end{align*}
$$
For now, it&rsquo;ll be beyond the scope on how to find the correct combination of operators within $\mathcal{S}$.</p>
<p>Since we&rsquo;re able to represent the missing operators in $\mathcal{S}$ the set is functionally complete.</p>
<p><strong>Example 2:</strong> Show that $\mathcal{S} = \{\mathtt{NOT},  \mathtt{AND}, \mathtt{IF}\}$ is functionally complete.</p>
<p>We have to show that we can represent the missing operators $\mathtt{OR}$ and $\mathtt{IFF}$ using only elements of $\mathcal{S}$.
$$
\begin{align*}
(\mathtt{IFF}~A~B) &amp;\equiv (\mathtt{AND}~ (\mathtt{IF}~ A~ B)~ (\mathtt{IF}~ B~ A)) \\
(\mathtt{OR}~ A~ B) &amp;\equiv (\mathtt{NOT}~ (\mathtt{AND}~ (\mathtt{NOT}~ A)~ (\mathtt{NOT}~ B)))
\end{align*}
$$
This shows that the set $\mathcal{S}$ is functionally complete.</p>
<p>At this point we know that $\{\mathtt{AND}, \mathtt{OR}, \mathtt{NOT}\}$ and $\{\mathtt{NOT},  \mathtt{AND}, \mathtt{IF}\}$ are functionally complete. Now comes the exciting part, finding functionally complete sets with only two operators.</p>
<ul>
<li>What knowledge can we derive from $\{\mathtt{AND}, \mathtt{OR}, \mathtt{NOT}\}$?</li>
</ul>
<p>Let&rsquo;s consider all possible sets of length two:
$$
\{\mathtt{AND}, \mathtt{NOT}\} \\
\{\mathtt{AND}, \mathtt{OR}\} \\
\{\mathtt{OR}, \mathtt{NOT}\}
$$
<strong>Example 3:</strong> Is ${\mathtt{AND}, \mathtt{NOT}}$ functionally complete?</p>
<p>We need to show that we can derive $\mathtt{OR}$.
$$
(\mathtt{OR}~ A~ B) \equiv (\texttt{NOT}~ (\texttt{AND}~ (\texttt{NOT}~ A)~ (\texttt{NOT}~ B)))
$$
This means using only $\mathtt{AND}$ and $\mathtt{NOT}$ we can represent $\{\mathtt{AND}, \mathtt{OR}, \mathtt{NOT}\}$ which is functionally complete. Therefore, $\mathcal{S} = \{ \mathtt{AND}, \mathtt{NOT}\}$ is functionally complete.</p>
<p><strong>Example 4:</strong> Is $\{\mathtt{AND}, \mathtt{OR}\}$ functionally complete?</p>
<p>No, because we cannot represent $\mathtt{NOT}$ using both $\mathtt{AND}$ and/or $\mathtt{OR}$. In a future iteration of this point I might describe why this is the case.</p>
<p><strong>Example 5:</strong> Is $\mathcal{S} = \{\mathtt{OR}, \mathtt{NOT}\}$ functionally complete?</p>
<p>We need to show that we can derive $\mathtt{AND}$.
$$
(\mathtt{AND}~ A~ B) \equiv (\texttt{NOT}~ (\texttt{OR}~ (\texttt{NOT}~ A)~ (\texttt{NOT} ~B))
$$
Therefore, $\mathcal{S}$ is functionally complete.</p>
<p>Similarly, we can look at $\{\mathtt{NOT}, \mathtt{AND}, \mathtt{IF}\}$ and consider pairs of operators that might still be functionally complete.</p>
<p><strong>Example 6:</strong> Is $\{\mathtt{AND}, \mathtt{IF}\}$ functionally complete?</p>
<p>No, because you cannot represent $\mathtt{NOT}$.</p>
<p><strong>Example 7:</strong> Is $\{\mathtt{NOT}, \mathtt{IF}\}$ functionally complete?</p>
<p>Here we&rsquo;re missing $\mathtt{AND}$, so if we can derive it using the two operators, then the set is functionally complete.
$$
(\mathtt{AND}~ A~ B) \equiv (\mathtt{NOT}~ (\mathtt{IF}~ A~ (\mathtt{NOT}~ B)))
$$</p>
<hr>
<p>There are no single operators that are functionally complete in this example. Though the above gives somewhat of a procedure for finding all the sets of functionally complete operators. Here&rsquo;s a summary:</p>
<ol>
<li>Start with your base set of operators $\mathcal{S} = {x_1, x_2, \dots, x_n}$</li>
<li>Remove $x \in \mathcal{S}$ such that $\mathcal{S}^\prime = \mathcal{S} - {x}$</li>
<li>Derive $x$ using the other operators in $\mathcal{S}^\prime$.</li>
<li>If possible, add $\mathcal{S}^\prime$ to $\mathcal{S}_{n - 1}$ the set of functionally complete operators of length $n - 1$</li>
<li>Repeat steps (2) and (3) on each $\mathcal{S}^\prime \in \mathcal{S}_{n - 1}$ to create $\mathcal{S}_{n - 2}$</li>
<li>Keep going until no new functionally complete sets are found.</li>
</ol>
<p>I know that this is a high level article and that there&rsquo;s some details that are left out, but please feel free to reach out if you have any questions.</p>
]]></description>
      <category>Logic</category>
      
    </item>
    
    <item>
      <title>AI fearmongering for regulatory moats</title>
      <link>https://brandonrozek.com/blog/ai-fearmongering-regulatory-moat/</link>
      <pubDate>Sun, 28 May 2023 19:45:55 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/ai-fearmongering-regulatory-moat/</guid>
      <description><![CDATA[<p>On May 16th 2023, Sam Altman <a href="https://www.washingtonpost.com/technology/2023/05/16/ai-congressional-hearing-chatgpt-sam-altman/">testified before a congress subcommittee</a> on the need for AI regulation. During so, he proposes three actions:</p>
<ol>
<li>Form a new government agency charged with licensing large AI models, and allow it to revoke it for companies whose don’t comply.</li>
<li>Create a set of safety standards for AI models.</li>
<li>Require independent audits, by independent experts.</li>
</ol>
<p>Okay I can get behind the idea behind &ldquo;safety standards&rdquo; but what about this new government agency? Why would Sam Altman the CEO of OpenAI want the creation of a new government agency?</p>
<p>Some (including me) speculate that he&rsquo;s trying to create a <em>regulatory moat</em> for OpenAI.</p>
<p>We can see where this goes. There are thousands of AI startups trying to sell models of various kinds, the idea is that they all need to register and be regularly monitored by a government agency?</p>
<p>This would probably lead to long wait times for these AI licenses with a few companies holding the majority of the licenses. Startups funded through Y-combinator rarely go beyond <a href="https://ycuniverse.com/longevity-of-yc-startups-from-funding-through-acquisition/">two years</a>.</p>
<p>It&rsquo;s a great way of trying to stamp out competition really. Currently it&rsquo;s highly expensive to train one of these large language models (LLMs) but would that always be the case? In the inevitable future where other companies have the compute to generate one of these models, OpenAI needs to find a way to keep its market share.</p>
<p>Now you might think I&rsquo;m being to pessimistic. What if Sam Altman is trying to think about the best for society? If that&rsquo;s the case, then I don&rsquo;t think he would threaten to not <a href="https://www.reuters.com/technology/openai-may-leave-eu-if-regulations-bite-ceo-2023-05-24/">provide AI services in the EU</a> due to their stringent regulations.</p>
<p>He&rsquo;s walked back the comment since then, but let&rsquo;s take a brief look at what the EU regulations look like.</p>
<ul>
<li>Most current version I&rsquo;m aware of: <a href="https://digital-strategy.ec.europa.eu/en/policies/regulatory-framework-ai%5D">https://digital-strategy.ec.europa.eu/en/policies/regulatory-framework-ai]</a></li>
<li>Summary in Presentation Form: <a href="https://www.ceps.eu/wp-content/uploads/2021/04/AI-Presentation-CEPS-Webinar-L.-Sioli-23.4.21.pdf">https://www.ceps.eu/wp-content/uploads/2021/04/AI-Presentation-CEPS-Webinar-L.-Sioli-23.4.21.pdf</a></li>
</ul>
<p>Generative models are considered at the time of writing to be a &ldquo;high-risk&rdquo; AI model. The additional regulations the EU imposes on these companies are:</p>
<blockquote>
<ul>
<li>adequate risk assessment and mitigation systems;</li>
<li>high quality of the datasets feeding the system to minimise risks and discriminatory outcomes;</li>
<li>logging of activity to ensure traceability of results;</li>
<li>detailed documentation providing all information necessary on the system and its purpose for authorities to assess its compliance;</li>
<li>clear and adequate information to the user;</li>
<li>appropriate human oversight measures to minimise risk;</li>
<li>high level of robustness, security and accuracy.</li>
</ul>
</blockquote>
<p>I feel like most of those bullet points fall under Altman&rsquo;s &ldquo;safety standards for AI models&rdquo;. Though I think what he really has an issue with is the data privacy requirements set forth by the EU. In fact this is an often criticized part of these models trained on Internet data:</p>
<ul>
<li>Personally identifiable information</li>
<li>Copyright information</li>
<li>Conspiracy/Misinformation</li>
<li>etc.</li>
</ul>
<p>My personal opinion is that these models should not be trained on the above data, but this isn&rsquo;t a new discussion. More recently, the issue of copyright has been brought up with generative code products such as GitHub Copilot. I am interested to see how it makes its way through the US court systems here.</p>
<p>Another question I think we need to answer as a society is whether to hold companies accountable for defamation. There are already instances where <a href="https://www.washingtonpost.com/technology/2023/04/05/chatgpt-lies/">ChatGPT incorrectly accused a law professor of sexual harrasment</a>. With the push to <a href="https://www.nytimes.com/2022/12/21/technology/ai-chatgpt-google-search.html">replace search</a> with these automatically generated responses, we can see how misinformation may be propagated and then further reinforced by these models.</p>
<p>I don&rsquo;t want to say that it&rsquo;s a bad idea for congress to regulate AI. I&rsquo;m mostly suggesting that perhaps we shouldn&rsquo;t call upon people who hold direct financial interest to come up with the regulations that will apply to themselves. That might lead these actors to bring attention to certain issue while avoiding others&hellip;</p>
]]></description>
      
    </item>
    
    <item>
      <title>Top 7 Attacks to My Website</title>
      <link>https://brandonrozek.com/blog/popular-automated-website-attacks/</link>
      <pubDate>Wed, 17 May 2023 23:19:22 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/popular-automated-website-attacks/</guid>
      <description><![CDATA[<p>Running a public server on the Internet means that it&rsquo;s bound to get attacked by automated scripts. Since I <a href="https://brandonrozek.com/blog/goaccess/">run analytics on my website</a>, I&rsquo;m able to see 404s. In other words, this list constitutes the top requests to my website that fail.</p>
<ol>
<li><code>/wp-login.php</code></li>
</ol>
<p>This is the login page of a Wordpress website. Since this blog engine powers 43% of the Internet, it&rsquo;s not surprising that this is a common target. Sadly for the bots, I don&rsquo;t run this website using Wordpress.</p>
<ol start="2">
<li><code>xmlrpc.php</code></li>
</ol>
<p>This one I haven&rsquo;t heard of before until looking it up. I know XML is a data format, and RPC means remote procedure call, but what is the attacker trying to exploit? <a href="https://codex.wordpress.org/XML-RPC_Support">Again it&rsquo;s Wordpress</a>. It seems that this is some API gateway that Wordpress provides to connect with mobile devices, provide pingbacks, and others.</p>
<ol start="3">
<li><code>/api/v1/instance</code></li>
</ol>
<p>Through the power of search this seems to be a <a href="https://docs.joinmastodon.org/methods/instance/#v1">API call to Mastodon</a>! This specifically grabs generic information about the instance such as the number of users, number of statuses, restrictions, etc. I&rsquo;ve considered at some point running a Mastodon instance, but maybe it&rsquo;s better to leave it to the pros :)</p>
<ol start="4">
<li><code>/.env</code></li>
</ol>
<p>It seems that the Javascript community likes using a <code>.env</code> file to keep environmental variables that hold the secrets of your application. Yikes! Make sure that you&rsquo;re blocking this if you have it!</p>
<ol start="5">
<li><code>/inbox</code></li>
</ol>
<p>Given #3, I feel like this is ActivityPub related. Though looking at how the <a href="https://www.w3.org/TR/activitypub/">actors are usually structured</a> it&rsquo;s generally <code>/username/inbox</code>. Maybe it&rsquo;s related to email servers instead? I&rsquo;m unsure.</p>
<ol start="6">
<li><code>/status.php</code></li>
</ol>
<p>I&rsquo;m not sure what this is. Doesn&rsquo;t seem to be Wordpress related. Maybe the attacker is hoping to get the output of <code>phpinfo()</code> in those &ldquo;getting started with PHP&rdquo; tutorials?</p>
<p>James Oswald suggests that perhaps it&rsquo;s the <a href="https://github.com/pfsense/pfsense/blob/master/src/usr/local/www/status.php">firewall status page in pfsense</a>.</p>
<ol start="7">
<li><code>/.git/config</code></li>
</ol>
<p>I can see a situation where someone has a git repository of their website on the server itself and they push to it. Personally, I rsync the generated HTML files. Generally the config will contain the URLs of remote repositories and other settings. Not entirely sure what&rsquo;s sensitive, but maybe someone can let me know.</p>
<hr>
<p>There you have it! The top automated attacks made to my website. If you have any additional information on any of these URL patterns please get in touch. I am curious what these bots are trying to do with the response of each of these queries.</p>
]]></description>
      
    </item>
    
    <item>
      <title>The Usefulness of Website Analytics for Personal Websites</title>
      <link>https://brandonrozek.com/blog/website-analytics-usefulness/</link>
      <pubDate>Wed, 17 May 2023 22:22:10 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/website-analytics-usefulness/</guid>
      <description><![CDATA[<p>Should personal websites contain analytics? One argument is that analytics create a perverse incentive for the author. Let&rsquo;s say that someone spent a lot of time uploading their favorite <a href="https://brandonrozek.com/menu/">recipes</a> which have gone mostly unnoticed. Meanwhile, their post on <a href="https://brandonrozek.com/blog/wildcarddomainspihole/">wildcard domains in PiHole</a> has thousands of visits regularly. Upon seeing their analytics, they decide to abandon uploading recipes and instead only write about Raspberry Pis.</p>
<p>While this would likely get you more views, the idea is that the personal website then loses some <em>personal touch</em>. Some argue that personal websites should not be driven by any agenda other than yourself. No data, no click-bait titles, etc.</p>
<p>Of course this goes into why you created your website to begin with? For myself, this website&rsquo;s primary purpose is for <em>me</em>. I often forget how to perform many tasks and use this website as a reference guide.</p>
<p>Though one of my core principles is in sharing knowledge. An important secondary goal of mine is that others find my website useful in some way. The best way to achieve this is to carefully craft each and every one of my blog posts.</p>
<p>Personally, I find this difficult. Striving for perfection in a post often leads to its incompletion. In fact, it&rsquo;s not uncommon for me to sit on a topic for multiple months (sorry <a href="https://brandonrozek.com/blog/contributing-openstreetmap/">OpenStreetMap</a>) only to then force myself to write something on the topic to remove it from my backlog.</p>
<p>Now I could revisit older posts and spruce them up, but over the years I&rsquo;ve written more than 300 blog posts covering a variety of technical content. It would be a slog to comb over each individual one.</p>
<p>This is where <a href="https://brandonrozek.com/blog/goaccess/">analytics come into play for me</a>. This allows me to identify popular blog posts and spruce up those instead. It does mean that unpopular blog posts are unlikely to get corrected<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. However since time is limited, it&rsquo;s nice to know that I&rsquo;m improving a post that many look at and potentially rely on.</p>
<p>Also throughout the years, I&rsquo;ve edited the engine that creates these posts. This means that there are some broken links in the Internet that gets surfaced every now and again in my error logs :)</p>
<p>I understand that to some looking at an analytics dashboard can cause <em>fixation</em>. That doesn&rsquo;t mean, however, that we should dismiss analytics entirely. It&rsquo;s a tool, and if there&rsquo;s some benefit and it&rsquo;s right for you, then we should make use of it.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I also improve posts as I reference them personally. Some people email me with questions and provide suggestions which eventually make their way to the blog posts as well.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      
    </item>
    
    <item>
      <title>Syndicating Hugo Posts to Medium</title>
      <link>https://brandonrozek.com/blog/syndicating-hugo-to-medium/</link>
      <pubDate>Wed, 17 May 2023 21:28:55 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/syndicating-hugo-to-medium/</guid>
      <description><![CDATA[<p><em>Warning: This work was done prior to the deprecation of the Medium API. I&rsquo;m unsure how much longer the contents of this post will be relevant. Regardless, the ideas in this post can be adapted to other syndication systems of Hugo content.</em></p>
<p>I wrote a script that can selectively upload blog posts generated from a Hugo website onto Medium. This post will go into further detail each of the following steps that make it happen:</p>
<ul>
<li>Creating labels on the YAML frontmatter</li>
<li>Outputting a JSON feed to make it easier to create scripts</li>
<li>Writing a python script to ingest the JSON feed and upload new specified posts onto Medium</li>
<li>Updating the YAML frontmatter with information from Medium</li>
</ul>
<p><strong>Step 1 Define Labels:</strong> This advice not only goes for Medium but any other site you want to syndicate to. Within each of the content files within Hugo, there&rsquo;s generally a frontmatter section that holds the metadata such as title, date, and tags. Within the YAML frontmatter, I created two new fields:</p>
<ul>
<li>medium_enabled: Whether or not I want this post to be uploaded to Medium</li>
<li>medium_post_id: The post identifier used within Medium, empty if non-existent.</li>
</ul>
<p>We don&rsquo;t need to include these two new fields in every new post we create. Instead in Step 2, we&rsquo;ll consider some sane defaults. Though it could be useful to include some default values within the <a href="https://gohugo.io/content-management/archetypes/">archetype</a> file so that you don&rsquo;t forget they exist.</p>
<p>For example:<code>themes/themeName/archetypes/default.md</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span><span style="color:#f92672">title</span>: <span style="color:#e6db74">&#34;{{ replace .TranslationBaseName &#34;</span>-<span style="color:#e6db74">&#34; &#34;</span> <span style="color:#e6db74">&#34; | title }}&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">date</span>: {{ <span style="color:#ae81ff">dateFormat &#34;2006-01-02T15:04:05Z07:00&#34; .Date }}</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">draft</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">tags</span>: []
</span></span><span style="display:flex;"><span><span style="color:#f92672">medium_enabled</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>---
</span></span></code></pre></div><p><strong>Step 2 Setup JSON Feed:</strong> Following the <a href="https://gohugo.io/templates/">Hugo template system</a>, I created a <code>item.json.json</code>, <code>list.json.json</code>, and <code>single.json.json</code>. For this post, the first two are important.</p>
<p><code>list.json.json</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;version&#34;</span>: <span style="color:#e6db74">&#34;https://jsonfeed.org/version/1.1&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;title&#34;</span>: <span style="color:#e6db74">&#34;{{ if eq  .Title  .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} | {{ end }}{{ .Site.Title }}{{ end }}&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;home_page_url&#34;</span>: <span style="color:#e6db74">&#34;{{ .Site.BaseURL }}&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;feed_url&#34;</span>: <span style="color:#e6db74">&#34;{{ .Permalink }}&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;description&#34;</span>: <span style="color:#e6db74">&#34;{{ .Description }}&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;icon&#34;</span>: <span style="color:#e6db74">&#34;{{ .Site.BaseURL }}img/{{ .Site.Params.avatar }}&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;language&#34;</span>: <span style="color:#e6db74">&#34;en-US&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;authors&#34;</span>: [
</span></span><span style="display:flex;"><span>        {<span style="color:#960050;background-color:#1e0010">{with</span> <span style="color:#960050;background-color:#1e0010">$.Site.Author.name</span> }<span style="color:#960050;background-color:#1e0010">}</span>
</span></span><span style="display:flex;"><span>            { <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;{{ . }}&#34;</span> }
</span></span><span style="display:flex;"><span>        {<span style="color:#960050;background-color:#1e0010">{</span> <span style="color:#960050;background-color:#1e0010">end</span> }<span style="color:#960050;background-color:#1e0010">}</span>
</span></span><span style="display:flex;"><span>    ],
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;items&#34;</span>: [
</span></span><span style="display:flex;"><span>        {<span style="color:#960050;background-color:#1e0010">{</span> <span style="color:#960050;background-color:#1e0010">range</span> <span style="color:#960050;background-color:#1e0010">$index,</span> <span style="color:#960050;background-color:#1e0010">$page</span> <span style="color:#960050;background-color:#1e0010">:=</span> <span style="color:#960050;background-color:#1e0010">.Pages</span> }<span style="color:#960050;background-color:#1e0010">}</span>{<span style="color:#960050;background-color:#1e0010">{</span> <span style="color:#960050;background-color:#1e0010">if</span> <span style="color:#960050;background-color:#1e0010">ne</span> <span style="color:#960050;background-color:#1e0010">$index</span> <span style="color:#960050;background-color:#1e0010">0</span> }<span style="color:#960050;background-color:#1e0010">}</span>,{<span style="color:#960050;background-color:#1e0010">{</span> <span style="color:#960050;background-color:#1e0010">end</span> }<span style="color:#960050;background-color:#1e0010">}</span>
</span></span><span style="display:flex;"><span>            {<span style="color:#960050;background-color:#1e0010">{</span> <span style="color:#960050;background-color:#1e0010">.Render</span> <span style="color:#f92672">&#34;item&#34;</span>}<span style="color:#960050;background-color:#1e0010">}</span>
</span></span><span style="display:flex;"><span>        {<span style="color:#960050;background-color:#1e0010">{</span> <span style="color:#960050;background-color:#1e0010">end</span> }<span style="color:#960050;background-color:#1e0010">}</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>My <code>list.json.json</code> follows the <a href="https://jsonfeed.org/">JSON Feed</a> specification. The idea here is that this file will contain a list of blog posts within the <code>items</code> attribute. For each item, we have the next template file that defines how it&rsquo;s structured.</p>
<p><code>item.json.json</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;id&#34;</span>: <span style="color:#e6db74">&#34;{{ .Permalink }}&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;url&#34;</span>: <span style="color:#e6db74">&#34;{{ .Permalink }}&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;title&#34;</span>: {<span style="color:#960050;background-color:#1e0010">{</span> <span style="color:#960050;background-color:#1e0010">.Title</span> <span style="color:#960050;background-color:#1e0010">|</span> <span style="color:#960050;background-color:#1e0010">jsonify</span> }}<span style="color:#960050;background-color:#1e0010">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;authors&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> [
</span></span><span style="display:flex;"><span>        {<span style="color:#960050;background-color:#1e0010">{with</span> <span style="color:#960050;background-color:#1e0010">$.Site.Author.name</span> }<span style="color:#960050;background-color:#1e0010">}</span>
</span></span><span style="display:flex;"><span>            { <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;{{ . }}&#34;</span> }
</span></span><span style="display:flex;"><span>        {<span style="color:#960050;background-color:#1e0010">{</span> <span style="color:#960050;background-color:#1e0010">end</span> }<span style="color:#960050;background-color:#1e0010">}</span>
</span></span><span style="display:flex;"><span>    ]<span style="color:#960050;background-color:#1e0010">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;content_html&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {<span style="color:#960050;background-color:#1e0010">{</span> <span style="color:#960050;background-color:#1e0010">.Content</span> <span style="color:#960050;background-color:#1e0010">|</span> <span style="color:#960050;background-color:#1e0010">jsonify</span> }<span style="color:#960050;background-color:#1e0010">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;date_published&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;{{ .Date.Format &#34;</span><span style="color:#ae81ff">2006.01</span><span style="color:#960050;background-color:#1e0010">.</span><span style="color:#ae81ff">02</span><span style="color:#e6db74">&#34; }}&#34;</span><span style="color:#960050;background-color:#1e0010">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;tags&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {<span style="color:#960050;background-color:#1e0010">{</span> <span style="color:#960050;background-color:#1e0010">.Params.tags</span> <span style="color:#960050;background-color:#1e0010">|</span> <span style="color:#960050;background-color:#1e0010">jsonify</span> }<span style="color:#960050;background-color:#1e0010">},</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;_syndication&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;medium&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;enabled&#34;</span>: {<span style="color:#960050;background-color:#1e0010">{</span> <span style="color:#960050;background-color:#1e0010">.Params.medium_enabled</span> <span style="color:#960050;background-color:#1e0010">|</span> <span style="color:#960050;background-color:#1e0010">default</span> <span style="color:#f92672">&#34;false&#34;</span> }},
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;post_id&#34;</span>: {<span style="color:#960050;background-color:#1e0010">{</span> <span style="color:#960050;background-color:#1e0010">.Params.medium_post_id</span> <span style="color:#960050;background-color:#1e0010">|</span> <span style="color:#960050;background-color:#1e0010">jsonify</span> }}
</span></span><span style="display:flex;"><span>        <span style="color:#960050;background-color:#1e0010">}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#960050;background-color:#1e0010">}</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">}</span>
</span></span></code></pre></div><p>Notice the <code>_syndication</code> attribute. Attributes beginning with an underscore are ignored by JSON feed readers. Within this attribute, we can store our <code>enabled</code> and <code>post_id</code> attributes. If the post does not specify the relevant attributes in the frontmatter YAML, we&rsquo;ll assume <code>false</code> for <code>enabled</code> and <code>null</code> to for <code>post_id</code>.</p>
<p>Finally for this step we need to tell Hugo to generate this JSON feed. Within your website&rsquo;s <code>config.toml</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#75715e"># ...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">outputFormats</span>.<span style="color:#a6e22e">json</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span> = <span style="color:#e6db74">&#34;json&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">mediaType</span> = <span style="color:#e6db74">&#34;application/feed+json&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">baseName</span> = <span style="color:#e6db74">&#34;index&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">isPlainText</span> = <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">rel</span> = <span style="color:#e6db74">&#34;alternate&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">isHTML</span> = <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">noUgly</span> = <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">permalinkable</span> = <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">outputs</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">section</span> = [<span style="color:#e6db74">&#34;json&#34;</span>, <span style="color:#e6db74">&#34;html&#34;</span>, <span style="color:#e6db74">&#34;rss&#34;</span>]
</span></span><span style="display:flex;"><span>	<span style="color:#75715e"># ...</span>
</span></span></code></pre></div><p><strong>Step 3 Ingest Feed and Syndicate via External Script:</strong> Now that we can store the metadata on whether or not we want to syndicate a particular post and its relevant medium id if it has already been, we can create an external script that ingests this data and uploads if necessary.</p>
<p>We&rsquo;ll use Python for this task. The core loop of the file will look like the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Generate the necessary feed files</span>
</span></span><span style="display:flex;"><span>subprocess<span style="color:#f92672">.</span>run([<span style="color:#e6db74">&#39;hugo&#39;</span>], check<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Grab blog&#39;s feed</span>
</span></span><span style="display:flex;"><span>data <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;public/blog/index.json&#34;</span>, <span style="color:#e6db74">&#34;r&#34;</span>, encoding<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;UTF-8&#34;</span>) <span style="color:#66d9ef">as</span> feed_file:
</span></span><span style="display:flex;"><span>    data <span style="color:#f92672">=</span> feed_file<span style="color:#f92672">.</span>read()
</span></span><span style="display:flex;"><span>feed_data <span style="color:#f92672">=</span> json<span style="color:#f92672">.</span>loads(data)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Go through each post and check syndication status</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> post <span style="color:#f92672">in</span> feed_data[<span style="color:#e6db74">&#39;items&#39;</span>]:
</span></span><span style="display:flex;"><span>    medium_enabled <span style="color:#f92672">=</span> post[<span style="color:#e6db74">&#39;_syndication&#39;</span>][<span style="color:#e6db74">&#39;medium&#39;</span>][<span style="color:#e6db74">&#39;enabled&#39;</span>]
</span></span><span style="display:flex;"><span>    medium_post_id <span style="color:#f92672">=</span> post[<span style="color:#e6db74">&#39;_syndication&#39;</span>][<span style="color:#e6db74">&#39;medium&#39;</span>][<span style="color:#e6db74">&#39;post_id&#39;</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> medium_enabled <span style="color:#f92672">and</span> medium_post_id <span style="color:#f92672">is</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Syndicate.....</span>
</span></span><span style="display:flex;"><span>        medium_result <span style="color:#f92672">=</span> syndicate_post(post)
</span></span><span style="display:flex;"><span>        update_front_matter(post, medium_result)
</span></span><span style="display:flex;"><span>        time<span style="color:#f92672">.</span>sleep(<span style="color:#ae81ff">60</span>) <span style="color:#75715e"># Throttle to not hit rate limit</span>
</span></span></code></pre></div><p>The last couple steps were necessary for Hugo to produce the JSON feed needed which contains the appropriate syndication fields. Now the magic is within the <code>syndicate_post</code> method.</p>
<p>For the magic we need to make use of the <a href="https://github.com/Medium/medium-api-docs">Medium API</a>. You&rsquo;ll need to know both your <code>access token</code> and your <code>authorId</code>. Your author id can be displayed publically, for example mine is <code>18bad95d2020608a45ef502ef0db83d2cad2e28886d8d3eeef71a6bd089fc2a4e</code>. However, your access token must be kept private. Otherwise, others can impersonate you. For my script, I put my access token within a secret file that I don&rsquo;t commit to the repository.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Grab secret</span>
</span></span><span style="display:flex;"><span>medium_secret <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;secrets/medium.secret&#34;</span>, <span style="color:#e6db74">&#34;r&#34;</span>) <span style="color:#66d9ef">as</span> secret_file:
</span></span><span style="display:flex;"><span>    medium_secret <span style="color:#f92672">=</span> secret_file<span style="color:#f92672">.</span>read()<span style="color:#f92672">.</span>strip()
</span></span></code></pre></div><p>The secret is used within the request headers</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>request_headers <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;Host&#34;</span>: <span style="color:#e6db74">&#34;api.medium.com&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;Authorization&#34;</span>: <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Bearer </span><span style="color:#e6db74">{</span>medium_secret<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;Content-Type&#34;</span>: <span style="color:#e6db74">&#34;application/json&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;Accept&#34;</span>: <span style="color:#e6db74">&#34;application/json&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;Accept-Charset&#34;</span>: <span style="color:#e6db74">&#34;utf-8&#34;</span>,
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Following the API documentation we then create a JSON object that represents our new blog post with its corresponding metadata.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Structure API Request</span>
</span></span><span style="display:flex;"><span>blog_post <span style="color:#f92672">=</span> dict(
</span></span><span style="display:flex;"><span>    title<span style="color:#f92672">=</span>post[<span style="color:#e6db74">&#39;title&#39;</span>],
</span></span><span style="display:flex;"><span>    contentFormat<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;html&#34;</span>,
</span></span><span style="display:flex;"><span>    content<span style="color:#f92672">=</span>post[<span style="color:#e6db74">&#39;content_html&#39;</span>],
</span></span><span style="display:flex;"><span>    tags<span style="color:#f92672">=</span>post[<span style="color:#e6db74">&#39;tags&#39;</span>],
</span></span><span style="display:flex;"><span>    canonicalUrl<span style="color:#f92672">=</span>post[<span style="color:#e6db74">&#39;url&#39;</span>],
</span></span><span style="display:flex;"><span>    publishStatus<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;public&#34;</span>,
</span></span><span style="display:flex;"><span>    license<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;all-rights-reserved&#34;</span>,
</span></span><span style="display:flex;"><span>    notifyFollowers<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>You&rsquo;re welcome to change the publish status, license, and follower notification as you see fit. Afterwards, following the API documentation we send a <code>POST</code> request to the Medium API</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Send Request</span>
</span></span><span style="display:flex;"><span>conn <span style="color:#f92672">=</span> HTTPSConnection(<span style="color:#e6db74">&#34;api.medium.com&#34;</span>)
</span></span><span style="display:flex;"><span>conn<span style="color:#f92672">.</span>request(
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;POST&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;/v1/users/</span><span style="color:#e6db74">{</span>AUTHOR_ID<span style="color:#e6db74">}</span><span style="color:#e6db74">/posts&#34;</span>,
</span></span><span style="display:flex;"><span>    json<span style="color:#f92672">.</span>dumps(blog_post)<span style="color:#f92672">.</span>encode(<span style="color:#e6db74">&#34;utf-8&#34;</span>),
</span></span><span style="display:flex;"><span>    request_headers
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>We&rsquo;ll need to hold onto the response from the website as that&rsquo;ll contain our post id.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Check and parse response</span>
</span></span><span style="display:flex;"><span>response <span style="color:#f92672">=</span> conn<span style="color:#f92672">.</span>getresponse()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> response<span style="color:#f92672">.</span>status <span style="color:#f92672">!=</span> <span style="color:#ae81ff">201</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">raise</span> <span style="color:#a6e22e">Exception</span>(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Medium API rejected the request with response code </span><span style="color:#e6db74">{</span>response<span style="color:#f92672">.</span>status<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>medium_response <span style="color:#f92672">=</span> json<span style="color:#f92672">.</span>loads(response<span style="color:#f92672">.</span>read()<span style="color:#f92672">.</span>decode(<span style="color:#e6db74">&#39;utf-8&#39;</span>))
</span></span><span style="display:flex;"><span>conn<span style="color:#f92672">.</span>close()
</span></span></code></pre></div><p><strong>Step 4 Update Frontmatter:</strong> Now that we have the post id within the Medium response, we can look towards the original markdown file that contains our content and modify the frontmatter so that the <code>medium_post_id</code> field is populated.</p>
<p>First we need to get the appopriate markdown file</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Figure out the file path of the post</span>
</span></span><span style="display:flex;"><span>ORIG_URL <span style="color:#f92672">=</span> urlparse(post[<span style="color:#e6db74">&#39;id&#39;</span>])
</span></span><span style="display:flex;"><span>file_path <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;content&#34;</span> <span style="color:#f92672">+</span> ORIG_URL<span style="color:#f92672">.</span>path[:<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>] <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;.md&#34;</span>
</span></span></code></pre></div><p>Using the <a href="https://pypi.org/project/frontmatter/">Frontmatter</a> Python library, we can read the existing YAML frontmatter and edit the post id with what we received from Medium</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Read existing frontmatter and edit the post id</span>
</span></span><span style="display:flex;"><span>item <span style="color:#f92672">=</span> {}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> open(file_path, <span style="color:#e6db74">&#34;r&#34;</span>, encoding<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;UTF-8&#34;</span>) <span style="color:#66d9ef">as</span> content_file:
</span></span><span style="display:flex;"><span>    item <span style="color:#f92672">=</span> frontmatter<span style="color:#f92672">.</span>load(content_file)
</span></span><span style="display:flex;"><span>    item[<span style="color:#e6db74">&#39;medium_post_id&#39;</span>] <span style="color:#f92672">=</span> medium_data[<span style="color:#e6db74">&#39;data&#39;</span>][<span style="color:#e6db74">&#39;id&#39;</span>]
</span></span></code></pre></div><p>The library also helps us edit the file with updated fronmatter</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Write out new frontmatter</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> open(file_path, <span style="color:#e6db74">&#34;w&#34;</span>, encoding<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;UTF-8&#34;</span>) <span style="color:#66d9ef">as</span> content_file:
</span></span><span style="display:flex;"><span>    content_file<span style="color:#f92672">.</span>write(frontmatter<span style="color:#f92672">.</span>dumps(item))
</span></span></code></pre></div><p><strong>Conclusion:</strong> This constitutes my somewhat generic framework for how you can selectively syndicate content within a Hugo website. To summarize, we do this by keeping track of the syndication through frontmatter additions and a JSON feed. We then created an external script that ingests this feed and syndicates if necessary, finally updating the original frontmatter with the server response.</p>
<p>Downsides to this approach is that the external script occurs outside of the <code>hugo</code> call. This adds an extra layer of complication when managing your website. However, keeping it all within Hugo is not only difficult but inflexible. The way I envision this is that Hugo is a database of sorts that keeps track of the syndication status and external scripts operates over this database.</p>
<p>How do you go about syndicating content to other websites? Is your approach different from mine? Please get in touch and share :)</p>
<p>Relevant files from my website repository:</p>
<ul>
<li><a href="https://github.com/Brandon-Rozek/website/blob/52b47f1311e7bf98b97c69bd0e838d8e3cdf8392/scripts/syndicate_medium.py">https://github.com/Brandon-Rozek/website/blob/52b47f1311e7bf98b97c69bd0e838d8e3cdf8392/scripts/syndicate_medium.py</a></li>
<li><a href="https://github.com/Brandon-Rozek/website/blob/52b47f1311e7bf98b97c69bd0e838d8e3cdf8392/config.toml">https://github.com/Brandon-Rozek/website/blob/52b47f1311e7bf98b97c69bd0e838d8e3cdf8392/config.toml</a></li>
<li><a href="https://github.com/Brandon-Rozek/website-theme/blob/fb42d6b950b1d7752e199dc16fd0d39292a16c93/layouts/_default/item.json.json">https://github.com/Brandon-Rozek/website-theme/blob/fb42d6b950b1d7752e199dc16fd0d39292a16c93/layouts/_default/item.json.json</a></li>
<li><a href="https://github.com/Brandon-Rozek/website-theme/blob/fb42d6b950b1d7752e199dc16fd0d39292a16c93/layouts/_default/list.json.json">https://github.com/Brandon-Rozek/website-theme/blob/fb42d6b950b1d7752e199dc16fd0d39292a16c93/layouts/_default/list.json.json</a></li>
</ul>
]]></description>
      <category>Hugo</category>
      
    </item>
    
    <item>
      <title>Avoid Mutating Items within a Set (Python Edition)</title>
      <link>https://brandonrozek.com/blog/avoid-mutating-set-items-python/</link>
      <pubDate>Wed, 17 May 2023 20:46:33 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/avoid-mutating-set-items-python/</guid>
      <description><![CDATA[<p>Whenever we create a set of objects, there&rsquo;s an implicit assumption that those objects don&rsquo;t change. What happens when we break this implicit contract and mutate items within a set? If negative properties occur, how can we get around this issue? We&rsquo;ll explore this through the lens of set membership and talk about how the hash method and equality method are used within sets.</p>
<p><strong>Example</strong></p>
<p>We start by defining a Person class that holds a name</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Person</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __init__(self, name):
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>name <span style="color:#f92672">=</span> name
</span></span></code></pre></div><p>In order to put it within a set, we need to define a hash method.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Person</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __init__(self, name):
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>name <span style="color:#f92672">=</span> name
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __hash__(self):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> hash((<span style="color:#e6db74">&#34;Person&#34;</span>, self<span style="color:#f92672">.</span>name))
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>people <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    Person(<span style="color:#e6db74">&#34;Brandon&#34;</span>),
</span></span><span style="display:flex;"><span>    Person(<span style="color:#e6db74">&#34;Clare&#34;</span>),
</span></span><span style="display:flex;"><span>    Person(<span style="color:#e6db74">&#34;James&#34;</span>),
</span></span><span style="display:flex;"><span>    Person(<span style="color:#e6db74">&#34;John&#34;</span>)
</span></span><span style="display:flex;"><span>} 
</span></span></code></pre></div><p>The property we&rsquo;ll focus on in this post is set membership. In other words, does the set contain a specified object?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>print(Person(<span style="color:#e6db74">&#34;Brandon&#34;</span>) <span style="color:#f92672">in</span> people) <span style="color:#75715e"># False</span>
</span></span></code></pre></div><p>This might seem strange since we can clearly see a <code>Person(&quot;Brandon&quot;)</code> in the set! The problem is that we didn&rsquo;t override the <code>__eq__</code> method within the class. Currently it only considers two <code>Person</code> objects the same if they share the same <em>reference</em>.</p>
<p>To test <code>Person</code> equivalence in a more intuitive way, we&rsquo;ll say that two <code>Person</code> objects are equivalent if they share the same name.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Person</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __init__(self, name):
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>name <span style="color:#f92672">=</span> name
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __hash__(self):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> hash((<span style="color:#e6db74">&#34;Person&#34;</span>, self<span style="color:#f92672">.</span>name))
</span></span><span style="display:flex;"><span>   	<span style="color:#66d9ef">def</span> __eq__(self, other):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> isinstance(other, Person) <span style="color:#f92672">and</span> self<span style="color:#f92672">.</span>name <span style="color:#f92672">==</span> other<span style="color:#f92672">.</span>name
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>print(Person(<span style="color:#e6db74">&#34;Brandon&#34;</span>) <span style="color:#f92672">in</span> people) <span style="color:#75715e"># True</span>
</span></span></code></pre></div><p>Now let&rsquo;s mutate the names so they all start with <code>Awesome </code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> p <span style="color:#f92672">in</span> people:
</span></span><span style="display:flex;"><span>    p<span style="color:#f92672">.</span>name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Awesome &#34;</span> <span style="color:#f92672">+</span> p<span style="color:#f92672">.</span>name
</span></span></code></pre></div><p>Let&rsquo;s test for membership again</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>print(Person(<span style="color:#e6db74">&#34;Awesome Brandon&#34;</span>) <span style="color:#f92672">in</span> people) <span style="color:#75715e"># False</span>
</span></span><span style="display:flex;"><span>print(Person(<span style="color:#e6db74">&#34;Brandon&#34;</span>) <span style="color:#f92672">in</span> people) <span style="color:#75715e"># False</span>
</span></span></code></pre></div><p>What happened? Why is both <code>Awesome Brandon</code> and <code>Brandon</code> not in the set? This has to do with the way that the sets keep track of the objects within them.</p>
<p>When an object is inserted, it&rsquo;s placed into bins according to their hash. Therefore, when checking for set membership, we look to see if there are items within that hash bin.</p>
<p>Originally when we added <code>Person(&quot;Brandon&quot;)</code> the following hash is computed:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>print(hash((<span style="color:#e6db74">&#34;Person&#34;</span>, <span style="color:#e6db74">&#34;Brandon&#34;</span>))) <span style="color:#75715e"># 8879556234447129293</span>
</span></span></code></pre></div><p>The object <code>Person(&quot;Brandon&quot;)</code> is then placed in bin 8879556234447129293.</p>
<p>When we mutate the object, we don&rsquo;t remove the item from the set and add it back in with a recomputed hash. Instead the mutated object stays within the same hash bin.</p>
<p>So what happens then when we ask if <code>Person(&quot;Awesome Brandon&quot;)</code> is within the set? First, it checks to see if there are any non-empty bins with the same hash.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>print(hash((<span style="color:#e6db74">&#34;Person&#34;</span>, <span style="color:#e6db74">&#34;Awesome Brandon&#34;</span>))) <span style="color:#75715e"># 8677222781184413080</span>
</span></span></code></pre></div><p>Our set contains no items within the 8677222781184413080 hash bin. Therefore, the set membership will immediately fail disregarding completely the <code>__eq__</code> method we define.</p>
<p><strong>Aside:</strong> Another implicit contract we have is that for a given hash method, two equivalent objects always produce the same hash. What happens when that&rsquo;s not the case?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> random
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Person</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __init__(self, name):
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>name <span style="color:#f92672">=</span> name
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __hash__(self):
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Add randomness!</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> hash((<span style="color:#e6db74">&#34;Person&#34;</span>, self<span style="color:#f92672">.</span>name, random<span style="color:#f92672">.</span>randint(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">5</span>)))
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __eq__(self, other):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> isinstance(other, Person) <span style="color:#f92672">and</span> self<span style="color:#f92672">.</span>name <span style="color:#f92672">==</span> other<span style="color:#f92672">.</span>name
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __str__(self):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Person(name=</span><span style="color:#e6db74">{</span>self<span style="color:#f92672">.</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">)&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>people <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    Person(<span style="color:#e6db74">&#34;Brandon&#34;</span>),
</span></span><span style="display:flex;"><span>    Person(<span style="color:#e6db74">&#34;Clare&#34;</span>),
</span></span><span style="display:flex;"><span>    Person(<span style="color:#e6db74">&#34;James&#34;</span>),
</span></span><span style="display:flex;"><span>    Person(<span style="color:#e6db74">&#34;John&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(Person(<span style="color:#e6db74">&#34;Brandon&#34;</span>) <span style="color:#f92672">in</span> people) <span style="color:#75715e"># False</span>
</span></span></code></pre></div><p>Again, the set membership returns false disregarding our <code>__eq__</code> method since the <code>__hash__</code> outputs don&rsquo;t match.</p>
<p><strong>Workaround:</strong> If we can&rsquo;t mutate the items in a set directly, what should we do instead? The solution here is to remove the old items and insert newly created items with the modified data.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>people <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    Person(<span style="color:#e6db74">&#34;Brandon&#34;</span>),
</span></span><span style="display:flex;"><span>    Person(<span style="color:#e6db74">&#34;Clare&#34;</span>),
</span></span><span style="display:flex;"><span>    Person(<span style="color:#e6db74">&#34;James&#34;</span>),
</span></span><span style="display:flex;"><span>    Person(<span style="color:#e6db74">&#34;John&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>to_remove <span style="color:#f92672">=</span> set()
</span></span><span style="display:flex;"><span>to_add <span style="color:#f92672">=</span> set()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> p <span style="color:#f92672">in</span> people:
</span></span><span style="display:flex;"><span>    to_remove<span style="color:#f92672">.</span>add(p)
</span></span><span style="display:flex;"><span>    to_add<span style="color:#f92672">.</span>add(Person(<span style="color:#e6db74">&#34;Awesome &#34;</span> <span style="color:#f92672">+</span> p<span style="color:#f92672">.</span>name))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>people <span style="color:#f92672">=</span> (people <span style="color:#f92672">-</span> to_remove)<span style="color:#f92672">.</span>union(to_add)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(Person(<span style="color:#e6db74">&#34;Awesome Brandon&#34;</span>) <span style="color:#f92672">in</span> people) <span style="color:#75715e"># True</span>
</span></span></code></pre></div>]]></description>
      <category>Python</category>
      
    </item>
    
    <item>
      <title>Why pay for things when you can get it for free?</title>
      <link>https://brandonrozek.com/blog/paying-for-things/</link>
      <pubDate>Mon, 08 May 2023 13:52:38 -0400</pubDate>
      
      <guid>https://brandonrozek.com/blog/paying-for-things/</guid>
      <description><![CDATA[<p>Other than physical hardware, it is often expected with technology that services come for free. It should be free to make a search query, free to host gigabytes of personal photos, free to send sticker packs to all my friends. And while I welcome lowering the barrier of entry to use technology, I want to provide a counter narrative. This type of expectation can have negative effects.</p>
<p><strong>Almost nothing is truly free</strong>. Outside of technology, it costs money to keep clean and maintain parks. Similarly, a server needs to be available to respond to a search query. In fact these are often multiple servers, each that run on electricity and operate within an air conditioned data center.</p>
<p>There is a common saying among privacy activists:</p>
<blockquote>
<p>If you are not paying for the product, then you are the product.</p>
</blockquote>
<p>This has some truth to it. A company can only operate at a loss for so long therefore the easiest way to monetize is through advertisements. For an advertisement to be effective, the company has to collect information about your behavior patterns.</p>
<p><strong>Expectation of free can drive out competition.</strong> Who can compete with a company that is offering their services for free? Knowing this, some large companies often operate at a loss for a certain time period. Why? In order to drive out competition. Google at one point offered unlimited photo storage as long as you saved the photo as &ldquo;High Quality&rdquo;. This has since changed in June 2021. This marketing tactic is known as the &ldquo;bait and switch&rdquo;. It relies on the fact that it&rsquo;s often effort for people to change services and that they would instead eat the cost once it&rsquo;s introduced later on.</p>
<p>I argue that the lack of competition hinders innovation. Currently Google reigns supreme over search. There&rsquo;s some competition from Bing and other smaller search engines, though the market share isn&rsquo;t enough for Google to try to improve the product.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>In a world where large tech companies offer expensive services for free. It is hard for a small company to bootstrap and provide a competitive take in the area.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>What about open source? It turns out that there&rsquo;s often a way to financially support these projects. The ways are varied but they include paying for support, hosting, or even solely donations. I argue that if no funding model exists, then it&rsquo;s likely for the project to be unsustainable. At the end of the day humans are behind it all and we all require to have our needs met.</p>
<p>Though another interesting component of open source is that it is often driven by the needs of people. Often times when a developer contributes a feature it&rsquo;s because they want to use the feature themselves. In that way they provide value to the project. I encourage people to contribute to the projects they depend on in some way (if possible), whether financially, through documentation, or code.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Nowadays, it seems that another currently free product ChatGPT is adding competition to the search engine space. Bing is betting on the introduction of large language models into their search engine. It has yet to be shown whether this will be the latest evolution of search.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Kagi is a search engine that is taking on this challenge. They have a small free trial, but otherwise require that people pay for their service. I currently pay and am happy with their product. One argument I heard against it is that it&rsquo;ll never take off and surpass Google. I&rsquo;m curious, does it need to surpass Google to be sustainable?&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      
    </item>
    
  </channel>
</rss>