<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Posts on ivy (likethevine)</title>
        <link>https://ivylikethevine.com/posts/</link>
        <description>Recent content in Posts on ivy (likethevine)</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <lastBuildDate>Sat, 10 Feb 2024 00:00:00 +0000</lastBuildDate>
        <atom:link href="https://ivylikethevine.com/posts/index.xml" rel="self" type="application/rss+xml" />
        
        <item>
            <title>Saffron - (S)erver (A)s a (F)ile (F)older (R)unning (O)n (N)etwork</title>
            <link>https://ivylikethevine.com/posts/saffron-server-as-a-file-folder-running-on-network/</link>
            <pubDate>Sat, 10 Feb 2024 00:00:00 +0000</pubDate>
            
            <guid>https://ivylikethevine.com/posts/saffron-server-as-a-file-folder-running-on-network/</guid>
            <description>&lt;p&gt;
















  
  
  
    
    
    
    
    
    
    
  &lt;figure class=&#34;center d-block text-center&#34; &gt;
  &lt;picture  &gt;
    &lt;source srcset=&#34;https://ivylikethevine.com/posts/saffron-server-as-a-file-folder-running-on-network/images/dashboard_hu_edcd5bebe1f00f31.webp&#34; type=&#34;image/webp&#34; /&gt;
  &lt;img class=&#34;figure-img img-fluid&#34; src=&#34;https://ivylikethevine.com/posts/saffron-server-as-a-file-folder-running-on-network/images/dashboard_hu_1bf8a75c58ce6f87.468425f50ab708bb6083057e9f1beb42.png&#34; alt=&#34;Dashboard&#34; title=&#34;My saffron dashboard&#34; loading=&#34;lazy&#34; height=&#34;418&#34; width=&#34;750&#34; /&gt;
&lt;/picture&gt;

  &lt;figcaption class=&#34;center&#34;&gt;My saffron dashboard&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h1 id=&#34;saffron-is-a-docker-compose-based-homelab&#34;&gt;&lt;strong&gt;Saffron&lt;/strong&gt; is a docker compose based homelab&lt;/h1&gt;
&lt;p&gt;It is deployable via static files, and managed as a git repository: &lt;em&gt;&lt;a href=&#34;https://github.com/ivylikethevine/saffron&#34;&gt;Public Git Repository&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td style=&#34;border: none&#34;&gt;
&lt;p&gt;
















  
  &lt;picture  &gt;
  &lt;img class=&#34;img-fluid&#34; src=&#34;https://img.shields.io/github/repo-size/ivylikethevine/saffron&#34; alt=&#34;GitHub repo size&#34; loading=&#34;lazy&#34; /&gt;
&lt;/picture&gt;
&lt;/p&gt;
&lt;/td&gt;
&lt;td style=&#34;border: none&#34;&gt;
&lt;p&gt;
















  
  &lt;picture  &gt;
  &lt;img class=&#34;img-fluid&#34; src=&#34;https://img.shields.io/github/last-commit/ivylikethevine/saffron&#34; alt=&#34;GitHub last commit&#34; loading=&#34;lazy&#34; /&gt;
&lt;/picture&gt;
&lt;/p&gt;
&lt;/td&gt;
&lt;td style=&#34;border: none&#34;&gt;
&lt;p&gt;
















  
  &lt;picture  &gt;
  &lt;img class=&#34;img-fluid&#34; src=&#34;https://img.shields.io/github/stars/ivylikethevine/saffron&#34; alt=&#34;GitHub Repo stars&#34; loading=&#34;lazy&#34; /&gt;
&lt;/picture&gt;
&lt;/p&gt;
&lt;/td&gt;
&lt;td style=&#34;border: none&#34;&gt;
&lt;p&gt;
















  
  &lt;picture  &gt;
  &lt;img class=&#34;img-fluid&#34; src=&#34;https://img.shields.io/github/forks/ivylikethevine/saffron&#34; alt=&#34;GitHub forks&#34; loading=&#34;lazy&#34; /&gt;
&lt;/picture&gt;
&lt;/p&gt;
&lt;/td&gt;
&lt;td style=&#34;border: none&#34;&gt;
&lt;p&gt;
















  
  &lt;picture  &gt;
  &lt;img class=&#34;img-fluid&#34; src=&#34;https://img.shields.io/github`/license/ivylikethevine/saffron&#34; alt=&#34;GitHub License&#34; loading=&#34;lazy&#34; /&gt;
&lt;/picture&gt;
&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;Ever since my last &lt;a href=&#39;https://ivylikethevine.com/projects/homelab-design-v1&#39;&gt;homelab design post&lt;/a&gt;, I have been trying to find a workflow that takes better advantage of docker compose based services. After literal months of trying various versions of container management systems, performing unholy command line shenanigans, and bashing my head into my keyboard, I have settled on &lt;code&gt;saffron&lt;/code&gt;, a simple, static folder structure with docker compose files &amp;amp; leveraging git SVM, running a modern web interface for easy configuration &amp;amp; modification.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/saffron-server-as-a-file-folder-running-on-network/images/dashboard_hu_edcd5bebe1f00f31.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/saffron-server-as-a-file-folder-running-on-network/images/dashboard_hu_1bf8a75c58ce6f87.468425f50ab708bb6083057e9f1beb42.png" alt="Dashboard" title="My saffron dashboard" loading="lazy" height="418" width="750" />
</picture>

  <figcaption class="center">My saffron dashboard</figcaption>
</figure>
</p>
<h1 id="saffron-is-a-docker-compose-based-homelab"><strong>Saffron</strong> is a docker compose based homelab</h1>
<p>It is deployable via static files, and managed as a git repository: <em><a href="https://github.com/ivylikethevine/saffron">Public Git Repository</a></em></p>
<table>
<tr>
<td style="border: none">
<p>
















  
  <picture  >
  <img class="img-fluid" src="https://img.shields.io/github/repo-size/ivylikethevine/saffron" alt="GitHub repo size" loading="lazy" />
</picture>
</p>
</td>
<td style="border: none">
<p>
















  
  <picture  >
  <img class="img-fluid" src="https://img.shields.io/github/last-commit/ivylikethevine/saffron" alt="GitHub last commit" loading="lazy" />
</picture>
</p>
</td>
<td style="border: none">
<p>
















  
  <picture  >
  <img class="img-fluid" src="https://img.shields.io/github/stars/ivylikethevine/saffron" alt="GitHub Repo stars" loading="lazy" />
</picture>
</p>
</td>
<td style="border: none">
<p>
















  
  <picture  >
  <img class="img-fluid" src="https://img.shields.io/github/forks/ivylikethevine/saffron" alt="GitHub forks" loading="lazy" />
</picture>
</p>
</td>
<td style="border: none">
<p>
















  
  <picture  >
  <img class="img-fluid" src="https://img.shields.io/github`/license/ivylikethevine/saffron" alt="GitHub License" loading="lazy" />
</picture>
</p>
</td>
</tr>
</table>
<p>Ever since my last <a href='/projects/homelab-design-v1'>homelab design post</a>, I have been trying to find a workflow that takes better advantage of docker compose based services. After literal months of trying various versions of container management systems, performing unholy command line shenanigans, and bashing my head into my keyboard, I have settled on <code>saffron</code>, a simple, static folder structure with docker compose files &amp; leveraging git SVM, running a modern web interface for easy configuration &amp; modification.</p>
<h2 id="and-i-am-releasing-it-with-all-of-my-preconfigured-docker-compose-services">And I am releasing it with all of my preconfigured docker compose services!</h2>
<p>Documentation is on the <a href="https://ivylikethevine.github.io/saffron/#/">wiki</a>!</p>
<h3 id="features">Features</h3>
<ul>
<li>Media libraries for TV, movies, music, ebooks, &amp; audiobooks. (+ subtitles!)</li>
<li>Easy migration from any existing docker or docker compose deployment.</li>
<li>Full torrenting suite with VPN integration.</li>
<li>Automated backup to cloud storage.</li>
<li>Smart home automation &amp; integration.</li>
<li>Network speedtests + hardware monitoring.</li>
<li>Zero DNS or networking configuration outside of docker.</li>
</ul>
<h5 id="to-deploy">To deploy</h5>
<p>Requires: git, docker, docker compose</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Grab saffron</span>
</span></span><span style="display:flex;"><span>git clone git@github.com:ivylikethevine/saffron.git
</span></span><span style="display:flex;"><span>cd saffron
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Create file paths with correct permissions &amp; start dockge</span>
</span></span><span style="display:flex;"><span>./install-saffron.sh</span></span></code></pre></div>
<h3 id="why-docker-why-compose">Why Docker? Why Compose?</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#75715e"># This compose file defines netdata, a Web UI for hardware monitoring.</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">version</span>: <span style="color:#e6db74">&#34;3&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">netdata</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">image</span>: <span style="color:#ae81ff">netdata/netdata</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">container_name</span>: <span style="color:#ae81ff">netdata</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">pid</span>: <span style="color:#ae81ff">host</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">network_mode</span>: <span style="color:#ae81ff">host</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">restart</span>: <span style="color:#ae81ff">unless-stopped</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">19999</span>:<span style="color:#ae81ff">19999</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">cap_add</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">SYS_PTRACE</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">SYS_ADMIN</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">volumes</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">/containers/netdata/config:/etc/netdata</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">/containers/netdata/lib:/var/lib/netdata</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">/containers/netdata/cache:/var/cache/netdata</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">/etc/passwd:/host/etc/passwd:ro</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">/etc/group:/host/etc/group:ro</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">/etc/localtime:/etc/localtime:ro</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">/proc:/host/proc:ro</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">/sys:/host/sys:ro</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">environment</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">NETDATA_EXTRA_DEB_PACKAGES=lm_sensors</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">networks</span>: {}
</span></span><span style="display:flex;"><span><span style="color:#75715e"># This is all we need to deploy an entire service</span></span></span></code></pre></div>
<p>Containers are great for a homelab because, like their physical counterparts, they are repositionable and movable. Servers are a pain because configuration is tedious and complicated, but containers let us use someone else&rsquo;s configuration.</p>
<p>Compose takes that further, allowing us to statically define services with configurations, easily network them, and allow complex services to easily run without complicated configuration on the host.</p>
<h4 id="alternatives">Alternatives</h4>
<p>There are exisitng solutions for this problemspace, namely <a href="https://www.portainer.io/">portainer</a> and <a href="https://github.com/SelfhostedPro/Yacht">yacht</a>. I&rsquo;ve used both of these, but my primary issue with them for the simple homelab use-case is that the user does not maintain full control of the docker compose files. These solutions add a level of complexity on top of compose files, allowing (supposedly) one-click templates, complex networking, and many features that are no doubt useful in some scenario.</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/saffron-server-as-a-file-folder-running-on-network/images/portainer_hu_d872143baa2655db.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/saffron-server-as-a-file-folder-running-on-network/images/portainer_hu_67c17fb2a17e5762.16e09d92df650f34191ef4cd5ca2a54a.png" alt="image" title="Portainer's Web UI" loading="lazy" height="500" width="750" />
</picture>

  <figcaption class="center">Portainer&#39;s Web UI</figcaption>
</figure>
</p>
<p>However, the homelab is a space where we need to maximize our compatibility and searchability. Portainer/yacht exist as supersets of docker compose, so if we can base our system on the lower level technology, we have more compability with the wealth of knowledge already on the internet. The level of tech-saviness required to grab someone else&rsquo;s compose file is about as simple as copy/pasting an emoji on a laptop.</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/saffron-server-as-a-file-folder-running-on-network/images/yacht_hu_d67d492f72ab416f.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/saffron-server-as-a-file-folder-running-on-network/images/yacht_hu_d11968a7e4b14c1.a526be28c2aa4963fa6f0f837b83a402.png" alt="image" title="Yacht's Web UI" loading="lazy" height="362" width="750" />
</picture>

  <figcaption class="center">Yacht&#39;s Web UI</figcaption>
</figure>
</p>
<p>Docker compose also allows us the ability to reconfigure our homelab dynamically. It is easy to run multiple different types or configurations of containers, test them for a while, then stop &amp; remove any eliminated contenders.</p>
<h4 id="dockge">Dockge</h4>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/saffron-server-as-a-file-folder-running-on-network/images/dockge_hu_eea13f37065288e5.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/saffron-server-as-a-file-folder-running-on-network/images/dockge_hu_830451c807e029b2.87bda724f672de6130988bf55f3bdd5f.png" alt="image" title="Dockge Web UI" loading="lazy" height="344" width="750" />
</picture>

  <figcaption class="center">Dockge Web UI</figcaption>
</figure>
</p>
<p>In comes <a href="https://github.com/louislam/dockge">Dockge</a>, a very nice tool that allows us to leverage the power of docker compose with very minimal difference from using a command line interface (CLI), which is (let&rsquo;s be real here) unbearable UI/UX for 99% of people. Instead of running its own templating system on top of docker compose, it is a simple Web UI to edit our compose files, stop, start, and edit networking &amp; environment variables. There are some downsides when compared to editing <code>saffron</code> directly on the host device inside of a code editor (as I do on swervy, my new homelab node), but with some slight workarounds, it is much easier to diagnose issues and find solutions using docker compose &amp; dockge than portainer or yacht in my experience.</p>
<h5 id="tools-we-can-use">Tools We Can Use</h5>
<p>Since we&rsquo;re using docker compose, we can leverage a ton of useful existing tools to allow us to migrate existing container systems to <code>saffron</code>, such as:</p>
<ul>
<li><a href="https://github.com/composerize/composerize">composerize</a> - to turn <code>docker run...</code> into docker compose yaml (though dockge does have an implementation of this in the UI)</li>
<li><a href="https://github.com/composerize/decomposerize">decomposerize</a> - inverse of above</li>
<li><a href="https://github.com/Red5d/docker-autocompose">autocompose</a> - to turn running containers into docker compose yaml</li>
</ul>
<h5 id="source-version-management">Source Version Management</h5>
<p>Additionally, dockge allows us to create an entire git managed repository that houses our compose files, <code>saffron</code>. The repository contains dockge, in addition to preconfigured docker compose files for the services I currently run on my homelab nodes. They can be turned on or off, and the user maintains complete control of their compose files.</p>
<p>Git allows us to track our changes, and restore working versions when things (eventually) go wrong. Our <code>.env</code> files are not stored in git, so we can (mostly) safely keep personal directory pathings, vpn credentials, and other secrets out of SVM/git.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git commit -m <span style="color:#e6db74">&#34;initial working version&#34;</span>
</span></span><span style="display:flex;"><span>git checkout -b changeMediaServer <span style="color:#75715e"># trying out new client</span>
</span></span><span style="display:flex;"><span>git commit -m <span style="color:#e6db74">&#34;new client&#34;</span>
</span></span><span style="display:flex;"><span>git commit -m <span style="color:#e6db74">&#34;hot fix&#34;</span>
</span></span><span style="display:flex;"><span>git stash <span style="color:#75715e"># new client isn&#39;t satisfactory, return to previous</span>
</span></span><span style="display:flex;"><span>git checkout main <span style="color:#75715e"># back to functional</span>
</span></span><span style="display:flex;"><span>git commit -m <span style="color:#e6db74">&#34;different change&#34;</span></span></span></code></pre></div>
<pre class="mermaid">
	gitGraph
	    commit
	    branch changeMediaServer
	    checkout changeMediaServer
	    commit
	    commit
	    commit
	    checkout main
	    commit
	</pre>

<h5 id="planned-features">Planned Features</h5>
<p><code>saffron</code> itself is a starting point, because it allows for easy development of future concepts. Some of the integrations I plan to work on are as follows:</p>
<ul>
<li><a href="https://github.com/flungo-docker/avahi">avahi</a> - configuring this would allow internal containers to reach external &lt;hostname&gt;.local domains on LAN, which is my preferred method of networking</li>
<li><a href="https://hub.docker.com/_/traefik">traefik</a> - easy docker integration allows for (in-theory) automatic routing of services (easily supported with labels, but conceptually possible as an automatic routing system)</li>
</ul>
<p>So if you&rsquo;re setting up a homelab, try <a href="https://github.com/ivylikethevine/saffron">Saffron</a>.</p>
]]></content>
        </item>
        
        <item>
            <title>3D Printing 0-100</title>
            <link>https://ivylikethevine.com/posts/printing-workflow/</link>
            <pubDate>Sun, 10 Dec 2023 00:00:00 +0000</pubDate>
            
            <guid>https://ivylikethevine.com/posts/printing-workflow/</guid>
            <description>&lt;h1 id=&#34;impetus&#34;&gt;Impetus&lt;/h1&gt;
&lt;p&gt;Okay so obviously 3d printing is not new. I&amp;rsquo;ve been aware of it for about 10 years or so, and I remember vividly seeing so many of these plywood boxes in every makerspace &amp;amp; home workshop in the Las Vegas tech scene:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.cnet.com/reviews/makerbot-replicator-review/&#34;&gt;
















  
  
  
    
    
    
    
    
    
    
  &lt;figure class=&#34;center d-block text-center&#34; &gt;
  &lt;picture  &gt;
  &lt;img class=&#34;figure-img img-fluid&#34; src=&#34;https://ivylikethevine.com/posts/printing-workflow/images/makerbot-plywood_hu_e53ad67774c9c211.6eaa47aaeb5d6299004205ec08568bed.webp&#34; alt=&#34;Little Old Me&#34; title=&#34;Your grandpa&#39;s 3d printer. Kiss the ring.&#34; loading=&#34;lazy&#34; height=&#34;376&#34; width=&#34;500&#34; /&gt;
&lt;/picture&gt;

  &lt;figcaption class=&#34;center&#34;&gt;Your grandpa&amp;#39;s 3d printer. Kiss the ring.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;the-problem-with-the-king&#34;&gt;The Problem with the King&lt;/h2&gt;
&lt;p&gt;3d printing is an incredibly hard problemspace to work in. Consider how often a 2d printer fails to work correctly, and then the idea of adding the third dimension clearly makes everything harder. So, with most new technology, it &lt;strong&gt;sucked&lt;/strong&gt;&amp;hellip;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h1 id="impetus">Impetus</h1>
<p>Okay so obviously 3d printing is not new. I&rsquo;ve been aware of it for about 10 years or so, and I remember vividly seeing so many of these plywood boxes in every makerspace &amp; home workshop in the Las Vegas tech scene:</p>
<p><a href="https://www.cnet.com/reviews/makerbot-replicator-review/">
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
  <img class="figure-img img-fluid" src="/posts/printing-workflow/images/makerbot-plywood_hu_e53ad67774c9c211.6eaa47aaeb5d6299004205ec08568bed.webp" alt="Little Old Me" title="Your grandpa's 3d printer. Kiss the ring." loading="lazy" height="376" width="500" />
</picture>

  <figcaption class="center">Your grandpa&#39;s 3d printer. Kiss the ring.</figcaption>
</figure>
</a></p>
<h2 id="the-problem-with-the-king">The Problem with the King</h2>
<p>3d printing is an incredibly hard problemspace to work in. Consider how often a 2d printer fails to work correctly, and then the idea of adding the third dimension clearly makes everything harder. So, with most new technology, it <strong>sucked</strong>&hellip;</p>
<p>If you wanted to 3d print in 2012 you needed:</p>
<ul>
<li><strong>Lots of Money</strong></li>
<li><strong>Lots of Time</strong></li>
<li><strong>Lots of Effort</strong></li>
<li><strong>Lots of Ibuprofen (for 3d printer induced headaches)</strong></li>
<li><strong>Lots of Patience</strong></li>
</ul>
<p>And even then, results were not that great. Filament composition was poor, the software &amp; firmware was a mess, and the idea of creating a coherent UI/UX flow was unheard of in the consumer space. So I ignored FDM printing for a while&hellip;</p>
<h2 id="consumerism-succeeds">Consumerism Succeeds</h2>
<p>Until Amazon showed me an <a href="https://store.creality.com/products/ender-3-v2-neo-3d-printer">Ender 3 v2 Neo</a> for under 300$ shipped to my apartment in 2 days and I just bought it. Conspicuous consurism got me.</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
  <img class="figure-img img-fluid" src="/posts/printing-workflow/images/ender3_hu_188ddc2537d1e973.0f9e06a6b7f2a8496d186ecb36045abe.webp" alt="image" title="A handsome young printer." loading="lazy" height="500" width="500" />
</picture>

  <figcaption class="center">A handsome young printer.</figcaption>
</figure>
</p>
<p>Why the Ender 3 v2 Neo? It had a few quality of life improvements over other Ender 3 priced printers:</p>
<ul>
<li>Quiet Motherboard, Power Supply, and Stepper Drivers</li>
<li>PEI-coated, Removable bed</li>
<li>Auto Bed Leveling</li>
<li>Metal Bowden Gears</li>
</ul>
<p>But most of all, it has a massive amount of aftermarket support, both for troubleshooting and upgrading (physical and software).</p>
<h3 id="technology-versus-uiux">Technology versus UI/UX</h3>
<p>Over the past decade, 3d printing technology and software has evolved massively. The remaining hurdles for normal people using 3d printers all revolve around the user experience flow. Here is the typical flow:</p>
<ol>
<li>Download model</li>
<li>Open slicer software</li>
<li>Process &amp; slice (converting STL -&gt; gcode, usually)</li>
<li>Export gcode to sd card</li>
<li>Bring sd card to printer</li>
<li>Use a small lcd to select file to print</li>
<li>Return in a few hours, usually with zero feedback on print progress/status unless you check the physical machine</li>
<li>Pry print off the bed</li>
</ol>
<p>Compare to a modern 2d printer flow:</p>
<ol>
<li>Download file</li>
<li>Print dialog</li>
<li>Remote print using CUPS/airprint</li>
<li>Pick up print-out</li>
</ol>
<p>Additionally, 2d printers don&rsquo;t have to have their output area (i.e. printer tray) before printing again, but 3d printers (except in very rare cases) require human intervention between prints. So if you are researching a 3d printer, I&rsquo;d advise that to really have a worthwhile tool, there is more than just the printer. The following is my guide to the various levels of rabbithole you can fall down.</p>
<h4 id="what-to-fix">What to Fix</h4>
<p>The main issues with a stock Ender type systems are simple:</p>
<ol>
<li><strong>Slow</strong> - Due to lots of engineering reasons, the physical max print speed of an Ender 3 is lower than other kinds. This is an inherent limit of the system, so we have to make the workflow around it as fast as possible to compensate.</li>
<li><strong>Dumb</strong> - There is no wifi connectivity, remote monitoring, or any kind of web interface. Only a physical screen and dials. This can be a pro or a con, as it allows you to add your own interface (I use octoprint, more info below). I would prefer my printer be dumb and off of my personal network, since I then have control over the entire monitoring system.</li>
<li><strong>Material Choice</strong> - Due to the hotend &amp; bowden feed system, certain filament types are not feasible on a stock ender, such as many composites or flexible filaments. This is usually alleviated through large physical modifications to the print system, such as changing major components.</li>
</ol>
<h3 id="level-1-physical-basics">Level 1: Physical Basics</h3>
<p>I specifically picked my printer because it came with a PEI magnetic steel bed. Basically, instead of scraping the plastic off a piece of glass, I take off a thin piece of steel, and flex it until the print comes off. The quality of life improvement is massive, but is also allows the use of multiple beds. For the first round of upgrades, I&rsquo;d recommend the following:</p>
<ul>
<li>Extra print beds (allows swapping between prints, reducing downtime) - <a href="https://www.amazon.com/gp/product/B07X3WBNPX">example</a></li>
<li>A physical fire alarm to mount nearby - <a href="https://www.amazon.com/gp/product/B088KJ93M3">example</a></li>
<li>Some simple printable upgrades
<ul>
<li>Bed locks to keep the bed level over time - <a href="https://www.thingiverse.com/thing:5858852">example</a></li>
<li>Replacement extrusion caps for if/when you break them - <a href="https://www.printables.com/model/194927-ender-3v2-4040-extrusion-cap">example</a></li>
<li>Another extrusion cap - <a href="https://www.printables.com/model/60939-frame-cap-for-v-slot-frames-ender-3-v2">example</a></li>
<li>Display cover - <a href="https://www.printables.com/model/263173-ender-3-v2-display-cover">example</a></li>
<li>Cable clips - <a href="https://www.printables.com/model/344807-klip-organizer-kabli-drukarka-3d">example</a></li>
</ul>
</li>
</ul>
<h3 id="level-2-smarten-up-that-printer">Level 2: Smarten up that Printer</h3>
<p>Now here is where things get interesting! We&rsquo;re going to add a single board computer running
<a href="https://octoprint.org/">Octoprint</a>. There are a lot of options for an octoprint host, such as</p>
<ol>
<li>Old Laptop/Desktop</li>
<li>(Some) Android Phones</li>
<li>Single Board Computers (or SBCs)</li>
</ol>
<h4 id="hardware-selection">Hardware Selection</h4>
<p>I&rsquo;d recommend selecting an SBC if possible. While most of the octoprint configuration I&rsquo;ve found useful so far has been entirely reproducable on the other systems, the easy ability to add sensors &amp; switches (such as auto-on/off) is worth it. I ended up using a <a href="https://www.raspberrypi.com/products/raspberry-pi-3-model-b-plus/">Raspberry Pi 3 Model B Plus Rev 1.3</a> because I had it on hand, but anything newer is also fine.</p>
<h4 id="webcam-selection">Webcam selection</h4>
<p>On many SBC&rsquo;s, there is often a CSI camera connector(s). These use this flat flex ribbon cable + Zero Force (ZF) connectors (quick tip: there are many other names ffor these cables/connectors):

















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/printing-workflow/images/rpicam_hu_4c8fe35880aa7b9d.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/printing-workflow/images/rpicam_hu_329a2747bf2200c4.f0768b7c161d942c24d489d7cd13186d.jpg" alt="image" title="CSI camera & cable." loading="lazy" height="453" width="418" />
</picture>

  <figcaption class="center">CSI camera &amp; cable.</figcaption>
</figure>
</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/printing-workflow/images/rpi3-picam_hu_5444a8eefac86cdf.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/printing-workflow/images/rpi3-picam_hu_b74afaa26886d9f.b66dc4dd0e00d0c29244f08747d67c73.jpg" alt="image" title="ZF CSI connector on an RPI3 " loading="lazy" height="375" width="500" />
</picture>

  <figcaption class="center">ZF CSI connector on an RPI3 </figcaption>
</figure>
</p>
<p>While these are useful and plentifulm, I would recommend a USB webcam over these if you don&rsquo;t already have one or more. It is much easier to disconnect USB than a CSI connector, which can be a hassle when working on the printer.</p>
<h4 id="putting-it-together">Putting it Together</h4>
<p>For my build, I ended up with the following 3d prints (after many other options as my setup has evolved &amp; changed):</p>
<ul>
<li><a href="https://www.printables.com/model/106225-modular-snap-together-raspberry-pi-2b3b3b4-case-w-">RPI 3B Case</a></li>
<li><a href="https://www.printables.com/model/161185-snap-fit-case-for-raspberry-pi-camera-module-2">RPI Cam Case for MMS</a></li>
<li><a href="https://www.printables.com/model/347669-v-slot-mounting-base-modular-mounting-system-gopro">MMS anchor for Ender aluminum extrusions</a></li>
<li><a href="https://www.printables.com/model/9693-modular-mounting-system-gopro-genderaxis-changer">MMS Axis Changer</a></li>
<li><a href="https://www.thingiverse.com/thing:2194278">Modular Mounting System/MMS (GoPro compatible mounts)</a></li>
</ul>
<p>Another classic octoprint gotcha - make sure to tape over the +5v pin on the USB cable from the SBC to the printer. Otherwise weird power things can occur. Kaptan tape or electrical tape are both solid options. See <a href="https://community.octoprint.org/t/put-tape-on-the-5v-pin-why-and-how/13574">this great explanation</a>.</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/printing-workflow/images/usb-5v-tape_hu_fab56e30f917b2e3.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/printing-workflow/images/usb-5v-tape_hu_f31ab5bef2c48ae4.33ae2b809ec19fa86fb0ac5e829efa06.jpeg" alt="image" title="Example of electrical tape on the +5v." loading="lazy" height="350" width="500" />
</picture>

  <figcaption class="center">Example of electrical tape on the &#43;5v.</figcaption>
</figure>
</p>
<p>A good quality power supply is essential! save yourself the headache and buy one that specifically says its rating, such as <a href="https://www.amazon.com/gp/product/B07TYQRXTK">this</a>. A vast majority of SBC issues I&rsquo;ve encountered are due to power supply weirdness.</p>
<p>A major feature that I wanted is to turn the entire printer on/off from the WebUI. This is easily done with a relay like <a href="https://www.amazon.com/gp/product/B00WV7GMA2">this one</a>. I have my SBC on the &lsquo;always on&rsquo; outlet, then my printer &amp; lamp on the &rsquo;normally off&rsquo; outlets. It&rsquo;s also very easy to create your own header to attach to the SBC GPIO pins, such as <a href="https://www.amazon.com/gp/product/B07ZK5F8HP">this</a>. (No soldering required!)</p>
<h4 id="more-sbc-gotchas">More SBC Gotcha&rsquo;s</h4>
<p>Now as far as software installation, I&rsquo;ve tried multiple popular Octoprint install methods, and by far the best option is to just use the RPI imager tool, install Octoprint to a high quality SD card, and go from there. Docker is an option, but for this application, it&rsquo;s easiest to use bare-metal. For SD cards, make sure to buy something as per <a href="https://docs.armbian.com/User-Guide_Getting-Started/#how-to-prepare-a-sd-card">this excellent article</a>.</p>
<h4 id="configuring-octoprint">Configuring Octoprint</h4>
<p>Now that we have Octoprint, we can configure it. Octoprint, like many pieces of good software, has a robust plugin system enabling most of the fancy functionality we want. The default setup for Octoprint is very basic, so here are two lists of plugins: 1 for any printer, and another that has some plugins specific to the Creality printers.</p>
<p><a href='resources/plugin-list.json' download>Download Plugins</a> &amp; <a href='resources/plugin-list-creality.json' download>Download Creality Temp Fix Plugins</a></p>
<p>Plus, since we&rsquo;re running a simple linux based machine, we can use the following scripts to install all the packages we want, then setup NAS backups just like any other machine in my homelab.</p>
<pre><code class="language-bash">sudo apt install -y zip git htop software-properties-common apt-transport-https wget xclip net-tools curl python3 python3-pip nodejs npm ca-certificates gnupg &amp;&amp;
  sudo apt update -y &amp;&amp;
  sudo apt upgrade -y &amp;&amp;
  sudo apt --fix-broken install -fy &amp;&amp;
  sudo apt autoremove -y
  sudo apt install -y cockpit cockpit-pcp nfs-common rsync cron
</code></pre>
<pre><code class="language-bash">nas_name=maccy # user configurable
nas_path=/mnt/sandisk # user configurable

nas_hostname=&quot;$nas_name.local&quot;
ssh_dir_path=/home/$USER/.ssh/
ssh_key_name=homelab
ssh_key_path=&quot;$ssh_dir_path$ssh_key_name&quot;

sudo mkdir -p &amp;&amp;
  ssh-keygen -t rsa -C &quot;homelab auth&quot; -f $ssh_key_path &amp;&amp;
  eval &quot;$(ssh-agent -s)&quot; &amp;&amp;
  ssh-add ~/.ssh/homelab &amp;&amp;
  ssh-copy-id -i $ssh_key_path $USER@$nas_hostname &amp;&amp;
  crontab -l &gt; cron-nas-backup &amp;&amp;
  echo &quot;0 0 * * * rsync --mkpath --delete -ravh /home/$USER/.octoprint/data/backup $USER@$nas_hostname:$nas_path/$hostname&quot; &gt;&gt; cron-nas-backup &amp;&amp;
  crontab cron-nas-backup &amp;&amp;
  rm cron-nas-backup
</code></pre>
<p>And with Octoprint configured, we can setup <a href="https://www.prusa3d.com/page/prusaslicer_424/">PrusaSlicer</a> with some useful defaults and send our processed g-code directly to Octoprint, giving us fully remote control. I recommend PrusaSlicer because it has a vast array of settings, as well as incredibly good documentation.</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/printing-workflow/images/verbose-and-label_hu_9f540d3bdb6a7ef8.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/printing-workflow/images/verbose-and-label_hu_7e57f8a4166d3e29.cc1f35ed6ccab3f2414b9fc005719fef.png" alt="image" title="Some Octoprint plugins rely on these options to know when to execute. Additionally, the {print_preset} is always useful to have in the gcode file names." loading="lazy" height="368" width="1000" />
</picture>

  <figcaption class="center">Some Octoprint plugins rely on these options to know when to execute. Additionally, the {print_preset} is always useful to have in the gcode file names.</figcaption>
</figure>
</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/printing-workflow/images/g-code-thumbnails_hu_78962b61ebe09069.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/printing-workflow/images/g-code-thumbnails_hu_9372bd829be5ea80.a2e62f62ca5bdad6d8dc4e40d6a8191d.png" alt="image" title="And these are just nice to have." loading="lazy" height="607" width="700" />
</picture>

  <figcaption class="center">And these are just nice to have.</figcaption>
</figure>
</p>
<p>And <a href="https://help.prusa3d.com/article/sending-files-to-octoprint-duet_1663">here</a> is how we send files to Octoprint, <strong>no more SD cards required</strong>.</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/printing-workflow/images/send-to-prusa_hu_7deef0d4631699af.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/printing-workflow/images/send-to-prusa_hu_4bfe1b4afe550a21.e8ffbe9f84b8e3ba8a1f3f594e251c69.png" alt="image" title="API Key & hostname.local are all we need!" loading="lazy" height="261" width="500" />
</picture>

  <figcaption class="center">API Key &amp; hostname.local are all we need!</figcaption>
</figure>
</p>
<h3 id="level-3-advanced-modifications">Level 3: Advanced Modifications</h3>
<p>Level 1 &amp; 2 are requirements, as far as I&rsquo;m concerned. Again, my printer is physically slow, so we have to compensate by making everything else as seamless as possible. The next few changes are more advanced, and not directly necessary at the start.</p>
<ol>
<li>Relocating the power supply to an external location, often a requirement for the next modification.</li>
<li>Creating an enclosure to house the printer, such as the <a href="https://www.lets-talk-about.tech/2020/02/3d-printing-famous-ikea-lack-enclosure.html">LACK enclosure</a>.</li>
<li>Adding a dry box to help print hydroscopic filaments, such as <a href="https://www.instructables.com/3D-Filament-Storage-Box-in-an-IKEA-SAMLA-Box/">this</a> or <a href="https://clevercreations.org/samla-filament-storage-dry-box/">this</a>.</li>
<li>Adding humidity &amp; temperature sensors.</li>
<li>AI print failure detection, such as Obico or OctoAnywhere. I plan to cover my deployment of Obico on my personal homelab, as it is fairly easy to setup, but that is another post.</li>
</ol>
<h3 id="conclusion">Conclusion</h3>
<p>That covers the majority of the trial &amp; error I&rsquo;ve gone through during the past 6 or so months getting my beloved 3d printer to behave like a tool, not just a fun gimmicky hobby anymore!</p>
<p>P.S. <a href="https://www.simplify3d.com/resources/print-quality-troubleshooting/">Here is a great guide on how to diagnose easy print issues</a></p>
<h4 id="note-on-links">Note on Links</h4>
<p>None of these links are sponsored in any way, and I selected these arbitrarily based on convenience.</p>
]]></content>
        </item>
        
        <item>
            <title>Current Homelab Hardware</title>
            <link>https://ivylikethevine.com/posts/current-homelab-hardware/</link>
            <pubDate>Sat, 09 Dec 2023 00:00:00 +0000</pubDate>
            
            <guid>https://ivylikethevine.com/posts/current-homelab-hardware/</guid>
            <description>&lt;h1 id=&#34;rolecall&#34;&gt;Rolecall&lt;/h1&gt;
&lt;p&gt;Part of the modus operandi of my work in the homelab space is to re-use old hardware, because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It&amp;rsquo;s a fun challenge to work with sub-optimal hardware.&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;m cheap.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As such, here&amp;rsquo;s the current information on my homelab hardware, none of which is very specialized or powerful.&lt;/p&gt;
&lt;h2 id=&#34;2024-update-swervy-the-node-202-build&#34;&gt;2024 Update: Swervy, the Node 202 Build&lt;/h2&gt;
&lt;p&gt;Role: Stable &lt;a href=&#34;https://github.com/ivylikethevine/saffron&#34;&gt;Saffron&lt;/a&gt; Host&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://pcpartpicker.com/list/kF8HQP&#34;&gt;PcPartPicker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.intel.com/content/www/us/en/products/sku/134584/intel-core-i312100-processor-12m-cache-up-to-4-30-ghz/specifications.html&#34;&gt;Intel Core i3-12100&lt;/a&gt; - 4 cores + hyperthreading, 3.3-4.3Ghz, Intel UHD 730&lt;/li&gt;
&lt;li&gt;TeamGroup Elite 1 x 16GB DDR4 3200MT/s RAM&lt;/li&gt;
&lt;li&gt;Asrock Z690M-ITX/ax
&lt;ul&gt;
&lt;li&gt;1x Intel 1GBe&lt;/li&gt;
&lt;li&gt;1x Dragon 2.5GBe&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Corsair SF450W PSU *&lt;/li&gt;
&lt;li&gt;Fractal Design Node 202 *&lt;/li&gt;
&lt;li&gt;1TB Intel 660p M.2 NVME **&lt;/li&gt;
&lt;li&gt;2TB Crucial P3 M.2 NVME **&lt;/li&gt;
&lt;li&gt;2TB Crucial SATA SSD **&lt;/li&gt;
&lt;li&gt;3TB Seagate 7200RPM HDD **&lt;/li&gt;
&lt;li&gt;Geekbench 6 Scores: ~2100 Single Core/~6500 Multi Core&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;*: Gifted by a friend&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h1 id="rolecall">Rolecall</h1>
<p>Part of the modus operandi of my work in the homelab space is to re-use old hardware, because:</p>
<ol>
<li>It&rsquo;s a fun challenge to work with sub-optimal hardware.</li>
<li>I&rsquo;m cheap.</li>
</ol>
<p>As such, here&rsquo;s the current information on my homelab hardware, none of which is very specialized or powerful.</p>
<h2 id="2024-update-swervy-the-node-202-build">2024 Update: Swervy, the Node 202 Build</h2>
<p>Role: Stable <a href="https://github.com/ivylikethevine/saffron">Saffron</a> Host</p>
<ul>
<li><a href="https://pcpartpicker.com/list/kF8HQP">PcPartPicker</a></li>
<li><a href="https://www.intel.com/content/www/us/en/products/sku/134584/intel-core-i312100-processor-12m-cache-up-to-4-30-ghz/specifications.html">Intel Core i3-12100</a> - 4 cores + hyperthreading, 3.3-4.3Ghz, Intel UHD 730</li>
<li>TeamGroup Elite 1 x 16GB DDR4 3200MT/s RAM</li>
<li>Asrock Z690M-ITX/ax
<ul>
<li>1x Intel 1GBe</li>
<li>1x Dragon 2.5GBe</li>
</ul>
</li>
<li>Corsair SF450W PSU *</li>
<li>Fractal Design Node 202 *</li>
<li>1TB Intel 660p M.2 NVME **</li>
<li>2TB Crucial P3 M.2 NVME **</li>
<li>2TB Crucial SATA SSD **</li>
<li>3TB Seagate 7200RPM HDD **</li>
<li>Geekbench 6 Scores: ~2100 Single Core/~6500 Multi Core</li>
</ul>
<p>*: Gifted by a friend</p>
<p>**: Repurposed from other computers</p>
<h2 id="maccy-the-2015-macbook-pro">Maccy, the 2015 Macbook Pro</h2>
<p>Role:  Experimental <a href="https://github.com/ivylikethevine/saffron">Saffron</a> Host</p>
<ul>
<li><a href="https://www.intel.com/content/www/us/en/products/sku/53450/intel-core-i52430m-processor-3m-cache-up-to-3-00-ghz/specifications.html">Intel Core i5-5257U CPU @ 2.70GHz</a>: 2 cores + hyperthreading, 2.7-3.1Ghz</li>
<li>2 x 4GB DDR3 1867MT/s RAM</li>
<li>1x Gigabit Ethernet (using 1x Thunderbolt -&gt; 1GbE)</li>
<li>2x Thunderbolt 2 Port (1x in use)</li>
<li>1x USB 3.0 (5Gbps) (in use with 1TB Sandisk Extreme SSD)</li>
<li>128GB SSD</li>
<li>Geekbench 6 Scores: ~900 Single Core/~1800 Multi Core</li>
</ul>
<h2 id="raspi-the-raspberry-pi-3b-mia">Raspi, the Raspberry Pi 3B+ [MIA]</h2>
<ul>
<li><a href="https://www.raspberrypi.com/products/raspberry-pi-3-model-b-plus/">Raspberry Pi 3 Model B Plus Rev 1.3</a></li>
<li>Broadcom BCM2837B0, ARM 4 cores, 1.4GHz</li>
<li>1GB LPDDR2 RAM</li>
<li>1x Gigabit Ethernet</li>
<li>128GB Micro-SD</li>
<li>Geekbench 4 (<strong>not 6</strong>) Scores: ~500 Single Core/~1200 Multi Core</li>
</ul>
<h2 id="nessie-the-2012-macbook-pro-retired">Nessie, the 2012 Macbook Pro [Retired]</h2>
<ul>
<li><a href="https://www.intel.com/content/www/us/en/products/sku/53450/intel-core-i52430m-processor-3m-cache-up-to-3-00-ghz/specifications.html">Intel Core i5-2430M</a>: 2 cores + hyperthreading, 2.4-3.0Ghz</li>
<li>2 x 2GB DDR3 1333MT/s RAM</li>
<li>1x Gigabit Ethernet</li>
<li>1x Thunderbolt 2 Port</li>
<li>2TB Crucial SATA SSD - Upgraded from original 128GB SSD. [Moved to swervy]</li>
<li>Geekbench 6 Scores: ~500 Single Core/~750 Multi Core</li>
</ul>
<h2 id="opie-the-orange-pi-3b-water-damaged-9785">Opie, the Orange Pi 3B [Water Damaged ☹]</h2>
<ul>
<li><a href="http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-3B.html">Orange Pi 3B 4GB</a></li>
<li>Rockchip RK3566 - ARM 4 cores, 0.4-1.8Ghz</li>
<li>4GB LPDDR4 RAM</li>
<li>1x Gigabit Ethernet</li>
<li>1x USB 3.0 (5Gbps)</li>
<li>128GB M.2 2230 SSD</li>
<li>Geekbench 6 Scores: ~150 Single Core/~500 Multi Core</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>NAS Backups and the Dark USB Arts</title>
            <link>https://ivylikethevine.com/posts/nas-backups-and-the-dark-usb-arts/</link>
            <pubDate>Fri, 20 Oct 2023 00:00:00 +0000</pubDate>
            
            <guid>https://ivylikethevine.com/posts/nas-backups-and-the-dark-usb-arts/</guid>
            <description>&lt;p&gt;
















  
  
  
    
    
    
    
    
    
    
  &lt;figure class=&#34;center d-block text-center&#34; &gt;
  &lt;picture  &gt;
    &lt;source srcset=&#34;https://ivylikethevine.com/posts/nas-backups-and-the-dark-usb-arts/images/sata-vs-usb3_hu_f70c324bc69e170a.webp&#34; type=&#34;image/webp&#34; /&gt;
  &lt;img class=&#34;figure-img img-fluid&#34; src=&#34;https://ivylikethevine.com/posts/nas-backups-and-the-dark-usb-arts/images/sata-vs-usb3_hu_84486ec3d940d5ac.ca3abf0157894f4b81f70cbba9c0e202.png&#34; alt=&#34;image&#34; title=&#34;Left: 2011 Macbook with 2TB SATA 3 SSD. Right: 2015 Macbook with 2TB USB 3 SSD.&#34; loading=&#34;lazy&#34; height=&#34;395&#34; width=&#34;750&#34; /&gt;
&lt;/picture&gt;

  &lt;figcaption class=&#34;center&#34;&gt;Left: 2011 Macbook with 2TB SATA 3 SSD. Right: 2015 Macbook with 2TB USB 3 SSD.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h1 id=&#34;the-backups-speech&#34;&gt;The Backups Speech&lt;/h1&gt;
&lt;p&gt;Every discussion about file backups generally revolves around the same paradigm:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You need to have 3 copies of your data: the production data, a local copy, and an off-site backup.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Similarly, when reading about home servers, there are lots of pieces of (good) advice that talk about the importance of redundancy at every level:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/nas-backups-and-the-dark-usb-arts/images/sata-vs-usb3_hu_f70c324bc69e170a.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/nas-backups-and-the-dark-usb-arts/images/sata-vs-usb3_hu_84486ec3d940d5ac.ca3abf0157894f4b81f70cbba9c0e202.png" alt="image" title="Left: 2011 Macbook with 2TB SATA 3 SSD. Right: 2015 Macbook with 2TB USB 3 SSD." loading="lazy" height="395" width="750" />
</picture>

  <figcaption class="center">Left: 2011 Macbook with 2TB SATA 3 SSD. Right: 2015 Macbook with 2TB USB 3 SSD.</figcaption>
</figure>
</p>
<h1 id="the-backups-speech">The Backups Speech</h1>
<p>Every discussion about file backups generally revolves around the same paradigm:</p>
<blockquote>
<p>You need to have 3 copies of your data: the production data, a local copy, and an off-site backup.</p>
</blockquote>
<p>Similarly, when reading about home servers, there are lots of pieces of (good) advice that talk about the importance of redundancy at every level:</p>
<ul>
<li>&lsquo;Fast and redundant storage, best results with SSD disks.&rsquo; - <a href="https://www.proxmox.com/en/proxmox-virtual-environment/requirements">Proxmox Recommended Hardware</a></li>
<li>&lsquo;We do not recommend installing TrueNAS on a single disk or striped pool unless you have a good reason to do so.&rsquo; - <a href="https://www.truenas.com/docs/core/gettingstarted/corehardwareguide/">TrueNas Minimum Hardware Requirements</a></li>
</ul>
<p>But the reality of the situation is my laptop has one internal drive and I want more NAS storage as cheap as possible. This led me to research USB external storage.</p>
<h2 id="my-setup">My Setup</h2>
<p>As mentioned in my <a href='/projects/homelab-design-v1'>homelab design post</a>, my NAS is a late 2011 Macbook Pro, and even with my swapped-in SSD, I have only 2TB of storage on a single drive.</p>
<p>But, thats not my full homelab. I have recently configured another laptop as per my previous post for 2 hosts:</p>
<ol>
<li><code>nessie</code>: a 2011 Macbook Pro with an upgraded 2TB SATA SSD</li>
<li><code>maccy</code>: a 2015 Macbook Pro with a 256GB not-easily-upgradable NVME SSD</li>
</ol>
<p>2TB is not insignificant, but most NAS solutions put that size to shame. Since we&rsquo;re using laptops, we don&rsquo;t have much internal upgradability (if any), so let&rsquo;s explore USB storage on NAS&rsquo;s, and see when it makes sense.</p>
<h3 id="rule-1-check-your-speed">Rule #1: Check Your Speed</h3>
<p>Most questions online about &lsquo;how do I use a USB drive on my NAS?&rsquo; end in the same answer: <strong>don&rsquo;t even bother</strong>.</p>
<p>And for a long time, this was a reasonable (if curt) answer because USB was <strong>unbearably slow</strong>. Here&rsquo;s a table showing file transfer times on all USB versions, SATA 3, and Gigabit ethernet:</p>
<table>
	<thead>
			<tr>
					<th>Version</th>
					<th>Speed (Gbps)<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></th>
					<th>Transfer Time 10GB<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></th>
					<th>Transfer Time 100GB<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>USB 1.0</td>
					<td>0.002</td>
					<td><strong>16 hours</strong></td>
					<td><strong>168 hours</strong></td>
			</tr>
			<tr>
					<td>USB 1.1</td>
					<td>0.012</td>
					<td><strong>2 hours</strong></td>
					<td><strong>21 hours</strong></td>
			</tr>
			<tr>
					<td>USB 2.0</td>
					<td>0.5</td>
					<td>3 minutes</td>
					<td><strong>0.5 hours</strong></td>
			</tr>
			<tr>
					<td>Ethernet</td>
					<td>1</td>
					<td>1.5 minutes</td>
					<td>15 minutes</td>
			</tr>
			<tr>
					<td>USB 3.0</td>
					<td>5</td>
					<td>17 seconds</td>
					<td>3 minutes</td>
			</tr>
			<tr>
					<td>USB 3.1</td>
					<td>10</td>
					<td>8 seconds</td>
					<td>1.5 minutes</td>
			</tr>
			<tr>
					<td>USB 3.2</td>
					<td>20</td>
					<td>4 seconds</td>
					<td>44 seconds</td>
			</tr>
			<tr>
					<td>USB 4/TB3</td>
					<td>40</td>
					<td>2 seconds</td>
					<td>20 seconds</td>
			</tr>
			<tr>
					<td>SATA 3</td>
					<td>6</td>
					<td>14 seconds</td>
					<td>2.5 minutes</td>
			</tr>
	</tbody>
</table>
<p>Before USB 3.0, USB storage was too slow for most applications. Even now, 5Gbps is not incredibly fast, but since an internal SATA 3 drive is around 6gbps, it is not as dreadful as it once seemed. Now, most servers don&rsquo;t have high speed USB, but plenty of laptops do! <code>maccy</code> has USB 3.0, at 5gbps. However, since <code>nessie</code> only has USB 2.0, it is not viable.</p>
<h4 id="so-usb-3-is-fast-enough-for-me">So USB 3 is fast enough for me</h4>
<p>The other issues with USB revolve around it&rsquo;s resiliciency. System Admins will never recommend using external storage because it is a terrible idea for any business. God forbid a USB drive with the production database fails, or just gets unplugged when an intern gets caught on a cable!<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup></p>
<p>Critical data does not belong on a USB drive, but none of my data is really that critical. All of my development work is already hosted on github. My files are really just old schoolwork, pictures, and videos that I would like to keep safe, but don&rsquo;t need 24/7/365 access with hardware redundancy. The best solution for me is just to run a USB drive and also backup remotely.</p>
<h3 id="rule-2-dont-fail-to-boot">Rule #2: Don&rsquo;t Fail to Boot</h3>
<p>My homelab posts are the top 10% of the stuff I have tried, and USB drives have been fighting me for a long time. When designing a NAS with USB storage, remember that they <strong>will disconnect</strong>. It&rsquo;s a &lsquo;when, not if&rsquo; situation. Our implementation has to account for this and mitigate the issues that this can cause.</p>
<p>First off, the USB drive cannot be the boot drive. Booting off of USB is useful sometimes, but when you are already playing in the margins of tech, double dipping is a bad idea. If we allow our server to have a USB drive as a storage drive that is mounted later, we can gracefully deal with disconnections.</p>
<p><code>fstab</code>, the file that lists how to mount all drives at boot, has a few options we need to enable. By default, most systems will either halt at boot or wait for a long timeout to pass before booting. If our USB storage is gone, we still want to be able to access the server&rsquo;s web UI!<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></p>
<p>With the cockpit UI that our system already has, all we really have to do is plug in the drive, format via the UI, and then work on our <code>fstab</code> options. There are many cool file systems, but I prefer to keep everything as simple as possible. One partition on the USB drive, <code>ext4</code>, no encryption. For nomenclature, I prefer for USB drives to be mounted at <code>/mnt/&lt;diskname&gt;</code>.</p>
<p>After basic configuration, our fstab line looks something like this:</p>
<p><code>UUID=f94b134d-4e14-4c05-8f30-19aaa477acb4 /mnt/sandisk auto defaults 0 0</code></p>
<p>We&rsquo;ll add a <code>nofail</code> to this line, making it like this:</p>
<p><code>UUID=f94b134d-4e14-4c05-8f30-19aaa477acb4 /mnt/sandisk auto defaults,nofail 0 0</code><sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></p>
<p>This option means that when booting, we will not fail to boot if we are unable to mount our external drive. There are other issues to deal with, but this is the main one that could break our entire deployment.</p>
<h3 id="rule-3-offsite-backups">Rule #3: Offsite Backups</h3>
<p>Another data backup quote I see bounce around often is &rsquo;local copies are not a replacement for remote backups&rsquo;, which is true, but is focused again at a business environment.</p>
<p>If a production hard drive dies, you need a copy of those files, right now. Servers typically do this via data redundancy over RAID (or other software solutions) with a drive already connected. Switchover is instant, the database never goes down, the business never loses a client due to a service outtage.</p>
<p>Again, at home, I am the client and my server needs are not critical to my ability to put food on the table. For me, a remote backup is fine. The disaster I am trying to prevent is not &lsquo;what happens if hardware fails during peak business&rsquo;, but &lsquo;my old laptop drive finally died&rsquo; or &lsquo;my apartment flooded&rsquo;. So, 1 local copy and remote backups are enough for me.</p>
<p>There are a myriad storage providers, and a myriad of backup solutions. Here is what I have found to work for my uses and be cost effective:</p>
<p><a href="https://www.duplicati.com/">Duplicati</a> running inside a Portainer template from <a href="https://github.com/Lissy93/portainer-templates">this repo</a> - This docker image then mounts our data directory (<code>/mnt/sandisk/</code>), allowing it to access the data directory.</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/nas-backups-and-the-dark-usb-arts/images/duplicati-mounts_hu_e98abad1a0bf9f2d.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/nas-backups-and-the-dark-usb-arts/images/duplicati-mounts_hu_cf877408a6001cbc.e1155b0c7f04420da012c6b42c93f7f5.png" alt="image" title="This is the portainer config for our volumes. The only one I have added is the `sandisk` one." loading="lazy" height="487" width="750" />
</picture>

  <figcaption class="center">This is the portainer config for our volumes. The only one I have added is the `sandisk` one.</figcaption>
</figure>
</p>
<p><a href="https://www.backblaze.com/cloud-storage">Backblaze B2</a> for bulk storage. My original pick was AWS, but B2 is easier for deployments that don&rsquo;t utilize the AWS/AIM/CLI ecosystem.<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup></p>
<p>Configuration tips:</p>
<ul>
<li>
<p>I prefer to create a separate Application Key for each folder that my NAS uploads to B2. This way, each folder has its own configuration and is slightly more portable. Most of the backup software will upload files to the cloud provider in a binary format (not readable without the backup software itself).</p>
</li>
<li>
<p>Make sure each Application Key only has access to a single bucket. Always keep permissions as minimal as possible, its just a good security practice.</p>
</li>
<li>
<p>Encrypt your backups. It&rsquo;s built-in to most backup software, so why risk it?</p>
</li>
<li>
<p>Check your backups! Backups that do not work are not backups at all. Restore a file, delete a file, modify a file, download a few gigabytes. Test, test, test!</p>
</li>
</ul>
<h3 id="rule-4-fail-gracefully">Rule #4: Fail Gracefully</h3>
<p>USB drives are going to fail, so we need to design our system to fail non-destructively. We&rsquo;ve already started to account for this with our new mount options above, which allows our system to continue operating if the USB drive is not found during boot, but there are a few other considerations:</p>
<ol>
<li>Nothing Critical
<ul>
<li>Our system is designed with the ability for our USB drive to fail, but our software implementation also depends on us continuing this assumption. Like we already keep our OS on the built-in drive, we should keep all of our programs, software, docker images, etc. on our built-in drive.</li>
<li>The USB drive should only store data files &amp; folders.</li>
</ul>
</li>
<li>Don&rsquo;t Break Remote
<ul>
<li>If our USB drive is not found, it is possible that some backup software could error out and assume that the files were deleted, thus causing a mess of our backups.</li>
<li>Always test your implementation for this oversight. (Even if the backup software handles this situation properly, adding in our docker environment could lead to weird &amp; undocumented behaviour.)</li>
<li>I personally tested this on my duplicati/docker instance, and my deployment properly errors and stops the backup process when it cannot mount our data directory.</li>
</ul>
</li>
<li>Don&rsquo;t Break Local
<ul>
<li>Our backup system and our software design account for failure, so our last step is to fail gracefully as a NAS. Just like how we used specific <code>fstab</code> options above, here are the important options for an NFS NAS (USB or not):</li>
</ul>
</li>
</ol>
<pre><code class="language-bash">maccy.local:/mnt/sandisk   /mnt/maccy/sandisk   nfs rw,auto,nofail,noatime,nolock,intr,tcp,actimeo=1800 0 0
</code></pre>
<p>If we want to add more USB drives (such as for local backups), we can repeat the above proces, adding any number of <code>/mnt/&lt;driveName&gt;</code>&rsquo;s as an NFS share via the Cockpit UI!</p>
<h3 id="rule-5-dont-talk-about-usb-drive-nass">Rule #5: Don&rsquo;t talk about USB Drive NAS&rsquo;s</h3>
<p>We have mitigated the downsides to USB drives, increased our homelab server storage and resiliency, and have a backup system for when the USB drives eventually break. Plus, we didn&rsquo;t have to buy anything new! Our system fails gracefully in all of the situations covered above, and here&rsquo;s how to fix pretty much every error condition I encountered during testing:</p>
<ol>
<li>Turn off the server.</li>
<li>Replug the USB drive.</li>
<li>Restart the server.</li>
</ol>
<p>Being a fault-tolerant, slightly janky system is good for home use, but it is always worth doing it right when actual business is on the line. Please do not apply these to production deployments!</p>
<h4 id="footnotes">Footnotes</h4>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>While file sizes are given in gigabytes, file transfer speeds are given in gigabits per second. <a href="https://en.wikipedia.org/wiki/Data-rate_units">https://en.wikipedia.org/wiki/Data-rate_units</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>An hour of h.264 1080p video @ 24-30fps is ~ 10GB <a href="https://www.omnicalculator.com/other/video-size">https://www.omnicalculator.com/other/video-size</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Calculations via <a href="https://techinternets.com/copy_calc">https://techinternets.com/copy_calc</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>If your production deployment relies on external drives to begin with, you may have problems that an internet blog post cannot solve.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Since my laptops are also docker hosts, this would also mean all containers would be halted even if they did not interact with the external storage.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>Make sure to have the entire <code>defaults,nofail</code> section without any spaces. The spaces are what designate the columns in the <code>fstab</code> file.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>AWS is very powerful, but with great power, comes great configuration.&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content>
        </item>
        
        <item>
            <title>Dev Ops are not Optional</title>
            <link>https://ivylikethevine.com/posts/dev-ops-are-not-optional/</link>
            <pubDate>Sun, 15 Oct 2023 00:00:00 +0000</pubDate>
            
            <guid>https://ivylikethevine.com/posts/dev-ops-are-not-optional/</guid>
            <description>&lt;p&gt;&lt;a href=&#34;https://www.ta-systems.com/system-categories/automated-assembly-lines/&#34;&gt;
















  
  
  
    
    
    
    
    
    
    
  &lt;figure class=&#34;center d-block text-center&#34; &gt;
  &lt;picture  &gt;
    &lt;source srcset=&#34;https://ivylikethevine.com/posts/dev-ops-are-not-optional/images/assembly-line_hu_cd11f221f49e67f2.webp&#34; type=&#34;image/webp&#34; /&gt;
  &lt;img class=&#34;figure-img img-fluid&#34; src=&#34;https://ivylikethevine.com/posts/dev-ops-are-not-optional/images/assembly-line_hu_84514751ff3e79e5.f71b40abc166321547be97e4e199eaa2.jpg&#34; alt=&#34;Automated Assembly Machines&#34; title=&#34;We want to automate as much as possible!&#34; loading=&#34;lazy&#34; height=&#34;448&#34; width=&#34;750&#34; /&gt;
&lt;/picture&gt;

  &lt;figcaption class=&#34;center&#34;&gt;We want to automate as much as possible!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&#34;sustainable-software&#34;&gt;Sustainable Software&lt;/h1&gt;
&lt;p&gt;Ok, so the title is Software Engineer or Software Developer, but writing code and building software is only part of the solution. The other half (or more) of the job is &lt;em&gt;designing software solutions for humans to interact with&lt;/em&gt;. Software only is useful because &lt;strong&gt;we humans&lt;/strong&gt; use it. Without us, the software is just some meaningless electricity in some far away warehouse. UI/UX design is important, but just as critical is &lt;strong&gt;developer operations&lt;/strong&gt; (dev ops).&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><a href="https://www.ta-systems.com/system-categories/automated-assembly-lines/">
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/dev-ops-are-not-optional/images/assembly-line_hu_cd11f221f49e67f2.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/dev-ops-are-not-optional/images/assembly-line_hu_84514751ff3e79e5.f71b40abc166321547be97e4e199eaa2.jpg" alt="Automated Assembly Machines" title="We want to automate as much as possible!" loading="lazy" height="448" width="750" />
</picture>

  <figcaption class="center">We want to automate as much as possible!</figcaption>
</figure>
</a></p>
<h1 id="sustainable-software">Sustainable Software</h1>
<p>Ok, so the title is Software Engineer or Software Developer, but writing code and building software is only part of the solution. The other half (or more) of the job is <em>designing software solutions for humans to interact with</em>. Software only is useful because <strong>we humans</strong> use it. Without us, the software is just some meaningless electricity in some far away warehouse. UI/UX design is important, but just as critical is <strong>developer operations</strong> (dev ops).</p>
<h2 id="dev-ops">Dev Ops</h2>
<ul>
<li>outlines a software development process and an organizational culture shift that speeds the delivery of higher quality software by automating and integrating the efforts of development and IT operations teams – two groups that traditionally practiced separately from each other, or in silos.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></li>
</ul>
<h3 id="what-is-dev-ops">What is Dev Ops?</h3>
<p>Don&rsquo;t get me wrong, large scale production deployments can have very complex dev ops! However, even for a single developer, the tools and systems required are very easy, often free, and can massively improve productivity.</p>
<p>Here is my list of main dev ops goals. Some may seem obvious, but all of these are drawn from experience starting new projects as well as inheriting many projects of dubious quality.</p>
<ul>
<li>Source Version Mangement
<ul>
<li><strong>Rule 1</strong>: software changes need to be easily assigned, tracked, and checked</li>
<li><strong>Rule 2</strong>: environment variables need to be stored safely (important enough to state again)</li>
</ul>
</li>
<li>Deployments
<ul>
<li><strong>Rule 1</strong>: deploying new versions should be automated via CI/CD and require no human intervention</li>
<li><strong>Rule 2</strong>: staging site to test versions before production deployment</li>
</ul>
</li>
<li>Onboarding
<ul>
<li><strong>Rule 1</strong>: new devs need to be able to download and deploy system within a day<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></li>
<li><strong>Rule 2</strong>: new devs should not be able to break production</li>
</ul>
</li>
</ul>
<h2 id="the-fallacy-of-saving-time">The Fallacy of Saving Time</h2>
<p>Dev Ops takes time to setup, but it is absolutely worth it. Here&rsquo;s a story from my days as a contractor. At one company, it took 2 developers at least 4 hours each to deploy our application (at a <em>generous</em> minimum). Here&rsquo;s how that math works out over just a single year:</p>

  <div class="katex-output">
  $$devHours \gtrapprox 2\frac{deployments}{month} *8\dfrac{devHours}{deployment}* 12\frac{months}{year}$$
</div>



  <div class="katex-output">
  $$devHours \gtrapprox 192$$
</div>


<p>That&rsquo;s over a month of dev hours lost per year! Does any dev want to waste a month every single year running the same deployments? However, this is just the tip of the iceberg. If your system takes a day to patch, what happens when a major exploit is found in production?</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
  <img class="figure-img img-fluid" src="/posts/dev-ops-are-not-optional/images/spongebob-panic_hu_42d9a09628d75933.01ba23cceed9d77fa53a4f90ee095786.gif" alt="image" title="This is what happens. I've been in the room." loading="lazy" height="400" width="533" />
</picture>

  <figcaption class="center">This is what happens. I&#39;ve been in the room.</figcaption>
</figure>
</p>
<p>This is a single example of poor dev ops, but time is money and similar math applies to dozens of scenarios.</p>
<h2 id="so-lets-do-it-right">So Let&rsquo;s Do It Right</h2>
<h3 id="i-source-version-mangement-svmgit">i. Source Version Mangement (svm/git)</h3>
<p>Talking to my college friends, it seems a lot of CS majors do not learn Source Version Management (or SVM). Basically, we want a history of all of our code changes, and the ability to go back to specific checkpoints and fix things that break. There are a few tools for this, but I use <code>git</code> as the SVM and github.com to hold my repositories (as well as provide other dev ops tools, discussed later).</p>
<p><code>git</code> is very powerful and very complex, but we only need a few commands to set up a useable CI/CD environment. This graph is a simple version of &ldquo;git flow&rdquo;, or a semi-standard way to use github branches.</p>
<pre class="mermaid">
	gitGraph
	commit
	branch develop
	checkout develop
	branch featureName
	checkout featureName
	commit
	commit
	checkout develop
	branch fixBug
	checkout fixBug
	commit
	checkout develop
	merge featureName
	checkout main
	merge develop
	checkout develop
	merge fixBug
	checkout main
	merge develop
	</pre>

<p><code>main</code> (previously defaulted to <code>master</code>): this is our production code. Never work directly from main. Never interact with main via CLI.</p>
<p><code>development</code>: this is our staging code. This is where we start new branches</p>
<p><code>&lt;namedBranch&gt;</code>: these are self contained changes to the project. Here we have <code>featureName</code> and <code>fixBug</code>, but often there are different categories of named branches such as <code>feature/featureName</code> or <code>bug/bugName</code>.</p>
<p>This is how we would setup a repository to follow basic git flow.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git config --global init.defaultBranch <span style="color:#e6db74">&#34;main&#34;</span> <span style="color:#75715e"># By default, git may still use `master`, so we set default to `main`.</span>
</span></span><span style="display:flex;"><span>cd projectFolder
</span></span><span style="display:flex;"><span>git init
</span></span><span style="display:flex;"><span>git push -u origin main <span style="color:#75715e"># here we setup the remote copy of our repo</span>
</span></span><span style="display:flex;"><span>git checkout -b development <span style="color:#75715e"># -b is only needed when creating a branch</span>
</span></span><span style="display:flex;"><span>git push
</span></span><span style="display:flex;"><span>git checkout -b featureName</span></span></code></pre></div>
<p>Now, we can work on <code>featureName</code> without impacting anything else. Additionally, other devs on our team can branch from <code>development</code> and any merge conflicts will be dealt with on the <code>development</code> branch before merging into <code>main</code>.</p>
<p>Once our feature is added:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git add . <span style="color:#75715e"># the &#39;.&#39; convention will add ALL modified files. Only modify files that you need to. git restore fileName will undo any changes.</span>
</span></span><span style="display:flex;"><span>git commit -m <span style="color:#e6db74">&#34;Added feature&#34;</span> <span style="color:#75715e"># -m only allows inline commit messages. A commit is basically a checkpoint we can go back to later.</span>
</span></span><span style="display:flex;"><span>git push
</span></span><span style="display:flex;"><span>git checkout develop</span></span></code></pre></div>
<p>Now, we could immediately create a pull request from <code>development</code> into <code>production</code>, but it is preferable to have a staging or development site. A staging site is a live site that usually only devs/testers can access. So let&rsquo;s deploy a staging site!</p>
<h3 id="ii-continuous-integration-and-continuous-deployment-cicd">ii. Continuous Integration and Continuous Deployment (CI/CD)</h3>
<p>My website is made with <a href="https://gohugo.io/">Hugo</a>, a static site generator written in Go. It has a theme called <a href="https://themes.gohugo.io/themes/hugo-theme-puppet/">Puppet</a>, and I have it deployed to a custom domain (ivylikethevine.com) Originally I had used <a href="https://pages.github.com/">Github Pages</a> alongside <a href="https://jekyllrb.com/">Jekyll</a>, but found that the setup for a staging &amp; production environment was tedious.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<p>Eventually, I switched to <a href="https://pages.Cloudflare.com/">Cloudflare Pages</a>, which I recommend as it allows us to access Cloudflare Zero Trust, a system to authenticate users before accessing webpages.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Cloudflare Pages also defaults to creating preview pages, essentially staging sites per branch, and we can configure our staging site to deploy from the <code>development</code> branch when a change is pushed.</p>
<p>Luckily, Cloudflare has a pretty slick Github integration, so we can follow the instructions <a href="https://developers.Cloudflare.com/pages/platform/git-integration">here</a>. Note: only allow the minimum required permissions for a service integration. Cloudflare Integration should only have access to the repository(ies) that are deployed to Cloudflare Pages.</p>
<h4 id="custom-domains--dns-pains">Custom Domains &amp; DNS Pains</h4>
<p>Github &amp; Cloudflare pages host websites, but they do so using their own domains. Github uses a <code>.github.io</code> extension and Cloudflare uses a <code>.pages.dev extension</code>. We would obviously prefer our own custom domain, so what we have to do is change the DNS records for our site to point to the content deployed by our hosting platform. In Cloudflare, here is what my DNS settings look like for my website.</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/dev-ops-are-not-optional/images/cloudflare-dns_hu_a688c41f8cc55a54.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/dev-ops-are-not-optional/images/cloudflare-dns_hu_caebe9fee0bf76bf.7a469cc382527bc4d4e7237f3713080a.png" alt="image" title="Middle entry unrelated & redacted." loading="lazy" height="88" width="750" />
</picture>

  <figcaption class="center">Middle entry unrelated &amp; redacted.</figcaption>
</figure>
</p>
<p>Our <code>CNAME</code> allow us to load the content of the <code>pages.dev</code>, but access it via our custom domain name. My domain name servers are with Cloudflare, which makes the setup very easy. If moving from Github Pages, make sure to remove the custom domain from the Github Pages site &amp; to transfer name servers. We also redirect the <code>www</code> subdomain since it is still sometimes used, and we just want it to redirect to the same content anyways. We can also see that on our Workers/Pages configuration we have setup our custom domains.</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/dev-ops-are-not-optional/images/custom-domains_hu_788548e6b016cb89.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/dev-ops-are-not-optional/images/custom-domains_hu_cec337b452d0de6d.a2e8d4ba81a12a68dfae6e50c2f07528.png" alt="image" title="Cloudflare 'Workers & Pages' tab." loading="lazy" height="247" width="500" />
</picture>

  <figcaption class="center">Cloudflare &#39;Workers &amp; Pages&#39; tab.</figcaption>
</figure>
</p>
<h4 id="staging-environment">Staging Environment</h4>
<p>Now, we have automatic deployments from our main branch, as well as automatic preview branches! We could be done, but I prefer to just have one staging site, at least for now. In future, I will cover using Cloudflare Zero Trust to password protect all staging sites.</p>
<p>On the &lsquo;Workers &amp; Pages&rsquo; tab of Cloudflare, we can click on the details for our site, then head to &ldquo;Settings&rdquo;, then &ldquo;Builds &amp; deployments&rdquo;. We can leave the top production branch alone, but here is my configuration for the preview deployments:</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/dev-ops-are-not-optional/images/staging-site_hu_8f35bab8d873ec2a.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/dev-ops-are-not-optional/images/staging-site_hu_9d5ba0b91d633feb.80a2dee8af27cc74e204481d4f3c89c4.png" alt="image" title="Now only commits pushed/merged to development will have preview pages." loading="lazy" height="665" width="750" />
</picture>

  <figcaption class="center">Now only commits pushed/merged to development will have preview pages.</figcaption>
</figure>
</p>
<p>So now we only publish a live version of our <code>development</code> branch at <code>https://development.repoName.pages.dev</code>!</p>
<h4 id="two-environments-both-alike-in-config">Two Environments, both alike in config</h4>
<p>Having two environments now means we need to have configurations for each. Hugo allows multiple configs, and we only need two:</p>
<ul>
<li><code>config/_default/config.toml</code> for development &amp; staging
<ul>
<li>This config has 90% of the site settings, since we need most of them for the site to build correctly.</li>
<li>My staging website has the title set as &ldquo;ivy (development)&rdquo; &amp; the <code>pages.dev</code> baseURL.</li>
</ul>
</li>
<li><code>config/production/config.toml</code> for production
<ul>
<li>This config only holds a few settings that we only want on our live site, such as application keys for analytics/comments/etc.</li>
<li>My production website has the title &ldquo;ivy (likethevine)&rdquo;, as well as giscus (comments) settings, and my custom domain baseURL.</li>
</ul>
</li>
</ul>
<p>Hugo uses the <code>_default</code> config, but it can apply the <code>production</code> config if passed a flag during deployment. Luckily, since Cloudflare Pages supports Hugo out of the box, we simply need to set a few environment variables in the GUI. Back at the settings for our website, we can define the following flags. These will be passed to the <code>hugo build</code> command when we deploy:</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/dev-ops-are-not-optional/images/environment-variables_hu_23e8b9782638bc2a.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/dev-ops-are-not-optional/images/environment-variables_hu_48cf4f541f651b2d.0bfcf3a2745f820f992387c70198e839.png" alt="image" title="Simple configuration, huh." loading="lazy" height="257" width="750" />
</picture>

  <figcaption class="center">Simple configuration, huh.</figcaption>
</figure>
</p>
<p><strong>Note</strong>: Environment variables are also useful for securing data that absolutely should not be in the main git repository, such as SSH/application keys.</p>
<h3 id="iii-prevent-disaster-with-automatic-checks">iii. Prevent Disaster with Automatic Checks</h3>
<p>Now that we have our SVM and our automatic deployments, lets talk workflow. Github (and other SVMs) provide many automated checks and systems to prevent the dreaded &ldquo;new dev destroys prod&rdquo; disaster. <a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/managing-a-branch-protection-rule">Branch protection rules</a> are super simple and useful. Common rules include preventing direct pushes to <code>main</code>, requiring pull requests to be approved by others before being merged, and tons of more complex versions.</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/dev-ops-are-not-optional/images/pull-request-check_hu_2cecf1d71a54267e.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/dev-ops-are-not-optional/images/pull-request-check_hu_7622f98f27fb3390.391f60745b16a14be3089375d806ed07.png" alt="image" title="Additionally, integrations (such as the Cloudflare Pages one), will actually show if the staging deployment works on the PR, providing even more feedback and warning if something is awry." loading="lazy" height="470" width="750" />
</picture>

  <figcaption class="center">Additionally, integrations (such as the Cloudflare Pages one), will actually show if the staging deployment works on the PR, providing even more feedback and warning if something is awry.</figcaption>
</figure>
</p>
<p>
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
    <source srcset="/posts/dev-ops-are-not-optional/images/branch-protection_hu_51524fcb86a2975.webp" type="image/webp" />
  <img class="figure-img img-fluid" src="/posts/dev-ops-are-not-optional/images/branch-protection_hu_32d462162fcd31a1.4e2bb2d415d56458156976bbd31d31ea.png" alt="image" title="And this branch protection rule option will not allow deployments with failed Cloudflare statuses on their Pull Requests." loading="lazy" height="342" width="750" />
</picture>

  <figcaption class="center">And this branch protection rule option will not allow deployments with failed Cloudflare statuses on their Pull Requests.</figcaption>
</figure>
</p>
<p>Here are the basic steps to working within a system like this:</p>
<ol>
<li>Create a <code>featureName</code> branch off of <code>development</code>.</li>
<li>Add a feature/bugfix/etc, modifying as few files as possible.</li>
<li>Add files &amp; commit at major checkpoints, leaving short but useful commit messages.</li>
<li>When a feature is ready for evaluation, push to the <code>featureName</code> branch, then open a pull request to merge <code>featureName</code> into <code>development</code>.</li>
<li>Have the pull request approved &amp; merged by supervisor (or double check manually).</li>
<li>Test the staging site.</li>
<li>Deploy to production via pull request from <code>development</code> into <code>main</code>.</li>
</ol>
<pre class="mermaid">
	gitGraph
	commit
	branch develop
	checkout develop
	commit
	branch featureName
	checkout featureName
	commit
	checkout develop
	merge featureName
	checkout main
	merge develop
	checkout develop
	</pre>

<h3 id="reap-the-benefits">Reap the Benefits</h3>
<p>So with all of that work, what do we really get? Time to revisit those goals:</p>
<ul>
<li>Source Version Mangement
<ul>
<li><strong>Rule 1</strong>: software changes need to be easily assigned, tracked, and checked
<ul>
<li>stored in github following git-flow</li>
</ul>
</li>
<li><strong>Rule 2</strong>: environment variables need to be stored safely (important enough to state again)
<ul>
<li>dual config files locally as well as environment variables stored in our host</li>
</ul>
</li>
</ul>
</li>
<li>Deployments
<ul>
<li><strong>Rule 1</strong>: deploying new versions should be automated via CI/CD and require no human intervention
<ul>
<li>most deployments require a simple approve &amp; merge of a pull request (via a Web UI)</li>
</ul>
</li>
<li><strong>Rule 2</strong>: staging site to test versions before production deployment
<ul>
<li>staging site automatically deployed from <code>development</code></li>
</ul>
</li>
</ul>
</li>
<li>Onboarding
<ul>
<li><strong>Rule 1</strong>: new devs need to be able to download and deploy system within a day<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>
<ul>
<li>with a proper SVM, git flow, and environment configurations, onboarding will be a breeze</li>
</ul>
</li>
<li><strong>Rule 2</strong>: new devs should not be able to break production
<ul>
<li>branch protection rules, pull requests, and automated integrations mitigate or prevent most accidental destruction</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>The initial setup takes time, for certain, but now we will gain efficiency almost every single day from now on! Your managers, coworkers, and <strong>your future self</strong> will thank you, trust me.</p>
<h4 id="footnotes">Footnotes</h4>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://www.ibm.com/topics/devops">https://www.ibm.com/topics/devops</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Slight caveat here, some systems require lots of permissions/database access, which could increase this time, but the repository setup should be quick.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>If you are migrating github -&gt; Cloudflare with a custom domain, I recommend changing the domain registrar to Cloudflare for easier integration. Additionally, if your repo followed the <code>username.github.io</code> nomenclature, rename it before you deploy to Cloudflare, otherwise your staging site will end up as development.<username>-github-io.pages.dev.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Stay tuned, I am working on an article about using Cloudflare Zero Trust to safely access self hosted services without network configuration.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content>
        </item>
        
        <item>
            <title>Laptop Homelab in 5 Commands</title>
            <link>https://ivylikethevine.com/posts/laptop-homelab-in-5-commands/</link>
            <pubDate>Thu, 12 Oct 2023 00:00:00 +0000</pubDate>
            
            <guid>https://ivylikethevine.com/posts/laptop-homelab-in-5-commands/</guid>
            <description>&lt;p&gt;&lt;a href=&#34;https://www.reviewjournal.com/business/no-lazy-kid-12-year-old-app-developer-off-to-fast-start&#34;&gt;
















  
  
  
    
    
    
    
    
    
    
  &lt;figure class=&#34;center d-block text-center&#34; &gt;
  &lt;picture  &gt;
  &lt;img class=&#34;figure-img img-fluid&#34; src=&#34;https://ivylikethevine.com/posts/laptop-homelab-in-5-commands/images/macbook-panera_hu_2cbf177d94b55aea.75db549254506116037efe15049a3048.webp&#34; alt=&#34;Little Old Me&#34; title=&#34;Back when I developed on my Macbook!&#34; loading=&#34;lazy&#34; height=&#34;427&#34; width=&#34;640&#34; /&gt;
&lt;/picture&gt;

  &lt;figcaption class=&#34;center&#34;&gt;Back when I developed on my Macbook!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&#34;the-background&#34;&gt;The Background&lt;/h1&gt;
&lt;p&gt;I have been in tech for over 10 years (look at little me up there), and I have seen firsthand that a proper workflow is essential for any decent project. So, when I bought a 3D printer a few months ago, it was embarrassing to see &lt;strong&gt;my&lt;/strong&gt; lack of workflow. Dealing with the dozens of STL files was a hassle by itself, but then, it came: &lt;em&gt;Raspberry Pi SD card corruption.&lt;/em&gt; And it came again&amp;hellip; and again&amp;hellip; and again&amp;hellip;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><a href="https://www.reviewjournal.com/business/no-lazy-kid-12-year-old-app-developer-off-to-fast-start">
















  
  
  
    
    
    
    
    
    
    
  <figure class="center d-block text-center" >
  <picture  >
  <img class="figure-img img-fluid" src="/posts/laptop-homelab-in-5-commands/images/macbook-panera_hu_2cbf177d94b55aea.75db549254506116037efe15049a3048.webp" alt="Little Old Me" title="Back when I developed on my Macbook!" loading="lazy" height="427" width="640" />
</picture>

  <figcaption class="center">Back when I developed on my Macbook!</figcaption>
</figure>
</a></p>
<h1 id="the-background">The Background</h1>
<p>I have been in tech for over 10 years (look at little me up there), and I have seen firsthand that a proper workflow is essential for any decent project. So, when I bought a 3D printer a few months ago, it was embarrassing to see <strong>my</strong> lack of workflow. Dealing with the dozens of STL files was a hassle by itself, but then, it came: <em>Raspberry Pi SD card corruption.</em> And it came again&hellip; and again&hellip; and again&hellip;</p>
<p>With least an hour of configuration per corruption, I finally bit the bullet and started setting up my own server. <strong>tl;dr? here&rsquo;s the <a href="https://gist.github.com/ivylikethevine/adba9472047741476e5a19b8741e906a">full setup script</a></strong></p>
<h2 id="why-a-home-server">Why a Home Server?</h2>
<p>I have avoided making my own server for the past few years because I did not have a need for one. At Amazon Music, we had access to the internal architecture &amp; work flows, and during my contracting work, our applications were usually relatively straightforward GIT/CI/CD Node/similar applications.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> But now, I have graduated college and have the time &amp; skill to setup my own, so lets do it &mdash; on the cheap.</p>
<h3 id="enter-the-laptop-server">Enter&hellip; the Laptop Server</h3>
<p>No, a laptop is not the ideal server. They lack multiple ethernet connections, extra storage, and were not designed for this use-case. However, for personal-use deployment, they come with many benefits:</p>
<ol>
<li>Power Efficiency
<ul>
<li>Laptops in general are built for battery life, and thus are designed to draw less power than a desktop/server often would.</li>
<li>Less power use = less waste heat. This is critical to me since the server must be in my bedroom to have access to ethernet.</li>
</ul>
</li>
<li>It&rsquo;s a Laptop
<ul>
<li>Small, easy to place wherever. (Mine sits under my TV)</li>
<li>Instead of a UPS, the laptop battery itself provides a healthy backup when power is cut-out (or more likely, when there are power fluctuations).</li>
<li>Built in keyboard/monitor for debugging <strong>when</strong> something breaks.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></li>
</ul>
</li>
<li>I already have one. While I would love a full custom rack setup, <strong>the best server is the server you have <em>right now</em></strong>.</li>
</ol>
<h3 id="the-hardware---nessie-the-2012-macbook-pro">The Hardware - Nessie, the 2012 Macbook Pro</h3>
<p>For my setup, my server hardware is my old 2012 Macbook Pro 13&quot;:</p>
<ul>
<li><a href="https://www.intel.com/content/www/us/en/products/sku/53450/intel-core-i52430m-processor-3m-cache-up-to-3-00-ghz/specifications.html">Intel Core i5-2430M</a>: 2 cores + hyperthreading, 2.4-3.0Ghz</li>
<li>2 x 2GB DDR3 1333MT/s RAM</li>
<li>1x Gigabit Ethernet</li>
<li>1x Thunderbolt 2 Port</li>
<li>2TB Crucial SATA SSD - Upgraded from original 128GB SSD.</li>
</ul>
<p>The specs are not incredible, and I was uncertain if this would be a performant system (spoiler: it works fine!). I upgraded the SSD with one I had laying around, since 128GB was not sufficient. Production servers should <strong>always have redundant storage and networking</strong>, but beggers can&rsquo;t be choosers! It is possible to adapt the Thunderbolt 2 port to Gigabit ethernet, but dual networking is a rabbithole to follow another day.</p>
<h3 id="the-software---keep-it-simple">The Software - Keep it Simple</h3>
<ul>
<li>OS: <a href="https://ubuntu.com/download/server">Ubuntu 22.04LTS</a></li>
<li>Host Management: <a href="https://cockpit-project.org/">Cockpit</a></li>
<li>Container Management: <a href="https://docs.portainer.io/start/install-ce">Portainer-CE</a></li>
<li>Virtualization: <a href="https://www.docker.com/">Docker</a></li>
<li>NAS Mount Protocol: <a href="https://en.wikipedia.org/wiki/Network_File_System">NFS</a></li>
</ul>
<p>There will be a future post going more in-depth about the specific tech selection! Many popular alternatives were tested or considered, but I arrived at this setup because each of these technologies are well known, well developed, and have tons of community support &amp; documentation.</p>
<h1 id="initial-installation">Initial Installation</h1>
<p>My servers all run Ubuntu 22.04LTS.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Installation of Ubuntu is relatively easy, and the only install configuration required is &ldquo;Install OpenSSH Server&rdquo;. Aside of that, the default settings work fine. <a href="https://ubuntu.com/tutorials/install-ubuntu-server#1-overview">Installation Instructions</a>.</p>
<p>Note: this guide assumes that the laptop is plugged into ethernet during the Ubuntu installation process.</p>
<p>After installing and rebooting, we need to <code>ssh</code> into the laptop to configure it. At this point, our system has a Dynamically Assigned IP address (such as 192.168.1.10), which we need to know in order to connect. If you have access to your router login, simply find the IP address assigned to the <code>hostname</code> of the server, in my case <code>nessie</code>. If you don&rsquo;t have access to that, then login to the server on the laptop (w/ the username &amp; password from installation), and run the following command: <code>hostname -I | awk '{print $1}'</code>. The result is the current IP of the host. The next commands can be run on the device itself, but I find it easier to connect to the host &amp; then copy paste from my normal laptop (versus trying to manipulate text from a command line). So let&rsquo;s configure us a server!</p>
<h2 id="the-five-commandments">The Five Command(ments)</h2>
<h3 id="i-thou-art-a-server">I. Thou art a Server</h3>
<p>Although we&rsquo;re installing a server OS, there are a few things we want to configure to allow our laptop to &ldquo;act like a server.&rdquo; This command does 2 things:</p>
<ol>
<li>Prevent system from sleeping when the lid is closed.</li>
<li>Turn off the screen after 60 seconds (but not fully disable &ndash; to allow on-device debugging).</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo echo <span style="color:#e6db74">$&#39;HandleLidSwitch=ignore\nHandleLidSwitchDocked=ignore&#39;</span> | sudo tee -a /etc/systemd/logind.conf <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>sudo echo <span style="color:#e6db74">&#34;GRUB_CMDLINE_LINUX_DEFAULT=\&#34;consoleblank=60\&#34;&#34;</span> | sudo tee -a /etc/default/grub <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>sudo update-grub</span></span></code></pre></div>
<p>** The <code>&amp;&amp;</code> chaining syntax means that the next command will not execute until/if the previous command executes successfully.</p>
<h3 id="ii-thou-shalt-have-basics">II. Thou Shalt Have Basics</h3>
<p>Ubuntu 22.04LTS comes with many useful packages, but there are a few that I always end up using, so I&rsquo;ve put them here. Of note:</p>
<ol>
<li>The <code>$nrconf</code> line prevents a pop-up window listing services that should be restarted. This halts our config, so I have altered the configuration to list and not pop-up and halt.</li>
<li><code>avahi-daemon</code> and <code>avahi-utils</code> are critical for our link-local addressing.</li>
<li><code>nfs-common</code> is the package we will use to access the files on the NAS as if it were an external drive plugged.</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo echo <span style="color:#e6db74">&#34;\$nrconf{restart} = &#39;l&#39;&#34;</span> | sudo tee -a /etc/needrestart/needrestart.conf <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>sudo apt update -y <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>sudo apt upgrade -y <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>sudo apt install -fy zip git htop software-properties-common apt-transport-https wget xclip net-tools curl python3 python3-pip nodejs npm ca-certificates gnupg avahi-daemon avahi-utils nfs-common <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>sudo apt autoremove -y</span></span></code></pre></div>
<h3 id="iii-thou-shall-have-containers">III. Thou Shall have Containers</h3>
<p>In the Homelab/Selfhosted/NAS space, containerized applications are common (and awesome!). Docker lets our server run containers, meaning we can run software on our host server without having to configure the host for every single application. Each container is separate and (more or less) sandboxed.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> In the case of deploying templates, containers are essentially &ldquo;applications&rdquo; that run on our server.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo install -m <span style="color:#ae81ff">0755</span> -d /etc/apt/keyrings <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>curl -fsSL &lt;https://download.docker.com/linux/ubuntu/gpg&gt; | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>sudo chmod a+r /etc/apt/keyrings/docker.gpg <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;deb [arch=&#34;</span><span style="color:#66d9ef">$(</span>dpkg --print-architecture<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34; signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> &#34;</span><span style="color:#66d9ef">$(</span>. /etc/os-release <span style="color:#f92672">&amp;&amp;</span> echo <span style="color:#e6db74">&#34;</span>$VERSION_CODENAME<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34; stable&#34;</span> |
</span></span><span style="display:flex;"><span>sudo tee /etc/apt/sources.list.d/docker.list &gt;/dev/null <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>sudo apt update -y <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>sudo usermod -aG docker $USER <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>newgrp docker <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>sudo systemctl enable docker</span></span></code></pre></div>
<h3 id="iv-thou-shalt-be-web-monitored">IV. Thou Shalt be Web Monitored</h3>
<p>For this server, two different monitoring solutions are needed.</p>
<ol>
<li><code>cockpit</code> - managing the host device &amp; NAS file sharing
<ul>
<li>we also install <code>cockpit-file-sharing</code> from 45Drives, which allows easy NAS setup from the cockpit webUI</li>
</ul>
</li>
<li><code>portainer</code> - a containerized application that allows the monitoring &amp; deployment of other containerized applications</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mkdir /portainer_data <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>docker volume create portainer_data <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart<span style="color:#f92672">=</span>always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/portainer_data portainer/portainer-ce:latest <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>sudo apt install -y cockpit cockpit-pcp <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>curl -LO &lt;https://github.com/45Drives/cockpit-file-sharing/releases/download/v3.2.9/cockpit-file-sharing_3.2.9-2focal_all.deb&gt; <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>sudo apt install -y ./cockpit-file-sharing_3.2.9-2focal_all.deb <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>rm ./cockpit-file-sharing_3.2.9-2focal_all.deb</span></span></code></pre></div>
<h3 id="v-thou-shalt-not-break-dns">V. Thou Shalt Not Break DNS</h3>
<p>After hours of blood, sweat, and tears, I&rsquo;ve arrived at a networking configuration that satisfies the following:</p>
<ol>
<li>Work with <code>cockpit</code>. (Requires use of <code>NetworkManager</code> for proper updates &amp; network monitoring in the Web UI, not <code>networkd</code>)</li>
<li>Don&rsquo;t break DNS. (Can&rsquo;t run my own DNS server/sinkhole)</li>
<li>Use a named address &amp; a dynamic IP. (<code>&lt;hostname&gt;.local</code> with DHCP)</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo echo <span style="color:#e6db74">&#34;network:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">version: 2
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">renderer: NetworkManager
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">ethernets:
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">$(</span>ip -o -4 route show to default | awk <span style="color:#e6db74">&#39;{print $5}&#39;</span><span style="color:#66d9ef">)</span><span style="color:#e6db74">:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">dhcp4: true
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">link-local: [ ipv4 ]&#34;</span> | sudo tee /etc/netplan/00-installer-config.yaml <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>sudo netplan apply</span></span></code></pre></div>
<p>Essentially what I am doing here is overwriting the default wired <code>netplan</code> with my own configuration. The renderer is set to <code>NetworkManager</code> to work with <code>cockpit</code>&rsquo;s UI. The line <code>$(ip -o -4 route show to default | awk '{print $5}'):</code> is a nifty way to grab the current active ethernet connection and configure it (versus unconfigured or virtual interfaces). On my macbook, it evalutes to <code>ens9</code>, the name of the built-in ethernet interface. We enable <code>dhcp4</code>, since my home network only uses <code>ipv4</code>, not <code>ipv6</code>, and my router uses DHCP instead of static IPs (as do most home networks).<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> <sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> <sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup></p>
<p>The <code>link-local: [ ipv4 ]</code> line is also crucial, as it works with <code>avahi-daemon</code>/<code>avahi-utils</code> to tell other devices on our network that <code>nessie.local</code> can be used, not a dynamically changing IP address. Alternate solutions involve editing files on every host (which is a hassle), or creating a custom DNS server (which is also a hassle and I could break the wifi for my roommate, which would be very bad!)</p>
<p>After the new <code>netplan</code> configuration is applied, our <code>ssh</code> session break or disconnect, so close the terminal and wait a few minutes. Then, reboot the machine (from command line: <code>sudo reboot</code>). When it finishes booting (which may take a few minutes, internet interfaces can take a while to start), you should be able to access it via <code>https://&lt;hostname&gt;.local:9090</code> for cockpit <code>https://&lt;hostname&gt;.local:9443</code> for portainer. At this point, all management can be done via cockpit&rsquo;s terminal or portainer&rsquo;s UI!</p>
<h3 id="congratulations-">Congratulations 🎉</h3>
<p>Now just configure the software we&rsquo;ve installed &amp; enjoy:</p>
<ul>
<li>
<p><a href="https://docs.portainer.io/start/install-ce/server/setup">Initial Portainer Setup</a></p>
</li>
<li>
<p>Optional: Change portainer templates to 3rd party repo to allow more 1-click application deployments, such as <a href="https://github.com/lissy93/portainer-templates">https://github.com/lissy93/portainer-templates</a></p>
</li>
<li>
<p><a href="https://github.com/45Drives/cockpit-file-sharing#samba-management-tab">Configure SAMBA on the server for Windows Clients</a></p>
</li>
<li>
<p><a href="https://github.com/45Drives/cockpit-file-sharing#nfs-management-tab">Configure NFS on the server for linux clients</a></p>
</li>
<li>
<p>Optional: Mount folder at boot on Linux client with the following command (requires <code>sudo apt install -y nfs-common</code> first):
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo echo <span style="color:#e6db74">&#39;&lt;hostname&gt;.local:/&lt;nfs_folder&gt; /&lt;local_mount&gt; nfs rw,auto,nofail,noatime,nolock,intr,tcp,actimeo=1800 0 0&#39;</span> | sudo tee -a /etc/fstab <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>sudo mount -a</span></span></code></pre></div></p>
<ul>
<li><code>&lt;hostname&gt;</code> - ex: nessie</li>
<li><code>&lt;nfs_folder&gt;</code> - the name of the nfs folder created above</li>
<li><code>&lt;local_mount&gt;</code> - the folder on the client device to alias to the server
<ul>
<li>often the <code>/media/&lt;folder&gt;</code> or <code>/mnt/&lt;folder&gt;</code>. ex: <code>/mnt/nessie</code></li>
<li>folder must be created before the <code>fstab</code> file modification<sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup>, ex: <code>sudo mkdir /mnt/nessie</code></li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>Next up, we&rsquo;ll configure offsite backups via a containerized application since our server has ZERO redundancy!</strong></p>
<h4 id="footnotes">Footnotes</h4>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I also had an experience with a developer above me that &ldquo;pushed changes to his local server, but forgot to push the changes to us&rdquo; around every week or so.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>I <strong>often</strong> broke my ability to communicate with the server during network configuration and had to run commands on the laptop itself to fix it.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>LTS designation means that this version of the OS will receive support for 5 years. I prefer to run an LTS version of an OS, since I&rsquo;d rather have stability versus new features (and doubly so on a server).&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Containerization is more complex than this, and the security implications of containers is its own discussion, but for most users, the simple understanding works.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><code>ipv4</code> addresses follow a period deliminated format, such as <code>192.168.1.1</code> (typically the router&rsquo;s IP address), but <code>ipv6</code> uses colon deliminated addresses, such as <code>fd12:3456:789a:1::1</code>. <code>ipv6</code> is useful in larger deployments, but unnecessary for most home use.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>In a DHCP network, typically the router assigns IP addresses to all devices, but these addresses (192.168.1.XX) can change when a device is disconnected/restarted. Static IPs do not change, but usually require more configuration.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p><a href="https://netplan.readthedocs.io/en/stable/netplan-yaml/">Netplan Documentation</a>&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p><code>fstab</code> is a file that essentially lists all drives and how to connect to them on boot.&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content>
        </item>
        
    </channel>
</rss>
