<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Random Bits]]></title><description><![CDATA[Thoughts, stories and ideas.]]></description><link>https://blog.randombits.host/</link><image><url>https://blog.randombits.host/favicon.png</url><title>Random Bits</title><link>https://blog.randombits.host/</link></image><generator>Ghost 5.40</generator><lastBuildDate>Fri, 23 Feb 2024 07:41:57 GMT</lastBuildDate><atom:link href="https://blog.randombits.host/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Naming Variables Just Got Harder]]></title><description><![CDATA[<p><a href="https://www.martinfowler.com/bliki/TwoHardThings.html?ref=blog.randombits.host">It&apos;s a joke that has been done to death</a>, but it is true. Naming things is one of the hardest aspects of Computer Science. It affects the readability, maintainability, and every facet of the lossy interface between the concept in your mind, and the cold reality of the</p>]]></description><link>https://blog.randombits.host/naming-variables-just-got-harder/</link><guid isPermaLink="false">6509ab2cf653220001ce6f13</guid><dc:creator><![CDATA[Conor]]></dc:creator><pubDate>Tue, 19 Sep 2023 21:43:25 GMT</pubDate><content:encoded><![CDATA[<p><a href="https://www.martinfowler.com/bliki/TwoHardThings.html?ref=blog.randombits.host">It&apos;s a joke that has been done to death</a>, but it is true. Naming things is one of the hardest aspects of Computer Science. It affects the readability, maintainability, and every facet of the lossy interface between the concept in your mind, and the cold reality of the code. This has been always the way since <a href="https://en.wikipedia.org/wiki/Short_Code_(computer_language)?ref=blog.randombits.host">variables could be named</a>. However, I believe that variable naming has become much more important in the past few months with the increasing presence of &quot;AI&quot; assisted tools.</p><p>I was initially stubborn when it came to personally using &quot;AI&quot; powered tools. I still am, but I have now accepted that these tools are here to stay. The cat is out of the bag, Pandora&apos;s Box has been opened, etc etc. For better or worse, we should all be operating under the assumption that these tools are currently in use, and will become more relied upon in future. This is why naming in software development has become even more important.</p><p>These tools rely, at least partially, on the semantic meaning of your variable and function names. That means if your names are inconsistent or poorly conceived, you&apos;re going to get worse output. Some examples in regular code block: (Because I also reject Jupyter Notebooks)</p><pre><code class="language-python">import openai
import os
import sys
import time

from pydantic import BaseModel, Field


openai.api_key = os.getenv(&quot;OPENAI_API_KEY&quot;)


class GermanTranslationResponse(BaseModel):
    message_in_german: str = Field(
        description=&quot;The user message translated into German&quot;
    )

class FrenchTranslationResponse(BaseModel):
    message_in_french: str = Field(
        description=&quot;The user message translated into French&quot;
    )

class FrenchTranslationResponse2(BaseModel):
    message_in_german: str = Field(
        description=&quot;The user message translated into French&quot;
    )

class FrenchTranslationResponse3(BaseModel):
    message_in_french: str = Field(
        description=&quot;The user message translated into German&quot;
    )


def get_response(message: str, language: str, model) -&gt; str:
    context = [{
        &quot;role&quot;: &quot;user&quot;,
        &quot;content&quot;: message
    }]

    chat_completion = openai.ChatCompletion.create(
        model=&quot;gpt-3.5-turbo-0613&quot;,
        messages=context,
        functions=[
            {
                &quot;name&quot;: f&quot;get_{language}_translation&quot;,
                &quot;description&quot;: f&quot;get the message translated to {language}&quot;,
                &quot;parameters&quot;: model.schema(),
            },
        ],
        function_call={&quot;name&quot;: f&quot;get_{language}_translation&quot;},
        temperature=0.0,
    )

    return chat_completion.choices[0].message.function_call.arguments

if __name__ == &quot;__main__&quot;:
    print(f&quot;Input message: {sys.argv[1]}&quot;)

    # Translates to German
    print(&quot;German&quot;)
    print(get_response(sys.argv[1], &quot;German&quot;, GermanTranslationResponse))
    time.sleep(1)

    # Translates to French
    print(&quot;French&quot;)
    print(get_response(sys.argv[1], &quot;French&quot;, FrenchTranslationResponse))
    time.sleep(1)

    # Translates to English
    print(&quot;French and German&quot;)
    print(get_response(sys.argv[1], &quot;French&quot;, GermanTranslationResponse))
    time.sleep(1)

    # Translates to English
    print(&quot;German and French&quot;)
    print(get_response(sys.argv[1], &quot;German&quot;, FrenchTranslationResponse))
    time.sleep(1)

    # Translates to German
    print(&quot;German and French2&quot;)
    print(get_response(sys.argv[1], &quot;German&quot;, FrenchTranslationResponse2))
    time.sleep(1)

    # Translates to French
    print(&quot;German and French3&quot;)
    print(get_response(sys.argv[1], &quot;French&quot;, FrenchTranslationResponse3))

$ python3 ex.py &quot;what&apos;s up&quot;
Input message: what&apos;s up
German
{
  &quot;message_in_german&quot;: &quot;Was gibt&apos;s Neues?&quot;
}
French
{
  &quot;message_in_french&quot;: &quot;Quoi de neuf&quot;
}
French and German
{
  &quot;message_in_german&quot;: &quot;what&apos;s up&quot;
}
German and French
{
  &quot;message_in_french&quot;: &quot;what&apos;s up&quot;
}
German and French2
{
  &quot;message_in_german&quot;: &quot;Was gibt&apos;s Neues?&quot;
}
German and French3
{
  &quot;message_in_french&quot;: &quot;Quoi de neuf&quot;
}</code></pre>]]></content:encoded></item><item><title><![CDATA[Dealing With Being Distrusting of HomeAssistant Automations]]></title><description><![CDATA[<p><a href="https://www.home-assistant.io/?ref=blog.randombits.host">HomeAssistant</a> is something I&apos;m sure everyone is aware of - an open source tool for managing your smart home devices. I have used it for a number of years, starting with a rough and ready setup on a Raspberry Pi 3B. Back then, I only had three smart</p>]]></description><link>https://blog.randombits.host/dealing-with-being-untrustworthy-with-homeassistant-automations/</link><guid isPermaLink="false">64cc063637a8b80001401b8e</guid><dc:creator><![CDATA[Conor]]></dc:creator><pubDate>Thu, 03 Aug 2023 20:49:54 GMT</pubDate><content:encoded><![CDATA[<p><a href="https://www.home-assistant.io/?ref=blog.randombits.host">HomeAssistant</a> is something I&apos;m sure everyone is aware of - an open source tool for managing your smart home devices. I have used it for a number of years, starting with a rough and ready setup on a Raspberry Pi 3B. Back then, I only had three smart light bulbs and essentially all I did was manually turn them on/off from my phone. Today, I have a slightly more elaborate setup, with lights, plugs, and sensors in almost every room of my house. The real power of Home Assistant however comes with the <em><a href="https://www.home-assistant.io/docs/automation/?ref=blog.randombits.host">automations</a></em>.</p><p>From having lights turn on a little dim before the morning alarm on my phone, to making sure all devices are off whenever there is nobody home, the depth of what you can do with automation is immense. However, there is one UX issue I have with these automations - I just don&apos;t quite <em>trust</em> them. Too many times I&apos;ve come home and realized that my automation didn&apos;t realize I had left the house and never turned off my devices. Similar to alarms and logs in software development, automations are only worthwhile if they are reliable and you can trust them. I set out to find a way to increase my trust in the automations I have at home, and also have an easier path to debugging them if they don&apos;t run when I expected them to.</p><p>My solution was to have a simple notification on my phone whenever an automation runs on Home Assistant. The notification medium was obvious for my workflow/style - use <a href="https://ntfy.sh/?ref=blog.randombits.host">ntfy.sh</a> to trigger a notification on my phone so that whenever I was concerned if the automation ran or not, I would already have the information available in a zero click manner. I thought this might be a little difficult, but as it turns out, there is a built in <a href="https://github.com/caronc/apprise?ref=blog.randombits.host">Apprise</a> notification service built into Home Assistant (which I think was a great idea!). My second issue was making sure that this notification would fire for <em>all</em> of my automations and that I wouldn&apos;t have to manually add it to each and every one. This turned out to be easier than expected, as there is an event you can trigger on called <code><a href="https://www.home-assistant.io/docs/configuration/events/?ref=blog.randombits.host#automation_triggered">AUTOMATION_TRIGGERED</a></code> which fires on every single automation run. The plan was set!</p><!--kg-card-begin: markdown--><p>The first thing I had to do was add the Apprise integraion to my <code>configuration.yaml</code> for Home Assistant with the URLs I wanted to trigger notifications on: <sup>[1]</sup></p>
<!--kg-card-end: markdown--><pre><code class="language-yaml">notify:
  - platform: apprise
    url: ntfy://ntfy.sh/home-assistant-notification-topic</code></pre><p>After that, I created an automation that would trigger whenever an automation ran, and would then notify me with the name of the automation that ran:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.randombits.host/content/images/2023/08/Screenshot-from-2023-08-03-22-17-43.png" class="kg-image" alt loading="lazy" width="972" height="865" srcset="https://blog.randombits.host/content/images/size/w600/2023/08/Screenshot-from-2023-08-03-22-17-43.png 600w, https://blog.randombits.host/content/images/2023/08/Screenshot-from-2023-08-03-22-17-43.png 972w" sizes="(min-width: 720px) 720px"><figcaption>Home Assistant UI screenshot showing the details of how to set up an automation to notify you whenever a different automation runs</figcaption></figure><p>That was the sum total of what I had to do! This has made me feel much more at ease and trusting of when my automations have run, and if they don&apos;t run when expected, I can easily debug what <em>didn&apos;t</em> happen when I expected it to. One interesting thing to note is that the automation that sends a notification doesn&apos;t end up triggering the automation to fire again. There is no recursive behavior from it. I hope this helps anyone looking to set up automations on Home Assistant using Apprise, ntfy.sh, or just trying to trigger an automation to run whenever a different automation runs! If you have any fun automations for Home Assistant, or any thoughts/comments, please let me know through whatever means you found this post, and thanks for reading!</p><p>[1] I <a href="https://docs.ntfy.sh/install/?ref=blog.randombits.host">self-host ntfy.sh</a> so this URL is just a dummy! If you want another blog post on self-hosting ntfy.sh, let me know, but those docs are great and it&apos;s one of the most stable services I have ever ran.</p>]]></content:encoded></item><item><title><![CDATA[Vanity, Recognition, and Fighting Perfectionism - A Buildlog for git-vain]]></title><description><![CDATA[<p><em>&#x2003;This post is 50% a build log, 10% thoughts on vanity, and 40% about dev ops/project structure/CI/CD/etc. Everything should be easily navigable from the headings below. If you want to get in touch, find me on <a href="https://mastodon.social/@conorf?ref=blog.randombits.host">Mastodon</a>, write me an email, or follow me on</em></p>]]></description><link>https://blog.randombits.host/git-vain/</link><guid isPermaLink="false">648b754d1edd0a00011d7d8a</guid><category><![CDATA[Docker]]></category><category><![CDATA[Side Project]]></category><category><![CDATA[Self Hosted]]></category><category><![CDATA[Non-Tech]]></category><dc:creator><![CDATA[Conor]]></dc:creator><pubDate>Wed, 12 Jul 2023 11:00:56 GMT</pubDate><content:encoded><![CDATA[<p><em>&#x2003;This post is 50% a build log, 10% thoughts on vanity, and 40% about dev ops/project structure/CI/CD/etc. Everything should be easily navigable from the headings below. If you want to get in touch, find me on <a href="https://mastodon.social/@conorf?ref=blog.randombits.host">Mastodon</a>, write me an email, or follow me on <a href="https://github.com/conor-f?ref=blog.randombits.host">Github</a> (My <code>vanity</code> will appreciate it!).</em></p><h1 id="introduction">Introduction</h1><p>&#x2003;Everybody is a little vain. Vanity frequently spurs people to action where other, more <em>pure</em>, motives fail. &#xA0;There&apos;s an interesting relationship between vanity and perfectionism. Perfectionism is often driven by vanity, the wish to be perceived as being a master in whatever actions you are currently undertaking. This is clearly an impossible task, but striving towards an impossible ideal can often lead to successful results along the way.</p><p>&#x2003;Recognizing that perfectionist tendencies could have a different root cause than you previously thought leads to a different perspective on them. For me, perfectionist tendencies have been counterproductive to my desire for acknowledgement and recognition in the things I (at least think!) am knowledgeable about. However, you need to <a href="https://brooker.co.za/blog/2023/04/20/hobbies.html?ref=blog.randombits.host">be visible in order to be recognized</a>, so perfectionism really gets in the way. If my perfectionist tendencies were allowed to run free, I would rarely, if ever, release anything. The concept of &quot;Building in Public&quot; has been a great perspective for me in this regard, even though almost all it ever leads to is me writing <a href="https://blog.randombits.host/piframe/">half-baked blog posts</a> and creating things like my most recent project - <a href="https://github.com/conor-f/git-vain?ref=blog.randombits.host">git-vain</a>.</p><h1 id="buildlog">Buildlog</h1><p>&#x2003;Gitvain is a simple Python project that sends you a notification whenever the follower/stargazers on a Github change. It supports notifications using <a href="https://github.com/caronc/apprise?ref=blog.randombits.host">Apprise</a> which is a fantastic tool that allows you to specify some simple configuration and then send notifications using many, many services. <a href="https://github.com/PyGithub/PyGithub?ref=blog.randombits.host">PyGithub</a> is used to fetch changes on the watched repositories. There are many outstanding issues, that will most likely never be completed. This is by design, however, as I set out a plan to get to a point where I could be satisfied enough to leave it alone, say I sufficiently completed it, and thereby prevent my perfectionism from getting in the way.</p><p>&#x2003;A major part of my recent work in software development has focused around best practices and ops-adjacent work. Recognizing that setting up CI/CD at the start of a project is very easy compared to getting it set up midway through, and that actually <em>defining </em>the feature you want to work on and writing short functions with no side-effects to get it done makes you progress quickly. Being realistic with the scope of what you&apos;re working on allows you to keep motivation and leave your work in a state that&apos;s trivial to restart whenever you next get the chance. To achieve this, I simply started by writing out a minimum set of features I wanted to have before being happy to cut a <code>v1.0.0</code>. I then broke these features down into small components and added them to <a href="https://vikunja.io/?ref=blog.randombits.host">Vikunja</a> my favorite todo list application.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.randombits.host/content/images/2023/07/Screenshot-from-2023-07-11-00-35-33.png" class="kg-image" alt loading="lazy" width="1150" height="986" srcset="https://blog.randombits.host/content/images/size/w600/2023/07/Screenshot-from-2023-07-11-00-35-33.png 600w, https://blog.randombits.host/content/images/size/w1000/2023/07/Screenshot-from-2023-07-11-00-35-33.png 1000w, https://blog.randombits.host/content/images/2023/07/Screenshot-from-2023-07-11-00-35-33.png 1150w" sizes="(min-width: 720px) 720px"><figcaption>A satisfyingly complete todo list!</figcaption></figure><p>&#x2003;I started by getting a <a href="https://github.com/conor-f/git-vain/tree/8b5c9232bf9e08f7387ad8984908cd53973157b3?ref=blog.randombits.host">basic Python project set up</a> with continuous deployment using Github Actions to build a Dockerfile that would deploy to my home server. This is so simple that it literally just prints a Hello World statement in the Dockerfile! Even though this seems insignificant, from here you have the perfect jumping off point; a stable point you can solidly build on.</p><p>&#x2003;After this, I prioritised one type of event I wanted to be notified on - stargazers - and wrote a <a href="https://github.com/conor-f/git-vain/pull/1/files?ref=blog.randombits.host">basic script</a> to star/unstar a repo, and <a href="https://github.com/conor-f/git-vain/pull/2?ref=blog.randombits.host">another</a> to use <code>ntfy</code> to send a notification when this list changed. Already it was starting to take shape enough to keep my motivation going.</p><p>&#x2003;Once this was complete, I used the simple Python stdlib <a href="https://docs.python.org/3/library/shelve.html?ref=blog.randombits.host">shelve</a> to add some persistence, and integrated with <a href="https://github.com/caronc/apprise?ref=blog.randombits.host">Apprise</a> to support a wider variety of channels for notification.</p><p>&#x2003;There&apos;s no point in going into any further detail, because A) it&apos;s not very interesting, and B) it didn&apos;t progress much further! But this is a feature, not a bug - I set out with the intention of cutting a <code>v1.0.0</code> and this was almost there already!</p><p>&#x2003;But what next?</p><h1 id="next-steps">Next Steps</h1><p>&#x2003;I realised I had a few things I wanted to add as I was going through the development process. This is typical, and it&apos;s the source of all <a href="https://en.wikipedia.org/wiki/Feature_creep?ref=blog.randombits.host">feature creep</a>. I curtailed this by putting anything I wanted to do into Vikunja, and deciding to convert them to issues once <code>v1.0.0</code> had been completed. If I didn&apos;t, I would certainly still be adding little bits and pieces here and there now as opposed to enjoying the feeling of having &quot;finished&quot; a piece of work. Even if it only lasts as long as it takes to go back to working on <code>v2.0.0</code>!</p><p>&#x2003;Some things I want to improve in <code>v2.0.0</code> or in the next side project I work on are as follows:</p><!--kg-card-begin: markdown--><ul>
<li>Use pre-commit to add more stability and standardisation to the work
<ul>
<li>This feels particularly powerful and would be a good way to finally upgrade my development process which still consists of a pretty vanilla vim setup. Who knows? Maybe I&apos;ll even switch to NeoVim with this new-fangled LSP!</li>
</ul>
</li>
<li>There are some bugs such as not accounting for pagination of the response from Github.
<ul>
<li>I deliberately didn&apos;t try to address these during this effort as I could easily have gotten bogged down in it and it&apos;s a small, contained piece of work that should be easy to add in the future if I leave the project and come back to it after a period of time.</li>
</ul>
</li>
<li>SemVer and Conventional Commits seem to be a fantastic way to completely automate the release cycle.
<ul>
<li>If there&apos;s no Github Action for it, it shouldn&apos;t be too difficult to make either as I didn&apos;t find a suitable service for my needs so far - next project maybe??</li>
</ul>
</li>
<li>Python has a good few issues when it comes to naming conventions and project setup.
<ul>
<li>This is potentially exacerbated by the fact that the tooling I&apos;m familiar with is outdated by now.
<ul>
<li>Using setup.py works nicely (especially if you have experience with it) for a small project like this, but it&apos;s not a good idea for more serious work.</li>
</ul>
</li>
<li>The naming conventions I was having issues with surrounded small things like - vs _ vs camelCase. Package names vs module names vs binary names. Small things, that actually ended up adding some annoying decision fatigue and cognitive load to keep consistent.</li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown--><h1 id="conclusion">Conclusion</h1><p>&#x2003;The Python standards and conventions are a bit of a mess. Dev Ops/best practices actually <em>do</em> provide great help when used in conjunction with common sense, and the &quot;perfect&quot; project template likely cannot exist due to the subtle variations in what &quot;perfect&quot; looks like for different purposes.</p><p>&#x2003;Nonetheless, that&apos;s what my vanity is currently driving me towards finding, so for now, I&apos;ll keep looking.</p><p><em>&#x2003;&#x2003;Hopefully not in vain!</em></p>]]></content:encoded></item><item><title><![CDATA[Monitoring Self-Hosted Services]]></title><description><![CDATA[<p>I have been self-hosting for almost <s>two</s> three years now, and one thing I have never quite figured out is how to monitor all the applications I host. At this stage, there are approximately <em>forty</em> running Docker containers so I really should have some means of monitoring what&apos;s</p>]]></description><link>https://blog.randombits.host/monitoring-self-hosted-services/</link><guid isPermaLink="false">640205addcdb77000189f267</guid><dc:creator><![CDATA[Conor]]></dc:creator><pubDate>Wed, 07 Jun 2023 10:49:47 GMT</pubDate><content:encoded><![CDATA[<p>I have been self-hosting for almost <s>two</s> three years now, and one thing I have never quite figured out is how to monitor all the applications I host. At this stage, there are approximately <em>forty</em> running Docker containers so I really should have some means of monitoring what&apos;s going on on them and the general health of the server they are running on. Professionally, I have used <a href="https://www.splunk.com/?ref=blog.randombits.host">Splunk</a> and <a href="https://www.sumologic.com/?ref=blog.randombits.host">Sumo Logic</a> for monitoring services, but the open source solution I would prefer to use for this is <a href="https://grafana.com/?ref=blog.randombits.host">Grafana</a>. I have already set up Grafana to get logs from the <a href="https://via.randombits.host/?ref=blog.randombits.host">Via</a> app, and it seems to be a very widely used tool industry-wide, so it would be nice to not be completely in the dark on it! In particular, I will be using <a href="https://grafana.com/oss/loki/?ref=blog.randombits.host">Loki</a>, <a href="https://grafana.com/oss/prometheus/?ref=blog.randombits.host">Prometheus</a>, <a href="https://grafana.com/docs/loki/latest/clients/promtail/?ref=blog.randombits.host">Promtail</a>, <a href="https://github.com/prometheus/node_exporter?ref=blog.randombits.host">Node-Exporter</a>, and <a href="https://github.com/google/cadvisor?ref=blog.randombits.host">cAdvisor</a>. As I have basically no experience with any of these tools, I will summarize my research on them for you, and document how they interact with each other in my setup. After that, I will describe which data I wish to collect and for what purpose, before finally showing the dashboards/alerts I have made. Let&apos;s go!</p><h2 id="the-tools">The Tools</h2><h3 id="grafana">&#x2003;Grafana</h3><p>&#x2003;&#x2003;&#x2003;Lets start with the main one - what is Grafana? Grafana is at its core a web-based data visualization platform. It acts as a front end to many time-series databases, and uses plugins to consume data from different sources and support custom dashboard visualizations. &#xA0;It also has a simple graphical tool to help you craft queries on the data. The best place to try the Grafana platform out is at <a href="https://play.grafana.org/?ref=blog.randombits.host">play.grafana.org</a>.</p><h3 id="prometheus">&#x2003;Prometheus</h3><p>&#x2003;&#x2003;&#x2003;Prometheus is a time-series database which operates on a <code>pull</code> model. You configure exporters which will have metrics requested from them by Prometheus on a regular schedule. There is a suite of <a href="https://prometheus.io/docs/introduction/overview/?ref=blog.randombits.host#components">components</a> it can make use of, but one core feature we will be using is <a href="https://prometheus.io/docs/prometheus/latest/querying/examples/?ref=blog.randombits.host">PromQL</a> - the Prometheus Query Language. We will use this through Grafana to aggregate metrics collected by Prometheus. One thing that is important to note is that Prometheus is designed to work with numeric information only. This means it cannot be used to search through textual logs like you might do in Splunk or Sumo Logic.</p><h3 id="loki">&#x2003;Loki</h3><p>&#x2003;&#x2003;&#x2003;Being restricted to just working with metrics is quite a limitation, so we will also be using <a href="https://grafana.com/oss/loki/?ref=blog.randombits.host">Loki</a>. Loki encompasses a set of tools/services, but my working model of it doesn&apos;t extend much further than Prometheus for log lines. It accepts data in any format, and similar to Prometheus, it allows you to build metrics and alerts based on them.</p><h3 id="promtail">&#x2003;Promtail</h3><p>&#x2003;&#x2003;&#x2003;<a href="https://grafana.com/docs/loki/latest/clients/promtail/?ref=blog.randombits.host">Promtail</a> is responsible for delivering log lines from log files to Loki. It is the roughly equivalent component in the Loki stack as Node-Exporter is in the Prometheus stack. This is confusing as <em>Prom</em>tail looks like it should be part of the <em>Prom</em>etheus stack, but alas naming of open source tooling is never great!</p><p>&#x2003;&#x2003;&#x2003;Promtail will be used to collect log lines from containers of my own services, or of services being debugged.</p><h3 id="node-exporter">&#x2003;Node-Exporter</h3><p>&#x2003;&#x2003;&#x2003;<a href="https://grafana.com/oss/prometheus/exporters/node-exporter/?ref=blog.randombits.host">Node Exporter</a> monitors and exports hardware and kernel level metrics to Prometheus. It is highly configurable with a <a href="https://github.com/prometheus/node_exporter?ref=blog.randombits.host#collectors">long list</a> of metrics it can collect if you desire. Despite the warnings, we will be running <code>node-exporter</code> from a Docker container for now. This is just for ease of encapsulation until I can move my personal home server to using NixOS or similar.</p><p>&#x2003;&#x2003;&#x2003;This will provide the host-level metrics we need, such as CPU usage, RAM usage, free space, etc.</p><h3 id="cadvisor">&#x2003;cAdvisor</h3><p>&#x2003;&#x2003;&#x2003;From the <a href="https://github.com/google/cadvisor?ref=blog.randombits.host">cAdvisor Github page</a>:</p><blockquote>[cAdvisor] is a running daemon that collects, aggregates, processes, and exports information about running containers.</blockquote><p>&#x2003;&#x2003;&#x2003;<a href="https://github.com/google/cadvisor/blob/master/docs/storage/prometheus.md?ref=blog.randombits.host">These metrics</a> can be exposed for Prometheus, and will provide the per-container resource usage metrics we need.</p><h2 id="stack">Stack</h2><p>&#x2003;&#x2003;&#x2003;So now we have all the components explained, it&apos;s worthwhile visualizing the stack we will have. One crucial thing to remember is that while this seems like a large number of services, each one is very small and modular, so it won&apos;t be consuming a huge amount of resources.</p><figure class="kg-card kg-image-card"><img src="https://blog.randombits.host/content/images/2023/03/grafana.png" class="kg-image" alt loading="lazy" width="711" height="321" srcset="https://blog.randombits.host/content/images/size/w600/2023/03/grafana.png 600w, https://blog.randombits.host/content/images/2023/03/grafana.png 711w"></figure><p></p><h2 id="the-data">The Data</h2><p>&#x2003;&#x2003;&#x2003;Now we know the <em>how</em> of observability, we need to get to the <em>what</em>. Honestly, I spent a long time putting this off, probably because this was the largest gap in my knowledge! However, I think an iterative approach works best here anyways - both in iteratively building up to &quot;complete&quot; observability/insight, and iteratively building up my knowledge of the Grafana stack.<br>&#x2003;&#x2003;&#x2003;I suppose it makes sense to think about <em>why</em> I&apos;m making some monitoring on these services. Primarily it&apos;s to see what my server is capable of. i.e. do I need to add some RAM/storage/replace the entire CPU? How many additional containers can I run? Has there been a large spike in usage? If so by what containers/services? How much network input/output is each service going through? As a percentage of the whole input/output? How much storage is each container using? Secondly, I want to have insights into actual logs of my own services (or for others if I really want I guess? But primarily my homemade services). This should be all logs for debug purposes and usage metrics in general.<br>&#x2003;&#x2003;&#x2003;Lets make a list:</p><!--kg-card-begin: markdown--><ul>
<li>Host
<ul>
<li>Metrics
<ul>
<li>CPU Usage</li>
<li>RAM Usage</li>
<li>Storage Usage %</li>
<li>Load (1 min, 5 min, 15 min seems standard)</li>
<li>Network Throughput (Input/Output volume)</li>
</ul>
</li>
<li>Logs
<ul>
<li>syslog</li>
<li>auth.log</li>
</ul>
</li>
</ul>
</li>
<li>Per-Service
<ul>
<li>Metrics
<ul>
<li>CPU Usage %</li>
<li>RAM Usage</li>
<li>Storage Usage %</li>
<li>Network Throughput</li>
</ul>
</li>
<li>Logs
<ul>
<li>For specific containers</li>
</ul>
</li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown--><h2 id="the-implementation">The Implementation</h2><p>&#x2003;&#x2003;&#x2003;Now we know <em>what</em> we&apos;re observing, and <em>how</em> we&apos;re going to ingest it, we just need to do it! </p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="200" height="113" src="https://www.youtube.com/embed/RZGV9Z5Gvgs?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Completing the plan"></iframe><figcaption>I wish I could have found the author&apos;s site to link to and not Youtube...</figcaption></figure><p>&#x2003;&#x2003;&#x2003;Since we painstakingly mapped out the different component services, we can tell instantly that we need <code>cAdvisor</code> for the per-service metrics, <code>NodeExporter</code> for the host metrics, and <code>loki</code> for all the log lines. Lets start with the metrics.<br>&#x2003;&#x2003;&#x2003;The metrics all need to feed into <code>prometheus</code> in order to end up in Grafana, so we need to edit the prometheus config file in order to do that. For getting all our container metrics from <code>cAdvisor</code>, we just need a few lines. For <code>NodeExporter</code>, just a few more:</p><pre><code class="language-yaml">scrape_configs:
  - job_name: &quot;cadvisor&quot;
    scrape_interval: 15s
    static_configs:
      - targets: [&quot;cadvisor:8080&quot;]
  - job_name: &quot;node_exporter&quot;
    scrape_interval: 15s
    static_configs:
      - targets: [&quot;node-exporter:9100&quot;]</code></pre><p>&#x2003;&#x2003;&#x2003;Then Loki needs to be configured for <code>syslog</code> and <code>auth.log</code>. This was achieved by a simple promtail config and mapping <code>/var/log:/var/log/host_logs</code> in docker-compose:</p><pre><code class="language-yaml">scrape_configs:
- job_name: hostlogs_job
  static_configs:
  - targets:
      - localhost
    labels:
      job: hostlogs
      __path__: /var/hostlogs/*log
- job_name: docker_container_logs
  docker_sd_configs:
  - host: unix:///var/run/docker.sock
    refresh_interval: 5s
  relabel_configs:
    - source_labels: [&apos;__meta_docker_container_name&apos;]
      regex: &apos;/(.*)&apos;
      target_label: &apos;container&apos;
</code></pre><h2 id="alerting">Alerting</h2><p>&#x2003;Finally we have the full stack set up. Last remaining thing to get a semi-professional (emphasis on the semi!) is to get some alerting going. For alerting, I&apos;m going to use <a href="https://github.com/binwiederhier/ntfy?ref=blog.randombits.host">ntfy</a> and a small Grafana integration I found called <a href="https://github.com/kittyandrew/grafana-to-ntfy?ref=blog.randombits.host">grafana-to-ntfy</a>. This took a little more work than expected, but eventually I got it all working. Firstly, I set up a personal <code>ntfy</code> instance, then added the <code>grafana-ntfy</code> container to my docker-compose along with a simple env file as explained in the README. I then integrated it with Grafana alerting. One of the key things to note here is that I just used plain <code>http</code> for communication with the <code>grafana-ntfy</code> container as I couldn&apos;t get it set up with SSL! I kept getting invalid cert errors with reference to a cert only valid for Traefik. Also not fully documented, but the <code>BAUTH</code> variables need to be passed too although they should be made optional. May submit a PR for that... Follow the README to do a test notification</p><p>Set up a query to make sure notifications are coming through and then just get on with standard alarming!</p><h2 id="conclusion">Conclusion</h2><p>&#x2003;So, as you&apos;ve probably realized, I <em>really</em> lost steam towards the end of this post. I have been working on this post/stack setup for about three months and it has been frustrating me to no end and stopping me from writing things I would prefer to write about, and follow my current tech interests. I try to balance doing things I feel I <em>should</em> do with things that I have a strong (but usually fleeting) motivation to do as these rarely overlap. This time however, even though I can see the huge benefit of having a well set up monitoring stack for my home server and how all aspects of this will improve my quality of life when debugging/doing basic admin, the balance has just tipped to being more stressful than beneficial to me.</p><p>&#x2003;I will update my stack in the future, and hopefully write a more concise post on setting up a home server monitoring stack, but for now, this is all you get!</p>]]></content:encoded></item><item><title><![CDATA[PiFrame V2.0]]></title><description><![CDATA[<p>&#x2003;&#x2003;&#x2003;During my move, the microSD card that was driving my LED matrix to show Spotify covers broke. As I&apos;m stupid and had nothing backed up or in source control, I&apos;m now tasked with recreating it from scratch. I&apos;ll be using Docker</p>]]></description><link>https://blog.randombits.host/piframe/</link><guid isPermaLink="false">6410ed1bdcdb77000189f3ef</guid><category><![CDATA[Docker]]></category><category><![CDATA[Docker Compose]]></category><category><![CDATA[Self Hosted]]></category><category><![CDATA[Side Project]]></category><dc:creator><![CDATA[Conor]]></dc:creator><pubDate>Sat, 25 Mar 2023 18:21:32 GMT</pubDate><media:content url="https://blog.randombits.host/content/images/2023/03/cover.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.randombits.host/content/images/2023/03/cover.jpeg" alt="PiFrame V2.0"><p>&#x2003;&#x2003;&#x2003;During my move, the microSD card that was driving my LED matrix to show Spotify covers broke. As I&apos;m stupid and had nothing backed up or in source control, I&apos;m now tasked with recreating it from scratch. I&apos;ll be using Docker sitting on top of a Ubuntu 22.04 server edition running on a Raspberry Pi 3B+. I will be doing it all headless as I find it to be a much more streamlined approach than getting a HDMI cable, Bluetooth keyboard, and other accessories all in one place. </p><h2 id="preparation">Preparation</h2><p>&#x2003;&#x2003;&#x2003;First, grab a microSD card and get the Ubuntu image from <a href="https://ubuntu.com/download/raspberry-pi/thank-you?version=22.04.2&amp;architecture=server-arm64+raspi&amp;ref=blog.randombits.host">here</a>. Extract it using <code>xz</code> and finally use a <code>dd</code> command to flash it onto the card.<br><br>&#x2003;&#x2003;&#x2003;Be careful! Make sure you specify the correct device! I am deliberately using something wrong in the snippet below so you don&apos;t blindly copy paste your way to wiping your system. Find the correct device by running <code>lsblk</code> before and after you plug in the SD card. It should look something like <code>mmcblk0</code> or <code>sd[a|b|c|...]</code>. Also note that you don&apos;t use a partition number with the <code>dd</code> command when you are flashing a bootable image.</p><pre><code class="language-shell">$ xz -d ubuntu-22.04.2-preinstalled-server-arm64+raspi.img.xz
$ sudo dd if=ubuntu-22.04.2-preinstalled-server-arm64+raspi.img of=/dev/REPLACE_WITH_YOUR_DEVICE_ID bs=4M status=progress &amp;&amp; sync</code></pre><p>&#x2003;&#x2003;&#x2003;Once this completes, you have to configure your Pi for remote access. The easiest way to do this is by setting the hostname, and then plugging in your Pi via Ethernet. If you were also too lazy to go find an Ethernet cable like me, then you will also have to update your <code>network-config</code> file to automatically connect to your WiFi network on boot.</p><pre><code class="language-shell">$ sudo mount /dev/sda1 /mnt
$ # Set the hostname:
$ sudo vim /mnt/user-data
... Search for &quot;hostname&quot; and modify the line as needed before exiting the editor
$ # Set your network-config:
$ sudo vim /mnt/network-config
... Modify as needed and quit.
$ # Finally tell cloud-init to reboot after applying your settings:
$ echo &quot;power_state:\n&quot; &gt;&gt; /mnt/user-data
$ echo &quot;\tmode: reboot&quot; &gt;&gt; /mnt/user-data
$ sudo umount /dev/sda1</code></pre><p>&#x2003;&#x2003;&#x2003;For more details on the cloud-init and network-config modifications, check <a href="https://github.com/DavidUnboxed/Ubuntu-20.04-WiFi-RaspberyPi4B?ref=blog.randombits.host">here</a> for a good explanation. Either way, by now you should have a Pi you can simply SSH directly into with a set hostname!</p><h3 id="installing-docker">Installing Docker</h3><p>&#x2003;&#x2003;&#x2003;To ease the development process, and to make the service easy to maintain/update in the future, I&apos;m going to be running everything through Docker. Setting up Docker is a pretty straightforward process. I&apos;ve copied the commands here for ease of getting started.</p><pre><code class="language-shell">$ sudo apt update &amp;&amp; sudo apt upgrade -y &amp;&amp; sudo reboot
$ curl -fsSL https://get.docker.com -o get-docker.sh &amp;&amp; sudo sh ./get-docker.sh
$ # Now allow running docker commands as non-root user:
$ sudo groupadd docker  # This is likely already created
$ sudo usermod -aG docker $USER
$ newgrp docker
$ # Test it:
$ docker run hello-world
$ # Now configure Docker to start on boot with systemd
$ sudo systemctl enable docker.service
$ sudo systemctl enable containerd.service</code></pre><h2 id="testing-a-docker-composeyaml">Testing a docker-compose.yaml</h2><p>&#x2003;&#x2003;&#x2003;The easiest way I have found to run multiple containers is to manage them through <code>docker compose</code>. I often test my setup with a simple <code>docker-compose.yaml</code> file once I think I have everything set up and ready to go. Here&apos;s the file I used to test this setup. If you are connected to your local network, you should then be able to type in the hostname of your device into your browser and see the <code>whoami</code> page.</p><pre><code class="language-yaml">version: &quot;3.3&quot;

services:

  traefik:
    image: &quot;traefik:v2.9&quot;
    container_name: &quot;traefik&quot;

    restart: &quot;unless-stopped&quot;

    command:
      #- &quot;--log.level=DEBUG&quot;
      #- &quot;--api.insecure=true&quot;
      - &quot;--providers.docker=true&quot;
      - &quot;--providers.docker.exposedbydefault=false&quot;
      - &quot;--entrypoints.web.address=:80&quot;
    ports:
      - &quot;80:80&quot;
      - &quot;8080:8080&quot;
    volumes:
      - &quot;/var/run/docker.sock:/var/run/docker.sock:ro&quot;

  whoami:
    image: &quot;traefik/whoami&quot;
    container_name: &quot;simple-service&quot;

    restart: &quot;unless-stopped&quot;

    labels:
      - &quot;traefik.enable=true&quot;
      - &quot;traefik.http.routers.whoami.rule=Host(`piframe`)&quot;
      - &quot;traefik.http.routers.whoami.entrypoints=web&quot;</code></pre><h2 id="v001">V0.0.1</h2><p>&#x2003;&#x2003;&#x2003;Now that all the prerequisites are set up, it&apos;s time to move onto <code>V0.0.1</code> of the project. I always find it good to spend some time setting out clearly defined goals to get to a <code>V1.0.0</code> and for me, <code>V0.0.1</code> always involves setting up continuous deployment and making sure I can view log messages/errors clearly. This involved setting up Github Actions to automatically build a Docker image and push it to Docker Hub on a push to the main branch or when a tag is pushed of the format <code>Vx.y.z</code>. You can see how the code looked at this point over at the <a href="https://github.com/conor-f/piframe/tree/v0.0.1?ref=blog.randombits.host">repo</a>. At this point, I also set up <a href="https://containrrr.dev/watchtower/?ref=blog.randombits.host">Watchtower</a> via a few lines in my <code>docker-compose.yaml</code>, which monitors my container for updates, and automatically pulls and restarts the container when it changes.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.randombits.host/content/images/2023/03/working_caching.png" class="kg-image" alt="PiFrame V2.0" loading="lazy" width="1919" height="871" srcset="https://blog.randombits.host/content/images/size/w600/2023/03/working_caching.png 600w, https://blog.randombits.host/content/images/size/w1000/2023/03/working_caching.png 1000w, https://blog.randombits.host/content/images/size/w1600/2023/03/working_caching.png 1600w, https://blog.randombits.host/content/images/2023/03/working_caching.png 1919w" sizes="(min-width: 1200px) 1200px"><figcaption>On the left, the container building (with nice caching!) through Github Actions, and on the right, the container running, updating, and logging on my host.</figcaption></figure><h2 id="v002">V0.0.2</h2><p>&#x2003;&#x2003;&#x2003;This version was centred around getting <em>anything</em> to display on the LED matrix. I completed all of this outside of a Docker container in the interest of development speed, keeping in mind that everything I do outside of the container should be reproducible within one. <a href="https://learn.adafruit.com/adafruit-rgb-matrix-bonnet-for-raspberry-pi?view=all&amp;ref=blog.randombits.host">Here</a> is the Adafruit documentation on the specific hardware I was using which was essential for configuring the hardware (which I&apos;m not going to cover unless requested!). During this, I found the authoritative resource on all things Raspberry Pi + LED Matrix related: <a href="https://github.com/hzeller/rpi-rgb-led-matrix/?ref=blog.randombits.host">hzeller&apos;s rpi-rgb-led-matrix repository</a>. Then it was simply a matter of running the following to get a basic demo square running.</p><pre><code class="language-shell">ubuntu@piframe:~/matrix_test$ git clone https://github.com/hzeller/rpi-rgb-led-matrix/
ubuntu@piframe:~/matrix_test/rpi-rgb-led-matrix$ make -C examples-api-use</code></pre><h2 id="v003">V0.0.3</h2><p>&#x2003;&#x2003;&#x2003;This step was to make the work I had done outside of Docker reproducible and working from my CD workflow. I additionally added some tools to the Docker image at this point to ease working with the container. e.g. installing <code>vim</code> and <code>git</code>. This may seem very pedantic and slow of an approach, but my goal when I am doing personal projects like this is to keep each step achievable, reproducible, and small. It allows me to easily leave a project and return to it days or weeks later and pick up where I left off. I find that breaking personal projects down into these achievable blocks makes finishing them much more likely. As usual, you can see the repo at this point <a href="https://github.com/conor-f/piframe/tree/v0.0.3?ref=blog.randombits.host">here</a>.</p><h2 id="v004">V0.0.4</h2><p>&#x2003;&#x2003;&#x2003;As the ultimate goal is to display full images on the LED matrix, I sought out an example that did just that. At this point we can display an image on the screen only using a command line interface which is workable, but definitely something to try improve in the future. Regardless <code><a href="https://github.com/conor-f/piframe/tree/v0.0.4?ref=blog.randombits.host">V0.0.4</a></code> was complete!</p><h2 id="v005">V0.0.5</h2><p>&#x2003;&#x2003;&#x2003;This is where we integrate with Spotify. Plan of action is to make a loop that periodically checks if Spotify is online and if so, display the album artwork. If nothing is playing, clear the screen. As I had previously worked with the Spotify API before while making <a href="https://github.com/conor-f/spotibar?ref=blog.randombits.host">Spotibar</a>, I decided to just pull that in as a dependency because I was already familiar with the setup of it.<br>&#x2003;&#x2003;&#x2003;As I had time, and was really annoyed at the command line interface for displaying an image, I decided to set up Python bindings as shown <a href="https://github.com/hzeller/rpi-rgb-led-matrix/tree/master/bindings/python?ref=blog.randombits.host">here</a>. This made development much smoother. <a href="https://github.com/conor-f/piframe/commit/de498eb6035b15cd29c7a148f2372d66e05b5b6c?ref=blog.randombits.host">Here</a> is the code as of that point. One thing of note is that I am continually SSHing into the Pi to test commands on the Docker container before putting them to the Dockerfile! </p><h2 id="the-doldrums">The Doldrums</h2><p>&#x2003;&#x2003;&#x2003;At this point, I had begun to slack on setting clear targets as I thought I was so close to the finish line. However, this is where the <a href="https://en.wikipedia.org/wiki/Pareto_principle?ref=blog.randombits.host">Pareto principle</a> struck. I had a very difficult time authorizing the Spotify API without having to take awkward steps. I tried a few different things (including switching to trying Last.fm instead of Spotify!) but ended up making a number of changes to <code>spotibar</code> to allow installation to just have to include one extra step as follows:</p><p>&#x2003;1) Add the <code>docker-compose.yaml</code> file to your Pi<br>&#x2003;2) Run <code>docker compose pull</code><br>&#x2003;3) Run <code>sudo docker compose run -it piframe spotibar --init</code><br>&#x2003;4) Follow the Spotibar <a href="https://github.com/conor-f/spotibar?ref=blog.randombits.host#installation">instructions</a><br>&#x2003;&#x2003;4.1) For config filepath, put in <code>/app/config/spotibar_config.json</code><br>&#x2003;&#x2003;4.2) For auth path, put in <code>/app/config/spotibar_auth_cache</code><br>&#x2003;&#x2003;4.3) Ignore any errors. Just look for the line &quot;Successfully authenticated.&quot;<br> &#xA0; &#xA0;Optional) <code>sudo chmod -R 777 config/</code> to remove some errors from the logs<br>&#x2003;5) <code>docker compose up -d</code></p><h2 id="finishing-touches">Finishing Touches</h2><p>&#x2003;&#x2003;&#x2003;As the project was transitioning from PoC to MVP stage, I wanted to put some gloss on it. The main issue was that the image still flickered quite a lot. To address that, I changed the boot options to disable audio as explained in the hzeller repo&apos;s <a href="https://github.com/hzeller/rpi-rgb-led-matrix?ref=blog.randombits.host#troubleshooting">Troubleshooting section</a>. However, there was a slight issue with the documentation where it used the wrong path, which I diligently created a <a href="https://github.com/hzeller/rpi-rgb-led-matrix/pull/1524?ref=blog.randombits.host">PR</a> to update the documentation to save people some time in the future. Alongside this, I supported the configuration of parameters of the LED Matrix through <a href="https://github.com/conor-f/piframe/blob/v1.0.0/src/piframe.py?ref=blog.randombits.host#L30">environment variables</a> as suggested by the <a href="https://12factor.net/config?ref=blog.randombits.host">12-factor app</a>.</p><h2 id="v100">V1.0.0</h2><p>&#x2003;&#x2003;&#x2003;At this point, I was happy to cut <code>V1.0.0</code>. I had achieved all my initial goals, and now had something I could plug into my wall and leave running confident in its reliability. I tested everything from scratch, took some pictures, and called it a day!</p><h2 id="finishing-thoughts">Finishing Thoughts</h2><ul><li>Continuous Deployment is <em>not</em> only for large projects or for your job. It makes your life far easier and allows you to jump in/out of a project at ease, knowing you won&apos;t forget some magic incantation if you stop working on it for a week. It&apos;s also quite easy to set up, and is almost templatable.</li><li>I hit major issues when I stopped breaking down my work into small chunks. This could be a coincidence, but the quality of my output was significantly lower when I didn&apos;t set clear goals.</li><li>The 80/20 rule hits hard.</li></ul><p>Thank you for reading this, and if you have any feedback, you can reach me on <a href="https://mastodon.social/@conorf?ref=blog.randombits.host">Mastodon</a>, by email (via anything at this hostname.tld), or any other way you can find me! Anything from content changes to advice on my writing style is appreciated!</p>]]></content:encoded></item><item><title><![CDATA[DIYnDNS - The Lengths I Will Go to Not Pay My ISP for a Static IP Address]]></title><description><![CDATA[<figure class="kg-card kg-image-card"><img src="https://blog.randombits.host/content/images/2023/02/4fc53ae8bb19ab51a9d72da70318a149-3.jpg" class="kg-image" alt loading="lazy" width="500" height="700"></figure><p>DNS is infuriating to me. It exists in the uncanny valley of technologies where on paper I know a decent amount about it, but in reality I just flounder aimlessly and ultimately end up in an IRC channel somewhere spilling my woes to any (seemingly mystically intelligent) kind soul who</p>]]></description><link>https://blog.randombits.host/diyndns/</link><guid isPermaLink="false">63f3c7be32cd480001b97dbb</guid><category><![CDATA[Docker]]></category><category><![CDATA[Self Hosted]]></category><category><![CDATA[DNS]]></category><dc:creator><![CDATA[Conor]]></dc:creator><pubDate>Mon, 20 Feb 2023 20:55:00 GMT</pubDate><content:encoded><![CDATA[<figure class="kg-card kg-image-card"><img src="https://blog.randombits.host/content/images/2023/02/4fc53ae8bb19ab51a9d72da70318a149-3.jpg" class="kg-image" alt loading="lazy" width="500" height="700"></figure><p>DNS is infuriating to me. It exists in the uncanny valley of technologies where on paper I know a decent amount about it, but in reality I just flounder aimlessly and ultimately end up in an IRC channel somewhere spilling my woes to any (seemingly mystically intelligent) kind soul who will help me debug my issues. This does not pair well with trying to host your own server in your living room.</p><p>I had all my DNS issues tidied up enough to forget about them (the ideal state of affairs for DNS in my opinion) while I was living in Ireland and my ISP by default gave out static IPv4 addresses, or if not, changed them so infrequently that I didn&apos;t experience a change in over a year. However, with a new ISP in Berlin, I had quite a different experience. After congratulating myself on the quality of the infrastructure-as-code I had developed, so that even after 5 months of downtime, I could just plug in my server again, connect it to the WiFi, and it was back online, I went to bed happy. The following morning, however, I woke up to find that my IP address had changed overnight. I gave the DNS provider a call, and they informed me that a static IP address can easily be provided, but it will cost an additional ten euro per month. They reassign IP addresses every 24 hours too, just to be annoying about it. This means you can&apos;t rely on a rarely updating dynamic IP address either.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.randombits.host/content/images/2023/02/Screenshot-from-2023-02-20-21-14-36.png" class="kg-image" alt="Screenshot of a chart and some raw data showing an IP address being updated once per 24 hours." loading="lazy" width="1840" height="582" srcset="https://blog.randombits.host/content/images/size/w600/2023/02/Screenshot-from-2023-02-20-21-14-36.png 600w, https://blog.randombits.host/content/images/size/w1000/2023/02/Screenshot-from-2023-02-20-21-14-36.png 1000w, https://blog.randombits.host/content/images/size/w1600/2023/02/Screenshot-from-2023-02-20-21-14-36.png 1600w, https://blog.randombits.host/content/images/2023/02/Screenshot-from-2023-02-20-21-14-36.png 1840w" sizes="(min-width: 720px) 720px"><figcaption>What&apos;s this wonderful tool updating my IP address every 24 hours?? Also, ignore the day I turned my server off by accident and was very confused why nothing was working...</figcaption></figure><p>I felt personally attacked at the thought of paying for a static IP address, and for some inexplicable reason, I didn&apos;t want to use existing Dynamic DNS solutions. Maybe some vague notion of not wanting another external resource that I&apos;d have to pay for or could break due to a misconfiguration, but most likely it was just stubbornness. Enter <a href="https://github.com/conor-f/diyndns?ref=blog.randombits.host">DIYnDNS</a>! A simple Python script that will check if my IP address has changed in the last N minutes, and if so, connect to Cloudflare, update my DNS records to point at the new IP address, then go back to sleep until it notices the IP address has changed again.</p><p>This is all pretty straightforward, but there are a few nice aspects and things I learned along the way.</p><!--kg-card-begin: markdown--><ul>
<li>First of all, this is all packaged up in a nice and small Docker container, which allows me to also make a simple <code>docker-compose</code> file to allow it to be installed with ease on any reverse proxy setup like <code>Caddy</code> or <code>Traefik</code>.</li>
<li>Secondly, it uses a plain <code>.ini</code> file for configuration, which I never understood why people used before. I think I&apos;ll be using <code>.ini</code> files for my configuration in future Python projects, primarily due to the inbuilt <code>configparser</code> library and how easy it was to use.</li>
<li>Thirdly, there&apos;s a <a href="https://containrrr.dev/watchtower/?ref=blog.randombits.host">watchtower</a> container defined in the <code>docker-compose</code> file too, which will automatically update the <code>diyndns</code> container whenever there&apos;s a new image pushed to docker hub.</li>
<li>Finally, this container is updated on every push to the repository&apos;s <code>main</code> branch using some straightforward Github Actions CI.</li>
</ul>
<!--kg-card-end: markdown--><p>The fact that all of this is possible with just some glorified configuration files and one easy to throw together python script is amazing to me. I really feel like anyone who isn&apos;t on the boat of self-hosting simple services, and understanding even the basics of how to put together a full CI/CD system like this, is missing out.</p><p>Finally, there are a good few areas that need to be improved. For starters, I should be able to configure the <code>cron</code> from the config file somehow, but I had horrific trials trying to get the <code>crontab</code> to respect env vars, follow my <code>PATH</code> etc. Best solution for this is likely to put it in a <code>systemd</code> service file, or else some baked in Python threading tool. I also think my usage of <code>configparser</code> could be better. I want to explore the library more so I can use it for other projects in the near future.</p><p>Thank you for reading, and please get in touch if you have any comments, advice, or suggestions. My next post will be about the hassle I had and the effort I put in to try getting this setup working with Namecheap, my original DNS provider. It was <em>significantly</em> more involved than this and ultimately was ridiculous enough that I moved my DNS to Cloudflare!</p>]]></content:encoded></item><item><title><![CDATA[Writing on your Github Contributions Heatmap]]></title><description><![CDATA[<p>I&apos;ve thought for a while now that the Github contributions heatmap is a particularly uninteresting page. This thought popped into my head once more after reading a <a href="https://jwiegley.github.io/git-from-the-bottom-up/?ref=blog.randombits.host">fantastic ebook on the fundamentals of Git</a>, so armed with new confidence and knowledge of Git commits, I decided to fake</p>]]></description><link>https://blog.randombits.host/writing-on-your-github-contributions-heatmap/</link><guid isPermaLink="false">62d3d95a3c4bf30001cd20ce</guid><dc:creator><![CDATA[Conor]]></dc:creator><pubDate>Sun, 17 Jul 2022 10:23:09 GMT</pubDate><media:content url="https://blog.randombits.host/content/images/2022/07/Screenshot-from-2022-07-17-10-41-31.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.randombits.host/content/images/2022/07/Screenshot-from-2022-07-17-10-41-31.png" alt="Writing on your Github Contributions Heatmap"><p>I&apos;ve thought for a while now that the Github contributions heatmap is a particularly uninteresting page. This thought popped into my head once more after reading a <a href="https://jwiegley.github.io/git-from-the-bottom-up/?ref=blog.randombits.host">fantastic ebook on the fundamentals of Git</a>, so armed with new confidence and knowledge of Git commits, I decided to fake contributions on my profile in order to spell out words on my heatmap.</p><p>I knew that the contributions chart responded to commits in a Git repo, along with issues, pull requests, and other Github specific interactions. The commits seemed to be the easiest to fake (although I am a big fan of the relatively new <a href="https://cli.github.com/?ref=blog.randombits.host">Github CLI</a>), so I found where dates are considered in a commit object and tried faking them to see if Github would accept them. The only two places I could see were the author date and the commit date. Faking the committer date was a simple matter of prepending the <code>git commit</code> command with an env variable <code>GIT_COMMITTER_DATE</code>. Changing the author date was even easier as <code>git commit</code> accepts a <code>--date</code> argument! So making a commit for a specific date came up to be:</p><p><code>$ LC_ALL=C GIT_COMMITTER_DATE=&quot;$(date --date=&apos;01/01/1970 12:00&apos;)&quot; git commit -a -m&quot;01/01/1970 12:00&quot; --no-edit --date &quot;$(date --date=&apos;01/01/1970 12:00&apos;)&quot;</code></p><p>Running this will give you a commit from 01/01/1970 which unsurprisingly is the earliest date you can have a commit on. We&apos;re not stopping there though, even though it is funny how the UI is broken on your profile page after doing that.</p><figure class="kg-card kg-image-card"><img src="https://blog.randombits.host/content/images/2022/07/Peek-2022-07-17-11-04.gif" class="kg-image" alt="Writing on your Github Contributions Heatmap" loading="lazy" width="1136" height="636" srcset="https://blog.randombits.host/content/images/size/w600/2022/07/Peek-2022-07-17-11-04.gif 600w, https://blog.randombits.host/content/images/size/w1000/2022/07/Peek-2022-07-17-11-04.gif 1000w, https://blog.randombits.host/content/images/2022/07/Peek-2022-07-17-11-04.gif 1136w" sizes="(min-width: 720px) 720px"></figure><p>Now with a basis to build on, I decided to package this up into a simple Python script, and add some helper methods around it. Namely, being able to &quot;draw&quot; in a particular <code>(x, y)</code> position on the heatmap.</p><!--kg-card-begin: markdown--><pre><code class="language-python">def get_origin_datetime(year):
    &quot;&quot;&quot;
    Returns the datetime of (0,0) on the contributions heatmap.

    This is horrific code.
    &quot;&quot;&quot;
    d = datetime.datetime(year, 1, 1, 12)

    while d.weekday() != 6:
        d += datetime.timedelta(1)

    return d


def xy_to_datetime(year, x, y):
    return get_origin_datetime(year) + datetime.timedelta((7 * x) + y)
</code></pre>
<!--kg-card-end: markdown--><p>Then I used a handy <a href="http://dotmatrixtool.com/?ref=blog.randombits.host">Dot Matrix Tool</a> to create a font which was 3x5 pixels, and translated each character into a simple template:</p><!--kg-card-begin: markdown--><pre><code>    &apos;s&apos;: [
        (0, 0), (1, 0), (2, 0),
        (0, 1),
        (0, 2), (1, 2), (2, 2),
                        (2, 3),
        (0, 4), (1, 4), (2, 4),
    ],
</code></pre>
<!--kg-card-end: markdown--><p>With this done, there was only one method left to implement and we were good to go:</p><!--kg-card-begin: markdown--><pre><code>def draw_letter(year, letter, letter_number):
    &quot;&quot;&quot;
    The letter_number is to calculate the offset to apply in the grid.
    &quot;&quot;&quot;
    x_offset = letter_number * (LETTER_WIDTH + INTER_LETTER_SPACE_WIDTH)
    # This centres the letter vertically
    y_offset = 1

    for coord in LETTERS[letter]:
        commit_on_xy(year, x_offset + coord[0], y_offset + coord[1])
</code></pre>
<!--kg-card-end: markdown--><p>To view this more sensibly, check out this <a href="https://gist.github.com/conor-f/93221bb74fb522d0e98029c48417eac0?ref=blog.randombits.host">gist</a>. The only improvement I&apos;m thinking about at the minute is to include an opacity/darkness option on each pixel that is drawn to add a bit more detail to the lettering. But for a few hours on a Saturday morning, I&apos;m happy with the result :)</p>]]></content:encoded></item><item><title><![CDATA[Infrastructure-First Development]]></title><description><![CDATA[<p>Unless you&apos;re a very particular kind of person, project management and system infrastructure are the last things on your mind when you undertake a new side project or startup. For a side project, the fun is in taking the motivation from having an idea, prototyping an MVP as</p>]]></description><link>https://blog.randombits.host/infrastructure-first-development/</link><guid isPermaLink="false">6208f86c31a6d600011a47a9</guid><category><![CDATA[Side Project]]></category><category><![CDATA[Self Hosted]]></category><category><![CDATA[Quick Tip]]></category><dc:creator><![CDATA[Conor]]></dc:creator><pubDate>Sun, 13 Feb 2022 13:37:56 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1519389950473-47ba0277781c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDN8fGluZnJhc3RydWN0dXJlJTIwZGV2ZWxvcG1lbnR8ZW58MHx8fHwxNjQ0NzU1MDk4&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1519389950473-47ba0277781c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDN8fGluZnJhc3RydWN0dXJlJTIwZGV2ZWxvcG1lbnR8ZW58MHx8fHwxNjQ0NzU1MDk4&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Infrastructure-First Development"><p>Unless you&apos;re a very particular kind of person, project management and system infrastructure are the last things on your mind when you undertake a new side project or startup. For a side project, the fun is in taking the motivation from having an idea, prototyping an MVP as quickly as possible, and following the shiny thoughts as much as you can. In a startup environment, the impetus for change is now customer feedback/demand, a pivoting idea, or features that were previously unaccounted for. In both cases however, the priority is speed. Speed of development and ability to dynamically adjust is what will make your side project fulfilling to work on (or even lead to you actually feeling you completed it!), or let your startup gain an edge over your competitors.</p><p>Most people will actively agree with this as a concept, but will then barrel straight into working as hard as possible leading to frustration and burn out. Inevitably when you couple rapidly changing goals with trying to keep pace with them, you&apos;re in for a bad time for the simple asymmetry of the work. It takes 30 seconds to point out an improvement that could take 3 days to implement! Once you think about this asymmetry and how frequently it occurs and the imbalances it causes, you&apos;ll see it everywhere and will try temper it with a renewed desire for planning and prioritization. Maybe even some of those dreaded &#xA0;<em>a g i l e &#xA0;t e c h n i q u e s</em> useful...</p><p>I&apos;m not there yet, don&apos;t worry. I&apos;m not going to turn into some agile-scrum-coach-master, but all I want to do is preach the virtue of having an infrastructure-first work focus. When rapidly changing goals are made the top priority, things get out of hand very quickly, especially if there&apos;s no reconciliation period to deal with the issues. Tech debt racks up, work is frequently duplicated as quickly developed, non-refactored code is hard to reuse, and ultimately you end up with <em>something</em> that while potentially fulfilling the goals, is near-impossible to sustainably work with. I suggest treating your infrastructure management almost as a pre-requisite of doing the work.</p><p>Given all the issues I pointed out above, the quickest antidote I can see is optimizing your common tasks. You will have to engage with rapidly changing goals if you&apos;re doing interesting things, it&apos;s almost a fact of doing them! You can however, in many cases predict the specific infrastructure you&apos;re going to be working with as you have a finite set of primary skills. For me (currently) they revolve around Vue for front-end, Python for back-end, and a mix of Github Actions, Docker, AWS Lambda, SQL-like databases, and some more bits for the glue. I have a set of infrastructure tooling built around these technologies that will let me spin up an end-to-end of a Vue static site, calling a REST API interacting with a MySQL database or reaching out to some serverless methods all running on a custom domain with prod/test environments set up all in less than half a day. That&apos;s just not possible for someone sitting down to manually do all this work and then worse again, what happens when they want to add a few new endpoints!</p><p>This realization of the outsized impact of infrastructure tooling is new to me, but when I get the chance I will be pushing all of these to Github to share with everyone, but for now a list will have to suffice! I suggest as a minimum the following:</p><ul><li>A Github Action to publish a Python package to PIP.</li><li>A Github Action to publish a Docker image to a hub.</li><li>A Github Action to publish a serverless method live.</li><li>A Github Action to run tests on PRs.</li><li>A few <a href="https://github.com/cookiecutter/cookiecutter?ref=blog.randombits.host">cookiecutter</a> templates to package combinations of these together with dummy code to get up and running with development (e.g. a Makefile that has rules for running tests and building a virtualenv, a <code>src</code> and <code>test</code> directory, and a basic <code>main.py</code> that will give a simple HTTP 200 return code for a serverless method cookiecutter).</li><li>A logging framework that incorporates invocation IDs and ideally logs to some central infrastructure. Invocation IDs are simply an ID that you reuse through one execution of the code path. This allows you to grep your logs for one ID and see all relevant logs to just that execution without guessing which logs are related from a group of random logs!</li><li>And as an extra, a mindset change. If there are tasks that you forsee being time consuming or frequently disruptive (sending notification texts/emails, creating new users, etc etc) then try find a way to automate them. Having the ability to start a long running command then just pipe it to a generic notifier is incredibly useful!<br></li></ul>]]></content:encoded></item><item><title><![CDATA[Why You Should Walk In The Bus Lane]]></title><description><![CDATA[<p>I&apos;m a huge advocate for public transport, and as is typical in <a href="https://europa.eu/eurobarometer/surveys/detail/1110?ref=blog.randombits.host">Europe</a>, my attitude towards private cars in an urban environment is on the negative side. My opinion however, may be much more negative than most! To preface this, I have a driver&apos;s license, and</p>]]></description><link>https://blog.randombits.host/why-you-should-walk-in-the-bus-lane/</link><guid isPermaLink="false">61e6ad402d5db10001f734d6</guid><category><![CDATA[Light Hearted]]></category><category><![CDATA[Non-Tech]]></category><category><![CDATA[Cycling]]></category><category><![CDATA[Socio-political Hot Takes]]></category><dc:creator><![CDATA[Conor]]></dc:creator><pubDate>Wed, 26 Jan 2022 06:57:53 GMT</pubDate><media:content url="https://images.unsplash.com/9/barcelona-traffic.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDQ1fHxidXMlMjBsYW5lfGVufDB8fHx8MTY0MjUwNzM2Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/9/barcelona-traffic.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDQ1fHxidXMlMjBsYW5lfGVufDB8fHx8MTY0MjUwNzM2Ng&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Why You Should Walk In The Bus Lane"><p>I&apos;m a huge advocate for public transport, and as is typical in <a href="https://europa.eu/eurobarometer/surveys/detail/1110?ref=blog.randombits.host">Europe</a>, my attitude towards private cars in an urban environment is on the negative side. My opinion however, may be much more negative than most! To preface this, I have a driver&apos;s license, and I do see the need for some private car ownership and use within a city (for example, people with mobility issues or work vehicles), but cities almost universally have given far too much importance to the private car and not half enough to alternative forms of transport like cycling, walking, and public transit.</p><p>I&apos;ve always believed that people will do the easiest thing over the &quot;right&quot; thing if there is any form of friction to doing the &quot;right&quot; thing<sup>[1]</sup>. In this vein, governments posit that they want to increase use of these alternative forms of transport, but haven&apos;t concretely made it easier to use alternative forms of transport instead of private cars, and so, all their &quot;efforts&quot; end up having unsatisfactory results. I think the problem is two-fold. On the one hand, the &quot;effort&quot; being put in really isn&apos;t substantial enough to actually make the difference expected, but secondly, the investment is just in the wrong place. An example of both of these in action was the boom in cycling during the COVID-19 pandemic. As widely written about, cities all around Europe <a href="https://www.bbc.com/news/world-europe-54353914?ref=blog.randombits.host">allocated extra money towards cycling infrastructure</a>, and there were so many people buying bikes that <a href="https://www.theguardian.com/world/2020/jun/09/we-sold-eight-bikes-in-20-minutes-will-the-cycling-boom-last?ref=blog.randombits.host">shops just couldn&apos;t keep up</a>. While great on the surface, what has transpired is that the extra money and infrastructure was temporary, and all the new bike owners are giving way to the danger they are put in by poor infrastructure and reverting to previous habits. People will do the easiest thing, and in this case, that is the safest.</p><p>The primary reason that roads are so dangerous for cyclists are private car use coupled with this lack of cyclist-first infrastructure (you only need to look at <a href="https://joyride.city/blog/amsterdam-tips-during-a-bike-boom/?ref=blog.randombits.host">Amsterdam&apos;s focus on infrastructure</a> to see how safety should be done at that level, but how and ever). With the assumption that people cannot force the development of infrastructure from an unwilling government, what can people do? In my opinion, people need to assert their ownership of their city. A city-space is not, and should not, be designed for the speed of cars over the safety and comfort of individuals. One of the few examples of <a href="https://en.wikipedia.org/wiki/Shared_space?ref=blog.randombits.host">shared space</a> are common bus lanes, meant to accommodate buses, taxis, and cyclists in the one lane of road. These are a huge benefit to cyclists over the alternative of fighting with all traffic, but they are constantly abused by entitled private car drivers to skip traffic, or by design in some cases where they are legally allowed to be used by private cars at certain times of day! Our cities would all have less noise and air pollution, be orders of magnitude safer, and encourage small businesses and green spaces all over if the private car was removed from them. The city streets would rightfully prioritize people instead of cars, and this is the reason I think people should walk in bus lanes (under certain conditions!).</p><p>You have more of a right to walk where you like in the city than a private car does. If you walk in the bus lane when there are no buses, bikes, or taxis around, then you are asserting to everyone who passes that this space could be used by people and it could be designed in a new way to maximize human comfort and enjoyment. It also has the added benefit of stopping entitled drivers from deciding they deserve to skip everyone in the traffic, which I&apos;m sure everyone appreciates!</p><p>[1] I looked for a source to this as I expected there to have been studies done on this phenomenon, but I didn&apos;t find anything. Let me know if you are aware of anything in this area.</p>]]></content:encoded></item><item><title><![CDATA[Sane Vim Configs on Remote Instances]]></title><description><![CDATA[<p>Vim is my editor of choice. Alongside the shell, it&apos;s my full IDE and I don&apos;t see much reason to change that. I can build all scaffolds and supports necessary to be as proficient (if not faster!) than people who rely solely on the capabilities of</p>]]></description><link>https://blog.randombits.host/sane-vim-configs-on-remote-instances/</link><guid isPermaLink="false">61d583c22d5db10001f7346f</guid><category><![CDATA[Vim]]></category><category><![CDATA[Side Project]]></category><category><![CDATA[Quick Tip]]></category><dc:creator><![CDATA[Conor]]></dc:creator><pubDate>Wed, 05 Jan 2022 08:08:00 GMT</pubDate><media:content url="https://blog.randombits.host/content/images/2022/01/Screenshot-from-2022-01-05-11-45-01.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.randombits.host/content/images/2022/01/Screenshot-from-2022-01-05-11-45-01.png" alt="Sane Vim Configs on Remote Instances"><p>Vim is my editor of choice. Alongside the shell, it&apos;s my full IDE and I don&apos;t see much reason to change that. I can build all scaffolds and supports necessary to be as proficient (if not faster!) than people who rely solely on the capabilities of their graphical IDE. Not only does building these type of supports make me learn more about the nitty-gritty of any project like the way different packages are built/deployed, or how imports are managed, but once it&apos;s done for one style of project, I usually end up with a readily transferable add-in to all similar projects! Whenever I &quot;have&quot; to use an IDE (I&apos;m still looking for a way to sanely work with Android for example!), the only plugin or setting I need to find before I can do anything meaningful is adding the <a href="https://plugins.jetbrains.com/plugin/164-ideavim?ref=blog.randombits.host">relevant</a> <a href="https://marketplace.visualstudio.com/items?itemName=vscodevim.vim&amp;ref=blog.randombits.host">Vim</a> <a href="https://github.com/XVimProject/XVim2?ref=blog.randombits.host">plugin</a> for that IDE. Really says a lot that there&apos;s so many, doesn&apos;t there....</p><p>For that reason, I was getting really frustrated when I was connecting to some Raspberry Pi&apos;s I was doing some side projects on (blogs to come!), or when I had to SSH into a fresh AWS instance and I couldn&apos;t use my own <code>.vimrc</code>! I decided I needed to address this, so introducing <sub>some of the most shoddily thrown together work I have ever done...</sub> <a href="https://github.com/conor-f/vim-stuff?ref=blog.randombits.host">vim-stuff</a>! Name, along with everything else, is clearly still work in progress!</p><p>This essentially will let you set up a nice set of defaults for a Vim config quickly on any machine. All you have to do is clone and run <code>make install</code>. This will give you <a href="https://github.com/jacoborus/tender.vim?ref=blog.randombits.host">a nice colour scheme</a> + <a href="https://github.com/sheerun/vim-polyglot?ref=blog.randombits.host">syntax highlighting</a>, <a href="https://github.com/preservim/nerdtree?ref=blog.randombits.host">NERDTree</a>, <a href="https://github.com/pechorin/any-jump.vim?ref=blog.randombits.host">AnyJump</a> (a personal underdog favourite), and a good few more! Give it a try if you need to get a decent Vim install quickly and easily on the go :)</p>]]></content:encoded></item><item><title><![CDATA[Robot Olympics]]></title><description><![CDATA[<p>I&apos;m not a staunch nationalist, nor am I an athlete of any description, yet I find myself in the same quadrennial boat as most others - the Olympics is an entertaining event to watch. Good-spirited competition drives people to push themselves to the extremes and work together to</p>]]></description><link>https://blog.randombits.host/robot-olympics/</link><guid isPermaLink="false">6182a608fa38ec000157388d</guid><category><![CDATA[Light Hearted]]></category><category><![CDATA[Non-Tech]]></category><dc:creator><![CDATA[Conor]]></dc:creator><pubDate>Wed, 03 Nov 2021 19:28:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1501514799070-290ae1c889fe?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDh8fGZsYWdzfGVufDB8fHx8MTYzNTk1MjI2NQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1501514799070-290ae1c889fe?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDh8fGZsYWdzfGVufDB8fHx8MTYzNTk1MjI2NQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Robot Olympics"><p>I&apos;m not a staunch nationalist, nor am I an athlete of any description, yet I find myself in the same quadrennial boat as most others - the Olympics is an entertaining event to watch. Good-spirited competition drives people to push themselves to the extremes and work together to achieve their goals. The athlete is only the tip of the iceberg, backed by coaches, nutritionists, therapists, and I&apos;m sure more teams of supports. Together, they dedicate themselves to one singular goal, and I want to see that happen for technology. <a href="https://www.inverse.com/science/olympic-world-records-broken-2021-science?ref=blog.randombits.host">The rate of improvement in human performance</a> backing these records is diminishing, and technology improvements be it in <a href="https://www.businessinsider.com/nike-runners-trounce-olympics-competitors-super-spike-shoe-technology-2021-8?r=US&amp;IR=T&amp;ref=blog.randombits.host">shoes</a>, <a href="https://apnews.com/article/2020-tokyo-olympics-elaine-thompson-track-and-field-dalilah-muhammad-technology-b9e92bd41a7dd218d942abccc2318079?ref=blog.randombits.host">track</a>, or anything in between, is increasingly the reason records are being shaved down. So why not embrace this? What would that even look like?</p><p>To foster the same level of excitement and dedication as the Olympics, lets mirror the structure of the Olympics as closely as possible and replicate as many events as we can with robot competitors. This will allow people who aren&apos;t interested in purely technical feats of engineering to recognize the level of performance on display by comparison to the main events. The primary entry requirement is that the robot must be able to autonomously perform the event to a standard above what the human record is. In short, lets have country teams make robots compete in the Olympic events!</p><p>In keeping with the theme of the Olympics, and to allow the host nation have another event to try recoup the enormous (and wildly varying) <a href="https://en.wikipedia.org/wiki/Cost_of_the_Olympic_Games?ref=blog.randombits.host#Table">costs</a>, lets host our new robot Olympics in the same locations as the Summer Olympics, but a few months after the main event. There also will have to be some regulation about the robots in order to keep an event running smoothly. The competitors must be able to negotiate their way to and from &quot;neutral&quot; zones autonomously. We want to get rid of the idea of someone wheeling out a huge, static trebuchet right up to the javelin line and saying it&apos;s good to go!</p><p>On the topic of trebuchets, there&apos;s one important caveat to these Robot Olympics. Repeatability. A competitor&apos;s skill should be somewhat consistent, and to that end, instead of it being a simple &quot;what&apos;s the best you can do?&quot;, the result a competitor achieves is the simple average of their attempts. In events where there&apos;s usually only one result considered (e.g. did you clear the high jump bar or not? Did you win the 100m race or not?) the success must be repeated (e.g. for these examples, you must clear the high jump bar 3/5 attempts or the 100m is raced three times and each result is the average of them). After all, nobody is interested in how high you can jump by strapping explosives underneath a robot and shooting it out of the stadium if it can&apos;t do it again!</p><p>Finally, there shouldn&apos;t be any explosive/inhuman elements to a competitor. They can&apos;t be 10m tall doing the long jump or 3,000 kg doing the hammer throw! Similarly, using explosives/combustion/similar is forbidden. We&apos;re focusing on kinetic movement here. The robots don&apos;t need to be humanoid, bipedal, or anything like <a href="https://www.youtube.com/watch?v=tF4DML7FIWk&amp;ref=blog.randombits.host">Boston Dynamics&apos; newest dystopian concept</a>, just:</p><ol><li>Capable of self-directing itself to the event start point, fulfilling the event on cue and returning to where it was released from.</li><li>Be able to perform the event consistently above the peak human level without destroying itself.</li><li>Not use any form of combustion.</li></ol><p>I really want this to be a legitimate event and I don&apos;t see much reason something better fleshed out couldn&apos;t be a reality in the future. I would be more than willing to be on the steering committee if someone wants to give me a warm introduction to the Olympic council. Naturally before I do this, I&apos;ll want to hear feedback, thoughts and further restrictions/improvements anyone has so leave a comment below or reach out to me by email so we can make this a reality!</p>]]></content:encoded></item><item><title><![CDATA[Syncing Mobile Photos with Photoprism]]></title><description><![CDATA[<figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://demo-cdn.photoprism.org/static/img/logo-avatar.svg" class="kg-image" alt="Photoprism Logo" loading="lazy"><figcaption>Photoprism Logo</figcaption></figure><p><a href="https://demo.photoprism.org/?ref=blog.randombits.host">Photoprism</a> is the self-hosted photo/video library that does it all for me. It has labeling of images so you can search by picture content, it has calendar and map features for more natural finding of the pictures you&apos;re trying to find and it lets you</p>]]></description><link>https://blog.randombits.host/syncing-mobile-photos-with-photoprism/</link><guid isPermaLink="false">616e707dfa38ec00015737d3</guid><category><![CDATA[Quick Tip]]></category><category><![CDATA[Self Hosted]]></category><dc:creator><![CDATA[Conor]]></dc:creator><pubDate>Tue, 19 Oct 2021 21:35:05 GMT</pubDate><content:encoded><![CDATA[<figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://demo-cdn.photoprism.org/static/img/logo-avatar.svg" class="kg-image" alt="Photoprism Logo" loading="lazy"><figcaption>Photoprism Logo</figcaption></figure><p><a href="https://demo.photoprism.org/?ref=blog.randombits.host">Photoprism</a> is the self-hosted photo/video library that does it all for me. It has labeling of images so you can search by picture content, it has calendar and map features for more natural finding of the pictures you&apos;re trying to find and it lets you have public/private content as you choose. To be clear, I want our reliance on huge mega-corps to be gotten rid of. I don&apos;t think that necessarily implies some form of <a href="https://en.wikipedia.org/wiki/Neo-Luddism?ref=blog.randombits.host">neo-luddism</a> or that we should have to deal with worse products. We get what we create and support, and this project is definitely worthy of both. Pictures are some of the most personal things we routinely create so we should try protect these moments from prying eyes. Moreover, with <a href="https://9to5google.com/2021/06/18/google-photos-storage-guide/?ref=blog.randombits.host">Google Photos continuing to degrade their product</a> by reneging on their promise of free unlimited storage and by silently <a href="https://www.theverge.com/2021/5/24/22451607/google-photos-high-quality-storage-saver-tool-free-space-blurry-screenshots?ref=blog.randombits.host">degrading your picture quality</a> an alternative should be welcomed!</p><p>From self-hosting Photoprism, I&apos;ve found it to be quite a smooth process relative to the size and scale of its features. The one process I found difficult to get working was one of the most crucial however; syncing with my Android phone. I mostly take 35mm analog pictures (because I&apos;m insufferable) but I appreciate being able to scroll back through my screenshots for conversations with friends and taking a quick picture on the go. I found Photoprism&apos;s <a href="https://docs.photoprism.org/user-guide/sync/mobile-devices/?ref=blog.randombits.host">guide</a> however lacking to solve the use case of my phone automatically uploading new content to Photoprism and having it imported ready for viewing, so I tried a few different apps and settled on <a href="https://play.google.com/store/apps/details?id=dk.tacit.android.foldersync.lite&amp;hl=en_IE&amp;gl=US&amp;ref=blog.randombits.host">FolderSync</a>.</p><p>FolderSync allows you to sync files between different locations on your phone or use <a href="https://en.wikipedia.org/wiki/WebDAV?ref=blog.randombits.host">WebDAV</a> to sync with an external location. Photoprism supports WebDAV so this should be a cinch! I decided to set up one folder on my device that would one-way upload to Photoprism and then locally sync any files in other media folders on my phone to that folder. This might be a needless step, but I think it&apos;s useful for supporting multiple different ingestion locations and also optionally not indiscriminately syncing all media from your phone with a potentially public library! From screenshots, my FolderSync setup looks something like this for SD Card Sync:</p><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161644_FolderSync.jpg" width="1080" height="2400" loading="lazy" alt srcset="https://blog.randombits.host/content/images/size/w600/2021/10/Screenshot_20211019-161644_FolderSync.jpg 600w, https://blog.randombits.host/content/images/size/w1000/2021/10/Screenshot_20211019-161644_FolderSync.jpg 1000w, https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161644_FolderSync.jpg 1080w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161653_FolderSync.jpg" width="1080" height="2400" loading="lazy" alt srcset="https://blog.randombits.host/content/images/size/w600/2021/10/Screenshot_20211019-161653_FolderSync.jpg 600w, https://blog.randombits.host/content/images/size/w1000/2021/10/Screenshot_20211019-161653_FolderSync.jpg 1000w, https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161653_FolderSync.jpg 1080w" sizes="(min-width: 720px) 720px"></div></div><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161703_FolderSync.jpg" width="1080" height="2400" loading="lazy" alt srcset="https://blog.randombits.host/content/images/size/w600/2021/10/Screenshot_20211019-161703_FolderSync.jpg 600w, https://blog.randombits.host/content/images/size/w1000/2021/10/Screenshot_20211019-161703_FolderSync.jpg 1000w, https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161703_FolderSync.jpg 1080w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161714_FolderSync.jpg" width="1080" height="2400" loading="lazy" alt srcset="https://blog.randombits.host/content/images/size/w600/2021/10/Screenshot_20211019-161714_FolderSync.jpg 600w, https://blog.randombits.host/content/images/size/w1000/2021/10/Screenshot_20211019-161714_FolderSync.jpg 1000w, https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161714_FolderSync.jpg 1080w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption>SD Sync Settings Screenshots&#xA0;</figcaption></figure><p>And for WebDAV we have the following:</p><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161736_FolderSync.jpg" width="1080" height="2400" loading="lazy" alt srcset="https://blog.randombits.host/content/images/size/w600/2021/10/Screenshot_20211019-161736_FolderSync.jpg 600w, https://blog.randombits.host/content/images/size/w1000/2021/10/Screenshot_20211019-161736_FolderSync.jpg 1000w, https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161736_FolderSync.jpg 1080w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161743_FolderSync.jpg" width="1080" height="2400" loading="lazy" alt srcset="https://blog.randombits.host/content/images/size/w600/2021/10/Screenshot_20211019-161743_FolderSync.jpg 600w, https://blog.randombits.host/content/images/size/w1000/2021/10/Screenshot_20211019-161743_FolderSync.jpg 1000w, https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161743_FolderSync.jpg 1080w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161749_FolderSync.jpg" width="1080" height="2400" loading="lazy" alt srcset="https://blog.randombits.host/content/images/size/w600/2021/10/Screenshot_20211019-161749_FolderSync.jpg 600w, https://blog.randombits.host/content/images/size/w1000/2021/10/Screenshot_20211019-161749_FolderSync.jpg 1000w, https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161749_FolderSync.jpg 1080w" sizes="(min-width: 720px) 720px"></div></div><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161755_FolderSync.jpg" width="1080" height="2400" loading="lazy" alt srcset="https://blog.randombits.host/content/images/size/w600/2021/10/Screenshot_20211019-161755_FolderSync.jpg 600w, https://blog.randombits.host/content/images/size/w1000/2021/10/Screenshot_20211019-161755_FolderSync.jpg 1000w, https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161755_FolderSync.jpg 1080w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161851_FolderSync.jpg" width="1080" height="2400" loading="lazy" alt srcset="https://blog.randombits.host/content/images/size/w600/2021/10/Screenshot_20211019-161851_FolderSync.jpg 600w, https://blog.randombits.host/content/images/size/w1000/2021/10/Screenshot_20211019-161851_FolderSync.jpg 1000w, https://blog.randombits.host/content/images/2021/10/Screenshot_20211019-161851_FolderSync.jpg 1080w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption>WebDAV FolderSync Settings Screenshots</figcaption></figure><p>Using these, you should be able to set up automatic photo syncing from your Android phone to Photoprism! If you have any issues, refer back to the <a href="https://docs.photoprism.org/user-guide/sync/mobile-devices/?ref=blog.randombits.host">Photoprism guide</a> for this topic as it&apos;s definitely more up to date, &#xA0;thorough, and useful than this short post. However, I strongly recommend FolderSync over their suggestions of PhotoSync and SMBSync2!</p>]]></content:encoded></item><item><title><![CDATA[Publishing a Package to Pip]]></title><description><![CDATA[<figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.randombits.host/content/images/2021/10/image.png" class="kg-image" alt loading="lazy" width="248" height="186"><figcaption>PyPi - The Python Package Index</figcaption></figure><p>I love Python. It&apos;s easy to read, even easier to write, and best of all, the easiest language (I&apos;ve used) to interact with other people&apos;s code. I spent too long being only on one end of that equation</p>]]></description><link>https://blog.randombits.host/publishing-a-package-to-pip/</link><guid isPermaLink="false">6165e485b1d5c50001eba661</guid><dc:creator><![CDATA[Conor]]></dc:creator><pubDate>Tue, 12 Oct 2021 21:14:33 GMT</pubDate><content:encoded><![CDATA[<figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.randombits.host/content/images/2021/10/image.png" class="kg-image" alt loading="lazy" width="248" height="186"><figcaption>PyPi - The Python Package Index</figcaption></figure><p>I love Python. It&apos;s easy to read, even easier to write, and best of all, the easiest language (I&apos;ve used) to interact with other people&apos;s code. I spent too long being only on one end of that equation though, only consuming code other people have written as opposed to taking the things I was making and making them public so the cycle could continue. I felt it was too much hassle to get started with and for no reason because who would be using the libraries I write anyways? After finally learning how to create and publish packages to <a href="https://pypi.org/?ref=blog.randombits.host">PyPi</a>, I&apos;m glad to report it&apos;s super easy and definitely worthwhile. Even if it&apos;s just so you can easily <code>import</code> code you wrote for a previous project without going into the horrors of relative imports, I feel it&apos;s worth the five minutes investment to get up and running with it.</p><p>First of all, you need to <a href="https://pypi.org/account/register/?ref=blog.randombits.host">make a PyPi account</a> and then add your credentials to <code>~/.pypirc</code>. Your file should look something like this:</p><pre><code class="language-toml">[distutils]
    index-servers = pypi
    
[pypi]
    repository: https://upload.pypi.org/legacy/
    username: &lt;Your PyPi username&gt;
    password: &lt;Your PyPi password&gt;</code></pre><p><br>Realistically, you should set up API keys, but I am just trying to show the bare minimum to get up and running with PyPi. I am in no way an authority &#xA0;on this and most often don&apos;t even follow best practices.</p><p>Next take a simple project or tool you&apos;ve been working on and impose some <em>structure.</em> Personally, I am a fan of simple tools that do things I understand. Consequentially, I end up using tools that started falling out of fashion in the 80s. I find <code>Makefile</code> to be just the right level of build tool for me. I understand the file at a glance, I can hack it to get what I want done, and it <strong>is</strong> going to be the topic of another blog post so I&apos;m not going to dwell on it too much here, suffice to say you should add a <code>Makefile</code> to the root of your repository/project and have these key rules:</p><pre><code class="language-make">PYTHON=python3.8

ENV_DIR=.env_$(PYTHON)
IN_ENV=. $(ENV_DIR)/bin/activate &amp;&amp;

upload_pip: build_dist
    twine upload --repository pypi dist/*

build:
    $(IN_ENV) $(PYTHON) -m pip install --editable .
    rm -fr dist/
    $(IN_ENV) $(PYTHON) setup.py sdist bdist_wheel

build_dist:
    rm -fr dist/
    $(IN_ENV) python setup.py sdist

setup:
    $(PYTHON) -m pip install --upgrade virtualenv
    $(PYTHON) -m virtualenv -p $(PYTHON) $(ENV_DIR)
    $(IN_ENV) $(PYTHON) -m pip install --upgrade -r requirements.txt
    $(IN_ENV) $(PYTHON) -m pip install --editable .</code></pre><p><br>This will let you run <code>make upload_pip</code> and magically have your package arrive on PyPi once you install <code>python3 -m pip install twine</code>. But wait, what is it going to upload?? That&apos;s defined in your <code>setup.py</code>:</p><pre><code class="language-python">from setuptools import (
    find_packages,
    setup
)

INSTALL_REQUIRES = []

setup(
    name=&apos;my-first-pip-package-name&apos;,
    description=&apos;This is my first Pip package!&apos;,
    version=0.0.1,
    url=&apos;https://github.com/link/to/your/repo&apos;,
    python_requires=&apos;&gt;=3.6&apos;,
    packages=find_packages(&apos;src&apos;),
    package_dir={&apos;&apos;: &apos;src&apos;},
    install_requires=INSTALL_REQUIRES,
    entry_points={
        &apos;console_scripts&apos;: []
    }
)</code></pre><p>This should be pretty self evident for what should be where, &#xA0;but examples are the best way to grok something and here are two <a href="https://github.com/conor-f/timewarp?ref=blog.randombits.host">personal</a> <a href="https://github.com/conor-f/spotibar?ref=blog.randombits.host">repos</a> where I use these structures for <code>Makefile</code> and <code>setup.py</code> to publish Pip packages.</p><p>I know this is quite a low quality introduction to making a Pip package, so please leave me feedback. I want to improve at my writing and hearing responses is the best way I can get there!</p>]]></content:encoded></item><item><title><![CDATA[Auto-Updating Docker Containers]]></title><description><![CDATA[<p>Docker is the biggest advance to production software engineering in the past decade. If you&apos;re like me though, you slept on it this entire time and are now feeling like you&apos;re too far behind to catch up and finally start using them. I&apos;m going</p>]]></description><link>https://blog.randombits.host/auto-updating-docker-containers/</link><guid isPermaLink="false">615c1fd7bb9dca00013d8ef3</guid><category><![CDATA[Docker]]></category><category><![CDATA[Docker Compose]]></category><category><![CDATA[Quick Tip]]></category><dc:creator><![CDATA[Conor]]></dc:creator><pubDate>Tue, 05 Oct 2021 10:07:34 GMT</pubDate><content:encoded><![CDATA[<p>Docker is the biggest advance to production software engineering in the past decade. If you&apos;re like me though, you slept on it this entire time and are now feeling like you&apos;re too far behind to catch up and finally start using them. I&apos;m going to make a few short posts on the rudimentary basics to get up and running with Docker, creating a <code>Dockerfile</code>, publishing it to <a href="https://hub.docker.com/?ref=blog.randombits.host">Docker Hub</a> and deploying/running it live on a server. This post will show you how to set up a <code>docker-compose.yaml</code> file which will automatically update when a new image is pushed to Docker Hub. Let&apos;s go!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://images.unsplash.com/photo-1464725220624-b292bb3a600c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDN8fHdhdGNodG93ZXJ8ZW58MHx8fHwxNjMzNDI3NzIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" class="kg-image" alt loading="lazy" width="6000" height="4000" srcset="https://images.unsplash.com/photo-1464725220624-b292bb3a600c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDN8fHdhdGNodG93ZXJ8ZW58MHx8fHwxNjMzNDI3NzIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=600 600w, https://images.unsplash.com/photo-1464725220624-b292bb3a600c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDN8fHdhdGNodG93ZXJ8ZW58MHx8fHwxNjMzNDI3NzIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1000 1000w, https://images.unsplash.com/photo-1464725220624-b292bb3a600c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDN8fHdhdGNodG93ZXJ8ZW58MHx8fHwxNjMzNDI3NzIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1600 1600w, https://images.unsplash.com/photo-1464725220624-b292bb3a600c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDN8fHdhdGNodG93ZXJ8ZW58MHx8fHwxNjMzNDI3NzIz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2400 2400w" sizes="(min-width: 720px) 720px"><figcaption>Photo by <a href="https://unsplash.com/@veroz?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Andy Mai</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></figcaption></figure><p>First of all, I would not suggest doing this for anything you want to remain stable or have any decent uptime on. This is to keep up to date with the latest bleeding edge of a project and can be useful for development and side projects when you merge your code you can show it to people live a few minutes later. If you do this with containers you&apos;re looking to rely on (such as Matrix, Vikunja or Home Assistant (ohh, foreshadowing?)), you can easily see how you could accidentally deploy to a breaking version and have to do some messy sysadmin work! However, there are valid reasons to automatically update to the <code>latest</code> tag, and to do this, we will be using <a href="https://github.com/containrrr/watchtower?ref=blog.randombits.host">Watchtower</a> which is a Docker container that monitors other containers and if their image updates, then will <code>SIGTERM</code> it, pull down the newest version and restart it. Pretty meta! Onto the code:</p><pre><code class="language-yaml">version: &apos;3.3&apos;

services:
  via-web:
    container_name: &quot;via-web&quot;
    image: conorjf/via-web:latest
    restart: unless-stopped

    volumes:
      - /var/log/instance_logs/via:/var/log

  via-watchtower:
    container_name: &quot;via-watchtower&quot;
    image: containrrr/watchtower
    restart: unless-stopped

    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

    command: via-web --interval 30</code></pre><p>This relatively simple <code>docker-compose.yaml</code> shows how easy this is. We define two services, <a href="https://github.com/RobertLucey/via?ref=blog.randombits.host">Via</a> and <a href="https://github.com/containrrr/watchtower?ref=blog.randombits.host">Watchtower</a>. Via&apos;s image is set to <code>latest</code> from Docker Hub and it uses a simple trick to easily view logs explained <a href="blog.randombits.host/simple-splunk-log-ingestion-from-docker-containers/">here</a>. This is clearly something under development and so it&apos;s safe to set it up to deploy <code>latest</code> blindly. The <code>via-watchtower</code> service is also very simple, really only requiring 2 statements apart from the image. The <code>volumes</code> is so Watchtower can be informed about Docker updates/changes in state, etc and the <code>command</code> is of the format <code>&lt;container to watch for updates on&gt; --interval &lt;seconds to check for updates&gt;</code>. It&apos;s that easy! Using this and some continuous integration to build and deploy the Docker image, Via is kept constantly up to date for development and beta testing.</p><p>I hope this was instructive and please reach out if you have any questions, suggestions or comments in general :)</p>]]></content:encoded></item><item><title><![CDATA[Building Your First Docker Image]]></title><description><![CDATA[<p>Docker is the biggest advance to production software engineering in the past decade. If you&apos;re like me though, you slept on it this entire time and are now feeling like you&apos;re too far behind to catch up and finally start using them. I&apos;m going</p>]]></description><link>https://blog.randombits.host/building-your-first-docker-image/</link><guid isPermaLink="false">615bff42bb9dca00013d8e47</guid><category><![CDATA[Docker]]></category><category><![CDATA[Getting Started]]></category><dc:creator><![CDATA[Conor]]></dc:creator><pubDate>Tue, 05 Oct 2021 08:08:49 GMT</pubDate><content:encoded><![CDATA[<p>Docker is the biggest advance to production software engineering in the past decade. If you&apos;re like me though, you slept on it this entire time and are now feeling like you&apos;re too far behind to catch up and finally start using them. I&apos;m going to make a few short posts on the rudimentary basics to get up and running with Docker, creating a <code>Dockerfile</code>, publishing it to <a href="https://hub.docker.com/?ref=blog.randombits.host">Docker Hub</a> and deploying/running it live on a server. This post will cover making a simple <code>Dockerfile</code> and building your image. Let&apos;s go!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://images.unsplash.com/photo-1605745341075-1b7460b99df8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGRvY2tlcnxlbnwwfHx8fDE2MzM0MTkzNTc&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" class="kg-image" alt loading="lazy" width="3412" height="2275" srcset="https://images.unsplash.com/photo-1605745341075-1b7460b99df8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGRvY2tlcnxlbnwwfHx8fDE2MzM0MTkzNTc&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=600 600w, https://images.unsplash.com/photo-1605745341075-1b7460b99df8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGRvY2tlcnxlbnwwfHx8fDE2MzM0MTkzNTc&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1000 1000w, https://images.unsplash.com/photo-1605745341075-1b7460b99df8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGRvY2tlcnxlbnwwfHx8fDE2MzM0MTkzNTc&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1600 1600w, https://images.unsplash.com/photo-1605745341075-1b7460b99df8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGRvY2tlcnxlbnwwfHx8fDE2MzM0MTkzNTc&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2400 2400w" sizes="(min-width: 720px) 720px"><figcaption>Photo by <a href="https://unsplash.com/@carrier_lost?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Ian Taylor</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></figcaption></figure><p>A <code>Dockerfile</code> is a simple, declarative file that contains the commands for how to create a Docker image. I find the easiest way to work with them is to consider them the sequence of commands you&apos;d have to run on a fresh machine in order to get your project up and running. Lets take a look at this simple example from <a href="https://github.com/RobertLucey/via?ref=blog.randombits.host">Via</a>:</p><pre><code class="language-dockerfile">FROM ubuntu:20.04

WORKDIR /code

RUN apt -y update &amp;&amp; apt -y install build-essential python-numpy python-setuptools python3-scipy libatlas-base-dev libatlas3-base python3-pip

RUN python3 -m pip install virtualenv

COPY requirements.txt requirements.txt
RUN python3 -m pip install -r requirements.txt

EXPOSE 8080
COPY . .

CMD [&quot;make&quot;, &quot;run_bottle&quot;]</code></pre><p><code>FROM ubuntu:20.04</code> Most (if not all... I&apos;m not experienced with this, really you should be finding a better source of information!) <code>Dockerfile</code>s start with a <code>FROM</code> statement. This defines what base image you want to used and is published and maintained by people who really know what they&apos;re doing. In this case we are basing our environment on <code>ubuntu:20.04</code>. Generally speaking this is a bad idea as it is a big image and you should use something like <a href="https://hub.docker.com/_/alpine?ref=blog.randombits.host">Alpine</a>. Using a big image like Ubuntu is helpful however for bootstrapping or for dealing with difficult requirements.</p><p><code>WORKDIR /code</code> simply defines the current working directory within the container. I consider this to be like <code>cd</code> from the container context.</p><p><code>RUN ...</code> This is where things start looking bash-y. The <code>RUN</code> command very simply runs whatever commands you give it within the container context. Here we&apos;re installing updates through <code>apt</code> and creating a Python virtual environment.</p><p><code>COPY</code> is more interesting as it is passing information between the host system and the container context. Here we are copying the <code>requirements.txt</code> file from the root of the repository to <code>/code/requirements.txt</code> (as the last <code>WORKDIR</code> statement placed us within that directory!).</p><p><code>EXPOSE</code> is used to open ports on the container. It wraps up what I&apos;m sure are some disgusting networking hassles and lets you just say what ports you want to map from the machine running the container to the container itself. Here we&apos;re picking <code>8080</code> because that&apos;s the port we have configured <a href="https://bottlepy.org/docs/dev/?ref=blog.randombits.host">Bottle</a> to run on.</p><p>Another <code>COPY</code> statement in order to copy all our code in our repo to the <code>/code</code> dir on the container and then finally...</p><p><code>CMD</code>. This is more nuanced and should probably be just used to specify arguments to an <code>ENTRYPOINT</code> statement, but we&apos;re just looking at getting up and running. This can be considered to be the command run when the container is started. Here we&apos;re using a <code>make</code> rule to start our server.</p><p>Now our <code>Dockerfile</code> is all made, lets build and run it!</p><pre><code class="language-bash">docker build -t conorjf/via-web .
docker run conorjf/via-web:latest</code></pre><p>Simple! the <code>docker build -t &lt;tag_name&gt; .</code> uses the Dockerfile in the current directory (and the context of the current directory) to build your image with the tag <code>tag_name</code>. <code>docker run</code> will then run the image and tag you specify. All going well, your image should now be accessible on your localhost and you can drop into a shell on it by running <code>docker exec -it conorjf/via-web bash</code> and do whatever looking around you&apos;d like to see what kind of a container you&apos;ve built!</p><p>I am a big advocate of learning just enough to get started with something and when you are using it, you can begin to discover the nuance and best practices in a breadth-first way wherever your curiosity points. I highly recommend checking out the <a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/?ref=blog.randombits.host">Dockerfile Best Practices</a>. Please leave me any feedback at all as I have never written content like this and would like to make it as informative as possible!</p>]]></content:encoded></item></channel></rss>