<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[/var/blog]]></title><description><![CDATA[/var/blog]]></description><link>https://desmondrogers.me</link><generator>RSS for Node</generator><lastBuildDate>Fri, 24 Apr 2026 14:22:09 GMT</lastBuildDate><atom:link href="https://desmondrogers.me/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Docker and Ansible: Using Utility Containers to replace NERDTools in Unraid]]></title><description><![CDATA[Apparently, there was some disagreement between Lime Tech, the makers of Unraid, and the maintainers of the beloved Unraid Community plugin NerdTools about a change in the NAS OS company's business model. So, the NerdTools folks abandoned the project...]]></description><link>https://desmondrogers.me/docker-and-ansible-using-utility-containers-to-replace-nerdtools-in-unraid</link><guid isPermaLink="true">https://desmondrogers.me/docker-and-ansible-using-utility-containers-to-replace-nerdtools-in-unraid</guid><category><![CDATA[Homelab]]></category><dc:creator><![CDATA[Desmond Rogers]]></dc:creator><pubDate>Tue, 24 Jun 2025 02:16:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/jOqJbvo1P9g/upload/0d7d92ad6915760083a6fa52687868d2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Apparently, there was some disagreement between Lime Tech, the makers of <a target="_blank" href="https://unraid.net/">Unraid</a>, and the maintainers of the beloved Unraid Community plugin <a target="_blank" href="https://github.com/UnRAIDES/unRAID-NerdTools">NerdTools</a> about a change in the NAS OS company's business model. So, the NerdTools folks abandoned the project.</p>
<p>UPDATE: <a target="_blank" href="https://raw.githubusercontent.com/ich777/un-get">un-get</a> appears to be the new tool power users from the community have gravitated toward. I still prefer the utility container approach though.</p>
<h2 id="heading-what-is-nerdtools">What is NERDtools</h2>
<p><a target="_blank" href="https://github.com/UnRAIDES/unRAID-NerdTools">NerdTools</a> is an Unraid OS plugin that allows you to install additional packages. From the looks of it, the plugin is essentially a GUI package manager for Unraid that installs <a target="_blank" href="http://www.slackware.com/">Slackware</a> packages (with dependencies) to the <code>/boot/extra/</code> directory, which is where Unraid looks for additional packages.</p>
<h2 id="heading-why-move">Why move?</h2>
<p>First, NERDtools was never an officially supported by Lime Tech.</p>
<p>It was great, but it only had a specific and limited set of utilities, so I found it lacking when I wanted to, for example, use vagrant with libvirt as a provider since Unraid uses libvirt as the backend for VMs. But that would have required going down a Slackware rabbithole, which I wasn't really committed to (although sbopkg looked promising).</p>
<p>Even those who have built tools specifically to install Slackware pkgs on Unraid like <a target="_blank" href="https://github.com/ich777/un-get">un-get</a> recommend using Docker, LXC, or VMs before installing on bare metal. So I can't argue in terms of the isolation and portability.</p>
<p>So I removed the NERDtools plugins and moved associate scripts run by cron jobs to a local home lab code repository.</p>
<h2 id="heading-how-everything-is-setup-now">How everything is setup now</h2>
<p>Simplest way to run things: containerize the process, then run via host cron.</p>
<p>First, to automate things with Ansible, I had to install Python 3 for Unraid which was essentially a suite of Slackware python tools.</p>
<p>I considered rolling my own Docker container and storing it in a self-hosted container <a target="_blank" href="https://hub.docker.com/_/registry">distribution registry</a>, which would keep the infrastructure internal and less reliant on external services like Dockerhub. But I later decided to build on the work of project maintainers since they tools I used already offered prebuilt images with the binaries baked in and decent documentation. I also considered adding a layer on top of theirs to have the args in the container, but that would make my playbooks less explicit. Although, I still might consider adding it in the future to reduce the number of tasks needed.</p>
<p>Here’s an example of one of the playbooks I now use for router backups:</p>
<pre><code class="lang-yaml"><span class="hljs-meta">---</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Backup</span> <span class="hljs-string">router</span>
  <span class="hljs-attr">hosts:</span> <span class="hljs-string">wrt</span>

  <span class="hljs-attr">tasks:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Mount</span> <span class="hljs-string">backup</span> <span class="hljs-string">share</span>
      <span class="hljs-attr">ansible.posix.mount:</span>
        <span class="hljs-attr">src:</span> <span class="hljs-string">nas:/mnt/user/backup/openwrt</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">/mnt/backup</span>
        <span class="hljs-attr">state:</span> <span class="hljs-string">ephemeral</span> 
        <span class="hljs-attr">boot:</span> <span class="hljs-literal">false</span>
        <span class="hljs-attr">fstype:</span> <span class="hljs-string">nfs</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Generate</span> <span class="hljs-string">backup</span>
      <span class="hljs-attr">ansible.builtin.shell:</span> <span class="hljs-string">|
        umask go=
        sysupgrade -b /mnt/backup/backup-FriendlyWrt-{{ ansible_date_time.date }}.tar.gz</span>
</code></pre>
<h2 id="heading-result">Result</h2>
<p>This approach makes it so much easier to manage and backup the various services and servers I’ve setup.</p>
]]></content:encoded></item><item><title><![CDATA[Internet Failover]]></title><description><![CDATA[Most ISPs provide a modem + router gateway combo to simplify network setup for the widest audience but if their network fails or is unreachable down for some reason, some IOT devices like cameras and smart locks that we increasingly rely on can becom...]]></description><link>https://desmondrogers.me/internet-backup</link><guid isPermaLink="true">https://desmondrogers.me/internet-backup</guid><category><![CDATA[Homelab]]></category><dc:creator><![CDATA[Desmond Rogers]]></dc:creator><pubDate>Wed, 08 Jan 2025 03:34:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/35tEcBB4_1c/upload/7f72a7f6385aa57165edc801438682a4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most ISPs provide a modem + router gateway combo to simplify network setup for the widest audience but if their network fails or is unreachable down for some reason, some IOT devices like cameras and smart locks that we increasingly rely on can become useless. Also, some of us have mobile deadzones in our living spaces and rely on Wi-Fi for messaging and calling most of the time. So to stay connected during power outages, storms and natural disasters, it's a good idea to setup the home network to have failover capability, or internet backup. My ISP offers an upgraded gateway that's supposed to failover to their cellular network, but while researching solutions I discovered that most people didn’t have good experiences. Also, I'd prefer not to put all of my eggs in one basket - so to speak - just in case SHTF and both their wire and mobile networks are unreachable.</p>
<p>TLDR; Decouple network components (modem, router, wifi access point) to provide more flexible configurations and redundancy during outages and scheduled maintenance. This setup also has the benefit of allowing upgrades to equipment as technology improves because the industries don't innovate at the same rate.</p>
<h2 id="heading-original-setup">Original Setup</h2>
<p>xfinity xFi router w/ xFi pod "mesh" extenders (ISP-provided)</p>
<h2 id="heading-new-setup-w-failover">New Setup w/ Failover</h2>
<h3 id="heading-topology">Topology</h3>
<p>Multi-WAN router: <a target="_blank" href="https://www.tp-link.com/us/business-networking/omada-sdn-router/er605/">TP-Link Omada Gigabit VPN Router ER605 V2</a> in Standalone Mode</p>
<p>Primary WAN: xFi modem/gateway (because free rental promotion at signup. I plan to switch in the future) in bridge mode - allowing the routing to be managed by a dedicated router</p>
<p>Failover WAN: <a target="_blank" href="https://www.netgear.com/home/mobile-wifi/lte-modems/lm1200/">Netgear LTE modem</a> + T-mobile $10 30GB Cellular Data-only Plan</p>
<p>Wireless AP: eero 6+ wifi mesh in <a target="_blank" href="https://support.eero.com/hc/en-us/articles/208276903-Bridge-Mode-and-Double-NAT">Bridge mode</a></p>
<p>This setup effectively keeps devices on the same subnet so that devices connected to wifi can communicate with devices wired to router like the NAS</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Pro-tip:</strong> The best way to prevent your Significant Other from getting upset because their device gets disconnected: keep the same SSID and password 😉. Most previously connected devices will switch to the new access point automatically.</div>
</div>

<hr />
<h2 id="heading-update-2-months-later">Update (2 months later)</h2>
<p>Eschew the advanced features of TP-Link Omada for the simplicity of smaller devices:</p>
<ul>
<li><p>Netgear cellular modem with native failover and SMS alerts</p>
</li>
<li><p>Netgear 5-port switch</p>
</li>
<li><p>eero+ AP / Router for mesh wifi</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736303990526/da737f2e-1ec7-4d43-a03a-861e2f59f255.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[Configuring TLS for Connecting Spring Boot to Elasticsearch]]></title><description><![CDATA[I wanted to put together an article that combines everything I found around the web on how to use self-signed certificates with Spring Boot when using Elastic Cloud on Kubernetes.
The Keystore
There are two options for creating the keystore we need t...]]></description><link>https://desmondrogers.me/configuring-tls-for-connecting-spring-boot-to-elasticsearch</link><guid isPermaLink="true">https://desmondrogers.me/configuring-tls-for-connecting-spring-boot-to-elasticsearch</guid><category><![CDATA[Java]]></category><category><![CDATA[Spring]]></category><category><![CDATA[elasticsearch]]></category><category><![CDATA[Kubernetes]]></category><dc:creator><![CDATA[Desmond Rogers]]></dc:creator><pubDate>Sun, 18 Jul 2021 20:41:09 GMT</pubDate><content:encoded><![CDATA[<p>I wanted to put together an article that combines everything I found around the web on how to use self-signed certificates with Spring Boot when using Elastic Cloud on Kubernetes.</p>
<h2 id="the-keystore">The Keystore</h2>
<p>There are two options for creating the keystore we need to connect securely to Elasticsearch: using the keytool or Keystore API. But I found that creating the Keystore programmatically was the better approach when publishing a Kubernetes cluster in the cloud since this configuration allows us to use the same configurations for a local development environment and in a cloud Kubernetes cluster such as AWS EKS.</p>
<h3 id="creating-keystore-with-keytool">Creating keystore with keytool</h3>
<p>To create the keystore:</p>
<ol>
<li><p>Download the cert from cluster secrets and save as <code>tls.crt</code></p>
<pre><code class="lang-sh">kubectl get secret <span class="hljs-string">"es1-es-http-certs-public"</span> -o go-template=<span class="hljs-string">'{{index .data "tls.crt" | base64decode }}'</span> &gt; tls.crt
</code></pre>
</li>
<li><p>Use the keytool to create the keystore and import the certificate</p>
<pre><code><span class="hljs-selector-tag">keytool</span> <span class="hljs-selector-tag">-import</span> <span class="hljs-selector-tag">-v</span> <span class="hljs-selector-tag">-trustcacerts</span> <span class="hljs-selector-tag">-file</span> <span class="hljs-selector-tag">tls</span><span class="hljs-selector-class">.crt</span> <span class="hljs-selector-tag">-keystore</span> <span class="hljs-selector-tag">keystore</span><span class="hljs-selector-class">.jks</span> <span class="hljs-selector-tag">-keypass</span> <span class="hljs-selector-tag">changeit</span> <span class="hljs-selector-tag">-storepass</span> <span class="hljs-selector-tag">changeit</span>
</code></pre></li>
</ol>
<h3 id="java-keystore-api">Java Keystore API</h3>
<p>For more information on the API see Baeldung's awesome article on <a target="_blank" href="https://www.baeldung.com/java-keystore">the java keystore</a></p>
<p>To create a keystore programmatically that will store the certificate retrieved from Kubernetes cluster secrets used to authenticate with ES cluster</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SSLConfig</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">char</span>[] keyStorePass = <span class="hljs-string">"changeit"</span>.toCharArray();
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String certPathName = <span class="hljs-keyword">new</span> File(<span class="hljs-string">"/etc/es-certificates/tls.crt"</span>).isFile() ? <span class="hljs-string">"/etc/es-certificates/tls.crt"</span> : <span class="hljs-string">"tls.crt"</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> File keyStoreFile;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">SSLConfig</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
        keyStoreFile = <span class="hljs-keyword">this</span>.generateKeyStoreFile();
    }

    <span class="hljs-function">Certificate <span class="hljs-title">generateCert</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
        InputStream inStream = <span class="hljs-keyword">new</span> FileInputStream(certPathName);
        CertificateFactory cf = CertificateFactory.getInstance(<span class="hljs-string">"X.509"</span>);
        <span class="hljs-keyword">return</span> cf.generateCertificate(inStream);
    }

    <span class="hljs-function">File <span class="hljs-title">generateKeyStoreFile</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
        String keyStoreName = <span class="hljs-string">"keystore.jks"</span>;
        File f = <span class="hljs-keyword">new</span> File(keyStoreName);
        <span class="hljs-keyword">if</span> (f.isFile()) <span class="hljs-keyword">return</span> f;

        Certificate cert = <span class="hljs-keyword">this</span>.generateCert();
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(<span class="hljs-keyword">null</span>);
        ks.setCertificateEntry(<span class="hljs-string">"es-http-public"</span>, cert);
        ks.store(<span class="hljs-keyword">new</span> FileOutputStream(keyStoreName), keyStorePass);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> File(keyStoreName);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> SSLContext <span class="hljs-title">getSSLContext</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
        SSLContextBuilder builder = SSLContexts.custom();
            builder.loadTrustMaterial(keyStoreFile, keyStorePass, <span class="hljs-keyword">new</span> TrustSelfSignedStrategy());
            <span class="hljs-keyword">return</span> builder.build();
    }
}
</code></pre>
<h2 id="spring-elasticsearch-configuration">Spring Elasticsearch Configuration</h2>
<p>The Java High-Level REST Client is based on the <a target="_blank" href="https://blog.knoldus.com/java-high-level-rest-client-elasticsearch/">Low-Level Client</a>, which has can be used for <a target="_blank" href="https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/_encrypted_communication.html">encrypted communication</a></p>
<pre><code class="lang-java"><span class="hljs-meta">@Configuration</span>
<span class="hljs-meta">@ComponentScan</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ESConfig</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractElasticsearchConfiguration</span> </span>{
    <span class="hljs-meta">@Value("${elasticsearch.url}")</span>
    <span class="hljs-keyword">public</span> String elasticsearchUrl;
    <span class="hljs-meta">@Value("${elasticsearch.port}")</span>
    <span class="hljs-keyword">public</span> String elasticsearchPort;
    <span class="hljs-meta">@Value("${elasticsearch.username}")</span>
    <span class="hljs-keyword">public</span> String elasticsearchUsername;
    <span class="hljs-meta">@Value("${elasticsearch.password}")</span>
    <span class="hljs-keyword">public</span> String elasticsearchPassword;

    <span class="hljs-keyword">private</span> SSLConfig sslConfig;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ESConfig</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
        sslConfig = <span class="hljs-keyword">new</span> SSLConfig();
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> RestHighLevelClient <span class="hljs-title">elasticsearchClient</span><span class="hljs-params">()</span> </span>{
        SSLContext sslContext = <span class="hljs-keyword">null</span>;
        <span class="hljs-keyword">try</span> {
            sslContext = sslConfig.getSSLContext();
        } <span class="hljs-keyword">catch</span> (Exception e) {
            e.printStackTrace();
        }

        <span class="hljs-keyword">final</span> ClientConfiguration config = ClientConfiguration.builder()
                .connectedTo(elasticsearchUrl + <span class="hljs-string">":"</span> + elasticsearchPort)
                .usingSsl(sslContext)
                .withBasicAuth(elasticsearchUsername, elasticsearchPassword)
                .build();
        <span class="hljs-keyword">return</span> RestClients.create(config).rest();
    }
}
</code></pre>
<h2 id="using-tls-secret-as-a-file-in-the-spring-pod">Using TLS Secret as a file in the Spring Pod</h2>
<p>To use the certificate stored as a secret by ECK, we can mount it in our deployment manifest and make it available in the API container.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">search-api</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">default</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">search-api</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">search-api</span>
        <span class="hljs-attr">namespace:</span> <span class="hljs-string">default</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">image:</span> <span class="hljs-string">$IMAGE</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">search-api</span>
          <span class="hljs-attr">imagePullPolicy:</span> <span class="hljs-string">Always</span>
          <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">8080</span>
          <span class="hljs-attr">volumeMounts:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">es-certificates</span>
              <span class="hljs-attr">mountPath:</span> <span class="hljs-string">"/etc/es-certificates"</span>
          <span class="hljs-attr">env:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">ES_CERT</span>
              <span class="hljs-attr">valueFrom:</span>
                <span class="hljs-attr">secretKeyRef:</span>
                  <span class="hljs-attr">name:</span> <span class="hljs-string">es1-es-http-certs-public</span>
                  <span class="hljs-attr">key:</span> <span class="hljs-string">tls.crt</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">ES_USER</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"elastic"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">ES_PWD</span>
              <span class="hljs-attr">valueFrom:</span>
                <span class="hljs-attr">secretKeyRef:</span>
                  <span class="hljs-attr">name:</span> <span class="hljs-string">es1-es-elastic-user</span>
                  <span class="hljs-attr">key:</span> <span class="hljs-string">elastic</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">ES_URL</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">"es1-es-http"</span>
      <span class="hljs-attr">volumes:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">es-certificates</span>
          <span class="hljs-attr">secret:</span>
            <span class="hljs-attr">secretName:</span> <span class="hljs-string">es1-es-http-certs-public</span>
</code></pre>
]]></content:encoded></item></channel></rss>