<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Homelab on blog.iankulin.com</title><link>https://blog.iankulin.com/tags/homelab/</link><description>Recent content in Homelab on blog.iankulin.com</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Mon, 28 Jul 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iankulin.com/tags/homelab/index.xml" rel="self" type="application/rss+xml"/><item><title>Getting Ghostty to Work on Synology</title><link>https://blog.iankulin.com/getting-ghostty-to-work-on-synology/</link><pubDate>Mon, 28 Jul 2025 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/getting-ghostty-to-work-on-synology/</guid><description>&lt;p&gt;Ghostty is a terminal application that I don&amp;rsquo;t really &lt;em&gt;need&lt;/em&gt; (it&amp;rsquo;s &lt;a href="https://ghostty.org/docs/about"&gt;listed features&lt;/a&gt; either already exist in the MacOS terminal, or seem so esoteric or marginal that I can&amp;rsquo;t imagine any real benefit from them in my normal use), but I &lt;em&gt;wanted&lt;/em&gt; to be one of the cool kids, so I thought I&amp;rsquo;d give it a try.&lt;/p&gt;
&lt;p&gt;After fiddling around with the themes for a bit I renamed it to &amp;rsquo;term-ghosty.app&amp;rsquo; so I&amp;rsquo;d remember to use it (ie when I pop up spotlight and type &amp;rsquo;term&amp;rsquo; it will come up) and got on with my day. Ten minutes later I&amp;rsquo;d run into a problem.&lt;/p&gt;
&lt;h3 id="the-problem"&gt;The Problem&lt;/h3&gt;
&lt;p&gt;I was ssh&amp;rsquo;d into a Synology NAS, and needed to use a command from two commands ago, in my long experience, pressing the up arrow twice works universally well, but not in Ghostty on this host:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screenshot-2025-07-26-at-11.31.38.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;This is a purely visual glitch - if I press return at this stage, it will run &lt;code&gt;command one&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;My first thought was to CTRL-U to clear the line:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screenshot-2025-07-26-at-11.32.00.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I guess not. Oh well, lets &lt;code&gt;clear&lt;/code&gt; and try all this again.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screenshot-2025-07-26-at-11.32.07.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;So - a clue. This is a Ghostty problem, not a weird shell issue. Also, this wasn&amp;rsquo;t just a visual thing - the command was also not working.&lt;/p&gt;
&lt;p&gt;I logged in with regular terminal to confirm that everything was still working with that.&lt;/p&gt;
&lt;h3 id="xterm-ghostty"&gt;xterm-ghostty&lt;/h3&gt;
&lt;p&gt;If you google &amp;lsquo;ghostty arrow history problem&amp;rsquo; you&amp;rsquo;ll likely find a number of github issues that are all closed after the developer has posted a link to &lt;a href="https://ghostty.org/docs/help/terminfo#ssh"&gt;this part of the docs&lt;/a&gt; explaining that you need to compile the Ghostty&amp;rsquo;s terminfo into the config on this host.&lt;/p&gt;
&lt;p&gt;At first I was a bit aghast at this complicated solution - but we need to keep in mind this is a terminal program that I&amp;rsquo;m sure is only used by tech orientated people who love fiddling with things. This is reflected in other design choices in Ghostty (going into &amp;lsquo;Settings&amp;rsquo; from the menu just opens the config file in a text editor).&lt;/p&gt;
&lt;p&gt;I downloaded &lt;a href="https://iterm2.com/index.html"&gt;iTerm2&lt;/a&gt; as a likely competitor to Ghostty and tried it on the same host - everything worked perfectly. I tried Ghostty on several of my VM&amp;rsquo;s, VPS&amp;rsquo;s and LXC&amp;rsquo;s. All no problem. So what&amp;rsquo;s going on?&lt;/p&gt;
&lt;h3 id="whats-going-on"&gt;What&amp;rsquo;s Going On?&lt;/h3&gt;
&lt;p&gt;If you open your terminal, and type &lt;code&gt;echo $TERM&lt;/code&gt; it will tell you the &lt;code&gt;TERM&lt;/code&gt; value. This is used by the shell to know how to interpret the inputs it&amp;rsquo;s receiving (for example, what to do when the user wants to &lt;code&gt;clear&lt;/code&gt; the screen). Unless you are using Ghostty, it will almost certainly be set to &lt;code&gt;xterm-256color&lt;/code&gt;&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/screenshot-2025-07-26-at-12.03.36.png" width="808" alt=""&gt;
&lt;p&gt;In Ghostty, it will say &lt;code&gt;xterm-ghostty&lt;/code&gt;&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/screenshot-2025-07-26-at-12.04.55.png" width="772" alt=""&gt;
&lt;p&gt;The reason I don&amp;rsquo;t have this same problem with Ghostty on my MacBook or the Debian base hosts is that those operating systems have the Ghostty &amp;rsquo;terminfo&amp;rsquo; entries in them, whereas apparently Synology does not (or not yet anyway).&lt;/p&gt;
&lt;p&gt;But that does that explain why iTerm2 works on Synology. Let&amp;rsquo;s look at it&amp;rsquo;s TERM value.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/screenshot-2025-07-26-at-12.12.45.png" width="882" alt=""&gt;
&lt;p&gt;Ah, so it&amp;rsquo;s just claiming to be xterm-256color the same as the mac terminal emulator - which all hosts will know since it&amp;rsquo;s an ancient thing. (xterm is the terminal from the X-Windows systems of the 1980&amp;rsquo;s).&lt;/p&gt;
&lt;p&gt;This is a developer choice, Ghostty could also claim it was an xterm-256color and this problem would not have popped up. I&amp;rsquo;m assuming they have decided this short term pain is worth it for some long term gain (of being able to do things an xter-256color terminal emulator can not).&lt;/p&gt;
&lt;h3 id="choices"&gt;Choices&lt;/h3&gt;
&lt;p&gt;Now we have two choices to fix this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Override the TERM choice the Ghostty developer has made for this host&lt;/li&gt;
&lt;li&gt;Compile the correct &lt;code&gt;terminfo&lt;/code&gt; into the config on this host&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Option one means we&amp;rsquo;d lose any special sauce Ghostty does (which I already said I don&amp;rsquo;t need, but could conceivably regret in the future if they do something cool).&lt;/p&gt;
&lt;p&gt;Option two feels like the proper (and is the one that Ghostty recommends).&lt;/p&gt;
&lt;p&gt;I suppose there is a third choice - wait until Synology includes the Ghostty terminfo in their distro. I get the vibe from the slightly scary MOTD when I ssh in that they would really prefer you did not, so I can&amp;rsquo;t seem them going out of their way to include it. Also they are not based on another distro as far as I can see, so they are not going to accidentally include it from an upstream. I feel this choice will never bear fruit.&lt;/p&gt;
&lt;h3 id="overriding-the-term"&gt;Overriding the TERM&lt;/h3&gt;
&lt;p&gt;ssh can have a config set for a host so we can override the TERM value. If you have something like this in &lt;code&gt;~/.ssh/config&lt;/code&gt; we can fix the issue.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Host NAS-DS2
 SetEnv TERM=xterm-256color
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screenshot-2025-07-26-at-12.46.46.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;And everything works again - I can up arrow to access the history without problems and the &lt;code&gt;clear&lt;/code&gt; command works as advertised. Note that I didn&amp;rsquo;t need to reload the ssh config - it gets read every time you run the ssh command.&lt;/p&gt;
&lt;p&gt;An extra reason for using this approach is that if you have a good naming convention for your hosts (I worked in the &amp;lsquo;data processing&amp;rsquo; department of a bank in the 1990&amp;rsquo;s so I learned good naming conventions for hosts) then you can wild-card this entry to for all of them. You might have guessed that all the Synology NAS&amp;rsquo;s I manage are named &lt;code&gt;NAS-DS&amp;lt;some positive integer&amp;gt;&lt;/code&gt;. Let&amp;rsquo;s change the config to say:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Host NAS-DS*
 SetEnv TERM=xterm-256color
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screenshot-2025-07-26-at-12.51.17.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Nice. If I had hundreds of these to deal with, that&amp;rsquo;s definitely the solution I&amp;rsquo;d be going for. Also, if you&amp;rsquo;ve come here because you had this exact problem stop here. This is the best you are going to do.&lt;/p&gt;
&lt;h3 id="installing-the-terminfo"&gt;Installing the terminfo&lt;/h3&gt;
&lt;p&gt;The alternate approach is to extract the xterm-ghostty &lt;code&gt;terminfo&lt;/code&gt; off the current machine (in my case a MacBook) to the other host, and compile it into the available &lt;code&gt;terminfo&lt;/code&gt;&amp;rsquo;s there. This seems more invasive, but it&amp;rsquo;s a per user thing and can be reversed.&lt;/p&gt;
&lt;p&gt;The command given in the &lt;a href="https://ghostty.org/docs/help/terminfo#ssh"&gt;Ghostty docs&lt;/a&gt; is:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;infocmp -x xterm-ghostty | ssh YOUR-SERVER -- tic -x -
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s try it:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screenshot-2025-07-26-at-13.02.16.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;This is not that surprising - Synology DSM is minimal (I think it uses busybox) so it&amp;rsquo;s missing lots of these commands. You might think that&amp;rsquo;s okay, I&amp;rsquo;ll compile it on this machine then &lt;code&gt;scp&lt;/code&gt; it across. That would be something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;infocmp -x xterm-ghostty &amp;gt; xterm-ghostty.src
tic -x -o ./terminfo xterm-ghostty.src
scp -r ./terminfo/78 ds2_admin@NAS-DS2:~/.terminfo/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But that won&amp;rsquo;t work because you don&amp;rsquo;t have &lt;code&gt;scp&lt;/code&gt; on the Synology either. So perhaps you think you&amp;rsquo;ll enable rysnc in the NAS GUI and rsync the file in:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rsync -av ./terminfo/78 ds2_admin@NAS-DS2:~/.terminfo/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Which will copy the file over, but it still won&amp;rsquo;t work. It&amp;rsquo;s just too minimal.&lt;/p&gt;
&lt;p&gt;I imagine this process works for other distros or it wouldn&amp;rsquo;t be in the Ghostty docs. But it does not work for Synology NASs in 2025.&lt;/p&gt;</description></item><item><title>Manually adding SSL certs in Nginx Proxy Manager</title><link>https://blog.iankulin.com/manually-adding-ssl-certs-in-nginx-proxy-manager/</link><pubDate>Mon, 31 Mar 2025 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/manually-adding-ssl-certs-in-nginx-proxy-manager/</guid><description>&lt;p&gt;A large part of the reason for my use of Nginx Proxy manager over vanilla NGINX, is that it has built-in Let&amp;rsquo;s Encrypt certificate requesting and renewing. This works perfectly for all my public facing services, and until recently, my homelab services. Before I dive into how I&amp;rsquo;ve fixed the problem I ran into, I better explain how my homelab domain is set up, and before I do that, an over-simplified description of how the SSL system works is required&lt;/p&gt;
&lt;h3 id="ssl"&gt;SSL&lt;/h3&gt;
&lt;p&gt;SSL (Secure Socket Layer) on a web site (the little padlock you see in the browser when you visit an https:// site) does three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It tells you that this web site you&amp;rsquo;ve visited is controlled by the same people who own the domain name. This is important to prevent someone hijacking your request to &amp;ldquo;mysecurebank.com&amp;rdquo; and sending it to their password stealing website.&lt;/li&gt;
&lt;li&gt;It encrypts the traffic between the web-browser and the website so it can&amp;rsquo;t be spied on.&lt;/li&gt;
&lt;li&gt;It detects is someone has tried to tamper with the traffic between the web-browser and the website.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For all this to work, there needs to be some way of assuring the certificate issuer that the entity claiming a certificate for the domain, has control of the website that the domain is pointing to. The simplest way to do this is for the certificate issuer to give you a token, you install that in a secret directory on the web server, then the certificate issuer can check it exists. This proves to them that you own the website, and they can issue you the certificate.&lt;/p&gt;
&lt;p&gt;This process is essentially what happens when you use &lt;a href="https://certbot.eff.org/"&gt;Certbot&lt;/a&gt; to obtain a &lt;a href="https://letsencrypt.org/"&gt;Let&amp;rsquo;s Encrypt&lt;/a&gt; SSL certificate. It works great as long as your website is public to the internet so it can be contacted by the certificate issuer. But, that&amp;rsquo;s not the case for my homelab services. They are on an internal network, deliberately not contactable from the wild internet.&lt;/p&gt;
&lt;h3 id="ssl-on-an-internal-network"&gt;SSL on an internal network&lt;/h3&gt;
&lt;p&gt;There is less need for SSL on an internal network. You probably assume there&amp;rsquo;s no one to spy on your traffic, or to fiddle with it, and you know you have control of your apps. Apart from not trusting that, there&amp;rsquo;s a couple of other benefits - you often can&amp;rsquo;t save your passwords for unsecured sites, you can get annoying warning messages, and some apps just straight up won&amp;rsquo;t let you use some functionality without it. This is the case for my Forgejo instance that wants working SSL to allow me to git push to it.&lt;/p&gt;
&lt;h3 id="homelab-ssl"&gt;Homelab SSL&lt;/h3&gt;
&lt;p&gt;There is a way around this - basically we need to assure the certificate issuer that we&amp;rsquo;re in control, but instead of exposing a token on the (unreachable) web server, we add it as a record in the DNS of the domain (called DNS Challenge). The logic of this is that if you control the DNS you control where it points and therefore the web server. This is the first part of the Homelab SSL setup - obtaining the certificate with DNS challenge. The second part is using the DNS to point the domain at an internal website address. My domain and DNS are public - you could enter the domain name in a browser, but it resolves to an address inside my network. This is all much better explained by Wolfgang than me.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/qlcVx-k-02E?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;h3 id="the-problem-ive-run-into"&gt;The problem I&amp;rsquo;ve run into&lt;/h3&gt;
&lt;p&gt;This all worked perfectly when I first set it up. Nginx Proxy Manager (NPM) has a plugin for my domain provider (&lt;a href="http://porkbun.com"&gt;porkbun&lt;/a&gt;) which uses their API to save the token into my DNS settings. The UX for this is that I tell Nginx Proxy Manager that I want to use &amp;ldquo;DNS Challenge&amp;rdquo; for a certificate request, and (by using an API key I&amp;rsquo;ve setup on Porkbun and given to NPM) it does all the fiddling around to obtain the certificate then installs them.&lt;/p&gt;
&lt;p&gt;I must of had that running for a year or so, with the certificates magically being renewed every couple of months with no input from my until just recently. I&amp;rsquo;m not exactly sure what&amp;rsquo;s happened - the error messages that I&amp;rsquo;m not smart enough to sort out suggest that the plugin&amp;rsquo;s operations to install the token at the domain provider is not working. I don&amp;rsquo;t know if it&amp;rsquo;s an API problem, or there&amp;rsquo;s been an NPM update that&amp;rsquo;s broken the plugin, or just something else has changed in my setup. What ever it is, turning everything off and on again, updating everything, and trying manually have not worked. So time for plan B.&lt;/p&gt;
&lt;h3 id="manual-certificates"&gt;Manual Certificates&lt;/h3&gt;
&lt;p&gt;Porkbun (and for all I know other domain sellers) provide a facility to download a &amp;lsquo;certificate bundle&amp;rsquo; directly from them - they are just doing that Let&amp;rsquo;s Encrypt dance directly - missing the NPM and Porkbun API step from the above.&lt;/p&gt;
&lt;p&gt;The certificate bundle contains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;public.key.pem&lt;/li&gt;
&lt;li&gt;private.key.pem&lt;/li&gt;
&lt;li&gt;domain.cert.pem&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And when you go to add a custom certificate in NPM you have these options:&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/screenshot-2025-03-08-at-15.47.04.png" width="981" alt=""&gt;
&lt;p&gt;As you can see your certificate (domain.cert.pem) goes in the certificate slot, and the Certificate Key it&amp;rsquo;s asking for is your private key (private.key.pem). You don&amp;rsquo;t need the intermediate key - this is the same for all Let&amp;rsquo;s Encrypt certificates.&lt;/p&gt;
&lt;p&gt;Doing the certificates this way is less good than having them automatically renewed. Currently Let&amp;rsquo;s Encrypt certificates are good for 90 days, so every three months my monitoring system will let me know they only have a couple of weeks left and I&amp;rsquo;ll have to repeat this manual process. There has been talk of shortening this time which would make that process even more annoying, so hopefully I can sort out the issue in NPM, or find out if Traefik or Caddy have the necessary plugins to do DNS challenge certificates.&lt;/p&gt;
&lt;p&gt;But in the meantime, my internal web apps are all up and secure.&lt;/p&gt;
&lt;h3 id="this-is-not-free"&gt;This is not free&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s Encrypt, and to a lesser extent Certbot have changed the web substantially. Obtaining and installing certificates used to be a difficult and costly process, but these two &amp;lsquo;free&amp;rsquo; services have turned that around. If you run a website and use these services I highly recommend you support the non-profits that keep them in existence as I do.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Let&amp;rsquo;s Encrypt - &lt;a href="https://letsencrypt.org/donate/"&gt;have a donation page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Certbot - is provided by the EFF who do other work can be &lt;a href="https://supporters.eff.org/donate/support-work-on-certbot"&gt;donated to here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Command chaining with NTFY for long running commands</title><link>https://blog.iankulin.com/command-chaining-with-ntfy-for-long-running-commands/</link><pubDate>Mon, 03 Feb 2025 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/command-chaining-with-ntfy-for-long-running-commands/</guid><description>&lt;p&gt;&lt;a href="https://ntfy.sh/"&gt;NTFY&lt;/a&gt; is a great open-source push notification service that&amp;rsquo;s self-hostable or free to use (although I suggest you &lt;a href="https://liberapay.com/ntfy"&gt;pay for it&lt;/a&gt; as I do). I&amp;rsquo;ve written before how I use it with &lt;a href="https://blog.iankulin.com/uptime-kuma-nfty/"&gt;UptimeKuma&lt;/a&gt; for my uptime monitoring, but another common use is just when I&amp;rsquo;m initiating long-running commands and backgrounding them.&lt;/p&gt;
&lt;p&gt;This magic is possible since we can just &lt;code&gt;curl&lt;/code&gt; to send a NTFY notification. For example:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;curl -d &amp;#34;😀 demo push message via NTFY&amp;#34; ntfy.sh/blog_demo
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since I&amp;rsquo;m subscribed to the &amp;ldquo;blog_demo&amp;rdquo; topic in NTFY, this message will be pushed to my phone and watch:&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/img_0056.png" width="640" alt=""&gt;
&lt;p&gt;How I use this is with &amp;lsquo;command chaining&amp;rsquo;. In Linux, you can stack commands together with the &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; characters like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mkdir test_dir &amp;amp;&amp;amp; echo &amp;#34;success&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will create the directory, then print &amp;ldquo;success&amp;rdquo; to the shell. I could use it like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;nohup rsync -rvits --bwlimit=20 &amp;#34;/volume1/media/video/Movies/Night of the Living Dead (1968)/&amp;#34; ds1_admin@100.78.2.105:&amp;#34;/volume1/media/video/Movies/Night of the Living Dead (1968)&amp;#34; &amp;gt; output.log 2&amp;gt;&amp;amp;1 &amp;amp;&amp;amp; curl -d &amp;#34;💾 upload to vm500-kr complete&amp;#34; ntfy.sh/blog_demo &amp;amp;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Both commands will run in the background, and the output of the first command is directed into the &amp;lsquo;output.log&amp;rsquo; file. If the rsync file transfer (that is going to take all night) finishes successfully, then the message saying it&amp;rsquo;s complete will be sent.&lt;/p&gt;
&lt;p&gt;What about if it fails? Well, posix has you covered here too. There&amp;rsquo;s a &lt;code&gt;||&lt;/code&gt; chaining operator that only runs if a command fails.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mkdir invalid/name &amp;amp;&amp;amp; (echo &amp;#34;Directory created successfully.&amp;#34;) || (echo &amp;#34;Failed to create directory.&amp;#34;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the command above, if we already have a directory called &lt;code&gt;invalid&lt;/code&gt;, the &lt;code&gt;mkdir&lt;/code&gt; will work and we&amp;rsquo;ll get the message &amp;ldquo;Directory created successfully.&amp;rdquo;. If &lt;code&gt;invalid&lt;/code&gt; doesn&amp;rsquo;t exist, the command will fail and we&amp;rsquo;ll get the message &amp;ldquo;Failed to create directory.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Note that I&amp;rsquo;ve added some parenthesis - it makes things clearer for the reader, and the command line parser.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s apply this to our slow file transfer:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;nohup rsync -rvits --bwlimit=20 &amp;#34;/volume1/media/video/Movies/Night of the Living Dead (1968)/&amp;#34; ds1_admin@100.78.2.105:&amp;#34;/volume1/media/video/Movies/Night of the Living Dead (1968)&amp;#34; &amp;gt; output.log 2&amp;gt;&amp;amp;1 &amp;amp;&amp;amp; curl -d &amp;#34;💾 upload to vm500-kr complete&amp;#34; ntfy.sh/blog_demo || curl -d &amp;#34;⚠️ upload to vm500-kr failed!&amp;#34; ntfy.sh/blog_demo &amp;amp;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we&amp;rsquo;ll get a push message for completion or failure. There is one more little bit of housekeeping to do though. When we curl ntfy like this, it actually returns some JSON:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-12-28-at-11.00.08-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-12-28-at-11.00.08-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Since we&amp;rsquo;re running this whole thing backgrounded, we really want that to go to the &lt;code&gt;output.log&lt;/code&gt; file with the other output:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;nohup rsync -rvits --bwlimit=20 &amp;#34;/volume1/media/video/Movies/Night of the Living Dead (1968)/&amp;#34; ds1_admin@100.78.2.105:&amp;#34;/volume1/media/video/Movies/Night of the Living Dead (1968)&amp;#34; &amp;gt; output.log 2&amp;gt;&amp;amp;1 &amp;amp;&amp;amp; curl -d &amp;#34;💾 upload to vm500-kr complete&amp;#34; ntfy.sh/blog_demo &amp;gt;&amp;gt; output.log || curl -d &amp;#34;⚠️ upload to vm500-kr failed!&amp;#34; ntfy.sh/blog_demo &amp;gt;&amp;gt; output.log &amp;amp;
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Share files securely with Enclosed</title><link>https://blog.iankulin.com/share-files-securely-with-enclosed/</link><pubDate>Mon, 27 Jan 2025 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/share-files-securely-with-enclosed/</guid><description>&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-12-05-at-7.53.56-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-12-05-at-7.53.56-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;My accountant works for one of those giant firms, and it bugs me that I&amp;rsquo;m emailing him password protected zip files of my accounts rather than to a secure upload facility at his firm. I can fix this with the power of self hosting, by running my own secure file dropping app on a VPS.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a number of applications that &lt;a href="https://github.com/awesome-selfhosted/awesome-selfhosted?tab=readme-ov-file#file-transfer---single-click--drag-n-drop-upload"&gt;do this sort of thing&lt;/a&gt; - allow you to upload a file, get a link in return which you can then share to people to download the file. For this to be more secure than emailing, the file needs to be encrypted on the server, and we want to be able to set a password, impose limits on downloads, and limit how long the link lives for. I&amp;rsquo;ve previously looked at &lt;a href="https://github.com/eikek/sharry"&gt;Sharry&lt;/a&gt; which adds the ability for unauthenticated users to &lt;em&gt;upload&lt;/em&gt; files to you securely, but for this slightly simpler job, I chose &lt;a href="https://github.com/CorentinTh/enclosed"&gt;Enclosed&lt;/a&gt; by &lt;a href="https://corentin.tech/"&gt;Corentin Thomasset&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The docs provide a &lt;a href="https://docs.enclosed.cc/self-hosting/docker-compose"&gt;simple compose file&lt;/a&gt; to get going docker. Mine is slightly more complex because it&amp;rsquo;s proxy-ed with Nginx Proxy Manager, so it needs to share it&amp;rsquo;s network.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;services:
 enclosed:
 container_name: enclosed
 image: corentinth/enclosed
 restart: unless-stopped
 networks:
 - nginx-proxy-manager_default

networks:
 nginx-proxy-manager_default:
 external: true
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="authentication"&gt;Authentication&lt;/h3&gt;
&lt;p&gt;What&amp;rsquo;s not well explained in the docs is how to set up authenticated login. By default, if you throw this up on a VPS, the entire world can use it to share their files. What I&amp;rsquo;d like is that I log in to share a file, but the person I send the link to can download the file without logging in. This is easy to do, we just need to add a couple of environment variables to our compose file. I always like to keep my secrets in an .env file since I source control all my home-lab and VPS setups, and I don&amp;rsquo;t want the secrets in source control.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a sample .env file. This just goes in the same directory as our docker-compose.yml&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;AUTHENTICATION_USERS=example@example.com:$$2a$$10$$n4StEr5Tcat7jItq
PUBLIC_IS_AUTHENTICATION_REQUIRED=true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The AUTHENTICATION_USERS string is just the username and a bcrypt salted/hashed password. You don&amp;rsquo;t need to do anything hard to create this, the project kindly provides a &lt;a href="https://docs.enclosed.cc/self-hosting/users-authentication-key-generator"&gt;tool for it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The tool includes an option for escaping the &amp;lsquo;$&amp;rsquo; character correctly for docker compose files (hence the double $ in the string above.&lt;/p&gt;
&lt;p&gt;To use this &lt;code&gt;.env&lt;/code&gt; file, we pull in the values in the docker-compose thus:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;services:
 enclosed:
 container_name: enclosed
 image: corentinth/enclosed
 environment:
 - AUTHENTICATION_USERS=${AUTHENTICATION_USERS}
 - PUBLIC_IS_AUTHENTICATION_REQUIRED=${PUBLIC_IS_AUTHENTICATION_REQUIRED}
 restart: unless-stopped
 networks:
 - nginx-proxy-manager_default

networks:
 nginx-proxy-manager_default:
 external: true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once that&amp;rsquo;s running, the user name and password will required to upload files or write notes. The interface is clean and self-explanatory:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-12-05-at-8.34.47-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-12-05-at-8.34.47-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>NGINX proxy manager - setting headers to use basic auth in your apps</title><link>https://blog.iankulin.com/nginx-proxy-manager-setting-headers-to-use-basic-auth-in-your-apps/</link><pubDate>Mon, 09 Dec 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/nginx-proxy-manager-setting-headers-to-use-basic-auth-in-your-apps/</guid><description>&lt;p&gt;When I&amp;rsquo;m spinning up side projects, I frequently ignore auth, and just rely on NGINX basic auth - one of the side benefits of reverse-proxying everything.&lt;/p&gt;
&lt;h3 id="regular-nginx"&gt;Regular NGINX&lt;/h3&gt;
&lt;p&gt;This &lt;a href="https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/"&gt;article in the docs&lt;/a&gt; explains how to set up basic auth to protect different paths. To make it work in my node apps, I need the successful user name passed in so I check it against the user table to work out access rights etc.&lt;/p&gt;
&lt;p&gt;To get it passed in with every request, we need to stick it in the headers. We do this in the NGINX conf for the site:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;server {
	root /var/www/ct.example.com;
	index index.html;
	server_name ct.example.com;
	location / {
		auth_basic &amp;#34;Secure app&amp;#34;;
	 auth_basic_user_file /etc/nginx/.htpasswd;
	 
		proxy_pass http://localhost:3000;	
		proxy_http_version 1.1;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection &amp;#39;upgrade&amp;#39;;
		proxy_set_header Host $host;
		proxy_set_header X-Username $remote_user;
		proxy_cache_bypass $http_upgrade;
	}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then in my app, I just check the header like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const USERNAME_HEADER = &amp;#34;x-username&amp;#34;;

// basic auth middleware
app.use((req, res, next) =&amp;gt; {
 const username = req.headers[USERNAME_HEADER];

 if (!username) {
 return res.status(400).send(`Missing header: ${USERNAME_HEADER}`);
 }

 const user = usersArray.find((user) =&amp;gt; user.user === username);

 if (user) {
 // Save user details to req object
 req.role = user.role;
 req.username = username;
 next();
 } else {
 res.status(401).send(`User unauthorised: ${username}`);
 }
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It&amp;rsquo;s a bit of a fudge, but for personal use apps it&amp;rsquo;s quick to set up, and pretty robust from a security point of view.&lt;/p&gt;
&lt;h3 id="nginx-proxy-manager"&gt;NGINX Proxy Manager&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve moved to using NGINX Proxy Manager (NPM) rather than raw NGINX since it makes getting SSL certificates super simple. NPM basically wraps all the reverse proxy functionality of NGINX into a nice click ops web GUI.&lt;/p&gt;
&lt;p&gt;We now specify the user names and passwords in the gui (instead of faffing around installing apache2-utils and running it to build the &lt;code&gt;.htpasswd&lt;/code&gt; file. This is done by creating an access list:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-10-27-at-11.43.16-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-10-27-at-11.43.16-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then in the Authorization tab, adding your users:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-10-27-at-11.42.44-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-10-27-at-11.42.44-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Once that&amp;rsquo;s saved, we apply it to the proxy record:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-10-27-at-11.47.38-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-10-27-at-11.47.38-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;But then how do we set the x-username header? Since there&amp;rsquo;s an advanced tab, you may think it&amp;rsquo;s in there, and that looks promising till you read the warning below the text box.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-10-27-at-11.52.24-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-10-27-at-11.52.24-am.png" width="839" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Setting a header is exactly what we want to do, so let&amp;rsquo;s head to the &amp;ldquo;locations&amp;rdquo; tab.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-10-27-at-11.55.04-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-10-27-at-11.55.04-am.png" width="674" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We just use the root path &amp;ldquo;/&amp;rdquo; for our path so it applies to every request, proxy those requests to our app, and set the header in the text box (which appears when you click the gear button).&lt;/p&gt;</description></item><item><title>rsync between Synology NAS</title><link>https://blog.iankulin.com/rsync-between-synology-nas/</link><pubDate>Mon, 30 Sep 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/rsync-between-synology-nas/</guid><description>&lt;p&gt;A while ago, I devised a complicated system where I could drop files in a web interface running on an LXD container and the files would then magically appear in a directory on a remote NAS in the morning. It turned out to not be very robust, and I gave up on it after a while.&lt;/p&gt;
&lt;p&gt;Also, really there should be no need for it - underneath, it was just using &lt;code&gt;rsync&lt;/code&gt; to move the files, so why not just do that direct from one NAS to another? Well, mainly because my NASs are all Synology - which I love, and they&amp;rsquo;ve been great, but in an effort to make them usable by muggles, Synology tend to somewhat complicate things for Linux command line wizards.&lt;/p&gt;
&lt;p&gt;It turns out to be totally possible to command line &lt;code&gt;rsync&lt;/code&gt;, including doing it over Tailscale, but there&amp;rsquo;s a couple of gotchas along the way.&lt;/p&gt;
&lt;h3 id="rsync-the-synology-way"&gt;rsync the Synology way&lt;/h3&gt;
&lt;p&gt;A reasonable question would be why didn&amp;rsquo;t I use the Synology rsync user interface to do all this - it&amp;rsquo;s right there in Control Panel / File Services? I did actually look at doing that, but after five minutes I couldn&amp;rsquo;t figure it out, so yeah na. It&amp;rsquo;s the command line for me.&lt;/p&gt;
&lt;h3 id="steps"&gt;Steps&lt;/h3&gt;
&lt;p&gt;The plan for the rest of this post is just to run through, in approximate order, the steps you&amp;rsquo;ll need to take to get &lt;code&gt;rsync&lt;/code&gt; working from the command line to sync files between two synology NASs. It&amp;rsquo;s probably also helpful between a real system and a Synology NAS. I&amp;rsquo;m going to talk about the &amp;rsquo;local&amp;rsquo; NAS (where we&amp;rsquo;ll be running the rsync command) and &amp;lsquo;remote&amp;rsquo; one. This is just for convenience - you might have two local or two remote NASs - I don&amp;rsquo;t judge. I&amp;rsquo;m just calling mine &amp;rsquo;local&amp;rsquo; and &amp;lsquo;remote&amp;rsquo; for this post that so you know which device I&amp;rsquo;m talking about.&lt;/p&gt;
&lt;h4 id="ssh"&gt;ssh&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;rsync&lt;/code&gt; works over an ssh connection, so you need to be able to ssh from one NAS to another without entering a password first. To test it, ssh into the local NAS, then without logging out, ssh into the remote NAS from the local one. If that works without asking for a password you&amp;rsquo;ve completed this step and can just ctrl-D to drop back to the local NAS.&lt;/p&gt;
&lt;p&gt;If the issue is that it asked for a password, that just means you need to install your public ssh keys on the remote. I usually do this with the &lt;code&gt;ssh-copy-id&lt;/code&gt; command on regular Linux, Mac and BSD systems, but that&amp;rsquo;s not available at the Synology command line so we&amp;rsquo;ll have to do it the old fashioned way.&lt;/p&gt;
&lt;p&gt;Anything to do with ssh is stored in a hidden directory, &lt;code&gt;.ssh&lt;/code&gt; in a user&amp;rsquo;s home directory. For example you can check you&amp;rsquo;ve got public keys with:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ls -la ~/.ssh/id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These are the keys you want to add to the remote NASs authorised keys, so we&amp;rsquo;ll use ssh (with a password) to add them to the end of that file:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ssh &amp;lt;user&amp;gt;@&amp;lt;remote NAS address&amp;gt; &amp;#39;cat &amp;gt;&amp;gt; ~/.ssh/authorized_keys&amp;#39; &amp;lt; ~/.ssh/id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You need to substitute your remote NASs username and address, so mayby it would look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ssh nas1_admin@83.78.2.105 &amp;#39;cat &amp;gt;&amp;gt; ~/.ssh/authorized_keys&amp;#39; &amp;lt; ~/.ssh/id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When you execute this, it will ask for the remote password, but once it&amp;rsquo;s worked you should be able to ssh in and it allows that without using a password.&lt;/p&gt;
&lt;h4 id="tailscale-out"&gt;Tailscale out&lt;/h4&gt;
&lt;p&gt;Perhaps you didn&amp;rsquo;t get as far as needing the ssh password, because when you tried to ssh to the remote, ssh didn&amp;rsquo;t even recognise the domain. If you are using Tailscale to connect your devices (which I recommend) then there are two tricks needed.&lt;/p&gt;
&lt;p&gt;Trick one is to get around the fact that since DSM 7, Synology have prevented (for good security reasons) external packages from making outbound connections. So you&amp;rsquo;ll be able to use Tailscale to access the Synology web interface, or even ssh &lt;em&gt;into&lt;/em&gt; it, but you won&amp;rsquo;t be able to ssh &lt;em&gt;out&lt;/em&gt; of it. When I first discovered this, I was running &lt;code&gt;ip a&lt;/code&gt; at the command line in the local NAS and noticed that the tailscale IP was not even listed - it was as if Tailscale wasn&amp;rsquo;t running, but I knew it was since I had ssh&amp;rsquo;d in with the Tailscale address.&lt;/p&gt;
&lt;p&gt;Tailscale have a fix for &lt;a href="https://tailscale.com/kb/1131/synology#enable-outbound-connections"&gt;enabling outbound connections via Tailscale on Synology&lt;/a&gt;, you need to run a thing on reboot to enable the TUN.&lt;/p&gt;
&lt;p&gt;Trick two is that even after you&amp;rsquo;ve done that and rebooted and can see the tailscale interface when you run &lt;code&gt;ip a&lt;/code&gt;, you still won&amp;rsquo;t be able to use the Tailscale &amp;lsquo;magic&amp;rsquo; DNS but will have to use the Tailscale IP address for the remote when you ssh (and later rsync) to it. So I can&amp;rsquo;t use:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ssh nas1_admin@NAS-01&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;as I would normally from my laptop, I have to use &lt;code&gt;ssh nas1_admin@104.43.22.181&lt;/code&gt; If you are not sure of the Tailscale IP for your remote, have a look at your &lt;a href="https://login.tailscale.com/admin/machines"&gt;machines list&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="turn-rsync-on"&gt;Turn rsync on&lt;/h4&gt;
&lt;p&gt;Via the web interface on both Synologys, you&amp;rsquo;ll need to enable rsync. The setting is in &lt;code&gt;Control Panel | File Services | rsync&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-25-at-1.56.57-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-25-at-1.56.57-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Leave the port as 22 and don&amp;rsquo;t bother with the other settings, but do hit &lt;code&gt;Apply&lt;/code&gt; at the bottom to save the change.&lt;/p&gt;
&lt;h4 id="give-it-a-try"&gt;Give it a try&lt;/h4&gt;
&lt;p&gt;We&amp;rsquo;re now at the stage where you should be able to ssh into the remote NAS from the local one without being asked for a password, and rsync is turned on both ends, so in theory, you should be able to do something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rsync -rvitn /volume1/ nas1_admin@104.43.22.181:/volume1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;rsquo;m not going to go into all the flags for rsync (the internet has plenty of good guides for that) except to say that the &amp;rsquo;n&amp;rsquo; on the end of the flags in the command above means that no files will actually be moved, it will do a &amp;lsquo;dry run&amp;rsquo; and tell you what it would have done if you let it.&lt;/p&gt;
&lt;p&gt;Note that if you have a jazillion files, this could take a while, you might be better to limit it to a smaller sub directory such as &lt;code&gt;/volume1/media/music/napster/Metallica&lt;/code&gt;/&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/yo-dawg-heard-you.jpg" width="497" alt=""&gt;
&lt;p&gt;The other bit of free rsync advice I&amp;rsquo;ll give you is to look carefully at the source and destination directories in the command above. The source sub directory has a trailing &amp;lsquo;/&amp;rsquo;, the destination does not. If you mess this up you&amp;rsquo;ll be making directories inside your directories dawg.&lt;/p&gt;
&lt;p&gt;In theory once you&amp;rsquo;re at this point, everything should work. But here&amp;rsquo;s a couple of other bumps / thoughts.&lt;/p&gt;
&lt;h4 id="eadir"&gt;@eaDir&lt;/h4&gt;
&lt;p&gt;Synology has a bunch of hidden directories with metadata stuff. My advice is don&amp;rsquo;t mess with them, but also don&amp;rsquo;t sync them over either. Tell rsync to ignore them. Same for the recycle bin.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rsync -rvitn --exclude &amp;#39;*@eaDir*&amp;#39; --exclude &amp;#39;#recycle*&amp;#39; /volume1/ nas1_admin@104.43.22.181:/volume1
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="permissions"&gt;Permissions&lt;/h3&gt;
&lt;p&gt;The user that you&amp;rsquo;re ssh&amp;rsquo;ing with needs to have permissions to all the places you are rsync&amp;rsquo;ing files to. Even though I&amp;rsquo;ve only ever had one user for each of my Synology NAS&amp;rsquo;s, and everything has been done by that one user either through the web GUI or command line, the files and directories on my NAS have a mixture of owners (my user and root) and permissions. Someone smarter than me could probably figure out why - and if your NAS has to include files from multiple users etc, you are going to need to do that. Because I like sledgehammers, all I did was ssh into the remote and:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo chown -R nas1_admin:users /volume1/media
sudo chmod -R 775 /volume1/media
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="throttling-bandwidth"&gt;Throttling bandwidth&lt;/h4&gt;
&lt;p&gt;If I saturate the downlink at my remote site while I&amp;rsquo;m rsync-ing a bunch of files, the users there will be unhappy when they can&amp;rsquo;t stream video reliability or if they&amp;rsquo;re getting killed in online games due to lag.&lt;/p&gt;
&lt;p&gt;rsync has a flag for that. If we want to limit the transfer bandwidth to 500KB it could look like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rsync -rvitn --bwlimit=500 --exclude &amp;#39;*@eaDir*&amp;#39; --exclude &amp;#39;#recycle*&amp;#39; /volume1/ nas1_admin@104.43.22.181:/volume1
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="get-destructive"&gt;Get destructive&lt;/h4&gt;
&lt;p&gt;If you only want to sync the files one way from your local to remote, then we can add a flag that will delete any files on the remote machine that are not present on the local one. Obviously use with care, and run with the -n flag first to see what&amp;rsquo;s going to get chopped.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rsync -rvitn --bwlimit=500 --exclude &amp;#39;*@eaDir*&amp;#39; --exclude &amp;#39;#recycle*&amp;#39; /volume1/ nas1_admin@104.43.22.181:/volume1 --del
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="do-something-else"&gt;Do something else&lt;/h4&gt;
&lt;p&gt;The first few times you do this, it will be exciting watching the terminal window as rsync carefully checks for each file and directory and copies them over as needed, and it will also be helpful to see what errors might pop up so you can sort them out.&lt;/p&gt;
&lt;p&gt;Eventually though, it will be so routine and error free you&amp;rsquo;d rather do something else, so you&amp;rsquo;ll wander off and leave it, then curse when you return to find your laptop turned itself off due to inactivity and wrecked the rsync. Don&amp;rsquo;t panic, rsync is robust and will pick right up next time you run it without damaging any files, but you might also consider doing it all on remote control.&lt;/p&gt;
&lt;p&gt;On the local NAS, create a file called &lt;code&gt;sync-media.sh&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#!/bin/bash

nohup rsync -rvitn --bwlimit=500 --exclude &amp;#39;*@eaDir*&amp;#39; --exclude &amp;#39;#recycle*&amp;#39; /volume1/ nas1_admin@104.43.22.181:/volume1 &amp;gt; sync_media.log 2&amp;gt;&amp;amp;1 &amp;amp;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Make it executable:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;chmod +x sync_media.sh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once you run it &lt;code&gt;./sync-media.sh&lt;/code&gt; you can log off and let it do it&amp;rsquo;s thing.&lt;/p&gt;</description></item><item><title>Containerised NGINX Proxy Manager &amp; the 502 error</title><link>https://blog.iankulin.com/containerised-nginx-proxy-manager-the-502-error/</link><pubDate>Mon, 16 Sep 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/containerised-nginx-proxy-manager-the-502-error/</guid><description>&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-6.46.49-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-6.46.49-am.png" width="695" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re used to running NGINX Proxy Manager in front of your web apps, and switch to running it in a container, you&amp;rsquo;re going to need to learn a little about Docker networks to get everything connected. If you just do your regular setup, and direct the proxy for an address to &lt;code&gt;127.0.0.1:&amp;lt;some port&amp;gt;&lt;/code&gt;, it won&amp;rsquo;t exist, and you&amp;rsquo;ll visit your page to find the &amp;ldquo;502 Bad Gateway openresty&amp;rdquo; message.&lt;/p&gt;
&lt;p&gt;If you pause to think for a second it will be obvious why - with NGINX Proxy Manager (I&amp;rsquo;m going to start calling it NPM to save myself some typing) &lt;em&gt;inside&lt;/em&gt; a container, any addresses you&amp;rsquo;re entering into the web interface when setting up proxys are &lt;em&gt;inside&lt;/em&gt; the NPM container. &lt;code&gt;127.0.0.1&lt;/code&gt; from that point of view refers to the inside of the NPM container, and not the host, so you exposing a port from your container to the host is not going to work.&lt;/p&gt;
&lt;p&gt;The fix for this is pretty simple, but first let&amp;rsquo;s look at the exception.&lt;/p&gt;
&lt;h3 id="the-exception"&gt;The Exception&lt;/h3&gt;
&lt;p&gt;Usually the very first proxy I add in NPM is to it&amp;rsquo;s own admin interface on port 81. Since this &lt;em&gt;is&lt;/em&gt; inside the NPM container, the setup looks exactly the same as if you were running on the host, and may well be why you were lulled into a false sense of familiarity.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.14.55-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.14.55-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is a little bit confusing, since we can also manually access NPM from &lt;code&gt;http://127.0.0.1:81&lt;/code&gt; on the host if we&amp;rsquo;ve exposed port 81 in our compose file, but it&amp;rsquo;s actually a different route. In fact, we could hide port 81 from the host, and the setting above will still work.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s my compose file, notice I haven&amp;rsquo;t exposed port 81&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;services:
 nginx-proxy-manager:
 image: &amp;#39;jc21/nginx-proxy-manager:latest&amp;#39;
 container_name: nginx-proxy-manager
 restart: unless-stopped
 ports:
 - &amp;#39;80:80&amp;#39;
 - &amp;#39;443:443&amp;#39;
 volumes:
 - ./data:/data
 - ./letsencrypt:/etc/letsencrypt
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So if we try to access port 81 from the host, it won&amp;rsquo;t be answering.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.25.39-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.25.39-am.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;But inside the container, 127.0.0.1 refers to itself, so if we open a shell into the container, the URL http://127.0.0.1:81 refers to something that exists, from there.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.30.49-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.30.49-am.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="getting-out"&gt;Getting Out&lt;/h3&gt;
&lt;p&gt;So how can our NPM point to a service that&amp;rsquo;s running in another container? You are probably used to specifying ports: in your compose file to expose internal container ports to a host, but that doesn&amp;rsquo;t really help us since NPM in a container can not easily access the host&amp;rsquo;s ports. What we&amp;rsquo;d really like is for NPM to be able to access the second container&amp;rsquo;s ports. And in an ideal world, we&amp;rsquo;d like that to be the only way to access them - that way all the access to our second container service is forced through our proxy. Sounds like security no?&lt;/p&gt;
&lt;p&gt;Turns out this is simple thanks to the magic of Docker networks.&lt;/p&gt;
&lt;p&gt;You can get a fair way with Docker without really thinking or knowing about &lt;a href="https://docs.docker.com/engine/network/"&gt;Docker networks&lt;/a&gt;, and I&amp;rsquo;m really only covering the very basics here - you should probably invest some time in learning about this sometime. Meanwhile, lets run the &lt;code&gt;docker network ls&lt;/code&gt; command and see what networks we&amp;rsquo;ve got.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.41.33-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.41.33-am.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That network &lt;code&gt;nginx-proxy-manager_default&lt;/code&gt; is the one we&amp;rsquo;re interested in. Its name is just the container name with &amp;ldquo;_default&amp;rdquo; added on the end. What we need to do is just make sure the second container is included in that same network. That&amp;rsquo;s a matter of declaring the external network with that name, and including it in our service definition. I&amp;rsquo;m going to use an NGINX server in my example, but it could be anything. Here&amp;rsquo;s the compose for the second container.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;services:
 nginx-example.com:
 image: nginx
 container_name: nginx-example.com
 volumes:
 - ./www:/usr/share/nginx/html
 - ./conf/:/etc/nginx/conf.d/:ro
 restart: unless-stopped
 networks:
 - nginx-proxy-manager_default

networks:
 nginx-proxy-manager_default:
 external: true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that I haven&amp;rsquo;t exposed any ports to the host here; We&amp;rsquo;re not going to need them since we&amp;rsquo;ll access this container direct from the NPM container via the internal Docker network docker created for us. That little network even contains a DNS server, so we don&amp;rsquo;t even need to worry about the container&amp;rsquo;s IP addresses, we can just use their names.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.52.54-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;So any web requests to &amp;ldquo;example.com&amp;rdquo; arriving at our host go to NPM (since I&amp;rsquo;ve exposed ports 80 &amp;amp; 443 - see the top compose file). Then using the proxy I&amp;rsquo;ve added above, they are forwarded to the container named &amp;ldquo;nginx-example.com&amp;rdquo; which is a DNS record inside the Docker network that Docker created for us, and which both the NPM, and my service, containers are members of.&lt;/p&gt;</description></item><item><title>Moving from Docker volumes to bind mounts</title><link>https://blog.iankulin.com/moving-from-docker-volumes-to-bind-mounts/</link><pubDate>Mon, 05 Aug 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/moving-from-docker-volumes-to-bind-mounts/</guid><description>&lt;p&gt;&lt;a href="https://placesjournal.org/article/all-is-lost-notes-on-broken-world-design/"&gt;&lt;img src="https://blog.iankulin.com/images/friedman-moe-lost-6.jpg" width="600" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When I started with Docker, the docs seemed to suggest that using Docker volumes was a good thing. With a Docker volume, you just create the volume and Docker manages the rest. You don&amp;rsquo;t have to worry about where it is, or really ever think about it.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a docker-compose for &lt;a href="https://github.com/louislam/uptime-kuma/wiki"&gt;Uptime Kuma&lt;/a&gt; using a volume.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;services:
 uptime-kuma:
 image: louislam/uptime-kuma:1
 container_name: uptime-kuma
 volumes:
 - kuma_data:/app/data
 ports:
 - 80:3001
 restart: unless-stopped

volumes:
 kuma_data:
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is telling Docker we want to create a volume called &amp;ldquo;kuma_data&amp;rdquo; and then map it into the container file system at &lt;code&gt;/app/data&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t love this for a couple of reasons. The first is that I don&amp;rsquo;t know how to back that volume up - although this is a commonly quoted reason for using them. And the second is that, in order to make moving containers around easier, I have settled on a sort of standard setup. All Docker containers are in a subdirectory in my home directory where the subdirectory is their name. In that subdirectory is a docker-compose file to manage them, and a further subdirectory named &amp;lsquo;data&amp;rsquo; that holds all their data.&lt;/p&gt;
&lt;p&gt;For example, here&amp;rsquo;s my Jellyfin directory. The Jellyfin docker container needs two &amp;lsquo;volumes&amp;rsquo; - config and cache:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-07-21-at-2.57.34-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-07-21-at-2.57.34-pm.png" width="907" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So if I ever needed to move this somewhere, I could &lt;code&gt;docker compose down&lt;/code&gt;, then &lt;code&gt;rsync&lt;/code&gt; the whole directory somewhere, then &lt;code&gt;docker compose up&lt;/code&gt; &lt;code&gt;-d&lt;/code&gt;, and I&amp;rsquo;d be in business.&lt;/p&gt;
&lt;p&gt;Instead of using Docker volumes, I&amp;rsquo;m using &amp;lsquo;bind mounts&amp;rsquo; to those sub-directories. The docker-compose is not more complicated, if anything, it&amp;rsquo;s simpler since we don&amp;rsquo;t need the &lt;code&gt;volumes&lt;/code&gt; part at the bottom.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;services:
 jellyfin:
 image: jellyfin/jellyfin
 container_name: jellyfin
 network_mode: host
 ports:
 - &amp;#34;8096:8096&amp;#34;
 volumes:
 - ./data/config:/config
 - ./data/cache:/cache
 - /mnt/media:/media
 restart: unless-stopped
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="pros-and-cons"&gt;Pros and Cons&lt;/h3&gt;
&lt;p&gt;Many people and projects prefer Docker volumes. My system of just using bind mounts might increase the likelihood of me breaking something by monkeying around with the files - and volumes reduce that possibility since they&amp;rsquo;d be hidden away somewhere and I guess owned by root. There are also some performance considerations if running under Mac or Windows since Docker Desktop in those cases is really running your container on a VM, and if you let it manage the volumes it can do so in the native file system.&lt;/p&gt;
&lt;p&gt;Another downside is that bind mount locations are not automatically initialised in the same way as volumes.&lt;/p&gt;
&lt;p&gt;Nevertheless, for my use, I like to use bind mounts and keep everything together.&lt;/p&gt;
&lt;h3 id="moving-advice"&gt;Moving advice&lt;/h3&gt;
&lt;p&gt;Since I started with volumes, I now needed to be able to change over to using bind mounts, and the first advice I googled up (such as &lt;a href="https://forums.docker.com/t/move-docker-volume-to-bind-mount/140843"&gt;here&lt;/a&gt; and &lt;a href="https://www.synoforum.com/threads/move-from-docker-volume-to-bind-mount.12990/"&gt;here&lt;/a&gt;) made it sound like it was going to be complicated. The advice was that the hidden file location of the volumes in the host system had some sort of magic about it and we needed to use a &lt;code&gt;docker cp&lt;/code&gt; command to access it or create a temporary container with the named volume and the bind mount you want to move it to, then copy the data across from inside that container.&lt;/p&gt;
&lt;h3 id="ignoring-advice"&gt;Ignoring advice&lt;/h3&gt;
&lt;p&gt;As long as the container is stopped when you access the files, I can&amp;rsquo;t see why we can&amp;rsquo;t just copy them as normal. Of course, I could be totally incorrect about this, but I&amp;rsquo;ve done it a number of times now, and not had any disasters. Of course, I always snapshot the VM before I start in case it doesn&amp;rsquo;t work, and you should as well. If you like to yolo your production data, here&amp;rsquo;s how.&lt;/p&gt;
&lt;h3 id="where-is-it"&gt;Where is it?&lt;/h3&gt;
&lt;p&gt;To find the actual location of the data in the host system, use the docker inspect command on the running container. You&amp;rsquo;ll get a bunch of JSON. About a third up from the bottom of that there&amp;rsquo;ll be a section called &amp;ldquo;Mounts&amp;rdquo; that will have the real location for each volume. (if you&amp;rsquo;re looking at a &amp;ldquo;Mounts&amp;rdquo; section that doesn&amp;rsquo;t have the &amp;ldquo;Source&amp;rdquo; line, scroll down a bit).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-07-21-at-3.27.33-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-07-21-at-3.27.33-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="copying"&gt;Copying&lt;/h3&gt;
&lt;p&gt;Next thing, I&amp;rsquo;ll make a data sub-directory for it. &lt;code&gt;mkdir ~/uptimekuma/data&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The (with the container stopped), copy the data over.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo cp -a /var/lib/docker/volumes/uptimekuma_kuma_data/_data/. ~/uptimekuma/data
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After checking that&amp;rsquo;s actually worked, I&amp;rsquo;ll kill the volume. We need to get the volume name first if you haven&amp;rsquo;t figured it out from the location path.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ian@ct390-test:~/uptimekuma$ sudo docker volume ls
DRIVER VOLUME NAME
local uptimekuma_kuma_data
ian@ct390-test:~/uptimekuma$ sudo docker volume rm uptimekuma_kuma_data 
uptimekuma_kuma_data
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we&amp;rsquo;ll need to update the &lt;code&gt;docker-compose.yaml&lt;/code&gt; file from the top of this post so it bind mounts the sub-directory that we&amp;rsquo;ve copied to instead of the named volume.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;services:
 uptime-kuma:
 image: louislam/uptime-kuma:1
 container_name: uptime-kuma
 volumes:
 - ./data:/app/data
 ports:
 - 80:3001
 restart: unless-stopped
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once that&amp;rsquo;s done, &lt;code&gt;docker compose up -d&lt;/code&gt; should get things running. And now we have a nice situation with the docker compose and all the data for the container together in one spot.&lt;/p&gt;</description></item><item><title>Upgrading to Forgejo 7.0.1</title><link>https://blog.iankulin.com/upgrading-to-forgejo-7-0-1/</link><pubDate>Mon, 06 May 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/upgrading-to-forgejo-7-0-1/</guid><description>&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-04-28-at-1.08.21-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-04-28-at-1.08.21-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not that long ago that &lt;a href="https://blog.iankulin.com/my-web-app-update-process/"&gt;I wrote about&lt;/a&gt; doing routine upgrades on containerised web apps using Forgejo as an example as I upgraded Forgejo (my git repository manager) between patch versions of 1.21, then a few days later, they dropped 7.0.0&lt;/p&gt;
&lt;p&gt;&lt;a href="https://forgejo.org/2024-04-release-v7-0/"&gt;They say&lt;/a&gt; the major version jump is due to it being an LTS (long term support) release, and changing to &lt;a href="https://semver.org/spec/v2.0.0.html"&gt;semantic versioning 2.0.0&lt;/a&gt; , but that doesn&amp;rsquo;t quite explain it to me, and I assume this is partly signifying the fork&amp;rsquo;s drift away from the gitea codebase. In any case, the upgrade to 7.0.0 it does involve some breaking changes, and signifies to me that a lot has been on, which makes me keen to wait for a patch release (I&amp;rsquo;m always keen for other people to debug these things) which has now landed.&lt;/p&gt;
&lt;p&gt;The reason I think the upgrade process is worth mentioning, is that the steps we went through to move from 1.21.0 to 1.21.8:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker compose down&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker pull codeberg.org/forgejo/forgejo:1.21&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker compose up&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;will not work this time, and gives me the excuse to talk about container tags.&lt;/p&gt;
&lt;h3 id="container-tags"&gt;Container Tags&lt;/h3&gt;
&lt;p&gt;When the developers had built their release for 1.21.8, they would have pushed the exact same image to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;codeberg.org/forgejo/forgejo:1.21.8&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;codeberg.org/forgejo/forgejo:1.21&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;codeberg.org/forgejo/forgejo:1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;codeberg.org/forgejo/forgejo:latest&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;that way, people like me who had specified &lt;code&gt;codeberg.org/forgejo/forgejo:1.21&lt;/code&gt; in their docker-compose.yml files just had to down/pull/up to be in business.&lt;/p&gt;
&lt;p&gt;If they had released another patch version, say 1.21.10, they they would have pushed the new image to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;codeberg.org/forgejo/forgejo:1.21.10&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;codeberg.org/forgejo/forgejo:1.21&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;codeberg.org/forgejo/forgejo:1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;codeberg.org/forgejo/forgejo:latest&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;i.e. the old 1.21.8 image would have stayed the same, so anyone who had depended on that not changing will still be fine, but people like me who want all the patch versions updated (but not a minor version change) would get the new one.&lt;/p&gt;
&lt;p&gt;Normally you can just click on &amp;rsquo;tags&amp;rsquo; for an image on Docker Hub, but since this one is hosted on Codeburg&amp;rsquo;s Forgejo instance, you need to go &lt;a href="https://codeberg.org/forgejo/-/packages/container/forgejo/versions"&gt;https://codeberg.org/forgejo/-/packages/container/forgejo/versions&lt;/a&gt; to see all the tags they&amp;rsquo;ve pushed to.&lt;/p&gt;
&lt;h3 id="upgrade-steps"&gt;Upgrade steps&lt;/h3&gt;
&lt;p&gt;The extra step we&amp;rsquo;ll need to go through this time is to decide what level of version we want to specify in our docker-compose. I&amp;rsquo;ll stick to specifying to the minor version so my new &lt;code&gt;docker-compose.yml&lt;/code&gt; will be:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;version: &amp;#39;3&amp;#39;

networks:
 forgejo:
 external: false

services:
 server:
 image: codeberg.org/forgejo/forgejo:7.0
 container_name: forgejo
 environment:
 - USER_UID=112
 - USER_GID=103
 restart: always
 networks:
 - forgejo
 volumes:
 - ./forgejo:/data
 - /etc/timezone:/etc/timezone:ro
 - /etc/localtime:/etc/localtime:ro
 ports:
 - &amp;#39;80:3000&amp;#39;
 - &amp;#39;2200:22&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once that decision is made, it&amp;rsquo;s just the same:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;backup the LXC&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker compose down&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker pull codeberg.org/forgejo/forgejo:7.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker compose up&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Then some testing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We could probably skip that pull step - when you compose up the system would notice the version change and pull it for us.&lt;/p&gt;</description></item><item><title>Virtual Hosts on "Static Web Server"</title><link>https://blog.iankulin.com/virtual-hosts-on-static-web-server/</link><pubDate>Mon, 22 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/virtual-hosts-on-static-web-server/</guid><description>&lt;p&gt;I&amp;rsquo;ve been running &lt;a href="https://blog.iankulin.com/nginx-proxy-manager/"&gt;NGINX Proxy Manager&lt;/a&gt; (NPM) in my homelab for a bit, and I&amp;rsquo;ve been meaning to clean up the VPS that runs most of my websites and public facing servers, so I&amp;rsquo;m considering running NGINX Proxy Manager on that VPS. While NGINX Proxy Manager wraps up the configs in a beautiful GUI, in the process you lose some of NGINXs capabilities. In particular there&amp;rsquo;s no GUI way to serve static virtual hosts from NGINX Proxy Manager.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s a pity, since it seems like it wouldn&amp;rsquo;t be a lot of work, but in any case we can easily just run another web server and proxy to it. I guess I could run another instance of NGINX, but on $5 VPSs memory is a bit scarce, and since my sites are extremely low traffic, perhaps something a bit lighter is in order.&lt;/p&gt;
&lt;p&gt;An option mentioned in several posts for this exact situation is the very well named &lt;a href="https://static-web-server.net/"&gt;Static Web Server&lt;/a&gt;. It&amp;rsquo;s written in Rust, the docker image is less that 10MB, and it claims to be able to serve for virtual hosts. Let&amp;rsquo;s give it a try.&lt;/p&gt;
&lt;h3 id="directory-structure"&gt;Directory Structure&lt;/h3&gt;
&lt;p&gt;My routine setup now is that everything runs in docker. There&amp;rsquo;s a directory under the home directory of my user for named for the container which holds the &lt;code&gt;docker-compose.yml&lt;/code&gt;. Underneath that there&amp;rsquo;s two sub-directories: &lt;code&gt;data&lt;/code&gt; - which holds all the app data, and &lt;code&gt;config&lt;/code&gt; - which holds the app settings files.&lt;/p&gt;
&lt;p&gt;In the data directory, I want to have sub-directories for each virtual host, then inside them a &lt;code&gt;public&lt;/code&gt; directory which will hold the files to be served. This will allow me to keep config or other files for each virtual host in the directory above &lt;code&gt;public&lt;/code&gt; which should not be accessible. That seems like a good arrangement so I can manage each virtual host in git and pull the sites down from a repository into their directory without things like the &lt;code&gt;.gitignore&lt;/code&gt; being exposed to the world.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-04-20-at-8.15.14-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-04-20-at-8.15.14-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m running Tailscale on this VM, so it can be referred to by any of the addresses &lt;code&gt;100.124.218.26&lt;/code&gt;, &lt;code&gt;192.168.100.35&lt;/code&gt; or &lt;code&gt;ct357-sws&lt;/code&gt;. These are going to be stand- ins for the different virtual host names. The index.html files you can see are all different; I&amp;rsquo;ve edited them so each one just outputs the name of the directory it is being served from. eg:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-04-20-at-9.20.26-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-04-20-at-9.20.26-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="docker"&gt;Docker&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;version: &amp;#34;3.3&amp;#34;

services:
 website:
 image: joseluisq/static-web-server:2
 container_name: &amp;#34;sws&amp;#34;
 ports:
 - 80:80
 restart: unless-stopped
 environment:
 - SERVER_CONFIG_FILE=/etc/config.toml
 volumes:
 - ./data:/var
 - ./config/config.toml:/etc/config.toml
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You&amp;rsquo;re probably familiar with docker-compose by now, but if not, the volumes lines probably need explaining. They map &amp;lsquo;real-world&amp;rsquo; directories on our server, to the internal directories &lt;em&gt;inside&lt;/em&gt; the container. This is a useful thing - we could copy our files into the container but those changes would be ephemeral, they&amp;rsquo;d be gone next time we restart the container. By creating these links, we can store the web server configs and data on our server, but from the point of view of the app, they appear inside.&lt;/p&gt;
&lt;p&gt;An example will make this more concrete, lets look at the first line:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; - ./data:/var
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is saying that the directory inside the container named &lt;code&gt;/var&lt;/code&gt; is mapped onto the external directory &lt;code&gt;./data&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Consider our file in the tree listing above &lt;code&gt;~/sws/data/100.124.218.26/public/index.html&lt;/code&gt; the web server running inside the container will see that file at &lt;code&gt;/var/100.124.218.26/public/index.html&lt;/code&gt; It seems weird at first, but you soon get used to this sort of container directory mapping maths.&lt;/p&gt;
&lt;h3 id="config"&gt;Config&lt;/h3&gt;
&lt;p&gt;There&amp;rsquo;s excellent documentation about Static Web Server on their &lt;a href="https://static-web-server.net/"&gt;web site&lt;/a&gt;, so I&amp;rsquo;m not going to go through the whole &lt;code&gt;config.toml&lt;/code&gt; file, in any case, I&amp;rsquo;ve left nearly everything on the defaults. Instead, lets scroll down to the bottom and look at the virtual hosts settings.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[[advanced.virtual-hosts]]
host = &amp;#34;100.124.218.26&amp;#34;
root = &amp;#34;/var/100.124.218.26/public&amp;#34;

[[advanced.virtual-hosts]]
host = &amp;#34;ct357-sws&amp;#34;
root = &amp;#34;/var/ct357-sws/public&amp;#34;

[[advanced.virtual-hosts]]
host = &amp;#34;192.168.100.35&amp;#34;
root = &amp;#34;/var/192.168.100.35/public&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="it-works"&gt;It works&lt;/h3&gt;
&lt;p&gt;A quick &lt;code&gt;docker compose up -d&lt;/code&gt;, and we&amp;rsquo;re in business.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-04-21-at-8.52.14-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-04-21-at-8.57.27-pm.png" alt=""&gt;&lt;/p&gt;</description></item><item><title>NGINX Proxy Manager</title><link>https://blog.iankulin.com/nginx-proxy-manager/</link><pubDate>Mon, 15 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/nginx-proxy-manager/</guid><description>&lt;p&gt;I&amp;rsquo;ve mentioned using NGINX as an &lt;a href="https://blog.iankulin.com/nginx-in-front-of-a-node-js-app/"&gt;interface between the internet and a service&lt;/a&gt; a while ago. This works by all incoming traffic coming to NGINX, and NGINX determining which service that traffic should go (from the NGINX config files) then acting as a middleman. This functionality is generally referred to as a &amp;lsquo;reverse proxy&amp;rsquo;.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/nginx.png" width="959" alt="Terrible drawing of NGINX proxying requests off to different services."&gt;
&lt;p&gt;This is nice for a few reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We can have a single point of entry to the services, easier to lock down and secure, with access centrally logged&lt;/li&gt;
&lt;li&gt;The services can be running on all sorts of odd addresses and ports (for example 192.168.101.23:4002) but they can be addressed with sensible names by the user (such as todo.example.com)&lt;/li&gt;
&lt;li&gt;We can add &lt;a href="https://blog.iankulin.com/quick-dirty-auth-with-nginx-node/"&gt;basic auth&lt;/a&gt; to any services that need it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All this stuff is managed through the &lt;a href="https://blog.iankulin.com/nginx-config-on-debian-ubuntu/"&gt;NGINX config&lt;/a&gt; files. Perhaps one might look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;server {
 listen 80;
 server_name example.com;

 location / {
 root /var/www;
 index index.html;
 }

 location /app2/ {
 proxy_pass http://192.168.101.65:8096/;
 proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header X-Forwarded-Proto $scheme;
 }

 location /secure_area/ {
 auth_basic &amp;#34;Restricted&amp;#34;;
 auth_basic_user_file /etc/nginx/.htpasswd;
 }
}

server {
 listen 80;
 server_name app1.example.com;

 location / {
 proxy_pass http://192.168.101.23:4000;
 proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header X-Forwarded-Proto $scheme;
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These config files are powerful, and in the usual trade-off somewhat complicated and I&amp;rsquo;ve certainly made problems for myself in the past by making errors in them.&lt;/p&gt;
&lt;p&gt;There is a great project, &lt;a href="https://nginxproxymanager.com/"&gt;NGINX Proxy Manager&lt;/a&gt; that throws a nice web GUI on this process. On top of that, it makes the process of obtaining &lt;a href="https://blog.iankulin.com/certbot-lets-encrypt-are-great/"&gt;Let&amp;rsquo;s Encrypt&lt;/a&gt; SSL certificates even easier than CertBot.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/npm.png" width="946" alt="Terrible drawing of NGINX Proxy Manager proxying requests off to different service, and obtaining SSL certificates for them."&gt;
&lt;p&gt;NGINX Proxy Manager is available as a docker image, and is trivial to set up if you&amp;rsquo;re used to docker. Once that&amp;rsquo;s done, the process of adding the proxies is simple enough that you probably don&amp;rsquo;t need any instructions.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-31-at-8.59.56-am.png" alt=""&gt;&lt;/p&gt;
&lt;h3 id="alternatives"&gt;Alternatives&lt;/h3&gt;
&lt;p&gt;Rolling your own, or using NGINX Proxy Manager are not your only options. There&amp;rsquo;s also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.haproxy.org/#desc"&gt;HAProxy&lt;/a&gt; - an industrial strength proxy/load balancer&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caddyserver.com/docs/quick-starts/reverse-proxy"&gt;Caddy&lt;/a&gt; - same as NGINX but different. Has a great plugin architecture. A particular plugin &lt;a href="https://github.com/lucaslorentz/caddy-docker-proxy"&gt;Caddy-docker-proxy&lt;/a&gt; enables configuration of each service with tables inside the service&amp;rsquo;s docker-compose file which is a particularly neat trick.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://traefik.io/traefik/"&gt;Traefik&lt;/a&gt; - does a similar trick to Caddy-docker-proxy of figuring out it&amp;rsquo;s config from the services it&amp;rsquo;s proxying. It&amp;rsquo;s a serious bit of kit valuable for putting in front of huge Kubernetes swams, and is therefore probably a bit more complex to manage than Caddy-docker-proxy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I haven&amp;rsquo;t used any of these (except NGINX Proxy Manager) so take these descriptions as a starting point only.&lt;/p&gt;
&lt;p&gt;For any self-hosted (at home or on a VPS) services, you are going to need some of this functionality, and NGINX Proxy Manager is a simple, robust approach that should definitely be considered.&lt;/p&gt;</description></item><item><title>Due Diligence on a Docker Image</title><link>https://blog.iankulin.com/due-diligence-on-a-docker-image/</link><pubDate>Mon, 08 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/due-diligence-on-a-docker-image/</guid><description>&lt;p&gt;&lt;a href="https://unsplash.com/photos/gray-figure-ELLDKLrXMoA"&gt;&lt;img src="https://blog.iankulin.com/images/brett-jordan-elldklrxmoa-unsplash.jpg" width="640" alt=""&gt;&lt;/a&gt;
&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@brett_jordan?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash"&gt;Brett Jordan&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/gray-figure-ELLDKLrXMoA?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I need a survey tool, and a quick search turned up &lt;a href="https://www.limesurvey.org/"&gt;LimeSurvey&lt;/a&gt;, there&amp;rsquo;s a &amp;lsquo;community edition&amp;rsquo; so naturally I plan to self-host it. I scrolled down to the &amp;lsquo;installation&amp;rsquo; section of the &lt;a href="https://manual.limesurvey.org/Installation_-_LimeSurvey_CE/en"&gt;manual&lt;/a&gt; which has a big list of PHP dependencies.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-29-at-7.20.31-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Ain&amp;rsquo;t nobody got the time for that in 2024, I scroll further looking for the docker-compose but there isn&amp;rsquo;t one. Huh. No official Docker image.&lt;/p&gt;
&lt;p&gt;Making my own docker image will be a little bit more work than just the pain of installing it manually and writing the Ansible playbook, but almost certainly someone else will have done that for us, so pop over to Docker Hub to have a look.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-29-at-7.38.06-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re going to want the &amp;rsquo;trusted content&amp;rsquo; right?&lt;/p&gt;
&lt;h3 id="who-do-you-trust"&gt;Who do you trust?&lt;/h3&gt;
&lt;p&gt;At some stage when you run software, you are going to need to decide who you trust for this application. You might say &amp;ldquo;no, as an Open Source advocate, I&amp;rsquo;m going to read through the code and compile it myself&amp;rdquo; - and that&amp;rsquo;s going to be a legit approach in some circumstances. But of course you&amp;rsquo;re still choosing to trust all the libraries it uses, the compiler, the operating system, and the computer chipset (or worse - your hosting provider).&lt;/p&gt;
&lt;p&gt;If you want to go hardcore secure, you&amp;rsquo;ll eventually find yourself constructing your own &lt;code&gt;[fab](https://en.wikipedia.org/wiki/Semiconductor_fabrication_plant)&lt;/code&gt;. This is the concept of the Security-Convenience trade-off. More security generally equals less convenience and vice versa.&lt;/p&gt;
&lt;p&gt;To make a sensible decision about this when choosing software I think about the threat profile, and have some rough metrics about what makes me more comfortable or less comfortable about a software artifact.&lt;/p&gt;
&lt;p&gt;The reason this is a front-of-mind issue for me is that software packaging is an easy stage for bad actors to insert their code. We&amp;rsquo;ve seen a &lt;a href="https://popey.com/blog/2024/02/exodus-bitcoin-wallet-490k-swindle/"&gt;bit of that over the past month&lt;/a&gt; in the Canonical Snap store. Nefarious bitcoin stealers were packaging their software as legit, users were downloading them and installing them and one cryptobro lost a heap of money. It&amp;rsquo;s easy to imagine lots of related exploits - for example just altering the original code and packaging it. In the case of LimeSurvey (which is used by many researchers at leading universities perhaps a bad actor might want to be able to run bots from inside verifiable uni IP addresses.&lt;/p&gt;
&lt;h3 id="trust-factors"&gt;Trust Factors&lt;/h3&gt;
&lt;p&gt;What are the things that might reduce our anxiety about the software bill of materials we&amp;rsquo;re dealing with? These are just mine - a complete non-expert:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Trusted organisation - If the Ubuntu team have signed off on something, I&amp;rsquo;m going to trust it more that some other group I never heard of till today.&lt;/li&gt;
&lt;li&gt;Popular - it&amp;rsquo;s not just it&amp;rsquo;s reassuring that other people have decided it&amp;rsquo;s trustworthy, it&amp;rsquo;s also a comforting idea that if there is an exploit, it&amp;rsquo;s more likely to be discovered, and to hurt someone else first. If there&amp;rsquo;s a zero day exploit in MacOS I&amp;rsquo;ll hear about it on Mastodon and be able to get some advice about a work-around or fix.&lt;/li&gt;
&lt;li&gt;Updated - a project under current development is more likely to be applying updates to any compromised libraries, and there&amp;rsquo;s probably a community of some sort where security concerns are considered.&lt;/li&gt;
&lt;li&gt;Verifiable - One of the big strengths of Open Source. Even if I don&amp;rsquo;t have the skills or time to read the code, there&amp;rsquo;s a good chance that other people are. And in the case of a container image, I actually do sort of have the skills to read the dockerfile and know what&amp;rsquo;s gone into it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="choosing-an-image"&gt;Choosing an Image&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s apply some of these rules-of-thumb to my LimeSurvey container needs.&lt;/p&gt;
&lt;p&gt;When Docker Hub says something is &amp;ldquo;trusted content&amp;rdquo; they&amp;rsquo;ve already done some of my work for me. And when I click through to &lt;a href="https://hub.docker.com/r/eucm/limesurvey"&gt;eucm/limesurvey&lt;/a&gt; it says that the developer (eucm) is a &amp;ldquo;&lt;a href="https://docs.docker.com/trusted-content/dsos-program/#:~:text=The%20Docker-Sponsored%20Open%20Source,Insights%20and%20analytics"&gt;Sponsored OSS&lt;/a&gt;&amp;rdquo; which means a human at Docker thinks this developer group are legit. These are all encouraging factors.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-03-29-at-9.47.41-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-29-at-9.47.41-am.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s less encouraging is &amp;ldquo;Pulls 635&amp;rdquo; which does not seem a lot, and &amp;ldquo;Updated over 3 years ago&amp;rdquo;. Also I&amp;rsquo;ve got no reason to think this is an official image - eucm doesn&amp;rsquo;t seem to have any connection to the LimeSurvey developers. Let&amp;rsquo;s look at the other images.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-03-29-at-10.25.21-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-29-at-10.25.21-am.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s more further down but all with about 100K pulls or less. Of these four, we&amp;rsquo;ve already eliminated the top one, and we can probably eliminate the bottom one based on it&amp;rsquo;s age - although it&amp;rsquo;s always interesting to see an image with so many pulls and no updates since that sometimes happens when a project changes hands or gets relisted under a different owner.&lt;/p&gt;
&lt;p&gt;Either of the other two (acspri/limesurvey &amp;amp; martialblog/limesurvey) are worth investigating - there&amp;rsquo;s nothing much to tell them apart in this view since they both have about the same number of stars (the number next to the pulls number). I&amp;rsquo;ll start at the top.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-03-29-at-11.34.52-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-29-at-11.34.52-am.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s promising to have a good readme, including a docker-compose example, but there&amp;rsquo;s no github link, and I really want a peek at the dockerfile. If I click through to the developer, they seem to be the &lt;a href="https://www.acspri.org.au"&gt;Australia Consortium for Social and Political Research Inc&lt;/a&gt; - which it makes sense why they would be interested in a survey tool. Additionally, they seem to have an &lt;a href="https://www.acspri.org.au/limesurvey"&gt;official link to the project&lt;/a&gt;. They do have an active GitHub account, but it doesn&amp;rsquo;t include a repository for this which seems, odd.&lt;/p&gt;
&lt;p&gt;However, there is this repo &lt;a href="https://github.com/adamzammit/limesurvey-docker"&gt;adamzammit / limesurvey-docker&lt;/a&gt; which appears to get pushed to it&amp;rsquo;s own &lt;a href="https://hub.docker.com/r/adamzammit/limesurvey"&gt;dockerhub&lt;/a&gt; as well as the ACSPRI one. There is a note on the adamzamit dockerhub saying this used to be the acspri but has been moved here - that would be more convincing if the note was on the acspri dockerhub. The github looks legit, and there was nothing suspicious (to my inexpert view) in the dockerfile.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-03-30-at-11.33.52-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-30-at-11.33.52-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://hub.docker.com/u/martialblog"&gt;martialblog/limesurvey&lt;/a&gt; one also looks good - recently updated, lots of pulls, a comprehensive readme, and a github link right at the top. The only criteria it doesn&amp;rsquo;t meet is &amp;ldquo;trusted organisation&amp;rdquo; but the link to an active github goes part of the way. I&amp;rsquo;d be happy to use this container for the purpose I&amp;rsquo;ve got in mind.&lt;/p&gt;</description></item><item><title>My Web App Update Process</title><link>https://blog.iankulin.com/my-web-app-update-process/</link><pubDate>Mon, 01 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/my-web-app-update-process/</guid><description>&lt;p&gt;I&amp;rsquo;ve settled on a very standard, reproducible setup for services in my homelab. This post looks at that, then runs through the update I did today to Forgejo which only took a few minutes and felt relatively risk free.&lt;/p&gt;
&lt;h3 id="standard-setups"&gt;Standard Setups&lt;/h3&gt;
&lt;p&gt;My system is based around Proxmox. I have three physical machines - one for production apps, a production spare, and a development/testbed machine. A Synology NAS serves for backups. Moving a VM or LXC between the machines is trivial; but it&amp;rsquo;s done manually - the machines are not clustered for high availability.&lt;/p&gt;
&lt;p&gt;Most workloads are Docker containers &lt;em&gt;inside&lt;/em&gt; an LXC. This works fine with a couple of caveats. I have an LXC template saved with Docker and Tailscale installed, my non-root user added, the mount for the NAS, and SSH keys. Setting up a new app starts with a full clone of this, a &lt;code&gt;dpkg-reconfigure openssh-server&lt;/code&gt; and &lt;code&gt;tailscale up&lt;/code&gt; and changing the root &amp;amp; non-root users&amp;rsquo; passwords.&lt;/p&gt;
&lt;p&gt;Next I create a sub directory for the app and write the &lt;code&gt;docker-compose.yaml&lt;/code&gt; in there. Then it&amp;rsquo;s just a matter of &lt;code&gt;docker compose up -d&lt;/code&gt;. If there&amp;rsquo;s any data, it goes in a another sub directory off this one.&lt;/p&gt;
&lt;p&gt;Unless I need something else, nightly backups to the NAS happen automatically for all the VMs and containers handled by a setting in Proxmox.&lt;/p&gt;
&lt;h3 id="upgrading-an-app"&gt;Upgrading an App&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve noticed a couple of posts about a new release of &lt;a href="https://forgejo.org/"&gt;Forgejo&lt;/a&gt; on Mastodon in the past few days, so I figure I should look at that. My version is 1.21.1 and the new one is 1.21.8&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-24-at-8.44.36-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Because of &lt;a href="https://semver.org/"&gt;semantic versioning&lt;/a&gt;, I&amp;rsquo;m confident this is not going to break anything, but I check the release notes anyway. It looks good.&lt;/p&gt;
&lt;h4 id="backup"&gt;Backup&lt;/h4&gt;
&lt;p&gt;I jump into the Proxmox web gui and make a backup of the container.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-24-at-8.47.06-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;h4 id="docker-compose"&gt;Docker Compose&lt;/h4&gt;
&lt;p&gt;I ssh in to look at the image tag in the docker-compose.yml file. The reason I&amp;rsquo;m interested in this is that if the compose is set to &lt;code&gt;codeberg.org/forgejo/forgejo:1.21.1&lt;/code&gt; then it will be locked into that patch version, but it says &lt;code&gt;codeberg.org/forgejo/forgejo:1.21&lt;/code&gt; so we&amp;rsquo;re good.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-24-at-8.48.38-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Now I take the service down from the CLI with &lt;code&gt;sudo docker compose down&lt;/code&gt;, then pull the new image with &lt;code&gt;sudo docker pull codeberg.org/forgejo/forgejo:1.21&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-24-at-9.24.21-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The to start it again, it&amp;rsquo;s just a &lt;code&gt;docker compose up -d&lt;/code&gt; and we&amp;rsquo;re live again.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-03-24-at-8.52.45-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-24-at-8.52.45-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="testing"&gt;Testing&lt;/h4&gt;
&lt;p&gt;My testing of this was pretty brief since (a) I&amp;rsquo;ve got high confidence in the developers at &lt;a href="https://blog.iankulin.com/gogs-gitea-forgejo/"&gt;gitea and forgejo&lt;/a&gt; and (b) this app gets pretty much daily use so if there are issues I&amp;rsquo;ll surface them pretty quickly, (c) anything I&amp;rsquo;m actively working on had full git histories on my laptop, and (d) the releases since my last update are pretty much just bug fixes.&lt;/p&gt;
&lt;p&gt;Nevertheless, I clicked around the web gui, and tried some pushes, pulls and clones and everything seemed fine.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m very comfortable with the way I&amp;rsquo;ve put all this together now. It&amp;rsquo;s a reliable, easily managed setup that makes maintenance like this simple and safe.&lt;/p&gt;</description></item><item><title>Deploying a Node app in Docker</title><link>https://blog.iankulin.com/deploying-a-node-app-in-docker/</link><pubDate>Sun, 31 Mar 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/deploying-a-node-app-in-docker/</guid><description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Cargo_ship#/media/File:Cargo_Ship_Puerto_Cortes.jpg"&gt;&lt;img src="https://blog.iankulin.com/images/cargo_ship_puerto_cortes.jpg" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When I wrote the install instructions for mdserver (little Markdown server Node app) on it&amp;rsquo;s &lt;a href="https://github.com/IanKulin/mdserver"&gt;github page&lt;/a&gt; it was something like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Have node.js installed and working&lt;/li&gt;
&lt;li&gt;Clone the repo&lt;/li&gt;
&lt;li&gt;Start with &lt;code&gt;npm start&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which is great if you know &lt;a href="https://blog.iankulin.com/installing-a-node-app-on-a-server/"&gt;how to do those things&lt;/a&gt; (they are bread and butter to a web dev) but not if you&amp;rsquo;re a self-hoster who just wants a web server that converts markdown to HTML on the fly. For any situation where you just want to use the app, what you probably want is a Docker image of the app.&lt;/p&gt;
&lt;h3 id="docker"&gt;Docker&lt;/h3&gt;
&lt;p&gt;Docker &lt;em&gt;containers&lt;/em&gt; are similar to a virtual machine in the sense that they need to be hosted, and are relatively isolated from other processes except is some explicitly defined ways. Docker images are stored in repositories (the default one is &lt;a href="https://hub.docker.com/"&gt;DockerHub&lt;/a&gt;). It probably sounds like a wasteful process to ship an entire operating system with every little app - this is somewhat overcome by the images being built up in layers, and duplicated layers don&amp;rsquo;t need to be shlipped around since they are cached.&lt;/p&gt;
&lt;p&gt;So to deploy our Node app as a Docker container, we need to build an image, and store it on Docker Hub. From there, users can deploy it from their command lines by calling it directly or declaratively with a &lt;code&gt;docker-compose.yml&lt;/code&gt; file.&lt;/p&gt;
&lt;h3 id="dockerfile"&gt;Dockerfile&lt;/h3&gt;
&lt;p&gt;To create a Docker image of our app that can be distributed, we run the &lt;code&gt;docker build&lt;/code&gt; command which reads a file named &lt;code&gt;Dockerfile&lt;/code&gt; to create the image. Here&amp;rsquo;s the &lt;code&gt;Dockerfile&lt;/code&gt; for the mdserver app.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Use an official Node.js runtime as the base image
FROM node:20-alpine

# Set the working directory in the container
WORKDIR /usr/src/app

# Copy package.json and package-lock.json to the working directory
COPY package*.json .

RUN npm install

# Copy the rest of the application source code to the container
COPY ./server.js .
COPY ./LICENSE .
COPY ./readme.md .

# Expose the port that the Node.js app will listen on
EXPOSE 3000

# Define the command to start your Node.js app
CMD [ &amp;#34;node&amp;#34;, &amp;#34;server.js&amp;#34; ]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;FROM node:20-alpine&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;FROM&lt;/code&gt; keyword specifies the base image we&amp;rsquo;re starting with. It could just be something like a Debian base, then in the following commands in the Dockerfile we&amp;rsquo;d install Node, but Node (and lots of other web dev tool builders) have provided &lt;a href="https://hub.docker.com/_/node/"&gt;official Docker images&lt;/a&gt; that they have crafted to make it easier for us. In this case I&amp;rsquo;m specifying that I want the image based on the lightweight Alpine Linux distro, with version 20 of Node installed on it.&lt;/p&gt;
&lt;p&gt;Note that when Node created the &lt;code&gt;node:20-alpine&lt;/code&gt; image, their Dockerfile probably started with &lt;code&gt;FROM [alpine:3.18](https://hub.docker.com/_/alpine)&lt;/code&gt; - you see? Layers.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;WORKDIR /usr/src/app&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;So now we&amp;rsquo;ve got a container with a fully working install of Linux (or close enough to that so we can think of it like that - I&amp;rsquo;m pretty sure there&amp;rsquo;s no kernel). This command is saying that all the next commands are going to refer to the working directory &lt;em&gt;inside&lt;/em&gt; the container as &lt;code&gt;/usr/src/app&lt;/code&gt;. In effect its as if you&amp;rsquo;d ssh&amp;rsquo;d in and run &lt;code&gt;mkdir /usr/scr/app &amp;amp;&amp;amp; cd mkdir /usr/scr/app&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;COPY package*.json .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve written before about the &lt;a href="https://blog.iankulin.com/sorting-out-node-package-dependencies-when-cloning-old-repos/"&gt;intricacies of the package files in Node&lt;/a&gt;. Basically these files (&lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt;) specify the dependencies for out project. The dependencies are all sitting in the node_modules folder, but having a listing of them in the package files means we can just check them into source control and not worry about that bloated folder.&lt;/p&gt;
&lt;p&gt;This &lt;code&gt;COPY&lt;/code&gt; command, just copies them both into out container image - the &lt;code&gt;.&lt;/code&gt; at the end just means the current working directory inside the container - ie &lt;code&gt;/usr/src/app&lt;/code&gt; in our case.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RUN npm install&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Now the package files are inside the container, we just run &lt;code&gt;npm install&lt;/code&gt;, exactly the same as we would on a server, in order to download all of the dependencies for our app into the container. If that looks like you could just say &lt;code&gt;RUN&lt;/code&gt; then run any old Linux command then you&amp;rsquo;re getting the hang of it. You can &lt;code&gt;apt install&lt;/code&gt; stuff, &lt;code&gt;echo&lt;/code&gt; a line into a config file - whatever you need.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;COPY ./server.js .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;COPY ./LICENSE .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;COPY ./readme.md .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;For this trivial app, we only need the one source file, but I like to copy the license and readme in as well. It&amp;rsquo;s possible for future users of the container to run commands in their copy of the container, so it&amp;rsquo;s conceivable someone might look in here to read them. Once again, the second parameter specifies where in the container the files are copied to, and once again we&amp;rsquo;ve said the current work dir.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll very commonly see &lt;code&gt;COPY . .&lt;/code&gt; in Dockerfiles. This is saying copy all the files in the current directory to the working directory inside the container image. I guess that way you don&amp;rsquo;t miss anything, but do I really need a copy of my &lt;code&gt;Dockerfile&lt;/code&gt;, my vscode settings, my &lt;code&gt;node_modules&lt;/code&gt; folder in the image? No. There is a way to avoid copying that stuff in - add a &lt;code&gt;.dockerignore&lt;/code&gt; file to your project. This works exactly like a &lt;code&gt;.gitignore&lt;/code&gt; - you just list one file or directory per line, and then the &lt;code&gt;COPY&lt;/code&gt; command will know not to bother with it,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;EXPOSE 3000&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;My node app is set to use port 3000, so we need to tell Docker to open that port for us since by default everything&amp;rsquo;s locked down. Note that the user of this container won&amp;rsquo;t be stuck with this decision, when they start the container, they can specify where in the outside world this internal container is going to be mapped to. That could be port &lt;code&gt;8080&lt;/code&gt;, &lt;code&gt;80&lt;/code&gt; or whatever.&lt;/p&gt;
&lt;p&gt;CMD [ &amp;ldquo;node&amp;rdquo;, &amp;ldquo;server.js&amp;rdquo; ]&lt;/p&gt;
&lt;p&gt;Finally, Docker needs to know how to start our app. This command is not being run now (when we&amp;rsquo;re building the image) it&amp;rsquo;s used by Docker when it launches the containerised app. I&amp;rsquo;m not sure why it is an array of strings instead of just a string, but it is. Just break it at each space in your command to run the app.&lt;/p&gt;
&lt;p&gt;If you look back at the list of manual steps I started this post with, you&amp;rsquo;ll see that we&amp;rsquo;ve pretty much just re-implemented them in the Dockerfile:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;set up a node environment&lt;/li&gt;
&lt;li&gt;copy the files in&lt;/li&gt;
&lt;li&gt;run server.js&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Obviously there&amp;rsquo;s lots more you can do with Dockerfiles, but the underlying concept is pretty straightforward - you&amp;rsquo;re setting up the whole environment for your app to run in so it can be mostly independent from its host OS.&lt;/p&gt;
&lt;h3 id="build-step"&gt;Build Step&lt;/h3&gt;
&lt;p&gt;To create the image from the Dockerfile, you are going to need Docker. I&amp;rsquo;m working on a Mac so I&amp;rsquo;ve got &lt;a href="https://www.docker.com/products/docker-desktop/"&gt;Docker Desktop&lt;/a&gt; installed. When it&amp;rsquo;s running there&amp;rsquo;s the little whale up in the toolbar.&lt;/p&gt;
&lt;p&gt;You don&amp;rsquo;t need a &lt;a href="https://hub.docker.com/"&gt;DockerHub&lt;/a&gt; account to build the image, but you&amp;rsquo;ll need one to upload it, and for naming your build, so head there now and create one. It is possible to use other registries for storing your images, but by default docker looks at it&amp;rsquo;s own registry, so that&amp;rsquo;s the best place to start when you&amp;rsquo;re figuring things out.&lt;/p&gt;
&lt;p&gt;When you&amp;rsquo;re working with Docker images and registries, to uniquely identify an image, it usually has a name format like this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;username&amp;gt;/&amp;lt;imagename&amp;gt;:&amp;lt;tag&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Usually the tag will be a version number, or perhaps &lt;code&gt;:latest&lt;/code&gt;. The build command for our image could be this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker build -t iankulin/mdserver:latest&lt;/code&gt; .&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-28-at-12.27.51-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;This will load the .dockerignore then step through the Dockerfile to build our image. The image is stored away by Docker - we don&amp;rsquo;t need to worry about where. You can get the list at the command line with &lt;code&gt;docker images&lt;/code&gt;, or if you&amp;rsquo;re running Docker Desktop, on the &amp;lsquo;images&amp;rsquo; tab.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-28-at-12.36.22-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-28-at-12.36.22-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I have skipped quite a bit of detail about the build step and options. For example I sometimes use the &lt;code&gt;--platform&lt;/code&gt; flag to specify &lt;code&gt;linux/amd64&lt;/code&gt; if I&amp;rsquo;m testing on one of my homelab VMs rather than &lt;code&gt;linux/arm64&lt;/code&gt; if I&amp;rsquo;m running the container on the mac. Also, we don&amp;rsquo;t have to just build from the local machine, it&amp;rsquo;s just as straightforward to build from your GitHub repo as part of a CI/CD system. I&amp;rsquo;m not planning to go into any of that today, except I will force it to build for x86 since it is my plan to test on the homelab VM&amp;rsquo;s.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker build --platform linux/amd64 -t iankulin/mdserver:latest .&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="the-registry"&gt;The Registry&lt;/h3&gt;
&lt;p&gt;So the images can be available to anyone, we need to make it available in a Docker Registry. The most famous one of these, and the one set up as the default for all the docker commands, is &lt;a href="https://hub.docker.com/"&gt;Docker Hub&lt;/a&gt;. Despite some &lt;a href="https://www.docker.com/blog/no-longer-sunsetting-the-free-team-plan/"&gt;missteps&lt;/a&gt;, it&amp;rsquo;s still the main place people and organisations store docker images.&lt;/p&gt;
&lt;p&gt;In order to push an image to a registry, we need to be signed in to it. As I&amp;rsquo;m using Docker Desktop, and I&amp;rsquo;m signed in to Docker Hub on that. I&amp;rsquo;ve skipped that step, but if you needed to, you&amp;rsquo;d use the &lt;a href="https://docs.docker.com/engine/reference/commandline/login/"&gt;docker login&lt;/a&gt; command. Once that&amp;rsquo;s sorted, the push is easy:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker push iankulin/mdserver:latest&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-28-at-2.12.15-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-28-at-2.12.15-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In this output, you can see some of the efficiencies of the layers - docker recognises (from the UUIDs) that the Alpine and Node layers are ones that I pulled down from it when I was creating the image locally, so it doesn&amp;rsquo;t send them back to Docker Hub.&lt;/p&gt;
&lt;p&gt;If we go to Docker Hub and search for mdserver, we should be able to find it now available to the public.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-28-at-2.10.44-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-28-at-2.10.44-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="using-the-image"&gt;Using the image&lt;/h3&gt;
&lt;p&gt;Now it&amp;rsquo;s in the registry, anyone can use it as easily as any of the Docker images - NGINX, Jellyfin - whatever. I provide a docker-compose file in the repo, it looks like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;version: &amp;#39;3&amp;#39;
services:
 mdserver:
 image: iankulin/mdserver:latest
 ports:
 - &amp;#34;3000:3000&amp;#34;
 volumes:
 - ./public:/usr/src/app/public 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So any user can just drop that into a directory, and enter &lt;code&gt;docker compose up -d&lt;/code&gt; then the image will be pulled down and run, and they&amp;rsquo;ll have their server live.&lt;/p&gt;</description></item><item><title>Hosting Your Own Docker Registry</title><link>https://blog.iankulin.com/hosting-your-own-docker-registry/</link><pubDate>Mon, 25 Mar 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/hosting-your-own-docker-registry/</guid><description>&lt;p&gt;&lt;a href="https://unsplash.com/photos/architectural-photography-of-cargo-containers-stack-hP4ZiN1_kdk?utm_content=creditShareLink&amp;utm_medium=referral&amp;utm_source=unsplash"&gt;&lt;img src="https://blog.iankulin.com/images/tri-eptaroka-mardiana-hp4zin1_kdk-unsplash.jpg" width="640" alt="Photo by Tri Eptaroka Mardianam on Unsplash
"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The Docker &lt;a href="https://docs.docker.com/subscription/core-subscription/details/"&gt;Personal (ie free tier) plan&lt;/a&gt; currently allows one private repository, but even if you want to pay for the next level where you can have unlimited repositories, you may still want to host your own private registry - it&amp;rsquo;s going to be quicker inside your network, and you won&amp;rsquo;t run up against Docker&amp;rsquo;s pull/push limits if you are hammering it with your CI/CD system.&lt;/p&gt;
&lt;p&gt;There are fancier tools, but in this post we&amp;rsquo;ll look at the basics of how to use the official registry app from Docker.&lt;/p&gt;
&lt;h3 id="initial-setup"&gt;Initial Setup&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://hub.docker.com/_/registry"&gt;registry app&lt;/a&gt; is (unsurprisingly) dockerised. So I&amp;rsquo;ve created a directory for the &lt;code&gt;docker-compose.yml&lt;/code&gt; file, and a &lt;code&gt;data&lt;/code&gt; sub directory.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-23-at-7.50.43-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;And the yaml.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;services: registry: image: registry:2 container_name: registry restart: unless-stopped ports: - &amp;#34;5000:5000&amp;#34; environment: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data volumes: - ./data:/data
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;docker compose up&lt;/code&gt;, and bingo. Our registry is live.&lt;/p&gt;
&lt;h3 id="creating-an-image"&gt;Creating an image&lt;/h3&gt;
&lt;p&gt;Now our registry is up, let&amp;rsquo;s jump over to another machine, and create an image to store in it. I&amp;rsquo;m only going to minimally explain this, since if you&amp;rsquo;re interested in your own registry, you&amp;rsquo;ve probably been down this path.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-23-at-1.24.50-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dockerfile&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;FROM busyboxRUN mkdir /appCOPY script.sh /app/script.shWORKDIR /appRUN chmod +x script.shCMD [&amp;#34;./script.sh&amp;#34;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;script.sh&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#!/bin/shecho &amp;#34;Hello from Docker!&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So basically, this image contains a small Linux distro, and all it does is run a script that outputs &amp;ldquo;Hello from Docker!&amp;rdquo; to the console. We can build our image by switching into the directory with the &lt;code&gt;dockerfile&lt;/code&gt; and running:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo docker build -t hello-docker .
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-23-at-1.37.15-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;If you want to run it to check my docker skills, use&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo docker run hello-docker
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="pushing--insecure"&gt;Pushing &amp;amp; Insecure&lt;/h3&gt;
&lt;p&gt;Now I want to push the image we&amp;rsquo;ve created to the new registry we set up earlier, but we&amp;rsquo;re going to run into a problem.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m using two Debian virtual machines (LXCs actually) both on my homelab network. They&amp;rsquo;ve been named with Tailscale to make things clearer in the screenshots. (If you&amp;rsquo;re following along you&amp;rsquo;ll probably be using IP addresses). Importantly, there are no TLS certificates, self-signed or otherwise.&lt;/p&gt;
&lt;p&gt;First we need to tag our image to include the registry name:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo docker tag hello-docker:latest ct390-docker-reg:5000/hello-docker
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-23-at-1.53.18-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;And we&amp;rsquo;ll try to push it up to our registry with:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker push ct390-docker-reg:5000/hello-docker
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-23-at-2.35.40-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s happening is that Docker would (quite reasonably) prefer to only work over secure connections. We can override this on this machine for today&amp;rsquo;s demo purposes by adding an exception for our self-hosted registry. You&amp;rsquo;ll need to create the file &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt; and add the registry that&amp;rsquo;s going to be allowed like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;{ &amp;#34;insecure-registries&amp;#34; : [ &amp;#34;ct390-docker-reg:5000&amp;#34; ]}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we restart docker and retry the push now, it should work:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-23-at-2.43.02-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;That looks like it worked. If we wanted to check, we can just hit an endpoint on the registry:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;curl http://ct390-docker-reg:5000/v2/_catalog
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-23-at-2.49.36-pm.png" alt=""&gt;&lt;/p&gt;
&lt;h3 id="pulling--insecure"&gt;Pulling &amp;amp; Insecure&lt;/h3&gt;
&lt;p&gt;Of course the ultimate test is going to be to use this image from a third machine, so let&amp;rsquo;s spin one up with a clean docker install with no images and try to run the image we&amp;rsquo;ve just added to our registry.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re going to have the same challenge pulling from a non-TLS registry as we had pushing to it, and the workaround is going to be exactly the same - add the registry to the insecure list in the &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;echo &amp;#39;{ &amp;#34;insecure-registries&amp;#34; : [ &amp;#34;ct390-docker-reg:5000&amp;#34; ]}&amp;#39; | sudo tee /etc/docker/daemon.jsonsudo systemctl daemon-reloadsudo systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we can run it. Since we don&amp;rsquo;t have the image locally yet, docker will pull it down for us from the registry before running it:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-23-at-3.19.03-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s it. Our own private Docker registry to store our images.&lt;/p&gt;
&lt;h4 id="references"&gt;References&lt;/h4&gt;
&lt;p&gt;In writing this post, I relied on some these resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Digital Ocean - &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-private-docker-registry-on-ubuntu-20-04"&gt;How To Set Up a Private Docker Registry on Ubuntu 20.04&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Baeldung - &lt;a href="https://www.baeldung.com/ops/docker-private-registry"&gt;Configure a Private Docker Registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;O&amp;rsquo;Reilly - &lt;a href="https://www.oreilly.com/library/view/kubernetes-in-the/9781492043270/app03.html"&gt;Configuring Docker to Push or Pull from an Insecure Registry&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Fly.io, Uptime Kuma &amp; scraping a status page</title><link>https://blog.iankulin.com/fly-io-uptime-kuma-scraping-a-status-page/</link><pubDate>Fri, 02 Feb 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/fly-io-uptime-kuma-scraping-a-status-page/</guid><description>&lt;p&gt;&lt;a href="https://dribbble.com/shots/5657880-Fly-io-Logo"&gt;&lt;img src="https://blog.iankulin.com/images/c1fef772e2dca5e1ab8c812f465c95a8.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been aware since I set up &lt;a href="https://blog.iankulin.com/uptime-kuma-nfty/"&gt;Uptime Kuma&lt;/a&gt; for my monitoring, that having an instance on my local network monitoring my VPS websites wasn&amp;rsquo;t ideal. The main reason being that the flakiest part of my infrastructure is my 4G home internet, so if that goes down I have no website monitoring, and even if I did, the notifications couldn&amp;rsquo;t get out.&lt;/p&gt;
&lt;p&gt;Of course, it would also be a simple matter to run an instance on the VPS that I host the sites on, but that has a similar problem in that if the VPS goes down, so does my monitoring of the VPS. What I really need is a third, independent space to run an instance.&lt;/p&gt;
&lt;h3 id="uptime-robot"&gt;Uptime Robot&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://uptimerobot.com/"&gt;Uptime Robot&lt;/a&gt; is a monitoring service that seems somehow related to Uptime Kuma? They have some of the same terminology and colour schemes - so I&amp;rsquo;m not really sure. Perhaps it&amp;rsquo;s a fork, or perhaps Uptime Kuma was inspired by Robot. Robot does have an API which is a nice addition, since ideally if my monitoring is spread around, I&amp;rsquo;d like to pull it all back into one &amp;lsquo;pane of glass&amp;rsquo; by having my system monitor the remote for how many &amp;lsquo;down&amp;rsquo; sites it&amp;rsquo;s tracking. It also has a number of other extra features such as heartbeat monitoring.&lt;/p&gt;
&lt;p&gt;Uptime Robot is a paid service, but like nearly all VC funded things growing a user base it has a free tier with some restrictions. I like NTFY for my notifications, but on Robot I could only access email notifications. There are iOS and Android apps, but I didn&amp;rsquo;t try them.&lt;/p&gt;
&lt;h3 id="third-space"&gt;Third Space&lt;/h3&gt;
&lt;p&gt;Ideally, I like to run another Uptime Kuma in a VPS on a different provider. I&amp;rsquo;ve heard that &lt;a href="https://www.oracle.com/au/cloud/free/"&gt;Oracle have a free tier&lt;/a&gt; which seems like it would be fine for this application, but a more interesting idea that I&amp;rsquo;ve been thinking of using for other projects is Fly.io.&lt;/p&gt;
&lt;h3 id="flyio"&gt;Fly.io&lt;/h3&gt;
&lt;p&gt;Fly.io own physical servers in colo datacentres around the world on which they offer compute based on &lt;a href="https://www.amazon.science/blog/how-awss-firecracker-virtual-machines-work"&gt;Firecracker VM&amp;rsquo;s&lt;/a&gt;. The cute bit is that you give them a Docker container, and they unpack it into one of these fast baby VM&amp;rsquo;s.&lt;/p&gt;
&lt;p&gt;The exact nature of their &amp;lsquo;free tier&amp;rsquo; is hard to figure out from their &lt;a href="https://fly.io/docs/about/pricing/"&gt;pricing page&lt;/a&gt;, but based on &lt;a href="https://community.fly.io/t/fly-io-free-tier-billing/11432"&gt;some answers to questions in their forum&lt;/a&gt;, and &lt;a href="https://jfmadrid.notion.site/Uptime-Kuma-for-Free-on-Fly-io-e5eeead6dfb4425b8403c100ec986191"&gt;blog posts from others who have set up Uptime Kuma&lt;/a&gt; there, it sounds like the deal is that if you use one shared CPU &lt;em&gt;and&lt;/em&gt; keep your storage under 3GB &lt;em&gt;and&lt;/em&gt; the charges for your use add up to less than $5/month - then it&amp;rsquo;s free. I did have to provide credit card details, so if &lt;a href="https://www.youtube.com/watch?v=N6lYcXjd4pg"&gt;I get a $71,393 bill,&lt;/a&gt; I&amp;rsquo;ll come back here and edit this. (&lt;em&gt;edit from the future: eight months later I haven&amp;rsquo;t paid a cent&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;To get Uptime Kuma running on Fly.io, I followed &lt;a href="https://jfmadrid.notion.site/Uptime-Kuma-for-Free-on-Fly-io-e5eeead6dfb4425b8403c100ec986191"&gt;this guide&lt;/a&gt;, but the steps where basically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create an account on Fly.io&lt;/li&gt;
&lt;li&gt;Install the Fly.io command line tools and run a command to &amp;lsquo;create&amp;rsquo; your app&lt;/li&gt;
&lt;li&gt;Create a &amp;lsquo;&lt;a href="https://github.com/lubien/fly-uptime-kuma/blob/main/fly.toml"&gt;fly.toml&lt;/a&gt;&amp;rsquo; file which is a text config file pointing to the docker image and supplying some details such as ports and location&lt;/li&gt;
&lt;li&gt;Use the CLI to set the disk space needed, and &amp;lsquo;deploy&amp;rsquo; the app&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It was impressive how simple all this was. If the intention of the free tier is to get you to try it, and show you how painless it is to deploy any dockerised app to the edge, then mission accomplished.&lt;/p&gt;
&lt;p&gt;You can check on the status of your app at &lt;a href="https://fly.io/dashboard"&gt;https://fly.io/dashboard&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-01-16-at-6.31.22-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-01-16-at-6.31.22-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And go to &lt;appname&gt;.fly.dev to see your app. On the free tier, you&amp;rsquo;re on a shared IPV4 address but it is possible to use your own domain if desired - that&amp;rsquo;s one of the things to set up in the .toml file.&lt;/p&gt;
&lt;p&gt;It is remarkable what you can deploy for free in the golden age of venture capital.&lt;/p&gt;
&lt;h3 id="extracting-status"&gt;Extracting Status&lt;/h3&gt;
&lt;p&gt;One of Uptime Kuma&amp;rsquo;s functions is to provide public (ie viewable without being logged in) &amp;lsquo;status&amp;rsquo; pages, and if all the services you&amp;rsquo;ve added to that status group are up, it has. great big heading saying &amp;ldquo;All Systems Operational&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-01-16-at-7.38.45-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-01-16-at-7.38.45-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So my plan to pull this status into my homelab instance of Uptime Kuma was just to add this remote status page as a monitor, and search for the keyword &amp;lsquo;All Systems Operational&amp;rsquo;. If that was found, I&amp;rsquo;d know everything was good. But of course, this is a modern web-app (I think using &lt;a href="https://vuejs.org/"&gt;Vue&lt;/a&gt;), so that text does not exist in the page, it&amp;rsquo;s added to the DOM by some JavaScript after the page is loaded based on some client side processing of (probably) some JSON data it pulls in.&lt;/p&gt;
&lt;p&gt;One option would be to use a web scraping library to write something to access this piece of information. On a page like this, that would involve a headless browser rendering the DOM then exposing it.&lt;/p&gt;
&lt;p&gt;But of course, the Javascript that is building the page we&amp;rsquo;re looking at is getting its data from somewhere, so it&amp;rsquo;s probably easier for us to grab that data directly and process it ourselves. How do we see where the data is from? We use the browser tools to look at the network requests when the page is loaded.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-01-16-at-7.20.50-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-01-16-at-7.20.50-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So if you view the status page at &lt;code&gt;&amp;lt;whatever.com&amp;gt;/status/&amp;lt;page_name&amp;gt;&lt;/code&gt;, it loads some data from &lt;code&gt;&amp;lt;whatever.com&amp;gt;/api/status-page/heartbeat/&amp;lt;page_name&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The JSON that&amp;rsquo;s returned from this request contains two objects: &lt;code&gt;heartbeatlist&lt;/code&gt;, and &lt;code&gt;uptimelist&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-01-16-at-8.06.05-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-01-16-at-8.06.05-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;heartbeatlist&lt;/code&gt; contains the last 50 retrievals for each of the URL&amp;rsquo;s being monitored. Each of these retrievals has a status (1 for up, 0 for down) and the response time. &lt;code&gt;uptimelist&lt;/code&gt; is the fraction of uptime. You can see in the data above that the first URL has a lower percentage of up-time (because I failed it to check my understanding of the status data).&lt;/p&gt;
&lt;p&gt;So I need to write an endpoint that requests this data, then checks the last array element of each of the URLs in the heartbeat list, then spit out some text saying if all the URL&amp;rsquo;s in this status group are available. That&amp;rsquo;s quite doable, I have the skills, but it&amp;rsquo;s probably a two hour job to do properly.&lt;/p&gt;
&lt;p&gt;Since this is an open source project, a better use of that time would be to add this functionality to Uptime Kuma so it would be available to anyone with the same problem. It might be a niche case, but the code to provide this output would be simpler inside the project and much more durable than reverse engineering it.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s have a look at the source and see what it&amp;rsquo;s like.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-01-16-at-8.34.24-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-01-16-at-8.34.24-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Well, well well. What do we have here? There&amp;rsquo;s an api route that outputs an SVG badge for a status page. The badge says &amp;lsquo;Degraded&amp;rsquo; in amber if some of the URL&amp;rsquo;s are down, and &amp;lsquo;Up&amp;rsquo; in green if they are all up. Those words are present in an aria label and the svg &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; tag, so they&amp;rsquo;ll be detectable by the Uptime Kuma &amp;lsquo;keyword&amp;rsquo; search.&lt;/p&gt;
&lt;p&gt;Five minutes later, we&amp;rsquo;re in business. Thank you open source!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-01-16-at-8.41.52-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-01-16-at-8.41.52-pm.png" width="772" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>What's unfinished in your Udemy?</title><link>https://blog.iankulin.com/whats-unfinished-in-your-udemy/</link><pubDate>Fri, 19 Jan 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/whats-unfinished-in-your-udemy/</guid><description>&lt;p&gt;If you work or study in tech, I always feel a good getting-to-know-you question is &amp;ldquo;what courses or tutorials did you start, but not finish?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;My Udemy doesn&amp;rsquo;t look &lt;em&gt;too&lt;/em&gt; bad:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-12-29-at-1.30.02-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The ZTM course was good, but I got stuck on an AI API exercise. I think it&amp;rsquo;s a common sticking point for students since Andrei includes a little rant about how it definitely does work - but I downloaded his repo with the solution and it was having the same errors I was and I gave up in frustration. I probably should have just skipped that one.&lt;/p&gt;
&lt;p&gt;The Linux one was really good - I learned a heap of basic little things (although I struggled with the guy&amp;rsquo;s accent a little) - things like tab for CLI completion. I guess you would learn this stuff from work colleagues, but if you&amp;rsquo;re self taught someone else needs to show you. This isn&amp;rsquo;t the highly recommended Linux basics course I wanted to do (&lt;a href="https://www.udemy.com/course/learn-linux-in-5-days/"&gt;Learn Linux in 5 days&lt;/a&gt;), but it was a lot cheaper.&lt;/p&gt;
&lt;h2 id="what-else"&gt;What else?&lt;/h2&gt;
&lt;p&gt;So that&amp;rsquo;s my Udemy, what else haven&amp;rsquo;t I finished?&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/100/swiftui"&gt;100 Days of Swift UI&lt;/a&gt; (47/100) - This is the free Paul Hudson course. I so highly recommend it for budding iOS developers I paid to join his super club or whatever that&amp;rsquo;s called although you don&amp;rsquo;t really need to. I got up to day 47 before deciding I wanted to work on web dev rather than iOS. I still use these skills occasionally for writing little MacOS apps.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://missing.csail.mit.edu/"&gt;Missing Semester Lectures&lt;/a&gt; (6/11) - Some CS lecturers at MIT realised there was some mechanics of day-to-day development missing from their courses (such as source control) so they put these together. They are great. Some of it falls into the &amp;lsquo;didn&amp;rsquo;t know you needed to know&amp;rsquo; so I plan to come back to these and finish one day.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cs193p.sites.stanford.edu/2021-0"&gt;CS193p&lt;/a&gt; (4/16) - These iOS development lectures are high quality and enjoyable, but if you run into issues (and are not enrolled in this unit at Stanford) you can get stuck - my best source of assistance was searching on github and finding others who had been through it. I gave up on these to focus on the Paul Hudson ones that were in more digestible chunks, and with some assistance (if you cared to pay for it).&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also completed numerous little free courses and stand-alone videos from YouTube - names that spring to mind are Jay from &lt;a href="https://www.youtube.com/@LearnLinuxTV"&gt;Learn Linux TV&lt;/a&gt;, &lt;a href="https://www.youtube.com/@WebDevSimplified"&gt;Web Dev Simplified&lt;/a&gt;, &lt;a href="https://www.youtube.com/@Fireship"&gt;Fireship&lt;/a&gt;, the &lt;a href="https://www.youtube.com/@NetNinja"&gt;Net Ninja&lt;/a&gt;, &lt;a href="https://www.youtube.com/@programmingwithmosh"&gt;Mosh&lt;/a&gt;, &lt;a href="https://www.youtube.com/@t3dotgg"&gt;Theo&lt;/a&gt;, &lt;a href="https://www.youtube.com/@NetworkChuck"&gt;Network Chuck&lt;/a&gt;, &lt;a href="https://www.youtube.com/@JamesQQuick"&gt;James Quick&lt;/a&gt;, and &lt;a href="https://www.youtube.com/@apalrdsadventures"&gt;Apalrd&amp;rsquo;s Adventures&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Mosh - I&amp;rsquo;ve paid for a month with the intention of doing his React 18 course in that time. I&amp;rsquo;m optimistic I will.&lt;/p&gt;
&lt;h3 id="whats-better-than-finishing-a-course"&gt;What&amp;rsquo;s better than finishing a course?&lt;/h3&gt;
&lt;p&gt;In most cases, what&amp;rsquo;s prevented me from finishing these courses, is that I&amp;rsquo;ve invested the time into writing code or doing projects that use the skills instead. I don&amp;rsquo;t feel bad about this, and in fact I&amp;rsquo;d recommend it. The only benefit of a course over just building projects is that they can teach you the things you didn&amp;rsquo;t know you needed. I listen to industry podcasts, and follow a lot of webdev people on Masterdon to try and help with that sort of discovery.&lt;/p&gt;
&lt;p&gt;For example, I&amp;rsquo;ve never used Zod, NextJS or Tailwind. But I know what they are, and where they would be useful to me because I&amp;rsquo;m tuned in to developer chatter about things.&lt;/p&gt;</description></item><item><title>Using LXC templates in Proxmox</title><link>https://blog.iankulin.com/using-lxc-templates-in-proxmox/</link><pubDate>Sun, 24 Dec 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/using-lxc-templates-in-proxmox/</guid><description>&lt;p&gt;I wrote a couple of weeks ago about a &lt;a href="https://blog.iankulin.com/new-self-hosted-service-workflow/"&gt;standard workflow&lt;/a&gt; I use to spin up a web service in an LXC container to add to my self-hosted collection of services. It went a bit like: do this, and then this, then this other thing. Whenever you find yourself repeating a set of steps like this, it&amp;rsquo;s usually a sign that you should be automating it. Not just to save time (although this is a key benefit) but also to improve repeatability and to avoid introducing errors.&lt;/p&gt;
&lt;p&gt;In Proxmox, this particular task is easily systematized using container &lt;em&gt;templates&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The simplest way to think of a container template is that it&amp;rsquo;s just a one-for-one snapshot of a container (ie the disk image, the configuration file that contains all the VM hardware information) all squashed up into a tarball - basically the same as a backup. This is then copied to create new containers.&lt;/p&gt;
&lt;p&gt;If we create new containers from a template, all the software and configuration that was in the template will be present in the new container. This is obviously the desired behaviour, but it presents some issues - we probably don&amp;rsquo;t want multiple containers with the same host name, or MAC address, or SSH host keys. Some of these issues Proxmox will sort out for us, some we&amp;rsquo;ll need to tidy up manually.&lt;/p&gt;
&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Issue&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Host name&lt;/td&gt;&lt;td&gt;When you 'clone' the template in Proxmox, it will ask you the new host name.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MAC address&lt;/td&gt;&lt;td&gt;Proxmox just creates a new one with no input needed from you.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Machine ID&lt;/td&gt;&lt;td&gt;If you truncate it in the template before you save it as a template, a new one will be created then the container is.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SSH host keys&lt;/td&gt;&lt;td&gt;Manually delete them in the template before saving the template, then manually re-create them in the new container once it's booted up.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id="making-the-template"&gt;Making the template&lt;/h3&gt;
&lt;p&gt;Create an LXC container as normal - ie chose &amp;ldquo;Create CT&amp;rdquo; in Proxmox, give it a name, choose a password, then a template, make the decisions about memory, disk, networking etc. Note that when you are choosing an official template to create it from (Apline, Debian, Ubuntu etc) , these files are almost identical to what we&amp;rsquo;ll be creating in this process.&lt;/p&gt;
&lt;p&gt;Once that&amp;rsquo;s up and running, I &lt;code&gt;ssh&lt;/code&gt; in and run all my apt updates and install any software or make any other changes. For me this includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Making it a client of &lt;a href="https://blog.iankulin.com/caching-apt-updates/"&gt;my local apt-cache&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;running ssh update and upgrades&lt;/li&gt;
&lt;li&gt;Copying in my SSH keys (ssh-copy-id)&lt;/li&gt;
&lt;li&gt;Installing sudo and adding myself as a sudo user&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/engine/install/debian/"&gt;Installing Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailscale.com/kb/1174/install-debian-bookworm/"&gt;Installing Tailscale,&lt;/a&gt; and doing the &lt;a href="https://blog.iankulin.com/getting-tailscale-working-in-lxc-containers/"&gt;Tailscale LXC fix&lt;/a&gt; (but not running &lt;code&gt;tailscale up&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Installing &lt;a href="https://blog.iankulin.com/simple-api-endpoint-in-go/"&gt;my simple machine status server&lt;/a&gt; that&amp;rsquo;s used for monitoring&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once that&amp;rsquo;s all done, we&amp;rsquo;ve got a nice clean container, but with all the software and config that we need for most future containers.&lt;/p&gt;
&lt;p&gt;Now we need to address a couple of the issues that could be caused by cloning this LXC from the table above.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Machine ID - you could probably get away with not worrying about this, but might run into a confusing issue later. A simple &lt;code&gt;sudo truncate -s 0 /etc/machine-id&lt;/code&gt; will nuke it, then a new unique one will be created when the clone container boots up.&lt;/li&gt;
&lt;li&gt;SSH host keys - you know when you ssh into a new system for the first time and OpenSSH asks you if you&amp;rsquo;re sure you want to recognise this server? This is done by the server identifying itself with one of these keys. If these are left the same for all of the clones of our template, you&amp;rsquo;ll have to be constantly deleting the keys out of your &lt;code&gt;known_hosts&lt;/code&gt; file. We can delete them now (which will make this template and any clones impossible to &lt;code&gt;ssh&lt;/code&gt; into) or later. I choose now. &lt;code&gt;sudo rm /etc/ssh/ssh_host_*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once this is all done, we are ready to convert this container into a template. Shut it down, then if you are cautious, back it up (you can&amp;rsquo;t convert a template back into a container). Then right click on it in Proxmox and choose &amp;lsquo;Convert to Template&amp;quot;. After a few seconds, it will be in your server view as a template with a slightly different icon.&lt;/p&gt;
&lt;h3 id="using-the-template"&gt;Using the template&lt;/h3&gt;
&lt;p&gt;The process of using our new template is called cloning. Right click on the template in Proxmox, and choose clone. You&amp;rsquo;ll be presented with a dialogue to give it a number, choose a host name, select the clone type (you want a &amp;lsquo;full clone&amp;rsquo;) and where this container&amp;rsquo;s storage will be.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-12-03-at-12.43.10-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-12-03-at-12.43.10-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A few seconds later the new LXC container will be in your server view and can be started.&lt;/p&gt;
&lt;p&gt;You won&amp;rsquo;t be able to ssh into this container yet as we deleted the host keys. Use the console in Proxmox to log in (with the root or sudo user credentials you set up earlier) and recreate the ssh host keys with &lt;code&gt;sudo dpkg-reconfigure openssh-server&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;While you are here, you should probably change the passwords for both users with &lt;code&gt;passwd&lt;/code&gt; or &lt;code&gt;sudo passwd &amp;lt;username&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The other thing I&amp;rsquo;ll need to do to use my container with Tailscale is to run &lt;code&gt;sudo tailscale up&lt;/code&gt; and complete the steps for that.&lt;/p&gt;
&lt;p&gt;And we&amp;rsquo;re done. You&amp;rsquo;ve now got a container that&amp;rsquo;s identical to our template, except for the things that need to be different. You can go ahead and use it as needed now.&lt;/p&gt;
&lt;h4 id="resources"&gt;Resources&lt;/h4&gt;
&lt;p&gt;Here&amp;rsquo;s a couple of useful things I came across in the writing of this post:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=J29onrRqE_I&amp;amp;t=619s"&gt;Proxmox VE Full Course: Class 8&lt;/a&gt; - Creating Container Templates - video from Jay (Learn Linux TV)&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pve.proxmox.com/wiki/Linux_Container"&gt;Linux Containers&lt;/a&gt; - from the Proxmox docs&lt;/p&gt;</description></item><item><title>Practice your restore strategy</title><link>https://blog.iankulin.com/practice-your-restore-strategy/</link><pubDate>Thu, 21 Dec 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/practice-your-restore-strategy/</guid><description>&lt;img src="https://blog.iankulin.com/images/img_7342.jpg" width="1000" alt=""&gt;
&lt;p&gt;My homelab set up is a production node, (pve-prod1) a backup production node (pve-prod2) and a development machine (pve-dev1). They are all G2 800 minis, but pve-prod1 has a i7 6700T and 32GB RAM, where as the other two are i5 6500T with 16GB. My thinking is that the older two can easily share the workload of the main production machine for disaster recovery. Everything is virtualised on top of Proxmox, so sharing up the VM&amp;rsquo;s and containers is trivial.&lt;/p&gt;
&lt;p&gt;Every three or four months, I run the nightly backups, turn off the production machine and restore back on to pve-prod2 and boot everything up. That was today&amp;rsquo;s job, and in the process I discovered a couple of things to address.&lt;/p&gt;
&lt;h3 id="issues"&gt;Issues&lt;/h3&gt;
&lt;p&gt;Issues were minor - everything was up again quite quickly, but they were:&lt;/p&gt;
&lt;h4 id="vm-disk-storage"&gt;VM disk storage&lt;/h4&gt;
&lt;p&gt;VM disk storage - I ran out on pve-prod2. Quite often when pve-prod1 is offline, it gets a new SSD, or most recently and 512GB of NMVE. So there&amp;rsquo;s oodles of room for the VM disks. As a result, I&amp;rsquo;m never mean with the sizes when I&amp;rsquo;m guessing what an application might need. I hate not allocating enough because expanding them is hard.&lt;/p&gt;
&lt;p&gt;Also, I&amp;rsquo;ve been moving docker workloads off the big docker VM and into their own LXC&amp;rsquo;s. But I&amp;rsquo;m still running the VM since it still has a couple of containers. All this adds up to there wasn&amp;rsquo;t enough room on the pve-prod2 SSD for all the VM disks. This is not the end of the world, I can leave the VM disks on the NAS and work over the network - but it&amp;rsquo;s a reminder to me to not let the backup hardware get to far behind the production hardware.&lt;/p&gt;
&lt;p&gt;Of course I could have moved some of these onto pve-dev1 (which is massively overspec&amp;rsquo;d) but I don&amp;rsquo;t really want to power two machines if I can get by with one. I have asked Father Christmas for another 512GB NMVE M2, so I&amp;rsquo;m optimistic this will be solved shortly.&lt;/p&gt;
&lt;h4 id="versions"&gt;Versions&lt;/h4&gt;
&lt;p&gt;After I moved all the VMs and LXCs, I realised I that pve-prod2 is running an old version of Proxmox - it&amp;rsquo;s on 7.4 and the others are on 8.1. Everything works (unless you need dark mode) but it was a mistake on my part, when I&amp;rsquo;d upgraded pve-prod1 I deliberately left prod2 on the old, known good, version but with the intention I&amp;rsquo;d upgrade it in a month or so, then never did.&lt;/p&gt;
&lt;h4 id="lxc-backup-to-nas"&gt;LXC Backup to NAS&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/problems-backing-up-lxc-to-nfs-in-proxmox/"&gt;I&amp;rsquo;ve previously discussed this issue&lt;/a&gt;, where an LXC apparently does not have the require permissions for it&amp;rsquo;s temporary files on an NFS share but does have them for the finished backup. It&amp;rsquo;s a simple config change, but one that I hadn&amp;rsquo;t made to prod2. This is a good case for maintaining a post-proxmox-install Ansible playbook.&lt;/p&gt;
&lt;h3 id="bouquets"&gt;Bouquets&lt;/h3&gt;
&lt;h4 id="proxmox"&gt;Proxmox&lt;/h4&gt;
&lt;p&gt;I&amp;rsquo;ve been pondering if I should move away from Proxmox. I imagine I can achieve something similar with some combination of KVM, QEMU, Virt-Manager or Cockpit. I&amp;rsquo;d be learning some new things and be closer to a generic solution. On the other hand, I&amp;rsquo;m still learning about Proxmox, especially the command line stuff as I convert more of the homelab to infrastructure as code.&lt;/p&gt;
&lt;p&gt;Also, it&amp;rsquo;s just worked flawlessly. I was reminded today as I did this now routine task of the first time I moved a VM between two computers how exciting it was - and I was doing that as a noob using the web interface. Proxmox certainly meets all my current needs so I&amp;rsquo;ll be sticking with it. If I&amp;rsquo;m eBay tempted by more iron, I might have a play with some of the other options, but for the moment, I&amp;rsquo;m sticking with it.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also conscious that the NAS is filling up (although slowly) and a future improvement would be to start to use the &lt;a href="https://www.proxmox.com/en/proxmox-backup-server/overview"&gt;Proxmox Backup Server&lt;/a&gt;. This delta&amp;rsquo;s your backups to allow a more comprehensive history to be kept while reducing the disk space being used. This will lock me into the Proxmox ecosystem a little more.&lt;/p&gt;
&lt;h4 id="synology"&gt;Synology&lt;/h4&gt;
&lt;p&gt;Also I need to shoutout Synology NAS&amp;rsquo;s. Just super reliable. I yearn for a ZFS solution, but if you just want reliable, gets things done storage for your homelab, they are an excellent choice for most situations. They are not sexy.&lt;/p&gt;
&lt;h4 id="monitoring"&gt;Monitoring&lt;/h4&gt;
&lt;img src="https://blog.iankulin.com/images/img_b42eca952bee-1.jpeg" width="577" alt=""&gt;
&lt;p&gt;A lot of the time I don&amp;rsquo;t really think about my monitoring - which consists or Uptime Kuma hooked up to Ntfy for phone notifications, and a &lt;a href="https://blog.iankulin.com/simple-api-endpoint-in-go/"&gt;custom Go program&lt;/a&gt; that exposes the RAM and disk use on each container and VM.&lt;/p&gt;
&lt;p&gt;But when you power down your production server, and your phone lights up in red, followed by green messages as each service comes back up, that&amp;rsquo;s a good feeling.&lt;/p&gt;
&lt;p&gt;Anyway, here&amp;rsquo;s your reminder to test your backup strategy if you haven&amp;rsquo;t done that for a while. Like me, you might learn something to your advantage.&lt;/p&gt;</description></item><item><title>Gogs, Gitea, Forgejo</title><link>https://blog.iankulin.com/gogs-gitea-forgejo/</link><pubDate>Mon, 18 Dec 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/gogs-gitea-forgejo/</guid><description>&lt;img src="https://blog.iankulin.com/images/img_7071-1.png" width="640" alt=""&gt;
&lt;p&gt;I&amp;rsquo;ve been really pleased with &lt;a href="https://blog.iankulin.com/tags/gogs/"&gt;Gogs&lt;/a&gt; - it&amp;rsquo;s lightweight, was simple to spin up, and has worked perfectly. But then this morning on Mastodon, there&amp;rsquo;s a &lt;a href="https://mastodon.social/@Codeberg@social.anoxinon.de/111471407276450348"&gt;post from @Codeberg.org&lt;/a&gt; describing a security vulnerability in their Git hosting project Forgejo. This issue also apparently affects Gitea and Gogs - what&amp;rsquo;s up with that?&lt;/p&gt;
&lt;p&gt;I actually already did spend a bit of time comparing Gogs and Gitea before deciding on Gogs, since I&amp;rsquo;d heard of people running Gitea over the past year or so, but only seen that Gogs seemed to be popular with self-hosters in a Lemmy post I&amp;rsquo;d read. My first impression was that Gitea was more focused on CI/CD and seemed to have a more complicated install process.&lt;/p&gt;
&lt;p&gt;What I didn&amp;rsquo;t do, was think about the project management and teams. It turns out that &lt;a href="https://about.gitea.com/"&gt;Gitea&lt;/a&gt; was forked from &lt;a href="https://gogs.io/"&gt;Gogs&lt;/a&gt; by contributors in 2016 due to &lt;a href="https://blog.gitea.com/welcome-to-gitea/"&gt;disagreements about the project management&lt;/a&gt;. Then at the end of 2022 &lt;a href="https://forgejo.org/"&gt;Forgejo&lt;/a&gt; was forked from Gitea due to &lt;a href="https://forgejo.org/2022-12-15-hello-forgejo/"&gt;Gitea moving the trademarks and domain into a company&lt;/a&gt; providing Gitea support.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://forgejo.org/2023-11-release-v1-20-5-1/"&gt;CVE announcement from Forgeo&lt;/a&gt;, while a little snarky about their ancestors, does give the impression of a functional organisation that&amp;rsquo;s able to deal with issues as they come up. It&amp;rsquo;s a credit to the group to be in that position after just a year, and their &lt;a href="https://codeberg.org/forgejo/forgejo"&gt;repo&lt;/a&gt; (which is dogfooded) seems plenty active.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve only just started on Gogs, so it&amp;rsquo;s still easy to move if that&amp;rsquo;s what I decide. I guess my learning from stumbling upon this security announcement is more that I should:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;take into account more than just project features when making these decisions&lt;/li&gt;
&lt;li&gt;I need to be subscribed to the channels where I&amp;rsquo;d learn about security issues in the projects I&amp;rsquo;m using and their major dependencies.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Gogs - your own tiny GitHub</title><link>https://blog.iankulin.com/gogs-your-own-tiny-github/</link><pubDate>Wed, 06 Dec 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/gogs-your-own-tiny-github/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-11-20-at-8.08.37-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;(edit: - I&amp;rsquo;ve &lt;a href="https://blog.iankulin.com/gogs-gitea-forgejo/"&gt;had a rethink about&lt;/a&gt; my source hosting)&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;re familiar with coding tools, like the excellent &lt;a href="https://code.visualstudio.com/"&gt;VS Code&lt;/a&gt;, and &lt;a href="https://git-scm.com/docs/git"&gt;git&lt;/a&gt;, it&amp;rsquo;s immediately apparent that these tools can be applicable for other purposes. A great example is that I now do my financial accounting in plain text (using &lt;a href="https://github.com/beancount/beancount"&gt;beancount&lt;/a&gt;). I have a python script that converts by bank account data in to the beancount format text files, I edit them in VS Code with a &lt;a href="https://marketplace.visualstudio.com/items?itemName=Lencerf.beancount"&gt;plugin&lt;/a&gt; that does the syntax highlighting and checks everything balances.&lt;/p&gt;
&lt;p&gt;Naturally, I want to version control that, so my text based accounts are all committed to git. But I don&amp;rsquo;t really want to push them up to GitHub, even to a private repo. I want to push them to a git server that&amp;rsquo;s available for me to pull down from anywhere, and is backed up with all my other data.&lt;/p&gt;
&lt;p&gt;It actually is possible to run a git server to do this with vanilla git, and I&amp;rsquo;m sure that&amp;rsquo;s how the hairy chested &lt;a href="https://www.linuxfoundation.org/blog/blog/classic-sysadmin-how-to-run-your-own-git-server"&gt;sysadmins of old&lt;/a&gt; do it. But I want a web gui a bit like GitGub that I&amp;rsquo;m familiar with. Of course, GutHub does all that other sweet stuff like CI/CD, but I don&amp;rsquo;t need that for my accounts.&lt;/p&gt;
&lt;p&gt;There are a couple of popular options for this job, one is &lt;a href="https://about.gitea.com/"&gt;Gitea&lt;/a&gt; which does lean into that CI/CD functionality, and the other is &lt;a href="https://gogs.io/"&gt;Gogs&lt;/a&gt;, which being a bit simpler, is also a bit simpler to get going. When I say simpler, it&amp;rsquo;s still massive overkill for my needs - you can have thousands of users, do pull requests, track issues, write project wikis - all that good stuff. It also has webhooks so you can knit together a pipeline with drone.io, Jenkins or other CI/CD tools. So I went with Gogs&lt;/p&gt;
&lt;h3 id="installing"&gt;Installing&lt;/h3&gt;
&lt;p&gt;It&amp;rsquo;s not mentioned on the &lt;a href="https://gogs.io/docs/installation"&gt;install page for Gogs&lt;/a&gt;, but there is an official &lt;a href="https://hub.docker.com/r/gogs/gogs/"&gt;container build&lt;/a&gt;. Possibly this is because there&amp;rsquo;s a couple of rough edges that I&amp;rsquo;ll get to shortly. I&amp;rsquo;ve talked before about how I like to run services in Docker on LXC, so I won&amp;rsquo;t go over that again. Here&amp;rsquo;s my docker-compose:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;version: &amp;#39;3&amp;#39;

services:
 gogs:
 image: gogs/gogs
 container_name: gogs
 ports:
 - &amp;#34;23:22&amp;#34;
 - &amp;#34;80:3000&amp;#34;
 volumes:
 - ./data:/data
 restart: always
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However, there is a gotcha I hadn&amp;rsquo;t encountered before - when I &lt;code&gt;docker compose up&lt;/code&gt; with this, I got the error &amp;ldquo;failed to register layer: unlinkat /app/gogs/docker/build: invalid argument&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-11-20-at-8.36.42-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-11-20-at-8.36.42-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When I asked ChatGPT about this, she thought it might be to do with the storage driver. I didn&amp;rsquo;t know what that was so I spent time googling around.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-11-20-at-8.40.36-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-11-20-at-8.40.36-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pretty soon, I discovered &lt;a href="https://forum.proxmox.com/threads/docker-failed-to-register-layer-applylayer-exit-status-1-stdout-stderr-unlinkat-var-log-apt-invalid-argument.119954/"&gt;this thread&lt;/a&gt;. Part way down there&amp;rsquo;s the suggestion to edit &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt; to add a different storage driver, followed by many comments of &amp;ldquo;Thanks!&amp;rdquo; and &amp;ldquo;That fixed it&amp;rdquo;. I followed that advice, (it uses a different driver &amp;ldquo;vfs&amp;rdquo; rather than &amp;ldquo;aufs&amp;rdquo; as suggested by ChatGPT) and then the container came up properly.&lt;/p&gt;
&lt;p&gt;With that out of the way, and the container live, if you go to the port you&amp;rsquo;ve specified in the docker-compose file (mine was :80), you&amp;rsquo;ll be greeted with the &amp;ldquo;Install Steps For First-time Run&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-11-20-at-8.54.19-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-11-20-at-8.54.19-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;They are not joking. You won&amp;rsquo;t be able to guess these settings. I guess they haven&amp;rsquo;t put a lot of work into the container experience - some of these settings need to be for inside the container, and some are used for prompting the user, which are outside of the container settings. I suspect this rough edge is why the container install is not on the Gogs website yet.&lt;/p&gt;
&lt;p&gt;Anyway, after I&amp;rsquo;d ignored this suggestion, run into problems, google them, found closed issues where people had had the same problem and the devs pointed them to the perfectly clear guidelines we hadn&amp;rsquo;t read&amp;hellip;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll leave you to read the guidelines. The only other things of note is that I used the SQLite database to make my life simpler, and you don&amp;rsquo;t need to muck around making an admin account - it just makes the first person to log in the admin. Once that&amp;rsquo;s all done, you have to create a user account, then log in with it. You&amp;rsquo;ll be greeted by a reasonably familiar sight.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-11-20-at-9.07.30-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-11-20-at-9.07.30-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you go ahead and create a repository in Gogs, it will give you the commands to push a repo up:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-11-20-at-9.09.28-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-11-20-at-9.09.28-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s do that:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-11-20-at-9.21.38-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The back in our repo on Gogs:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-11-20-at-9.22.53-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-11-20-at-9.22.53-pm.png" width="1023" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>New Self-Hosted Service Workflow</title><link>https://blog.iankulin.com/new-self-hosted-service-workflow/</link><pubDate>Sun, 03 Dec 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/new-self-hosted-service-workflow/</guid><description>&lt;p&gt;I&amp;rsquo;ve developed a bit of a workflow for setting up a new service of some type on the homelab. Installing it is the obvious thing, but I also have a few quality of life things I do to make it a full production-quality part of my installation. I thought it might be helpful to run through those things using a recent example of adding &lt;a href="https://www.audiobookshelf.org/"&gt;audiobookshelf&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="audiobookshelf"&gt;audiobookshelf&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.audiobookshelf.org/"&gt;audiobookshelf&lt;/a&gt; is a web based system for viewing, playing, downloading and/or generally managing your audio books. I&amp;rsquo;ve been an &lt;a href="https://www.audible.com.au/"&gt;Audible&lt;/a&gt; user/subscriber, but recently got grumpy at them about something - I think I had paused my subscription, and my downloaded books were still available on my phone. I was halfway through one, upgraded the app, and then wasn&amp;rsquo;t able to play the book without re-subscribing. That might not be exactly right, but it was some type of frustrating carry on like that.&lt;/p&gt;
&lt;p&gt;In any case, that made me decide I couldn&amp;rsquo;t trust them, and it was time to reassert my digital sovereignty by downloading the books I&amp;rsquo;d paid for (and the ones they&amp;rsquo;d given me), removing the &lt;a href="https://en.wikipedia.org/wiki/Digital_rights_management"&gt;DRM&lt;/a&gt;, and hosting it myself. The first two steps of that process were easily carried out with a brilliant bit of software called &lt;a href="https://openaudible.org/"&gt;OpenAudible&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="do-it-on-dev"&gt;Do it on dev&lt;/h3&gt;
&lt;img src="https://blog.iankulin.com/images/img_7003.jpg" width="900" alt=""&gt;
&lt;p&gt;Since I have the luxury of having separate production and development servers, I generally play around with new things I&amp;rsquo;m trying out on the dev instance of Proxmox. Note that this is almost entirely unnecessary - since everything is virtualised in Proxmox on the production server, there&amp;rsquo;s hardly any damage I could cause in one VM or container that would adversely affect anything else.&lt;/p&gt;
&lt;p&gt;Nevertheless, whether it&amp;rsquo;s caution, or a need to justify the size of the homelab, I always start building new things on the dev server. Once it&amp;rsquo;s all working perfectly, it&amp;rsquo;s a simple matter (that we&amp;rsquo;ll get to later) to move it as-is to the production server.&lt;/p&gt;
&lt;h3 id="installation-stack"&gt;Installation Stack&lt;/h3&gt;
&lt;p&gt;My default setup now is a Docker container, inside an LXC container on Proxmox. Although this originally felt like a comical number of levels of abstraction, each layer is doing something for me, and now it just feels like the cost of doing business.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Proxmox - virtualising everything insulates services from each other, makes moving them around easier, backing them up and restoring them trivial, and provides a level of high availability.&lt;/li&gt;
&lt;li&gt;LXC - lighter than a full VM, more VM like than Docker, and quicker to play with. Does add a bit of complexity we&amp;rsquo;ll get to later.&lt;/li&gt;
&lt;li&gt;Docker - OCI compliant containers are the bomb. This is how we do software now. I pushed back as long as I could but the logic is too strong. There are problems still to solve around &lt;a href="https://www.cisa.gov/sbom"&gt;SBOM&lt;/a&gt;, but the reduction in the work of managing installations is compelling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I create a non-root user, and the &lt;code&gt;docker-compose.yml&lt;/code&gt; and the directories for any config or data all go in that user&amp;rsquo;s home directory. I don&amp;rsquo;t prefer &lt;a href="https://docs.docker.com/storage/volumes/"&gt;Docker volumes&lt;/a&gt; for the data any more since the &lt;a href="https://blog.iankulin.com/docker-volume-backup-is-more-complicated-than-it-should-be/"&gt;downsides&lt;/a&gt; annoy me and the upsides must be in order to solve problems I haven&amp;rsquo;t encountered yet.&lt;/p&gt;
&lt;p&gt;Since there are a few little gotchas using LXC, when I&amp;rsquo;m trying something for the very first time, and I&amp;rsquo;m not even sure if it&amp;rsquo;s going to end up being used, I&amp;rsquo;ll do it in an VM first. I have a bunch of VM&amp;rsquo;s on the dev machine in varying states, so I normally pick one of them that already had Docker installed. This also gives me an idea for the amount of RAM and disk space the container is going to need. Changing the memory size once it&amp;rsquo;s in production is no biggie, but expanding the disk space is a bit of stuffing around.&lt;/p&gt;
&lt;p&gt;When I&amp;rsquo;m ready to make the container, it&amp;rsquo;s always the latest Debian stable, unprivileged, nesting turned on. Very few web services require more than 1GB RAM, and I guess the disk usage from the earlier trials then add a bit. I have lots of disk space and CPU time - it&amp;rsquo;s usually memory that&amp;rsquo;s the first bottleneck you&amp;rsquo;ll run into on little homelab servers. I&amp;rsquo;m sure I&amp;rsquo;ve heard &lt;a href="https://2.5admins.com/"&gt;Jim Salter and Allan Jude&lt;/a&gt; recommend that you should keep the VM memory low to leave more for the host so the it can effectively cache for all the guests.&lt;/p&gt;
&lt;p&gt;I always use docker-compose. Too many times I&amp;rsquo;ve wanted to upgrade a container, and have to waste time figuring out what the run command was. The compose file is good documentation for where your data is as well if you are, like me, avoiding volumes.&lt;/p&gt;
&lt;h3 id="the-steps"&gt;The Steps&lt;/h3&gt;
&lt;h4 id="some-installs"&gt;Some installs&lt;/h4&gt;
&lt;p&gt;With the fresh LXC created (latest Debian stable, unprivileged, nesting turned on), and started, I use the Proxmox console to log in, do some &lt;code&gt;apt&lt;/code&gt; updates, use &lt;code&gt;adduser&lt;/code&gt; to add my user, &lt;code&gt;apt install sudo&lt;/code&gt; and then &lt;code&gt;usermod&lt;/code&gt; to add my user to the sudo group.&lt;/p&gt;
&lt;p&gt;I then switch to a real terminal and ssh in as that user to install Docker. While that&amp;rsquo;s happening, I log into my router and reserve the IP address for the new container. This will follow when I move the container to the production server since it takes it&amp;rsquo;s MAC address with it.&lt;/p&gt;
&lt;p&gt;My pattern for SSH keys, which might not be the most secure, is that I have a key per device. So there&amp;rsquo;s one from my laptop, one for the terminal on my phone, and one for a VM that I sometimes use as an entry point to my home network via Tailnet. My theory with all this is that if any of those devices are compromised (for example my laptop is stolen) I can revoke that key from each of my services.&lt;/p&gt;
&lt;h4 id="nas-mount"&gt;NAS Mount&lt;/h4&gt;
&lt;p&gt;Often the service I&amp;rsquo;m installing needs access to the NAS - and that&amp;rsquo;s the case for audibookshelf which obviously needs access to my collection of audio books on my four bay Synology. I use an &lt;code&gt;/etc/fstab&lt;/code&gt; entry to mount the folder I&amp;rsquo;m interested in. I&amp;rsquo;ve set up the NAS to share these over SMB. The entry for audiobookshelf looks like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;//192.168.100.32/media/books/audio/ /mnt/media cifs username=abs_user,password=SeCrErpaSSword,file_mode=0660,dir_mode=07
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There&amp;rsquo;s a bit going on here, let&amp;rsquo;s pull it apart:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;//192.168.100.32/media/books/audio/&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The directory on the NAS where my audiobooks are stored. I&amp;rsquo;ve been a bit slack here. It would have been better for that directory to have been it&amp;rsquo;s own share to reduce the attack surface.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;/mnt/media&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the directory in the LXC container that we&amp;rsquo;re mounting the books to. If I could go back in time to when I started by Linux &amp;amp; self-hosting journey, I would not have used the word media, since in Linux that more refers to things like USB drives and less like entertainment to consume. &lt;a href="https://www.karlton.org/2017/12/naming-things-hard/"&gt;Naming things is hard&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;cifs&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The protocol being used for the share. I&amp;rsquo;ve got this shared folder set up as SMB, so I use CIFS. Some of my shares are NFS, so you could have &lt;code&gt;nfs&lt;/code&gt; at this position in the entry.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;username=abs_user,password=SeCrErpaSSword&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It seems bad to have these credentials in /etc/fstab where any user on this system can read them, but I am the only user on this system and I don&amp;rsquo;t know what other convenient way I could get around this.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;file_mode=0660&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read/write for user and group&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;dir_mode=07&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read/write/execute on directories for user &amp;amp; group&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once that&amp;rsquo;s in the &lt;code&gt;/etc/fstab&lt;/code&gt;, you need to mount it with a &lt;code&gt;mount -a&lt;/code&gt;, then you should see the share by &lt;code&gt;ls&lt;/code&gt;-ing the mount point.&lt;/p&gt;
&lt;h4 id="docker-compose"&gt;Docker compose&lt;/h4&gt;
&lt;p&gt;Obviously this will vary with whatever service you&amp;rsquo;re running. Here&amp;rsquo;s mine for audiobookshare.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;version: &amp;#39;3&amp;#39;

services:
 audiobookshelf:
 image: ghcr.io/advplyr/audiobookshelf
 container_name: audiobookshelf
 ports:
 - &amp;#34;80:80&amp;#34;
 volumes:
 - ./config:/config
 - ./metadata:/metadata
 - /mnt/media:/audiobooks
 restart: always
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The notable things here are that I store all the container data - in this case &lt;code&gt;/config&lt;/code&gt; and &lt;code&gt;/metadata&lt;/code&gt; in subdirectories from the current directory, which is actually the user&amp;rsquo;s home directory. This LXC container is only for running this single service, so as soon as I &lt;code&gt;ssh&lt;/code&gt; in, everything I need to know or find out is easily discoverable, and easily accessible if I want to &lt;code&gt;scp&lt;/code&gt; it without a convoluted path.&lt;/p&gt;
&lt;p&gt;Another benefit of running in individual LXC&amp;rsquo;s is that each service has its own IP address - so I can use port 80 for every service.&lt;/p&gt;
&lt;h4 id="tailscale"&gt;Tailscale&lt;/h4&gt;
&lt;p&gt;Now that we can have up to 100 Tailscales on the free tier, every real service gets one. For the install, I just follow the &lt;a href="https://tailscale.com/kb/1174/install-debian-bookworm/"&gt;Debian Tailscale installation instructions&lt;/a&gt; since I&amp;rsquo;m using a Debian LXC. And now when we try &lt;code&gt;tailscale up&lt;/code&gt; we run into the LXC problem. I&amp;rsquo;ve already documented how to overcome that in &lt;a href="https://blog.iankulin.com/getting-tailscale-working-in-lxc-containers/"&gt;an earlier post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The combination of using Tailscale, and having access to port 80 means that the web address for this service will just be whatever hostname I gave it, in this case http://ct327-audiobookshelf&lt;/p&gt;
&lt;h4 id="ansible"&gt;Ansible&lt;/h4&gt;
&lt;p&gt;Some of the next steps are so common, I&amp;rsquo;ve set up Ansible playbooks for them, but to allow me to apply them to the new server, they need to be added into my Ansible infrastructure. First the hosts file where they get a host entry and some variables.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-11-18-at-5.48.08-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-11-18-at-5.48.08-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then in the encrypted &lt;code&gt;vault.yml&lt;/code&gt; file for the secrets. I&amp;rsquo;ve written about these before &lt;a href="https://blog.iankulin.com/first-ansible-playbook/"&gt;here&lt;/a&gt; and &lt;a href="https://blog.iankulin.com/ansible-with-secrets/"&gt;here&lt;/a&gt;. Since I have &lt;code&gt;hosts:all&lt;/code&gt; in the playbook that runs all my &lt;a href="https://gist.github.com/IanKulin/41dbf097ac6bddd9e315859d3a06fe02"&gt;&lt;code&gt;apt&lt;/code&gt; updates&lt;/a&gt;, this now means the LXC container will get all it&amp;rsquo;s updates.&lt;/p&gt;
&lt;p&gt;Now we can automate some tasks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make this server use our &lt;code&gt;apt-cache&lt;/code&gt; server to make updates a bit faster and efficient. Described &lt;a href="https://blog.iankulin.com/caching-apt-updates/"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Install a &lt;a href="https://blog.iankulin.com/simple-api-endpoint-in-go/"&gt;little endpoint&lt;/a&gt; so the available memory and disk space can be monitored.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once that endpoint is installed, I can add a couple of entries to my &lt;a href="https://blog.iankulin.com/uptime-kuma-nfty/"&gt;Uptime Kuma&lt;/a&gt; instance to keep track of the server health and notify me with &lt;a href="https://blog.iankulin.com/uptime-kuma-nfty/"&gt;ntfy&lt;/a&gt; - so that&amp;rsquo;s monitoring covered off.&lt;/p&gt;
&lt;h4 id="backups"&gt;Backups&lt;/h4&gt;
&lt;p&gt;Backups in Proxmox are easy. I already have a general backup job set up for the prod DataCenter - it just snapshots every VM and LXC to the NAS at 1:00am each day. That&amp;rsquo;s plenty for this service - the only thing that would get lost would be a day&amp;rsquo;s worth of metadata, most of which is automatically pulled from web services anyway.&lt;/p&gt;
&lt;p&gt;This backup is of the LXC container with all the audiobookshelf config and code - not my book library. There is a backup process for it that&amp;rsquo;s a complicated collection of and external USB drive and &lt;code&gt;rsync&lt;/code&gt;-ing to a remote that might be a story for another day.&lt;/p&gt;
&lt;h3 id="done"&gt;Done&lt;/h3&gt;
&lt;p&gt;And that&amp;rsquo;s it. Now my audiobookshelf is running in an LXC container, serving the books off my NAS. The service is monitored for health, and there&amp;rsquo;s a backup plan in place. I can kick back and catch up on some technical reading.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/img_7018.jpg" alt=""&gt;&lt;/p&gt;</description></item><item><title>ViewTube</title><link>https://blog.iankulin.com/viewtube/</link><pubDate>Mon, 27 Nov 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/viewtube/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-11-18-at-5.17.47-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Whenever I encounter one of those &amp;ldquo;What are you self-hosting?&amp;rdquo; threads, I know I&amp;rsquo;m about to waste an hour looking at, and often trying out, software I probably don&amp;rsquo;t really need, and that was the case with &lt;a href="https://lemmy.world/post/8385160"&gt;this post&lt;/a&gt; on the &lt;a href="https://lemmy.world/c/selfhost@lemmy.ml"&gt;lemmy.world Selfhosted&lt;/a&gt; community.&lt;/p&gt;
&lt;p&gt;The basic idea of ViewTube is that it&amp;rsquo;s a self-hosted front end for YouTube, which just happens to strip out all the advertising and tracking. You can create your own local accounts which allows you to subscribe to channels and which keeps your progress so you don&amp;rsquo;t start over if you go back to a video - although I couldn&amp;rsquo;t see a history list. Forgetting your history might be a feature in an app designed to prevent tracking.&lt;/p&gt;
&lt;p&gt;It only took five minutes to get it running to try out, and most of that was downloading the docker images. I just made a directory in a VM and dropped this docker compose into it.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;version: &amp;#39;3&amp;#39;

services:
 viewtube:
 restart: unless-stopped
 # Or use mauriceo/viewtube:dev for the development version
 image: mauriceo/viewtube:latest
 # ViewTube will not start until the database and redis are ready
 depends_on:
 - viewtube-mongodb
 - viewtube-redis
 # Make sure all services are in the same network
 networks:
 - viewtube
 volumes:
 # This will map ViewTube&amp;#39;s data directory to the local folder ./data/viewtube/
 - ./data/viewtube:/data
 environment:
 - VIEWTUBE_DATABASE_HOST=viewtube-mongodb
 - VIEWTUBE_REDIS_HOST=viewtube-redis
 ports:
 - 8066:8066

 viewtube-mongodb:
 restart: unless-stopped
 image: mongo:4.4
 networks:
 - viewtube
 volumes:
 - ./data/db:/data/db

 viewtube-redis:
 restart: unless-stopped
 image: redis:7
 networks:
 - viewtube
 volumes:
 - ./data/redis:/data

networks:
 viewtube:
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The only change in here from the &lt;a href="https://viewtube.wiki/installation/docker"&gt;official one&lt;/a&gt; was to change to an older version since I hadn&amp;rsquo;t passed through the CPU in host mode, so there was no &lt;a href="https://old.reddit.com/r/homelab/comments/yvo4jm/how_do_i_enable_avx_on_my_server/"&gt;AVX support which is required by newer versions&lt;/a&gt; of MongoDB.&lt;/p&gt;</description></item><item><title>Docker volume backup is more complicated than it should be</title><link>https://blog.iankulin.com/docker-volume-backup-is-more-complicated-than-it-should-be/</link><pubDate>Fri, 17 Nov 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/docker-volume-backup-is-more-complicated-than-it-should-be/</guid><description>&lt;p&gt;&lt;a href="https://unccelearn.org/course/view.php?id=128&amp;page=overview&amp;lang=en"&gt;&lt;img src="https://blog.iankulin.com/images/big.jpg" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When I set up my first Docker container (I think for &lt;a href="https://blog.iankulin.com/uptime-kuma-nfty/"&gt;Uptime Kuma&lt;/a&gt;), I had read around and understood there were two choices for persistent; &lt;em&gt;bind mounts&lt;/em&gt; (where the data inside the container is effectively a symlink to a location on the local file system) or &lt;em&gt;name volumes&lt;/em&gt; where Docker abstracted that away a bit, so you didn&amp;rsquo;t have to worry where it was - I sort of understood Docker &amp;lsquo;managed&amp;rsquo; it.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been lazily doing my &amp;lsquo;backups&amp;rsquo; by just saving snapshots of entire VM&amp;rsquo;s - which works really well, Proxmox handles the scheduling of them, I regularly test them (every month I run off the backup production server for a couple of days from the backups). I don&amp;rsquo;t mind that backing up up an entire VM for a couple of Dockerised apps is expensive in disk because local disk is cheap and it&amp;rsquo;s super convenient.&lt;/p&gt;
&lt;p&gt;However, I&amp;rsquo;ve got a couple of projects on the list where I&amp;rsquo;d like to move a container and it&amp;rsquo;s data between VM&amp;rsquo;s. One is trying out Jellyfin in Docker in an LXC, and another is moving the containers on my general utility dockerhost to a new VM with a bit larger disk since that seems easier than expanding the disk.&lt;/p&gt;
&lt;p&gt;I assumed I&amp;rsquo;d be stoping the container and doing something like &lt;code&gt;docker export portainer_data somebackupfile.name&lt;/code&gt; then moving that file over to the new system and running &lt;code&gt;docker import portainer_data somebackupfile.name&lt;/code&gt; to re-create it.&lt;/p&gt;
&lt;p&gt;But no, that&amp;rsquo;s not how it works. According to the Docker people, I need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use inspect to find out the internal data directories of the container&lt;/li&gt;
&lt;li&gt;Stop the container&lt;/li&gt;
&lt;li&gt;Create a new generic linux container&lt;/li&gt;
&lt;li&gt;Have it mount the docker volumes&lt;/li&gt;
&lt;li&gt;Also have it bind mount to the current directory&lt;/li&gt;
&lt;li&gt;Run a command inside the container to tar ball the internal data directory and save it to the bind mount&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The only real concession to usability along the way is that there&amp;rsquo;s a &lt;code&gt;--volumes_from&lt;/code&gt; flag that saves you from extracting all the volume names from a &lt;code&gt;docker inspect&lt;/code&gt; of the container whose data you want to back up.&lt;/p&gt;
&lt;h3 id="example"&gt;Example&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s run through those steps with an example. I&amp;rsquo;m going to set up &lt;a href="https://uptime.kuma.pet/"&gt;Uptime Kuma&lt;/a&gt; in Docker. I&amp;rsquo;ll use the &lt;a href="https://github.com/louislam/uptime-kuma/blob/master/docker/docker-compose.yml"&gt;suggested compose file&lt;/a&gt; which creates a named volume &lt;code&gt;uptime-kuma&lt;/code&gt;. I tested that&amp;rsquo;s up and running on port 3001 - when I visited there, it wanted me to create an admin account.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-28-at-9.55.47-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-28-at-9.55.47-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For demo purposes, I created the admin user &lt;code&gt;ian&lt;/code&gt; and set up Uptime Kuma to monitor Google for us.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-28-at-10.39.40-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-28-at-10.39.40-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you started the app from a docker compose file, you can just look in there to see what the internal data directories that are being mounted to are:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;version: &amp;#39;3.8&amp;#39;

services:
 uptime-kuma:
 image: louislam/uptime-kuma:1
 container_name: uptime-kuma
 volumes:
 - uptime-kuma:/app/data
 ports:
 - &amp;#34;3001:3001&amp;#34; # &amp;lt;Host Port&amp;gt;:&amp;lt;Container Port&amp;gt;
 restart: always

volumes:
 uptime-kuma:
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;or alternatively use the &lt;code&gt;docker inspect &amp;lt;container name&amp;gt;&lt;/code&gt; command. You&amp;rsquo;ll get back a barrage of Json - somewhere in there will be the mount details:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;#34;Mounts&amp;#34;: [
 {
 &amp;#34;Type&amp;#34;: &amp;#34;volume&amp;#34;,
 &amp;#34;Name&amp;#34;: &amp;#34;uptimekuma_uptime-kuma&amp;#34;,
 &amp;#34;Source&amp;#34;: &amp;#34;/var/lib/docker/volumes/uptimekuma_uptime-kuma/_data&amp;#34;,
 &amp;#34;Destination&amp;#34;: &amp;#34;/app/data&amp;#34;,
 &amp;#34;Driver&amp;#34;: &amp;#34;local&amp;#34;,
 &amp;#34;Mode&amp;#34;: &amp;#34;z&amp;#34;,
 &amp;#34;RW&amp;#34;: true,
 &amp;#34;Propagation&amp;#34;: &amp;#34;&amp;#34;
 }
],
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Either way, we now know that the internal directory for data is &lt;code&gt;/app/data&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next stop the container with &lt;code&gt;docker stop uptime-kuma&lt;/code&gt;, then type in this bad boy based on the one in the docs.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo docker run --rm --volumes-from uptime-kuma -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /app/data
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The highlighted bits are the pieces I changed for our demo - the name of our container and the internal data directory for it that we found in the steps above. Pulling down an entire &lt;a href="https://hub.docker.com/_/ubuntu"&gt;Ubuntu container&lt;/a&gt; seemed overkill - we&amp;rsquo;re just running a tar command so perhaps &lt;a href="https://hub.docker.com/_/alpine"&gt;Alpine&lt;/a&gt; or &lt;a href="https://hub.docker.com/_/busybox"&gt;Busybox&lt;/a&gt; would be fine, however, it pulled down quite quickly so it&amp;rsquo;s either smaller that I imagined or I already had the main layers locally.&lt;/p&gt;
&lt;p&gt;Now if we look in the directory where we ran that command, there should be a &lt;code&gt;backup.tar&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-28-at-10.34.38-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-28-at-10.34.38-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now, for the purposes of this demo, I&amp;rsquo;ll copy the backup.tar (and my compose file) over to another VM and we&amp;rsquo;ll see if we can recreate this install.&lt;/p&gt;
&lt;p&gt;Once I&amp;rsquo;d copied them over and &lt;a href="https://docs.docker.com/engine/install/debian/"&gt;installed Docker&lt;/a&gt;, I ran &lt;code&gt;docker compose up&lt;/code&gt; to start a new, empty Uptime Kuma. As expected, when I tried to visit the main page, it wanted me to create an admin user. Then I stopped the container. Note that you don&amp;rsquo;t want to &lt;code&gt;docker compose down&lt;/code&gt; to stop the container since that also removed it. If it&amp;rsquo;s removed, the next command won&amp;rsquo;t be able to find the name volumes it uses.&lt;/p&gt;
&lt;p&gt;Now we need copy the backed up data (which is just sitting in the current directory) into the named volume. Once again, this will be achieved by creating a new container, mounting the named volume and and current external working directory.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo docker run --rm --volumes-from uptime-kuma -v $(pwd):/backup ubuntu bash -c &amp;#34;cd /app &amp;amp;&amp;amp; tar xvf /backup/backup.tar --strip 1&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once again, I&amp;rsquo;ve highlighted the bits I&amp;rsquo;ve changed from the &lt;a href="https://docs.docker.com/storage/volumes/#back-up-restore-or-migrate-data-volumes"&gt;instructions&lt;/a&gt;. It&amp;rsquo;s important to note I&amp;rsquo;ve changed the destination directory. We backed up from &lt;code&gt;/app/data&lt;/code&gt; but we&amp;rsquo;re just restoring to &lt;code&gt;/app&lt;/code&gt; - the un-taring will copy the backed up data into the existing data directory. That&amp;rsquo;s a trick for young players - when I blindly followed the official instructions, I ended up with an &lt;code&gt;/app/data/data&lt;/code&gt; directory with the backed info which was, or course, ignored, and only discoverable buy &lt;code&gt;exec&lt;/code&gt;-ing into the container to see what was happening.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-28-at-11.34.08-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-28-at-11.34.08-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="why-not-just-copy-the-local-file-system-version"&gt;Why not just copy the local file system version?&lt;/h3&gt;
&lt;p&gt;The named docker volume is just stored on our local file system, usually at &lt;code&gt;/var/lib/docker/volumes&lt;/code&gt; so it would be reasonable to wonder why we don&amp;rsquo;t just copy that. I don&amp;rsquo;t have a great explanation for why not. I assume since the &lt;a href="https://docs.docker.com/storage/volumes/#back-up-restore-or-migrate-data-volumes"&gt;official docs&lt;/a&gt; suggest something different and more complex that there must be a reason. Possibly there&amp;rsquo;s some extra Docker magic (file locks, caching, etc) going on we don&amp;rsquo;t know about, or there&amp;rsquo;s some planned for the future.&lt;/p&gt;</description></item><item><title>Ansible playbook to start Proxmox hosts</title><link>https://blog.iankulin.com/ansible-playbook-to-start-proxmox-hosts/</link><pubDate>Sun, 05 Nov 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/ansible-playbook-to-start-proxmox-hosts/</guid><description>&lt;img src="https://blog.iankulin.com/images/mick-jagger-start-me-up-video-the-rolling-stones-far-out-magazine-copy.jpg" width="683" alt=""&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/proxmox-tags-to-solve-a-problem/"&gt;In my last post&lt;/a&gt;, I talked about tagging guests in a Proxmox node so I could easily see which VMs and LXCs I needed to manually start before I ran an Ansible script to run all my &lt;code&gt;apt updates&lt;/code&gt;. It would have been reasonable to wonder why I didn&amp;rsquo;t just add things to my playbook to magically do that.&lt;/p&gt;
&lt;p&gt;The answer would be, I haven&amp;rsquo;t gotten around to it yet, so here goes:&lt;/p&gt;
&lt;h3 id="modules"&gt;Modules&lt;/h3&gt;
&lt;p&gt;You might remember we discussed that the various functionalities for Ansible are in &lt;em&gt;modules&lt;/em&gt;. The modules for starting Proxmox guests are &lt;code&gt;[community.general.proxmox_kvm](https://docs.ansible.com/ansible/2.9/modules/proxmox_kvm_module.html)&lt;/code&gt; for VMs, and &lt;code&gt;[community.general.proxmox](https://docs.ansible.com/ansible/2.9/modules/proxmox_module.html)&lt;/code&gt; for LXC containers. If you look at the documentation for either of those, you&amp;rsquo;ll see a couple of prerequisites: &lt;em&gt;proxmoxer&lt;/em&gt; and &lt;em&gt;requests&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-14-at-8.18.46-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-14-at-8.18.46-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;requests&lt;/em&gt; is a common Python library (Ansible is actually running Python on the machines it&amp;rsquo;s configuring) for HTTP requests. We can ignore it since (a) you probably already have it installed, and (b) if not, when we install &lt;em&gt;proxmoxer&lt;/em&gt;, it will be installed as a dependency. You&amp;rsquo;ve probably already guessed that &lt;em&gt;proxmoxer&lt;/em&gt; is the Python library for interacting with Proxmox through it&amp;rsquo;s API.&lt;/p&gt;
&lt;p&gt;So before we can start any of the guests, we need to ensure proxmoxer is installed:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; tasks:

 - name: Install proxmoxer
 apt:
 name: python3-proxmoxer
 state: latest
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="my-ansible-setup"&gt;My Ansible setup&lt;/h3&gt;
&lt;p&gt;It&amp;rsquo;s probably worth going over how my Ansible is set up so you can make sense of the rest of this without going back to read earlier posts. In the directory where I&amp;rsquo;m running this playbook, I have an &lt;code&gt;ansible.cfg&lt;/code&gt; file. Here&amp;rsquo;s the entire contents:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[defaults]
INVENTORY = hosts
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It&amp;rsquo;s an INI type file, and in this case it&amp;rsquo;s just saying if I don&amp;rsquo;t specify the name of an inventory file (a list of all my machines and their IP addresses or names), then use the file named &amp;lsquo;hosts&amp;rsquo;. This just saves me specifying the inventory file at the command line with the flag &lt;code&gt;-i&lt;/code&gt; each time.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;hosts&lt;/code&gt; file looks a bit like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[pve_dev1]
pve-dev1
#192.168.100.28

[pve_dev1:vars]
ansible_user=&amp;#39;{{pve_dev1_user}}&amp;#39;
ansible_become_password=&amp;#39;{{pve_dev1_become_pass}}&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There&amp;rsquo;s a couple of these entries for every &amp;lsquo;machine&amp;rsquo; that I manage. The first bit just gives the address for the machine, and the second the variables for that machine - a sudo user and their password. You could just type those entries in here like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[pve_dev1]
pve-dev1
#192.168.100.28

[pve_dev1:vars]
ansible_user=root
ansible_become_password=password1234
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Instead of putting my credentials in a text file that&amp;rsquo;s pushed up to github, I use another file called a &amp;lsquo;vault&amp;rsquo; which is encrypted to keep them in. I&amp;rsquo;ve explained about &lt;a href="https://blog.iankulin.com/ansible-with-secrets/"&gt;that elsewhere,&lt;/a&gt; but to understand what&amp;rsquo;s going on here, you just need to know that &lt;code&gt;'{{pve_dev1_user}}'&lt;/code&gt; gets resolved to &lt;code&gt;root&lt;/code&gt; when the playbook is run.&lt;/p&gt;
&lt;p&gt;You might also be wondering about the IP address that&amp;rsquo;s commented out in the snippets above. I am using the Tailscale MagicDNS on my machines, so I can just refer to this dev Proxmox instance as &lt;code&gt;pve-dev1&lt;/code&gt;, but yours is probably setup with IP address instead- in which case use that:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[pve_dev1]
192.168.100.28

[pve_dev1:vars]
ansible_user=root
ansible_become_password=password1234
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So now the name being used in Ansible is pve_dev1, but it&amp;rsquo;s referring to the machine at 192.168.100.28&lt;/p&gt;
&lt;h3 id="starting-a-proxmox-vm"&gt;Starting a Proxmox VM&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; - name: start vm321-deb
 community.general.proxmox_kvm:
 api_user : root@pam
 api_password: &amp;#39;{{pve_dev1_become_pass}}&amp;#39;
 api_host : pve-dev1
 name : vm321-deb
 state : started
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The api_host is the address of the node, and the user and password above it are the same ones you use to log into the web gui of this Proxmox server. name is the you gave the VM in Proxmox when you created it. Note that this is for a stand-alone Proxmox server, not a node that&amp;rsquo;s part of a cluster. If we had a cluster called &amp;lsquo;mycluster&amp;rsquo; and the server/node that vm321-deb was hosted on was called &amp;rsquo;node2&amp;rsquo; the Ansible entry for it would be:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; - name: start vm321-deb
 community.general.proxmox_kvm:
 api_user : root@pam
 api_password: &amp;#39;{{pve_dev1_become_pass}}&amp;#39;
 api_host : mycluster
 node : node2
 name : vm321-deb
 state : started
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="starting-an-lxc-container"&gt;Starting an LXC container&lt;/h3&gt;
&lt;p&gt;Increasingly, I run services in their own LXC container. They are quick to create and start, use less resources, but can still be snapshot-ed for easy backups.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; - name: start ct351-go
 community.general.proxmox:
 api_user : root@pam
 api_password: &amp;#39;{{pve_dev1_become_pass}}&amp;#39;
 api_host : pve-dev1
 vmid : 351
 state : started
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So for these containers, we use a different module, and call them by their VMID instead of name.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the full playbook.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Start pve-dev hosts for updating&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# ansible-playbook start-apt-dev-vms.yaml --ask-vault-pass &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;vars_files&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;./vault.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;hosts&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;pve-dev1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;become&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;tasks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Install proxmoxer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;apt&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;python3-proxmoxer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;start babybuntu&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;community.general.proxmox_kvm&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_user &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;root@pam&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_password&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;{{pve_dev1_become_pass}}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_host &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;pve-dev1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;babybuntu&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;started&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;start vm321-deb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;community.general.proxmox_kvm&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_user &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;root@pam&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_password&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;{{pve_dev1_become_pass}}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_host &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;pve-dev1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;vm321-deb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;started&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;start vm322-deb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;community.general.proxmox_kvm&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_user &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;root@pam&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_password&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;{{pve_dev1_become_pass}}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_host &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;pve-dev1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;vm322-deb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;started&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;start vm323-deb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;community.general.proxmox_kvm&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_user &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;root@pam&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_password&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;{{pve_dev1_become_pass}}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_host &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;pve-dev1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;vm323-deb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;started&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;start ct351-go&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;community.general.proxmox&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_user &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;root@pam&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_password&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;{{pve_dev1_become_pass}}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_host &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;pve-dev1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;vmid &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;351&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;started&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;start ct353-omada&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;community.general.proxmox&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_user &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;root@pam&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_password&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;{{pve_dev1_become_pass}}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_host &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;pve-dev1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;vmid &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;353&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;started&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;start ct356-proxy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;community.general.proxmox&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_user &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;root@pam&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_password&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;{{pve_dev1_become_pass}}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;api_host &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;pve-dev1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;vmid &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;356&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state &lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;started&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Proxmox tags to solve a problem</title><link>https://blog.iankulin.com/proxmox-tags-to-solve-a-problem/</link><pubDate>Thu, 02 Nov 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/proxmox-tags-to-solve-a-problem/</guid><description>&lt;p&gt;Each weekend I run an Ansible script that updates all my apt based VMs and containers. For the production machines, that&amp;rsquo;s everything, but my dev Proxmox is full of half-finished projects. Some of these have IP addresses reserved and are in the Ansible hosts file (because whatever service they are running is almost ready to move to the production server) others do not.&lt;/p&gt;
&lt;p&gt;Long story short, the dev server has some containers and VM&amp;rsquo;s that need turned on before I run the updates, and some that don&amp;rsquo;t. I could just start them all up, for the ten minutes the updates usually take, but that seems wasteful somehow. If there was only some way to mark the ones I need to turn on in the Proxmox webgui! Well, there is. We can add tags to machines in Proxmox.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-14-at-11.23.57-am-copy.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-14-at-11.23.57-am-copy.png" width="512" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Proxmox has quite a &lt;a href="https://pve.proxmox.com/pve-docs/pve-admin-guide.html#_tags"&gt;comprehensive tagging system&lt;/a&gt; - there are different display formats, and tags can be limited to a specific set, or completely free form. Also, there&amp;rsquo;s a heap of command line tools to work with them. For this job, I don&amp;rsquo;t really need much of that stuff - I just want to click a few things in the web gui to mark some of my VM&amp;rsquo;s with a coloured marker so I know which ones to start when I&amp;rsquo;m going to run my updates.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the steps.&lt;/p&gt;
&lt;p&gt;Go into &lt;code&gt;DataCenter | Options&lt;/code&gt;. One of the options is &lt;code&gt;Tag Style Override&lt;/code&gt;. It&amp;rsquo;s called &amp;ldquo;Override&amp;rdquo; because by default, the colours are deterministically figured out from the tag text. I want to just have a nice dark blue associated with the tag &lt;code&gt;apt&lt;/code&gt;, so I&amp;rsquo;m going to set it. It turns out I could have just skipped this step and got a nice light blue for &lt;code&gt;apt&lt;/code&gt;. This system (of just figuring out a colour from the text) means in most cases you can completely skip this step. Each machine you tag with a particular tag will be marked with the same colour - it will just work. &lt;code&gt;test&lt;/code&gt; = pink, &lt;code&gt;fred&lt;/code&gt; = green, and so on.&lt;/p&gt;
&lt;p&gt;Back to me being fussy. Opening up the &lt;code&gt;Tag Style Override&lt;/code&gt; I&amp;rsquo;m setting apt to be dark blue with white text.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-14-at-2.58.00-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-14-at-2.58.00-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To apply these tags, you just click on the machine you want to tag, then notice that up the top of the web gui, next to the machine name, it says &amp;ldquo;No Tags&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-14-at-3.07.11-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-14-at-3.07.11-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You just click on the pencil, and enter the tag name. If you haven&amp;rsquo;t changed any of the other defaults, a coloured circle will appear next to the machine in the server view.&lt;/p&gt;
&lt;p&gt;There are three display options for the tags - &amp;ldquo;full&amp;rdquo; which is a coloured bar including the text of the tag, &amp;ldquo;circle&amp;rdquo; which is the one shown in the first screenshot above, and &amp;ldquo;dense&amp;rdquo; which is a small rectangular bar - designed for stacking several different tags against each machine. All these options are under &amp;ldquo;tree shape&amp;rdquo; in the &lt;code&gt;Tag Color Override&lt;/code&gt; dialogue we opened earlier.&lt;/p&gt;
&lt;p&gt;As well as being able to see the tag blobs in the tree view, if you look at all your machines on the &lt;code&gt;Datacenter | Search&lt;/code&gt; view, it&amp;rsquo;s possible to sort by tags - which will even further simplify the job for me of starting them all up before I run the updates.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-14-at-3.35.21-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-14-at-3.35.21-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>apt update - BADSIG 871920D1991BC93C</title><link>https://blog.iankulin.com/apt-update-badsig-871920d1991bc93c/</link><pubDate>Mon, 30 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/apt-update-badsig-871920d1991bc93c/</guid><description>&lt;p&gt;I have an ansible script that runs each weekend which basically does an &lt;code&gt;apt update &amp;amp;&amp;amp; apt upgrade -Y&lt;/code&gt; on every Debian based instance. This weekend it failed on one Ubuntu host. When I went it to try it manually, this was the output:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Hit:1 http://au.archive.ubuntu.com/ubuntu jammy InRelease
Hit:2 https://download.docker.com/linux/ubuntu jammy InRelease 
Hit:3 http://au.archive.ubuntu.com/ubuntu jammy-backports InRelease 
Hit:4 http://au.archive.ubuntu.com/ubuntu jammy-security InRelease 
Get:5 http://au.archive.ubuntu.com/ubuntu jammy-updates InRelease [119 kB] 
Err:5 http://au.archive.ubuntu.com/ubuntu jammy-updates InRelease 
 The following signatures were invalid: BADSIG 871920D1991BC93C Ubuntu Archive Automatic Signing Key (2018) &amp;lt;ftpmaster@ubuntu.com&amp;gt;
Get:6 https://pkgs.tailscale.com/stable/ubuntu jammy InRelease
Fetched 125 kB in 1s (125 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
11 packages can be upgraded. Run &amp;#39;apt list --upgradable&amp;#39; to see them.
W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: http://au.archive.ubuntu.com/ubuntu jammy-updates InRelease: The following signatures were invalid: BADSIG 871920D1991BC93C Ubuntu Archive Automatic Signing Key (2018) &amp;lt;ftpmaster@ubuntu.com&amp;gt;
W: Failed to fetch http://au.archive.ubuntu.com/ubuntu/dists/jammy-updates/InRelease The following signatures were invalid: BADSIG 871920D1991BC93C Ubuntu Archive Automatic Signing Key (2018) &amp;lt;ftpmaster@ubuntu.com&amp;gt;
W: Some index files failed to download. They have been ignored, or old ones used instead.
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="solved"&gt;Solved&lt;/h3&gt;
&lt;p&gt;The first &lt;a href="https://ubuntuforums.org/showthread.php?t=2484710"&gt;google result&lt;/a&gt; mentions apt-cache - which &lt;a href="https://blog.iankulin.com/caching-apt-updates/"&gt;I also run&lt;/a&gt;, so a first level debug step is to delete the &lt;code&gt;/etc/apt/apt.conf.d/00aptproxy&lt;/code&gt; file that redirects apt requests to the cache I run in an LXC container. After that, if I re-run the &lt;code&gt;apt update&lt;/code&gt; it works perfectly. Seems like a problem with the cache then. I&amp;rsquo;m not sure why it would only affect this host though - I have other Ubuntu VM&amp;rsquo;s in the fleet that are not getting the original error.&lt;/p&gt;
&lt;p&gt;In any case, adding the conf back to force the server to use the cache made the error reappear - so it&amp;rsquo;s definitely related to the cache. With any type of cache, when there&amp;rsquo;s a problem related to it, deleting the contents is usually a &amp;ldquo;plan A&amp;rdquo; response. Assuming there&amp;rsquo;s some mechanism in &lt;a href="https://wiki.debian.org/AptCacherNg"&gt;Apt Cacher NG&lt;/a&gt; to do this, I went to the little stats/config webpage it serves up.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-08-at-8.41.44-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Well &amp;ldquo;Force the download of index files&amp;rdquo; sounds promising, let&amp;rsquo;s try that.&lt;/p&gt;
&lt;p&gt;I ticked the box for Force the download of index files (even having fresh ones), but it wasn&amp;rsquo;t clear to me how to make that change stick. The first button I could click further down the page was &amp;ldquo;Start Scan&amp;rdquo; which was related to some different checkboxes. I tried it anyway, but it didn&amp;rsquo;t force the downloading of index files. Time for some command line comandoing.&lt;/p&gt;
&lt;p&gt;The cache files for &lt;code&gt;aptcacher-ng&lt;/code&gt; are in &lt;code&gt;/var/cache/apt-cacher-ng/&lt;/code&gt; each distro has a directory in there.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-08-at-9.08.48-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Guessing the Ubuntu repository cache is probably stored in &lt;code&gt;uburep&lt;/code&gt;, I deleted that with &lt;code&gt;rm -R /var/cache/apt-cacher-ng/uburep&lt;/code&gt;. When I retried the &lt;code&gt;apt update&lt;/code&gt;, it worked perfectly, and I could see that the &lt;code&gt;/var/cache/apt-cacher-ng/uburep&lt;/code&gt; directory had re-appeared.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the immediate problem fixed. The cause of this problem is unclear. Presumably it related to a package running on this Ubuntu machine (runs docker with a couple of small services) that is not running on my other Ubuntu hosts. It probably falls into the category of &amp;ldquo;don&amp;rsquo;t worry about unless it crops up again&amp;rdquo;.&lt;/p&gt;</description></item><item><title>Tailscale keys expire</title><link>https://blog.iankulin.com/tailscale-keys-expire/</link><pubDate>Tue, 24 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/tailscale-keys-expire/</guid><description>&lt;p&gt;I have an &lt;a href="https://blog.iankulin.com/ansible-with-secrets/"&gt;Ansible playbook&lt;/a&gt; I run each weekend to do all the &lt;code&gt;apt&lt;/code&gt; updates. As well as keeping everything up to date, it&amp;rsquo;s a good check-in that everything&amp;rsquo;s alive and working as expected. I have Uptime Kuma checking the services are alive, and that no one is running out of disk or memory so there shouldn&amp;rsquo;t be any drama right?&lt;/p&gt;
&lt;p&gt;This weekend, three instances (two remote, one local) timed out with &amp;ldquo;unreachable&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-09-30-at-2.53.24-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-09-30-at-2.53.24-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Since Ansible is effectively ssh-ing in, I guess try that from the terminal.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-09-30-at-2.58.01-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vm100-dockhost&lt;/code&gt; is the &amp;ldquo;magic DNS&amp;rdquo; name for this machine. One of the cool things Tailscale does is to allow these sorts of names. I use them so much, I&amp;rsquo;ve forgotten all their IP addressees. When I look it up and try with the local IP address for this machine, it works fine.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/itcrowd.jpg" width="872" alt=""&gt;
&lt;p&gt;Since it seems like a Tailscale problem, I tried turning it off and on again with &lt;code&gt;sudo tailscale down&lt;/code&gt; and &lt;code&gt;sudo tailscale up&lt;/code&gt;. When it came up, it printed the URL to re-authenticate - so something&amp;rsquo;s happened&amp;hellip;&lt;/p&gt;
&lt;p&gt;It turns out that &lt;a href="https://tailscale.com/kb/1028/key-expiry/"&gt;Tailscale keys expire&lt;/a&gt; for security reasons - by default every 180 days. Once the key is expired, you can&amp;rsquo;t access that machine via the Tailnet. Obviously, this is going to make an issue if you have a remote site and the key expires. So how can we prevent it from happening?&lt;/p&gt;
&lt;p&gt;My first idea was to use the Tailscale CLI to do the re-authentication on each machine &lt;em&gt;before&lt;/em&gt; it expires. And handily, there is a command for this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;tailscale up --force-reauth
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But, small catch (mentioned in the &lt;a href="https://tailscale.com/kb/1028/key-expiry/"&gt;docs&lt;/a&gt;, or in the CLI if you try it) if you are ssh&amp;rsquo;d in over Tailscale, when you run this, it actually drops the ssh link. So you&amp;rsquo;ll never see the URL you need to re-authorise, so now you&amp;rsquo;ve lost access to that machine.&lt;/p&gt;
&lt;p&gt;If a key has expired, it is possible to remotely reauthorise it from your &lt;a href="https://login.tailscale.com/admin/machines"&gt;machines admin page&lt;/a&gt; for a short period it to allow someone with local access to reauthorise it properly. If you don&amp;rsquo;t have local access to it, you&amp;rsquo;re in trouble if you discover this after it&amp;rsquo;s expired. I guess it would be possible to write a script to run the &lt;code&gt;tailscale up&lt;/code&gt; on the remote machine, capture the output and send it to me, but that&amp;rsquo;s starting to sound like more work than I want to do.&lt;/p&gt;
&lt;h3 id="avoiding-the-problem"&gt;Avoiding the problem&lt;/h3&gt;
&lt;p&gt;If you want to avoid the problem of Tailscale keys expiring on remote systems, it&amp;rsquo;s possible to turn it off so they never expire. This option is in the menu for each machine on the &lt;a href="https://login.tailscale.com/admin/machines"&gt;machines admin page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-01-at-4.38.33-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-01-at-4.38.33-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I guess another way of avoiding this problem, if it&amp;rsquo;s possible, would be to visit your remote sites every six months and do the force update to reset the expiry. For my setup of the remote backup sites that&amp;rsquo;s a reasonable plan.&lt;/p&gt;
&lt;p&gt;One slightly annoying thing is that it&amp;rsquo;s not easy to see the expiry date of each Tailscale instance. I would have thought it would appear on that machines admin page, or in the CLI with &lt;code&gt;tailscale status&lt;/code&gt;. When I was searching for an answer, I see that there is an &lt;a href="https://github.com/tailscale/tailscale/issues/4854"&gt;open github issue&lt;/a&gt; for it, and there&amp;rsquo;s been an update to the JSON version of the &lt;code&gt;tailscale status&lt;/code&gt; command that includes the key expiry date.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-01-at-5.33.54-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-01-at-5.33.54-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Getting Tailscale working in LXC containers</title><link>https://blog.iankulin.com/getting-tailscale-working-in-lxc-containers/</link><pubDate>Wed, 18 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/getting-tailscale-working-in-lxc-containers/</guid><description>&lt;p&gt;I&amp;rsquo;ve taken to running lots of my services in LXC containers under Proxmox. I like the feeling of installing in a VM, but it&amp;rsquo;s lightweight. I like the backups, I like things being isolated from each other, I like moving them around between machines easily. I&amp;rsquo;m just a big LXC lover at the moment.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also a Tailscale lover, and the generous number of nodes in the free tier means I now just routinely install them in my VMs and containers without a thought.&lt;/p&gt;
&lt;p&gt;There is an issue with unprivileged LXC containers and Tailscale though. Unprivileged containers have less access to the host system&amp;rsquo;s internals, and are therefore a bit safer, but part of that reduced access includes some of the networking stuff that Tailscale needs. If you try to install Tailscale, it will look fine, until you get to the &lt;code&gt;tailscale up&lt;/code&gt; command, at which point it will say something like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;failed to connect to local tailscaled (which appears to be running as tailscaled, pid 3121). Got error: 503 Service Unavailable: no backend
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There is an easy way to fix this, documented in a &lt;a href="https://tailscale.com/kb/1130/lxc-unprivileged/"&gt;Tailscale how to guide&lt;/a&gt;. Basically you need to stop the container and edit the LXC conf file. These are named by the container number. My container is 354, so the conf file is &lt;code&gt;/etc/pve/lxc/354.conf&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Add the lines:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-09-19-at-8.01.13-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;This creates a TUN/TAP device (commonly used for VM networking) and creates a bind point to it inside the container. The effect of this is to enable the container to work with TUN/TAP devices and use them for networking purposes. This can be essential for various networking-related applications or services running within the container - including, in this case, Tailscale.&lt;/p&gt;
&lt;p&gt;Start the container again, redo your &lt;code&gt;tailscale up&lt;/code&gt;, and you should be in business.&lt;/p&gt;</description></item><item><title>Certbot - adding more virtual hosts</title><link>https://blog.iankulin.com/certbot-adding-more-virtual-hosts/</link><pubDate>Sun, 15 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/certbot-adding-more-virtual-hosts/</guid><description>&lt;p&gt;I&amp;rsquo;ve got a domain that&amp;rsquo;s not currently used, so I&amp;rsquo;m going to set it up as a virtual host under NGINX. This server is already serving two domains set up with Certbot for SSL. Is it going to be possible to add another site and have Certbot manage the certificates for it after I&amp;rsquo;ve run Certbot once?&lt;/p&gt;
&lt;p&gt;When I googled around to find out, I didn&amp;rsquo;t find anything - which is usually a sign I&amp;rsquo;m either asking a wrong question, or it&amp;rsquo;s so little drama that no one ever mentions it. I decided just to move the site, check it was all working for the http version, then run Certbot and see what it said.&lt;/p&gt;
&lt;p&gt;Since I already had Certbot installed, I just ran &lt;code&gt;sudo certbot --nginx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-09-03-at-10.03.19-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-09-03-at-10.03.19-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s probably worth explaining at this point that Certbot does not obtain separate certificates for each domain (which is what I&amp;rsquo;d been doing when I was doing this manually), but instead grabs a single certificate that includes all the domains, and stores it under the the first domain - in the case above, for agnet.&lt;/p&gt;
&lt;p&gt;I hit &amp;ldquo;E&amp;rdquo; for Expand, and Certbot did it&amp;rsquo;s thing by acquiring the new certificate expanded to cover the new domain and installed it. No drama.&lt;/p&gt;
&lt;h3 id="what-if-you-already-have-a-certificate-from-another-provider"&gt;What if you already have a certificate from another provider?&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve got two more domains to move from another server, but both of these already have active SSL certificates that I obtained via Porkbun. Is that going to be a problem? Can Let&amp;rsquo;s Encrypt (who actually does the certificates for Porkbun) include these sites on the combined certificate on my main VPS so I can use Certbot to maintain them? Let&amp;rsquo;s see.&lt;/p&gt;
&lt;p&gt;I went through the same routine - created a nginx conf for the virtual host in &lt;code&gt;/etc/nginx/sites-available/&lt;/code&gt;, created a simple index.html in &lt;code&gt;/var/www/drysea.xyz&lt;/code&gt; and then symlinked the conf file into &lt;code&gt;/etc/nginx/sites-enabled&lt;/code&gt;. Then changed the A records for the DNS to point to the server address and waited for them to propagate so I could test the http version of the site.&lt;/p&gt;
&lt;p&gt;After that, I ran the sudo certbot &amp;ndash;nginx command again, and exactly as before, it asked if I wanted to expand the existing certificate. I did that, and the site can now be visited securely with no warning about the incorrect certificate. So that&amp;rsquo;s all worked well.&lt;/p&gt;
&lt;p&gt;It is allowable for a site to have more than one active, valid SSL certificate. This often happens in the exact scenario we&amp;rsquo;ve got here where domains are being moved around. There is a security implication for this though. A &lt;a href="https://www.csoonline.com/article/561111/dns-record-will-help-prevent-unauthorized-ssl-certificates.html"&gt;system&lt;/a&gt; of entering a particular DNS record that would prevent certificates being issued by all but one particular certificate authority exists, but is not widely used.&lt;/p&gt;
&lt;p&gt;It is probably a good idea for my to change my configuration on Porkbun to stop it from going on generating certificates that are not needed though, so I&amp;rsquo;ll go ahead and revoke that.&lt;/p&gt;</description></item><item><title>BOINC in an LXC container</title><link>https://blog.iankulin.com/boinc-in-an-lxc-container/</link><pubDate>Mon, 09 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/boinc-in-an-lxc-container/</guid><description>&lt;img src="https://blog.iankulin.com/images/boinc_logo.png" width="900" alt=""&gt;
&lt;p&gt;Years ago, I was very keen on the &lt;a href="https://youtu.be/WwxTc6pFOcU"&gt;SETI@home&lt;/a&gt; project that used a distributed computing model whereby packets of digitized received radio data were farmed out to individuals&amp;rsquo; computers to be processed to look for any unusual signals that could potentially be from an intelligent extra-terrestrial source.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s long since defunct, but the idea lives on with &lt;a href="https://boinc.berkeley.edu/"&gt;BOINC&lt;/a&gt; - a system run out of Berkley that allows different science organisations to offer projects to run on individuals&amp;rsquo; computers.&lt;/p&gt;
&lt;p&gt;I thought that figuring out how to get all that running in an LXC container would make a good blog post, and wasted about a day fiddling around with it, with limited success. I forget the exact details, but I think the projects I&amp;rsquo;d subscribed to via the &lt;a href="https://www.worldcommunitygrid.org/"&gt;World Community Grid&lt;/a&gt; might have wanted serious GPU power which my container does not have - but I wasn&amp;rsquo;t 100% sure I&amp;rsquo;d set everything up correctly. There was so many fiddly variables I wasn&amp;rsquo;t confident to commit to posting about it.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s my custom on the weekends to turn all my nodes on, and start every VM and container, even the testing ones on the dev node, then run the &lt;a href="https://blog.iankulin.com/tags/ansible/"&gt;Ansible playbook&lt;/a&gt; to do all of the &lt;code&gt;apt&lt;/code&gt; updates. When I did that today, I noticed this CPU pulsing:&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/WwxTc6pFOcU?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;Well, that seems like it&amp;rsquo;s doing some serious work. Either I&amp;rsquo;ve been hacked and someone&amp;rsquo;s mining crypto, or BOINC is working.&lt;/p&gt;
&lt;p&gt;Each of the organisations enrolled in BOINC have a community page where you sign up and get an API key that identifies your computers to the project, and you can head there to see your contributions. Sure enough, I&amp;rsquo;ve been receiving, processing and returning packets.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-27-at-7.12.05-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-27-at-7.12.05-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is another thing I&amp;rsquo;d like to return to later - I don&amp;rsquo;t think it was as simple as following the &lt;a href="https://boinc.berkeley.edu/wiki/Installing_BOINC_on_Debian_or_Ubuntu"&gt;instructions&lt;/a&gt; because I&amp;rsquo;d made my life a bit more complicated by running it in an LXC. It also occurs to me that this might be a good workload to use an orchestration tool like Kubernetes for - since I don&amp;rsquo;t really have any actual need (excuse) to play with those.&lt;/p&gt;</description></item><item><title>Solved DNS Issues - Proxmox, LXC, Ubuntu, Tailscale</title><link>https://blog.iankulin.com/solved-dns-issues-proxmox-lxc-ubuntu-tailscale/</link><pubDate>Fri, 06 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/solved-dns-issues-proxmox-lxc-ubuntu-tailscale/</guid><description>&lt;p&gt;&lt;a href="https://i.imgur.com/WmRbmf5.png"&gt;&lt;img src="https://blog.iankulin.com/images/wmrbmf5.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve picked up an new TP-Link WAP with Omada, so I wanted to spin up an Ubuntu 20.04 LXC to run the controller software in, and ended up spending a couple of hours figuring out why things where not working.&lt;/p&gt;
&lt;p&gt;The initial problem was I was having connectivity issues pulling down the updates for all the packages required. I went down a bit of a tangent because I installed an apt cache the other day, so I was looking for problems there. Eventually I narrowed it down to DNS not working and started A/B testing like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-26-at-4.49.24-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;A more seasoned sysadmin probably would have been looking at the &lt;code&gt;/etc/resolv.conf&lt;/code&gt; a bit earlier where the glaring hint was. I&amp;rsquo;ll get to that in a second, but first a bit about my setup.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m running Proxmox 8.0.4 on one of my HP G2 800 Minis (love these little power-frugal &lt;a href="https://blog.iankulin.com/moving-a-vm-between-two-proxmox-hosts/"&gt;gems&lt;/a&gt;) and I use Tailscale to tie all my network (my homelab here, and two remote locations) together. The Tailscale version on this node is 1.48.1&lt;/p&gt;
&lt;p&gt;You can see in the table above, that a LXC using the Ubuntu 20.04 template had no domain name resolution, but the Debian 12 (and Debian 11 I tried earlier did). The &lt;code&gt;/etc/resolv.conf&lt;/code&gt; on the Debian containers looked like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;nameserver 192.168.100.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And on the Ubuntu container&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# --- BEGIN PVE ---
search tailaf96a.ts.net
nameserver 100.100.100.100
# --- END PVE ---
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;192.168.100.1&lt;/code&gt; is my local DNS which is provided from the DHCP, but clearly Ubuntu is not using that. The &lt;code&gt;PVE&lt;/code&gt; comments tells me it&amp;rsquo;s Proxmox messing with my container, and that&amp;rsquo;s the Tailscale DNS server number in there. The container does not have a route to &lt;code&gt;100.100.100.100&lt;/code&gt; so that DNS is not going to be able to resolved anything.&lt;/p&gt;
&lt;p&gt;So, that&amp;rsquo;s a bit weird, but easily fixed by just editing this back to set the nameserver to &lt;code&gt;192.160.100.1&lt;/code&gt; right? Well, yes - if you do that, it works, but then as soon as the container is rebooted, the Tailnet DNS gets written back in. Those blocky PVE comments are probably part of the automated system for doing that. So, what&amp;rsquo;s going on here?&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s two screens for network configuration when you&amp;rsquo;re creating an &lt;a href="https://en.wikipedia.org/wiki/RAS_syndrome"&gt;LXC container&lt;/a&gt; in the Proxmox GUI.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-26-at-4.55.54-pm-1.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-26-at-4.56.03-pm-1.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s no option in the GUI to just say &lt;em&gt;&amp;ldquo;Use the DNS settings provided by the DHCP server&amp;rdquo;&lt;/em&gt;, although we&amp;rsquo;ll see later, there is a work around for this.&lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;d been leaving the &lt;code&gt;DNS domain:&lt;/code&gt; set to &lt;code&gt;use host settings&lt;/code&gt;. You might reasonably wonder what the Proxmox node /etc/resolv.conf looks like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# resolv.conf(5) file generated by tailscale
# For more info, see https://tailscale.com/s/resolvconf-overwrite
# DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN

nameserver 100.100.100.100
search tailaf96a.ts.net local
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So actually, although I was thinking there must be some bug with Ubuntu since Debian was working how I expected, it&amp;rsquo;s the other way around - Ubuntu and Proxmox are working together to do exactly what the settings have told it to - to use the host settings. And actually, the Debian containers are not working correctly (although they were working how I expected). The process of Proxmox making these types of changes is documented in the &lt;a href="https://pve.proxmox.com/pve-docs/pve-admin-guide.html#_guest_operating_system_configuration"&gt;Admin Guide&lt;/a&gt;. I&amp;rsquo;d actually never seen that guide till today (although there is a large &amp;ldquo;Documentation&amp;rdquo; button in the top right of the web GUI), but it looks pretty great so I&amp;rsquo;ll be revisiting it.&lt;/p&gt;
&lt;h3 id="solution-1"&gt;Solution 1&lt;/h3&gt;
&lt;p&gt;The first solution is just to specify the DNS address in the GUI - then our container works exactly as the PVE developers intended. A slight downside is that if I change the network configuration in future and update the DNS address in the DHCP server (which is the logical way to do that) then it won&amp;rsquo;t update for this container and domain name resolution will stop working for it.&lt;/p&gt;
&lt;p&gt;If I do that, the &lt;code&gt;/etc/resolv.conf&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# --- BEGIN PVE ---
search tailaf96a.ts.net
nameserver 192.168.100.1
# --- END PVE ---
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And it all works fine.&lt;/p&gt;
&lt;h3 id="solution-2"&gt;Solution 2&lt;/h3&gt;
&lt;p&gt;This &lt;a href="https://forum.proxmox.com/threads/lxc-dns-from-dhcp.36200/"&gt;post on the Proxmox Forums&lt;/a&gt; lead me to a second solution. It&amp;rsquo;s possible to stop Proxmox from adding the host by adding a little signal file with&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;touch /etc/.pve-ignore.resolv.conf
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When Proxmox sees that. it won&amp;rsquo;t mess with the &lt;code&gt;/etc/resolv.conf&lt;/code&gt; file, so if that&amp;rsquo;s been edited to:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;nameserver 192.168.100.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It will be left alone, and things will work fine. This is not quite what I&amp;rsquo;d like - I&amp;rsquo;d really prefer it picks everything up from DHCP, but I don&amp;rsquo;t know enough about how that works in Linux to fix it, yet.&lt;/p&gt;</description></item><item><title>Caching APT updates</title><link>https://blog.iankulin.com/caching-apt-updates/</link><pubDate>Tue, 03 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/caching-apt-updates/</guid><description>&lt;p&gt;It&amp;rsquo;s bothered me for a while that all these VM&amp;rsquo;s are pulling down a lot of the same updates. As well as needlessly using some bandwidth, I&amp;rsquo;m hammering the update servers (that I don&amp;rsquo;t pay for) with the same requests over and over. I did briefly consider running my own mirror, but that&amp;rsquo;s not simple, plus I&amp;rsquo;d then be mirroring a heap of files in a complete repository that I&amp;rsquo;d never use. What I really needed was some sort of cache so once I&amp;rsquo;ll pulled down an update, it would hang around for a few days being available to other machines on the local network. Luckily, that exact thing exists - &lt;a href="https://www.unix-ag.uni-kl.de/~bloch/acng/html/index.html"&gt;APT Cacher NG&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It works pretty much as described above - all of the machines on the LAN have their APT calls proxied through a little server. If the server doesn&amp;rsquo;t have a copy of the appropriate package, it pulls it down and delivers it. If it&amp;rsquo;s got a good copy already, it just provides that.&lt;/p&gt;
&lt;h3 id="installing-the-server"&gt;Installing the server&lt;/h3&gt;
&lt;p&gt;I decided an unprivileged LXC container would be the perfect base for this service. I created one from the Debian 12 image with 1MB RAM but a largish 30GB drive. I don&amp;rsquo;t really have any feel for how big the cache will get under normal use so I erred on the large side. It&amp;rsquo;s not doing any computationally expensive work, so one CPU is plenty.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-20-at-10.42.06-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-20-at-10.42.06-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then we just install it, and start and enable it as a service.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apt install apt-cacher-ng
systemctl start apt-cacher-ng
systemctl enable apt-cacher-ng
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;During the install, it asked me about https:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-20-at-10.46.32-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-20-at-10.46.32-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I said no, but then to enable it, I had to (after the installation) edit the config file at &lt;code&gt;/etc/apt-cacher-ng/acng.conf&lt;/code&gt; to uncomment the line&lt;/p&gt;
&lt;p&gt;&lt;code&gt;PassThroughPattern: .* # this would allow CONNECT to everything&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;With that done, and the service restarted, we&amp;rsquo;re now serving proxies at localhost:3142, and also a little web page with some advice.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-20-at-2.37.46-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;h3 id="installing-the-client"&gt;Installing the client&lt;/h3&gt;
&lt;p&gt;The two bits of information I&amp;rsquo;ve put red boxes around are the things we need to do to enable &lt;code&gt;apt&lt;/code&gt; on the client machines to use the cache. We need to create a file called &lt;code&gt;/etc/apt/apt.conf.d/00aptproxy&lt;/code&gt; and add the single line to it of:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Acquire::http::Proxy &amp;quot;http://192.168.100.37:3142&amp;quot;;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Note that the ip address will be different on yours, just copy it off the little web page. Since I&amp;rsquo;ve got a heap of machines to do this do, I made the &lt;code&gt;conf&lt;/code&gt; file once and pushed out out with Ansible.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-20-at-3.00.26-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-20-at-3.00.26-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;hosts: local&lt;/code&gt; in the pkaybook refers to the &lt;code&gt;local: children&lt;/code&gt; group in my hosts ini file.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-20-at-3.14.24-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;h3 id="statistics"&gt;Statistics&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;re curious to see what the savings are, there&amp;rsquo;s another web page served by the cache at &lt;code&gt;&amp;lt;server ip&amp;gt;:3142/acng-report.html&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-20-at-3.16.27-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Further down on that page are some options that can be changed as well.&lt;/p&gt;
&lt;h3 id="resources"&gt;Resources&lt;/h3&gt;
&lt;p&gt;I learned most of this from &lt;a href="https://www.youtube.com/watch?v=t8kI4YwdvRA"&gt;this video by RickMakes&lt;/a&gt;, and &lt;a href="https://www.linuxhelp.com/how-to-set-up-apt-caching-server-using-apt-cacher-ng-on-debian-11-3"&gt;this Linux Help page&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Installing service with Ansible</title><link>https://blog.iankulin.com/installing-service-with-ansible/</link><pubDate>Sat, 30 Sep 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/installing-service-with-ansible/</guid><description>&lt;p&gt;Having written my little monitoring endpoint in Go, it needs pushed out to all my servers and VM&amp;rsquo;s. Clearly this is a job for Ansible which I&amp;rsquo;ve already &lt;a href="https://blog.iankulin.com/ansible-with-secrets/"&gt;dabbled my toes in&lt;/a&gt;. Before we get onto doing that though, we need to have a think about how to make it a service.&lt;/p&gt;
&lt;h3 id="linux-services"&gt;Linux Services&lt;/h3&gt;
&lt;p&gt;A service in Linux is just a program, but one that&amp;rsquo;s usually required to be running all the time to provide some piece of functionality. The &amp;ldquo;program&amp;rdquo; can be any executable, but to allow systemd to manage it, we need to tell it a bit about what we want in a &lt;code&gt;.service&lt;/code&gt; file. This file is used by &lt;code&gt;systemd&lt;/code&gt; to know how to manage the service. They can get quite complex, but here&amp;rsquo;s the simple one for &lt;code&gt;vitals-glimpse&lt;/code&gt; - my little monitoring API endpoint.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-19-at-11.23.21-am.png" alt="[Unit]
Description=Memory and Disk statistics server on port 10321
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/vitals-glimpse
[Install]
WantedBy=default.target"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ExecStart&lt;/code&gt; is just saying what executable file is to be run. In this case it&amp;rsquo;s my compiled Go program. It&amp;rsquo;s a whopping 6MB so I&amp;rsquo;m assuming it&amp;rsquo;s all statically linked and standalone, so to run it we just copy it into &lt;code&gt;/usr/local/bin&lt;/code&gt; and run it from there.&lt;/p&gt;
&lt;p&gt;The two lines mentioning &lt;code&gt;.target&lt;/code&gt;s might not be obvious. These refer to the different times things happen in the machine startup sequence. &lt;code&gt;After=network.target&lt;/code&gt; means &amp;ldquo;don&amp;rsquo;t start this until the network is up and running&amp;rdquo;. You can see how it would be pointless to start a server that&amp;rsquo;s listening on a network port before networking is live. &lt;code&gt;default.target&lt;/code&gt; is just the system state when everything is going and ready for the users to interact with things, so when we specify &lt;code&gt;WantedBy=default.target&lt;/code&gt; we&amp;rsquo;re just saying &amp;ldquo;this service needs to be running by the time we are ready for user interactions&amp;rdquo;.&lt;/p&gt;
&lt;h3 id="installation"&gt;Installation&lt;/h3&gt;
&lt;p&gt;I already have my hosts file listing every machine, and an encrypted vault for my secrets (we&amp;rsquo;ve discussed those before), so the installation Ansible playbook just needs to copy the executable file into place in &lt;code&gt;/usr/local/bin&lt;/code&gt;, mark it as executable, copy the service file into place, and then start the service.&lt;/p&gt;
&lt;p&gt;If the files are already up to date and we don&amp;rsquo;t copy anything, then there&amp;rsquo;s no need touch the service, but if we have copied a new file, then we want to restart the service to pick up the change. Here&amp;rsquo;s how that all looks.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;---
- name: Install vitals-glimpse to a Debian based server
 # ansible-playbook vg-install.yml --ask-vault-pass 
 vars_files: ./vault.yml
 hosts: vm100-dockhost
 become: true

 tasks:
 - name: Copy service file
 ansible.builtin.copy:
 src: files/vitals-glimpse.service
 dest: /etc/systemd/system/vitals-glimpse.service
 notify: Restart vitals-glimpse

 - name: Copy executable
 ansible.builtin.copy:
 src: files/vitals-glimpse
 dest: /usr/local/bin/vitals-glimpse
 mode: &amp;#39;0755&amp;#39; # Set the executable permissions
 notify: Restart vitals-glimpse

 handlers:
 - name: Restart vitals-glimpse
 ansible.builtin.service:
 name: vitals-glimpse
 state: restarted
 enabled: yes
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first thing to know is that I have a hosts inventory file in my Ansible config, and &lt;code&gt;vm100-dockhost&lt;/code&gt; is just one of those hosts. The sudo credentials for that host are in the &lt;code&gt;vault.yml&lt;/code&gt; file mentioned in the code as &lt;code&gt;vars_file&lt;/code&gt;. I&amp;rsquo;ve started putting the command I need to run each playbook in a comment in the file so I don&amp;rsquo;t have to remember them, the command for this one: &lt;code&gt;ansible-playbook vg-install.yml --ask-vault-pass&lt;/code&gt; tells Ansible to run this playbook, and ask me for the password to decrypt the vault file.&lt;/p&gt;
&lt;p&gt;The if/then mechanism to only do something based on something earlier happening in Ansible is usually achieved with notify/handles. We put the declarative block which is optionally executed in the &lt;code&gt;handlers:&lt;/code&gt; block. The name of this block (in the case above it is &lt;code&gt;Restart vitals-glimpse&lt;/code&gt;) is specified with the &lt;code&gt;notify&lt;/code&gt; key. If either of the files are copied in, then the notify flag is set and the service is restarted.&lt;/p&gt;</description></item><item><title>Simple API endpoint in Go</title><link>https://blog.iankulin.com/simple-api-endpoint-in-go/</link><pubDate>Wed, 27 Sep 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/simple-api-endpoint-in-go/</guid><description>&lt;img src="https://blog.iankulin.com/images/gopher.png" width="219" alt=""&gt;
&lt;p&gt;I&amp;rsquo;d like a small, quick, low load endpoint on all my nodes and VM&amp;rsquo;s that exposes a text keyword indicating if that machine is okay for RAM and disk space. I&amp;rsquo;m currently using &lt;a href="https://blog.iankulin.com/tags/uptime-kuma/"&gt;Uptime Kuma&lt;/a&gt; to monitor if these machines are pingable, but I&amp;rsquo;d love a tiny bit more information from them so I&amp;rsquo;d get a &lt;a href="https://blog.iankulin.com/uptime-kuma-nfty/"&gt;Ntfy&lt;/a&gt; buzz on my phone if a machine is in trouble.&lt;/p&gt;
&lt;p&gt;I mentioned a couple of weeks ago that the benefit of doing it in C rather than Node.js was probably not worth the trouble, but then being a fickle developer, decided to write it in Go.&lt;/p&gt;
&lt;p&gt;This was a pretty sweet experience, it&amp;rsquo;s a nice language and the ecosystem is good. When writing such a small utility, you don&amp;rsquo;t really get a full appreciation for a language, but there is a couple of nice things going on - one I appreciated was that unused code - for example an import that&amp;rsquo;s not used, or a variable declared but not accessed is a compiler error and flagged by the intellisense as you type.&lt;/p&gt;
&lt;p&gt;In terms of the language as written, it&amp;rsquo;s fair to say C-like - there&amp;rsquo;s no weirdness like the formatting being semantic. It&amp;rsquo;s statically typed, but has good inference.&lt;/p&gt;
&lt;p&gt;The code is up on &lt;a href="https://github.com/IanKulin/vitals-glimpse"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s hard-coded to port 10321 and the route is &lt;code&gt;/vitals.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-15-at-9.37.44-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-15-at-9.37.44-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You get back this JSON. In my Uptime Kuma system, I search for the keywords &lt;code&gt;mem_okay&lt;/code&gt; and &lt;code&gt;disk_okay&lt;/code&gt; - no need to parse the JSON, it&amp;rsquo;s just an on/off status check that will show up in red on the page if there&amp;rsquo;s trouble, and ping my phone using &lt;a href="https://blog.iankulin.com/uptime-kuma-nfty/"&gt;ntfy.sh&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In Uptime Kuma, there&amp;rsquo;s an option when setting up a new monitor for &lt;code&gt;Http(s) Keyword&lt;/code&gt;. How this works is that it will scrape that web address and look to see if a particular keyword exists. If the keyword is present on the page, that site is marked as up, if not, it&amp;rsquo;s marked as down.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-15-at-7.47.44-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-15-at-7.47.44-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Testing the memory threshold for the screenshot above was fun:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;stress-ng --vm-bytes $(awk &amp;#39;/MemAvailable/{printf &amp;#34;%d\n&amp;#34;, $2 * 0.9;}&amp;#39; &amp;lt; /proc/meminfo)k --vm-keep -m 1
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Problems backing up LXC to NFS in Proxmox</title><link>https://blog.iankulin.com/problems-backing-up-lxc-to-nfs-in-proxmox/</link><pubDate>Sun, 24 Sep 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/problems-backing-up-lxc-to-nfs-in-proxmox/</guid><description>&lt;p&gt;If you create an unprivileged LXC container on Proxmox, then try to back it up to an NFS share, for example on a NAS, you&amp;rsquo;ll get an error when it tries to build the temporary file.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-14-at-9.15.29-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-14-at-9.15.29-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The clue is in the &lt;code&gt;Permission denied&lt;/code&gt; line. It is trying to create a temporary file on my NAS, and failing because of a &lt;a href="https://blog.iankulin.com/could-it-be-a-permissions-problem/"&gt;permissions&lt;/a&gt; problem. If I try the same backup to the local storage, it works fine.&lt;/p&gt;
&lt;p&gt;The solution is to build the temporary file in the local storage. To do this, you need to edit the &lt;code&gt;/etc/vzdump.conf&lt;/code&gt; on the Proxmox node to set the &lt;code&gt;tmpdir: /tmp&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-14-at-9.16.14-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-14-at-9.16.14-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then if you run the backup again, it will be able to create the temporary file, and successfully copy it to the share.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-14-at-9.15.20-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-14-at-9.15.20-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It doesn&amp;rsquo;t make sense to me how it has the permissions to copy the finished backup file to the share, but not create a temporary file there - but I&amp;rsquo;m not curious enough today to find out. Shout out to user &lt;a href="https://forum.proxmox.com/members/dunuin.96080/"&gt;Dunuin&lt;/a&gt; in the Proxmox &lt;a href="https://forum.proxmox.com/threads/cannot-backup-only-lxc-to-nfs-vm-works.90797/"&gt;forums&lt;/a&gt; for the suggestion to change the &lt;code&gt;tmpdir&lt;/code&gt; in &lt;code&gt;/etc/vzdump.conf&lt;/code&gt;&lt;/p&gt;</description></item><item><title>Use VS Code to work on remote files</title><link>https://blog.iankulin.com/use-vs-code-to-work-on-remote-files/</link><pubDate>Thu, 21 Sep 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/use-vs-code-to-work-on-remote-files/</guid><description>&lt;p&gt;If you&amp;rsquo;ve got a script, or some code to work on, and it&amp;rsquo;s on a VM somewhere, you can always &lt;code&gt;ssh&lt;/code&gt; in and use &lt;code&gt;nano&lt;/code&gt; or &lt;a href="https://blog.iankulin.com/bloody-vim/"&gt;&lt;code&gt;vim&lt;/code&gt;&lt;/a&gt; to make your edits. Like a caveman. With an archaic editor, no intellisense, and no spell checking.&lt;/p&gt;
&lt;p&gt;Or&amp;hellip;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-13-at-3.50.15-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-13-at-3.50.15-pm.png" width="900" alt="VS Code connected to a remote server over SSH"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This magic - of editing a files on a remote server over SSH is achieved by using a Microsoft plugin for VS Code - &amp;ldquo;&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh"&gt;Remote - SSH&lt;/a&gt;&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh"&gt;&lt;img src="https://blog.iankulin.com/images/untitled.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;How the plugin works is that it installs a copy of &lt;a href="https://code.visualstudio.com/docs/remote/vscode-server"&gt;VS Code Server&lt;/a&gt; on the remote machine, then connects to it over SSH.&lt;/p&gt;
&lt;p&gt;The experience is pretty great, once it&amp;rsquo;s installed (which I&amp;rsquo;ll run through below) it&amp;rsquo;s as if you are natively on the remote machine - the terminal is in the current directory on the remote machine, you can navigate around your files, edit them, use git, drag local files in and drop them in your remote folder and run your code.&lt;/p&gt;
&lt;h3 id="setup"&gt;Setup&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You need to be able to SSH into the remote machine, preferably with keys if you want a smooth experience. I&amp;rsquo;ve &lt;a href="https://blog.iankulin.com/ssh-key-login-on-vps/"&gt;talked about this&lt;/a&gt; before if that&amp;rsquo;s new to you.&lt;/li&gt;
&lt;li&gt;Install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh"&gt;Remote-SSH&lt;/a&gt; plugin&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-13-at-3.29.05-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-13-at-3.29.05-pm.png" width="825" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Once that&amp;rsquo;s done, there will be a &amp;ldquo;Remote Explorer&amp;rdquo; icon over on the left edge of the VS Code window. If you click on that, the explorer area will show a list of machines you&amp;rsquo;ve configured. There&amp;rsquo;s a + to add a new one.&lt;/li&gt;
&lt;li&gt;If you click on that, it will ask you to enter the SSH command to access a machine.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-13-at-3.31.49-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-13-at-3.31.49-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When you hit enter on that, it will ask you where to save the config file - I just chose the top one since that&amp;rsquo;s where I usually go to edit &lt;code&gt;known_hosts&lt;/code&gt; etc.&lt;/p&gt;
&lt;p&gt;You can get back to this config file later by clicking on the gear icon next to SSH in the remote explorer. That&amp;rsquo;s what I&amp;rsquo;ve done in the screenshot below to change the server name to something a bit more memorable.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;With that all set up, just click on the &amp;ldquo;Connect in New Window&amp;rdquo; icon next to the server you want to work on.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-13-at-4.59.30-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-13-at-4.59.30-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Once the connection is established, new VS Code window will open up, with the files in the remote directory loaded ready for work. The status of the connection is shown in the bottom left corner of the window.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-13-at-5.04.18-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-13-at-5.04.18-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If anything here didn&amp;rsquo;t make sense, there&amp;rsquo;s a &lt;a href="https://code.visualstudio.com/docs/remote/ssh-tutorial"&gt;good tutorial on the Visual Studio Code website&lt;/a&gt;, or if you&amp;rsquo;re more of a video person, this overenthusiastic guy has a &lt;a href="https://www.youtube.com/watch?v=7kum46SFIaY"&gt;good quick summary&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Disable SSH root logins</title><link>https://blog.iankulin.com/disable-ssh-root-logins/</link><pubDate>Mon, 18 Sep 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/disable-ssh-root-logins/</guid><description>&lt;p&gt;This always makes me laugh:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-03-at-8.01.20-pm.jpg" alt="Screenshot of terminal output full of lines saying &amp;ldquo;Failed password for root&amp;rdquo;"&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s like half the traffic on the internet is &lt;a href="https://blog.iankulin.com/chinese-hackers-want-to-steal-my-hello-world-container/"&gt;bots&lt;/a&gt; trying random passwords on root accounts over ssh. This is on an Ubuntu VPS on BinaryLane that had only been spun up five minutes or so. Looks like about one attempt every 10 seconds.&lt;/p&gt;
&lt;p&gt;This is why the number three thing on my new install list is to disable root access via ssh. Here&amp;rsquo;s my system - possibly just for Ubuntu and related systems:&lt;/p&gt;
&lt;p&gt;Add a new user, and put them in the sudo group&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;add user fred
usermod -aG sudo fred
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then log out, and ssh back in with the user you just created. Now we want to edit the config file for the ssh daemon. Since we&amp;rsquo;re not logged in as root now, we&amp;rsquo;ll have to use &lt;code&gt;sudo&lt;/code&gt;, so we&amp;rsquo;ll also find out if that&amp;rsquo;s working.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo nano /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If &lt;code&gt;sudo&lt;/code&gt; doesn&amp;rsquo;t work, either you stuffed up adding the new username to the sudo group, or you don&amp;rsquo;t have sudo installed. If the problem is the latter, log out, and ssh back in as root and install it with &lt;code&gt;apt install sudo&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;There is probably a line with &lt;code&gt;PermitRootLogin&lt;/code&gt; in it. It may be commented out, or set to &lt;code&gt;yes&lt;/code&gt;. But we want it to end up looking like this&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;PermitRootLogin no
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;ll need to restart the daemon to pick up the config changes.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo systemctl restart sshd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now if you log out, and try to ssh back in as root, it should fail. If it doesn&amp;rsquo;t, a likely issue is that there&amp;rsquo;s other configuration files being included. I feel I&amp;rsquo;ve mentioned before that a common pattern with Linux config files, at least on the Debian based systems I use, is that there&amp;rsquo;s a main config file that you probably shouldn&amp;rsquo;t mess with, but it pulls in subsidiary config files, often in a subdirectory called &lt;code&gt;conf.d&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;In the case of this file, there&amp;rsquo;s a line up the top saying&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Include /etc/ssh/sshd_config.d/*.conf
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;which does exactly that.&lt;/p&gt;
&lt;h3 id="spy-vs-spy"&gt;Spy vs Spy&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;d love to know what passwords these bots are trying. I was thinking it wouldn&amp;rsquo;t be all that hard to write something that would face the password login process and run it on port 22 to see. I asked ChatGPT about this, but goodie-goddie that it is, all I got was a warning about ethics and some ssh security tips.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-04-at-6.04.02-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-04-at-6.04.02-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not the first person to &lt;a href="https://www.darkreading.com/endpoint/a-common-password-list-accounts-for-nearly-all-cyberattacks"&gt;think of this&lt;/a&gt;, so I might come back to this idea later. If I was running brute force ssh I guess I&amp;rsquo;d use one of the common password lists from one of the big leaks, so it might not be that exciting. It would also be interesting to see what the first command they tried to run is as well.&lt;/p&gt;</description></item><item><title>Lightweight Web Servers</title><link>https://blog.iankulin.com/lightweight-web-servers/</link><pubDate>Fri, 15 Sep 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/lightweight-web-servers/</guid><description>&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-02-at-9.09.48-pm-2.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-02-at-9.09.48-pm-2.png" width="300" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using the excellent &lt;a href="https://github.com/louislam/uptime-kuma"&gt;Uptime Kuma&lt;/a&gt; for my monitoring, but a couple of recent incidents - an external USB mount disappeared on a remote machine, an NVME drive filled up on a different node and stopped backups working because of a configuration error - have made me start to think about more robust monitoring.&lt;/p&gt;
&lt;p&gt;The are many great tools for this - &lt;a href="https://www.nagios.org/"&gt;Nagios&lt;/a&gt;, &lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt; etc. but they are pretty substantial time investments for the excellent power. They can save time series data and display them beautifully. However, all I really want is to add some extra ability to Uptime Kuma.&lt;/p&gt;
&lt;p&gt;Uptime Kuma is already pretty great - it can parse a webpage to search for a particular phrase, it can execute searches in popular databases, it can ping, check a docker container is running and all sorts of other tricks - but it can&amp;rsquo;t check memory use of a service, or if a machine is running out of disk space. Uptime Kuma works in binary - things either pass a check, or they don&amp;rsquo;t. It does do some nice graphs of ping times, but that&amp;rsquo;s about all.&lt;/p&gt;
&lt;p&gt;I could expose some of this data - disk space free, CPU temp, checking a mount is working - pretty easily in a little Node endpoint. But it thinking about this, it made me wonder what the overhead of running Node (probably with Express) to carry out this menial task might be. I was thinking that the alternatives would be to use python/flask, or just to write it in C or Golang.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dev.to/wickdchromosome/is-the-pain-worth-the-gain-writing-webapps-in-c-benchmarks-vs-flask-and-nodejs-14l0"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-02-at-9.34.50-pm.png" width="129" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Whilst searching for answers about this, I found this excellent article from Bence Cotis. It turns out, that for very low loads (I&amp;rsquo;ll probably hit these endpoints once every five minutes) C is a bit better, but probably not (in my opinion) worth the hassle. I&amp;rsquo;ll stick to Node.&lt;/p&gt;</description></item><item><title>Testing Storage Speed</title><link>https://blog.iankulin.com/testing-storage-speed/</link><pubDate>Sun, 03 Sep 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/testing-storage-speed/</guid><description>&lt;p&gt;Now I&amp;rsquo;ve added NVME drives to my nodes, plus added an external NMVE RAID, I&amp;rsquo;ve got quite the collection of storage options. For one of my nodes, it looks like this:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-23-at-1.20.34-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-23-at-1.20.34-pm.png" width="979" alt="Screenshot of Proxmox GUI showing 5 storage options"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The 256GB NVME the OS is installed to&lt;/li&gt;
&lt;li&gt;The 512GB SSD, currently running ZFS&lt;/li&gt;
&lt;li&gt;The Synology NAS - 4 x 6TB drives in RAID 5 on a 1GB switch&lt;/li&gt;
&lt;li&gt;A pair of 256GB NVME sticks in an external USB3 enclosure set up as a mirrored ZFS pool.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For my dev VM&amp;rsquo;s I often set them up to have their storage on the NAS - it&amp;rsquo;s just super easy to move them around then. The production VM&amp;rsquo;s currently have their storage on the SSD (that machine hasn&amp;rsquo;t had the NVME upgrade yet), but obviously with all these options, it&amp;rsquo;d be interesting to think about what goes where.&lt;/p&gt;
&lt;p&gt;The biggest lots of files - media and distro ISO&amp;rsquo;s are clearly going to be on the NAS. No thinking to do there. Production VM backups also go there, and now I&amp;rsquo;ve got room they might also go cross-node as an extra layer of redundancy.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s really the live VM hard disks that need a decision. So I need to do some measuring.&lt;/p&gt;
&lt;p&gt;Jim Salter - one of the ZFS kings on the &lt;a href="https://2.5admins.com/"&gt;2.5 Admins&lt;/a&gt; podcast has an &lt;a href="https://arstechnica.com/gadgets/2020/02/how-fast-are-your-disks-find-out-the-open-source-way-with-fio/"&gt;article on drive speed testing&lt;/a&gt; that&amp;rsquo;s worth reading even just for the good descriptions of different drives and the range of workloads to consider.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://arstechnica.com/gadgets/2020/02/how-fast-are-your-disks-find-out-the-open-source-way-with-fio/"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-23-at-4.42.31-pm.jpg" alt="Article headline: How fast are your disks? Find out the open source way,
with fio."&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;He ends up recommending three tests using &lt;code&gt;fio&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Writing 4K blocks randomly - disks do not enjoy this&lt;/li&gt;
&lt;li&gt;Having 16 parallel processes write 64K blocks to random locations in 256MB files&lt;/li&gt;
&lt;li&gt;Writing 1MB blocks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I ran all of those tests once on each of my storage options and this is what we got.&lt;/p&gt;
&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;NVME&lt;/td&gt;&lt;td&gt;NAS/NFS/RAID 5&lt;/td&gt;&lt;td&gt;SDD/ZFS&lt;/td&gt;&lt;td&gt;External NVME RAID 1 ZFS&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Single 4KiB random write process&lt;/td&gt;&lt;td&gt;430&lt;/td&gt;&lt;td&gt;17.9&lt;/td&gt;&lt;td&gt;80.6&lt;/td&gt;&lt;td&gt;75.7&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;16 parallel 64KiB random write processes&lt;/td&gt;&lt;td&gt;2328&lt;/td&gt;&lt;td&gt;4897&lt;/td&gt;&lt;td&gt;267&lt;/td&gt;&lt;td&gt;135&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Single 1MiB random write process&lt;/td&gt;&lt;td&gt;651&lt;/td&gt;&lt;td&gt;70.7&lt;/td&gt;&lt;td&gt;379&lt;/td&gt;&lt;td&gt;195&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;em&gt;Speeds in MB/s&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can better see how crazy this is in a graph.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/picture-1.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;What is up with the figure for the NAS 16 x 64K?&lt;/p&gt;
&lt;p&gt;Probably worth talking about what I was expecting. I thought the internal NVME would be quickest, then the internal SDD, then the external NVME RAID then the NAS since it&amp;rsquo;s over the network.&lt;/p&gt;
&lt;p&gt;Given the excellent speed of the internal NVME, the external NMVE was a d=bit disappointing, but it&amp;rsquo;s got a few factors going against it. One is that it&amp;rsquo;s talking over USB 3 which has a theoretical 600MB/s limit, but we&amp;rsquo;re well under that. More likely it&amp;rsquo;s the ZFS - a system that is focused on data integrity rather than speed. But then the external drive is worse that the internal SATA SSD - and they both have ZFS with compression turned on. The enclosure is a no-name brand one, so we don&amp;rsquo;t really know the quality of it&amp;rsquo;s USB 3.0 implementation.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m dubious about the data for the NAS. When I first ran the 4K test it took so long, I killed the process a couple of times thinking it was frozen. The test command limits all three tests to a minute, so you&amp;rsquo;d expect them to take just a few seconds more than that, but all the NAS tests seemed to hang on the very last piece of output for minutes. I&amp;rsquo;m wondering is there was some smart caching going on, but then it waited in an async way to complete. Jim actually discusses that this is the purpose of the &lt;code&gt;--end_fsync=1&lt;/code&gt; in the commands so likely that&amp;rsquo;s it. I&amp;rsquo;ll have a think about how to adjust for this for a future post.&lt;/p&gt;
&lt;h3 id="cost-of-zfs"&gt;Cost of ZFS&lt;/h3&gt;
&lt;p&gt;What would the story be on the internal SSD if we turn the ZFS compression off, or just use ext4?&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/ssd-speed.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;It looks like compression has quite a cost on the little writes. It&amp;rsquo;s not a simple matter though - you could expect a speed improvement in many situations if the data compresses well. I had a look at the data in some of the test files that were generated, and it seemed quite random which is probably the worst case scenario for compression. Even so, in the graphs above the compressed looks like it did better than the uncompressed for the 64K random writes. I didn&amp;rsquo;t bother to replicate the tests, so it&amp;rsquo;s possible that&amp;rsquo;s just noise.&lt;/p&gt;
&lt;p&gt;Looking at the external dual NVME USB 3 drive with compression on/off is a similar story.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/external.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;A big increase for the small writes, slight decrease in the parallel ones.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s really a bit hard to come to any conclusions from all this, but let&amp;rsquo;s have a go:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There&amp;rsquo;s a speed penalty for using ZFS at all&lt;/li&gt;
&lt;li&gt;ZFS compression is expensive for many small writes of uncompressible data&lt;/li&gt;
&lt;li&gt;I need to know more about fio to figure out what was going on with that near impossible speed&lt;/li&gt;
&lt;li&gt;NVME is really fast connected to the bus, plugged in through USB, not so much&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Based on this, I wish I&amp;rsquo;d bought bigger NVME drives - that might be a future plan when I&amp;rsquo;ve run these for a while. These 256GB ones were $20 each which is just about a disposable price. They&amp;rsquo;ll probably end up as cache for a NAS or something in the future. In the mean time, I&amp;rsquo;ll format my SATA SSDs with ZFS with no compression and the VM disks will live on them. The benefits I get from that (100% data integrity checks with scrubs, and easy transfer of snapshots) seem like a reasonable tradeoff for a little speed.&lt;/p&gt;</description></item><item><title>Error wiping old drive in Proxmox</title><link>https://blog.iankulin.com/error-wiping-old-drive-in-proxmox/</link><pubDate>Thu, 31 Aug 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/error-wiping-old-drive-in-proxmox/</guid><description>&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-22-at-12.19.42-pm-copy.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-22-at-12.19.42-pm-copy.png" width="568" alt="Error: disk/partition '/dev/sda3' has a holder (500)"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When I popped in an NVME drive and freshly installed Proxmox to it, I assumed I&amp;rsquo;d just be able to wipe the SDD that had previously been the boot drive to set it up as a ZFS pool. However, when I tried to do the wipe, I was greeted with the error:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;disk/partition &amp;#39;/dev/sda3&amp;#39; has a holder (500)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I assume this means there&amp;rsquo;s a flag set on one of the Proxmox partitions to prevent accidental deletion or Proxmox thought that&amp;rsquo;s where it was running from. It&amp;rsquo;s likely that it&amp;rsquo;s related to this message I had during installation that I haven&amp;rsquo;t seen before:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/img_5830.jpg" alt="Detected existing &amp;lsquo;pve&amp;rsquo; Volume Group(s)! Do you want to: rename VG backed by PV &amp;lsquo;/dev/sda3&amp;rsquo; to &amp;lsquo;pve-OLD-D4DDE7DC&amp;rsquo; or cancel the installation?"&gt;&lt;/p&gt;
&lt;p&gt;Since I didn&amp;rsquo;t want to cancel the installation, I went ahead and told it okay. On the non-graphical &amp;lsquo;console&amp;rsquo; version of the installer, this message is truncated, and the only option available is abort. I guess that&amp;rsquo;s an installer bug. So if you are adding a extra boot drive to an existing Proxmox node, I suggest using the graphical installer.&lt;/p&gt;
&lt;p&gt;When I Googled around for the &amp;ldquo;has a holder&amp;rdquo; error, there were several unanswered requests for help for this, several speculative answers, and &lt;a href="https://www.reddit.com/r/Proxmox/comments/xff5ri/how_do_i_wipe_an_old_drive/"&gt;one that worked&lt;/a&gt;.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/66d29d7d-bc29-4747-b92a-7fc7c790227f_text.gif" width="400" alt=""&gt;
&lt;p&gt;You need to use &lt;code&gt;fdisk&lt;/code&gt; to remove each partition. Take a note of the drive name - I could see in the Proxmox GUI that mine was sda, so the command to run was:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fdisk /dev/sda&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You probably need to have a &lt;a href="https://www.howtogeek.com/106873/how-to-use-fdisk-to-manage-partitions-on-linux/"&gt;read-up on&lt;/a&gt; &lt;code&gt;[fdisk](https://www.howtogeek.com/106873/how-to-use-fdisk-to-manage-partitions-on-linux/)&lt;/code&gt; if you&amp;rsquo;re not familiar with it, but basically, you&amp;rsquo;re in the command mode, for one of the partitions (my &lt;code&gt;sda&lt;/code&gt; had three) if you press the &lt;code&gt;d&lt;/code&gt; key here it marks that partition for deletion. Even though the error message had said it was the last partition that was causing the headache, I just went ahead and deleted all of them. There&amp;rsquo;s no warnings as you do this, and actually no changes have been made yet, that happens when you press &lt;code&gt;w&lt;/code&gt; to write the changes. No warning here either. 🙂&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-22-at-12.29.16-pm.png" alt="fdisk screenshot"&gt;&lt;/p&gt;
&lt;p&gt;That gave an error saying the third partition was still in use by the kernel, so I followed the advice to reboot, then I was able to wipe the drive in the Proxmox web GUI.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-22-at-12.30.09-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-22-at-12.30.09-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>How to install M.2 SSD in HP G2 800 Mini</title><link>https://blog.iankulin.com/how-to-install-m-2-ssd-in-hp-g2-800-mini/</link><pubDate>Mon, 28 Aug 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/how-to-install-m-2-ssd-in-hp-g2-800-mini/</guid><description>&lt;img src="https://blog.iankulin.com/images/img_5821-copy.jpg" width="512" alt=""&gt;
&lt;p&gt;As part of my strategy to not worry about the &lt;a href="https://blog.iankulin.com/sdd-wearout-numbers/"&gt;slightly dodgy SMART reporting&lt;/a&gt; on the SDD&amp;rsquo;s in my HP Elitedesk G2 800 Mini Proxmox nodes, I&amp;rsquo;d decided to make use of the full sized &lt;a href="https://en.wikipedia.org/wiki/M.2"&gt;M.2&lt;/a&gt; slot to install 256GB NVME drives. That way I can boot from those, and have the SSD&amp;rsquo;s running &lt;a href="https://arstechnica.com/information-technology/2020/05/zfs-101-understanding-zfs-storage-and-performance/"&gt;ZFS&lt;/a&gt; which allows &lt;em&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/man/8/zpool-scrub.8.html"&gt;scrubbing&lt;/a&gt;&lt;/em&gt; to check the integrity of all the data. My VM disks can live on this drive.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://support.hp.com/au-en/product/hp-elitedesk-800-35w-g2-desktop-mini-pc/7633266"&gt;G2 800 Mini&lt;/a&gt; has two M.2 slots, a 2230 (M.2 sizes are &lt;code&gt;wwll&lt;/code&gt; where &lt;code&gt;ww&lt;/code&gt; is width in mm, and &lt;code&gt;ll&lt;/code&gt; is length in mm) for the wireless/bluetooth adaptor and a 2280 for storage. These slots are under the SSD drive cage.&lt;/p&gt;
&lt;h2 id="steps"&gt;Steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Undo the large finger-operable screw on the back of the case, then slide the case off in the direction of the front of the unit.&lt;/li&gt;
&lt;li&gt;Unplug the drive SATA connector&lt;/li&gt;
&lt;/ul&gt;
&lt;img src="https://blog.iankulin.com/images/img_5818.jpg" width="800" alt=""&gt;
&lt;ul&gt;
&lt;li&gt;At the right side of the SSD (when the machine is orientated per the photo above) is a lever that can be pushed a little to the right to allow the drive to slide back and be lifted out.&lt;/li&gt;
&lt;li&gt;There&amp;rsquo;s three giant screws holding the drive cage in numbered 1, 2 &amp;amp; 3. There&amp;rsquo;s also several smaller screws with numbers - ignore them. The ones you are looking for have a torx in the middle, but also a slot for an ordinary flat blade screwdriver. If you can only find two, that&amp;rsquo;s probably because the drive&amp;rsquo;s SATA connector is covering it up.&lt;/li&gt;
&lt;/ul&gt;
&lt;img src="https://blog.iankulin.com/images/img_5829.jpg" width="800" alt=""&gt;
&lt;ul&gt;
&lt;li&gt;Once the drive cage is removed and set aside, you&amp;rsquo;ll be able to see the two M.2 slots. The NVME drive slots in like SODIMM memory - sort of sprung up on the end away from the connector. I didn&amp;rsquo;t like the look of those lose wires - but I assume they are for the wifi or bluetooth antennas.&lt;/li&gt;
&lt;/ul&gt;
&lt;img src="https://blog.iankulin.com/images/img_5821-copy.jpg" width="512" alt=""&gt;
&lt;ul&gt;
&lt;li&gt;Wriggle it in, then push the end down and secure it with the little M.2 screws. You did remember to &lt;a href="https://www.ebay.com.au/itm/254101897159?hash=item3b29a73fc7:g:Wi4AAOSw6JpfdRiw&amp;amp;amdata=enc%3AAQAIAAABAFQS9v%2BRrt%2FNj4OpgTFaOvObhlzxvwZi%2BTxcYYqqbid7A6%2BkHvM6T3%2BDJ%2FegE3E9k3OH8bnHIBDJATYnIeJb9db%2FcKPWZP%2FAeNLDhwPi%2FDebbCZOJmhrSd3j0GRYLzE03YK%2F8DvMMAeLjPWLUO6mqZSUv%2FB7%2FuOs4Yz%2F5%2Bj6atvgCb0afWi9igSdklHlr6N1gqWN7DSb9WrCi2Dx62LQdasjvyrTNm%2BeDGzRj1ADzEJTG1oyJkOto6DOY2cUiGM5gLssMknszOh25RhBgXrNLf%2BUFnzUI2%2BOr5fvcamWs7zxKJJcndcMYOzbm3v%2B243SsWoGymttCsbsWi%2FLRekQRpQ%3D%7Ctkp%3ABFBMvrXWjLBi"&gt;order those screws&lt;/a&gt;, right? My $20 Samsung PM981a 256GB drives didn&amp;rsquo;t come with any, but perhaps fancy ones do.&lt;/li&gt;
&lt;li&gt;Then, as 1970 &lt;a href="https://haynes.com/en-au/holden/kingswood/1968-1971?part=04085&amp;amp;selector=print&amp;amp;gclid=EAIaIQobChMI3u6DqfKjgAMVLtcWBR2U7gT2EAQYBiABEgIgmfD_BwE"&gt;Greggory&amp;rsquo;s workshop manuals&lt;/a&gt; used to say, &amp;ldquo;&lt;em&gt;Assembly is the reverse of the disassembly steps with attention to the following:&lt;/em&gt;&amp;rdquo;. In this case, the attention would be towards being gentle with that SSD ribbon connector.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Installing a Node app on a server</title><link>https://blog.iankulin.com/installing-a-node-app-on-a-server/</link><pubDate>Tue, 22 Aug 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/installing-a-node-app-on-a-server/</guid><description>&lt;p&gt;Before I write a fancy Ansible playbook to automatically set up the Nginx/Node combo on my web servers, it might be worth going through how to deploy a Node app so it can run on a server without you being logged in.&lt;/p&gt;
&lt;p&gt;Until now, I&amp;rsquo;ve been running my tests on my laptop, or in a server logged in as myself - sometimes detaching from tmux. But we need a bit more professional set up than that. The process will look something like this:&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/hqdefault.jpg" width="150" alt=""&gt;
&lt;ul&gt;
&lt;li&gt;Install Node and npm (I&amp;rsquo;m assuming we&amp;rsquo;ve done that since I&amp;rsquo;ve covered the playbook for it before).&lt;/li&gt;
&lt;li&gt;Copy the app files over&lt;/li&gt;
&lt;li&gt;Install the dependencies&lt;/li&gt;
&lt;li&gt;Write the systemd config file&lt;/li&gt;
&lt;li&gt;Start it up&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="app-files"&gt;App files&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ll use the very simple server (&lt;code&gt;index.js&lt;/code&gt;) I&amp;rsquo;ve written for the future Ansible post. All it does is listen on port 3000 to serve a tiny piece of text if someone hits the &lt;code&gt;/api&lt;/code&gt; route.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const express = require(&amp;#39;express&amp;#39;);
const app = express();

const PORT = 3000;

app.get(&amp;#34;/api&amp;#34;, (req, res) =&amp;gt; {
 res.status(200).send(&amp;#39;Success - from /api route via node.js&amp;#39;);
});

app.listen(PORT, () =&amp;gt; {console.log(`Listening on port ${PORT}`)});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;ll also have our &lt;code&gt;package.json&lt;/code&gt;, of which the only interesting thing to notice is that we&amp;rsquo;ve got a dependency on the &lt;code&gt;express&lt;/code&gt; package which I originally installed with &lt;code&gt;npm&lt;/code&gt;. All the files for our dependencies are stored in the &lt;code&gt;./node_modules&lt;/code&gt; directory, but we don&amp;rsquo;t need to copy them to the server.&lt;/p&gt;
&lt;h3 id="where-to-put-the-app-files"&gt;Where to put the app files&lt;/h3&gt;
&lt;p&gt;If I was doing this for a commercial app, I might store the app under &lt;code&gt;/var/www/&amp;lt;app name&amp;gt;&lt;/code&gt; since that&amp;rsquo;s where a future sysadmin might look for it if they don&amp;rsquo;t have access to the playbooks. Another good place might be the home directory of the ansible/node user. Since that&amp;rsquo;s me in this case, they&amp;rsquo;re just going to go in my home directory - it makes the playbook commands shorter. We can use &lt;code&gt;scp&lt;/code&gt; to copy the files in.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-16-at-5.50.02-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Once the files are there, we can install the dependencies with &lt;code&gt;npm install&lt;/code&gt;. This looks at the &lt;code&gt;package.json&lt;/code&gt;, then grabs them down.&lt;/p&gt;
&lt;h3 id="systemd"&gt;systemd&lt;/h3&gt;
&lt;p&gt;systemd manages the init and daemon processes in most Linux distros, so we&amp;rsquo;ll be using that to get our node app running as a service. It was &lt;a href="https://en.wikipedia.org/wiki/Systemd#History"&gt;a present from Red Hat&lt;/a&gt;. Processes that run like this need a configuration file in &lt;code&gt;/lib/systemd/system&lt;/code&gt;, ours will be called&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/lib/systemd/system/test-server.service&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[Unit]
Description=index.js - test server 
After=network.target

[Service]
Type=simple
User=ian 
ExecStart=/usr/bin/node /home/ian/index.js
Restart=on-failure

[Install]
WantedBy=multi-user.target 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This file needs to say that we should wait for the network to come up before starting, what we&amp;rsquo;re running and what to do if it dies. &amp;lsquo;on-failure&amp;rsquo; means it will be restarted in pretty much any case but us stopping it cleanly. The &lt;code&gt;[multi-user.target](https://unix.stackexchange.com/questions/506347/why-do-most-systemd-examples-contain-wantedby-multi-user-target)&lt;/code&gt; bit is saying we want this service up and running for the system to be considered ready as a server.&lt;/p&gt;
&lt;p&gt;Once that file is in place, we can reload the configs, and start the service, this can be from anywhere, including a user home directory, and check it&amp;rsquo;s status.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo systemctl daemon-reload
sudo systemctl start test-server
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-16-at-8.10.32-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;That all looks good, and if I visit the endpoint, there&amp;rsquo;s the expected response, even after we&amp;rsquo;ve logged out of the server.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-16-at-8.13.04-pm.png" alt=""&gt;&lt;/p&gt;</description></item><item><title>Ansible with Secrets</title><link>https://blog.iankulin.com/ansible-with-secrets/</link><pubDate>Sun, 13 Aug 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/ansible-with-secrets/</guid><description>&lt;p&gt;We wrote a nice &lt;a href="https://blog.iankulin.com/first-ansible-playbook/"&gt;little Ansible playbook&lt;/a&gt; the other day to install nginx on our web servers and ensure it was running. We were able to store the usernames in the &lt;code&gt;hosts&lt;/code&gt; inventory file using the a&lt;code&gt;nsible_ssh_user&lt;/code&gt; variable. Then, we ran the playbook with the command:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ansible-playbook web_installs.yaml --ask-become-pass&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This asked us the password to use with the usernames in the &lt;code&gt;hosts&lt;/code&gt; file. Luckily that day, it was the same username/password combo to use for sudo on every server. What happens if that&amp;rsquo;s not the case? Here&amp;rsquo;s our new hosts file for today. There&amp;rsquo;s a cool new sysadmin in town - Jane.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[vm323_deb]
100.108.154.133

[vm323_deb:vars]
ansible_ssh_user=ian

[vm324-deb]
100.77.75.14

[vm324_deb:vars]
ansible_ssh_user=jane
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We could still use &lt;code&gt;--ask-become-pass&lt;/code&gt; but it only asks us one password, and it&amp;rsquo;s highly unlikely (we hope) that Jane and Ian have chosen the same password.&lt;/p&gt;
&lt;p&gt;If you look at the inventory file above, you can see how the variables work - it&amp;rsquo;s the same variable name - Ansible swaps the correct value in for each server as it accesses them. There&amp;rsquo;s many of these variables in addition to &lt;code&gt;ansible_ssh_user&lt;/code&gt;, including &lt;code&gt;ansible_ssh_pass&lt;/code&gt;, so maybe we can do something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[vm323_deb]
100.108.154.133

[vm323_deb:vars]
ansible_ssh_user=ian
ansible_become_password=mittens96

[vm324_deb]
100.77.75.14

[vm324_deb:vars]
ansible_ssh_user=jane
ansible_become_password=GBLEzrvc8rnUFruVrCwm
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you try that, it will work. However &lt;strong&gt;it is a terrible idea to store our ssh passwords in that inventory file in plaintext.&lt;/strong&gt; They would be available to anyone who gets access to my workstation, AND when I commit my work to git, it&amp;rsquo;s getting copied somewhere, probably including github. This is such a common problem there&amp;rsquo;s &lt;a href="https://www.gitguardian.com/solutions/scan-github-for-passwords"&gt;some business&lt;/a&gt; that have come into being to scan for passwords and API keys in people&amp;rsquo;s repos.&lt;/p&gt;
&lt;p&gt;There &lt;em&gt;is&lt;/em&gt; a better way. Ansible will allow us to store the secrets in a separate file as variables, and that separate file can be encrypted while it&amp;rsquo;s on disk, and Ansible will decrypt it to use from memory then clean up after itself. There&amp;rsquo;s a couple of new (to us) things here - variables, and the encryption. Let&amp;rsquo;s look at them separately.&lt;/p&gt;
&lt;h3 id="variables"&gt;Variables&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;[Ansible variables](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html)&lt;/code&gt; is a whole subject, we&amp;rsquo;re just going to look at the minimum we need to solve out problem.&lt;/p&gt;
&lt;p&gt;We can create another file in our project, let&amp;rsquo;s call it plaintext.yaml and store our usernames and passwords in there as key: value pairs.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-06-at-11.43.01-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Then we need to tell our playbook to import that &lt;code&gt;plaintext.yaml&lt;/code&gt; file with &lt;code&gt;vars_files&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-06-at-11.53.25-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Then in the inventory file, we can substitute the usernames and passwords with our variables.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-06-at-11.58.55-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Now, when the playbook runs, it will substitute the real values into the &lt;code&gt;host&lt;/code&gt; inventory file for us. Let&amp;rsquo;s check that.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-06-at-12.03.09-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Bingo bongo. But we haven&amp;rsquo;t actually solved our security problem yet. The ssh passwords are still dangerously stored in plaintext in our file system. What we have done is learned how to have variables in an external file, pull that into the playbook and have the values substituted in. Now we need the next step - keeping those passwords safe.&lt;/p&gt;
&lt;h3 id="ansible-vault"&gt;Ansible Vault&lt;/h3&gt;
&lt;p&gt;Clearly, every serious use of Ansible is going to have this issue (of needing ssh credentials, but not wanting to give them away to hackers) so of course, there is an elegant solution for it.&lt;/p&gt;
&lt;p&gt;What if the file with all the passwords was stored encrypted, then only decrypted for use by our playbook, and it was never saved anywhere as plaintext? That solves the problem.&lt;/p&gt;
&lt;p&gt;Ansible has a tool for this called &lt;a href="https://docs.ansible.com/ansible/2.8/user_guide/vault.html"&gt;Ansible Vault&lt;/a&gt;. We&amp;rsquo;ll create the yaml file with our variables with that tool, and it will be saved encrypted. When we run the playbook we&amp;rsquo;ll get it to ask us for the password to decrypt the file. It will do that in memory and run the playbook.&lt;/p&gt;
&lt;p&gt;The command to create our file, which we&amp;rsquo;ll call &lt;code&gt;vault.yaml&lt;/code&gt; will be:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ansible-vault create vault.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will ask us to enter the password we want to use. The strength of this password needs to be good. If it&amp;rsquo;s crackable, you are giving away root access to your systems. And it&amp;rsquo;s in a file, not in a login situation where you can time out after three logins. Whoever has the file has the time to brute force password of the the &lt;a href="https://www.ipswitch.com/blog/use-aes-256-encryption-secure-data"&gt;AES 256&lt;/a&gt; encryption.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/h4c7m5z2a2b71-copy.jpg" width="460" alt=""&gt;
&lt;p&gt;Once you&amp;rsquo;ve entered your strong password twice, it will open up the new file in the default editor - probably vim. You may need help to use this editor.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-06-at-12.46.35-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;When you&amp;rsquo;re done, save the file and exit. What does this file look like:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-06-at-12.51.38-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Yep. That should do it. We still need to tell our playbook to use this file instead of the other one.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;--- 
- name: nginx for all web servers
 vars_files: ./vault.yaml
 hosts: all
 become: yes
 tasks: 
 - name: nginx installed
 apt:
 name: nginx
 state: latest
 - name: nginx running
 service: 
 name: nginx
 state: started
 enabled: yes
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And when we run the playbook, we need to let it know to ask us the vault password.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ansible-playbook web_installs.yaml --ask-vault-pass
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-06-at-1.00.16-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;If you need to edit the secrets file in the future, the command for that would be&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ansible-vault edit vault.yaml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Once again it will ask the password, and once again it will open up in vim.&lt;/p&gt;</description></item><item><title>Bloody VIM</title><link>https://blog.iankulin.com/bloody-vim/</link><pubDate>Thu, 10 Aug 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/bloody-vim/</guid><description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Vim is a highly configurable text editor built to make creating and changing any kind of text very efficient. It is included as &amp;ldquo;vi&amp;rdquo; with most UNIX systems and with Apple OS X.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.vim.org/"&gt;vim.org&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You will encounter vi/vim as the incomprehensible text editor that pops up by default when you need to edit something in your sysadmin journey. Perhaps you issued the command to edit your Ansible vault, perhaps you forgot to add a message to a commit. It&amp;rsquo;s going to be unavoidable.&lt;/p&gt;
&lt;p&gt;Here is the minimum knowledge you need to survive these encounters.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It has modes. When it opens up, you are in &amp;lsquo;moving around mode&amp;rsquo;. Use the cursor keys to get to where you need to.&lt;/li&gt;
&lt;li&gt;When you actually want to edit something, press &lt;code&gt;i&lt;/code&gt; now you&amp;rsquo;re in &lt;em&gt;INSERT&lt;/em&gt; mode and it&amp;rsquo;s behaving how 99% of the world expect an editor to behave. When you type things, they go into the document where the cursor is.&lt;/li&gt;
&lt;li&gt;When you are done, press &lt;em&gt;escape&lt;/em&gt; to get out of INSERT mode, then these keys one at a time &lt;code&gt;: w q&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The underlying theory of Vim - that you spend more time moving around in text than editing it, is true of most sysadmin and programming tasks. So Vim has powerful, non-intuitive commands to do that efficiently. It&amp;rsquo;s impressive to watch people who have learned these arcane ways move around a file. They don&amp;rsquo;t have girlfriends. There are also powerful, non-intuitive commands for editing.&lt;/p&gt;</description></item><item><title>Finding the host IP from inside a Docker container</title><link>https://blog.iankulin.com/finding-the-host-ip-from-inside-a-docker-container/</link><pubDate>Mon, 07 Aug 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/finding-the-host-ip-from-inside-a-docker-container/</guid><description>&lt;p&gt;Having successfully set up and tested my node.js api handling app behind nginx on a development VM in the homelab, I decided to move it to my VPS so I could start using it for real. I had a bit of trouble finding the nginx.conf files on the VPS, until I remembered I was running nginx in a docker container on this machine!&lt;/p&gt;
&lt;p&gt;I got everything set up, I could hit the domain in a web browser and get served the static page, and I could &amp;lt;domain_name&amp;gt;:3000/api/gnp_temp.txt and get the file delivered by the node script, but if I tried &amp;lt;domain_name&amp;gt;/api/gnp_temp.txt - &amp;ldquo;Bad Gateway&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;I looked at the nginx.conf over and over - it seemed fine. The location block was clearly being triggered, otherwise I&amp;rsquo;d be getting a 404. I dived into stack overflow to no avail. It was if http://localhost:3000 just wasn&amp;rsquo;t working. But it definitely was when I &lt;code&gt;curl&lt;/code&gt;ed it from the command line.&lt;/p&gt;
&lt;p&gt;In desperation I started writing out an explanation to ChatGPT about the setup and my problem, and before I pressed enter realised - from nginx&amp;rsquo;s point of view, http://localhost:3000 was an address &lt;em&gt;inside&lt;/em&gt; the container 🤦, what I needed was the address of the host, from the point of view of the docker container. Surely that must be a common requirement that&amp;rsquo;s been solved.&lt;/p&gt;
&lt;p&gt;From reading around, it seems like I should be able to just substitute &lt;code&gt;host.docker.internal&lt;/code&gt; but that didn&amp;rsquo;t seem to work. I opened a shell into the container to look at &lt;code&gt;ip a&lt;/code&gt; or &lt;code&gt;/sbin/ip route|awk '/default/ { print $3 }'&lt;/code&gt; but of course these containers are slim installs without the general tools you need.&lt;/p&gt;
&lt;p&gt;Docker networking is a whole thing that I should learn, but I haven&amp;rsquo;t yet, I just start up containers with whatever the defaults for networking are. But I figured the host must be part of the docker network, and a quick look in &lt;code&gt;ip a | grep docker&lt;/code&gt; produced this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;3: docker0: &amp;lt;NO-CARRIER,BROADCAST,MULTICAST,UP&amp;gt; mtu 1500 qdisc noqueue state DOWN group default 
 inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Bingo. I popped that IP address into my nginx.conf and everything started working perfectly. That was forty minutes of learning I wouldn&amp;rsquo;t have had to live through if I could have just turned around to a work colleague and asked.&lt;/p&gt;</description></item><item><title>nginx in Front of a node.js app</title><link>https://blog.iankulin.com/nginx-in-front-of-a-node-js-app/</link><pubDate>Fri, 04 Aug 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/nginx-in-front-of-a-node-js-app/</guid><description>&lt;p&gt;NGINX is a great webserver and reverse proxy - as in it can hand off requests to other web-servers. That&amp;rsquo;s the situation I want to have set up on my VPS. I want NGINX to handle incoming requests - some of them will just be sorted out by returning static HTML, others (like the weather api I&amp;rsquo;ve been playing with) need to be handed off to other services to respond to.&lt;/p&gt;
&lt;p&gt;In the situation I&amp;rsquo;m looking at, I want requests that have the route /api (eg example.com/api/weather) to be passed to a node.js program I&amp;rsquo;ve written. All the other http requests should just be treated as requests for static pages and dealt with by NGINX.&lt;/p&gt;
&lt;p&gt;So I guess is part V of my adventures in the weather API, if you just want to know how to set up NGINX to serve static pages AND pass some routes off to node, you don&amp;rsquo;t need to be up to date on these.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.iankulin.com/outside-temperature-from-an-api-in-a-shell-script/"&gt;Outside Temperature From an API in a Shell Script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.iankulin.com/complicating-the-temperature-api/"&gt;Complicating the Temperature API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.iankulin.com/how-to-deploy-a-node-js-app/"&gt;Using Node.js to serve a static file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.iankulin.com/how-to-deploy-a-node-js-app/"&gt;How to Deploy a Node.js App&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="nginx-configuration"&gt;nginx Configuration&lt;/h3&gt;
&lt;p&gt;Once nginx and node.js are installed (with &lt;a href="https://gist.github.com/IanKulin/bd6d1a78f9a9fa9a859384a26ca95235"&gt;the Ansible script&lt;/a&gt; if you want to rock the dev ops tattoo) you&amp;rsquo;ll need to configure nginx. On my Debian systems, the config file &lt;code&gt;nginx.conf&lt;/code&gt; is in &lt;code&gt;/etc/nginx/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a line in that file that includes all the &lt;code&gt;*.conf&lt;/code&gt; files in &lt;code&gt;/etc/nginx/conf.d&lt;/code&gt;. This is a common pattern I see in some distros - the main config files are not really meant to be messed with, but then there&amp;rsquo;s a directory to add config files to whihc are included. The theoretical advantage of this is that the distro maintainers can roll out a new version of a package and change the main config file, and your stuff will still work.&lt;/p&gt;
&lt;p&gt;The way they have done this with the nginx.conf means that the only changes we can make in the &lt;code&gt;conf.d&lt;/code&gt; directory are to do with virtual hosts, but that&amp;rsquo;s going to be 99% of the things we would want to change. So much so, I&amp;rsquo;m not even going to show you the &lt;code&gt;nginx.conf&lt;/code&gt; file, just our little &lt;code&gt;/etc/nginx/conf.d/nodeapi.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; server {
 listen 80;
 server_name 192.168.100.40;

 # Serve static files
 root /var/www;

 # pass api requests to node
 location /api {
 proxy_pass http://localhost:3000;
 proxy_set_header Host $host;
 }
 }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Okay, we are listening on port 80, and this &amp;ldquo;server block&amp;rdquo; is only for requests like http://192.168.100.40. The purpose of &lt;code&gt;server_name&lt;/code&gt; is that we might run the websites for several domains from one nginx installation. For example, we might be serving example.com and otherexample.com from the same VPS.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;root /var/www;&lt;/code&gt; - tells nginx to grab the files from that directory, so that&amp;rsquo;s the static web server part.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;location /api {&lt;/code&gt; - is telling nginx &amp;ldquo;all the requests with /api on the end are dealt with differently, look in this block for instructions&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;proxy_pass http://localhost:3000;&lt;/code&gt; - send them all to a server on this machine listening on port 3000. This is where the node/express server is running.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;proxy_set_header Host $host;&lt;/code&gt; - it doesn&amp;rsquo;t matter for the purposes of this api, but it&amp;rsquo;s often nice to tell the server being proxied who the real host receiving the request is. If we didn&amp;rsquo;t do this, the node app would only be able to see that &amp;ldquo;localhost&amp;rdquo; was making a request. By doing this, it knows the request was to the server running nginx, in this case 192.168.100.40, but usually a real domain name. This might be needed if our node app was also servicing more than one domain.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s it. We&amp;rsquo;re done. You do need to have your node app running on port 3000 on the same machine, but as long as that&amp;rsquo;s happening this should all be working. Do remember to restart nginx each time you make a config changes with &lt;code&gt;sudo service nginx restart&lt;/code&gt;, and it would also be good practice to check the config files with &lt;code&gt;sudo nginx&lt;/code&gt; -t before that.&lt;/p&gt;</description></item><item><title>Where to go after Reddit</title><link>https://blog.iankulin.com/where-to-go-after-reddit/</link><pubDate>Tue, 01 Aug 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/where-to-go-after-reddit/</guid><description>&lt;p&gt;A big chunk of my mindless doomscrolling used to go to Reddit, but also, Reddit posts from the various communities were frequently the useful results when googling error messages. I lurked in many a sub-reddit, but only posted in a couple - usually r/self-hosted or r/Homelab.&lt;/p&gt;
&lt;p&gt;The problematic treatment of the communities in the leadup to their IPO has been well publicised, and the short blackout by some subreddits seemed to have zero effect on the company&amp;rsquo;s approach to it&amp;rsquo;s users (which is in fact what they have to sell). Those subreddits, and many others are still working, but (and perhaps I&amp;rsquo;m imagining this) seem somehow thinner. Additionally, I feel like it&amp;rsquo;s a fragile arrangement - the company has shown how they will deal with their communities, so depending on them in the long term does not seem wise, or even, somehow, ethical - like I&amp;rsquo;m crossing a picket line.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a great pity. The format of asynchronous chats around bite-size topics that are promoted or demoted based on user interests is a great format for technology questions.&lt;/p&gt;
&lt;p&gt;In any case, if we are to abandon Reddit, in some sort of fuck-you to u/spez, where to go?&lt;/p&gt;
&lt;p&gt;Currently for me, the answer is probably Lemmy. This is a &amp;lsquo;federated&amp;rsquo; system like Masterdon. You can spin up your own instance, and other instances can decide to block you if they are not happy with your moderation policies - sort of the same as email. Communities (ie subreddits) live on a particular server, but you can subscribe to them from whichever server you have your account on. For example, I&amp;rsquo;m a member of the lemmy.world server where a lot of subreddits and redditors have washed up, but there&amp;rsquo;s a homelab community over at &lt;a href="https://lemmy.ml/c/homelab"&gt;lemmy.ml/c/homelab&lt;/a&gt;. When I&amp;rsquo;m signed in at &lt;a href="https://lemmy.world/"&gt;lemmy.world&lt;/a&gt;, I can access the homelab community - reading and posting - just as if it was hosted where I&amp;rsquo;m logged in.&lt;/p&gt;
&lt;p&gt;The Lemmy system will be familiar to ex-redditors. There&amp;rsquo;s upvotes and downvotes, mods, a sidebar with rules etc. I have noticed that many Lemmy communities have decided to try and use the change to make things a bit more civil. Stackoverflow behavior was never an issue in the selfhosted or homelab subreddits, but it certainly has been an issue elsewhere on the site. This slightly-nicer-on-Lemmy phenomenon is interesting, perhaps &lt;a href="https://www.garbageday.email/"&gt;Ryan Broderick&lt;/a&gt; will make sense of it. An extreme example might be that lemmynsfw.com has decide their rule number two is &amp;lsquo;respect and consent&amp;rsquo;. I don&amp;rsquo;t know (but it&amp;rsquo;s easy to imagine) this might not have been the case for NSFW subreddits.&lt;/p&gt;
&lt;p&gt;Not all communities are translated directly across to Lemmy, an example of that, that I&amp;rsquo;ve followed is that Jim Salter (who was a r/ZFS mod) has set up a Discourse server for &lt;a href="https://discourse.practicalzfs.com/c/openzfs/4"&gt;ZFS&lt;/a&gt; and &lt;a href="https://discourse.practicalzfs.com/c/openzfs/proxmox/7"&gt;Proxmox&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I mentioned internet technology journalist Ryan Broderick earlier. One of his theories is that the internet is going through a period where we will not really be having a &lt;a href="https://threadreaderapp.com/thread/1174694510527488000.html"&gt;common internet experience&lt;/a&gt;. With Twitter and Reddit being fractured like they have been in the recent past, that seems like a supportable theory. If it means the little different islands of, say, self-hosted fans are each smaller numbers of people, it does devalue the experience a little - expose each group of users to a smaller range of ideas and thoughts. The upside might be this opportunity to re-think what each community should feel like to be part of.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s been some speculation that the &lt;a href="https://www.youtube.com/watch?v=cXlxMP9PU8I"&gt;sudden decline in Stack Overflow&lt;/a&gt; is because ChatGPT has gobbled up all of it&amp;rsquo;s content, so it&amp;rsquo;s better to just ask ChatGPT your web development questions. It&amp;rsquo;s certainly the case that when I&amp;rsquo;ve been talking to it, ChatGPT has always been present to deal with, and never treated me like an idiot regardless of the noob mistakes I&amp;rsquo;m making. If new Lemmy (or other versions of) subreddits are going to be nicer, I&amp;rsquo;m all for it.&lt;/p&gt;
&lt;p&gt;Doubtless users will slowly vote with their feet, and the situation will evolve over time, but for the moment, I&amp;rsquo;ll be at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://lemmy.world/"&gt;lemmy.world&lt;/a&gt; - general stuff&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lemmy.world/c/selfhosted"&gt;lemmy.world/c/selfhosted&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lemmy.ml/c/webdev"&gt;lemmy.ml/c/webdev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lemmy.ml/c/homelab"&gt;lemmy.ml/c/homelab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://discourse.practicalzfs.com/c/openzfs/proxmox/"&gt;discourse.practicalzfs.com/c/openzfs/proxmox/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m still reading Twitter/X - not so much for the tech stuff - most of the tech folk I follow have moved to Masterdon, but for a couple of sassy-quipping influencers who haven&amp;rsquo;t made the leap, and for the education accounts I follow who don&amp;rsquo;t seemed to have migrated at all.&lt;/p&gt;</description></item><item><title>ZFS Basics on Proxmox</title><link>https://blog.iankulin.com/zfs-basics-on-proxmox/</link><pubDate>Sat, 29 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/zfs-basics-on-proxmox/</guid><description>&lt;p&gt;I&amp;rsquo;m a keen listener of the &lt;a href="https://2.5admins.com/"&gt;2.5 Admins&lt;/a&gt; podcast in which there&amp;rsquo;s frequent enumeration of the advantages of &lt;a href="https://itsfoss.com/what-is-zfs/"&gt;ZFS&lt;/a&gt; as a file system. So much so, that I&amp;rsquo;ve had occasional twinges or regret about the money I spent on the Synology - although it has been boringly reliable and does everything I need.&lt;/p&gt;
&lt;p&gt;Proxmox has some built in support for ZFS, including through the web GUI. So I&amp;rsquo;ve been itching to give it a try.&lt;/p&gt;
&lt;p&gt;I had a 256GB M2 NVME sitting around - I bought it with the plan to try it as the root drive in one of the servers. That was when I was worried that one of the servers&amp;rsquo; drives was about dead because the SMART data said it was at 100% use. I&amp;rsquo;ve since discovered that various companies interpret that different ways, so probably it&amp;rsquo;s 100% okay.&lt;/p&gt;
&lt;p&gt;I started to think a little &lt;a href="https://www.techtarget.com/searchstorage/definition/JBOD"&gt;JBOD&lt;/a&gt; with a couple of NVME SSD mirrored drives would be a fun project. There&amp;rsquo;s no way I could do that inside the case to get the proper PCI access, but the my HP 800 G2&amp;rsquo;s all have USB 3 so it shouldn&amp;rsquo;t be terrible - probably a lot better than the spinning rust NAS over 1GB Ethernet.&lt;/p&gt;
&lt;p&gt;I purchased this little UNITEK S1206A dual bay enclosure and another stick of 256GB Samsung SSD.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/img_5742.jpg" width="600" alt=""&gt;
&lt;img src="https://blog.iankulin.com/images/s-l960.jpg" width="600" alt=""&gt;
&lt;p&gt;The instructions for the unit show sticking a layer of silicon over the top of the gum sticks, and then a thin piece of aluminum. I&amp;rsquo;ve heard these get hot, but it wasn&amp;rsquo;t clear to me if I should peel that paper off first. So I&amp;rsquo;ve done nothing for the moment while I do some more research.&lt;/p&gt;
&lt;p&gt;The process of getting it set up in Proxmox was simple. If you select the node in the web interface, and go in to &lt;em&gt;Disks&lt;/em&gt;, you can see a list of the physical disks attached. The NVME drives showed up as NTFS so I wiped them by selecting the drive and pressing the &lt;em&gt;Wipe Disk&lt;/em&gt; button.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-6.19.00-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;You can see on the screenshot above, that further down in the Disks list it says ZFS. That&amp;rsquo;s where you go to create the ZFS pool. I probably need to pause here, and go over some of the ZFS terminology.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.victormendonca.com/2020/11/03/zfs-for-dummies/"&gt;&lt;img src="https://blog.iankulin.com/images/zfs-components-1.png" width="706" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To start in the middle, we have the concept of a ZFS Pool. This is, well, a pool of storage that&amp;rsquo;s available to be used. It has a size, and we can see how much space is available. The pool is made up of vdev (virtual devices). A vdev could be a single physical drive, or multiple drives in some kind of RAID arrangement.&lt;/p&gt;
&lt;p&gt;In my situation, with the two NVME drives, my zpool will be made up of a single vdev comprising two physical drives which have been mirrored.&lt;/p&gt;
&lt;p&gt;In the zpool, we can create &lt;em&gt;datasets&lt;/em&gt; where we can actually put some data. You can think of these as directories in the sense they have a name and we can create directories and store data inside them, but in ZFS, the datasets in a zpool can have different settings (such as compression, de-duplication) applied to them. This is also the level where snapshots can be taken for backups.&lt;/p&gt;
&lt;p&gt;To create the ZFS pool in Proxmox, again select the node, then select ZFS in the list under &lt;em&gt;Disks&lt;/em&gt;. At the top is a button for &lt;em&gt;Create ZFS&lt;/em&gt;. Select the wiped drives, chose your RAID and give it a name. By tradition the pools are usually called &amp;rsquo;tank&amp;rsquo; - if you look at a few tutorials you&amp;rsquo;ll see that all over the place.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-8.26.42-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Once that was done, tank appeared as storage in the list under my node. I moved the drives of these dev guests across to it so the zpool would have something to do. I did notice that this process would rush through, then pause for a few seconds - something I haven&amp;rsquo;t noticed when moving guest droves between the NAS and internal SSDs. Early reviews of Samsung pm981 NVME SSD &lt;a href="https://www.tomshardware.com/reviews/samsung-pm981-980-nvme-ssd,5323.html"&gt;noted a sustained write dropoff&lt;/a&gt;, so this might be something to come back and have a look at later.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-8.23.10-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;If we drop into the shell now, we can have a look at the datasets.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@pve-prod1:/# zfs list
NAME USED AVAIL REFER MOUNTPOINT
tank 32.0G 199G 96K /tank
tank/vm-300-disk-0 16.5G 209G 6.04G -
tank/vm-321-disk-0 5.16G 202G 1.67G -
tank/vm-322-disk-0 5.16G 202G 1.62G -
tank/vm-323-disk-0 5.16G 202G 1.60G -
root@pve-prod1:/# 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So my pool is &lt;code&gt;tank&lt;/code&gt;, and there&amp;rsquo;s been datasets created for each of the VM guests&amp;rsquo; disks. We can create a data set to start using the pool as well.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;zfs create tank/temp_set
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That creates a dataset called &lt;code&gt;temp_set&lt;/code&gt; in the &lt;code&gt;tank&lt;/code&gt; zpool. It will have been mounted for us too. Let&amp;rsquo;s create a 1 GB file in there.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@pve-prod1:/# cd /tank/temp_set
root@pve-prod1:/tank/temp_set# head -c 1G &amp;lt;/dev/urandom &amp;gt;myfile
root@pve-prod1:/tank/temp_set# ls
myfile
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then if we list the datasets again.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@pve-prod1:/tank/temp_set# zfs list
NAME USED AVAIL REFER MOUNTPOINT
tank 33.0G 198G 104K /tank
tank/temp_set 1.00G 198G 1.00G /tank/temp_set
tank/vm-300-disk-0 16.5G 208G 6.04G -
tank/vm-321-disk-0 5.16G 201G 1.67G -
tank/vm-322-disk-0 5.16G 201G 1.62G -
tank/vm-323-disk-0 5.16G 201G 1.60G -
root@pve-prod1:/tank/temp_set# 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once you&amp;rsquo;ve created a dataset, you can just use it as a regular place to store stuff. ZFS will go on doing it&amp;rsquo;s magic in the background to keep your data safe with copy-on-write and other magic. It&amp;rsquo;s good ZFS practice to do a &lt;em&gt;scrub&lt;/em&gt; every now and then. This causes ZFS to use whatever information it&amp;rsquo;s got to check the integrity of all your data.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@pve-prod1:/# zpool scrub tank

root@pve-prod1:/# zpool status -v tank
 pool: tank
 state: ONLINE
 scan: scrub repaired 0B in 00:00:53 with 0 errors on Tue Jul 4 20:58:16 2023
config:

 NAME STATE READ WRITE CKSUM
 tank ONLINE 0 0 0
 mirror-0 ONLINE 0 0 0
 sdb ONLINE 0 0 0
 sdc ONLINE 0 0 0

errors: No known data errors
root@pve-prod1:/# 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While I&amp;rsquo;ve been writing this post, I&amp;rsquo;ve been copying data to and fro (I&amp;rsquo;d try things out, then have to delete and repeat to get the screen shot I wanted, and at one stage I decided to change the name of the zpool so all the disk images had to be moved off then back on after I&amp;rsquo;d recreated it etc) for about 90 minutes, and I&amp;rsquo;ve just been in the server rooms to see if the external NVME enclosure is hot. It&amp;rsquo;s warm to the touch, I&amp;rsquo;d guess 40° - so not alarming for this level of use. That box is pretty well ventilated.&lt;/p&gt;
&lt;p&gt;If you want a good summary of ZFS, particularly the thinking behind it, this is a great overview.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/lsFDp-W1Ks0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;
</description></item><item><title>First Ansible Playbook</title><link>https://blog.iankulin.com/first-ansible-playbook/</link><pubDate>Wed, 26 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/first-ansible-playbook/</guid><description>&lt;p&gt;In the &lt;a href="https://blog.iankulin.com/getting-started-with-ansible/"&gt;previous post&lt;/a&gt;, we looked at getting up and running with Ansible, including using the ad-hoc mode to send commands to our servers. We had a inventory file called hosts that had groups of server IP addresses and a simple &lt;code&gt;ansible.cfg&lt;/code&gt; file that pointed to our inventory file.&lt;/p&gt;
&lt;h3 id="playbooks"&gt;Playbooks&lt;/h3&gt;
&lt;p&gt;Ansible playbooks are used to collect together a description of the state we want in a server. When the playbook is executed, Ansible figures out what things need need changed, and changes them. If you&amp;rsquo;re used to the procedural nature of a bash script, where things proceed from one step to the next, and there might be decision branches, this requires an adjustment in your thinking. This is similar to the adjustment I had getting my head around &lt;a href="https://betterprogramming.pub/swiftui-understanding-declarative-programming-aaf05b2383bd"&gt;SwiftUI&lt;/a&gt;, and moving from JS to &lt;a href="https://levelup.gitconnected.com/why-react-is-declarative-a300d1e930b7?gi=3d11485226b4"&gt;React&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before we dive in and look at a playbook, I should probably say a couple of things about the &lt;a href="https://www.tutorialspoint.com/yaml/yaml_basics.htm"&gt;YAML&lt;/a&gt; format used for these files. It&amp;rsquo;s yet another attempt to strike a compromise between human readable and machine processable files. Spacing is important, it doesn&amp;rsquo;t like tabs, it&amp;rsquo;s case sensitive, and begins with three hyphens. The rest, you&amp;rsquo;ll figure out.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the files we currently have in our working directory:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.11.16-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.11.16-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.11.02-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.11.02-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The config file just specifies our inventory file, and the inventory file (named &lt;code&gt;hosts&lt;/code&gt;) lists the servers in groups, and provides some &lt;em&gt;variables&lt;/em&gt; for the servers.&lt;/p&gt;
&lt;p&gt;Our web servers are going to need something to serve web pages. Let&amp;rsquo;s write a playbook to ensure they have NGINX installed. If you don&amp;rsquo;t know what &lt;a href="https://www.nginx.com/"&gt;NGINX&lt;/a&gt; is, don&amp;rsquo;t worry about it, it&amp;rsquo;s a web server.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.38.27-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.38.27-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;- name:&lt;/code&gt; - YAML files are hierarchical. Ansible YAML files are a collection of &lt;em&gt;plays&lt;/em&gt;. This file only has one play, named &amp;ldquo;nginx for all web servers&amp;rdquo;. All the plays will be at the top level like this starting with a single hyphen in column 1. Names are great; pick good ones and you won&amp;rsquo;t need much in the way of comments. These names also appear in the output, helping anyone using the playbook to understand what&amp;rsquo;s happening.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hosts: web&lt;/code&gt; - tells Ansible that we are only running this play against our &lt;code&gt;web&lt;/code&gt; servers. These are defined in the &lt;code&gt;hosts&lt;/code&gt; file that we kept from the last post. If you look back up at the top for that file, you can see we&amp;rsquo;re specifying it for 192.168.100.37 and 192.168.100.38&lt;/p&gt;
&lt;p&gt;&lt;code&gt;become: yes&lt;/code&gt; - To install packages with &lt;code&gt;apt&lt;/code&gt;, we need to &lt;code&gt;sudo&lt;/code&gt;. &lt;code&gt;become yes&lt;/code&gt; is telling Ansible that we need to do this. I guess if we were already the &lt;code&gt;root&lt;/code&gt; user we wouldn&amp;rsquo;t have to do that, but in our &lt;code&gt;hosts&lt;/code&gt; file, we&amp;rsquo;ve said to use the user &lt;code&gt;ian&lt;/code&gt; so &lt;code&gt;ssh&lt;/code&gt; in. We&amp;rsquo;ll see later how to deal with needing the password for this escalation.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tasks:&lt;/code&gt; - In our hierarchy, each &lt;em&gt;play&lt;/em&gt; consists of a number of &lt;em&gt;tasks&lt;/em&gt;. Here&amp;rsquo;s our list of tasks. Because we&amp;rsquo;re changing levels, they&amp;rsquo;ll be indented.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;- name:&lt;/code&gt; - This time, it&amp;rsquo;s the name of the task. Again, pick good ones.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;apt:&lt;/code&gt; Ansible functionality is organised according to modules. Here we are saying we are going to use the &lt;a href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/apt_module.html"&gt;apt module&lt;/a&gt;. &lt;code&gt;apt&lt;/code&gt; is the command to install packages on Debian flavoured systems&lt;/p&gt;
&lt;p&gt;&lt;code&gt;name : nginx&lt;/code&gt; - This time, it&amp;rsquo;s the name of the package we want installed. It&amp;rsquo;s the same name as you would have used if using &lt;code&gt;apt&lt;/code&gt; manually.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;state: latest&lt;/code&gt; - we&amp;rsquo;re saying we want the nginx package to be installed, and we want it to be the latest one. This is were you should really be noticing the declarative nature of the playbook. We could also say &lt;code&gt;state: absent&lt;/code&gt; and Ansible would uninstall it if it was installed, or &lt;code&gt;state: present&lt;/code&gt; in which case Ansible would just check it&amp;rsquo;s there but not worry about the version.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;- name:&lt;/code&gt; - Okay, we&amp;rsquo;re back up at the lists of tasks level. Here&amp;rsquo;s the name of a new task.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;service:&lt;/code&gt; In the previous task we were using the apt module. This task is going to use the &lt;a href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/service_module.html"&gt;service module&lt;/a&gt;. If you&amp;rsquo;re wondering how you get to know what things are in each module, the &lt;a href="https://docs.ansible.com/ansible/latest/module_plugin_guide/index.html#"&gt;documentation at Ansible&lt;/a&gt; is pretty great. Sometimes you&amp;rsquo;ll get pretty close by thinking of what you&amp;rsquo;re doing. In this case, we want to check if the NGINX service is running, and if not start it - so it&amp;rsquo;s logical that the module we want is going to be &lt;code&gt;service&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;name : nginx&lt;/code&gt; - This time, it&amp;rsquo;s the name of the service.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;state: started&lt;/code&gt; - declaratively saying what we want the state of the NGINX service to be.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;enabled: yes&lt;/code&gt; - it will also be started on a reboot.&lt;/p&gt;
&lt;p&gt;Phew. Okay. We want NGINX to be installed on these machines, and for the service to be running and for that to still be the case after a reboot. Let&amp;rsquo;s run this playbook and see what happens. Here&amp;rsquo;s the command we&amp;rsquo;re going to do that with.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ansible-playbook web_installs.yaml --ask-become-pass
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;--ask-become-pass&lt;/code&gt; piece of this command is telling Ansible to ask us for the password for this user so it can have sudo privileges to install things. We could have just added the password in the hosts file like we have the user name, but that would be quite insecure. Especially when we push our code up to github. Scanning pubic github commits for passwords and API keys is a popular pastime.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.38.19-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;After asking me for the password, Ansible has correctly identified the two servers and gathered facts from them. The facts are a lot of information that&amp;rsquo;s then stored in variables that we can then use in our playbooks. For example this playbook is assuming a Linux distro that uses the apt package manager. If we wanted to check for that, one of the facts variables would contain the distro name and we could use that to conditionally use apt or some other package manager.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ve probably noticed the colours. Green messages mean something&amp;rsquo;s in the correct state, yellow means it wasn&amp;rsquo;t in the correct state before, but is now, and red means it&amp;rsquo;s not in the correct state, and couldn&amp;rsquo;t be made to be for some reason.&lt;/p&gt;
&lt;p&gt;Since this is the first time this playbook&amp;rsquo;s been run against these servers, we expected the &amp;rsquo;nginx installed&amp;rsquo; tasks to be yellow for both servers. The highlighted IP address under &amp;rsquo;nginx running&amp;rsquo; is just because I was copying it to go and check the web server was working. Let&amp;rsquo;s have a look.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-3.36.22-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Well done Ansible.&lt;/p&gt;
&lt;p&gt;In regard to those yellow messages where Ansible found that NGINX wasn&amp;rsquo;t installed, so it went ahead and installed them, you might be thinking &amp;ldquo;if we run the playbook again, shouldn&amp;rsquo;t they be green?&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.50.42-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;So that&amp;rsquo;s our first playbook done. We&amp;rsquo;ve only learned the commands for installing packages and working with services, but Ansible can do pretty much anything. Certainly anything you can do by sshing in and running a script of some kind. I don&amp;rsquo;t think I want to go any further with trying to show the range of things that can be accomplished (although it is tempting to now install a web page into our servers) - it makes more sense for you to just find what you need as you need it.&lt;/p&gt;
&lt;p&gt;There is however one problem I ran into almost immediately and couldn&amp;rsquo;t find a simple description of that I&amp;rsquo;ll cover in the next post. Every Saturday morning, I ssh into my local and remote servers (15 of them) and run &lt;code&gt;apt update&lt;/code&gt; and &lt;code&gt;apt upgrade&lt;/code&gt;. You can see from the yaml above, that&amp;rsquo;s going to be quite easy to automate with Ansible and save me heaps of time and effort. My problem is - all my servers have unique user names and passwords. It&amp;rsquo;s not possible to just add a &amp;ndash;ask-become-pass to my command; that would only work for the first one.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll look at how to solve that securely in a future post.&lt;/p&gt;</description></item><item><title>Proxmox 8.0 Install</title><link>https://blog.iankulin.com/proxmox-8-0-install/</link><pubDate>Sun, 23 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/proxmox-8-0-install/</guid><description>&lt;p&gt;I&amp;rsquo;m normally a x.1 release type of sysadmin, but the increasing temptation of installing Proxmox 8.0 while I&amp;rsquo;ve got some time off, and the fact that I&amp;rsquo;ve got a cluster, so I can just move the VM&amp;rsquo;s around all adds up to thinking I&amp;rsquo;ll do that today.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/cluster-2.png" width="328" alt=""&gt;
&lt;p&gt;Here&amp;rsquo;s how my system works. It consists of three HP-800 mini G2&amp;rsquo;s. &lt;code&gt;pve-prod1&lt;/code&gt; is a bit fancier - i7 6700T and 32GB, the other two are i5 6500T and 16GB. The production VM&amp;rsquo;s use the local SSD but backups go to the NAS. All the machines are currently running Proxmox 7.4. They are not clustered in the proper sense - I don&amp;rsquo;t need high availability, and I don&amp;rsquo;t want to run them all the time. &lt;code&gt;pve-prod1&lt;/code&gt; runs 24/7 and I just power up &lt;code&gt;pve-dev1&lt;/code&gt; when I&amp;rsquo;m working on something.&lt;/p&gt;
&lt;p&gt;The intention is that although I&amp;rsquo;m not on high availability, I can quickly come back from a machine failure by powering &lt;code&gt;pve-prod2&lt;/code&gt; up and restoring from the latest VM backup from the NAS. &lt;code&gt;pve-prod1&lt;/code&gt; does not have a full load yet (I&amp;rsquo;m slowly cancelling cloud services and moving them in-house) but once it does, I&amp;rsquo;d have the capacity to fully replace it by sharing any guests between &lt;code&gt;pve-prod2&lt;/code&gt; and &lt;code&gt;pve-dev1&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="migration-plan"&gt;Migration plan&lt;/h3&gt;
&lt;img src="https://blog.iankulin.com/images/migration-1.png" width="273" alt=""&gt;
&lt;p&gt;Currently &lt;code&gt;pve-prod1&lt;/code&gt; is only running two guests, jellyfin, and a docker host with a collection of smallish services. The plan is to move those to &lt;code&gt;pve-prod2&lt;/code&gt;, check everything is working, then install the new Proxmox 8 onto &lt;code&gt;pve-prod1&lt;/code&gt;. Apart from giving me the opportunity to do that, it&amp;rsquo;s a good test of the plan for recovering from a &lt;code&gt;pve-prod1&lt;/code&gt; failure. I&amp;rsquo;ll live off it for a few days to ensure that it&amp;rsquo;s a viable process.&lt;/p&gt;
&lt;p&gt;A small hitch with this is that the RAM in &lt;code&gt;pve-prod1&lt;/code&gt; cost me $100, and I didn&amp;rsquo;t want to not use it, so I created the jellyfin VM with 16GB RAM. It&amp;rsquo;s a simple matter to stop it, give it less, and restart it - except it seems to be using it all.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-7.31.59-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;You can see from this, I tried shutting it down and restarting - thinking that the memory use might climb up slowly as the app was used, but it just went straight back to 15GB. In a way, I approve of a VM using the memory I&amp;rsquo;ve given it - presumably it is caching or something. Jellyfin should certainly be able to run on a machine with much less memory, so I suppose I&amp;rsquo;ll stop it, back it up, and try it in a smaller VM.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-7.42.58-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Yep, that works fine. And I can&amp;rsquo;t notice any difference in the app performance. So I stopped it, backed it up, and restored onto prod2. And immediately bumped into a couple of problems when I tried to start it.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-8.52.34-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;There was two hardware incompatibilities - the first was that on prod1 I had passed through the GPU from the host (in an unsuccessful attempt to use quicksync hardware transcoding for video). I don&amp;rsquo;t need that, so that gets deleted out of the &amp;lsquo;hardware&amp;rsquo; for the VM.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-8.47.00-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;And the second was that I still had the Debian 11 ISO mounted in the &amp;lsquo;cd-rom&amp;rsquo;. Lol - the Debian installer specifically tells you to remove this before it reboots. That can be removed exactly as I had done for the GPU pass through, and the VM boots fine, and the app tests out ok.&lt;/p&gt;
&lt;p&gt;The first time I ever did this - move a guest VM from one lot of hardware to another, then boot it up and all my apps are working perfectly on their old IP addresses - I was amazed and danced around in excitement. I didn&amp;rsquo;t dance today, but it is so cool.&lt;/p&gt;
&lt;p&gt;Interestingly, it&amp;rsquo;s decided to use much less RAM now. I caused that increase at the end of the graph by rescanning the media library, then browsing through all the titles so the cover images would have to be loaded - so perhaps it&amp;rsquo;s the web server caching them all. It&amp;rsquo;s hard to know for sure without some objective measurements, but I suspect the app was crisper and more responsive than before. In any case, it certainly wasn&amp;rsquo;t any worse.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-9.02.56-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Moving the docker host over was straightforward and only took five minutes of downtime as it&amp;rsquo;s a smaller image. I guess a lot of that time is just my 1GB network limitation or the spinning disk transfer speed from the NAS - the docker hoats was 4GB and Jellyfin 14GB.&lt;/p&gt;
&lt;h3 id="nuke-and-pave"&gt;Nuke and pave&lt;/h3&gt;
&lt;p&gt;I try and keep my hosts very clean, so wiping them and starting over is no biggie, but since this node has been up I have installed a chron job for &lt;a href="https://blog.iankulin.com/linux-shell-script-for-temperature-logging/"&gt;temperature logging&lt;/a&gt;. I&amp;rsquo;ve documented that in a blog post so I&amp;rsquo;ll be able to recreate it, but this sort of thing is the reason I&amp;rsquo;m interested in &lt;a href="https://blog.iankulin.com/getting-started-with-ansible/"&gt;Ansible&lt;/a&gt;. Another project while I&amp;rsquo;ve got some time will be to recreate that on the new machine with Ansible so it&amp;rsquo;s trivial to restore in future. I pulled the temperature log file down though - because who doesn&amp;rsquo;t like eighty thousand data points.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/temp1.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;There is a &lt;a href="https://pve.proxmox.com/wiki/Upgrade_from_7_to_8"&gt;published process to upgrade Proxmox&lt;/a&gt; from 7.x to 8, so I briefly considered it, but fresh installs are generally less likely to lead to drama, especially this early in the major release cycle. Plus, I keep my installs clean to allow it - this is a freedom allowed by my sysadmin discipline along with the investment in redundant hardware so there&amp;rsquo;s zero time pressure while I&amp;rsquo;m doing it.&lt;/p&gt;
&lt;h3 id="run-book-for-new-proxmox-install"&gt;Run Book for New Proxmox Install&lt;/h3&gt;
&lt;p&gt;My install process for Proxmox goes something like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Flash the ISO onto a USB drive with &lt;a href="https://etcher.balena.io/"&gt;Balena Etcher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Plug in the USB drive, my bluetooth keyboard/mouse USB, and the screen - I&amp;rsquo;ve got a special long HDMI cord that reaches from my desk to the servers&lt;/li&gt;
&lt;li&gt;Boot up, mashing the boot menu key (F9 on my G2&amp;rsquo;s)&lt;/li&gt;
&lt;li&gt;Follow my nose through the prompts - since this is an existing server, the DHCP serves up the correct IP address&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ssh&lt;/code&gt; into it to check everything&amp;rsquo;s fine. Since this IP was already in my known hosts file, I had to go an delete it out&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ssh-copy-id&lt;/code&gt; to get my ssh keys across&lt;/li&gt;
&lt;li&gt;Update the repositories - by default, Proxmox comes set up to use with a subscription. I wish they had a lower tier and I&amp;rsquo;d by one since it gives me so much joy - even if it didn&amp;rsquo;t remove the nags. In the meantime, you can follow the instructions &lt;a href="https://pve.proxmox.com/wiki/Package_Repositories#sysadmin_no_subscription_repo"&gt;here&lt;/a&gt; to set it up to use the non-subscription repoistories:
&lt;ul&gt;
&lt;li&gt;edit &lt;code&gt;/etc/apt/sources.list&lt;/code&gt; to add &lt;code&gt;deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;edit &lt;code&gt;/etc/apt/sources.list.d/pve-enterprise.list&lt;/code&gt; to comment out the line in there&lt;/li&gt;
&lt;li&gt;and a new one that&amp;rsquo;s not mentioned on that wiki page, edit &lt;code&gt;/etc/apt/sources.list.d/ceph.list&lt;/code&gt; to comment out the line in there. I don&amp;rsquo;t know where that leaves you if you are using Ceph (which is a cool file system if you&amp;rsquo;re using high availability) but I&amp;rsquo;m not, so all good. If you don&amp;rsquo;t do this, you&amp;rsquo;ll get errors like &lt;code&gt;E: Failed to fetch https://enterprise.proxmox.com/debian/ceph-quincy/dists/bookw orm/InRelease 401 Unauthorized IP: 103.76.41.50 4431 E: The repository &amp;quot;https://enterprise.proxmox.com/debian/ceph-quincy bookworm In Release' is not signed.&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Run the updates with &lt;code&gt;apt update&lt;/code&gt; &amp;amp;&amp;amp; &lt;code&gt;apt upgrade&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Install the certificate - you need SSL setup for the web interface if you want Chrome to let it save your password, which I do. Also the red &lt;em&gt;insecure&lt;/em&gt; message bugs me
&lt;ul&gt;
&lt;li&gt;Log into the web interface at https://&lt;ip address&gt;:8006 - you&amp;rsquo;ll need to jump through all those hoops to take on the responsibility of opening an unsecured site&lt;/li&gt;
&lt;li&gt;If you click on the node, then certificates&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-12.08.29-pm.png" alt=""&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;You can open up that certificate, and copy out the raw certificate, paste it into a text editor and save it somewhere. I drag that into my macOS keychain app. It shows up with a red cross, but if you open it up you can mark it as &amp;ldquo;always trust&amp;rdquo;&lt;/li&gt;
&lt;li&gt;We&amp;rsquo;re not done yet, now back in Chrome, click on the &lt;em&gt;insecure&lt;/em&gt; message next to the URL. Go into &lt;em&gt;Site Settings&lt;/em&gt; | &lt;em&gt;Insecure Content&lt;/em&gt; and change it to &lt;em&gt;Allow&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Almost there - at the top of those settings is a button to clear the cache, do that&lt;/li&gt;
&lt;li&gt;Reload the page. Profit.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Then I &lt;a href="https://tailscale.com/kb/1031/install-linux/"&gt;install Tailscale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Last of all, add my NAS to the storage. I use NFS. The only trick here is to go into the dropdown of what type of content is on that storage, and select everything&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-12.17.35-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s it. Nice new Proxmox. I&amp;rsquo;ll leave my production VM&amp;rsquo;s on pve-prod2 for a week, and move all of my dev work over to this machine so it gets some exercise before I upgrade the other machines.&lt;/p&gt;
&lt;h3 id="tailscale"&gt;Tailscale&lt;/h3&gt;
&lt;p&gt;The only small issue I ran into (apart from the Ceph repository) was I couldn&amp;rsquo;t access the machine via it&amp;rsquo;s &amp;ldquo;magic DNS&amp;rdquo; Tailscale name. Since it was going to be the same name as a machine in my existing network, I&amp;rsquo;d thought ahead and deleted the old one out via the &lt;a href="https://login.tailscale.com/admin/machines"&gt;Tailscale machines&lt;/a&gt; page, but even so, it wouldn&amp;rsquo;t connect from my laptop.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-11.45.38-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I assume the old Tailscale IP address was cached somewhere, and fixed it by turning Tailscale off and on again on my laptop.&lt;/p&gt;</description></item><item><title>Getting Started with Ansible</title><link>https://blog.iankulin.com/getting-started-with-ansible/</link><pubDate>Wed, 19 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/getting-started-with-ansible/</guid><description>&lt;p&gt;Ansible is a system for executing commands on remote systems. It allows a declarative approach - so if you run a playbook (the system configuration files are called playbooks) that says a system has a Docker container running Jellyfin, Ansible will check if that&amp;rsquo;s true, and if not, make it so. Ansible is best used when you have a large number of systems to maintain, but even with a small number, it serves to document systems as well as to automate their creation.&lt;/p&gt;
&lt;p&gt;Since, with Ansible, system configurations can be completely described, it&amp;rsquo;s a step in the journey to &amp;ldquo;infrastructure as code&amp;rdquo; and allows infrastructure to be version controlled, and lends itself to Git-Ops where you push a change to a playbook file, and it&amp;rsquo;s executed to make that description of the configuration reality on your servers. The list of servers is stored in a file called the inventory.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=wgQ3rHFTM4E"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-02-at-11.28.10-am.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve considered implementing it a couple of times, but put it off as soon as I started looking at these complicated yaml files. Jeff Geerling&amp;rsquo;s &lt;a href="https://www.ansiblefordevops.com/"&gt;&amp;ldquo;Ansible for DevOps&amp;rdquo;&lt;/a&gt; seemed like the perfect place to start, but then he uses Vagrant and VirtualBox in his early examples, and Vagrant&amp;rsquo;s integration with Ansible means things are not being done in a standard way and I couldn&amp;rsquo;t follow along without mirroring his setup. I don&amp;rsquo;t want to run VM&amp;rsquo;s on my laptop, I want to use my homelab VMs or a VPS - both of which I think would be a more common setup.&lt;/p&gt;
&lt;p&gt;This mini guide is just a start. I&amp;rsquo;ll step through to the point where you have a yaml file describing a system configuration that can be applied to a VM to install some software. After that, you probably want to buy Jeff&amp;rsquo;s book, hit up some &lt;a href="https://www.youtube.com/watch?v=wgQ3rHFTM4E"&gt;good v&lt;/a&gt;ideos, or head to the &lt;a href="https://docs.ansible.com/"&gt;Ansible documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="prerequisites"&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-02-at-11.16.33-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-02-at-11.16.33-am.png" width="118" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For this to be helpful to you, you probably need to have been mucking about running Linux servers. You know how to ssh into them and have set up key pairs to allow that without typing your password each time. You can write a bash script (but don&amp;rsquo;t want to), You know how to install software with apt/yum/pip/homebrew etc. You should go and install it now. Note that you also need python (preferably 3) on the host.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve saved a run book of the things you need to do to recreate particular setups or deal with common issues, then you are at the exact point that Ansible is going to make your life better.&lt;/p&gt;
&lt;p&gt;Get &lt;a href="https://docs.ansible.com/ansible/latest/installation_guide/installation_distros.html"&gt;Ansible installed&lt;/a&gt;, you do need an up to date Python. You also need to have ssh set up for each of the nodes (servers) you are going to manage, preferably including using keys rather than passwords.&lt;/p&gt;
&lt;h3 id="starting-concepts"&gt;Starting Concepts&lt;/h3&gt;
&lt;p&gt;Ansible can execute &lt;em&gt;playbooks&lt;/em&gt; which are yaml files setting out the actions needed or final state of the node to be achieved. Alternatively, single commands can be executed from the command line in &amp;lsquo;ad-hoc mode&amp;rsquo;. When setting things up, ad-hoc mode is a good starting place to check you&amp;rsquo;ve installed everything correctly since it&amp;rsquo;s simpler.&lt;/p&gt;
&lt;p&gt;Ansible &lt;em&gt;modules&lt;/em&gt; are bits of code to support particular pieces of functionality. You could think of them as code libraries. For example, there&amp;rsquo;s an &lt;code&gt;apt&lt;/code&gt; module that enables Ansible to execute commands related to package management on the Debian family of Linux distros. Similar to code libraries, you&amp;rsquo;ll need to know which library is needed for the functionality you want to use. Luckily, Ansible&amp;rsquo;s documentation is excellent, and as with your programming, you&amp;rsquo;ll soon become familiar with the ones you use all the time.&lt;/p&gt;
&lt;h3 id="demo-environment"&gt;Demo Environment&lt;/h3&gt;
&lt;p&gt;For the following examples, I&amp;rsquo;ve set up three virtual machines (VM&amp;rsquo;s) 192.168.100.37 - 192.168.100.39 running Debian. I use Proxmox on my servers, so it looks a bit like this.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-03-at-6.46.26-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re trying things from a single machine, you could install something like &lt;a href="https://www.virtualbox.org/"&gt;VirtualBox&lt;/a&gt; to create VM&amp;rsquo;s, or I&amp;rsquo;d probably recommend just commissioning a VPS on &lt;a href="https://www.linode.com/lp/podcasts/?ifso=ssh&amp;amp;utm_source=podcast&amp;amp;utm_medium=audio&amp;amp;utm_campaign=ssh"&gt;Linode&lt;/a&gt; or &lt;a href="https://cloud.digitalocean.com/registrations/new"&gt;Digital Ocean&lt;/a&gt;. They both have deals whereby you get a dollar amount credit for signing up, for the minimal machine you need to try these things out, you&amp;rsquo;re probably looking at a cost of $0.30 an hour. I&amp;rsquo;m in Australia, so my VPS&amp;rsquo;s are on &lt;a href="https://www.binarylane.com.au/register"&gt;Binary Lane&lt;/a&gt; which costs less that AUD5 a month for a low-end instance.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re running against multiple machines, you&amp;rsquo;ll make your life easier by having the same user name on each one. For example, the commands I use to &lt;code&gt;ssh&lt;/code&gt; into mine are:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ssh ian@192.168.100.37
ssh ian@192.168.100.38
ssh ian@192.168.100.39
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="inventory"&gt;Inventory&lt;/h3&gt;
&lt;p&gt;Ansible has the concept of an &lt;em&gt;Inventory&lt;/em&gt;. The Inventory is a text file of the servers/nodes (I&amp;rsquo;m just going to say nodes from now on). We need this inventory whether using playbooks or ad-hoc commands. Here&amp;rsquo;s mine, which I&amp;rsquo;ve saved in the directory I&amp;rsquo;m working from as &lt;code&gt;hosts&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;192.168.100.37
192.168.100.38
192.168.100.39
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that these could also be domain names if your nodes are set up on DNS.&lt;/p&gt;
&lt;h3 id="first-command"&gt;First Command&lt;/h3&gt;
&lt;p&gt;Finally, we&amp;rsquo;re at the point we can run something. Let&amp;rsquo;s try this command to find the host name of each node. There&amp;rsquo;s a lot going on, so we&amp;rsquo;ll break it down after we&amp;rsquo;ve looked at the output.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ansible -i hosts all -u ian -a &amp;#34;hostname&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-03-at-7.41.00-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s break down all those arguments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-i hosts&lt;/code&gt; - the inventory flag points to the inventory file. In my example the file is named &amp;ldquo;hosts&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;all&lt;/code&gt; - we&amp;rsquo;re saying to execute this against all of the nodes in the &lt;code&gt;hosts&lt;/code&gt; file. Later on we&amp;rsquo;ll see how to separate the nodes into groups inside the inventory file and this will make more sense.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-u ian&lt;/code&gt; - the ssh user name for each node&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-a &amp;quot;hostname&amp;quot;&lt;/code&gt; - the command to run&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What Ansible has actually done here is ssh into each node and use python to execute the command. Collected the output, then formatted that for us to see. Here it is:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;192.168.100.37 | CHANGED | rc=0 &amp;gt;&amp;gt;
vm321-deb
192.168.100.38 | CHANGED | rc=0 &amp;gt;&amp;gt;
vm322-deb
192.168.100.39 | CHANGED | rc=0 &amp;gt;&amp;gt;
vm323-deb
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There&amp;rsquo;s our node IP addresses. The &lt;code&gt;rc=0&lt;/code&gt; is the successful return code, then there&amp;rsquo;s the actual host names - &lt;code&gt;vm321-deb&lt;/code&gt; etc.&lt;/p&gt;
&lt;p&gt;But, what&amp;rsquo;s going on with &lt;code&gt;CHANGED&lt;/code&gt;? Ansible always indicates some sort of status - things like &lt;code&gt;CHANGED&lt;/code&gt;, &lt;code&gt;SUCCESS&lt;/code&gt;, &lt;code&gt;FAILED&lt;/code&gt; etc. In this case, there should not have been any change - we were just retrieving the hose names, not altering them. The best answer is just ignore this for now. The long answer is that when we&amp;rsquo;re using &lt;code&gt;-a&lt;/code&gt; to run commands on a node, Ansible&amp;rsquo;s &lt;code&gt;command&lt;/code&gt; module isn&amp;rsquo;t able to tell if there have been changes or not, so it reports &lt;code&gt;CHANGED&lt;/code&gt; as a better safe than sorry approach.&lt;/p&gt;
&lt;p&gt;Even though it&amp;rsquo;s possible to use Ansible to run native commands, when there is an equivalent Ansible module that can carry out the same action, it&amp;rsquo;s always better to use that. The reason is that that module code is smart enough to see if something needs done or not. If it does not need done, it will just return &lt;code&gt;SUCCESS&lt;/code&gt;, if it needs done, it will carry out what&amp;rsquo;s needed and return &lt;code&gt;CHANGED&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="idempotence"&gt;Idempotence&lt;/h3&gt;
&lt;p&gt;Every Ansible tutorial includes this word, which I have never encountered anywhere else. A command is idempotent if the result is the same no matter how many times it is executed. In the case of Ansible, this is because it checks if something is needed before it does it.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at an example. If I wanted to create a test directory in the home folder of each of my machines, the Ansible module for this is the file module. I could use this command:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ansible -i hosts all -u ian -m file -a &amp;#34;path=test state=directory&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;-m&lt;/code&gt; tells Ansible with module to use, and our arguments after the &lt;code&gt;-a&lt;/code&gt; flag tell Ansible that the state we want to achieve is a directory named &lt;code&gt;test&lt;/code&gt;. Let&amp;rsquo;s run that and have a look at the output:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-03-at-9.03.12-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;That makes sense, each one is CHANGED because we needed to create the directory. Let&amp;rsquo;s run it again and see what happens.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-03-at-9.03.25-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;This time, since the directory is there, there&amp;rsquo;s no need to change it. Ansible checks for the directories existence before it bothers to create it - because it is idempotent.&lt;/p&gt;
&lt;h3 id="ansiblecfg"&gt;ansible.cfg&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m getting a bit sick of this long command. We can move the inventory file name to a config file to save the typing. Create an ansible.cfg file in your working directory like this.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[defaults]
inventory = hosts
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we can eliminate that from our command line input.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-03-at-9.14.25-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d also like to get rid of the &lt;code&gt;-u ian&lt;/code&gt; from each command. That&amp;rsquo;s not stored in the .cfg file. Since it&amp;rsquo;s likely that your nodes will have different user names in a real situation, they can be stored in the inventory file.&lt;/p&gt;
&lt;h3 id="inventory-file"&gt;Inventory file&lt;/h3&gt;
&lt;p&gt;We started off with a very simple inventory file - literally just a list of IP addresses. let&amp;rsquo;s revisit that to add the ssh user, and while we&amp;rsquo;re there, we can group the nodes according to their functions - this will come in handy later.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[web]
192.168.100.37
192.168.100.38

[db]
192.168.100.39

[web:vars]
ansible_ssh_user=ian

[db:vars]
ansible_ssh_user=ian
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here I&amp;rsquo;ve created two groups for my nodes, a &lt;code&gt;web&lt;/code&gt; group and a &lt;code&gt;db&lt;/code&gt; group. I&amp;rsquo;ve also set the ssh_user for each group. Now that argument can be left out of out commands. So to get the hostnames now, we can just say:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ansible all -a &amp;#34;hostname&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So much neater! Additionally, since our nodes are in groups now, we can specify the group if we don&amp;rsquo;t want to execute the command on all nodes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-03-at-9.38.41-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-03-at-9.38.41-am.png" width="472" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s probably as far as I want to go in this post. We&amp;rsquo;ve got our heads around some early Ansible concepts, learned how to use the Ad-Hoc commands to do things to our nodes, learned a big word that won&amp;rsquo;t ever come up again except in coding interviews, and seen how to set up the ansible.cfg and inventory files.&lt;/p&gt;
&lt;p&gt;The real power to be unleashed is using Ansible playbooks. We&amp;rsquo;ll look at them next.&lt;/p&gt;</description></item><item><title>How to recover a docker run command</title><link>https://blog.iankulin.com/how-to-recover-a-docker-run-command/</link><pubDate>Sun, 16 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/how-to-recover-a-docker-run-command/</guid><description>&lt;p&gt;Imagine if, lets say hypothetically, you&amp;rsquo;d set up an application months ago with a &lt;code&gt;docker run&lt;/code&gt; command. Then you&amp;rsquo;d heard there had been an update to the app because of a security update. So you need to stop/remove the container, pull a new image and restart it, trouble is, you don&amp;rsquo;t remember the exact &lt;code&gt;run&lt;/code&gt; command you used to start it.&lt;/p&gt;
&lt;p&gt;This didn&amp;rsquo;t happen to me, since all my vm setups are in git as markdown (I&amp;rsquo;m pre-Ansible), but I did google how to do this thinking that there would be an easy way before I bothered to look through my config files.&lt;/p&gt;
&lt;h3 id="short-answer"&gt;Short answer&lt;/h3&gt;
&lt;p&gt;There isn&amp;rsquo;t a docker command that will retrieve your run command for you.&lt;/p&gt;
&lt;h3 id="long-answer"&gt;Long answer&lt;/h3&gt;
&lt;p&gt;It&amp;rsquo;s probably still in your bash history, try:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;history | grep &amp;#34;docker run&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If that doesn&amp;rsquo;t work, it must have been a long time ago.&lt;/p&gt;
&lt;p&gt;Most likely, the crucial information you want to know will be the ports you specified, the network setup, and any directories you&amp;rsquo;ve bound or volumes used. All of this information is available from the &lt;code&gt;docker inspect&lt;/code&gt; command, but you&amp;rsquo;re going to have to trawl through it a bit. Search for &lt;code&gt;Mounts&lt;/code&gt; to see what you did there:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;#34;Mounts&amp;#34;: [
 {
 &amp;#34;Type&amp;#34;: &amp;#34;volume&amp;#34;,
 &amp;#34;Name&amp;#34;: &amp;#34;jellyfin-config&amp;#34;,
 &amp;#34;Source&amp;#34;: &amp;#34;/var/lib/docker/volumes/jellyfin-config/_data&amp;#34;,
 &amp;#34;Destination&amp;#34;: &amp;#34;/config&amp;#34;,
 &amp;#34;Driver&amp;#34;: &amp;#34;local&amp;#34;,
 &amp;#34;Mode&amp;#34;: &amp;#34;z&amp;#34;,
 &amp;#34;RW&amp;#34;: true,
 &amp;#34;Propagation&amp;#34;: &amp;#34;&amp;#34;
 },
 {
 &amp;#34;Type&amp;#34;: &amp;#34;bind&amp;#34;,
 &amp;#34;Source&amp;#34;: &amp;#34;/mnt/media/video&amp;#34;,
 &amp;#34;Destination&amp;#34;: &amp;#34;/media&amp;#34;,
 &amp;#34;Mode&amp;#34;: &amp;#34;&amp;#34;,
 &amp;#34;RW&amp;#34;: true,
 &amp;#34;Propagation&amp;#34;: &amp;#34;rprivate&amp;#34;
 },
 {
 &amp;#34;Type&amp;#34;: &amp;#34;volume&amp;#34;,
 &amp;#34;Name&amp;#34;: &amp;#34;jellyfin-cache&amp;#34;,
 &amp;#34;Source&amp;#34;: &amp;#34;/var/lib/docker/volumes/jellyfin-cache/_data&amp;#34;,
 &amp;#34;Destination&amp;#34;: &amp;#34;/cache&amp;#34;,
 &amp;#34;Driver&amp;#34;: &amp;#34;local&amp;#34;,
 &amp;#34;Mode&amp;#34;: &amp;#34;z&amp;#34;,
 &amp;#34;RW&amp;#34;: true,
 &amp;#34;Propagation&amp;#34;: &amp;#34;&amp;#34;
 }
 ],
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="better-answer"&gt;Better Answer&lt;/h3&gt;
&lt;p&gt;Investigate &lt;code&gt;[docker compose](https://docs.docker.com/compose/compose-file/)&lt;/code&gt; to save some effort next time.&lt;/p&gt;</description></item><item><title>How to deploy a Node.js app</title><link>https://blog.iankulin.com/how-to-deploy-a-node-js-app/</link><pubDate>Wed, 05 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/how-to-deploy-a-node-js-app/</guid><description>&lt;p&gt;This is one of those things that is simple once you know it. I had my &lt;a href="https://blog.iankulin.com/using-node-js-to-return-a-static-file/"&gt;tiny Node service working&lt;/a&gt; on my MacBook, but how do I run it on the server?&lt;/p&gt;
&lt;h3 id="native-or-container"&gt;Native or Container&lt;/h3&gt;
&lt;p&gt;Obviously I need Node.js installed on the server, should I have it in a Docker container, or native on the machine. There&amp;rsquo;s no clear answer here - in a container set up with Docker Compose might be more in line with my ideology of treating machines as disposable, but a native install is simpler, and I probably want to make life simpler at this stage when I&amp;rsquo;m learning everything.&lt;/p&gt;
&lt;h3 id="installing-node"&gt;Installing Node&lt;/h3&gt;
&lt;p&gt;This took me down a bigger rabbit hole than I was expecting. My VPS is Unbuntu LTS 22.04.2, so I spun one of those up in a VM on the homelab to try things out.&lt;/p&gt;
&lt;p&gt;A quick google search suggested the &lt;a href="https://github.com/nodesource/distributions"&gt;NodeSource binary distributions&lt;/a&gt;. That involves curling a big script (when I pasted the script into ChatGPT it said it wasn&amp;rsquo;t malicious). I could chose the Node version, so I grabbed 20.x That was as painless as you&amp;rsquo;d expect.&lt;/p&gt;
&lt;p&gt;Then I started wondering why I couldn&amp;rsquo;t just &lt;code&gt;apt install&lt;/code&gt; it. If I could do that, it would reduce the chance of a supply chain attack since I&amp;rsquo;d have the power of Canonical on my side. So I rolled the previous install back (thank you Proxmox backups of VMs), and tried:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;apt install nodejs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;That worked fine - Node is in the Ubuntu packages, but the version is &lt;a href="https://nodejs.dev/en/about/releases/"&gt;quite old&lt;/a&gt; - v12.22.9. This is on the current Ubuntu LTS 22.04.2. I don&amp;rsquo;t think it will matter for my purposes, but it explains why you&amp;rsquo;d do something other than just this.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also going to need &lt;a href="https://www.npmjs.com/"&gt;npm&lt;/a&gt;, so lets get that with:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;apt install npm&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;That seemed to download a heap more stuff that the node install.&lt;/p&gt;
&lt;h3 id="deploying-your-project"&gt;Deploying your project&lt;/h3&gt;
&lt;p&gt;Again, the first search result was more complicated than I needed. The advice was to clone my repository onto the server where I wanted to deploy. This is such a minor project, I hadn&amp;rsquo;t pushed it up to GitHub. So that seemed excessive. You know, not everything has to be DevOps CI/CD! I mean, we ain&amp;rsquo;t talking about a very complicated project here:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-06-26-at-8.34.20-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve got this tiny source file, and the text file I want to serve. All the dependencies (just Express) are in the &lt;code&gt;package.json&lt;/code&gt;, so presumably that&amp;rsquo;s all I need on the server to get going.&lt;/p&gt;
&lt;p&gt;I &lt;code&gt;scp&lt;/code&gt;&amp;rsquo;d those from my laptop to a directory on the folder:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-06-26-at-8.41.19-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Once they are there, I need to install the packages from the package.json, so we do that with:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm install&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;That installed 59 packages (presumably Express plus 58 of it&amp;rsquo;s dependencies). Then I started the app with:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;node .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;and it worked!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-06-26-at-8.55.34-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;h3 id="insomnia"&gt;Insomnia&lt;/h3&gt;
&lt;p&gt;I should probably explain what you&amp;rsquo;re looking at above. I could have tested this little node server by going to the api address in a browser and checked that I got back the text file I was expecting. And in Chrome (and I assume Firefox) there are developer tools that would show the return code etc. However, most of the REST API videos I&amp;rsquo;ve watched use a better tool - mostly &lt;a href="https://www.postman.com/"&gt;Postman&lt;/a&gt;. These sort of tools give you a heap of other capabilities, none of which I really need for this simple project, but will be very handy for more complex APIs where there is a body to the request.&lt;/p&gt;
&lt;p&gt;The only reason I&amp;rsquo;m using &lt;a href="https://insomnia.rest/"&gt;Insomnia&lt;/a&gt; instead of Postman is that when I tried Postman, it straightaway wanted some of my data to make it work. Insomnia hasn&amp;rsquo;t forced me to do that yet.&lt;/p&gt;</description></item><item><title>Complicating the Temperature API</title><link>https://blog.iankulin.com/complicating-the-temperature-api/</link><pubDate>Wed, 28 Jun 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/complicating-the-temperature-api/</guid><description>&lt;p&gt;I&amp;rsquo;ve been slammed with other work, so my web dev learning has fallen well behind. Luckily, the YouTube procrastination algorithm noticed this and suggested I watch a video from &lt;a href="https://www.youtube.com/@codewithcon"&gt;CodeWithCon&lt;/a&gt; titled &lt;a href="https://www.youtube.com/watch?v=KNa-wMpry00&amp;amp;list=PLkJHe6eU_tzeoe7vKUEa4MrS74CpVEwdI&amp;amp;index=3&amp;amp;t=305s"&gt;Learn Backend in 10 MINUTES&lt;/a&gt;.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/KNa-wMpry00?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;Since I was watching a video of a guy learning to land a C152 at St Baths (a skill I do &lt;em&gt;not&lt;/em&gt; need) at the time, it was hard to argue with myself that I didn&amp;rsquo;t have ten minutes to learn all of backend programming.&lt;/p&gt;
&lt;p&gt;I mean, &lt;em&gt;all&lt;/em&gt; of backend programming in 10 minutes is a big claim, but the video did do a surprising good job of simple REST APIs in &lt;a href="https://nodejs.org/en"&gt;Node&lt;/a&gt; using the &lt;a href="http://expressjs.com/"&gt;Express&lt;/a&gt; framework.&lt;/p&gt;
&lt;p&gt;I abandoned iOS programming a year ago when I started to think about the sort of applications I wanted to develop, and saw they would need to run against cloud databases, and so I was going to have to learn backend web dev at some stage anyway, and if so, learning that, then writing the front-ends for web seemed like a lower friction, and wider audience approach.&lt;/p&gt;
&lt;p&gt;I have &lt;em&gt;sort&lt;/em&gt; of created an API to solve my &lt;a href="https://blog.iankulin.com/outside-temperature-from-an-api-in-a-shell-script/"&gt;temperature logging problem&lt;/a&gt;. A Python script runs as a cron job every 5 minutes on a VPS, calls a weather API, parses the json and drops the values I want into a text file on an NGINX server which can be called with a straightforward GET.&lt;/p&gt;
&lt;p&gt;While that was great to learn a bit of Python, it&amp;rsquo;s not pretty, or standard. It does solve the problem I intended (I wanted that weather data for three servers running at home, but didn&amp;rsquo;t want to hammer the weather API I was using for free) it has a few other problems. As the cron job on the VPS runs each five minutes, the data there can be up to five minutes behind the API, and since the cron jobs on my servers are running on the same five minute intervals, and the call to the Australian VPS is quicker than the API call to the US based API, I&amp;rsquo;m always returning the VPS data from five minutes ago - so now my data is up to ten minutes old.&lt;/p&gt;
&lt;p&gt;Does that matter for this application? No, but the whole exercise was for learning, and this is a good enough reason to improve it my making it even more unnecessarily complicated.&lt;/p&gt;
&lt;p&gt;I think my new system will be that the homelab servers will still poll the VPS, but the VPS will be a Node.js endpoint. When it receives a GET from one of the servers, it will check the age of it&amp;rsquo;s current weather data. If it&amp;rsquo;s less than a minute, it will return that, if it&amp;rsquo;s older than a minute, it will call the weather API, store that and return it.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/20230624-weather.drawio-1.png" width="512" alt=""&gt;
&lt;p&gt;Apart from reducing the latency of the outside temperature data, this has a couple of other benefits. The first is that my VPS won&amp;rsquo;t go on for ever requesting the weather API data after I&amp;rsquo;ve reloaded the operating system on the home servers and completely forgotten about this project. The second is that the temperatures in the data I&amp;rsquo;m getting back look like they only change every 20 minutes, so probably they are stale before I ever get them from Open Weather. There are live weather station web pages that I could scrape for better data, so doing things in node on the VPS leaves a good option open for that future improvement.&lt;/p&gt;
&lt;p&gt;To chunk the project down to really small bite sizes, I&amp;rsquo;ll to it in two parts.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The first will just be to replicate the current system - return a text file when receiving a GET - in Node. That way I will have dealt with the issue of running Node behind NGINX on the VPS.&lt;/li&gt;
&lt;li&gt;The second part will be to expand that to call the weather API from inside the Node program when it&amp;rsquo;s needed.&lt;/li&gt;
&lt;li&gt;A possible third part would be to convert it all to JSON instead of text, and then deal with that in the Python scripts running on the servers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;rsquo;s the plan.&lt;/p&gt;</description></item><item><title>Outside Temperature From an API in a Shell Script</title><link>https://blog.iankulin.com/outside-temperature-from-an-api-in-a-shell-script/</link><pubDate>Wed, 03 May 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/outside-temperature-from-an-api-in-a-shell-script/</guid><description>&lt;p&gt;I&amp;rsquo;m interested in &lt;a href="https://blog.iankulin.com/linux-shell-script-for-temperature-logging/"&gt;collecting some internal temperature data&lt;/a&gt; from my servers to look at the effect of adding an NMVe drive. Last week we had a couple of warm days immediately followed by a couple of cool ones. I imagine a 20° ambient temperature change could effect the server temperatures, so I thought it would be good to add that to my temperature logs.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t have a weather station or other automated system for collecting the temperature, but there are several commercial sources for this data which, while probably not as good as a sensor in the server room, will be fine for our purposes.&lt;/p&gt;
&lt;p&gt;One of the more well known weather APIs was &lt;a href="https://darksky.net/dev"&gt;Dark Sky&lt;/a&gt;, they got bought up by Apple and now similar data is available in the &lt;a href="https://developer.apple.com/weatherkit/get-started/"&gt;WeatherKit API&lt;/a&gt;. I hold a developer program membership, so that would be free to use for the frequency I need, but the API and sign up looked a bit complex, so I looked elsewhere.&lt;/p&gt;
&lt;p&gt;OpenWeather have a &lt;a href="https://openweathermap.org/current"&gt;simple API&lt;/a&gt; (including one intended to make changing over from Dark Sky easy), &lt;a href="https://openweathermap.org/price"&gt;a good free tier&lt;/a&gt;, and simple sign up - no credit card required. On the free tier I can pull the current weather for a location 22 times a minute continuously. Since I&amp;rsquo;m only collecting my server temps on a five minute cycle, that will be more than fine.&lt;/p&gt;
&lt;p&gt;Even thought the API would allow it, it seems wasteful, and greedy (since I&amp;rsquo;m not paying for it), to pull the same data three times (for each of the three servers), so to complicate things (and learn some interesting stuff) I decided to poll the OpenWeather API once every five minutes from my VPS, process that current weather JSON down to just the temperature I was after, then expose that as a http endpoint. Then each of my servers would poll the VPS to get that outside temp as part of their logging.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/20230425-weather.drawio-1.png" width="435" alt=""&gt;
&lt;p&gt;This will all extend involve some scripting that I haven&amp;rsquo;t encountered yet.&lt;/p&gt;
&lt;h3 id="vps--weather-api"&gt;VPS / Weather API&lt;/h3&gt;
&lt;p&gt;The OpenWeather API couldn&amp;rsquo;t be more straightforward, you sign up with an email and get an API token, then it&amp;rsquo;s just this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;https://api.openweathermap.org/data/2.5/weather?lat={lat}&amp;amp;lon={lon}&amp;amp;appid={API key}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There&amp;rsquo;s a couple of options for language and units, I went with &lt;em&gt;metric&lt;/em&gt;, then you get have some JSON.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;{
 &amp;#34;coord&amp;#34;: {
 &amp;#34;lon&amp;#34;: 118,
 &amp;#34;lat&amp;#34;: -33.93
 },
 &amp;#34;weather&amp;#34;: [
 {
 &amp;#34;id&amp;#34;: 803,
 &amp;#34;main&amp;#34;: &amp;#34;Clouds&amp;#34;,
 &amp;#34;description&amp;#34;: &amp;#34;broken clouds&amp;#34;,
 &amp;#34;icon&amp;#34;: &amp;#34;04d&amp;#34;
 }
 ],
 &amp;#34;base&amp;#34;: &amp;#34;stations&amp;#34;,
 &amp;#34;main&amp;#34;: {
 &amp;#34;temp&amp;#34;: 12.59,
 &amp;#34;feels_like&amp;#34;: 11.68,
 &amp;#34;temp_min&amp;#34;: 12.59,
 &amp;#34;temp_max&amp;#34;: 12.59,
 &amp;#34;pressure&amp;#34;: 1007,
 &amp;#34;humidity&amp;#34;: 68,
 &amp;#34;sea_level&amp;#34;: 1007,
 &amp;#34;grnd_level&amp;#34;: 976
 },
 &amp;#34;visibility&amp;#34;: 10000,
 &amp;#34;wind&amp;#34;: {
 &amp;#34;speed&amp;#34;: 7.39,
 &amp;#34;deg&amp;#34;: 307,
 &amp;#34;gust&amp;#34;: 11.23
 },
 &amp;#34;clouds&amp;#34;: {
 &amp;#34;all&amp;#34;: 64
 },
 &amp;#34;dt&amp;#34;: 1682401802,
 &amp;#34;sys&amp;#34;: {
 &amp;#34;country&amp;#34;: &amp;#34;AU&amp;#34;,
 &amp;#34;sunrise&amp;#34;: 1682375848,
 &amp;#34;sunset&amp;#34;: 1682415263
 },
 &amp;#34;timezone&amp;#34;: 28800,
 &amp;#34;id&amp;#34;: 2070753,
 &amp;#34;name&amp;#34;: &amp;#34;Gnowangerup&amp;#34;,
 &amp;#34;cod&amp;#34;: 200
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From this, I want to extract the temperature, and the unix timestamp &amp;ldquo;dt&amp;rdquo;. Here&amp;rsquo;s my bash script.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#!/bin/bash

weather_text=`curl -s &amp;#34;https://api.openweathermap.org/data/2.5/weather?lat=-33.93&amp;amp;lon=118.00&amp;amp;appid=somegiantrandomUIDtypenumber&amp;amp;units=metric&amp;#34;`

temp_text=`echo $weather_text | awk -F&amp;#39;&amp;#34;temp&amp;#34;:&amp;#39; &amp;#39;{print $2}&amp;#39; | cut -d&amp;#39;,&amp;#39; -f1`
time_text=`echo $weather_text | awk -F&amp;#39;&amp;#34;dt&amp;#34;:&amp;#39; &amp;#39;{print $2}&amp;#39; | cut -d&amp;#39;,&amp;#39; -f1`

log_file=&amp;#34;/home/ian/iankulin.com/www/gnp_temp.txt&amp;#34;

printf &amp;#34;%s,%s&amp;#34; $temp_text $time_text &amp;gt; $log_file
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Of note, and that I haven&amp;rsquo;t already discussed:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;weather_text=`curl -s &amp;#34;https://api.openweathermap.org/data/2.5/weather?lat=-33.93&amp;amp;lon=118.00&amp;amp;appid=somegiantrandomUIDtypenumber&amp;amp;units=metric&amp;#34;`
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;curl&lt;/code&gt; basically sends out a network request the same as if you had typed it into the top of your browser. If it was a web page, it would return the text of the HTML, but in this case it returns the JSON I showed before - although less formatted.&lt;/p&gt;
&lt;p&gt;weather_text is a variable to which we are assigning the return value of the curl - ie the string of JSON. Note the backticks `` the curl is enclosed in. This is how the script knows to execute the command and assign the results rather than assigning some text beginning with &lt;code&gt;curl&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;temp_text=`echo $weather_text | awk -F&amp;#39;&amp;#34;temp&amp;#34;:&amp;#39; &amp;#39;{print $2}&amp;#39; | cut -d&amp;#39;,&amp;#39; -f1`
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Oh man, this took me on a journey. Firstly, keep in mind I&amp;rsquo;ve prettified the JSON above, actually the string looked like this, so it wasn&amp;rsquo;t possible to process it on a line by line.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;{&amp;#34;coord&amp;#34;:{&amp;#34;lon&amp;#34;:118,&amp;#34;lat&amp;#34;:-33.93},&amp;#34;weather&amp;#34;:[{&amp;#34;id&amp;#34;:803,&amp;#34;main&amp;#34;:&amp;#34;Clouds&amp;#34;,&amp;#34;description&amp;#34;:&amp;#34;broken clouds&amp;#34;,&amp;#34;icon&amp;#34;:&amp;#34;04d&amp;#34;}],&amp;#34;base&amp;#34;:&amp;#34;stations&amp;#34;,&amp;#34;main&amp;#34;:{&amp;#34;temp&amp;#34;:12.59,&amp;#34;feels_like&amp;#34;:11.68,&amp;#34;temp_min&amp;#34;:12.59,&amp;#34;temp_max&amp;#34;:12.59,&amp;#34;pressure&amp;#34;:1007,&amp;#34;humidity&amp;#34;:68,&amp;#34;sea_level&amp;#34;:1007,&amp;#34;grnd_level&amp;#34;:976},&amp;#34;visibility&amp;#34;:10000,&amp;#34;wind&amp;#34;:{&amp;#34;speed&amp;#34;:7.39,&amp;#34;deg&amp;#34;:307,&amp;#34;gust&amp;#34;:11.23},&amp;#34;clouds&amp;#34;:{&amp;#34;all&amp;#34;:64},&amp;#34;dt&amp;#34;:1682401802,&amp;#34;sys&amp;#34;:{&amp;#34;country&amp;#34;:&amp;#34;AU&amp;#34;,&amp;#34;sunrise&amp;#34;:1682375848,&amp;#34;sunset&amp;#34;:1682415263},&amp;#34;timezone&amp;#34;:28800,&amp;#34;id&amp;#34;:2070753,&amp;#34;name&amp;#34;:&amp;#34;Gnowangerup&amp;#34;,&amp;#34;cod&amp;#34;:200}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We are assigning to the variable &lt;code&gt;temp_text&lt;/code&gt; the contents of this command, where $weather_text is the JSON string.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;echo $weather_text | awk -F&amp;#39;&amp;#34;temp&amp;#34;:&amp;#39; &amp;#39;{print $2}&amp;#39; | cut -d&amp;#39;,&amp;#39; -f1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The vertical lines are called &lt;em&gt;pipes&lt;/em&gt; &lt;code&gt;|&lt;/code&gt; they send the output of the command on their left into the command to their right. So there&amp;rsquo;s three different things happening here. The &lt;code&gt;echo&lt;/code&gt; just outputs the JSON, then we process it twice more.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;awk -F&amp;#39;&amp;#34;temp&amp;#34;:&amp;#39; &amp;#39;{print $2}&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href="https://www.geeksforgeeks.org/awk-command-unixlinux-examples/"&gt;awk&lt;/a&gt; is one of the great text processing commands along with &lt;code&gt;grep&lt;/code&gt; and &lt;code&gt;sed&lt;/code&gt;. The way it is being used here is to break the string into multiple parts, where the parts are delimited by the text &lt;code&gt;&amp;quot;temp&amp;quot;:&lt;/code&gt; which in our case is just two parts. Then we are outputting the second part ready for the next processing. So at this stage, the text would look like this.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;12.59,&amp;#34;feels_like&amp;#34;:11.68,&amp;#34;temp_min&amp;#34;:12.59,&amp;#34;temp_max&amp;#34;:12.59,&amp;#34;pressure&amp;#34;:1007,&amp;#34;humidity&amp;#34;:68,&amp;#34;sea_level&amp;#34;:1007,&amp;#34;grnd_level&amp;#34;:976},&amp;#34;visibility&amp;#34;:10000,&amp;#34;wind&amp;#34;:{&amp;#34;speed&amp;#34;:7.39,&amp;#34;deg&amp;#34;:307,&amp;#34;gust&amp;#34;:11.23},&amp;#34;clouds&amp;#34;:{&amp;#34;all&amp;#34;:64},&amp;#34;dt&amp;#34;:1682401802,&amp;#34;sys&amp;#34;:{&amp;#34;country&amp;#34;:&amp;#34;AU&amp;#34;,&amp;#34;sunrise&amp;#34;:1682375848,&amp;#34;sunset&amp;#34;:1682415263},&amp;#34;timezone&amp;#34;:28800,&amp;#34;id&amp;#34;:2070753,&amp;#34;name&amp;#34;:&amp;#34;Gnowangerup&amp;#34;,&amp;#34;cod&amp;#34;:200}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then I need to do the same sort of thing again - split the string using a delimiter, and just keep the part with the termperature in it. This time we&amp;rsquo;ll use a comma , as the delimiter, and only keep the part in front of it.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cut -d&amp;#39;,&amp;#39; -f1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;re saying cut this string in to bits were the delimiter &lt;code&gt;-d&lt;/code&gt; is a comma, then output the first field.&lt;/p&gt;
&lt;p&gt;You might be wondering why I didn&amp;rsquo;t just use &lt;code&gt;awk&lt;/code&gt; again - I could have, but &lt;code&gt;cut&lt;/code&gt; is simpler. The reason I didn&amp;rsquo;t use &lt;code&gt;cut&lt;/code&gt; both times is that it can only take a single character as a delimiter. In fact, the first version I wrote of this script only used &lt;code&gt;cut&lt;/code&gt;, and I had the delimiters as colon for the first cut and comma for the second. As I was writing it, I was thinking that I should stress in the blog post about it that it was quite fragile - a small change in the JSON (for example adding a field, or changing the order - both things that would not cause a problem to a good Swift or JS JSON library) would break it. Then the weather changed and so was two layers of clouds, and the script broke and output the time as &lt;code&gt;{&amp;quot;all&amp;quot;&lt;/code&gt; instead of a number.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-25-at-3.00.13-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;printf&lt;/code&gt; just outputs the two values - temperature and timestamp as plain text with a comma between them into a text file that&amp;rsquo;s in the root of the Nginx webserver.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-04-25-at-8.06.34-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-25-at-8.06.34-pm.png" width="794" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now that&amp;rsquo;s in place, I just edited &lt;code&gt;/etc/crontab&lt;/code&gt; to have the new script run every five minutes to update the file with the temperature and timestamp.&lt;/p&gt;
&lt;h3 id="server-temp-logging"&gt;Server Temp Logging&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ve already seen most of this, but I&amp;rsquo;ve made a couple of additions.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#!/bin/bash

#check drivetemp has been loaded - needed for ssd temp
if ! lsmod | grep -wq drivetemp; then
 modprobe drivetemp
fi

#collect the temp data
pch_name=`cat /sys/class/hwmon/hwmon0/name`
pch_temp=`cat /sys/class/hwmon/hwmon0/temp1_input`
cpu_name=`cat /sys/class/hwmon/hwmon1/name`
cpu_temp=`cat /sys/class/hwmon/hwmon1/temp1_input`
ssd_name=`cat /sys/class/hwmon/hwmon2/name`
ssd_temp=`cat /sys/class/hwmon/hwmon2/temp1_input`

#this should contain the current outside temp and unix time
outside_temp=`curl -s &amp;#34;https://iankulin.com/gnp_temp.txt&amp;#34;`

log_file=&amp;#34;/var/log/temps.csv&amp;#34;

# Print the temperatures to a log file
printf &amp;#34;$(date +&amp;#39;%d/%m/%Y,%T&amp;#39;),%s,%d,%s,%d,%s,%d,out,%s\n&amp;#34; $pch_name $pch_temp $cpu_name $cpu_temp $ssd_name $ssd_temp $outside_temp &amp;gt;&amp;gt; $log_file
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;ve already discussed how the curl works - this one is picking up the script we wrote to run on the VPS earlier. More interesting is checking for the &lt;code&gt;drivetemp&lt;/code&gt; module.&lt;/p&gt;
&lt;p&gt;The drivetemp module needs to be loaded into the Linux kernel before we can read the SSD temperature with the line.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ssd_temp=`cat /sys/class/hwmon/hwmon2/temp1_input`
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once it&amp;rsquo;s loaded, it stays there, unless computer is shutdown for any reason. There&amp;rsquo;s a &lt;a href="https://www.baeldung.com/linux/run-script-on-startup"&gt;number of places&lt;/a&gt; we can execute things on startup, but really this &lt;code&gt;drivetemp&lt;/code&gt; module is only needed for this script, so we should do it here. As far as I can make out, telling Linux to load a module that&amp;rsquo;s already loaded does not do any harm, and at once every five minutes it&amp;rsquo;s hardly going to cause a performance issue. Nevertheless, some sort of programmer ethics compels me to only do it if its needed.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#check drivetemp has been loaded - needed for ssd temp
if ! lsmod | grep -wq drivetemp; then
 modprobe drivetemp
fi
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;lsmod&lt;/code&gt; returns a list of the loaded modules, this is passed to the &lt;code&gt;grep&lt;/code&gt;. &lt;code&gt;grep&lt;/code&gt; looks through lines of input and usually returns any lines that match. However in this case, we&amp;rsquo;re using the &lt;code&gt;-q&lt;/code&gt; (quiet) option. With this option on, instead of lines of text, you get nothing on the standard output, instead it sets the exit code to 0 (true) if it&amp;rsquo;s found, or 1 (false) if not.&lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;m interested in only running the &lt;code&gt;modprobe&lt;/code&gt; if &lt;code&gt;drivetemp&lt;/code&gt; is &lt;em&gt;not&lt;/em&gt; found, I have to negate the result of the &lt;code&gt;grep&lt;/code&gt; with &lt;code&gt;!&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;After that, all the temperature data is collected, then written out to a log fie for later processing.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-25-at-9.00.47-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;h3 id="the-results"&gt;The Results&lt;/h3&gt;
&lt;p&gt;Here&amp;rsquo;s 24 hours of the five minute temperature logs. For each server I averaged the three different temperatures (PCH, CPU core, and SSD drive) and graphed them along with the outside temperature from OpenWeather. &lt;code&gt;pve-prod1&lt;/code&gt; is the only one doing any real work here. It hosts my Jellyfin media server on a VM, and another VM with a collection of utilities such as Uptime Kuma. The Y axis is degrees centigrade.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/20230427-server-temps.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/img_4315.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The spike in &lt;code&gt;pve-dev&lt;/code&gt;1 at 2100 was caused by me stress testing one core to 100% load for ten minutes. I think I can see &lt;code&gt;pve-prod2&lt;/code&gt; (which sits directly on top of &lt;code&gt;pve-dev1&lt;/code&gt;) warming up a little as well. But strangely, and perhaps I&amp;rsquo;m imagining it, it seems like &lt;code&gt;pve-prod1&lt;/code&gt; (which sits on top of the stack) was a bit cooler in that time?&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t remember if I watched some TV between 6 and 8pm, but it looks like I did, and the spike at 2am will be the nightly snapshots being taken and sent off to the NAS.&lt;/p&gt;
&lt;p&gt;You can see that &lt;code&gt;pve-prod2&lt;/code&gt; and &lt;code&gt;pve-dev1&lt;/code&gt; were turned on to run this test, and it takes about 40 minutes for them to warm up. It&amp;rsquo;s interesting to notice the bigger amplitude of the production machine compared to the others just idling. And also interesting that &lt;code&gt;pve-dev1&lt;/code&gt; (which wasn&amp;rsquo;t running any load till I ran the stress test on it) was just generally warmer that &lt;code&gt;pve-prod1&lt;/code&gt; which was running a small work load.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t remember if I watched some TV between 6 and 8pm, but it looks like I did, and the spike at 2am will be the nightly snapshots being taken and sent off to the NAS.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s have a look at pve-dev1 while the stress test was running.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/pve-dev1-temp.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;It makes sense that the PCH which is mm away from, and directly connected to, the CPU would warm up as the CPU was hammered with square root calculations, and since the drive temp is a up a little so I guess that reflects the ambient temperature inside the case.&lt;/p&gt;
&lt;p&gt;The CPU temperature hadn&amp;rsquo;t plateaued yet, so it might be interesting to run it until it does one day and see what that looks like.&lt;/p&gt;</description></item><item><title>Running a Browser Remotely - n.eko</title><link>https://blog.iankulin.com/running-a-browser-remotely-n-eko/</link><pubDate>Tue, 02 May 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/running-a-browser-remotely-n-eko/</guid><description>&lt;p&gt;When I installed the backup NAS and a media server at the remote site, one of the jobs on my list was to reserve the IP addresses for the NAS, node, and the VM in the local router. I carefully did that, but when I got home (200 km later) and opened my laptop, the browser page was open on the DHCP settings with a table of mac addresses I&amp;rsquo;d added, and the reserved IP&amp;rsquo;s, and at the bottom of the page, a large blue &amp;ldquo;Apply Changes&amp;rdquo; button. Had I pressed that button to save my changes correctly? I&amp;rsquo;m not sure.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve got a couple of options to access the router (without one of those sometimes frustrating tech support calls).&lt;/p&gt;
&lt;p&gt;One is that &lt;a href="https://tailscale.com/"&gt;Tailscale&lt;/a&gt; (that I&amp;rsquo;m using for the VPN tunnel) has a feature called &lt;a href="https://tailscale.com/kb/1019/subnets/"&gt;Subnet Routers&lt;/a&gt;. This is intended for this use-case - there&amp;rsquo;s a device (the remote router) that can&amp;rsquo;t run a Tailnet client that we&amp;rsquo;d like to access, but it&amp;rsquo;s on a local network with a device that can. This would often be the case for things like you want to print to a printer at home from work - you can&amp;rsquo;t Tailnet into the printer, but you can to the home server which is on the same network. Installing the Tailscale subnet router would then allow a path to the printer over the local home network.&lt;/p&gt;
&lt;p&gt;To do that, I need to ssh into the media server node and set it all up, and it will involved a network disconnect. This has a bit of risk involved since I might make an error and then not be able to fix it.&lt;/p&gt;
&lt;p&gt;A neater option might be to run a web browser instance on the media server, and use that to access the web interface. In days gone past that might have been &lt;a href="https://lynx.invisible-island.net/"&gt;Lynx&lt;/a&gt; for a pure text experience, but another, more modern possibility might be &lt;a href="https://www.brow.sh/"&gt;browsh&lt;/a&gt;. This very cool project uses Firefox as it&amp;rsquo;s engine, but then renders all the page output as ASCII text into a terminal. There&amp;rsquo;s a &lt;a href="https://www.youtube.com/watch?v=zqAoBD62gvo"&gt;fun video of browsh&lt;/a&gt; playing Youtube videos all in half size ascii colour blocks in the terminal.&lt;/p&gt;
&lt;p&gt;I ended up going an even more modern way by using the virtual browser &lt;a href="https://neko.m1k1o.net/"&gt;N.eko&lt;/a&gt;. This way you get a full graphical browser running on a remote machine. Licenced under the Apache license, it&amp;rsquo;s a project of &lt;a href="https://github.com/m1k1o/neko"&gt;Miroslav Šedivý&lt;/a&gt;. I&amp;rsquo;m sure I&amp;rsquo;ve read in a forum somewhere that he built it to watch anime online with friends - and there&amp;rsquo;s some functionality (such as chats) that supports that.&lt;/p&gt;
&lt;p&gt;Docker is the easy way to spin it up. It is a little bit resource hungry - 1GB is specified for the lowest resolution, but &lt;a href="//www.youtube.com/watch?v=ISunHDh7WyQ"&gt;this guy&lt;/a&gt; shows adding a swap file to make the memory up to that. My VPS has 512KB, but NVMe storage, so I tried without the swap file and could not get it to start, and then with the swap file and it worked fine. Here&amp;rsquo;s my compose:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;version: &amp;#39;3.5&amp;#39;services: neko: image: m1k1o/neko:chromium restart: always cap_add: - SYS_ADMIN ports: - &amp;#34;8081:8080&amp;#34; - &amp;#34;59000-59100:59000-59100/udp&amp;#34; environment: DISPLAY: :99.0 SCREEN_WIDTH: 1024 SCREEN_HEIGHT: 576 SCREEN_DEPTH: 16 NEKO_PASSWORD: neko NEKO_ADMIN: admin NEKO_BIND: :8080 NEKO_NAT1TO1: 100.138.120.102 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The only thing in there that took a bit of sorting out was the last line - the NEKO_ATA1TO1 environment variable. I didn&amp;rsquo;t need that at all running on a remote VPS - it just all worked out of the box, but when I was trying on my development node at home (which is a close hardware/software mirror of the remote setup I need it for) I had less luck.&lt;/p&gt;
&lt;p&gt;In that configuration, when I went to log in, it would take ages, then say &lt;em&gt;peer disconnected&lt;/em&gt;. In the logs, there was an error &lt;code&gt;WRN read message error error=&amp;quot;websocket: close 1005 (no status)&amp;quot;&lt;/code&gt;. Some googling took me to a GitHub issue about it. When n.eko starts, it autodetects the external IP and expects connections from there. This will also be an issue for my remote site, since it&amp;rsquo;s on a Tailnet that won&amp;rsquo;t match it&amp;rsquo;s external IP. The solution is to add the &lt;code&gt;NEKO_NAT1TO1&lt;/code&gt; environment variable in your docker-compose.yaml and set it to a single IP address with no quotes - being the IP address you&amp;rsquo;ll connect to it on.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-23-at-5.08.42-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Edits:&lt;/p&gt;
&lt;p&gt;4/3/24 - changed the image name to &lt;code&gt;m1k1o/neko:chromium&lt;/code&gt; from &lt;code&gt;nurdism/neko:chromium&lt;/code&gt; which was an old old version.&lt;/p&gt;</description></item><item><title>ISO wrangling - Etcher and Ventoy</title><link>https://blog.iankulin.com/iso-wrangling-etcher-and-ventoy/</link><pubDate>Mon, 01 May 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/iso-wrangling-etcher-and-ventoy/</guid><description>&lt;p&gt;If you fiddle around with computers, and especially with Linux drives, you&amp;rsquo;ll often find yourself with an ISO file you need to boot a device from. These can&amp;rsquo;t just be copied onto an existing USB or SD card - they need to be bootable, so you&amp;rsquo;ll need a special program to write the ISO to the storage device.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-04-23-at-2.02.44-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-23-at-2.02.44-pm.png" width="247" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Previously I&amp;rsquo;ve been a big fan of &lt;a href="https://www.balena.io/etcher"&gt;Balena Etcher&lt;/a&gt;. It couldn&amp;rsquo;t be much more simple - you chose the ISO file you&amp;rsquo;ve downloaded from somewhere, chose your removable drive (it intelligently hides the non-removable drives to prevent you from accidentally wiping your hard disk), then tell it to do it&amp;rsquo;s thing.&lt;/p&gt;
&lt;p&gt;When you want to try a different ISO file, you go through that whole process again. At least that&amp;rsquo;s what I did till I heard about &lt;a href="https://www.ventoy.net/en/index.html"&gt;Ventoy&lt;/a&gt;.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/ventoy.png" width="241" alt=""&gt;
&lt;p&gt;This installs onto the USB in a similar way - although with it&amp;rsquo;s own program Ventoy2Disk (no macOS version). Once it&amp;rsquo;s on there, the USB drive just appears as an ordinary empty ExFat drive if you plug it in.&lt;/p&gt;
&lt;p&gt;You just copy any (multiples allowed) ISO&amp;rsquo;s you might use in the future onto the drive. The if the USB is used to boot from, it starts a GRUB like program that lets you choose one of the ISO&amp;rsquo;s, then will go on to boot from that ISO. It&amp;rsquo;s saved me a lot of time by not having to re-etch ISO files - they are often quite large so is was a time consuming process.&lt;/p&gt;</description></item><item><title>Linux Shell Script for Temperature Logging</title><link>https://blog.iankulin.com/linux-shell-script-for-temperature-logging/</link><pubDate>Thu, 27 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/linux-shell-script-for-temperature-logging/</guid><description>&lt;p&gt;A potential solution to my concern about the either perfect, or nearly dead, SSD would be to add a NVMe disk to the M.2 slot in the HP Elitedesk 800 G2&amp;rsquo;s. I&amp;rsquo;d use those to boot from and run Proxmox, then the existing SSD&amp;rsquo;s on each node in the cluster would just be part of the CephFS pool that has some redundancy built into it and hosts the VMs that are not using the NAS for their storage.&lt;/p&gt;
&lt;p&gt;These &amp;lsquo;gumstick&amp;rsquo; NVMe drives are remarkably good value in the smaller sizes at the moment, with Samsung 250GB NVMe&amp;rsquo;s costing less than a pack of cigarettes in Australia.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-20-at-7.02.57-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;A small concern I&amp;rsquo;ve got about that, and about the (very cute looking) way I&amp;rsquo;ve just got the computers all stacked on top of each other, is about the internal temperatures. I noticed SSD temperatures in the SMART data I was looking the other day, and I&amp;rsquo;ve seen CPU temperatures somewhere, so this data is available. So I set out on a quest to log some of it so I could do a before and after (NMVe installation) look at the temperatures.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.pyroelectro.com/tutorials/cron-automation/check.html"&gt;This article&lt;/a&gt; was very close to what I wanted - a shell script to run in a cron job that would log the drive and CPU temperatures. The script goes a fair way beyond that, but my main issue was that it uses a couple of packages - &lt;code&gt;sensors&lt;/code&gt;, and &lt;code&gt;hddtemp&lt;/code&gt;. I like to avoid dependencies if I can, but I also thought the temp data was pretty simple and is probably just sitting in the &lt;code&gt;/sys/&lt;/code&gt; directory tree somewhere.&lt;/p&gt;
&lt;p&gt;That sort of turned out to be true. In /sys/class/hwmon/ there&amp;rsquo;s a couple of directories (actually symlinks) for bits of hardware that can be monitored for temperature, and in those directories are text files with some values, the ones we&amp;rsquo;re interested in being &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;temp1_input&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@pve-dev1:~# tree /sys/class/hwmon/
/sys/class/hwmon/
├── hwmon0 -&amp;gt; ../../devices/virtual/thermal/thermal_zone0/hwmon0
├── hwmon1 -&amp;gt; ../../devices/platform/coretemp.0/hwmon/hwmon1
└── hwmon2 -&amp;gt; ../../devices/pci0000:00/0000:00:17.0/ata1/host0/target0:0:0/0:0:0:0/hwmon/hwmon2

3 directories, 0 files
root@pve-dev1:~# ls /sys/class/hwmon/hwmon0
device name power subsystem temp1_input uevent
root@pve-dev1:~# cat /sys/class/hwmon/hwmon0/name /sys/class/hwmon/hwmon0/temp1_input
pch_skylake
45500
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href="https://commons.wikimedia.org/w/index.php?curid=9817206"&gt;&lt;img src="https://blog.iankulin.com/images/intel_5_series_architecture.png" width="232" alt=""&gt;&lt;/a&gt;
&lt;em&gt;Intel 5 architecture - Anas hashmi&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The first two are temperatures of the &lt;a href="https://commons.wikimedia.org/w/index.php?curid=9817206"&gt;Platform Controller Hub (PCH)&lt;/a&gt; and actual CPU. Both of these values were already just sitting there.&lt;/p&gt;
&lt;p&gt;The third value, for the SSD temperature didn&amp;rsquo;t appear until added by running &lt;code&gt;modprobe drivetemp&lt;/code&gt; to load a kernel module.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the three values I want to log sorted then, but how to go about it? &lt;a href="http://www.pyroelectro.com/tutorials/cron-automation/check.html"&gt;That first article&lt;/a&gt; I mentioned had a shell script using printf() to output some values to a log file, then the script was triggered by a cron job. Two things I&amp;rsquo;ve never done before, so let&amp;rsquo;s dive in. Here&amp;rsquo;s the finished code.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-20-at-8.32.11-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I wrote MS-DOS batch files in the 1980s so this wasn&amp;rsquo;t completely alien to me. A few points were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Everyone else&amp;rsquo;s shell scripts start with &lt;code&gt;#!/bin/bash&lt;/code&gt; so I assume that&amp;rsquo;s compulsory. Other lines starting with &lt;code&gt;#&lt;/code&gt; are comments.&lt;/li&gt;
&lt;li&gt;In that list of variables, each one is filled with the results of the command being assigned to it in the single quotes. So if typing in &lt;code&gt;cat /sys/class/hwmon/hwmon0/name&lt;/code&gt; at the terminal prompt would result in the output &lt;code&gt;pch_skylake&lt;/code&gt;, then the variable &lt;code&gt;pch_name&lt;/code&gt; will contain &lt;code&gt;pch_skylake&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If you&amp;rsquo;re not coming to this from a programming background, this printf() command is going to look weird.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;printf &amp;#34;$(date +&amp;#39;%d/%m/%Y,%T&amp;#39;),%s,%d,%s,%d,%s,%d\n&amp;#34; $pch_name $pch_temp $cpu_name $cpu_temp $ssd_name $ssd_temp &amp;gt;&amp;gt; $log_file
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;How these work is that the first part is the string to print, but it has some placeholders (all those &lt;code&gt;%&lt;/code&gt;letters). At runtime, the values from the end of the line are inserted into them. Like this:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@pve-dev1:~# printf &amp;#34;Hello %s\n&amp;#34; &amp;#34;Ian&amp;#34;
Hello Ian
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;%s&lt;/code&gt; is a placeholder for a some text, then we supply the text at the end - &lt;code&gt;&amp;quot;Ian&amp;quot;&lt;/code&gt;. In this case, &lt;code&gt;&amp;quot;Ian&amp;quot;&lt;/code&gt; is a string literal, if we&amp;rsquo;d used a variable (as in our logging script) then the contents of the variable would be used instead. The &lt;code&gt;\n&lt;/code&gt; at the end of the string is a newline character so whatever comes after starts on a new line.&lt;/p&gt;
&lt;p&gt;At this point I know enough about Linux permissions that I knew I&amp;rsquo;d have to set the shell script file to be executable with a &lt;code&gt;chmod 755&lt;/code&gt;, and to call it with the &lt;code&gt;./&lt;/code&gt; in front of it that I was perplexed about a couple of days ago.&lt;/p&gt;
&lt;p&gt;Again, the original article gave an example of the line to put into /etc/crontab. It just needed the path to my script and it was good to go.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) 
# | | | | |
# * * * * * user-name command to be executed
*/5 * * * * root /root/bin/tempCheck.sh 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next thing you know, the log file is slowly growing at &lt;code&gt;/var/log/temps.csv&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;20/04/2023,20:30:01,pch_skylake,45000,coretemp,38000,drivetemp,38000
20/04/2023,20:35:01,pch_skylake,45000,coretemp,37000,drivetemp,38000
20/04/2023,20:40:01,pch_skylake,44500,coretemp,37000,drivetemp,38000
20/04/2023,20:45:01,pch_skylake,45000,coretemp,37000,drivetemp,38000
20/04/2023,20:50:01,pch_skylake,44500,coretemp,37000,drivetemp,38000
20/04/2023,20:55:01,pch_skylake,44500,coretemp,37000,drivetemp,38000
20/04/2023,21:00:01,pch_skylake,45000,coretemp,37000,drivetemp,38000
20/04/2023,21:05:01,pch_skylake,45500,coretemp,38000,drivetemp,38000
20/04/2023,21:10:01,pch_skylake,45000,coretemp,38000,drivetemp,38000
20/04/2023,21:15:01,pch_skylake,45500,coretemp,37000,drivetemp,38000
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Obviously I&amp;rsquo;m going to graph this, and also obviously, I&amp;rsquo;m going to run a CPU stress test in a VM in the middle of the data collection.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/temp-chart.jpg" alt=""&gt;&lt;/p&gt;</description></item><item><title>SDD Wearout numbers</title><link>https://blog.iankulin.com/sdd-wearout-numbers/</link><pubDate>Tue, 25 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/sdd-wearout-numbers/</guid><description>&lt;p&gt;I didn&amp;rsquo;t understand why the default Proxmox install sets up the storage the way it does - with the available disk split up into an LVM and an LVM thin storage - so I&amp;rsquo;ve been reading this excellent &lt;a href="https://blog.programster.org/proxmox-storage-guide"&gt;Proxmox Storage Guide&lt;/a&gt; by Programster (spoiler - the LVM thin makes VM snapshots easier).&lt;/p&gt;
&lt;p&gt;At one point in the post they mention that you can see the &amp;ldquo;Wearout&amp;rdquo; percentage for any SSD drives in the Proxmox GUI, so of course, since I now own five second hand HP Elitedesk 800 G1/G2&amp;rsquo;s all with SSD drives, I dived in to have a look at each drive and found this.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Server&lt;/th&gt;
 &lt;th&gt;GB&lt;/th&gt;
 &lt;th&gt;Model&lt;/th&gt;
 &lt;th&gt;SMART&lt;/th&gt;
 &lt;th&gt;Wearout&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;pve-prod1&lt;/td&gt;
 &lt;td&gt;512&lt;/td&gt;
 &lt;td&gt;Micron_1100 SATA&lt;/td&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;6%&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;pve-prod2&lt;/td&gt;
 &lt;td&gt;120&lt;/td&gt;
 &lt;td&gt;SSD2S120SF1200SA2&lt;/td&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;100%&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;pve-dev1&lt;/td&gt;
 &lt;td&gt;256&lt;/td&gt;
 &lt;td&gt;TOSHIBA_THNSNK256GCS8&lt;/td&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;2%&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;pve-kr01&lt;/td&gt;
 &lt;td&gt;120&lt;/td&gt;
 &lt;td&gt;KINGSTON_SA400S37120G&lt;/td&gt;
 &lt;td&gt;Pass&lt;/td&gt;
 &lt;td&gt;0%&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I&amp;rsquo;m no expert, but 100% &amp;ldquo;wearout&amp;rdquo; sounds bad, or maybe these figures go the other way, and that drive is 100% good and the others are just about dead. Either way, I&amp;rsquo;m suddenly interested in this number and what it means.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a button to look at the S.M.A.R.T (Self-Monitoring, Analysis and Reporting Technology backronym) attributes, so let&amp;rsquo;s have a look at this suspicious no-name drive.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-19-at-7.27.43-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Well, some of this is comprehensible. The Power_On_Hours is saying it&amp;rsquo;s been on for about one and a half years worth of hours. Since it&amp;rsquo;s been power cycled over a thousand times, that all sort of matches a corporate desk machine that&amp;rsquo;s been in use for five or six years. These values look like the sort of data you get from running the &lt;code&gt;smartctl -a /dev/sda&lt;/code&gt; command. I&amp;rsquo;ve snipped this output because it is huge, but the middle part is very similar to the table above, and there was nothing scary it it,&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;...

SMART overall-health self-assessment test result: PASSED

...
 

ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
 1 Raw_Read_Error_Rate 0x0032 120 120 050 Old_age Always - 0
 5 Reallocated_Sector_Ct 0x0033 100 100 003 Pre-fail Always - 0
 9 Power_On_Hours 0x0032 060 060 000 Old_age Always - 35173 (2 96 0)
 12 Power_Cycle_Count 0x0032 099 099 000 Old_age Always - 1059
171 Unknown_Attribute 0x000a 100 100 000 Old_age Always - 0
172 Unknown_Attribute 0x0032 100 100 000 Old_age Always - 0

...

No self-tests have been logged. [To run self-tests, use: smartctl -t]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;rsquo;s a lot, but it clearly says that it&amp;rsquo;s &amp;ldquo;passed&amp;rdquo; the test.&lt;/p&gt;
&lt;p&gt;I tried to run the short SMART test a couple of times with the command: &lt;code&gt;smartctl --test=short /dev/sda&lt;/code&gt; but each time (after I&amp;rsquo;d waited a couple of minutes) when I ran &lt;code&gt;smartctl -l selftest /dev/sda&lt;/code&gt; to look at the results, it claimed the test had been aborted by the host. Presumably I need to shut down Proxmox to run the test properly.&lt;/p&gt;
&lt;p&gt;For the moment, I&amp;rsquo;m just hoping that different manufacturers report that wearout figure differently, but I&amp;rsquo;ll show an increased interest in these drives health for a while.&lt;/p&gt;
&lt;p&gt;The reason I have three nodes locally is that I&amp;rsquo;m anticipating going to HA (high availability) as I move more services out of the paid cloud onto self-hosted. When I do that some of the VM&amp;rsquo;s (with low disk speed needs) will have their storage on the NAS, and the others in a Ceph or ZFS pool to facilitate quick migration on failure. To support that, I&amp;rsquo;m probably looking at provisioning new high quality 512GB SSDs to these machines anyway, so if I do get to that stage, that&amp;rsquo;s a strong (although expensive) possibility, and I&amp;rsquo;d certainly rather buy two than three.&lt;/p&gt;</description></item><item><title>Why use './' in front of filenames?</title><link>https://blog.iankulin.com/why-use-in-front-of-filenames/</link><pubDate>Sun, 23 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/why-use-in-front-of-filenames/</guid><description>&lt;p&gt;In Linux (and MS-DOS I guess) the period signifies the current directory, so if I have a file in the current directory called &lt;code&gt;test.txt&lt;/code&gt;, I can refer to it as &lt;code&gt;test.txt&lt;/code&gt; or &lt;code&gt;./test.txt&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ian@enrico-rider:~$ cat test.txt
test
ian@enrico-rider:~$ cat ./test.txt
test
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I mostly see this in references to files in HTML and have often wondered why. Here it is being used in a Udemy course I&amp;rsquo;m following.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-19-at-10.49.00-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s one of those things that&amp;rsquo;s difficult to Google, so these days my reflex is to ask ChatGPT such questions.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-04-19-at-11.17.53-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-19-at-11.17.53-am.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Okay. That makes sense for executable files. If you just type in the name, Linux will look in the current directory, then if not found, in each of the directories in your $PATH variable. But if you add the ./ to the front, it will only look in your current directory. This claim of ChatGPT&amp;rsquo;s is easily tested, lets try with &lt;code&gt;cat&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ian@enrico-rider:~$ cat test.txt
test
ian@enrico-rider:~$ ./cat test.txt
-bash: ./cat: No such file or directory
ian@enrico-rider:~$ cp /usr/bin/cat cat
ian@enrico-rider:~$ ./cat test.txt
test
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So that checks out, but it doesn&amp;rsquo;t explain the main place I see it - in HTML.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-19-at-12.00.12-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Again, that makes sense. But it still doesn&amp;rsquo;t answer why the instructor in my course is using it for:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cp /etc/passwd ./users.txt
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-19-at-12.06.52-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Lol - I feel this is a real edge case. I can see it being more a problem with the first file rather than the second one. eg.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-04-19-at-12.21.05-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-19-at-12.21.05-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So, TL:DR; using &amp;lsquo;./&amp;rsquo; in front of a filename can be useful when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;executing a shell script in the current directory (to avoid ambiguity with other executable files in your PATH);&lt;/li&gt;
&lt;li&gt;when running a command that takes filenames as arguments, and the filename might be confused with an argument;&lt;/li&gt;
&lt;li&gt;in HTML to avoid confusion between the directory the current file is in and the root directory of the web server.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Mounting NFS shares into LXC containers</title><link>https://blog.iankulin.com/mounting-nfs-shares-into-lxc-containers/</link><pubDate>Fri, 21 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/mounting-nfs-shares-into-lxc-containers/</guid><description>&lt;p&gt;I&amp;rsquo;m playing with &lt;a href="https://syncthing.net/"&gt;Syncthing&lt;/a&gt; with the idea that it might be a good replacement for Dropbox. There wasn&amp;rsquo;t a Docker container listed in the install options, so I thought this might be a good app to run in an LXC.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to use a share from the NAS, and I&amp;rsquo;m assuming I&amp;rsquo;ll need it mount it into the container for Syncthing to access. I&amp;rsquo;m experienced enough to know that I&amp;rsquo;m going to want a privileged container, and I thought I&amp;rsquo;d done all the NFS sharing correctly, but when I tried to mount the NFS share, I was getting an error.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@ct356-syncthing:~# showmount -e 192.168.100.32

Export list for 192.168.100.32:
/volume1/syncthing 192.168.100.37
/volume1/proxmox 192.168.100.24,192.168.100.31,192.168.100.28,192.168.100.23

root@ct356-syncthing:~# mount -t nfs 192.168.100.32:/volume1/syncthing /mnt/syncthing

mount.nfs: access denied by server while mounting 192.168.100.32:/volume1/syncthing
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is just part of the security nature of the LXC containter getting in our way. We can edit the &lt;code&gt;.conf&lt;/code&gt; for the container, or just change it in the container options via the web GUI and restart the container.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-04-18-at-7.19.41-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-18-at-7.19.41-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-18-at-7.49.05-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I learned this from &lt;code&gt;[theorangeone](https://theorangeone.net/posts/mount-nfs-inside-lxc/)&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Running Multiple Linux Commands in One Line</title><link>https://blog.iankulin.com/running-multiple-linux-commands-in-one-line/</link><pubDate>Wed, 19 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/running-multiple-linux-commands-in-one-line/</guid><description>&lt;p&gt;Since I&amp;rsquo;m constantly standing up Linux virtual machines and containers - almost always of the &lt;code&gt;apt&lt;/code&gt; variety, I&amp;rsquo;m constantly typing in:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apt update
apt upgrade
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then hitting enter again to allow whatever installation is needed to proceed. I&amp;rsquo;ve noticed in some of the commands I&amp;rsquo;ve been pasting in from installation instructions or StackExchange solutions have been separated by characters that look like it allows several commands to be run one after the other. To cut a long story short, the commands above could be entered like this with double ampersands:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apt update &amp;amp;&amp;amp; apt upgrade
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The ampersands mean that the second command will run after the first one as long as it completes without an error. To avoid having to hit enter again, we can add the &lt;code&gt;-y&lt;/code&gt; flag.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apt update &amp;amp;&amp;amp; apt upgrade -y
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;According to the &lt;a href="https://www.makeuseof.com/how-to-run-multiple-linux-commands-at-once/"&gt;MakeUseOf article&lt;/a&gt; I learned about the double ampersands from, we could also have used a semicolon if we want each command to run regardless of the success of the previous one, or a double pipe if you only wanted the second command run run if the first one fails.&lt;/p&gt;</description></item><item><title>Linux on HP Mini 110</title><link>https://blog.iankulin.com/linux-on-hp-mini-110/</link><pubDate>Mon, 17 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/linux-on-hp-mini-110/</guid><description>&lt;p&gt;I&amp;rsquo;ve been furthering my Linux education by playing with some desktop distros in VMs, but it&amp;rsquo;s not a great experience accessing them through the Proxmox web GUI. The alternative to this is to use a good &lt;a href="https://en.wikipedia.org/wiki/Simple_Protocol_for_Independent_Computing_Environments"&gt;SPICE&lt;/a&gt; client on the remote desktop, but there is &lt;a href="https://forum.proxmox.com/threads/access-vm-thru-spice-on-osx.66727/"&gt;not a simple good solution&lt;/a&gt; for this for MacOS.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been playing with the idea of picking up an old i3/i5 Thinkpad - these are around the AUD130 mark on eBay, to run a Linux distro with the main idea being to use it to SPICE into my VMs.&lt;/p&gt;
&lt;p&gt;This weekend at my parents house, I&amp;rsquo;ve been going through the cupboard secure wiping a couple of the discarded laptops, and found a fancy looking HP Mini 110-1131dx.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.notebookcheck.net/HP-Mini-110-Series.24414.0.html"&gt;&lt;img src="https://blog.iankulin.com/images/9563377_ra.jpg" width="500" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.notebookcheck.net/HP-Mini-110-Series.24414.0.html"&gt;&lt;img src="https://blog.iankulin.com/images/9563377cv3a.jpg" width="500" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This was a netbook, quite a cute little thing, and like most HP hardware - well made and popular enough that I should be able to googlesolve any issues I encounter. The Atom N270 that mousepowers it is a single core 32bit baby, but there&amp;rsquo;s also a moderate graphics accelerator chip - the &lt;a href="https://www.notebookcheck.net/Intel-Graphics-Media-Accelerator-950.2177.0.html"&gt;GMA 950&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Mint or Ubuntu would probably be my first choices for a desktop distro, but given the very low specs of the HP 110, I&amp;rsquo;m guessing that&amp;rsquo;s not going to be a good experience even if you go back to the most recent 32 bit versions. After a bit of googling around, I decided &lt;a href="https://antixlinux.com/"&gt;antiX&lt;/a&gt; or &lt;a href="https://lubuntu.net/"&gt;Lubuntu&lt;/a&gt; might be good choices (I&amp;rsquo;m partial to the &lt;code&gt;apt get&lt;/code&gt; family of distros).&lt;/p&gt;
&lt;h3 id="antix"&gt;antiX&lt;/h3&gt;
&lt;p&gt;As with most modern distros, the install was a painless experience - booting from the USB and following prompts. The UI was pleasant and crisp, and I especially liked the background on the desktop with some live statistics.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.linuxinsider.com/story/antix-linux-not-pretty-but-highly-functional-86942.html"&gt;&lt;img src="https://blog.iankulin.com/images/86942_antix-3-small.jpg" width="620" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;From the base install, the wireless would not work properly. On the HP 100, there&amp;rsquo;s a little momentary switch on the front left of the keyboard with an indicator light. It was correctly indicating that the wireless was disabled (by glowing orange) and if I flicked it, I could see in the settings the bluetooth was going off and on, but not the wireless.&lt;/p&gt;
&lt;h2 id="lubuntu"&gt;Lubuntu&lt;/h2&gt;
&lt;p&gt;Again, a painless install experience. The ISO was about twice the size at 2.7GB and the install took a lot longer, although much of it was familiar to me from the numerous Debian and Ubuntu server installs I&amp;rsquo;ve done. Once it was installed and booted, the desktop seemed a bit chunky and dated compared to the flatter antiX, and although slower it was very usable.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/img_4846b.jpg" width="1000" alt=""&gt;
&lt;p&gt;The wifi didn&amp;rsquo;t work, although the indicator was blue suggesting it was turned on. In the menu was an option to check for needed propitiatory drivers, and when I plugged into the Ethernet and ran this, it decided there was a Broadcom chipset wifi that it knew the drivers for. I allowed it to fetch and install them, and the wireless came to life.&lt;/p&gt;
&lt;p&gt;The proprietary drivers not being installed is a common and reasonable thing, and almost certainly the issue with my antiX install, so it seemed like it was probably worth having another go at that plugged into the ethernet and enabling whatever is needed to allow the non-FOSS stuff.&lt;/p&gt;
&lt;h2 id="antix-wifi"&gt;antiX wifi&lt;/h2&gt;
&lt;p&gt;My first thought was that perhaps I could just enable the non-free option for the Debian sources. The way that the apt package manager works is that there&amp;rsquo;s a list of sources it checks with. Some distros are strict about non-FOSS stuff and this needs changed in /etc/apt/sources to &lt;a href="https://serverfault.com/questions/240920/how-do-i-enable-non-free-packages-on-debian"&gt;make it check the non-free parts of the repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;antiX had a slightly different setup, with a whole directory of sources, but the non-free option was set. I also mucked around with the &lt;code&gt;rfkill&lt;/code&gt; command which kept saying the softblock was on - although I could see, by using rfkill list that the hardware button was working exactly how it should - flick it once and the hardware block for wifi and bluetooth was activated, flick it again and it went off.&lt;/p&gt;
&lt;p&gt;I also jumped off the cliff of just trying commands that I found on the internet that were suggested for similar sounding situations, and that I only had the shakiest idea of what they did. I&amp;rsquo;m certain that the problem at this stage is that I need to install those Broadcom 43 drivers. Without something poping up to ask me if I want to do that (which is exactly the sort of thing a lean distro wouldn&amp;rsquo;t have) I&amp;rsquo;m a bit lost.&lt;/p&gt;
&lt;p&gt;Antix seemed so right for my purposes, I might come back and try it again when my Linux knowledge is a bit better. In the mean time, I need a popular distro, lighter weight than Lubuntu, so I&amp;rsquo;ll give Mint a shot.&lt;/p&gt;
&lt;h2 id="mint-xfce"&gt;Mint Xfce&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://distrowatch.com/"&gt;Distrowatch&lt;/a&gt; currently lists Mint as #3, so it meets my &amp;ldquo;popular&amp;rdquo; criterion, and is has a Xfce (lightweight desktop environment) version, so perhaps it will run crisply on the Atom, but still hold my hand to install these wifi drivers.&lt;/p&gt;
&lt;p&gt;As with all the other distros, it went on smoothly. Once I booted in to it, some sort of system checker popped up to complain about the Broadcom drivers, and offered to extract them from the install USB - that didn&amp;rsquo;t work for me since I&amp;rsquo;d used &lt;a href="https://www.ventoy.net/en/index.html"&gt;Ventoy&lt;/a&gt; for the install, and the ISO was not loaded after I&amp;rsquo;d rebooted. Heading back up to the office to reconnect to an Ethernet cable allowed the system to download the drivers and five minutes later I was on wifi.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not sure if I&amp;rsquo;ve used it enough to be sure, but the performance with Mint Xfce seems similar to Lubuntu - ie not as good as antiX. Also, there&amp;rsquo;s a scary message saying long term support runs out in nine days since I had to go back to version 19.3 to find a 32 bit version.&lt;/p&gt;</description></item><item><title>Recursively Deleting Files in Linux</title><link>https://blog.iankulin.com/recursively-deleting-files-in-linux/</link><pubDate>Fri, 14 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/recursively-deleting-files-in-linux/</guid><description>&lt;p&gt;I&amp;rsquo;ve been using this rsync command to backup files from my NAS to a USB drive. The &amp;ndash;excludes are to avoid copying over some junk hidden files - some created by MacOS and some by Synology.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo rsync -rvit --exclude &amp;#39;*@eaDir*&amp;#39; --exclude &amp;#39;.DS_Store&amp;#39; /volume1/media/ /volumeUSB1/usbshare1-2/media --del
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;.DS_Store&lt;/code&gt; files seem to be dropped by MacOS every time I view a directory on the NAS from my MacBook. They&amp;rsquo;re not doing any harm, and they presumably do something handy for the Mac - remembering the view settings for that folder or some such. Nevertheless, they annoy me. It makes sense to not back them up - they don&amp;rsquo;t serve any useful purpose in that context.&lt;/p&gt;
&lt;p&gt;If I wanted to delete them anyway, how would I go about it? They are scattered randomly around, including in sub-directories of sub-directories. Is there some recursive flag I can add to &lt;code&gt;rm&lt;/code&gt; to accomplish this?&lt;/p&gt;
&lt;p&gt;I found a better solution on &lt;a href="https://askubuntu.com/questions/377438/how-can-i-recursively-delete-all-files-of-a-specific-extension-in-the-current-di"&gt;AskUbuntu&lt;/a&gt;, we can use the &lt;code&gt;find&lt;/code&gt; command. This way it&amp;rsquo;s easy to safely test your filename matching first before you destroy any files.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;find . -name &amp;#34;.DS_Store&amp;#34; -type f
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;-name&lt;/code&gt; is clear enough, and the &lt;code&gt;-type f&lt;/code&gt; option is just saying to look for files (rather than directories etc). The period at the start is the location, ie, start in the directory above so the current working directory is included. Once you&amp;rsquo;ve tuned this, you can add &lt;code&gt;-delete&lt;/code&gt; to go nuclear.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;find . -name &amp;#34;.DS_Store&amp;#34; -type f -delete
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Proxmox LXC backup to NFS share failing</title><link>https://blog.iankulin.com/proxmox-lxc-backup-to-nfs-share-failing/</link><pubDate>Wed, 12 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/proxmox-lxc-backup-to-nfs-share-failing/</guid><description>&lt;p&gt;I was doing updates on all my nodes and VM&amp;rsquo;s today, and backing up the VMs that aren&amp;rsquo;t already on a backup schedule. On my dev machine I have a Debian LXC container that I mostly just use for trying out Linux commands and playing around. I used to have a backup of it that I used a lot - after playing around I like to set it back to a fresh install plus my ssh keys - but I lost it somehow when moving the VM to new metal.&lt;/p&gt;
&lt;p&gt;When I tried to back it up today, I got this drama.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;INFO: starting new backup job: vzdump 200 --node pve-dev1 --mode snapshot --remove 0 --notes-template &amp;#39;{{vmid}}-{{guestname}} ({{node}}) - after timezone fix&amp;#39; --storage NAS-DS2 --compress zstd
INFO: Starting Backup of VM 200 (lxc)
INFO: Backup started at 2023-04-07 17:11:08
INFO: status = running
INFO: CT Name: babydeb
INFO: including mount point rootfs (&amp;#39;/&amp;#39;) in backup
INFO: backup mode: snapshot
INFO: ionice priority: 7
INFO: create storage snapshot &amp;#39;vzdump&amp;#39;
 Logical volume &amp;#34;snap_vm-200-disk-0_vzdump&amp;#34; created.
INFO: creating vzdump archive &amp;#39;/mnt/pve/NAS-DS2/dump/vzdump-lxc-200-2023_04_07-17_11_08.tar.zst&amp;#39;
INFO: tar: /mnt/pve/NAS-DS2/dump/vzdump-lxc-200-2023_04_07-17_11_08.tmp: Cannot open: Permission denied
INFO: tar: Error is not recoverable: exiting now
INFO: cleanup temporary &amp;#39;vzdump&amp;#39; snapshot
 Logical volume &amp;#34;snap_vm-200-disk-0_vzdump&amp;#34; successfully removed
ERROR: Backup of VM 200 failed - command &amp;#39;set -o pipefail &amp;amp;&amp;amp; lxc-usernsexec -m u:0:100000:65536 -m g:0:100000:65536 -- tar cpf - --totals --one-file-system -p --sparse --numeric-owner --acls --xattrs &amp;#39;--xattrs-include=user.*&amp;#39; &amp;#39;--xattrs-include=security.capability&amp;#39; &amp;#39;--warning=no-file-ignored&amp;#39; &amp;#39;--warning=no-xattr-write&amp;#39; --one-file-system &amp;#39;--warning=no-file-ignored&amp;#39; &amp;#39;--directory=/mnt/pve/NAS-DS2/dump/vzdump-lxc-200-2023_04_07-17_11_08.tmp&amp;#39; ./etc/vzdump/pct.conf ./etc/vzdump/pct.fw &amp;#39;--directory=/mnt/vzsnap0&amp;#39; --no-anchored &amp;#39;--exclude=lost+found&amp;#39; --anchored &amp;#39;--exclude=./tmp/?*&amp;#39; &amp;#39;--exclude=./var/tmp/?*&amp;#39; &amp;#39;--exclude=./var/run/?*.pid&amp;#39; ./ | zstd --rsyncable &amp;#39;--threads=1&amp;#39; &amp;gt;/mnt/pve/NAS-DS2/dump/vzdump-lxc-200-2023_04_07-17_11_08.tar.dat&amp;#39; failed: exit code 2
INFO: Failed at 2023-04-07 17:11:09
INFO: Backup job finished with errors
TASK ERROR: job errors
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href="https://blog.iankulin.com/could-it-be-a-permissions-problem/"&gt;Permissions&lt;/a&gt;! I was puzzled - the line before (creating the backup file) is working, but not creating the temp file on the same share and directory? Very odd. Backing up a real VM on the same node and to the same share was working fine. Luckily it&amp;rsquo;s a simple, and fast, matter to create a heap of LXCs with different settings and see if the error can be reproduced, so I was soon confidently able to say the problem only existed for unprivileged LXC containers backing up to the share - I didn&amp;rsquo;t have the problem if I used the local disk.&lt;/p&gt;
&lt;p&gt;If I dropped to the console for the node, I could create an identically named file in the same directory with no problems.&lt;/p&gt;
&lt;p&gt;During all that testing, I saw some output that led to more helpful thinking.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;INFO: starting new backup job: vzdump 303 --storage NAS-DS2 --compress zstd --notes-template &amp;#39;{{guestname}}&amp;#39; --remove 0 --node pve-dev1 --mode snapshot
INFO: Starting Backup of VM 303 (lxc)
INFO: Backup started at 2023-04-07 18:43:44
INFO: status = running
INFO: CT Name: apline-unpriv
INFO: including mount point rootfs (&amp;#39;/&amp;#39;) in backup
INFO: mode failure - some volumes do not support snapshots
INFO: trying &amp;#39;suspend&amp;#39; mode instead
INFO: backup mode: suspend
INFO: ionice priority: 7
INFO: CT Name: apline-unpriv
INFO: including mount point rootfs (&amp;#39;/&amp;#39;) in backup
INFO: temporary directory is on NFS, disabling xattr and acl support, consider configuring a local tmpdir via /etc/vzdump.conf
INFO: starting first sync /proc/39778/root/ to /mnt/pve/NAS-DS2/dump/vzdump-lxc-303-2023_04_07-18_43_44.tmp
INFO: first sync finished - transferred 9.35M bytes in 2s
INFO: suspending guest
INFO: starting final sync /proc/39778/root/ to /mnt/pve/NAS-DS2/dump/vzdump-lxc-303-2023_04_07-18_43_44.tmp
INFO: final sync finished - transferred 0 bytes in 0s
INFO: resuming guest
INFO: guest is online again after &amp;lt;1 seconds
INFO: creating vzdump archive &amp;#39;/mnt/pve/NAS-DS2/dump/vzdump-lxc-303-2023_04_07-18_43_44.tar.zst&amp;#39;
INFO: tar: /mnt/pve/NAS-DS2/dump/vzdump-lxc-303-2023_04_07-18_43_44.tmp: Cannot open: Permission denied
INFO: tar: Error is not recoverable: exiting now
ERROR: Backup of VM 303 failed - command &amp;#39;set -o pipefail &amp;amp;&amp;amp; lxc-usernsexec -m u:0:100000:65536 -m g:0:100000:65536 -- tar cpf - --totals --one-file-system -p --sparse --numeric-owner --acls --xattrs &amp;#39;--xattrs-include=user.*&amp;#39; &amp;#39;--xattrs-include=security.capability&amp;#39; &amp;#39;--warning=no-file-ignored&amp;#39; &amp;#39;--warning=no-xattr-write&amp;#39; --one-file-system &amp;#39;--warning=no-file-ignored&amp;#39; &amp;#39;--directory=/mnt/pve/NAS-DS2/dump/vzdump-lxc-303-2023_04_07-18_43_44.tmp&amp;#39; ./etc/vzdump/pct.conf ./etc/vzdump/pct.fw &amp;#39;--directory=/mnt/pve/NAS-DS2/dump/vzdump-lxc-303-2023_04_07-18_43_44.tmp&amp;#39; --no-anchored &amp;#39;--exclude=lost+found&amp;#39; --anchored &amp;#39;--exclude=./tmp/?*&amp;#39; &amp;#39;--exclude=./var/tmp/?*&amp;#39; &amp;#39;--exclude=./var/run/?*.pid&amp;#39; . | zstd --rsyncable &amp;#39;--threads=1&amp;#39; &amp;gt;/mnt/pve/NAS-DS2/dump/vzdump-lxc-303-2023_04_07-18_43_44.tar.dat&amp;#39; failed: exit code 2
INFO: Failed at 2023-04-07 18:43:47
INFO: Backup job finished with errors
TASK ERROR: job errors
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And sure enough, there is a helpful &lt;code&gt;/etc/vzdump.conf&lt;/code&gt; file. Uncommenting the &lt;code&gt;tmpdir&lt;/code&gt; line and pointing it to &lt;code&gt;/tmp&lt;/code&gt; fixed all my problems.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-07-at-6.50.45-pm-copy.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;So what&amp;rsquo;s going on? I did some googling and found some discussions &lt;a href="https://forum.proxmox.com/threads/cannot-backup-only-lxc-to-nfs-vm-works.90797/"&gt;1&lt;/a&gt;/&lt;a href="https://forum.proxmox.com/threads/in-7-0-i-cant-backup-a-container-to-a-nfs-that-worked-in-6-0.97808/"&gt;2&lt;/a&gt;/&lt;a href="https://forum.proxmox.com/threads/backup-of-lxc-containers-to-nfs-mount-fail.95146/"&gt;3&lt;/a&gt; in the &lt;a href="https://forum.proxmox.com/"&gt;Proxmox forums&lt;/a&gt;. They are saying it&amp;rsquo;s because the unprivileged containers (they don&amp;rsquo;t run as root, which seems like good practice) don&amp;rsquo;t have permissions for the NFS share directory. I feel there&amp;rsquo;s a few problems with this theory:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It seems to do fine creating the other files&lt;/li&gt;
&lt;li&gt;Why would the LXC container be doing this work? Surely the process is being run at the Proxmox level.&lt;/li&gt;
&lt;li&gt;Actually the LXC container should not have access to the NAS at all, even if it&amp;rsquo;s privileged - it&amp;rsquo;s not mounted in there, the LXC knows nothing about it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nevertheless, I&amp;rsquo;m sure they know better than me. If I was shipping this product, I&amp;rsquo;d probably engineer around this problem. Maybe by detecting it and switching to &lt;code&gt;/var/tmp&lt;/code&gt; or even just by making that the default in the config file.&lt;/p&gt;</description></item><item><title>Using NAS for Proxmox backups</title><link>https://blog.iankulin.com/using-nas-for-proxmox-backups/</link><pubDate>Mon, 10 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/using-nas-for-proxmox-backups/</guid><description>&lt;p&gt;&lt;a href="https://blog.iankulin.com/moving-a-vm-between-two-proxmox-hosts/"&gt;A few weeks ago&lt;/a&gt;, I was very excited to be able to take a snapshot of a virtual machine, copy it across the network from that Proxmox node, copy it back across the network to a different Proxmox node, start it there, and have it up and running, without it noticing it was actually on different hardware.&lt;/p&gt;
&lt;p&gt;Backing up a VM is pretty simple, you just click on the node, choose &lt;em&gt;Backup&lt;/em&gt; and click the &lt;em&gt;Backup Now&lt;/em&gt; button. The ease, and completeness of backing up a VM is one of the main reasons I&amp;rsquo;m using Proxmox for my systems.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-04-07-at-12.02.59-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-07-at-12.02.59-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;By default, VM backups are saved to the &amp;ldquo;local drive&amp;rdquo; - actually the &lt;code&gt;/var/lib/vz&lt;/code&gt; directory. This would not be useful if the physical machine dies, but also it&amp;rsquo;s not convenient to restore to a different machine. Ideally you&amp;rsquo;d have a central place to store these files that was accessible to all the Proxmox nodes.&lt;/p&gt;
&lt;p&gt;This is exactly the situation I&amp;rsquo;ve setup with my lab, the NAS is the storage for the VM backups. Each of the Proxmox nodes uses the same directory for backups, so moving a machine from one node to another is a simple as backing it up on one node, stopping the VM, and restoring it on another node just by choosing the backup file to restore in the web GUI.&lt;/p&gt;
&lt;h3 id="steps"&gt;Steps&lt;/h3&gt;
&lt;p&gt;Proxmox can use all sorts of shares as a location for backups (and other files such as the ISO&amp;rsquo;s used to boot new machines), but the simplest is probably &lt;a href="https://en.wikipedia.org/wiki/Network_File_System"&gt;NFS&lt;/a&gt;. This is also straightforward to do from the Synology NAS.&lt;/p&gt;
&lt;p&gt;In the web interface for the NAS, go into &lt;em&gt;Control Panel&lt;/em&gt;, &lt;em&gt;Shared Folder&lt;/em&gt; and create a new shared folder. I called mine Proxmox. One of the tabs there is for NFS permissions - just add the IP address of the Proxmox node that you&amp;rsquo;d life to access the folder.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-07-at-1.46.02-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not much harder from the Proxmox end. Although the storage you add will appear at the node level in the &lt;em&gt;Server View&lt;/em&gt; of the web GUI, it is added at the &lt;em&gt;Datacenter&lt;/em&gt; level.&lt;/p&gt;
&lt;p&gt;Go into &lt;em&gt;Storage&lt;/em&gt;, select &lt;em&gt;Add&lt;/em&gt; and choose &lt;em&gt;NFS&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-04-07-at-2.00.04-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-07-at-2.00.04-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then enter an ID (this will be the name of the storage in Proxmox) and the IP address. If you wait half a second, then you can click the dropdown for all the folders that are shared from that IP address.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-07-at-2.06.19-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The last field is content - this refers the the type of Proxmox stuff you want to keep in there - for backups, you just need VZDumps, but I usually click on everything since I&amp;rsquo;ll also use it for ISOs for new VMs and templates for LXCs.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-07-at-2.11.03-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve added that, the storage will appear in the server view, but also as an option when you go into &lt;em&gt;Backup&lt;/em&gt; for a VM and select &lt;em&gt;Backup Now&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-07-at-2.15.53-pm.png" alt=""&gt;&lt;/p&gt;</description></item><item><title>Where Do Docker Container Logs Go?</title><link>https://blog.iankulin.com/where-do-docker-container-logs-go/</link><pubDate>Sat, 08 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/where-do-docker-container-logs-go/</guid><description>&lt;p&gt;I&amp;rsquo;m still loving the Docker &amp;ldquo;just works&amp;rdquo; magic, despite their &lt;a href="https://www.theregister.com/2023/03/17/docker_free_teams_plan/"&gt;terrible PR skills&lt;/a&gt;, but sometimes I start a container, then the &lt;code&gt;docker ps -a&lt;/code&gt; shows it exited almost immediately. Clearly I&amp;rsquo;ve made a mistake, but there&amp;rsquo;s no stdout error message to tell me what I&amp;rsquo;ve done wrong, where is it.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at an example from today. I&amp;rsquo;m testing &lt;a href="https://filebrowser.org/"&gt;Filebrowser&lt;/a&gt; on a dev machine before I deploy it to the remote backup machine I&amp;rsquo;m assembling. And instead of following the &lt;a href="https://filebrowser.org/installation"&gt;official instructions&lt;/a&gt;, I&amp;rsquo;m following a &lt;a href="https://bobcares.com/blog/filebrowser-installation-in-docker/"&gt;blog post&lt;/a&gt; which has a few more details, but unfortunately also a small error.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-04-02-at-1.35.16-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-02-at-1.35.16-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The first sign of a problem is that the container is not running after I&amp;rsquo;ve launched it.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-02-at-1.42.09-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;To cat the log for the exited container is simple. Note that the (randomly provided) name for the container is &lt;code&gt;eager_haslett&lt;/code&gt; so to see the log we enter:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo docker logs eager_haslett
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The log output was:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;panic: While parsing config: invalid character &amp;#39;\n&amp;#39; in string literal

goroutine 1 [running]:
github.com/filebrowser/filebrowser/v2/cmd.initConfig()
	/home/runner/work/filebrowser/filebrowser/cmd/root.go:410 +0x346
github.com/spf13/cobra.(*Command).preRun(...)
	/home/runner/go/pkg/mod/github.com/spf13/cobra@v1.4.0/command.go:886
github.com/spf13/cobra.(*Command).execute(0x1828580, {0xc000030220, 0x0, 0x0})
	/home/runner/go/pkg/mod/github.com/spf13/cobra@v1.4.0/command.go:822 +0x44e
github.com/spf13/cobra.(*Command).ExecuteC(0x1828580)
	/home/runner/go/pkg/mod/github.com/spf13/cobra@v1.4.0/command.go:974 +0x3b4
github.com/spf13/cobra.(*Command).Execute(...)
	/home/runner/go/pkg/mod/github.com/spf13/cobra@v1.4.0/command.go:902
github.com/filebrowser/filebrowser/v2/cmd.Execute()
	/home/runner/work/filebrowser/filebrowser/cmd/cmd.go:9 +0x25
main.main()
	/home/runner/work/filebrowser/filebrowser/main.go:8 +0x17
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There&amp;rsquo;s our answer right at the top of the log - there&amp;rsquo;s a newline character in the middle of a key:value pair in the config JSON. If you look at the blog page above you can see it after the database.db&lt;/p&gt;</description></item><item><title>Allowing Proxmox to use a Dynamic IP</title><link>https://blog.iankulin.com/allowing-proxmox-to-use-a-dynamic-ip/</link><pubDate>Thu, 06 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/allowing-proxmox-to-use-a-dynamic-ip/</guid><description>&lt;p&gt;I&amp;rsquo;ve &lt;a href="https://blog.iankulin.com/proxmox-dynamic-ip/"&gt;discussed before&lt;/a&gt;, that when you first install Proxmox, it grabs an IP address from your DHCP server (this usually runs in your ISP modem if you haven&amp;rsquo;t created a better setup), but then it stores it as a static ip. This is a sort of compromise that makes sense and works for most circumstances.&lt;/p&gt;
&lt;p&gt;As soon as I&amp;rsquo;ve provisioned a new Proxmox server, I then usually tell the DHCP server, to always serve that address to the MAC address of the new Proxmox server. Since Proxmox does not use the DHCP server on subsequent boots, all that really does is prevent the DHCP server give the same IP address out to another device - which had happened to me prompting the earlier post. The DHCP server had given the address to a wifi lightbulb while the server was off, then when the Proxmox server booted up, the netwrok access was all messed up.&lt;/p&gt;
&lt;p&gt;In general, servers should have a static IP address - they are providing resources that other devices on the network need to access, so the combination of grabbing a DHCP address, using it statically, then me locking it in at the DHCP server makes sense.&lt;/p&gt;
&lt;p&gt;Except that I&amp;rsquo;m building a system with a couple of VM&amp;rsquo;s and a NAS that I&amp;rsquo;m going to post off, and have it set up by a non-techie at a remote site. So I really need Proxmox on that machine to look for a DHCP server when it boots and collect a dynamic IP address. Like a lot of things in Linux, this is quite a simple change if you know where to look.&lt;/p&gt;
&lt;h3 id="what-to-change"&gt;What to Change&lt;/h3&gt;
&lt;p&gt;The configuration file for the network interfaces is /&lt;code&gt;etc/network/interfaces&lt;/code&gt; the one on the Proxmox machine I&amp;rsquo;m setting up looked like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;iface lo inet loopback

iface eno1 inet manual

auto vmbr0
iface vmbr0 inet static
	address 192.168.100.30/24
	gateway 192.168.100.1
	bridge-ports eno1
	bridge-stp off
	bridge-fd 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;iface&lt;/code&gt; is short for interface, and is followed by the interface name. These are the same names you see when you type in &lt;code&gt;ip addr&lt;/code&gt; to see the IP addresses.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/edit.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;So this is the bit we are interested in:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;iface vmbr0 inet static
	address 192.168.100.30/24
	gateway 192.168.100.1
	bridge-ports eno1
	bridge-stp off
	bridge-fd 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;All that bridge stuff can stay the same, I&amp;rsquo;ll comment out the static bits and change it to use the DHCP. The final file looks like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;auto lo
iface lo inet loopback

iface eno1 inet manual

auto vmbr0
#iface vmbr0 inet static
#	address 192.168.100.30/24
#	gateway 192.168.100.1
iface vmbr0 inet dhcp
	bridge-ports eno1
	bridge-stp off
	bridge-fd 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I used the mac address to tell the DCHP server to allocate it a different address, and rebooted and Proxmox picked up the new address perfectly.&lt;/p&gt;
&lt;h3 id="hosts"&gt;Hosts&lt;/h3&gt;
&lt;p&gt;Now the server had a new address, there&amp;rsquo;s one more place I need to update; /etc/hosts contains the domain information you set during the Proxmox install, and it will include that old IP address. Once the system has a new one, it needs to be edited to include that.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;127.0.0.1 localhost.localdomain localhost
192.168.100.28 pve-kr01.local pve-kr01

# The following lines are desirable for IPv6 capable hosts

::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After the system is installed at the remote site and booted up, I&amp;rsquo;ll ssh in (with Tailscale) and make that change, and hopefully be able to access the DHCP server so it does not change in future.&lt;/p&gt;
&lt;h3 id="resources"&gt;Resources&lt;/h3&gt;
&lt;p&gt;I found these posts useful when figuring this out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://forum.proxmox.com/threads/set-a-dynamic-address-to-pve.119847/"&gt;Set a dynamic address to PVE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://schoolitexpert.com/network-tools/proxmox-ve/dynamic-ip-address"&gt;Dynamic IP address&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Versions: Proxmox 7.4-3&lt;/p&gt;</description></item><item><title>RAID Rescue</title><link>https://blog.iankulin.com/raid-rescue/</link><pubDate>Tue, 04 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/raid-rescue/</guid><description>&lt;p&gt;I&amp;rsquo;m in the process of shuffling disks around as I move towards my 3-2-1 storage arrangements. I thought after my extensive rsync adventures I&amp;rsquo;d mirrored everything everywhere, but then realised, with a sinking (no pun) feeling, after I&amp;rsquo;d repurposed a drive out of the 2 drive Synology as a USB caddy drive and wiped it, that I&amp;rsquo;d forgotten my audio book directory. All my rsync fiddling around had been on the video subdirectory of the media folder, not the whole media directory that included my audiobooks.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not the end of the world if I&amp;rsquo;d wiped them, I&amp;rsquo;ve just been working through downloading them from Audible and de-drming, so I could do that again in the few days I&amp;rsquo;ve got left till my subscription cancellation date comes around. That was a painful and slow process, so I don&amp;rsquo;t really want to.&lt;/p&gt;
&lt;p&gt;I still had one of the RAID drives that hadn&amp;rsquo;t been wiped, so in theory it should have a full copy of the data, and if I put it back in the Synology by itself it should work. That would be the same situation as if one drive in the RAID pool had died completely.&lt;/p&gt;
&lt;p&gt;A few screws, and a drive swap later and I&amp;rsquo;m looking at this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-31-at-4.35.40-pm-copy.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;There must be a tiny bit of storage in the Synology, so it knows I&amp;rsquo;ve been fiddling around. I hit &lt;em&gt;Recover&lt;/em&gt;, and it did the ten minute thing that I assume is it downloading and installing the new DSM.&lt;/p&gt;
&lt;p&gt;During this process, it started beeping in a plaintive way. I couldn&amp;rsquo;t access with the old Tailscale address, so I fired up the IP address to the web interface, logged in with the old credentials and was greeted with this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-31-at-4.08.58-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;It was not happy working in the degraded state, but the files were all still there, I was able to mount it to the other NAS and copy my files out. A success. The Tailscale package was still installed, so perhaps that business at the beginning was not really a new install of DSM, but some sort of checking.&lt;/p&gt;
&lt;p&gt;This was a good experience, it is worthwhile to test these scenarios, and I&amp;rsquo;m reassured to discover the audible beeping when the RAID pool was degraded. At the moment while I get everything sorted I&amp;rsquo;m in the web interfaces a lot, but the dream is this learning and setup time comes to an end and I just consume my self-hosted services without much manual intervention. In that scenario, some un-ignorable beeping when the NAS needs attention is a good thing.&lt;/p&gt;</description></item><item><title>HP EliteDesk 800 G2 Memory Upgrade</title><link>https://blog.iankulin.com/hp-elitedesk-800-g2-memory-upgrade/</link><pubDate>Sun, 02 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/hp-elitedesk-800-g2-memory-upgrade/</guid><description>&lt;p&gt;The hardware engineering of these corporate world mini-PCs is really nice. I swapped out the RAM today to bump my main machine up to 32GB from 16GB. It was a straightforward task - no screwdrivers, no drama.&lt;/p&gt;
&lt;p&gt;To open the machine up, there is a single large screw on the back that can be undone with your fingers - it&amp;rsquo;s a captive screw, as in it doesn&amp;rsquo;t fall out - just another nice engineering thought.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/img_4432.jpg" width="331" alt=""&gt;
&lt;p&gt;Once that&amp;rsquo;s undone, to get the case off, you just push down lightly on the top so the rubber feet grip the desk and slide it towards the front about an inch. Then it just lifts off - no wires. Once that&amp;rsquo;s off, you&amp;rsquo;ll see the SSD on the left (looking from the front) and fan on the right.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/img_4434.jpg" width="364" alt=""&gt;
&lt;p&gt;There&amp;rsquo;s a little plastic tab on the front of the fan just over the front USB ports. If you lift that up a little, you can pull the fan towards you a couple of centimeters then put it down on it&amp;rsquo;s back next to the case without unplugging its power. You can see the RAM modules were underneath the fan.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/img_4438.jpg" width="813" alt=""&gt;
&lt;p&gt;Either side of each RAM module you can see little metal clips. If you push these both outwards, the module will pop up to a 20° angle, then it can just be pulled out of the connector gently. Do this first for the top one, then the bottom.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/img_4439.jpg" width="1000" alt=""&gt;
&lt;p&gt;Inserting the new modules is done in the reverse order. Push the bottom one all the way into it&amp;rsquo;s socket at the same angle you took the other one out. Then with a finger on the raised edge, push it down until the clips both sides engage. Then do the same with the top one.&lt;/p&gt;
&lt;p&gt;When you flip the fan over to replace it, you&amp;rsquo;ll see that it has a small protrusion each side on the back legs, these slide into the two metal slots on top of the CPU cooler, then the fan just sits down into it&amp;rsquo;s previous spot. Lower the case top down about an inch from the back, slide it into place and finger tighten the screw, and you&amp;rsquo;re down.&lt;/p&gt;
&lt;h3 id="ram-specifications"&gt;RAM Specifications&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://support.hp.com/au-en/product/hp-elitedesk-800-35w-g2-desktop-mini-pc/7633266/manuals"&gt;Hardware Reference and Maintenance and Service guides&lt;/a&gt; for the HP EliteDesk 800 G2 Desktop Mini have this page on the RAM specifications. You&amp;rsquo;re looking for 1.2V DDR4-2133MHz SODIMMs, PC4-17000&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-03-26-at-3.11.40-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-26-at-3.11.40-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The eBay listing for the ones I bought said they were &amp;ldquo;SK Hynix 16GB DDR4 SODIMM RAM 2133 MHz Laptop PC4-17000 HMA82GS6MFRN-TF&amp;rdquo; and they went in and worked perfectly.&lt;/p&gt;</description></item><item><title>Proxmox Backup Files</title><link>https://blog.iankulin.com/proxmox-backup-files/</link><pubDate>Fri, 31 Mar 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/proxmox-backup-files/</guid><description>&lt;p&gt;I&amp;rsquo;ve got some extra RAM to drop into the HP 800 G2 mini that I use as my production server. I feel like that&amp;rsquo;s a low risk change, but since it&amp;rsquo;s easy to take VM snapshots I shutdown the VM&amp;rsquo;s and did that, and wanted to just copy them off the local storage.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m moving towards having these backups (and the ISOs) on the NAS rather than locally, but have not implemented that. So to get my backups I need to SSH in and find them.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://pve.proxmox.com/wiki/Storage:_Directory"&gt;Proxmox documentation for storage&lt;/a&gt; says to have a look in &lt;code&gt;/etc/pve/storage.cfg&lt;/code&gt; to see what&amp;rsquo;s up. Mine looks like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;dir: local
	path /var/lib/vz
	content iso,vztmpl,backup

lvmthin: local-lvm
	thinpool data
	vgname pve
	content rootdir,images
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And sure enough, if I look in &lt;code&gt;/var/lib/vz/dump&lt;/code&gt; (dump is the backup location mentioned in the docs):&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-03-26-at-11.59.10-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-26-at-11.59.10-am.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I ain&amp;rsquo;t messing around this morning, so I&amp;rsquo;ll just grab these onto my laptop with scp.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;scp root@192.168.100.23:/var/lib/vz/dump/\* Downloads
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You may notice in the command above that I&amp;rsquo;ve got a backslash in front of the wildcard. This was a little gotcha that is specific to using zsh/OhMyZsh that I had to escape the wildcard. I found I could specify the whole filename and it worked okay, but the wildcards needed escaping. Thanks again &lt;a href="https://superuser.com/questions/420525/scp-with-zsh-no-matches-found"&gt;StackExchange&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-26-at-12.15.35-pm.png" alt=""&gt;&lt;/p&gt;</description></item><item><title>rsync episode IV - a sudo hope</title><link>https://blog.iankulin.com/rsync-episode-iv-a-sudo-hope/</link><pubDate>Thu, 30 Mar 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/rsync-episode-iv-a-sudo-hope/</guid><description>&lt;p&gt;With all those earlier rsync bumps out of the way, I was ready to try my first rsync backup at the command line to sync my movies directory on the NAS to a (NTFS formatted) USB drive plugged into the same NAS. This is to be one of the simplest since there&amp;rsquo;s no remote server involved, just copying from mount point directory to another - so no drama with remote permissions.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a lot of files involved, and I knew from running the dry run that there would be a lot of output. I could see a few error messages, but each of the file copies was taking a while so I was confident they, at least, were working. If you missed the last episode, here&amp;rsquo;s where I landed for this rsync command.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rsync -avi --exclude &amp;#39;*@eaDir*&amp;#39; /volume1/media/video/Movies/ /volumeUSB1/usbshare/media/video/Movies --del
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Before I worried about the error messages, I had a look to see if the files had been copied correctly, but they had not. Even though each file was taking about the right amount of time to copy, the new files were not making it to the destination directories. Nor did they seem to be anywhere else. So I guess go back to the error messages and try to understand them. Here&amp;rsquo;s a &lt;em&gt;very&lt;/em&gt; condensed selection of the output.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;
rsync: failed to set times on &amp;#34;/volumeUSB1/usbshare/media/video/Movies/Jungle Book (1942)&amp;#34;: Operation not permitted (1)

...

&amp;gt;f+++++++++ Jungle Book (1942)/Jungle Book (1942).mkv
&amp;gt;f+++++++++ Jungle Book (1942)/trailer.mp4

...

rsync: mkstemp &amp;#34;/volumeUSB1/usbshare/media/video/Movies/Jungle Book (1942)/.Jungle Book (1942).mkv.Wd141R&amp;#34; failed: Operation not permitted (1)

rsync: mkstemp &amp;#34;/volumeUSB1/usbshare/media/video/Movies/Jungle Book (1942)/.trailer.mp4.TNu7UC&amp;#34; failed: Operation not permitted (1)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This log is from one of my later attempts. This video was new on the NAS so an earlier running of the rsync would have had some more lines about creating the directory on the USB - and I had a through the file browser in the NAS. The correct directory had been created, but there were no files in it.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s have a look at this output a bit at a time:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rsync: failed to set times on &amp;#34;/volumeUSB1/usbshare/media/video/Movies/Jungle Book (1942)&amp;#34;: Operation not permitted (1)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;rsync is trying to update the date/time of the destination folder to match the source one - something similar to the &lt;code&gt;touch&lt;/code&gt; command. &lt;code&gt;not permitted&lt;/code&gt; sounds like a &lt;a href="https://blog.iankulin.com/could-it-be-a-permissions-problem/"&gt;permissions issue&lt;/a&gt;. There was one of these messages for every directory and they were all near the start of the log.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;f+++++++++ Jungle Book (1942)/Jungle Book (1942).mkv
&amp;gt;f+++++++++ Jungle Book (1942)/trailer.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These are not errors, but encouraging output. The code at the beginning says what&amp;rsquo;s going on - they are files, and are being copied from the source to the destination. These showed up for every file that was in the source but not the destination, and the times for the file copies felt about right - the .mkv file took a while, the trailer was quick. These messages were all together in the middle of the log.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rsync: mkstemp &amp;#34;/volumeUSB1/usbshare/media/video/Movies/Jungle Book (1942)/.Jungle Book (1942).mkv.Wd141R&amp;#34; failed: Operation not permitted (1)

rsync: mkstemp &amp;#34;/volumeUSB1/usbshare/media/video/Movies/Jungle Book (1942)/.trailer.mp4.TNu7UC&amp;#34; failed: Operation not permitted (1)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;[mkstemp](https://man7.org/linux/man-pages/man3/mkstemp.3.html)&lt;/code&gt; is a command for creating a temporary file. It&amp;rsquo;s common in operating systems to use a hidden temporary file when transferring a file - you save into the temp file then rename it when its successfully completed. I&amp;rsquo;m guessing that&amp;rsquo;s what&amp;rsquo;s happening here, and it&amp;rsquo;s failing for permissions reasons. What I don&amp;rsquo;t understand is why they would all be grouped together at the end of the log instead of back next to each individual copy.&lt;/p&gt;
&lt;p&gt;But anyway, this is clearly a permissions problem. I can easily check this just by trying the copy manually. I&amp;rsquo;m still logged in as the same user who executed the &lt;code&gt;rsync&lt;/code&gt; command so the &lt;code&gt;cp&lt;/code&gt; will have the same rights etc and so should also fail&amp;hellip;&lt;/p&gt;
&lt;p&gt;No - this copy worked perfectly. No error message, and when I used the NAS filebrowser, the new file had correctly copied onto the USB drive.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cp /volume1/media/video/Movies/&amp;#39;Jungle Book (1942)&amp;#39;/&amp;#39;Jungle Book (1942).mkv&amp;#39; /volumeUSB1/usbshare/media/video/Movies/&amp;#39;Jungle Book (1942)&amp;#39;/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So somehow &lt;code&gt;rsync&lt;/code&gt; is running with lower permissions than &lt;code&gt;cp&lt;/code&gt; when executed by the same user? Well, (grasping at straws now) what if I &lt;code&gt;sudo&lt;/code&gt; it? I tried that, and (1) there&amp;rsquo;s no &lt;em&gt;set times&lt;/em&gt; error, (2) all the file copies worked, and (3) no &lt;em&gt;mkstemp&lt;/em&gt; errors.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo rsync -avi --exclude &amp;#39;*@eaDir*&amp;#39; /volume1/media/video/Movies/ /volumeUSB1/usbshare/media/video/Movies --del
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I did notice one other difference, there was lots of &amp;lsquo;o&amp;rsquo; in the itemize changes output:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;.d..tpo.... Jungle Book (1942)/
&amp;gt;f..tpo.... Jungle Book (1942)/Jungle Book (1942).mkv
&amp;gt;f+++++++++ Jungle Book (1942)/trailer.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From the &lt;a href="https://download.samba.org/pub/rsync/rsync.1#opt--itemize-changes"&gt;man page&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.d..tpo....&lt;/code&gt; - not being copied, it&amp;rsquo;s a directory, modification time is different and is being updated, permissions are different and are being updated, the owner is different and is being updated&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;gt;f..tpo....&lt;/code&gt; - is being copied to destination, it&amp;rsquo;s a file, modification time is different and is being updated, permissions are different and are being updated, the owner is different and is being updated&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;gt;f+++++++++&lt;/code&gt; - is being copied to destination, it&amp;rsquo;s a file, everything is being newly created so will be the same as the source&lt;/p&gt;
&lt;p&gt;So that&amp;rsquo;s a major step forward, the files are syncing correctly, and if I rerun the rsync and haven&amp;rsquo;t made any changes to the source files, it zips through. I do notice it is still updating the permissions and owner each time. This doesn&amp;rsquo;t produce an error message, but since it thinks they need done every run it suggests it is not happening correctly.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s possible the owner/permissions issue is related to the USB drive being NTFS formatted. It&amp;rsquo;s also possible I can get rsync to stop trying to change those since they are not important in this context. the &lt;code&gt;-a&lt;/code&gt; flag (short for archive) is a shortcut that pulls in a number of other flags. Perhaps I can just pick the ones I need and eliminate the owner and permissions ones.&lt;/p&gt;
&lt;p&gt;Having to sudo to get this to work does not seem like a great solution - presumably this will come back to bite me when I try and automate it. So there is still some figuring out to do, but at least one step of my backup is down now.&lt;/p&gt;</description></item><item><title>rsync / Synology / @eaDir</title><link>https://blog.iankulin.com/rsync-synology-eadir/</link><pubDate>Tue, 28 Mar 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/rsync-synology-eadir/</guid><description>&lt;p&gt;The reason I&amp;rsquo;ve been figuring out rsync is to setup my backup strategy. Eventually this will partly be managed with scheduled tasks (ie cron jobs) running rsync. I wanted the SSH in and try this out, since I didn&amp;rsquo;t know some basic things like the mount points of the shares.&lt;/p&gt;
&lt;h3 id="mount-points"&gt;Mount points&lt;/h3&gt;
&lt;p&gt;My first issue was to find the paths to all my data. This turned out not to be a drama. Each of the volumes you create when the NAS is set up are just in the root directory. This includes any USB drives plugged in.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-25-at-8.08.10-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Inside each of those &lt;em&gt;volumes&lt;/em&gt; are any &lt;em&gt;shares&lt;/em&gt; you&amp;rsquo;ve created. At the moment I want to rsync my movies which are in a &amp;lsquo;media&amp;rsquo; share on volume1 to the usb drive, so the directories I&amp;rsquo;ll be using are:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/volume1/media/video/Movies/&lt;/code&gt;&lt;br&gt;
&lt;code&gt;/volumeUSB1/usbshare/media/video/Movies&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="rsync-attempt"&gt;rsync attempt&lt;/h3&gt;
&lt;p&gt;rsync has a cool feature whereby you can do a &amp;lsquo;dry run&amp;rsquo; where it goes through the motions of the command you&amp;rsquo;ve given it, but doesn&amp;rsquo;t change any files. If you combine this with the verbose output, you can clearly see what it&amp;rsquo;s going to do before you let it start changing things. That&amp;rsquo;s an especially good idea when you&amp;rsquo;re dealing with large amounts of data, so my first pass at this included the -n option.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rsync -avin /volume1/media/video/Movies/ /volumeUSB1/usbshare/media/video/Movies --del
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The situation with these two lots of data is that I&amp;rsquo;ve copied my media off the USB drive onto the NAS, then when I installed Jellyfin to access it, I discovered lots of misnamed items (had the years incorrect mostly) and I&amp;rsquo;ve been combining some directories, and renaming others and so on. So I expected this first run or rsync to pull up a heap of changes to make, which it did - thousands of lines of them.&lt;/p&gt;
&lt;p&gt;I noticed a lot of them included this weird directory that I didn&amp;rsquo;t recognise.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;f+++++++++ @eaDir/Tora Tora Tora (1970 PG)@SynoEAStream
&amp;gt;f+++++++++ @eaDir/Tora Tora Tora (1970 PG)@SynoResource
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;rsquo;ve since learned it might be extended attributes, people started noticing it around the introduction of DSM7. &lt;a href="https://tech.webit.nu/synology-nas-those-eadir-folders/"&gt;I don&amp;rsquo;t seem to be the only user who hates&lt;/a&gt; Synology messing with my data. There&amp;rsquo;s some consensus they are created by the indexing service (which I&amp;rsquo;ve turned off as much as is possible in the GUI) and when the &lt;a href="https://www.reddit.com/r/synology/comments/exh5ho/preventing_eadir_from_being_created/"&gt;drives are externally mounted&lt;/a&gt; - which of course I have been doing quite a bit while moving things around.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll tackle removing them all and trying to prevent their reoccurence another day, but for the moment, I&amp;rsquo;ll just tell rsync to ignore them using the &lt;code&gt;--exclude&lt;/code&gt; option.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rsync -avin --exclude &amp;#39;*@eaDir*&amp;#39; /volume1/media/video/Movies/ /volumeUSB1/usbshare/media/video/Movies --del
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>SSH with Keys to Synology</title><link>https://blog.iankulin.com/ssh-with-keys-to-synology/</link><pubDate>Mon, 27 Mar 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/ssh-with-keys-to-synology/</guid><description>&lt;p&gt;The Synology operating system DSM (I&amp;rsquo;m on DSM 7.1.1) is Linux, but its highly customised for the purpose of making running a complicated Linux NAS doable for less technical users.&lt;/p&gt;
&lt;p&gt;Due to that, some things that are routine in a regular distro, require a few more steps to jump through to get them to work. SSH-ing in to a Synology with keys is one of those things.&lt;/p&gt;
&lt;h3 id="should-you"&gt;Should you?&lt;/h3&gt;
&lt;p&gt;Before you do start fiddling around, it&amp;rsquo;s probably worth mentioning that almost all the things you might want to do on the Synology can be accomplished through their web interface, or by installing a &amp;lsquo;package&amp;rsquo; from the &lt;em&gt;Package Center&lt;/em&gt;. For example, if you need to run a cron job, that&amp;rsquo;s done through the &lt;em&gt;Control Panel&lt;/em&gt; &amp;lsquo;&lt;em&gt;Task Scheduler&lt;/em&gt;&amp;rsquo;. If you need TailScale installed to easily access it over Wireguard, there&amp;rsquo;s a TailScale package. In general it&amp;rsquo;s probably easier and safer to do things their way.&lt;/p&gt;
&lt;h3 id="enabling-ssh"&gt;Enabling SSH&lt;/h3&gt;
&lt;p&gt;Before you can SSH into the Synology, you need to enable the SSH service. This is straightforward with the web interface. In &lt;em&gt;Control Panel&lt;/em&gt;, look for &lt;em&gt;Terminal &amp;amp; SMNP&lt;/em&gt; and tick the box, and click &lt;em&gt;Apply&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-25-at-6.01.35-pm.png" alt=""&gt;&lt;/p&gt;
&lt;h3 id="home-directory"&gt;Home directory&lt;/h3&gt;
&lt;p&gt;If you SSH to the Synology now, it works, but you&amp;rsquo;ll notice that there&amp;rsquo;s a warning message saying &amp;ldquo;Could not chdir to home directory /var/services/homes/&lt;user name&gt;: No such file or directory&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-25-at-7.12.36-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The reason for this is that unlike most distros, when you create a user in DSM, there&amp;rsquo;s no home directory created for them. There must be some bash config somewhere since I get that nice prompt, but no user home directory.&lt;/p&gt;
&lt;p&gt;Lot&amp;rsquo;s of times, you could just ignore that warning, you can still probably do what you wanted to, but it is going to be an issue for installing SSH keys - when you do the &lt;code&gt;ssh-copy-id&lt;/code&gt; it will want to create a .ssh file in the user&amp;rsquo;s home directory, and if they haven&amp;rsquo;t got one, that is not going to work. You&amp;rsquo;ll get a similar sort of error saying something like&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;sh: line 0: cd: /var/services/homes/&amp;lt;user_name&amp;gt;: No such file or directory mkdir: cannot create directory '.ssh': Permission denied&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Again, there&amp;rsquo;s a setting in the web interface to create home directories for the users. We&amp;rsquo;re in the &lt;em&gt;Control Panel&lt;/em&gt; again, but this time look for &lt;em&gt;User &amp;amp; Group&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-25-at-6.20.35-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Tick the box for &lt;em&gt;Enable user home service&lt;/em&gt;, and hit &lt;em&gt;Apply&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Now you&amp;rsquo;ll be able to copy the keys as usual with ssh-copy-id.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-03-25-at-7.25.59-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-25-at-7.25.59-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>rsync basics</title><link>https://blog.iankulin.com/rsync-basics/</link><pubDate>Sun, 26 Mar 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/rsync-basics/</guid><description>&lt;p&gt;I&amp;rsquo;ve started down the path of improved storage management, including embracing the &lt;a href="https://www.backblaze.com/blog/the-3-2-1-backup-strategy/"&gt;3-2-1&lt;/a&gt; mantra. I&amp;rsquo;ve settled on a RAID6 NAS for local, mirrored to an off-site NAS, and an offline local USB drive.&lt;/p&gt;
&lt;p&gt;While I&amp;rsquo;ve been setting those up, my services have been live, so files have been changing on my main storage, which I&amp;rsquo;ve then switched to the bigger NAS, and I&amp;rsquo;ve been trying to keep data in sync by remembering what changes have been made where, and manually replicating them. That&amp;rsquo;s not sustainable and not the plan.&lt;/p&gt;
&lt;h3 id="beyond-compare"&gt;Beyond Compare&lt;/h3&gt;
&lt;p&gt;Many years ago, on Windows, I paid for a file/text comparison app called &lt;a href="https://www.scootersoftware.com/features.php"&gt;Beyond Compare&lt;/a&gt; - it says on their website that these are perpetual licences, and I&amp;rsquo;ve had many years of value out of it. I think I bought it about the same time I dabbled in version control thanks to the excellent book Code Complete (&lt;a href="https://www.amazon.com.au/Code-Complete-Steve-McConnell/dp/0735619670"&gt;this link&lt;/a&gt; is to the second version, but I&amp;rsquo;ve had both over the years).&lt;/p&gt;
&lt;p&gt;If you have to do manual syncing jobs between different directories or machines, this is an excellent graphical tool to do that with. I have never tried any other software because it&amp;rsquo;s always just done exactly what I needed with no hassle. Highly recommend.&lt;/p&gt;
&lt;p&gt;However, doing things manually is no way to do backups - you need a setup that runs without human intervention so it actually gets done.&lt;/p&gt;
&lt;h2 id="rsync"&gt;rsync&lt;/h2&gt;
&lt;p&gt;I never fail to be amazed at the substantial but unassuming command line tools from the Linux world, and this is another one. &lt;code&gt;rsync&lt;/code&gt; is perfect for this specific job - of keeping my remote backup in sync with the production storage. Here&amp;rsquo;s a few examples of how it works.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say I&amp;rsquo;ve got two directories in my home folder called localdir and remotedir. localdir has some files in it and I want them copied to the remotedir. &lt;code&gt;rsync&lt;/code&gt; can do that for us with this command. The &lt;code&gt;-a&lt;/code&gt; flag does a few things, including recursing into directories and preserving some of the file attributes when it copies files. I found if I didn&amp;rsquo;t do this, and just used -&lt;code&gt;r&lt;/code&gt; for the recursion, rsync&amp;rsquo;s system for checking for changes didn&amp;rsquo;t work.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rsync -a localdir/ remotedir
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-25-at-1.13.44-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Okay, that&amp;rsquo;s not impressive, I could have done the same thing with &lt;code&gt;cp&lt;/code&gt; to copy those files. And actually I could do that for my remote backup as well, except there&amp;rsquo;s no point burning up resources for copying identical files on top of each other. Really we just want to copy the files that have changed, or new files that have been added.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s try that by editing &lt;code&gt;file1.txt&lt;/code&gt;, and adding a new &lt;code&gt;file0.txt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-25-at-1.18.57-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;You might be thinking that perhaps rsync really is just copying all the files again. We can add a couple more flags to get rsync to tell us what is getting copied (&lt;code&gt;-v&lt;/code&gt; for verbose, and &lt;code&gt;-i&lt;/code&gt; which gives some output explaining how it decided a file needed copied.&lt;/p&gt;
&lt;p&gt;Without making any changes, let&amp;rsquo;s re-run the rsync with those flags. We shouldn&amp;rsquo;t see any files updated.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-25-at-1.24.51-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;So no files transferred, but if we edit a one and change the timestamp on another one, that should trigger a couple of files to be copied.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-25-at-1.27.48-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The optimisation in rsync extends further than just copying the files that have changed, the &lt;a href="https://download.samba.org/pub/rsync/rsync.1"&gt;man page&lt;/a&gt; says &amp;ldquo;&lt;em&gt;It is famous for its delta-transfer algorithm, which reduces the amount of data sent over the network by sending only the differences between the source files and the existing files in the destination.&lt;/em&gt;&amp;rdquo;&lt;/p&gt;
&lt;h3 id="deletions"&gt;Deletions&lt;/h3&gt;
&lt;p&gt;So that covers copying most of the changes in the source, but what about if a local file is deleted, does that propagate to the destination and delete the file there? Naturally, there is also an option for this; simply add &lt;code&gt;--del&lt;/code&gt; to the end of the command:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-25-at-3.35.49-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;h3 id="remote-hosts"&gt;Remote hosts&lt;/h3&gt;
&lt;p&gt;So far, all of these examples have been between directories on a single instance. What about on a remote machine? There&amp;rsquo;s a couple of steps.&lt;/p&gt;
&lt;p&gt;First, the machine where the rsync is being executed must have ssh access to the other machine. This is just the usual ssh setup - &lt;code&gt;ssh-keygen&lt;/code&gt; some keys if you don&amp;rsquo;t have any, then copy them over with &lt;code&gt;ssh-copy-id&lt;/code&gt; and we&amp;rsquo;re ready to go&lt;/p&gt;
&lt;p&gt;The second part is to add an ssh like address to the remote directory. So instead of just&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rsync -avi localdir/ remotedir --del
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;it will be&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rsync -avi localdir/ ian@192.168.100.33:remotedir --del
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Before I&amp;rsquo;ve run this, I&amp;rsquo;ve sshed in and created the directory &lt;code&gt;remotedir&lt;/code&gt; on the target machine, but then it&amp;rsquo;s simple as&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-25-at-5.06.01-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;h3 id="reading"&gt;Reading&lt;/h3&gt;
&lt;p&gt;To figure all this out, I&amp;rsquo;ve leaned heavily on this tutorial &amp;ldquo;&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-use-rsync-to-sync-local-and-remote-directories"&gt;How To Use Rsync to Sync Local and Remote Directories&lt;/a&gt;&amp;rdquo; from Digital Ocean, and for the stuff about deleting remote files when they are deleted locally, on &lt;a href="https://askubuntu.com/questions/476041/how-do-i-make-rsync-delete-files-that-have-been-deleted-from-the-source-folder"&gt;this Stack Exchange&lt;/a&gt; question.&lt;/p&gt;
&lt;p&gt;The source of truth, is the surprisingly readable &lt;a href="https://download.samba.org/pub/rsync/rsync.1"&gt;man page&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>CPU Comparisons</title><link>https://blog.iankulin.com/cpu-comparisons/</link><pubDate>Fri, 24 Mar 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/cpu-comparisons/</guid><description>&lt;img src="https://blog.iankulin.com/images/cloud.jpg" width="800" alt=""&gt;
&lt;p&gt;When I was a young whipper-snapper, working at the &amp;ldquo;data processing&amp;rdquo; centre, you could see if one CPU was better than another one by the CPU name/number. No one wanted an 8086 once the 286&amp;rsquo;s came out. Then a 386 was what you wanted for the latest multitasking support, but only till the 486 was available, then you wanted that for the gargantuan memory addressing.&lt;/p&gt;
&lt;p&gt;With that idea firmly in mind, I&amp;rsquo; wanted an i5 to be better than an i3, and an i7 better than all of them, but it&amp;rsquo;s &lt;a href="https://www.makeuseof.com/tag/compare-different-cpus-right-way/"&gt;apparently not that simple&lt;/a&gt;. I do come across people in forums talking about &amp;lsquo;generations&amp;rsquo; of Intel processors - so all this is probably decodable, but I&amp;rsquo;m not exactly sure how.&lt;/p&gt;
&lt;p&gt;Luckily, there are some handy CPU comparison sites like &lt;a href="http://versus.com"&gt;versus.com&lt;/a&gt;. I was looking at it tonight to try and decide if my new i5 6500T is better than my i7 6700T. Whichever one is best will get a RAM upgrade, and be the boss node in my cluster and run all my self-hosted services.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-21-at-8.45.04-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Spoiler alert - it turns out the i7 is not 2 better than the i5, they are almost identical except the i7 has double the threads (because Hyperthreading?) - a not insignificant different for a machine that&amp;rsquo;s running several VM&amp;rsquo;s&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-03-21-at-8.49.52-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-21-at-8.49.52-pm.png" width="787" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>HP Secure Boot Pain</title><link>https://blog.iankulin.com/hp-secure-boot-pain/</link><pubDate>Thu, 23 Mar 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/hp-secure-boot-pain/</guid><description>&lt;p&gt;Since the HP EliteDesk 800 G1 I&amp;rsquo;m using as a dev/homelab machine is going to be re-purposed as a media/backup server elsewhere, I&amp;rsquo;ve grabbed another G2 to use as a second box. The homelab machine serves as a backup device for the production server that runs my self-hosted services, but also is the machine I play with - testing my software, but also trying out any new self-hosted software I&amp;rsquo;m having a look out or configurarions I&amp;rsquo;m thinking about for the &amp;lsquo;production&amp;rsquo; server.&lt;/p&gt;
&lt;p&gt;Normally, installing Proxmox is pretty routine for me, but the newer G2&amp;rsquo;s have a Secure Boot &amp;lsquo;feature&amp;rsquo; which is probably super secure for installing Windows from the restore partition, but a pain for installing Proxmox from a USB. You&amp;rsquo;ll know this is the issue if you are trying to boot to the USB and getting the message &lt;code&gt;Selected boot image did not authenticate.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/img_4416.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;If you Google this error there&amp;rsquo;s good advice to turn it off in the BIOS settings (it&amp;rsquo;s on the &lt;code&gt;Advanced&lt;/code&gt; page by setting &lt;code&gt;Secure Boot&lt;/code&gt; to &lt;code&gt;Legacy Support Enable and Secure Boot Disable&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/img_4418.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;d probably think now would be the time for the old F10 Save &amp;amp; Exit, then start mashing the F9 (boot device) key while it reboots. At least, that&amp;rsquo;s what I thought, but every time I&amp;rsquo;d just get that same error, and if I went back into the BIOS, Secure Boot was somehow turned back on.&lt;/p&gt;
&lt;p&gt;When I googled &lt;em&gt;that&lt;/em&gt; problem, several responses in HP forums from HP support mentioned setting a BIOS password in order to be able to change some BIOS settings like secure boot. I tried, but it wouldn&amp;rsquo;t let me set the password. This was actually a blessing in disguise since this wasn&amp;rsquo;t the problem that was stopping me from turning secure boot off.&lt;/p&gt;
&lt;p&gt;I was right to be saving the settings and exiting with F10, but then instead of mashing buttons during what I thought was the reboot, I should have waited for a different message asking me to type in a number shown on the screen to confirm the change. I&amp;rsquo;m not sure what the purpose of this message is - perhaps it&amp;rsquo;s a &amp;lsquo;confirm you&amp;rsquo;re a human&amp;rsquo; thing. As you type in the numbers, they don&amp;rsquo;t appear, you just have to trust you&amp;rsquo;re doing it correctly. Once that&amp;rsquo;s done, you&amp;rsquo;ll be able to reboot, mach the F9, select the USB, and you&amp;rsquo;re on your way to Proxmox land.&lt;/p&gt;
&lt;h3 id="bonus-problem"&gt;Bonus Problem&lt;/h3&gt;
&lt;p&gt;If you get the message &lt;code&gt;No support for KVM virtualization detected&lt;/code&gt; from Proxmox.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/img_4419.jpg" width="560" alt=""&gt;
&lt;p&gt;Then follow its advice. Head into the BIOS settings and look for Virtualization Technology (VTx) and turn it on.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/img_4420.jpg" width="697" alt=""&gt;</description></item><item><title>Mounting one Synology NAS to another one</title><link>https://blog.iankulin.com/mounting-one-synology-nas-to-another-one/</link><pubDate>Tue, 21 Mar 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/mounting-one-synology-nas-to-another-one/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/img_4344.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I went over mounting a Synology NAS share on a Mac or Linux host &lt;a href="https://blog.iankulin.com/accessing-a-synology-nas-from-linux/"&gt;a while ago&lt;/a&gt;. Now I&amp;rsquo;ve populated a new NAS, and I want to copy my data over to it. I could mount them both to my laptop, and the data flow would look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;NAS1 - switch - wifi - laptop - wifi - switch - NAS2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since I&amp;rsquo;m copying 4TB, it will take a few hours, and if I forget what&amp;rsquo;s going on and close the laptop, or take it outside of my wifi the transfer will die, and I won&amp;rsquo;t be sure which files are patent. What might be better would be something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;NAS1 - switch - NAS2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is eminently possible, we just need to mount a share from one NAS into the other, then I can just initiate the copy and bug out, leaving it them to do their thing.&lt;/p&gt;
&lt;p&gt;DSM - the Synology operating system, is Linux, so in theory I could just SSH in and mount the other NAS with the &lt;code&gt;mount&lt;/code&gt; command, or by editing &lt;code&gt;/etc/fstab&lt;/code&gt;, but DSM is highly customised and slimmed down, and somethings are just outright different, sometimes in a dangerous way.&lt;/p&gt;
&lt;p&gt;On the flipside, Synology&amp;rsquo;s whole reason for existing is to make things easier and GUI-ier, so there&amp;rsquo;s no need to drop into the command line, we can do what we want quite easily via their web interface.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll start out on NAS2 (the empty one). Using FileStation, I&amp;rsquo;ll create a share, then inside that, I&amp;rsquo;ll create a &lt;code&gt;nas1&lt;/code&gt; directory - this will be the mount point. In the root of the share, &lt;code&gt;Open Tools | Mount Remote Folder&lt;/code&gt;. I&amp;rsquo;m using SMB/CIFS for my share so I&amp;rsquo;ll chose CIFS option, but obviously if you&amp;rsquo;re using NFS, use that instead.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-03-17-at-5.03.08-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-17-at-5.03.08-pm.png" width="1008" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Since underneath it&amp;rsquo;s actually running the &lt;code&gt;mount&lt;/code&gt; for us, or doing whatever their equivalent of editing &lt;code&gt;/etc/fstab&lt;/code&gt; is, it needs all the same information.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-03-17-at-5.04.45-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-17-at-5.04.45-pm.png" width="1007" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Once you press &lt;code&gt;Mount&lt;/code&gt;, the other (remote) NAS will appear in FileStation under &lt;code&gt;Remote Folder&lt;/code&gt;. The it was just a matter of &lt;code&gt;right click | copy&lt;/code&gt; on the NAS1 then &lt;code&gt;paste&lt;/code&gt; into the NAS2 share.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-03-17-at-5.05.43-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-17-at-5.05.43-pm.png" width="1009" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;According to &lt;a href="https://datarecovery.com/rd/does-hard-drive-rpm-affect-lifespan/"&gt;these guys&lt;/a&gt;, the theoretical max transfer rate of a 5400RPM drive (which is what I&amp;rsquo;m running in both NASs) is 75MB/s. NAS1 that I&amp;rsquo;m copying from is RAID1, so I assume if the OS is smart enough it could pull data from both disks at once, but I don&amp;rsquo;t really understand what happens with the writes when that data hits the RAID6. In any case, it was maxing out at around 70MB/s. Although most of the time it was anywhere between 30 and 70.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-03-17-at-5.55.58-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-17-at-5.55.58-pm.png" width="1008" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Proxmox VM Memory Upgrade</title><link>https://blog.iankulin.com/proxmox-vm-memory-upgrade/</link><pubDate>Sun, 19 Mar 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/proxmox-vm-memory-upgrade/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-16-at-6.36.10-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I ordered some RAM this week for my production server - it&amp;rsquo;s quickly becoming clear that memory is the limiting factor when running lots of services and VM&amp;rsquo;s that don&amp;rsquo;t get much use - rather than processing power. I&amp;rsquo;m not really a hardware guy, so figuring out exactly what RAM I need is a slightly fraught process - I won&amp;rsquo;t be fully confident I&amp;rsquo;ve ordered the right thing until I install it, boot up, and see my &lt;a href="https://support.hp.com/us-en/product/hp-elitedesk-800-35w-g2-desktop-mini-pc/7633266/document/c04816235"&gt;G2 800&lt;/a&gt; come to life maxed out at 32GB.&lt;/p&gt;
&lt;p&gt;Something that&amp;rsquo;s not fraught however, is upgrading the RAM in a virtual machine (VM) running under &lt;a href="https://www.proxmox.com/en/proxmox-ve"&gt;Proxmox&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="ram-hunger"&gt;RAM Hunger&lt;/h3&gt;
&lt;p&gt;I run two VM&amp;rsquo;s full time on the production node - a general docker host for a variety of small services, and a separate VM for &lt;a href="https://jellyfin.org/"&gt;Jellyfin&lt;/a&gt;. I&amp;rsquo;d allocated 6GB for this VM, but when I checked tonight ProxMox was reporting that 5GB was already being used.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-03-16-at-6.16.57-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-16-at-6.16.57-pm.png" width="974" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I have noticed that the Jellyfin memory usage seems to slowly grow over time. That might be related to my current usage pattern - I&amp;rsquo;m frequently re-scanning the libraries as I check and update the metadata.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-16-at-6.17.40-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;In any case, it needs more RAM, and I&amp;rsquo;ve got some up my sleeve on this physical machine so let&amp;rsquo;s allocate some more to the Jellyfin VM.&lt;/p&gt;
&lt;p&gt;Normally, you specify the amount of RAM to allocate when you&amp;rsquo;re creating the machine, but it&amp;rsquo;s quite straightforward to change it afterwards. With your VM selected, click into the &amp;ldquo;Hardware&amp;rdquo; page. Then if you double click on &amp;ldquo;Memory&amp;rdquo; a dialogue will open up to&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-16-at-6.18.19-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;You can just edit this number, in MB. Once you OK it, there will be two values listed for memory in the Hardware specs. The first is what the VM is running with now, and the second, orange value is what you are changing it to. In my case, I&amp;rsquo;ve bumped it up to 8GB from 6.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-16-at-6.19.47-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not possible to change the memory dynamically - it requires a reboot. Of course, rebooting the machine also restarts Jellyfin, so after the reboot we have plenty of headroom.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-16-at-6.58.21-pm.png" alt=""&gt;&lt;/p&gt;</description></item><item><title>No DNS on Proxmox machine</title><link>https://blog.iankulin.com/no-dns-on-proxmox-machine/</link><pubDate>Fri, 17 Mar 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/no-dns-on-proxmox-machine/</guid><description>&lt;p&gt;I had some more network weirdness setting up this new Proxmox machine. When I went to run the updates it couldn&amp;rsquo;t resolve any of the addresses:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@pve-kr01:~# apt update
Err:1 http://ftp.au.debian.org/debian bullseye InRelease
 Temporary failure resolving &amp;#39;ftp.au.debian.org&amp;#39;
Err:2 http://download.proxmox.com/debian/pve bullseye InRelease
 Temporary failure resolving &amp;#39;download.proxmox.com&amp;#39;
Err:3 http://security.debian.org bullseye-security InRelease
 Temporary failure resolving &amp;#39;security.debian.org&amp;#39;
Err:4 https://enterprise.proxmox.com/debian/pve bullseye InRelease
 Temporary failure resolving &amp;#39;enterprise.proxmox.com&amp;#39;
Err:5 http://ftp.au.debian.org/debian bullseye-updates InRelease
 Temporary failure resolving &amp;#39;ftp.au.debian.org&amp;#39;
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
All packages are up to date.
W: Failed to fetch http://ftp.au.debian.org/debian/dists/bullseye/InRelease Temporary failure resolving &amp;#39;ftp.au.debian.org&amp;#39;
W: Failed to fetch http://ftp.au.debian.org/debian/dists/bullseye-updates/InRelease Temporary failure resolving &amp;#39;ftp.au.debian.org&amp;#39;
W: Failed to fetch http://download.proxmox.com/debian/pve/dists/bullseye/InRelease Temporary failure resolving &amp;#39;download.proxmox.com&amp;#39;
W: Failed to fetch http://security.debian.org/dists/bullseye-security/InRelease Temporary failure resolving &amp;#39;security.debian.org&amp;#39;
W: Failed to fetch https://enterprise.proxmox.com/debian/pve/dists/bullseye/InRelease Temporary failure resolving &amp;#39;enterprise.proxmox.com&amp;#39;
W: Some index files failed to download. They have been ignored, or old ones used instead.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So some sort of DNS problem. The entry for the DNS is in &lt;code&gt;/etc/resolv.conf&lt;/code&gt; when I looked in there, it said:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;search local
nameserver 127.0.0.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Well, that does not seem great. I feel like it should be pointing at the DNS in my router, or even upstream at my ISP or google&amp;rsquo;s DNS server. Before I dive in and start editing, I thought I&amp;rsquo;d check my other servers. The first one has clearly been altered as part of installing TailScale, so that wasn&amp;rsquo;t much help, but on the dev machine it said:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;search local
nameserver 192.168.100.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Which is more like what I was expecting, that&amp;rsquo;s the address given out by my DHCP server for DNS. I could just edit the new machine to this, but since this is the third lot of network related weirdness related to this install (the second was that my managed switch&amp;rsquo;s web interface was down, and I couldn&amp;rsquo;t ping it, but it was still passing traffic, &lt;a href="https://blog.iankulin.com/netgear-gs108e-switch-problem/"&gt;again&lt;/a&gt;), and the first, discussed in yesterday&amp;rsquo;s post, was that DHCP had provided a dynamic address that was already assigned to another device.&lt;/p&gt;
&lt;p&gt;I swapped out the network cable, and noticed the port lights flashing. Perhaps there is a broken pair in the other cable? It was odd that it was working sort of.&lt;/p&gt;
&lt;p&gt;I reinstalled Proxmox from scratch, and carefully watched the console messages and checked all the network settings (it correctly picked up the reserved address and correct DNS server). Then everything worked.&lt;/p&gt;</description></item><item><title>Proxmox Dynamic IP</title><link>https://blog.iankulin.com/proxmox-dynamic-ip/</link><pubDate>Thu, 16 Mar 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/proxmox-dynamic-ip/</guid><description>&lt;p&gt;I ran into a little hiccup today. I&amp;rsquo;m building out a Jellyfin media server in a little HP G2 Mini PC. The config was going to be a Debian server inside Proxmox (because I love VM snapshots for backups) running Jellyfin in a container. There&amp;rsquo;ll be an external USB3 hard drive for the media storage.&lt;/p&gt;
&lt;p&gt;I was intending to build it all out and test it, then ship it to it&amp;rsquo;s final home.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve probably installed Proxmox five or six times by now since I&amp;rsquo;m always playing around with my test machine, and never really thought about the screen that comes up during the install showing the network details it&amp;rsquo;s picked up from DHCP.&lt;/p&gt;
&lt;p&gt;Today once I&amp;rsquo;d finished installing Proxmox, I couldn&amp;rsquo;t SSH into it&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-12-at-3.57.03-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I knew I had the right IP address since it shows that on the console at the end of the boot process. Looking in my router, it said 192.168.100.2 was connected, but by wifi on the SSID I use for IOT devices.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-12-at-4.01.09-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;That ESP device name is a giveaway - it&amp;rsquo;s one of my wifi light bulbs. A quick &lt;code&gt;ip addr&lt;/code&gt; on the new Proxmox via the console shows it is convinced that it is 192.168.100.2 I can ping 8.8.8.8 from it, but DNS is not working. My conclusion is that I&amp;rsquo;ve got two devices with the same IP on my network.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not sure how this came about. The network cable I was using is an old CAT5 with the clips broken off both ends I use for fiddling around, so perhaps there was a dodgy connection right at a crucial moment? It seems odd. usually when I encounter the &amp;rsquo;two machines with the same IP&amp;rsquo; problem, I&amp;rsquo;ve caused it somehow.&lt;/p&gt;
&lt;p&gt;No problem &lt;em&gt;I thought&lt;/em&gt;, now I&amp;rsquo;ve got the MAC address from the Proxmox machine, I&amp;rsquo;ll just reserve an available IP address for it. I did that, and rebooted Proxmox, but it was still on the old address. Then I remembered that question during the install process - it must collect an address from DHCP, then after the users has committed to it, write it into &lt;code&gt;/etc/hosts&lt;/code&gt; and &lt;code&gt;/etc/network/interfaces&lt;/code&gt; I reinstalled Proxmox, it picked up the new address and I saved it as the static IP.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s not really problem solved though - I&amp;rsquo;m sending this off to a network where I don&amp;rsquo;t know the network configuration. I was hoping just to let it pick up a DHCP address that would remain somewhat stable since the machine is going to be on 24/7.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not unreasonable for Proxmox to expect a VM host machine is going to have a static IP address, but it&amp;rsquo;s inconvenient for this situation. I&amp;rsquo;ll have to discover how to make it dynamic (probably by editing those two files). I&amp;rsquo;ll have Tailscale on it, so I can remote in afterwards to make it static, although without also reserving it in their router that carries a small risk too.&lt;/p&gt;</description></item><item><title>NAS Storage Calculations</title><link>https://blog.iankulin.com/nas-storage-calculations/</link><pubDate>Sat, 11 Mar 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/nas-storage-calculations/</guid><description>&lt;p&gt;I&amp;rsquo;ve been really happy with my two bay Synology NAS - a DS216j. The Synology&amp;rsquo;s seem to have great reputation for just pushing on. Mine is loaded up with two 8TB Seagate Barracudas in RAID 1 leaving me with a one drive failure redundancy.&lt;/p&gt;
&lt;p&gt;I guess a more hard-core host-er than me would be building their own array and using Unraid or ZFS or something. I&amp;rsquo;m pretty comfortable with the Synology off the shelf system; it&amp;rsquo;s a good match for my (low) level of expertise, and more robust than my previous storage system of a USB external drive.&lt;/p&gt;
&lt;p&gt;As I start to move real world applications out of the cloud and on to self-hosting, I need to be serious about availability and data security. The general standard for this in the self-hosting community seems to be three versions of data:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;production version&lt;/li&gt;
&lt;li&gt;local backup&lt;/li&gt;
&lt;li&gt;remote backup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I feel like the local and remote backups don&amp;rsquo;t &lt;em&gt;have&lt;/em&gt; to be NAS - a large enough external drive might be a reasonable cost saving. I would like the local backup to be able to be swapped into production though, so even if it was an external USB drive and would provide a degraded service, it should be able to be used to maintain services.&lt;/p&gt;
&lt;p&gt;A few days ago, there was an 8 bay, hot swappable Synology on eBay that got me a bit excited thinking about running different pools with a variety of RAIDs or just packing it with low cost smaller HDDs. Luckily I didn&amp;rsquo;t win it, but it triggered me to think about exactly what I need and what the trade-offs are.&lt;/p&gt;
&lt;h3 id="drive-quality"&gt;Drive Quality&lt;/h3&gt;
&lt;p&gt;The drives I&amp;rsquo;ve got in my NAS are second-hand, brand name non-NAS drives. They had just under 9000 hours on them, and the company selling them had hundreds of identical drives. From this, I&amp;rsquo;m assuming they came out of a data centre who replace drives at the one year mark.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s possible to buy &amp;ldquo;NAS&amp;rdquo; drives (and also &amp;ldquo;surveillance&amp;rdquo; drives) which are sold for uses where they are in use 24/7. They cost more.&lt;/p&gt;
&lt;p&gt;Is it possible these two types of drives (and perhaps even the USB external drives) are all the same drives, just marketed differently? Well, it&amp;rsquo;s possible. But it&amp;rsquo;s also possible they are mechanically identical, but have been sent to different market segments based on their initial test results.&lt;/p&gt;
&lt;h3 id="raid"&gt;RAID&lt;/h3&gt;
&lt;p&gt;RAID is a way of combining physical disks into one logical volume, usually in a way that reduces the capacity but allows for a drive failure without data loss. There&amp;rsquo;s several different &amp;rsquo;levels&amp;rsquo; of RAID. My current setup is RAID1 - I have two 8TB disks which present to the system as a single 8TB disk, but if one drive fails, I still have access to all of my data. If you have more than a couple of disks, RAID5 is a better option - if you had 3 x 8TB drives, you&amp;rsquo;d end up with 16TB usable space, and still be able to tolerate one drive failure. If you&amp;rsquo;re super cautious, RAID6 will allow two drive failures before you&amp;rsquo;re in danger. Of course this comes at a cost, if we had a 4x 8TB drive setup, there&amp;rsquo;d only be 16TB available, but any two of the drives could die without stopping the system.&lt;/p&gt;
&lt;h3 id="scoping-out-the-options"&gt;Scoping out the options&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve decided I need about 12TB - I currently have about 3TB of media locally and 0.5TB of general data on my laptop, that&amp;rsquo;s backed up to an external drive about weekly, along with about 20GB in DropBox. The bottom Dropbox plan is about AUD190 for 2TB, and I&amp;rsquo;m only using a fraction of it, so as part of my self-hosting that will get canned. 12TB seems like a lot of headroom from 4TB which is about where I&amp;rsquo;m sitting. I&amp;rsquo;d like to offer a couple of TB to whichever relative ends up hosting my remote backup. And finally it&amp;rsquo;s a multiple of 6TB which is a common ex-enterprise second hand drive size on ebay, so I know I can get year old ones for about $100&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll run through the thinking of each of the options I&amp;rsquo;ve considered.&lt;/p&gt;
&lt;h3 id="ds412"&gt;DS412+&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-10-at-5.14.28-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I had a couple of Synologys with more bays than this in my eBay watchlist, but as you add drives, you add power consumption and heat, so I think realistically at the 12TB point, 4 bays is the most you could justify. There&amp;rsquo;s a bit of a price step up as well when you go to five bays and leave the serious home user segment of the market.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s worth mentioning how the Synology model numbers work in the DS range. My existing unit is a DS216j. The 2 is for two bays and it&amp;rsquo;s 2016 model. So the DS412+ is from 2012 and has four bays. The &amp;lsquo;j&amp;rsquo; on mine denotes its a mouse-power CPU - in this case a Marvell Armada 385 - some sort of low power ARM. The DS412+ is rocking a bigger mouse - the Intel Atom D2700, and it has a bit more RAM.&lt;/p&gt;
&lt;p&gt;The processor is not a big deal to me. Some folk host a lot of apps - media servers etc on their NAS. I&amp;rsquo;m not planning to do that. As long as it can run Tailscale in a container (which the &amp;lsquo;j&amp;rsquo; models can) we&amp;rsquo;re good to go.&lt;/p&gt;
&lt;p&gt;My theory with drive quality is that the lower the quality of the drive, the higher level RAID I need. So If I scope this unit out with the $100 used, non-NAS drives, I can install four disks as RAID6, have two fail on the same day, and still be operational.&lt;/p&gt;
&lt;p&gt;This second-hand (about ten years old) unit was on &amp;ldquo;buy now&amp;rdquo; for $416, the four year old drives adds $440 making it $856 unit - around $71/TB. The quoted power draw is 44W which works out at 3.7W/TB - the highest of everything I considered.&lt;/p&gt;
&lt;h3 id="ds420j"&gt;DS420j&lt;/h3&gt;
&lt;p&gt;Thinking about likely points of failure with the eleven year old NAS (if I used the DS412j) made me wonder if a unit failure might be higher on the probability list than a second-hand drive failure. The answer is who knows? - probably both events are quite unlikely. However I have some redundancy built in to the drives, but a single point of failure in the NAS unit. It made me wonder what a new 4 bay Synology costs, and the answer is not much more.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/27459.jpg" width="229" alt=""&gt;
&lt;p&gt;This DS420j is again 4 bays, and like the older 4 nay unit it&amp;rsquo;s hot-swapable. This means that in the event of a drive failure, you can leave the NAS running, remove the faulty drive and insert a new one. The unit will then (slowly) rebuild the RAID array, but while you are removing a drive and rebuilding the RAID the system is still fully operational.&lt;/p&gt;
&lt;p&gt;Listed at $439, that works out to $880 total if I used the same second-hand 6TB drives as in the calculation above. So with my RAID 6 (two drive failures can be tolerated without losing data) the cost per TB is $73 - only a couple more than in the first example.&lt;/p&gt;
&lt;p&gt;Apart from the peace of mind of running a newer unit, there&amp;rsquo;s a big difference in power consumption. The DS420j uses 44W, this one is 22W with all the drives spinning, or if you allow them to hibernate as low as 8W. So the max power burn is half at 1.8W per TB&lt;/p&gt;
&lt;h3 id="trading-raid"&gt;Trading RAID&lt;/h3&gt;
&lt;p&gt;RAID 6 - where I&amp;rsquo;m installing 4 x 6TB drives, and only ending up with 12TB of usable space is very conservative - and I was doing that because I was using the second hand drives. What if I bought cheap, but still brand name HDDs, but only three of them and configured as RAID 5 so I&amp;rsquo;d still get 12TB of usable disk, and a single drive can fail without affecting my data?&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-03-10-at-5.40.03-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-10-at-5.40.03-pm.png" width="239" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There are no three bay Synologys, so I&amp;rsquo;d use the same NAS as above - the DS420j. I can buy three Seagate Skyhawk 6TB disks for $660, so the total comes to $1100 or $92/TB - quite a bit more than four of the older drives in RAID6. With less drives spinning, we can probably assume a total power consumption of around 15W - 1.25W/TB&lt;/p&gt;
&lt;h3 id="even-less-disks"&gt;Even less disks&lt;/h3&gt;
&lt;p&gt;What if we reduce the number of disks even further? If we double the drive capacity to 12TB we could run 2 x 12TB drives as RAID1, have a smaller NAS, save some power, and still have a single drive fail with no data loss. We might want to go to an even higher quality of drive, perhaps one of the ones rated for NAS use - The cheapest new brand name 12TB NAS drive on eBay was a Seagate IronWolf. Two of those costs $756. The two bay DS220j NAS only adds $241 for a total of $997 - $83/TB. Power looks great at 1.1W/TB.&lt;/p&gt;
&lt;p&gt;These smaller NAS&amp;rsquo;s are not hot-swappable. You have to power down the NAS to replace a drive. This is not as cool as just clicking a button and sliding a drive out while all your services are still up, but it&amp;rsquo;s not really a significant factor in my decision making.&lt;/p&gt;
&lt;h3 id="disk-singular"&gt;Disk singular&lt;/h3&gt;
&lt;p&gt;I wouldn&amp;rsquo;t do this for my production storage, but it&amp;rsquo;s worth mentioning the single disk NAS. You&amp;rsquo;d want the best quality of drive possible, and probably schedule to swap it out after three years or so. A Synology single drive NAS with a NAS rated drive is a big step up in quality and convenience from an external USB drive. With that same IronWolf 12TB drive and a DS120j you&amp;rsquo;d be out of pocket $578 or $48/TB, and power is down to 0.83W/TB.&lt;/p&gt;
&lt;h3 id="ye-olde-usb-drive"&gt;Ye Olde USB Drive&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve not had a USB drive failure, but mine generally live a happy life powered down in a cool dry drawer until they are fished out for a backup session. Just by way of a comparison to the options above, a WD &amp;ldquo;Elements&amp;rdquo; USB drive costs the same as the single disk NAS at $580 ($48/TB) but the power is down at 0.67W/TB. The cheaper Seagate &amp;ldquo;One Touch Desktop Hub&amp;rdquo; drive works out at $35/TB and 0.83W/TB&lt;/p&gt;
&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="has-text-align-center" data-align="center"&gt;&lt;strong&gt;NAS&lt;/strong&gt;&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;&lt;strong&gt;Disks&lt;/strong&gt;&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;&lt;strong&gt;$/TB&lt;/strong&gt;&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;&lt;strong&gt;W/TB&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="has-text-align-center" data-align="center"&gt;DS412+&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;4 x 6TB used&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;$856&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;71&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;3.7&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="has-text-align-center" data-align="center"&gt;DS420j&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;4 x 6TB used&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;$880&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;73&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;1.8&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="has-text-align-center" data-align="center"&gt;DS420j&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;3 x 6TB new&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;$1,100&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;92&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;1.25&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="has-text-align-center" data-align="center"&gt;DS220j&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;2 x 12TB NAS&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;$997&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;83&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;1.1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="has-text-align-center" data-align="center"&gt;DS120j&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;1 x 12TB NAS&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;$578&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;48&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;0.83&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="has-text-align-center" data-align="center"&gt;WD Elements&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;1 x 12TB USB&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;$580&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;48&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;0.67&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="has-text-align-center" data-align="center"&gt;Seagate&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;1 x 12TB USB&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;$423&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;35&lt;/td&gt;&lt;td class="has-text-align-center" data-align="center"&gt;0.83&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Clearly out of the first two options, you&amp;rsquo;d choose the second. $2 extra per TB of storage is easily worth it to start with a new NAS compared with an eleven year old one. After that the price per TB doesn&amp;rsquo;t go down till you hit the single drive devices.&lt;/p&gt;
&lt;p&gt;Someone else may well start with different assumptions that I have made here, especially in the way I&amp;rsquo;ve decided to increase the drive quality as I&amp;rsquo;ve reduced the redundancy. For instance, you may be happy with a couple of second hand 12TB drives in a DS220j at $54/TB instead of rooting for new NAS drives. This would be along the lines of my original purchase of a DS216j and two 8TB second hand drives for $58/TB.&lt;/p&gt;
&lt;h2 id="the-plan"&gt;The Plan&lt;/h2&gt;
&lt;p&gt;Based on all this, I went with option two - the new DS420j and four old drives in a RAID6. It turned out a bit cheaper since there was a discount code for the NAS, and the price per drive was a touch less when buying four.&lt;/p&gt;
&lt;p&gt;For a local backup, I&amp;rsquo;ll use a single second hand 12TB drive in a DS120j, and for the remote, mainly because I want to share some storage with the home owner, and I feel that has to be on RAID, I&amp;rsquo;ll buy a pair of 14TB second hand drives to put in the DS216j for the remote, so I can open up a 2TB pool for them to use as a local backup for laptops or what have you.&lt;/p&gt;</description></item><item><title>Recursive list of files in Linux</title><link>https://blog.iankulin.com/recursive-list-of-files-in-linux/</link><pubDate>Wed, 08 Mar 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/recursive-list-of-files-in-linux/</guid><description>&lt;p&gt;I&amp;rsquo;ve spent a few hours over the weekend migrating a media library from an external USB drive to the NAS, and in the process reorganised it, and in many cases bulk changed file names. I&amp;rsquo;ve also added a heap of metadata.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d like to check that I haven&amp;rsquo;t missed any files, but a side by side listing of each data source won&amp;rsquo;t do the trick, so I&amp;rsquo;ll probably end up pulling the data into a spreadsheet, but I&amp;rsquo;d like to get as close as possible with Linux-fu first.&lt;/p&gt;
&lt;p&gt;Before I go over my trial and error, and eventual solution, here&amp;rsquo;s how I&amp;rsquo;ve set up my test data for the examples. I thought I&amp;rsquo;d better start with something simple and small for testing commands.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-03-06-at-5.02.17-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-06-at-5.02.17-pm.png" width="495" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is actually the output of the &lt;code&gt;tree&lt;/code&gt; command on a &lt;code&gt;test&lt;/code&gt; directory I&amp;rsquo;ve created in my home directory. (I had to install it - &lt;code&gt;sudo apt install tree&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;What I need to end up with is something that recursively lists all the files, with one file per line, and it needs to include the directory tree to reach it. I should be able to pipe it through something to ignore lines that are just directories (and any other fluff).&lt;/p&gt;
&lt;h3 id="ls"&gt;ls&lt;/h3&gt;
&lt;p&gt;My go to for listing files is &lt;code&gt;ls -all&lt;/code&gt;, perhaps than can help us? It lists one line per file (along with permissions etc), so if we add &lt;code&gt;-R&lt;/code&gt; for recursive, that could be it. Here&amp;rsquo;s the output for &lt;code&gt;ls -all -R test&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test:
total 16
drwxr-xr-x 4 ian ian 4096 Mar 6 16:36 .
drwxr-xr-x 4 ian ian 4096 Mar 6 16:36 ..
drwxr-xr-x 2 ian ian 4096 Mar 6 17:01 dir1
drwxr-xr-x 2 ian ian 4096 Mar 6 17:01 dir2

test/dir1:
total 8
drwxr-xr-x 2 ian ian 4096 Mar 6 17:01 .
drwxr-xr-x 4 ian ian 4096 Mar 6 16:36 ..
-rw-r--r-- 1 ian ian 0 Mar 6 17:01 ignore.me
-rw-r--r-- 1 ian ian 0 Mar 6 17:00 media1.ex1
-rw-r--r-- 1 ian ian 0 Mar 6 17:00 media1.ex2
-rw-r--r-- 1 ian ian 0 Mar 6 17:01 media3.ex1
-rw-r--r-- 1 ian ian 0 Mar 6 16:36 somefile
-rw-r--r-- 1 ian ian 0 Mar 6 16:36 somefile2

test/dir2:
total 8
drwxr-xr-x 2 ian ian 4096 Mar 6 17:01 .
drwxr-xr-x 4 ian ian 4096 Mar 6 16:36 ..
-rw-r--r-- 1 ian ian 0 Mar 6 17:01 ignore.me
-rw-r--r-- 1 ian ian 0 Mar 6 17:01 media4.ex1
-rw-r--r-- 1 ian ian 0 Mar 6 17:01 media5.ex1
-rw-r--r-- 1 ian ian 0 Mar 6 17:01 media6.ex2
-rw-r--r-- 1 ian ian 0 Mar 6 16:37 somefile
-rw-r--r-- 1 ian ian 0 Mar 6 16:37 somefile3
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So we get one line per file, but the directory is on it&amp;rsquo;s own at the beginning of each directory listing.&lt;/p&gt;
&lt;h3 id="find"&gt;find&lt;/h3&gt;
&lt;p&gt;Based on &lt;a href="https://www.cyberciti.biz/faq/how-to-show-recursive-directory-listing-on-linux-or-unix/"&gt;this post&lt;/a&gt;, there is a command, &lt;code&gt;find&lt;/code&gt;, that might do what we want. The simple version would be &lt;code&gt;find test&lt;/code&gt; (remember test is the directory name).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ian@vm102-jellyfin:~$ find test
test
test/dir2
test/dir2/ignore.me
test/dir2/media4.ex1
test/dir2/somefile
test/dir2/media5.ex1
test/dir2/somefile3
test/dir2/media6.ex2
test/dir1
test/dir1/media3.ex1
test/dir1/ignore.me
test/dir1/somefile
test/dir1/media1.ex1
test/dir1/media1.ex2
test/dir1/somefile2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Well that is real close, but there&amp;rsquo;s no way to discern between a file and directory. In that same post, it;s suggested to use the &lt;code&gt;-ls&lt;/code&gt; option to see some more detail. Let&amp;rsquo;s try find &lt;code&gt;test -ls&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-06-at-5.20.59-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;This looks pretty close. If there was someway of using that &amp;rsquo;d&amp;rsquo; in the first position of the permissions output to eliminate those lines, we&amp;rsquo;d be well on our way. I have a feeling this is a &lt;code&gt;grep&lt;/code&gt; question. I have some basic grep, so for example I know I could pull all of those directories with &lt;code&gt;find test -ls | grep ' d'&lt;/code&gt;, or even invert it with the &lt;code&gt;-v&lt;/code&gt; flag to get just the files (which is out eventual goal).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-06-at-5.36.37-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;However, this is pretty hacky. A space followed by a lowercase could easily occur in a filename. What I really need to do is look at just that column which I think is character number 18. Off to &lt;a href="https://unix.stackexchange.com/questions/32170/find-all-lines-in-a-file-with-a-certain-character-at-a-certain-position"&gt;Stack Exchange&lt;/a&gt; I guess&amp;hellip;&lt;/p&gt;
&lt;h3 id="grep-with-regex"&gt;grep with regex&lt;/h3&gt;
&lt;p&gt;Okay, it turns out we can use regex with grep. I&amp;rsquo;m no expert in that either, but in regex the caret ^ represents the start of the line, a fullstop represents any character, and we can repeat that however many times we want by following it with a number in (escaped) curly braces. Something like &lt;code&gt;'^.{17}d'&lt;/code&gt; should do it.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-06-at-5.44.46-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Okay! We&amp;rsquo;re getting close. I also want to ignore all the metadata and just see the media files. This can be determined by the extensions - probably .avi .mp4 .mkv .mv4. With this test data, we&amp;rsquo;ll pretend it&amp;rsquo;s .ex1 and .ex2&lt;/p&gt;
&lt;h3 id="combining-grep-tests-with-logical-or"&gt;Combining grep tests with logical or&lt;/h3&gt;
&lt;p&gt;I guess I could build some sort of super regex combined with the first one, but I&amp;rsquo;m only dealing with thousands of files, not millions so the extra overhead of piping through another grep is not going to be a drama, and I can simplify my work. In the same way that the caret ^ marks the start of a line, the dollar $ marks the end of it. So to just get the .ex1 files something like &lt;code&gt;'\.ex1$'&lt;/code&gt; should do it. The backslash at the start is to escape the period, because here we want that to mean a literal full stop, and not a wildcard for any character.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-06-at-5.55.25-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Nice, but remember I&amp;rsquo;ve got a big list of extensions, so I need to logical or a few together. This is done by putting the expressions with a pipe between them. I had a couple of goes at this with no luck and that familiar feeling of being out of my depth with regex. However, there&amp;rsquo;s a grep way out of this, because the grep flag -e allows us to OR matching expressions.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-06-at-6.06.52-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re definitely getting somewhere. You might think at this point that the chance of a directory name ending in .mp4 or one of the other media extensions is no low we could ignore it, and you&amp;rsquo;d probably be right. But as a matter of programmer pride, I never like to leave a future problem, so I&amp;rsquo;ll be keeping the directory rejecting grep. So now my command looks like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;find test -ls | grep -v &amp;#39;^.{17}d&amp;#39; | grep -e &amp;#39;\.ex1$&amp;#39; -e &amp;#39;\.ex2$&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Any experienced regex people would be pointing out the match for .ex1 .ex2 can easily be merged into a simple expression, but remember when I do this for real I&amp;rsquo;ve got a list of more complex extensions to test for.&lt;/p&gt;
&lt;h3 id="cut"&gt;cut&lt;/h3&gt;
&lt;p&gt;All that text at the beginning of these lines is not needed. Surely I can trim that off somehow? Yep - there&amp;rsquo;s a command &lt;code&gt;cut&lt;/code&gt; that does exactly that. The -b flag specifies which byte to extract, and this can also be a range. putting a dash after the position number says to output all of the bytes after that position. So if we applied &lt;code&gt;cut -b 5-&lt;/code&gt; to the string &lt;code&gt;123456789&lt;/code&gt;, the output would be &lt;code&gt;56789&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-06-at-6.26.18-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Bingo. Just one more problem. In my data, I have a heap of files with a valid extension but I want to exclude them based on their file name. Every directory with a movie has a trailer named &lt;code&gt;trailer.mp4&lt;/code&gt;, so I need to eliminate them. To simulate this, lets add in another extension with our test data.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-06-at-6.29.48-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;So I want to take out the lines that include &amp;lsquo;/ignore.me&amp;rsquo;. I should be able to do this with another &lt;code&gt;grep -v&lt;/code&gt; regex on a line end. Something like &lt;code&gt;grep -v 'ignore.me$'&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-03-06-at-6.33.22-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;And we&amp;rsquo;re done! I&amp;rsquo;ll just direct this into a file, run it on both disks and pull them into Excel to separate the file names and directories, and sort them to compare.&lt;/p&gt;</description></item><item><title>Sudoers' file not working</title><link>https://blog.iankulin.com/sudoers-file-not-working/</link><pubDate>Mon, 27 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/sudoers-file-not-working/</guid><description>&lt;p&gt;A couple of weeks ago, I posted &lt;a href="https://blog.iankulin.com/sudo-incident-reports-where-do-they-go/"&gt;about the sudoers&amp;rsquo; file&lt;/a&gt;, and how there was a special tool for editing it since breaking it is a bad idea, and that in fact I needn&amp;rsquo;t bother, since I can just add my user to the sudoers&amp;rsquo; group with:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;usermod -a -G sudo ian
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That worked (on Unbuntu) since &lt;code&gt;/etc/sudoers&lt;/code&gt; contained a line saying:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Allow members of group sudo to execute any command
%sudo	ALL=(ALL:ALL) ALL
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I tried the same trick on a fresh Debian install today, and no dice:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-26-at-3.32.49-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I assumed this might mean that the &lt;code&gt;sudoers&lt;/code&gt; file is different on Debian than Ubuntu, but no, that same line granting permission to the &lt;code&gt;sudo&lt;/code&gt; group is there. My next guess is that I hadn&amp;rsquo;t correctly added ian to that group. But no, that looks okay.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-26-at-3.48.51-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/offon.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Yup. Log out, log in&amp;hellip;&lt;/p&gt;</description></item><item><title>Folder ownership problems with Jellyfin</title><link>https://blog.iankulin.com/folder-ownership-problems-with-jellyfin/</link><pubDate>Wed, 22 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/folder-ownership-problems-with-jellyfin/</guid><description>&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-02-18-at-5.32.36-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-18-at-5.32.36-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After being so blase about the file permissions when mounting the share to the Linux file system, and testing that root could read and write to the share, I ran into problems immediately when trying to add the media folder as a library in Jellyfin - getting the error &amp;ldquo;The path could not be found. Please ensure the path is valid and try again.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I definitely had the path correct - I could copy it from the dialog and cd to it at the CLI. So I suspected it was a permissions thing. The app might not have read permissions for the directory.&lt;/p&gt;
&lt;p&gt;If, as root, I ls -l (-l for long) any of the directories in this path, they look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@jellyfin:/mnt/media/video# ls -l
total 0
drwxrwx--- 2 1000 1000 0 Feb 18 09:13 Movies
drwxrwx--- 2 1000 1000 0 Feb 18 04:30 Shows
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Those letters at the start of each listing &lt;a href="https://detailed.wordpress.com/2017/10/28/understanding-ls-command-output/"&gt;have a meaning&lt;/a&gt;. The first &lt;code&gt;d&lt;/code&gt; just means it&amp;rsquo;s a directory. Then there&amp;rsquo;s three groups of three letters:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;d rwx rwx --- (I&amp;#39;ve just added those spaces to make things clear)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These three lots of letters are the &lt;em&gt;permissions&lt;/em&gt; in the order of &lt;em&gt;owner&lt;/em&gt;, &lt;em&gt;group&lt;/em&gt; &amp;amp; &lt;em&gt;everybody&lt;/em&gt;. So for these directories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The owner can read, write &amp;amp; execute (&lt;code&gt;rwx&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Members of this group can read, write &amp;amp; execute (&lt;code&gt;rwx&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Everybody else can&amp;rsquo;t read, can&amp;rsquo;t write, and can&amp;rsquo;t execute (&lt;code&gt;---&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This raises the question, who is the owner of this directory, and what is the group we are talking about? The answer to those questions are the next items in the listing.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://detailed.wordpress.com/2017/10/28/understanding-ls-command-output/"&gt;&lt;img src="https://blog.iankulin.com/images/ls-command3.jpg" width="686" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In our case the owner is &lt;code&gt;1000&lt;/code&gt; and the group is &lt;code&gt;1000&lt;/code&gt;. Where did these come from? Well, they were in the mount command I used in &lt;code&gt;etc/fstab&lt;/code&gt; yesterday:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;//192.168.100.25/media /mnt/media cifs username=jelly,password=jellypass,uid=1000,gid=1000,file_mode=0660,dir_mode=07
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So most likely that&amp;rsquo;s the source of my troubles. As I mentioned, I tested this from the command line logged in as root, and it worked fine. And I was imagining since I&amp;rsquo;d installed Jellyfin as root that Jellyfin would have all those rights, but perhaps (as would be wise) Jellyfin is running as a different user, and I need to add that user to the 1000 group in order to make this work.&lt;/p&gt;
&lt;p&gt;How to I find out what user Jellyfin is running as? A good place to start is to look at the running processes with the &lt;code&gt;ps&lt;/code&gt; command:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-02-18-at-6.02.04-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-18-at-6.02.04-pm.png" width="998" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Well, lookee here. User &lt;code&gt;jellyfin&lt;/code&gt; is running this process. We can see what groups she&amp;rsquo;s a member of by running the &lt;code&gt;groups&lt;/code&gt; command.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@jellyfin:/# groups jellyfin
jellyfin : jellyfin
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So, not a member of the &lt;code&gt;1000&lt;/code&gt; group then. We can use the &lt;code&gt;getent&lt;/code&gt; command to see the group numbers for users:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@jellyfin:/# getent group jellyfin 
jellyfin:x:115:
root@jellyfin:/# getent group root 
root:x:0:
root@jellyfin:/# 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Okay, so that&amp;rsquo;s likely out problem. To confirm it, we could change the group for the directory tree and files, or add &lt;code&gt;jellyfin&lt;/code&gt; to the &lt;code&gt;1000&lt;/code&gt; group. Since I now know that &lt;code&gt;jellyfin&lt;/code&gt; is a member of the &lt;code&gt;115&lt;/code&gt; group, and that I just plucked &lt;code&gt;1000&lt;/code&gt; out of the air, I&amp;rsquo;m inclined to remount the share with a &lt;code&gt;gid=115&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-02-18-at-6.24.11-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-18-at-6.24.11-pm.png" width="757" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And&amp;hellip;. it works.&lt;/p&gt;</description></item><item><title>Accessing a Synology NAS from Linux</title><link>https://blog.iankulin.com/accessing-a-synology-nas-from-linux/</link><pubDate>Mon, 20 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/accessing-a-synology-nas-from-linux/</guid><description>&lt;img src="https://blog.iankulin.com/images/img_4154x.jpg" width="1000" alt=""&gt;
&lt;p&gt;I picked up a Synology DS216j NAS from eBay to use for storage for the rapidly growing home lab. The eventual plan is that as well as my VM backups, it will host the media library, and eventually (when this has all proved itself reasonably bullet-proof) my current DropBox contents. That won&amp;rsquo;t all fit on the 2x2TB drives that the DS216j came with, and I have a pair of 8TBs on hand, but I wanted to set it up and checked it all worked.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-18-at-3.15.25-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Configuration of the NAS was a &amp;lsquo;follow the prompts&amp;rsquo; exercise for the most part. The Synology OS is a Linux port called DSM, but it&amp;rsquo;s intended to be an appliance so all the interactions are through the web client. I&amp;rsquo;m using RAID 1 since the plan is that the production segment of the homelab will all be high-ish available. There&amp;rsquo;s a few options to install extras (such as Tailscale), but these little &amp;lsquo;j&amp;rsquo; models don&amp;rsquo;t run an x86 processor, so no docker etc.&lt;/p&gt;
&lt;p&gt;Once I&amp;rsquo;d got through all of that, I created a share in &amp;lsquo;File Station&amp;rsquo; and copied a couple of files in. By default, Samba shares are on (with the name WORKGROUP - so I guess this is aimed at making it simple for Windows users) but NFS are not. I know nothing about NFS, so this suits me for the moment. Additionally, my &lt;a href="https://en.wikipedia.org/wiki/WD_TV"&gt;WD-TV&lt;/a&gt; shares it&amp;rsquo;s attached USB drive using Samba, so I&amp;rsquo;m used to accessing it from the MacBook. Let&amp;rsquo;s try the NAS from the MacBook:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-02-18-at-3.23.38-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-18-at-3.23.38-pm.png" width="512" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It asked for the login details, then I was in. Could not have been much easier.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-02-18-at-3.28.55-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-18-at-3.28.55-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="accessing-from-linux"&gt;Accessing from Linux&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m planning on running Jellyfin in an LCX container. So I&amp;rsquo;ll set that up for this test too. I stood it up with the Debian server .iso in Proxmox and specified it should be a &amp;lsquo;privileged&amp;rsquo; container, and in the Proxmox options for the LXC ticked &amp;lsquo;SMB/CIFS&amp;rsquo;. This process is not just for Synology - it will work to mount any samba share on a network to your Linux machine.&lt;/p&gt;
&lt;p&gt;We need to make an empty directory to mount to:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mkdir /mnt/media
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then edit (I use nano) the file &lt;code&gt;/etc/fstab&lt;/code&gt; to include:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;//192.168.100.25/media /mnt/media cifs username=jelly,password=jellypass,uid=1000,gid=1000,file_mode=0660,dir_mode=07
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;etc/fstab&lt;/code&gt; runs at startup, and if the shares are available (cautionary note about booting up your lab after a power outage) it will set them up. There&amp;rsquo;s a fair bit going on in the command, perhaps we should pull it apart:&lt;/p&gt;
&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;//192.168.100.25/media /mnt/media&lt;/code&gt;&lt;/td&gt;&lt;td&gt;The first directory is the share, the second is the empty directory on this machine we are mounting the share to.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;username=jelly, password=jellypass&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Our credentials needed to log into the share. Actually I used my root credentials, but obviously a good idea would be to make a user on the NAT for this specific purpose with only access to the share they need to operate.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;uid=1000, gid=1000&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Okay, we're about to enter the Linux zone... These numbers are a &lt;a href="https://medium.com/@gggauravgandhi/uid-user-identifier-and-gid-group-identifier-in-linux-121ea68bf510"&gt;Linux user id and a group ownership id&lt;/a&gt; that Linux assigns to resources - you know, for &lt;code&gt;chown&lt;/code&gt; and stuff like that. If you type in &lt;code&gt;id&lt;/code&gt; at the CLI you can see your numbers. For some reason code examples often use 1000 for both, and things seem to work so I don't worry about it.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;file_mode=0660, dir_mode=07&lt;/code&gt;&lt;/td&gt;&lt;td&gt;More Linux permission stuff. Used in combination with the previous two parameters, and &lt;a href="http://file_mode=0660,dir_mode=07"&gt;will probably cause me problems later&lt;/a&gt;.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Note that a couple of other posts on the internet about mounting samba shares thought I&amp;rsquo;d have to do one or both of these commands to install extra samba goodness:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apt install smbclient
apt install cifs-utils
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But it turns out I didn&amp;rsquo;t. I suspect that was something to do with ticking the box for SMB/CIFS when I was creating the LXC container in Proxmox.&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve saved that command in &lt;code&gt;/etc/fstab&lt;/code&gt;, reload the mounts with:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mount -a
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If there&amp;rsquo;s no errors, you are probably right to go. Have a look at your mount point to see your shared files.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-18-at-4.59.09-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Since the mount command is in the /etc/fstab file, this mount will be durable - as long as the share is available, it will be mounted every time this machine starts.&lt;/p&gt;</description></item><item><title>Configuring Proxmox for Free Use</title><link>https://blog.iankulin.com/configuring-proxmox-for-free-use/</link><pubDate>Thu, 16 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/configuring-proxmox-for-free-use/</guid><description>&lt;p&gt;I installed Proxmox on my second server last night, and tonight when I ran &lt;code&gt;apt update&lt;/code&gt; I ran into the error you get when you haven&amp;rsquo;t bought a license.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Err:5 https://enterprise.proxmox.com/debian/pve bullseye InRelease 
 401 Unauthorized [IP: 103.67.14.50 443]
Reading package lists... Done 
E: Failed to fetch https://enterprise.proxmox.com/debian/pve/dists/bullseye/InRelease 401 Unauthorized [IP: 103.67.14.50 443]
E: The repository &amp;#39;https://enterprise.proxmox.com/debian/pve bullseye InRelease&amp;#39; is not signed.
N: Updating from such a repository can&amp;#39;t be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Even though I guess it was only a month ago (let that sink in people who think the raspberry Pi they just bought is going to be the last homelab hardware they buy 😊) since I set up my first Proxmox server, I&amp;rsquo;d already forgotten there&amp;rsquo;s a step to enable it to get updates without a subscription.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a couple of little steps for this. They are both &lt;a href="https://pve.proxmox.com/wiki/Package_Repositories#sysadmin_enterprise_repo"&gt;here on the Proxmox wiki&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;edit &lt;code&gt;/etc/apt/sources.list.d/pve-enterprise.list&lt;/code&gt; to comment out the single repository listed in there.&lt;/li&gt;
&lt;li&gt;edit &lt;code&gt;/etc/apt/sources.list&lt;/code&gt; to look like this:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;deb http://ftp.debian.org/debian bullseye main contrib
deb http://ftp.debian.org/debian bullseye-updates main contrib

# PVE pve-no-subscription repository provided by proxmox.com,
# NOT recommended for production use
deb http://download.proxmox.com/debian/pve bullseye pve-no-subscription

# security updates
deb http://security.debian.org/debian-security bullseye-security main contrib
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then you&amp;rsquo;ll be good to go.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-07-at-8.41.15-pm.jpg" alt=""&gt;&lt;/p&gt;</description></item><item><title>Moving a VM between two Proxmox hosts</title><link>https://blog.iankulin.com/moving-a-vm-between-two-proxmox-hosts/</link><pubDate>Thu, 16 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/moving-a-vm-between-two-proxmox-hosts/</guid><description>&lt;img src="https://blog.iankulin.com/images/s-l640.jpg" width="264" alt=""&gt;
&lt;p&gt;So, the very small datacentre has undergone a major hardware upgrade today. The HP 800 G1 is joined by an HP 800 G2. Four core i7 vs the old two core i5. Double the RAM to 16GB, four times the disk. The old machine will become a dev/play machine - still virtualised, and the new machine will run the production apps, mostly in Docker containers.&lt;/p&gt;
&lt;p&gt;Since everything is containerised, I did consider running Unbuntu Server on the bare metal of the new machine, but running it on Proxmox will give me some flexibility, and since we&amp;rsquo;ve stepped up the underlying hardware resource so substantially, performance will be well in front anyway. Plus it will give me some flexibility if needed in the future.&lt;/p&gt;
&lt;p&gt;Another massive benefit of virtualisation is the ability to backup a VM to a single file.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve invested several hours in the old server - downloading ISOs, updating everything, installing Docker, adding my containers, reserving the IP addresses in DNS and so on. Wouldn&amp;rsquo;t it be amazing if I could stop my main VM, back it up, copy the backup to the new server, then boot it there and have every thing just work.&lt;/p&gt;
&lt;p&gt;In theory this should be entirely possible. So let&amp;rsquo;s give it a go.&lt;/p&gt;
&lt;p&gt;In the Proxmox web interface, you can execute a backup on a VM. There&amp;rsquo;s three flavours with &lt;code&gt;STOP&lt;/code&gt; being the most reliable as it actually stops the VM to grab it&amp;rsquo;s copy. On this system I can easily afford to stop everything for ten minutes so I&amp;rsquo;ll actually be shutting down my VM and doing this sort of back up. We do this by clicking on the VM, then selecting backup. At the top is a backup button.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-02-06-at-8.35.38-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-06-at-8.35.38-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve done your backup it appears in a couple of places in the web interface - in this backup screen associated with the VM, but also if you select the &lt;code&gt;local&lt;/code&gt; disk then backup.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-06-at-8.41.43-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;So that&amp;rsquo;s my VM nicely backed up into a single tarball, now I want to download it. I really feel the Proxmox interface should have buttons for Download and Upload on this screen - that would make this operation even easier. But it does not.&lt;/p&gt;
&lt;p&gt;The first problem is to find where these files are stored. Thanks to u/walalauw&amp;rsquo;s answer in &lt;a href="https://old.reddit.com/r/Proxmox/comments/jj6eqz/downloading_backups/"&gt;this reddit thread&lt;/a&gt;, it sounds like they are at &lt;code&gt;/var/lib/vz/dump&lt;/code&gt; I head there in FileZilla, and find:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-02-06-at-7.54.09-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-06-at-7.54.09-pm.png" width="826" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You only need the &lt;code&gt;.zst&lt;/code&gt; file, but neat freaks can grab the the &lt;code&gt;.notes&lt;/code&gt; as well. It contains the text you wrote for the backup - in the previous screenshot you can see I&amp;rsquo;d written &amp;ldquo;Ready to move&amp;rdquo; for this one.&lt;/p&gt;
&lt;p&gt;Copy this file somewhere - I copied it one to my local machine, then from there to the new Proxmox (same &lt;code&gt;/var/lib/vz/dump&lt;/code&gt; directory) since I was using FileZilla, but a hardcore scp user would have gone direct between the two servers and saved a bit of time.&lt;/p&gt;
&lt;p&gt;Now on the new server, I can see my backup! All you do then is select it and hit the &lt;code&gt;Restore&lt;/code&gt; button.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-06-at-7.58.49-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;A minute or two later, the VM &amp;ldquo;dockhost&amp;rdquo; is in the list. I press &lt;code&gt;Start&lt;/code&gt;, and it boots, my containers all start. And magically, amazingly it all works perfectly.&lt;/p&gt;
&lt;p&gt;If I wasn&amp;rsquo;t already sold on virtualization, this would definitely sell me on it. I understand there are other ways of moving VM&amp;rsquo;s between hosts, but this is hard to beat for simplicity if you can afford the downtime. This was the first time I&amp;rsquo;d ever done this, and I was stopping to screenshot things along the way. From the time I stopped the VM, to the time my last container went green was only nine minutes.&lt;/p&gt;</description></item><item><title>Uptime Kuma &amp; NTFY</title><link>https://blog.iankulin.com/uptime-kuma-ntfy/</link><pubDate>Wed, 15 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/uptime-kuma-ntfy/</guid><description>&lt;p&gt;&lt;a href="https://github.com/louislam/uptime-kuma"&gt;Uptime Kuma&lt;/a&gt; is a monitoring tool suitable for self-hosting, and as well as being a good tool for monitoring the status of your network and applications, it&amp;rsquo;s a nice smallish app to get started on Docker containers.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-02-05-at-6.41.24-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-05-at-6.41.24-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Since it&amp;rsquo;s in a container, you need to create a volume for it and pass it in to persist your settings. Then it&amp;rsquo;s just a matter of adding each item you want to monitor. There&amp;rsquo;s a heap of fancy options for this, the only three I&amp;rsquo;ve used are ping - just pings an address, http(s) - requests a page and checks the header for a 200, and http(s) keyword - looks at the returned page for a keyword in the html.&lt;/p&gt;
&lt;p&gt;You choose the time intervals for all these. Additionally you can set up a notification for each. This is a great idea - I&amp;rsquo;m not sitting in my datacentre command room watching Uptime Kuma all day, I need to know on my phone if a CAT5 cable&amp;rsquo;s been pulled out inadvertently while I was vacuuming.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s lots of options for how to do this, including messaging platforms such as Telegram and Discord. I had a look on &lt;a href="https://www.reddit.com/r/selfhosted/comments/z0gpr2/free_push_service_for_uptime_kuma/"&gt;r/selfhosted&lt;/a&gt; to see what was recommended, and discovered &lt;a href="https://ntfy.sh/"&gt;NTFY&lt;/a&gt; which is an amazing little service. It has Android and iOS apps, in the app you subscribe to an endpoint, then a notification can be sent to your phone with a simple http get, for example:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;curl -d &amp;#34;This message will pop up on phone&amp;#34; ntfy.sh/ian_test
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-05-at-12.39.22-pm.png" alt=""&gt;&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/img_4055.jpg" width="192" alt=""&gt;
&lt;p&gt;The &lt;code&gt;ian_test&lt;/code&gt; part of the url is called the &lt;em&gt;topic&lt;/em&gt;, and in the app you can subscribe to several topics. It&amp;rsquo;s worth noting this is all completely open. Anyone can send messages to the ian_test topic, and anyone can receive them. You should choose a topic name that&amp;rsquo;s likely to be unique, and be mindful that you&amp;rsquo;re leaking intelligence. For example:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;curl -d &amp;#34;CCTV offline - 12 George St&amp;#34; ntfy.sh/maquarie_bank
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;would not a be a good use case. Get something more secure for that application. It&amp;rsquo;s probably not going to be free.&lt;/p&gt;
&lt;p&gt;Speaking of which, NTFY is free, including the server. It is possible (and probably a good idea since then you could add a little security) to self host it. It&amp;rsquo;s such a great little tool, and just so immediately and completely achieved what I wanted with zero drama and low effort, I hit the &lt;a href="https://github.com/sponsors/binwiederhier"&gt;github sponsor button for it&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Netgear GS108E switch problem</title><link>https://blog.iankulin.com/netgear-gs108e-switch-problem/</link><pubDate>Tue, 14 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/netgear-gs108e-switch-problem/</guid><description>&lt;p&gt;I had a weird issue today that I wouldn&amp;rsquo;t have known about if I didn&amp;rsquo;t have an over-engineered home network monitoring system.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve got a new &lt;a href="https://www.netgear.com/au/business/wired/switches/plus/gs108e/"&gt;GS108E managed switch&lt;/a&gt;, purchased in anticipation of connecting a NAS to the homelab - I want to have a solid 1Gb connection between the NAS and the servers, and also in anticipation of moving to VLANs before I start to expose self-hosted services to the internet.&lt;/p&gt;
&lt;p&gt;It sat plugged into the network for 24 hours with no load, then I moved the server over on to it. I hadn&amp;rsquo;t changed any configuration on it (it comes out of the box just configured as a switch). It&amp;rsquo;s IP address was set by DHCP, and I&amp;rsquo;d reserved that in my Netgear modem.&lt;/p&gt;
&lt;p&gt;It was all working perfectly, then a day later, it suddenly stopped responding to pings and the interface was unavailable. The weird bit was that the network was still working perfectly - I could ssh to the server through it, but it didn&amp;rsquo;t appear on the network. The leds on the connected ports where flashing away happily.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-02-05-at-7.18.51-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-05-at-7.18.51-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There are a couple of search results for this switch working, but the interface not reachable - but they all seem to involve complicated VLAN setups where people have locked themselves out rather than the default config suddenly having an issue.&lt;/p&gt;
&lt;p&gt;It will be disappointing if this is going to be a regular issue, but at least I&amp;rsquo;ll have good data to investigate it!&lt;/p&gt;</description></item><item><title>Local host names with Pi-hole</title><link>https://blog.iankulin.com/local-host-names-with-pi-hole/</link><pubDate>Mon, 13 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/local-host-names-with-pi-hole/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-5.46.22-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I run an instance of Pi-hole as a network-wide advert and surveillance blocker. It also has a setting to block individual domain which I use to force myself to really consider if 30 minutes of &lt;a href="https://old.reddit.com/r/homelab/"&gt;Reddit&lt;/a&gt; is a good idea when I should probably just be going to bed.&lt;/p&gt;
&lt;p&gt;As I&amp;rsquo;ve increased the number of real and virtual devices on my network, it&amp;rsquo;s getting to be a pain remembering all of their IP addresses. So I&amp;rsquo;d like to have DNS entries for them, for example I&amp;rsquo;d much rather:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ssh ian@vm100-dockhost
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;than&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ssh ian@192.168.100.29
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Luckily, since Pi-hole works by being a DNS server (who drops requests for domains it doesn&amp;rsquo;t like) it has the capability of handling these local names.&lt;/p&gt;
&lt;h4 id="how-to-set-up"&gt;How to set up&lt;/h4&gt;
&lt;p&gt;Down the left side of the Pi-hole interface, there&amp;rsquo;s a link for Local DNS | DNS Records. Click on that for this screen, then input the name you&amp;rsquo;d like to use and the IP address it should go to. I use the device host names so there&amp;rsquo;s less confusion, but there&amp;rsquo;s no rule for this - you can use whatever you like.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-6.05.02-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-6.05.02-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-6.08.26-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-6.08.26-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>ssh key login on VPS</title><link>https://blog.iankulin.com/ssh-key-login-on-vps/</link><pubDate>Sun, 12 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/ssh-key-login-on-vps/</guid><description>&lt;p&gt;Due to &lt;a href="https://blog.iankulin.com/chinese-hackers-want-to-steal-my-hello-world-container/"&gt;potential brute force attacks&lt;/a&gt;, it&amp;rsquo;s a good idea to turn off password access via shh and instead rely on ssh keys. In this post, I&amp;rsquo;ll run through that process.&lt;/p&gt;
&lt;h4 id="generating-your-key"&gt;Generating your key&lt;/h4&gt;
&lt;p&gt;On a mac (or actually most *ix systems), your ssh keys live in the &lt;code&gt;.ssh&lt;/code&gt; directory inside the users home directory. Since it starts with a period, it&amp;rsquo;s a &amp;lsquo;hidden&amp;rsquo; directory. To see it in Finder press&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Shift|Command|.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;(that command includes the full stop). Here&amp;rsquo;s mine:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-31-at-6.05.49-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The keys are in pairs, there are two pairs of keys above: id_ecdsa and id_rsa. Each pair of keys includes a public key and a private key. It&amp;rsquo;s the public key we want to put on the server. The private key is precious - it should not be anywhere that others can access it. The public key can safely be provided to a server that can use it to securely authenticate the holder of the private key by doing some complicated stuff.&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t already have a key pair, we need to create one. On Mac or Linux that is a &lt;a href="https://www.makeuseof.com/ssh-keygen-mac/"&gt;straightforward process in a terminal&lt;/a&gt;, on Windows I think the recommendation is usually to use &lt;a href="https://www.ssh.com/academy/ssh/putty/windows/puttygen"&gt;PuTTY&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="installing-your-key-on-the-target-system"&gt;Installing your key on the target system&lt;/h4&gt;
&lt;p&gt;It&amp;rsquo;s possible to create a file on the system we want to ssh onto and to paste the public key into in, but from a Mac or Linux machine, we can use the &lt;code&gt;ssh-copy-id&lt;/code&gt; command which will do all that for us in an error-free way. It has the format:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ssh-copy-id &amp;lt;username&amp;gt;@&amp;lt;host&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-1.51.49-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-1.51.49-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="turning-off-password-access"&gt;Turning off password access&lt;/h4&gt;
&lt;p&gt;Although it&amp;rsquo;s convenient to ssh in without a password (because we&amp;rsquo;re using keys), the main reason for doing this is to turn off passwords to make brute-force password attacks impotent. For that, we need to turn off passwords for &lt;code&gt;ssh&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Note that I&amp;rsquo;m running on Unbuntu server 22.x so your mileage may vary. Certainly when I was reading around about this I found many slightly different approaches, but this is what I&amp;rsquo;ve done and tested so that ssh refuses to accept passwords.&lt;/p&gt;
&lt;p&gt;The configuration files for ssh on Ubuntu are at /etc/ssh/&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-3.57.15-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-3.57.15-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s the &lt;code&gt;sshd_config&lt;/code&gt; we&amp;rsquo;re interested in (&lt;code&gt;ssh_config&lt;/code&gt; is for the client, we&amp;rsquo;re wanting to change the ssh server - daemon). You&amp;rsquo;ll also notice there&amp;rsquo;s a &lt;code&gt;sshd_config.d&lt;/code&gt; directory. The reason for this is that the config file has a line in it at the top that includes all the config files in that directory. This is a common pattern in Unbuntu - the main config file pulls in other config files. When you see that, you really shouldn&amp;rsquo;t edit the main config file as it&amp;rsquo;s possible that a future update will change it, you edit, or add to the files in the directory below.&lt;/p&gt;
&lt;p&gt;The way it words is that the commands in the files in the sub-directory will have priority over the defaults in the main config file (which is slightly counter-intuitive for me).&lt;/p&gt;
&lt;p&gt;The VPS I am using is on &lt;a href="https://blog.iankulin.com/your-own-aussie-server-on-binarylane/"&gt;binarylane&lt;/a&gt;, and they already have a config file in the subdirectory, so I&amp;rsquo;ll edit that.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-4.08.24-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;With a standard Unbuntu install, there are no files in there, so you&amp;rsquo;ll need to create one with &lt;code&gt;touch&lt;/code&gt;, then add the line &lt;code&gt;PasswordAuthentication no&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Many of the articles on the internet also talk about turning off &lt;code&gt;ChallengeResponseAuthentication&lt;/code&gt; and &lt;code&gt;UsePAM&lt;/code&gt;, but I found with my VPS and local Unbuntu servers, all that was needed was &lt;code&gt;PasswordAuthentication&lt;/code&gt; if my intention was to prevent these attacks.&lt;/p&gt;
&lt;p&gt;Note that once this config is activated, you won&amp;rsquo;t be able to log in via ssh with a password, so it would be foolhardy to do it without having set up &lt;em&gt;and&lt;/em&gt; tested your login with keys.&lt;/p&gt;
&lt;p&gt;This config won&amp;rsquo;t be active until the ssh daemon is restarted.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo systemctl reload ssh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now if someone attempts to ssh in they&amp;rsquo;ll see this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-4.16.37-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Not that turning off passwords like this is only for ssh. You&amp;rsquo;ll still be able to log in via the console if you&amp;rsquo;ve stuffed something up.&lt;/p&gt;</description></item><item><title>Save Proxmox password in Chrome</title><link>https://blog.iankulin.com/save-proxmox-password-in-chrome/</link><pubDate>Sat, 11 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/save-proxmox-password-in-chrome/</guid><description>&lt;p&gt;When I installed Proxmox, I&amp;rsquo;d used a secure, and therefore absurdly long and complicated root password. I do use a password manager, but don&amp;rsquo;t have it integrated into Chrome, so it was buggging me having to find it and paste it in each time - why wasn&amp;rsquo;t Chrome offering to save it for me?&lt;/p&gt;
&lt;p&gt;Well, you&amp;rsquo;d guess it was something to do with this. I feel like Chrome is trying to tell me something here:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-7.06.49-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Seems like a certificate thing. &lt;a href="https://forum.proxmox.com/threads/how-can-i-save-pve-web-loginpassword-on-firefox-chrome.46180/"&gt;These peeps&lt;/a&gt; say that I need to import the CA from PVE, and one more &lt;a href="https://pve.proxmox.com/wiki/Import_certificate_in_browser"&gt;googlestep reveals&lt;/a&gt; the certificate is on the Proxmox machine at &lt;code&gt;/etc/pve/pve-root-ca.pem&lt;/code&gt; so we need to grab that.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/aint.jpg" width="90" alt=""&gt;
&lt;p&gt;A while ago, I wrote a post about &lt;a href="https://blog.iankulin.com/copying-a-file-via-ssh/"&gt;using scp to copy files over ssh&lt;/a&gt;, and you should totally know how to do that, but my daily drive for secure file copying is now &lt;a href="https://filezilla-project.org/"&gt;filezilla&lt;/a&gt;. Once you have a bundle of servers in VM&amp;rsquo;s and containers that you revisit and move stuff around all the time, its just a big productivity step-up to have that list of hosts and credentials a tap away, plus having the visual arrangement of nested folders works for my brain somehow.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-7.14.40-am-1.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;On Mac, certificates need to live in the KeyChain, so you just drag the file into the certificates page. But it won&amp;rsquo;t be trusted, so you need to go in and manually do that. Where it says &amp;ldquo;Use System Defaults&amp;rdquo; change it to &amp;ldquo;Always Trust&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-7.19.54-am-1.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;It was annoying at this stage to find that Chrome was still saying it was insecure - even though it had changed to saying the certificate was valid.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-7.20.50-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Looking at the settings for the site in Chrome, there&amp;rsquo;s an option for &amp;ldquo;Insecure Content&amp;rdquo; I try changing that to &amp;ldquo;Allow&amp;rdquo;, but really I&amp;rsquo;m guessing by this stage.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-7.21.15-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;But it actually does help - I&amp;rsquo;ve got the little padlock. That wasn&amp;rsquo;t quite the end since Chrome still wasn&amp;rsquo;t offering to save the password, but clearing the cache fixed that.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-7.24.08-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-02-04-at-7.24.08-am.png" width="566" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Saved by the qemu_guest_agent</title><link>https://blog.iankulin.com/saved-by-the-qemu_guest_agent/</link><pubDate>Fri, 10 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/saved-by-the-qemu_guest_agent/</guid><description>&lt;p&gt;Literally an hour after I wrote the post &lt;a href="https://blog.iankulin.com/proxmox-qemu-guest-agent/"&gt;about installing the qemu guest agent&lt;/a&gt; in a VM and explaining how it can be used to inject root level commands into a VM, I had use of it due to a mistake.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d decided to add myself to the sudoers file. Since the last line in that file is a directive to include all the files in the /etc/sudoers.d directory, the accepted way to do that for local changes is to create a file in that directory with the necessary commands.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# User privilege specification
root	ALL=(ALL:ALL) ALL

# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL

# Allow members of group sudo to execute any command
%sudo	ALL=(ALL:ALL) ALL

# See sudoers(5) for more information on &amp;#34;@include&amp;#34; directives:

@includedir /etc/sudoers.d
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The format of this command is important to get right, since if you stuff it up, sudo will not work, and I don&amp;rsquo;t even have a root login for this server, so then I&amp;rsquo;d be in a pickle. It&amp;rsquo;s so important to not stuff this up that there is a special command for editing the files that won&amp;rsquo;t let you save them if you&amp;rsquo;ve made a mistake.&lt;/p&gt;
&lt;p&gt;Out of an abundance of caution, I decided to copy the system sudoers file to the directory as a starting point since it would have the correct format and be easy to edit. It didn&amp;rsquo;t occur to me that then the &lt;code&gt;@includedir&lt;/code&gt; at the end would become an infinite loop.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-29-at-2.06.12-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;So here I am, logged in as ian, with no sudo, needing to edit or delete a protected file, and with no root login. Luckily, it&amp;rsquo;s a VM running the qemu user agent, so I can access it from Proxmox.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-01-29-at-2.04.37-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-29-at-2.04.37-pm.png" width="895" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Saved by over-engineering! Thank you open source contributors.&lt;/p&gt;</description></item><item><title>Proxmox - Qemu-guest-agent</title><link>https://blog.iankulin.com/proxmox-qemu-guest-agent/</link><pubDate>Thu, 09 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/proxmox-qemu-guest-agent/</guid><description>&lt;p&gt;One of the strengths of having virtual machines (VMs) running inside a hypervisor like Proxmox is how they are isolated from each other and their host. This is a strength - if there is a problem with a particular VM nothing else should be affected by it.&lt;/p&gt;
&lt;p&gt;But this can also be a pain if the hypervisor needs access to a VM to control or monitor it in some way that&amp;rsquo;s only possible from inside the VM. Proxmox can use the &lt;a href="https://qemu-project.gitlab.io/qemu/interop/qemu-ga.html"&gt;Qemu Guest Agent&lt;/a&gt; for this purpose. To over simplify, this is a deamon that runs in the VM and opens a unix socket/virtual serial port to the hypervisor, and listens for commands on it. With Proxmox, the main use of this is to aid in orderly shutdowns and backups, but it also allows us to run commands in the VM from Proxmox - an obvious security compromise. You definitely would not want to install this daemon on a hosted VPS.&lt;/p&gt;
&lt;h4 id="installing-qemu-guest-agent"&gt;Installing Qemu-guest-agent&lt;/h4&gt;
&lt;p&gt;I&amp;rsquo;m running Unbuntu Server 22.4.1 inside Proxmox 7.3 for the following examples.&lt;/p&gt;
&lt;p&gt;Use apt (or whatever you distro uses) to install the agent inside the VM.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apt install qemu-guest-agent
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will do the usual thing - build the list, ask your permission to use the disk space, then download and unpack everything.&lt;/p&gt;
&lt;p&gt;Some guides on the internet will tell you to either use &lt;code&gt;systemctl&lt;/code&gt; to start the agent now, or to reboot the VM. Don&amp;rsquo;t do either of those.&lt;/p&gt;
&lt;p&gt;Instead, shutdown the VM entirely from Proxmox. Then in Proxmox, with the VM selected, we need to go into &lt;code&gt;Options&lt;/code&gt; and find &lt;code&gt;QEMU Guest Agent&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-29-at-9.21.27-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;To change an option you either double click on the line your are interested in, or select it and click edit up the top. So do that for &lt;code&gt;QEMU Guest Agent&lt;/code&gt; and select the box to enable it.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-29-at-9.33.05-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Once that&amp;rsquo;s done. We&amp;rsquo;ll select the VM and start it. If you watch the summary screen as it starts, you&amp;rsquo;ll be able to see if everything is working by watching the IP Address field. It will start off saying &lt;em&gt;Guest Agent not running&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-29-at-9.37.33-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;But then change once the boot gets to the stage of running all the daemons. This is an example of the hypervisor being able to use the agent to get information about what&amp;rsquo;s going on inside the VM.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-29-at-9.33.53-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;If you want to double check everything is working, you can &lt;code&gt;ssh&lt;/code&gt; into the VM, and have a look at the process with &lt;code&gt;systemctl status qemu-guest-agent&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-01-29-at-12.07.46-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-29-at-12.07.46-pm.png" width="938" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Or, we can look from the host. If you select the shell of the node - remember mine was called &lt;code&gt;pve&lt;/code&gt;, you have a console for the root node that owns all the virtual machines. We can run qm with &lt;a href="https://qemu.readthedocs.io/en/latest/interop/qemu-ga-ref.html"&gt;all sorts of options&lt;/a&gt; to accomplish different things. One of the most interesting is &lt;code&gt;qm guest exec&lt;/code&gt; which allows us to run whatever we&amp;rsquo;d like, as root, on the guest vm.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-29-at-12.13.17-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The number 101 in &lt;code&gt;qm guest exec 101 -- hostname&lt;/code&gt; is the Proxmox id for the server we want to access - it&amp;rsquo;s shown in the server view in the top left, and the text after &lt;code&gt;--&lt;/code&gt; is the command to execute. What&amp;rsquo;s returned is some JSON with the exit code and the output. This should be a chilling reminder that anyone with access to the proxmox account will also have root access to all your VM&amp;rsquo;s running the daemon.&lt;/p&gt;</description></item><item><title>SSH &amp; the scary warning</title><link>https://blog.iankulin.com/ssh-the-scary-warning/</link><pubDate>Wed, 08 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/ssh-the-scary-warning/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-8.41.11-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The first time you connect to a new server with ssh, it asks you something like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;➜ ~ &amp;gt; ssh ian@192.168.100.20 
The authenticity of host &amp;#39;192.168.100.20 (192.168.100.20)&amp;#39; can&amp;#39;t be established.
ED25519 key fingerprint is SHA256:ZcNTcOjO/0fOLC5iNChf8Q8MHN7z2d+VV0qz7XqH1g4.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added &amp;#39;192.168.100.20&amp;#39; (ED25519) to the list of known hosts.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once you&amp;rsquo;ve said yes, it adds the server &amp;lsquo;fingerprint&amp;rsquo; to the known hosts file, then next time you ssh there, it feels safe - we know this server.&lt;/p&gt;
&lt;p&gt;But&amp;hellip;. if you&amp;rsquo;re playing around with virtual machines. Loading them, booting them, rebuilding them, cloning them etc. You might try and connect to a VM which is a different one from before, but which has the same ip address. SSH will not be happy:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;➜ ~ &amp;gt; ssh ian@192.168.100.20
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ED25519 key sent by the remote host is
SHA256:ZcNTcOjO/0fOLC5iNChf8Q8MHN7z2d+VV0qz7XqH1g4.
Please contact your system administrator.
Add correct host key in /Users/user/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /Users/user/.ssh/known_hosts:9
Host key for 192.168.100.20 has changed and you have requested strict checking.
Host key verification failed.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It is right to be suspicious. From its point of view, it goes to Joe&amp;rsquo;s house each day, and it&amp;rsquo;s always Joe who answers the door. Today, it&amp;rsquo;s someone completely different but who says they are Joe. But since we know this is a different server, this is an expected result, so I&amp;rsquo;d like to ignore it.&lt;/p&gt;
&lt;p&gt;Although the message says to add the new fingerprint to the known_hosts file, it&amp;rsquo;s easier just to delete the old ones. Then when I try to connect to this server again, it will think it&amp;rsquo;s a new one and ask me to accept it. To delete this ip address (or hostname) out of the known hosts file, I just need to:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;➜ ~ &amp;gt; ssh-keygen -R 192.168.100.20
# Host 192.168.100.20 found: line 7
# Host 192.168.100.20 found: line 8
# Host 192.168.100.20 found: line 9
/Users/user/.ssh/known_hosts updated.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And we&amp;rsquo;re good to go again. But thank you ssh for being so careful.&lt;/p&gt;</description></item><item><title>Proxmox - Installing a Virtual Machine</title><link>https://blog.iankulin.com/proxmox-installing-a-virtual-machine/</link><pubDate>Tue, 07 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/proxmox-installing-a-virtual-machine/</guid><description>&lt;p&gt;Installing your first virtual machine (VM) in the Proxmox hypervisor is pretty straightforward. This post runs through those steps using Proxmox 7.3.&lt;/p&gt;
&lt;p&gt;You need an operating system for your virtual machine, I&amp;rsquo;m going to use &lt;a href="https://ubuntu.com/download/server"&gt;Ubuntu server&lt;/a&gt; in this example, but it could just as easily be &lt;a href="https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2016-essentials"&gt;Windows server&lt;/a&gt;, or regular windows, or one of the desktop Linux distributions. Whichever you decide, you&amp;rsquo;ll need to find and download the ISO for it. The ISO is a (usually quite large) file needed to install the operating system.&lt;/p&gt;
&lt;p&gt;Once, you&amp;rsquo;ve got the ISO for the operating system, you need to upload it into Proxmox via the web interface. The ISO will be stored in the &lt;code&gt;local&lt;/code&gt; directory style storage. If you click on it in Proxmox, you&amp;rsquo;ll see there&amp;rsquo;s actually a section for ISOs, as well as buttons there to upload an ISO from your machine, or to directly download it into ProxMox from a link.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-27-at-5.45.54-pm-copy.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Above you can seen I&amp;rsquo;ve now got two ISO images stored in my local storage. Once an image is there, you are ready to install it.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-3.03.45-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;In the top right of the Proxmox screen there are two blue buttons. One of them says &amp;ldquo;Create VM&amp;rdquo;, and that&amp;rsquo;s what we want to do. Now there will be a series of dialogs to click through and fill out. Most things we can just leave as defaults, but a few need some decisions.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-3.06.56-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The node (your server) is already filled out. Mine is &lt;code&gt;pve&lt;/code&gt; since I just used the default name when I first installed Proxmox. The VM (virtual machine) ID is used by Proxmox to identify the server. You can change this to any three digit number you haven&amp;rsquo;t used. I&amp;rsquo;m keeping 100. Some people use this to separate their server types, for example all their production servers might be in the three hundreds.&lt;/p&gt;
&lt;p&gt;You need to come up with a name for this VM. These can only use letters and numbers - no punctuation. I like to keep them short, and describe the purpose of this VM, but perhaps you want to name yours after the OS you are using. I&amp;rsquo;m calling this one dockerhost because it&amp;rsquo;s going to host my Docker containers. Once you&amp;rsquo;ve decided, hit &lt;code&gt;next&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-3.15.02-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s where we choose the image, I&amp;rsquo;m going with the Unbuntu I downloaded earlier.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-3.16.46-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The System page - I&amp;rsquo;m just leaving all the defaults and hitting &lt;code&gt;next&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-3.18.24-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;On the Disks page, we go have a decision to make: how much drive space does this VM get. You&amp;rsquo;ll remember from our discussion about thin provisioning that we can allocate more disk than we have, but it&amp;rsquo;s not a good idea. The final decision about this is something you need to make considering the purpose of this VM and the space you&amp;rsquo;ve got available to you. You might need to google around for recommendations. It&amp;rsquo;s pretty easy to increase the disk size after your VM is created, but more difficult to reduce it.&lt;/p&gt;
&lt;p&gt;The Wizard has suggested 32GB for me, but the &lt;a href="https://linuxconfig.org/ubuntu-22-04-minimum-requirements"&gt;minimum spec is for 2.5GB&lt;/a&gt;. I am going to be downloading a few large containers, so 10GB seems like a good starting point for me.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-3.29.27-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Next is the CPU&amp;rsquo;s. Leave the defaults for everything, except you need to make a decision about the number of cores. My baby server only has two cores, but yours may have a eight or more. Proxmox will ration things out to some extent by time slicing - so you can easily run eight VM&amp;rsquo;s all allocated one core on a four core processor. And in fact, since a lot of them will probably just be sitting there waiting for something to happen, none of them will need to wait.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s probably a bad idea to allocate all of your cores to one VM, so I&amp;rsquo;m going to say &amp;lsquo;one&amp;rsquo; for mine, but you should also consider the processing needs of your VMs.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-3.44.45-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Another important consideration is the amount of memory. Again, the needs will be determined by your use case. In my case, the minimum spec is for 1GB, but I&amp;rsquo;m planning on loading up some large containers and I have 8GB in hardware. So I&amp;rsquo;ll go with 4GB. The story with the minimum memory field is a &lt;a href="https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_memory"&gt;little bit complicated&lt;/a&gt;, but basically, setting this lower than the max memory gives Proxmox a little bit of flexibility to share it around if you&amp;rsquo;re not using it all - which sounds like a good idea, so I&amp;rsquo;ll say my minimum is 2GB.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-3.47.21-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Networking in a visualized environment is a whole thing. But I have simple needs and only one hardware port, so all these defaults are fine for us.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-3.48.50-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The Confirm page is just a last chance to look over what we&amp;rsquo;ve chosen, then we can press &lt;code&gt;Finish&lt;/code&gt; to create our VM! A few seconds later it should be showing up in the server view. If we click on the VM in the server view, we can see the summary. It&amp;rsquo;s not very exciting yet because our machine is not running.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-3.57.42-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve highlighted the buttons we are going to use next in the image above. &lt;code&gt;Start&lt;/code&gt; is going to start the VM, and we&amp;rsquo;ll need to open the &lt;code&gt;Console&lt;/code&gt; to see what&amp;rsquo;s going on. Go ahead and click both of these now, and sit back in amazement.&lt;/p&gt;
&lt;p&gt;What happens next depends on what OS you are installing into this VM. You&amp;rsquo;ll just need to work your way through the questions accordingly. One point worth noticing though is that if is asks you questions like &amp;ldquo;Use the entire disk&amp;rdquo;, it&amp;rsquo;s talking about the virtual disk you allocated - not the physical disk.&lt;/p&gt;
&lt;p&gt;This operating system you&amp;rsquo;re installing now &lt;em&gt;doesn&amp;rsquo;t know&lt;/em&gt; it&amp;rsquo;s inside a virtual machine. Everything it sees - the machine bios, the screen, the memory - it&amp;rsquo;s all faked - and managed by Proxmox. You and Proxmox are playing god here. From the VM point of view, it could be installed directly on hardware. It doesn&amp;rsquo;t know the true nature of it&amp;rsquo;s world.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/pappademas_matrixkeanureeves.webp" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-4.15.24-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;While you are killing time waiting for your new OS to install, if you haven&amp;rsquo;t used noVNC before, it&amp;rsquo;s worth noticing the little slide in options on the left edge there.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-6.45.34-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-6.45.34-pm.png" width="974" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I most commonly use it to force this window fullscreen, but in the &amp;ldquo;Extra Keys&amp;rdquo; button might be handy if you&amp;rsquo;re running a Windows OS and want the Windows key. I don&amp;rsquo;t love this console window - I&amp;rsquo;d rather SSH in and use my terminal, but it&amp;rsquo;s a handy tool that&amp;rsquo;s always going to work if the VM is running.&lt;/p&gt;</description></item><item><title>Chinese Hackers Want to steal my Hello World container</title><link>https://blog.iankulin.com/chinese-hackers-want-to-steal-my-hello-world-container/</link><pubDate>Mon, 06 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/chinese-hackers-want-to-steal-my-hello-world-container/</guid><description>&lt;p&gt;A smart thing to do after setting up a server on the internet, is to set up SSH keys and then turn passwords off for SSH. The reason for this is that scanning for open port 22 on IP addresses, then brute forcing password files on them is pretty much hacker 101. So if you have passwords turned on, and especially if you have a weak password you are really inviting someone to take over your server as root and add it to their botnet army for liking Putin&amp;rsquo;s twitter posts or whatever.&lt;/p&gt;
&lt;p&gt;When I was writing &lt;a href="https://blog.iankulin.com/sudo-incident-reports-where-do-they-go/"&gt;the post about looking for the sudo attempt&lt;/a&gt; &amp;lsquo;report&amp;rsquo;, you might have noticed some sshd timeouts:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-12.08.21-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s what&amp;rsquo;s going on there. SSH has a timeout value of about a minute. I&amp;rsquo;d also guess those kex_exchange_identification messages are suspicious as well. I thought I&amp;rsquo;d google one of the IP addreses:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-12.18.14-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-12.18.14-pm.png" width="895" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Oh, so it&amp;rsquo;s China, and multiple people are reporting SSH brute force attacks:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-12.20.35-pm.jpg" alt=""&gt;&lt;/p&gt;</description></item><item><title>Your own Aussie server on BinaryLane</title><link>https://blog.iankulin.com/your-own-aussie-server-on-binarylane/</link><pubDate>Sun, 05 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/your-own-aussie-server-on-binarylane/</guid><description>&lt;p&gt;Listening to podcasts, I&amp;rsquo;ve been jealous of US developers who seem to have masses of $5/month VPS (Virtual Private Server) options. When I looked for similar Australian offerings a few months ago, they all seem to start at around $35 which is outside of my &amp;lsquo;have a play with something&amp;rsquo; budget range.&lt;/p&gt;
&lt;p&gt;I could of course use one of the international options, but one of the main apps on my app ideas list needs to be hosted in Australia and work under Australian data privacy rules. That might be the case for Digital Ocean (or other US companies) if you select an AU server, but I&amp;rsquo;m not a lawyer. For the imaginary clients of my imaginary app, me being able to say that the hosting is with an Australian company in Australia would be a plus.&lt;/p&gt;
&lt;p&gt;I was having another look recently and discovered that &lt;a href="https://www.mammoth.com.au/"&gt;Mammoth&lt;/a&gt; (who are reputable Australian VPS providers) have a service branded &amp;ldquo;&lt;a href="https://www.binarylane.com.au/"&gt;binary lane&lt;/a&gt;&amp;rdquo; that is aimed at developers needing to quickly stand up test servers that are at this low end price point.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.binarylane.com.au/"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-12.49.14-pm.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I mean, &amp;ldquo;ex GST&amp;rdquo; is a bit sly, but still, this is definitely in the starting price category I&amp;rsquo;m interested in. Naturally the prices scale up as your needs do.&lt;/p&gt;
&lt;p&gt;I started hitting buttons to make an account, and true to the advertising, I was logged into an Ubuntu (you can chose from a heap of ISOs or upload your own) server that was live on the internet, hosted from Sydney, with it&amp;rsquo;s own IP address inside a minute. A few minutes after that, I&amp;rsquo;d done updates, installed Docker and had a website live on the internet.&lt;/p&gt;
&lt;p&gt;It has a nice panel interface in their web site with a console and some vital statistics and information.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-1.09.06-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Although convenient, the webpage console has a tiny lag I find unsettling, so as soon as I had the basics sorted out I switched to ssh from my MacBook terminal.&lt;/p&gt;
&lt;p&gt;This was a super painless experience, and super affordable. Since they are in the business of selling you more VPS capacity, it looks like the process of scaling up your virtual machine as needed is going to be painless as well.&lt;/p&gt;
&lt;p&gt;My plan for this VPS is to use it to learn how to add a domain, set up SSL, and eventually just keep it as a test server for apps and api endpoints.&lt;/p&gt;</description></item><item><title>sudo Incident Reports - where do they go?</title><link>https://blog.iankulin.com/sudo-incident-reports-where-do-they-go/</link><pubDate>Sat, 04 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/sudo-incident-reports-where-do-they-go/</guid><description>&lt;p&gt;Even though it&amp;rsquo;s &lt;em&gt;my&lt;/em&gt; server, I still have a pang of guilt when this happens.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-28-at-10.40.43-am-copy.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I always imagine &lt;a href="https://en.wikipedia.org/wiki/Richard_Stallman"&gt;Richard Stallman&lt;/a&gt; (or someone with a similar 2000&amp;rsquo;s database administrator beard) looking at me disappointedly and shaking his head slowly.&lt;/p&gt;
&lt;p&gt;It does raise the question though - since it&amp;rsquo;s my server, shouldn&amp;rsquo;t I be getting a text message from CERN or something?&lt;/p&gt;
&lt;h4 id="where-is-this-report"&gt;Where is this report?&lt;/h4&gt;
&lt;p&gt;(&lt;a href="https://xkcd.com/838/"&gt;Relevant xkcd&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Like everything, the answer is &amp;lsquo;it&amp;rsquo;s logged&amp;rsquo;. We can use the &lt;code&gt;journalctl&lt;/code&gt; command to look at the logs, on this server that&amp;rsquo;s been running less than 20 hours, there&amp;rsquo;s already several thousand lines to look through if you just enter &lt;code&gt;journalctl&lt;/code&gt;, so I&amp;rsquo;m going to just send all the high priority logs to a file:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;journalctl -p 3 &amp;gt; errors.txt
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then since this just happened, it should be at the end of the file:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;tail errors.txt
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;Jan 28 12:10:40 enrico-rider sshd[5168]: fatal: Timeout before authentication for 110.41.153.190 port 41826
Jan 28 12:11:01 enrico-rider sshd[5170]: fatal: Timeout before authentication for 110.41.153.190 port 41856
Jan 28 12:23:15 enrico-rider sshd[5222]: fatal: Timeout before authentication for 61.177.173.39 port 29421
Jan 28 12:23:26 enrico-rider sshd[5223]: fatal: Timeout before authentication for 61.177.173.39 port 49692
Jan 28 12:23:37 enrico-rider sshd[5226]: fatal: Timeout before authentication for 61.177.173.39 port 10416
Jan 28 12:39:51 enrico-rider sshd[5517]: fatal: Timeout before authentication for 61.177.172.108 port 53867
Jan 28 12:50:06 enrico-rider sshd[5653]: error: kex_exchange_identification: Connection closed by remote host
Jan 28 13:03:53 enrico-rider sshd[5696]: error: kex_exchange_identification: Connection closed by remote host
Jan 28 13:24:58 enrico-rider sshd[5804]: fatal: Timeout before authentication for 61.177.173.39 port 46041
Jan 28 13:40:06 enrico-rider sudo[6077]: ian : user NOT in sudoers ; TTY=pts/0 ; PWD=/home/ian ; USER=root ; COMMAND=/usr/bin/docker ps
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There we go, it really has been reported!&lt;/p&gt;
&lt;h4 id="how-to-add-a-user-to-the-sudoers-file"&gt;How to add a user to the sudoers file&lt;/h4&gt;
&lt;p&gt;To avoid these terrible reports, it sounds like I need to add myself to the &amp;lsquo;sudoers file&amp;rsquo;. I won&amp;rsquo;t be able to do that as myself, so I&amp;rsquo;ll log back in as &lt;code&gt;root&lt;/code&gt; for a bit. The reason I don&amp;rsquo;t just operate as &lt;code&gt;root&lt;/code&gt; all the time is that I quite like the constant reminder that I&amp;rsquo;m about to do something administratory - so I should have a second thought before I sudo that shell command I just copied out of a stackoverflow answer.&lt;/p&gt;
&lt;p&gt;Since the error message says I&amp;rsquo;m not in the sudoers file, I should just add my name right? Well yes, and no. That is possible, but it&amp;rsquo;s slightly dangerous - it has a specific format, and if you stuff things up bad things can happen. For this reason there&amp;rsquo;s a special command to edit it (visudo) which refuses to save it if you make a mistake.&lt;/p&gt;
&lt;p&gt;The sudoers file is at &lt;code&gt;/etc/sudoers&lt;/code&gt;, if you &lt;code&gt;cat&lt;/code&gt; it, it has a heap of commented out stuff, but there&amp;rsquo;s be a section that looks like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# User privilege specification
root	ALL=(ALL:ALL) ALL

# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL

# Allow members of group sudo to execute any command
%sudo	ALL=(ALL:ALL) ALL

# See sudoers(5) for more information on &amp;#34;@include&amp;#34; directives:

@includedir /etc/sudoers.d
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That last line includes any files in the &lt;code&gt;/ect/sudoers.d&lt;/code&gt; as part of this one, so if we really did want to add &lt;code&gt;ian&lt;/code&gt; to this file, we&amp;rsquo;d do it there, but still by using the &lt;code&gt;visudo&lt;/code&gt; command to do it safely.&lt;/p&gt;
&lt;p&gt;But, we don&amp;rsquo;t need to. The &lt;code&gt;%admin&lt;/code&gt; and &lt;code&gt;%sudo&lt;/code&gt; lines are granting these permissions to groups, so all we need to do is add &lt;code&gt;ian&lt;/code&gt; to the &lt;code&gt;sudo&lt;/code&gt; group and those permissions will be granted, safely.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;usermod -a -G sudo ian
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Success:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ian@enrico-rider:~$ docker ps
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get &amp;#34;http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json&amp;#34;: dial unix /var/run/docker.sock: connect: permission denied
ian@enrico-rider:~$ sudo docker ps
[sudo] password for ian: 
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
520ed656ef12 dockersamples/101-tutorial &amp;#34;nginx -g &amp;#39;daemon of…&amp;#34; 14 hours ago Up 14 hours 0.0.0.0:80-&amp;gt;80/tcp, :::80-&amp;gt;80/tcp pedantic_bartik
ian@enrico-rider:~$ 
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Proxmox - Storage Basics</title><link>https://blog.iankulin.com/proxmox-storage-basics/</link><pubDate>Fri, 03 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/proxmox-storage-basics/</guid><description>&lt;p&gt;Once you&amp;rsquo;ve got Proxmox installed, you can point your web browser at the IP for the physical server, and use the port 8006. Log in as &lt;code&gt;root&lt;/code&gt; using the password you entered during the install. If you just accepted all the defaults during the install it will look something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-26-at-7.52.16-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s discuss what you&amp;rsquo;re seeing in that &amp;lsquo;Server View&amp;rsquo; on the left there. &lt;code&gt;pve&lt;/code&gt; is the name of my &lt;em&gt;node&lt;/em&gt; - this installation of Proxmox on my physical server. If you named your server something different during the install, it will be show that name here.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Datacenter&lt;/code&gt; is just the idea of a container for all your nodes. I just have the one node, but if I had another physical server and set it up with Proxmox, it could be configured to appear in this dashboard along with my first node.&lt;/p&gt;
&lt;p&gt;Looking at my node, &lt;code&gt;pve&lt;/code&gt;, it has two storage items. Both are &amp;rsquo;local&amp;rsquo; which means they are physically on this machine. A common setup would be to have a Network Attached Storage (NAS) and have Proxmox use that for the Virtual Machine (VM) images. A big benefit of that would be the ability to move the VMs between nodes (physical servers) in your datacentre if needed - for example if a server failed.&lt;/p&gt;
&lt;p&gt;Since I only have local storage, you might be wondering why the installer set me up with two. Let&amp;rsquo;s click on the first one &lt;code&gt;local (pve)&lt;/code&gt; and look at the summary for it.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-26-at-8.14.04-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;So we can see in the summary at the right, that the type of this storage is &amp;lsquo;Directory&amp;rsquo;. Meaning that this is just a directory in the host (internally, Proxmox is just a specialised Linux distribution - in theory we could drop in to bash and look at this directory).&lt;/p&gt;
&lt;p&gt;The summary helpfully tells us the content for this storage as well, saying &lt;code&gt;VZDump backup file, ISO image, Container template&lt;/code&gt;. These are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VZDump backup file - backups of VMs or containers&lt;/li&gt;
&lt;li&gt;ISO image - the images that VMs are created from&lt;/li&gt;
&lt;li&gt;Container template - images that containers are created from. For the moment, you can just imagine containers as lightweight VMs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can see from the graph that I&amp;rsquo;ve used a bit of this storage already. That because I have some ISO&amp;rsquo;s and container templates already downloaded to play with for the next post and stored in the local directory type storage.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s click on the other storage &lt;code&gt;local-lmv (pve)&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-01-26-at-8.28.41-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-26-at-8.28.41-pm.png" width="999" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We already discussed the &lt;code&gt;local&lt;/code&gt; part of the name &lt;code&gt;local-lvm (pre)&lt;/code&gt; means it&amp;rsquo;s on this machine/node. LVM stands for Logical Volume Manager. An LVM is an abstraction from the physical disk. A single LMV might actually be made of of a number of physical partitions, or even drives. Regardless of this, and LVM presents as a single volume at the software layer.&lt;/p&gt;
&lt;p&gt;This storage is used for disk for the VMs we&amp;rsquo;ll be running. If you look at the use graph, you can see that about 45 minutes ago, I had been using 10GB. That&amp;rsquo;s because I had a VM and a couple of containers configured. When I created them, part of that process is to specify how much disk storage each VM is allowed to use. Then that allocation is stored here.&lt;/p&gt;
&lt;p&gt;You can see in the summary, that the type of this storage is &lt;code&gt;LVM-Thin&lt;/code&gt;. The Thin part of this description means that although a hunk of storage is allocated, if it&amp;rsquo;s not actually used, then it&amp;rsquo;s still available to be allocated. For example, if you have a 100GB LVM, then you allocate 50GB to a VM, then on this display, you&amp;rsquo;ll see that 50 has been used up. But if the VM is only actually using 5GB, you&amp;rsquo;ll still effectively have 95GB to allocate.&lt;/p&gt;
&lt;p&gt;This is a great idea, until those VM&amp;rsquo;s &lt;em&gt;do&lt;/em&gt; start using up most of their allocation, because at that point the VM&amp;rsquo;s will start getting IO errors. Of course, since it&amp;rsquo;s an LVM, you&amp;rsquo;ll be able to add more storage to it before that happens if you&amp;rsquo;re keeping an eye on it. Thin provisioning was invented for companies that sell virtual server services. Their customers rarely use 100% of the hard disk space they pay for, so it&amp;rsquo;s highly profitable to use thin provisioning of storage and resell the same disk space multiple times.&lt;/p&gt;</description></item><item><title>Upgrade Cycle</title><link>https://blog.iankulin.com/upgrade-cycle/</link><pubDate>Thu, 02 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/upgrade-cycle/</guid><description>&lt;p&gt;Now that I&amp;rsquo;ve seen I can easily stand up VM&amp;rsquo;s on this baby server, it&amp;rsquo;s apparent the first limitation I&amp;rsquo;ll run into is RAM. It has two laptop sized memory slots that can take up to 8GB apiece. So it could easily be doubled, but at a cost of around $70.&lt;/p&gt;
&lt;p&gt;While I&amp;rsquo;m looking on eBay for RAM, the algorithm thinks I might be interested in this.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-26-at-3.47.47-pm-2.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/upgrades.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;While I&amp;rsquo;m looking at the specs (4 cores - the current one has 2, double the RAM, bigger disk), eBay is like &amp;ldquo;Hey, how about this 20% off discount code - is thAt soMetHing ThAt miGHt HeLp yoU deCiDe?&amp;rdquo;&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/whynot.jpg" width="1000" alt=""&gt;
&lt;p&gt;The rationalisation is that even with both, I&amp;rsquo;ve only spent the same as a single Pi4 for a lot more computing power, and I could &lt;em&gt;in theory&lt;/em&gt; sell the first one (trying to get a licensed Windows back on to it could be an interesting challenge).&lt;/p&gt;
&lt;p&gt;And anyway, I don&amp;rsquo;t smoke, and if your deduct the cost of the RAM, it&amp;rsquo;s only the price of three packs of cigarettes&amp;hellip;.&lt;/p&gt;
&lt;p&gt;and really it&amp;rsquo;s an investment in my future job prospects&amp;hellip;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-01-26-at-4.07.13-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-26-at-4.07.13-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Proxmox Hypervisor</title><link>https://blog.iankulin.com/proxmox-hypervisor/</link><pubDate>Wed, 01 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/proxmox-hypervisor/</guid><description>&lt;p&gt;I &lt;a href="https://blog.iankulin.com/pi-server/"&gt;mentioned a while ago&lt;/a&gt; that the price of the &lt;a href="https://www.raspberrypi.com/products/raspberry-pi-4-model-b/specifications/"&gt;Raspberry Pi4&lt;/a&gt; was getting such that it&amp;rsquo;s smarter to purchase one of the little business workstations instead. Depsite having little need for such a thing, I went ahead and bought an &lt;a href="https://support.hp.com/au-en/document/c04266271"&gt;HP Elitedesk 800 G1&lt;/a&gt; &amp;ldquo;mini&amp;rdquo; PC. It has 8GB RAM (which is the max for the Pi4) as well as a 128GB SDD, the processor is an Intel i5.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-26-at-10.54.25-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;This compares pretty well with the 8GB Pi4 which only has a fraction of the storage (on an SD card) at around $400. One area where the Pi would have an edge might be in power consumption - I expect it would be a bit less. One possible catch for young players is that the HP has a &amp;lsquo;display port&amp;rsquo; rather than HDMI for the screen connection, so pick up a $5 adapter if you&amp;rsquo;re getting one. The metal case and nice finishing on the HP actually looks really great in my office compared with my Pi 3b+ dev server that&amp;rsquo;s sort of hanging on the end of a cat5 cable.&lt;/p&gt;
&lt;p&gt;My reason (excuse) for getting the HP is that I&amp;rsquo;m quite interested in getting some experience with (having a play with) deploying web apps in Docker containers. I&amp;rsquo;m also thinking that having better Linux skills and some understanding of devops would be helpful for working in IT in any capacity.&lt;/p&gt;
&lt;p&gt;Virtualization (running several servers inside one physical server) has a number of benefits anyway, but when your main purpose is to fiddle around with things, it&amp;rsquo;s the perfect tool. How this works is that you have a layer between the hardware and the virtual machines (VMs) called the hypervisor. The hypervisor deals with the hardware, and allocates resources to the separate VMs it is hosting. It&amp;rsquo;s probably worth underlining separate in that sentence. The VMs can be set up to communicate via networking, or have access to shared storage, but they are running independently. If one of them crashes for some reason, the others are not affected.&lt;/p&gt;
&lt;p&gt;In practice this means that I can install and run a number of different operating systems on my server. They can be stopped, started, exported, deleted etc all without affecting each other or any &amp;lsquo;critical&amp;rsquo; systems I&amp;rsquo;ve got running in the same box. There&amp;rsquo;s a number of choices for virtualization software. Microsoft has Hyper-V, VMWare is probably the most famous and has a reduced feature, free version called ESXi. That would probably be a good choice if you want directly transferable skills as VMWare have a substantial profile in the commercial world.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://enlyft.com/tech/virtualization-platforms"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-26-at-12.40.24-pm.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;But one of the missing features from ESXi is central management, and I want to play with my toys, and anyway, all the cool kids are using &lt;a href="https://www.proxmox.com/en/proxmox-ve"&gt;Proxmox&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-01-26-at-12.25.13-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-26-at-12.25.13-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Like a couple of others on this list, Proxmox is built on Linux, and is a great choice for a home server setup. It is available for commercial use with different (&lt;a href="https://www.proxmox.com/en/proxmox-ve/pricing"&gt;paid&lt;/a&gt;) tiers of support.&lt;/p&gt;
&lt;h4 id="installing-proxmox"&gt;Installing Proxmox&lt;/h4&gt;
&lt;p&gt;There&amp;rsquo;s quite a few guides around for installing and setting up Proxmox, I won&amp;rsquo;t rehash all the steps here, but rather just make a couple of points, especially in relation to the HP 800 as a host.&lt;/p&gt;
&lt;p&gt;I followed &lt;a href="https://www.youtube.com/watch?v=Flw_ycAwT3E"&gt;this video from Darin Wood&lt;/a&gt;. He did lead me a bit astray by fiddling around with the storage options using the command line. That&amp;rsquo;s probably great if you are going to use an external NAS, but form my situation it would have been better to just leave all the storage defaults as they where - so when Darin gets to those commands just skip over them.&lt;/p&gt;
&lt;p&gt;If Darin is a bit dry for you, a very enthusiastic alternative might be &lt;a href="https://www.youtube.com/watch?v=Flw_ycAwT3E"&gt;this series&lt;/a&gt; from Jeremy Cioara (&lt;a href="https://www.youtube.com/watch?v=Flw_ycAwT3E"&gt;Viatto&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Other points were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I used Balena Etcher to flash the USB thumbdrive with the 7.3 &lt;a href="https://www.proxmox.com/en/downloads/category/iso-images-pve"&gt;Proxmox ISO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;To get the BIOS settings on the HP, you mash the F10 key on start up, but if you just want to choose the boot device, F9 does a better job of that (because you don&amp;rsquo;t need to change it back later).&lt;/li&gt;
&lt;li&gt;A couple of places mentioned having to turn virtualization on in the BIOS of the HP - it&amp;rsquo;s in the &lt;a href="https://h30434.www3.hp.com/t5/Desktops-Archive-Read-Only/How-to-turn-on-the-virtualization-on-hp-elitedesk-800-g1/td-p/3958272"&gt;BIOS settings under security&lt;/a&gt;, but mine was already on, so perhaps it&amp;rsquo;s on by default. &lt;a href="https://en.wikipedia.org/wiki/X86_virtualization#Hardware-assisted_virtualization"&gt;AMD and Intel processors have some special features to support virtualization&lt;/a&gt;, so that&amp;rsquo;s probably what that&amp;rsquo;s about.&lt;/li&gt;
&lt;li&gt;There&amp;rsquo;s a couple of config files to edit so you can do the updates - this is the step to make your life a bit complicated for not paying for Proxmox support. They are well explained in all the guides.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apart from that, I pretty much just followed all the instructions and used the defaults (I used a made up email address, and &lt;code&gt;local&lt;/code&gt; as my hostname), and I was soon up and running. The only other thing I did was go into my router settings to reserve the IP address that the Proxmox machine had picked up from the DHCP server to prevent (the low chance of) it changing in the future.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-26-at-1.29.27-pm-copy.jpg" alt=""&gt;&lt;/p&gt;</description></item><item><title>Pi Server</title><link>https://blog.iankulin.com/pi-server/</link><pubDate>Sun, 04 Dec 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/pi-server/</guid><description>&lt;p&gt;I have a a couple of Raspberry Pi&amp;rsquo;s on my home network. One is a radio interface on the &lt;a href="https://www.allstarlink.org/"&gt;AllStar network&lt;/a&gt;, and the other is just a toy server - I can&amp;rsquo;t actually recall why I bought it. Both of them are Model 3B&amp;rsquo;s - I&amp;rsquo;d love a 4, but they are scarce and expensive.&lt;/p&gt;
&lt;p&gt;This doesn&amp;rsquo;t have much to do with Swift, although it&amp;rsquo;s possible to run &lt;a href="https://lickability.com/blog/swift-on-raspberry-pi/"&gt;Swift on a Pi&lt;/a&gt;, or even &lt;a href="https://medium.com/@jhheider/installing-vapor-and-swift-on-the-raspberry-pi-45a6c7baef35"&gt;Vapor&lt;/a&gt;. Mine is set up as a generic web server that I use as the back end for my tiny projects. It runs &lt;a href="https://nodejs.org/en/about/"&gt;Node.js&lt;/a&gt;, &lt;a href="https://www.apache.org/"&gt;apache&lt;/a&gt; and &lt;a href="https://www.lighttpd.net/"&gt;lighttpd&lt;/a&gt; webservers, &lt;a href="https://www.php.net/"&gt;PHP&lt;/a&gt;, &lt;a href="https://www.mysql.com/"&gt;MySQL&lt;/a&gt;, &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt;, and, when I get to that stage of my programmming, &lt;a href="https://pimylifeup.com/raspberry-pi-postgresql/"&gt;Postgres&lt;/a&gt;. I could do all that on my MacBook, but it&amp;rsquo;s somehow more fun on the Pi.&lt;/p&gt;
&lt;p&gt;A recent addition is to run &lt;a href="https://pi-hole.net/"&gt;Pi-hole&lt;/a&gt; - a DNS sink to block advertisements on all devices on my network. It was a painless addition, and I enjoy looking at the stats as well as ads being blocked at the network level instead of depending on browser addons.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-12-02-at-7.17.04-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not sure that I&amp;rsquo;ll stick with Raspberry Pi. With the current shortage, it probably makes more sense to repurpose an old laptop, or buy a refurb mini pc which for a similar $400 price tag would be substantially more powerful. One con of that approach is you can spend several weekends getting everything running linux properly, whereas with the Pis that&amp;rsquo;s all done for you. Another is the idle power consumption would be much higher than my slow Pi 3Bs.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-12-02-at-8.02.59-pm-1.jpg" alt=""&gt;&lt;/p&gt;</description></item></channel></rss>