<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ansible on blog.iankulin.com</title><link>https://blog.iankulin.com/tags/ansible/</link><description>Recent content in Ansible on blog.iankulin.com</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Sat, 16 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iankulin.com/tags/ansible/index.xml" rel="self" type="application/rss+xml"/><item><title>Ubuntu Ansible 'waiting for privilege escalation prompt'</title><link>https://blog.iankulin.com/ubuntu26-bump/</link><pubDate>Sat, 16 May 2026 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/ubuntu26-bump/</guid><description>&lt;p&gt;I ran into a hiccup today - provisioning a new Ubuntu VPS, the ansible playbook to apply our security hardening failed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ~/Developer/ansible_hl % ansible-playbook ssh-harden.yml --ask-vault-pass -e @vault.yml 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Vault password: 
&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;PLAY &lt;span style="color:#81a1c1"&gt;[&lt;/span&gt;Copy the hardened SSHD_CONFIG file to the remote server&lt;span style="color:#81a1c1"&gt;]&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;TASK &lt;span style="color:#81a1c1"&gt;[&lt;/span&gt;Gathering Facts&lt;span style="color:#81a1c1"&gt;]&lt;/span&gt; ********************************************************************************************
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;[&lt;/span&gt;ERROR&lt;span style="color:#81a1c1"&gt;]&lt;/span&gt;: Task failed: Timeout &lt;span style="color:#81a1c1"&gt;(&lt;/span&gt;12s&lt;span style="color:#81a1c1"&gt;)&lt;/span&gt; waiting &lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; privilege escalation prompt:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;fatal: &lt;span style="color:#81a1c1"&gt;[&lt;/span&gt;&amp;lt;redacted IP&amp;gt;&lt;span style="color:#81a1c1"&gt;]&lt;/span&gt;: UNREACHABLE! &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;&amp;gt; &lt;span style="color:#81a1c1"&gt;{&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;changed&amp;#34;&lt;/span&gt;: false, &lt;span style="color:#a3be8c"&gt;&amp;#34;msg&amp;#34;&lt;/span&gt;: &lt;span style="color:#a3be8c"&gt;&amp;#34;Task failed: Timeout (12s) waiting for privilege escalation prompt:&amp;#34;&lt;/span&gt;, &lt;span style="color:#a3be8c"&gt;&amp;#34;unreachable&amp;#34;&lt;/span&gt;: true&lt;span style="color:#81a1c1"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;My routine is to run Ubuntu LTS, and when I was provisioning the server, I selected Ubuntu 26.04 LTS x64 without thinking. This LTS dropped in April, and excitingly the new versions of Ubuntu have Rust coreutils including &lt;code&gt;sudo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The cause of the issue above (where anisible waits for the sudo password request but never sees it) is that the password prompt in &lt;code&gt;sudo-rs&lt;/code&gt; is different from real sudo. Here&amp;rsquo;s the old one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ian@iris-orca:~$ sudo lsb_release -d
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;[&lt;/span&gt;sudo&lt;span style="color:#81a1c1"&gt;]&lt;/span&gt; password &lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; ian: 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;No LSB modules are available.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Description:	Ubuntu 24.04.4 LTS
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ian@iris-orca:~$ sudo --version
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Sudo version 1.9.15p5
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Sudoers policy plugin version 1.9.15p5
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Sudoers file grammar version &lt;span style="color:#b48ead"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Sudoers I/O plugin version 1.9.15p5
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Sudoers audit plugin version 1.9.15p5
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ian@iris-orca:~$ 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the new one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ian@ksd-on-syd-001:~$ sudo lsb_release -d
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;[&lt;/span&gt;sudo: authenticate&lt;span style="color:#81a1c1"&gt;]&lt;/span&gt; Password: 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Description:	Ubuntu 26.04 LTS
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ian@ksd-on-syd-001:~$ sudo --version
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo-rs 0.2.13-0ubuntu1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ian@ksd-on-syd-001:~$ 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So, &lt;code&gt;[sudo] password for ian: &lt;/code&gt; vs &lt;code&gt;[sudo: authenticate] Password: &lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not a big deal, and ansible has &lt;a href="https://github.com/ansible/ansible/pull/86175"&gt;already made&lt;/a&gt; a fix for the incompatibility, it just hasn&amp;rsquo;t flowed down to me yet. Ubuntu 24 is still LTS so I&amp;rsquo;ll drop back to that.&lt;/p&gt;
&lt;p&gt;For the adventurous, another possible approach would be to create an ansible user with passwordless ssh - I&amp;rsquo;d rather wait for the ansible update before I move to a Linux version using sudo_rs.&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;//192.168.100.32/media/books/audio/ /mnt/media cifs username=abs_user,password=SeCrErpaSSword,file_mode=0660,dir_mode=07
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;version: &amp;#39;3&amp;#39;
&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;services:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; audiobookshelf:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: ghcr.io/advplyr/audiobookshelf
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container_name: audiobookshelf
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ports:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &amp;#34;80:80&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; volumes:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./config:/config
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./metadata:/metadata
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - /mnt/media:/audiobooks
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; restart: always
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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>Ansible - Importing a Playbook</title><link>https://blog.iankulin.com/ansible-importing-a-playbook/</link><pubDate>Thu, 30 Nov 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/ansible-importing-a-playbook/</guid><description>&lt;p&gt;&lt;a href="https://blog.iankulin.com/tags/ansible/"&gt;Ansible&lt;/a&gt; is a system for automating server tasks, and these tasks are written in a special yaml file called a playbook. I had need to call one playbook from another today and learned a couple of things.&lt;/p&gt;
&lt;h3 id="plays-vs-tasks"&gt;Plays vs Tasks&lt;/h3&gt;
&lt;p&gt;In Ansible we run &lt;em&gt;tasks&lt;/em&gt;. A group of tasks run against one particular sets of hosts is called a &lt;em&gt;play&lt;/em&gt;. Here is a playbook with one play, and two tasks:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&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;- name: Play 1 - Print the Book 1 messages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts: 127.0.0.1
&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; tasks:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - name: Print message 1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; debug:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; msg: Book 1, Task 1
&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; - name: Print message 2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; debug:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; msg: Book 1, Task 2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s possible to have multiple plays in a single &lt;em&gt;playbook&lt;/em&gt;. This would often be done if there was different tasks to run on different servers. For example you might want to run log rotations on you web servers, but reindexing on the database servers.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&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;- name: Play 1 - Print the Book 1 messages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts: 127.0.0.1
&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; tasks:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - name: Print message 1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; debug:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; msg: Book 1, Task 1
&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; - name: Print message 2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; debug:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; msg: Book 1, Task 2
&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;- name: Play 2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts: 127.0.0.1
&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; tasks:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - name: Print message
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; debug:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; msg: This is the second play in Book 1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="importing-playbooks"&gt;Importing Playbooks&lt;/h3&gt;
&lt;p&gt;When you run a playbook, generally the whole playbook gets run. So if we did have a playbook that included the tasks for the web servers and database servers, they would all be executed. That makes sense a lot of the time, but what if you usually wanted to run them together, but then sometimes just the database tasks?&lt;/p&gt;
&lt;p&gt;Ansible has an answer for this - you put the plays in different playbooks, but then import one into the top of the other one.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start over. Say this is our first playbook - &lt;code&gt;book1.yml&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&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;- name: Play 1 - Print the Book 1 messages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts: 127.0.0.1
&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; tasks:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - name: Print message
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; debug:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; msg: Book 1, Task 1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we run that with &lt;code&gt;ansible-playbook book1.yml&lt;/code&gt; the output will be:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PLAY [Play 1 - Print the Book 1 messages] *************************************************************************
&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;TASK [Gathering Facts] *******************************************************************************
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ok: [127.0.0.1]
&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;TASK [Print message] *******************************************************************************
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ok: [127.0.0.1] =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;msg&amp;#34;: &amp;#34;Book 1, Task 1&amp;#34;
&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PLAY RECAP *******************************************************************************
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;127.0.0.1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And we&amp;rsquo;ll make a &lt;code&gt;book2,yml&lt;/code&gt; very similar:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&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;- name: Print the Book 2 messages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts: 127.0.0.1
&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; tasks:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - name: Print message
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; debug:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; msg: Book 2, Task 1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can probably imagine the output from the example above - it&amp;rsquo;s identical except for the numbers in the messages.&lt;/p&gt;
&lt;p&gt;These two playbooks can easily be run separately, but then if we wanted to run them together sometimes, we could just make a new playbook that imported both of them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&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;- import_playbook: book1.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- import_playbook: book2.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we run this playbook, the output is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PLAY [Play 1 - Print the Book 1 messages] 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TASK [Gathering Facts] *******************************************************************************ok: [127.0.0.1]TASK [Print message] *******************************************************************************ok: [127.0.0.1] =&amp;gt; { &amp;#34;msg&amp;#34;: &amp;#34;Book 1, Task 1&amp;#34;}PLAY [Print the Book 2 messages] *******************************************************************************TASK [Gathering Facts] *******************************************************************************ok: [127.0.0.1]TASK [Print message 1] *******************************************************************************ok: [127.0.0.1] =&amp;gt; { &amp;#34;msg&amp;#34;: &amp;#34;Book 2, Task 1&amp;#34;}PLAY RECAP *******************************************************************************127.0.0.1 : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="potential-problems"&gt;Potential problems&lt;/h3&gt;
&lt;p&gt;Instead of making a new file to import both playbooks, you might want to just import one playbook into the other. For instance, you could achieve the same as we&amp;rsquo;ve done above by editing &lt;code&gt;book2.yml&lt;/code&gt; to import &lt;code&gt;book1.yml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&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;- import_playbook: book1.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- name: Print the Book 2 messages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts: 127.0.0.1
&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; tasks:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - name: Print message 1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; debug:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; msg: Book 2, Task 1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s important to note that this has to be done at that level in the hierarchy. You can&amp;rsquo;t import a playbook in the middle of a play. For example this is legal yaml, but won&amp;rsquo;t work.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&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;- name: Print the Book 2 messages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts: 127.0.0.1
&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; import_playbook: book1.yml
&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; tasks:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - name: Print message 1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; debug:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; msg: Book 2, Task 1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Instead, we&amp;rsquo;ll get an message like &lt;code&gt;ERROR! 'hosts' is not a valid attribute for a PlaybookInclude&lt;/code&gt;. However this is fine:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&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;- name: Print the Book 2 messages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts: 127.0.0.1
&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; tasks:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - name: Print message 1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; debug:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; msg: Book 2, Task 1
&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;- import_playbook: book1.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The import above is at the highest level of yaml, not inside the play, so it works well.&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tasks:
&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; - name: Install proxmoxer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apt:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: python3-proxmoxer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state: latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[defaults]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;INVENTORY = hosts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;pve_dev1&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pve&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;dev1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#616e87;font-style:italic"&gt;#192.168.100.28&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:#eceff4"&gt;[&lt;/span&gt;pve_dev1&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;vars&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_user&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;{{pve_dev1_user}}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_become_password&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;{{pve_dev1_become_pass}}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;pve_dev1&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pve&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;dev1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#616e87;font-style:italic"&gt;#192.168.100.28&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:#eceff4"&gt;[&lt;/span&gt;pve_dev1&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;vars&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_user&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;root
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_become_password&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;password1234
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;pve_dev1&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b48ead"&gt;192.168&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;100.28&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:#eceff4"&gt;[&lt;/span&gt;pve_dev1&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;vars&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_user&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;root
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_become_password&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;password1234
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - name: start vm321-deb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; community.general.proxmox_kvm:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; api_user : root@pam
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; api_password: &amp;#39;{{pve_dev1_become_pass}}&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; api_host : pve-dev1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name : vm321-deb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state : started
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - name: start vm321-deb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; community.general.proxmox_kvm:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; api_user : root@pam
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; api_password: &amp;#39;{{pve_dev1_become_pass}}&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; api_host : mycluster
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; node : node2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name : vm321-deb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state : started
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - name: start ct351-go
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; community.general.proxmox:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; api_user : root@pam
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; api_password: &amp;#39;{{pve_dev1_become_pass}}&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; api_host : pve-dev1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vmid : 351
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state : started
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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:#d8dee9;background-color:#2e3440;-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 style="color:#8fbcbb"&gt;---&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#81a1c1"&gt;name&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Start pve-dev hosts for updating
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&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:#81a1c1"&gt;vars_files&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; ./vault.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;hosts&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; pve-dev1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;become&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&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:#81a1c1"&gt;tasks&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&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:#81a1c1"&gt;name&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Install proxmoxer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;apt&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;name&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; python3-proxmoxer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;state&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; latest
&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:#81a1c1"&gt;name&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; start babybuntu
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;community.general.proxmox_kvm&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;api_user &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; root@pam
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;api_password&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&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:#81a1c1"&gt;api_host &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; pve-dev1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;name &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; babybuntu
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;state &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; started
&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:#81a1c1"&gt;name&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; start vm321-deb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;community.general.proxmox_kvm&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;api_user &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; root@pam
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;api_password&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&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:#81a1c1"&gt;api_host &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; pve-dev1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;name &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; vm321-deb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;state &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; started
&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:#81a1c1"&gt;name&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; start vm322-deb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;community.general.proxmox_kvm&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;api_user &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; root@pam
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;api_password&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&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:#81a1c1"&gt;api_host &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; pve-dev1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;name &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; vm322-deb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;state &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; started
&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:#81a1c1"&gt;name&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; start vm323-deb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;community.general.proxmox_kvm&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;api_user &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; root@pam
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;api_password&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&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:#81a1c1"&gt;api_host &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; pve-dev1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;name &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; vm323-deb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;state &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; started
&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:#81a1c1"&gt;name&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; start ct351-go
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;community.general.proxmox&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;api_user &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; root@pam
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;api_password&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&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:#81a1c1"&gt;api_host &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; pve-dev1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;vmid &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;351&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;state &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; started
&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:#81a1c1"&gt;name&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; start ct353-omada
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;community.general.proxmox&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;api_user &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; root@pam
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;api_password&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&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:#81a1c1"&gt;api_host &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; pve-dev1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;vmid &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;353&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;state &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; started
&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:#81a1c1"&gt;name&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; start ct356-proxy
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;community.general.proxmox&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;api_user &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; root@pam
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;api_password&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&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:#81a1c1"&gt;api_host &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; pve-dev1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;vmid &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;356&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;state &lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; started
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;apt install apt-cacher-ng
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl start apt-cacher-ng
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl enable apt-cacher-ng
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;---&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt; name&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Install vitals&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;glimpse to a Debian based server
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;# ansible-playbook vg-install.yml --ask-vault-pass &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vars_files&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;./&lt;/span&gt;vault&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;yml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; vm100&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;dockhost
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; become&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&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; tasks&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;-&lt;/span&gt; name&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Copy service file
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;builtin&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;copy&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; src&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; files&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;vitals&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;glimpse&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;service
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dest&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;etc&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;systemd&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;system&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;vitals&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;glimpse&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;service
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; notify&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Restart vitals&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;glimpse
&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:#81a1c1"&gt;-&lt;/span&gt; name&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Copy executable
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;builtin&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;copy&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; src&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; files&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;vitals&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;glimpse
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dest&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;usr&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;local&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;bin&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;vitals&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;glimpse
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mode&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#39;0755&amp;#39;&lt;/span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;# Set the executable permissions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; notify&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Restart vitals&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;glimpse
&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; handlers&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;-&lt;/span&gt; name&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Restart vitals&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;glimpse
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;builtin&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;service&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; vitals&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;glimpse
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; restarted
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; enabled&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; yes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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>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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;vm323_deb&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b48ead"&gt;100.108&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;154.133&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:#eceff4"&gt;[&lt;/span&gt;vm323_deb&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;vars&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_ssh_user&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;ian
&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:#eceff4"&gt;[&lt;/span&gt;vm324&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;deb&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b48ead"&gt;100.77&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;75.14&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:#eceff4"&gt;[&lt;/span&gt;vm324_deb&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;vars&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_ssh_user&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;jane
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;vm323_deb&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b48ead"&gt;100.108&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;154.133&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:#eceff4"&gt;[&lt;/span&gt;vm323_deb&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;vars&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_ssh_user&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;ian
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_become_password&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;mittens96
&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:#eceff4"&gt;[&lt;/span&gt;vm324_deb&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b48ead"&gt;100.77&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;75.14&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:#eceff4"&gt;[&lt;/span&gt;vm324_deb&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;vars&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_ssh_user&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;jane
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_become_password&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;GBLEzrvc8rnUFruVrCwm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible-vault create vault.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;---&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt; name&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; nginx &lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; all web servers
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vars_files&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;./&lt;/span&gt;vault&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; all
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; become&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; yes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tasks&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;-&lt;/span&gt; name&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; nginx installed
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apt&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; nginx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; latest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;-&lt;/span&gt; name&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; nginx running
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; service&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; nginx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; started
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; enabled&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; yes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible-playbook web_installs.yaml --ask-vault-pass
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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>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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible-playbook web_installs.yaml --ask-become-pass
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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>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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh ian@192.168.100.37
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh ian@192.168.100.38
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh ian@192.168.100.39
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;192.168.100.37
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;192.168.100.38
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;192.168.100.39
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible -i hosts all -u ian -a &amp;#34;hostname&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;192.168.100.37 | CHANGED | rc=0 &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;vm321-deb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;192.168.100.38 | CHANGED | rc=0 &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;vm322-deb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;192.168.100.39 | CHANGED | rc=0 &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;vm323-deb
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible -i hosts all -u ian -m file -a &amp;#34;path=test state=directory&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[defaults]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;inventory = hosts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;web&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b48ead"&gt;192.168&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;100.37&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b48ead"&gt;192.168&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;100.38&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:#eceff4"&gt;[&lt;/span&gt;db&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b48ead"&gt;192.168&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;100.39&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:#eceff4"&gt;[&lt;/span&gt;web&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;vars&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_ssh_user&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;ian
&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:#eceff4"&gt;[&lt;/span&gt;db&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;vars&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_ssh_user&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;ian
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible all -a &amp;#34;hostname&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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></channel></rss>