<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Caius&#x27; Lab - Ansible</title>
    <link rel="self" type="application/atom+xml" href="https://caius.dev/tags/ansible/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://caius.dev/"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-04-19T00:00:00+00:00</updated>
    <id>https://caius.dev/tags/ansible/atom.xml</id>
    <entry xml:lang="en">
        <title>Setting up a Homelab: Step 1</title>
        <published>2026-04-19T00:00:00+00:00</published>
        <updated>2026-04-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Caius Brindescu
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://caius.dev/blog/homelab-step-1/"/>
        <id>https://caius.dev/blog/homelab-step-1/</id>
        
        <content type="html" xml:base="https://caius.dev/blog/homelab-step-1/">&lt;p&gt;I am building a Kubernetes cluster out of second-hand Lenovo ThinkCenter machines.
As part of this project, I want to streamline the setup as much as possible, and I&#x27;ll try to automate most steps.
The goal is to have a reproducible setup that will simplify modifications, adding more hardware, etc.&lt;&#x2F;p&gt;
&lt;p&gt;In this post, I will go over what I call the &quot;bootstrap step.&quot;
How do you start with a fresh install of an OS, as easily as possible without having to &quot;manually&quot; install it from a USB stick on each machine?
The solution is to create a pre-configured NVMe drive, from a cloud OS image, and set up &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;cloud-init.io&#x2F;&quot;&gt;cloud init&lt;&#x2F;a&gt; to do the configuration that we need.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;theory-of-operation&quot;&gt;Theory of Operation&lt;a class=&quot;post-anchor&quot; href=&quot;#theory-of-operation&quot; aria-label=&quot;Anchor link for: theory-of-operation&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h1&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;I am running this process on macOS.
This comes with a few extra complications, compared to running this from a Linux machine&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-1-1&quot;&gt;&lt;a href=&quot;#fn-1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.
Should this be run from Linux, as it makes life easier?
Probably, but where&#x27;s the fun in that.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Before I dive into the details, let&#x27;s look at what needs to be done:&lt;&#x2F;p&gt;
&lt;p&gt;First, we need to write the image on the NVMe drive.
That&#x27;s easy enough.
However, I want the machines to be headless, so I don&#x27;t have any inputs or output connected.
Ideally when they boot, they are accessible from the network, and ready to set up.
This is where cloud-init comes in.&lt;&#x2F;p&gt;
&lt;p&gt;Cloud-init runs on a cloud VM during first boot, to handle machine-specific configuration.
That&#x27;s our exact use case!
It reads its configuration from a fairly straightforward &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.cloud-init.io&#x2F;en&#x2F;latest&#x2F;explanation&#x2F;format&#x2F;cloud-config.html&quot;&gt;yaml configuration format&lt;&#x2F;a&gt;.
While we aren&#x27;t running in &quot;the cloud,&quot; the abstraction makes sense, and cloud-init is flexible enough to work on a bare-metal machine.&lt;&#x2F;p&gt;
&lt;p&gt;The goal of the setup is to do 2 things:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Set up SSH key for remote access&lt;&#x2F;li&gt;
&lt;li&gt;Set up a hostname, so we can easily find it on the network using DNS.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h1 id=&quot;step-1-writing-the-image&quot;&gt;Step 1: Writing the image&lt;a class=&quot;post-anchor&quot; href=&quot;#step-1-writing-the-image&quot; aria-label=&quot;Anchor link for: step-1-writing-the-image&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h1&gt;
&lt;p&gt;First, we need to choose an OS that we want to run.
For my setup I chose to run Ubuntu 25.10, the latest at the time of writing.
You can find releases on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;cloud-images.ubuntu.com&#x2F;releases&#x2F;&quot;&gt;this page.&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Once we have that, we can&#x27;t write that directly to the NVMe drive.
They are in the QEMU Qcow2 image format (they are designed for cloud use).
We&#x27;ll need to convert them to raw.
We&#x27;ll do this with qemu&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-2-1&quot;&gt;&lt;a href=&quot;#fn-2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;qemu-img&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; convert&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; -f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; qcow2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; -O&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; raw ubuntu-25.10-server-cloudimg-amd64.img ubuntu-25.10-server-cloudimg-amd64.raw&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now we have an image we can write to the NVMe drive.
We&#x27;ll use &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.ansible.com&#x2F;&quot;&gt;Ansible&lt;&#x2F;a&gt; to automate the whole process, so it&#x27;s easily repeatable.&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;markdown-alert-caution&quot;&gt;
&lt;p&gt;Doing any of these following steps on the wrong disk WILL wipe out that disk, and you will lose any and all stored data!
Double check all paths, and your assumptions!
Proceed at your own risk!&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;We&#x27;ll assume that the following variables are defined:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;target_disk: &#x2F;dev&#x2F;disk2&lt;&#x2F;code&gt; - the buffered block device that our NVMe is mounted to&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;target_raw_disk: &#x2F;dev&#x2F;rdisk2&lt;&#x2F;code&gt; - the unbuffered block device for faster writes using &lt;code&gt;dd&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;First, we need to make sure that the drive is unmounted and ready to be written to:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Unmount disk&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; diskutil unmountDisk {{ target_disk }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then we can copy the image to the NVMe drive:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Write image to disk&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; dd if={{ image_src }} of={{ target_raw_disk }} bs=4m&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We now have an NVMe drive that will boot on our machine.
However, it doesn&#x27;t have the configuration needed for headless remote access.
So we&#x27;ll have to tackle that next, and this is where cloud-init comes into play.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;step-2-cloud-init-configuration&quot;&gt;Step 2: Cloud-init configuration&lt;a class=&quot;post-anchor&quot; href=&quot;#step-2-cloud-init-configuration&quot; aria-label=&quot;Anchor link for: step-2-cloud-init-configuration&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h1&gt;
&lt;p&gt;Cloud-init has different ways in which it can get its initial scripts.
On a cloud machine, it&#x27;s usually through some kind of metadata service.
For example, on AWS it&#x27;s from &lt;code&gt;http:&#x2F;&#x2F;169.254.169.254&#x2F;latest&#x2F;&lt;&#x2F;code&gt;.
However, this isn&#x27;t a cloud machine, so we&#x27;ll have to do something different.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily, Cloud-init supports the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.cloud-init.io&#x2F;en&#x2F;22.1&#x2F;topics&#x2F;datasources&#x2F;nocloud.html&quot;&gt;&lt;code&gt;NoCloud&lt;&#x2F;code&gt; datasource&lt;&#x2F;a&gt;.
This allows us to get the user data from a specific partition called &lt;code&gt;cidata&lt;&#x2F;code&gt; or &lt;code&gt;CIDATA&lt;&#x2F;code&gt;.
This is the option we&#x27;ll go with next&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-3-1&quot;&gt;&lt;a href=&quot;#fn-3&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;creating-the-partition&quot;&gt;Creating the partition&lt;a class=&quot;post-anchor&quot; href=&quot;#creating-the-partition&quot; aria-label=&quot;Anchor link for: creating-the-partition&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Since I&#x27;m managing a Linux file system, I decided to use &lt;code&gt;gptfdisk&lt;&#x2F;code&gt;&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-4-1&quot;&gt;&lt;a href=&quot;#fn-4&quot;&gt;4&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; for all the partition management.
First, we need to make sure that we see the whole disk, otherwise we&#x27;ll have no new space to create the new partition.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Make GPT see the full disk&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; sgdisk --move-second-header {{ target_disk }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The default root (&lt;code&gt;&#x2F;&lt;&#x2F;code&gt;) partition will expand to fill all the available space, so we don&#x27;t want to create our new partition immediately after.
This would prevent the partition from growing.
So we&#x27;ll have to create the partition at the end.
For this example, I chose a size of 50MB.
This is probably overkill for the 2 tiny files we&#x27;re writing, but it gives room to grow if we need to.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;Add CIDATA partition at the end&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; sgdisk --new=0:-51M:+50M --typecode=0:0700 --change-name=0:CIDATA {{ target_disk }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Next, we&#x27;ll format it as FAT32, and mount it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;Format CIDATA to FAT32&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; diskutil eraseVolume MS-DOS CIDATA {{ target_disk }}s2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Mount CIDATA partition&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; diskutil mount &#x2F;dev&#x2F;disk2s2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At the end, if you run &lt;code&gt;diskutil&lt;&#x2F;code&gt;, you&#x27;ll get something like this, if all went well.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;dev&#x2F;disk2 (external, physical):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   #:                       TYPE NAME                    SIZE       IDENTIFIER&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   0:      GUID_partition_scheme                        *256.1 GB   disk2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   1: BC13C2FF-59E6-4262-A352-B275FD6F7172               1.1 GB     disk2s13&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   2:        Bios Boot Partition                         4.2 MB     disk2s14&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   3:                        EFI UEFI                    111.1 MB   disk2s15&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   4:           Linux Filesystem                         254.8 GB   disk2s1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   5:       Microsoft Basic Data CIDATA                  52.8 MB    disk2s2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;writing-the-cloud-init-config&quot;&gt;Writing the cloud-init config&lt;a class=&quot;post-anchor&quot; href=&quot;#writing-the-cloud-init-config&quot; aria-label=&quot;Anchor link for: writing-the-cloud-init-config&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Now, we&#x27;re ready to write our cloud-init files.
We&#x27;ll need two of them:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;meta-data&lt;&#x2F;code&gt; for setting the hostname&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;user-data&lt;&#x2F;code&gt; for setting up SSH access.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Write cloud-init meta-data&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  ansible.builtin.copy&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    content&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;      local-hostname: {{ target_hostname }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    dest&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &#x2F;Volumes&#x2F;CIDATA&#x2F;meta-data&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Write cloud-init user-data&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  ansible.builtin.copy&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    content&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;      #cloud-config&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;      users:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;      - name: ubuntu&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;        ssh_authorized_keys:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;        {% for key in public_keys -%}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;          - {{ key }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;        {% endfor -%}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;          sudo: ALL=(ALL) NOPASSWD:ALL&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;          shell: &#x2F;bin&#x2F;bash&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    dest&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &#x2F;Volumes&#x2F;CIDATA&#x2F;user-data&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  become&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We&#x27;ll unmount, eject, and we&#x27;re done.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Unmount disk&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; diskutil unmountDisk {{ target_disk }}s2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Eject disk&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; diskutil eject {{ target_disk }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To create our new configured boot disk, all we need to do is run:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;ansible-playbook&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; playbooks&#x2F;ubuntu-start.yml&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; --ask-become-pass -e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;target_hostname=machine1&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This makes setting up multiple machines for the same cluster really easy, as we only need to change the &lt;code&gt;target_hostname&lt;&#x2F;code&gt; variable when running the playbook.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;a class=&quot;post-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h1&gt;
&lt;p&gt;In this post, I&#x27;ve detailed how to create a bootable drive for a new PC, that will boot up, and configure itself with the correct hostname and SSH keys for remote access.
Using cloud-init allows us to specify a custom bootstrap step for each machine, so we can use the same &quot;base&quot; image in all cases.
This has greatly simplified adding new machines to the cluster, or reprovisioning old ones if we replace the SSD.&lt;&#x2F;p&gt;
&lt;p&gt;To state the obvious, this would be a lot simpler if done using Linux, as we could mount the root file system, and make our changes there.
In that case, using cloud-init is probably superfluous, and this post would be 1&#x2F;4 of the size it is now.
However, using cloud-init gives us more flexibility on what&#x27;s run.
More importantly, it presented a great opportunity for experimenting with a novel (to me) way of bootstrapping Linux on a bare-metal headless machine.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;appendix&quot;&gt;Appendix&lt;a class=&quot;post-anchor&quot; href=&quot;#appendix&quot; aria-label=&quot;Anchor link for: appendix&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h1&gt;
&lt;p&gt;The full playbook:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Flash Ubuntu disk image&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  hosts&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; localhost&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  become&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  vars&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    image_src&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &#x2F;tmp&#x2F;ubuntu-25.10-server-cloudimg-amd64.raw&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    target_raw_disk&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &#x2F;dev&#x2F;rdisk2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    target_disk&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &#x2F;dev&#x2F;disk2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    target_hostname&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; ubuntu&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    public_keys&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; ssh-rsa AAAAB3N....&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;  tasks&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Check running on macOS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      ansible.builtin.assert&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        that&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; ansible_facts[&amp;#39;os_family&amp;#39;] == &amp;quot;Darwin&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        fail_msg&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;This playbook can only be run on macOS&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Unmount disk&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; diskutil unmountDisk {{ target_disk }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Write image to disk&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; dd if={{ image_src }} of={{ target_raw_disk }} bs=4m&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Make GPT see the full disk&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; sgdisk --move-second-header {{ target_disk }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;Add CIDATA partition at the end&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; sgdisk --new=0:-51M:+50M --typecode=0:0700 --change-name=0:CIDATA {{ target_disk }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;Format CIDATA to FAT32&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; diskutil eraseVolume MS-DOS CIDATA {{ target_disk }}s2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Mount CIDATA partition&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; diskutil mount &#x2F;dev&#x2F;disk2s2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Write cloud-init meta-data&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      ansible.builtin.copy&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        content&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;          local-hostname: {{ target_hostname }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        dest&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &#x2F;Volumes&#x2F;CIDATA&#x2F;meta-data&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Write cloud-init user-data&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      ansible.builtin.copy&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        content&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;          #cloud-config&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;          users:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;            - name: ubuntu&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;              ssh_authorized_keys:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;              {% for key in public_keys -%}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;                - {{ key }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;              {% endfor -%}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;              sudo: ALL=(ALL) NOPASSWD:ALL&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;              shell: &#x2F;bin&#x2F;bash&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        dest&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &#x2F;Volumes&#x2F;CIDATA&#x2F;user-data&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      become&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Unmount disk&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; diskutil unmountDisk {{ target_disk }}s2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Eject disk&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      ansible.builtin.command&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        cmd&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; diskutil eject {{ target_disk }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;You &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.jeffgeerling.com&#x2F;blog&#x2F;2024&#x2F;mounting-ext4-linux-usb-drive-on-macos-2024&#x2F;&quot;&gt;can&#x27;t reliably mount an ext4 file system&lt;&#x2F;a&gt;, and using cloud-init gets around this limitation. &lt;a href=&quot;#fr-1-1&quot;&gt;↩&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li id=&quot;fn-2&quot;&gt;
&lt;p&gt;On a Mac, you can install the right package with &lt;code&gt;brew install qemu&lt;&#x2F;code&gt; if you&#x27;re using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;brew.sh&#x2F;&quot;&gt;Homebrew&lt;&#x2F;a&gt;. On a Debian-based Linux distro, you can use &lt;code&gt;apt install qemu-utils&lt;&#x2F;code&gt;. &lt;a href=&quot;#fr-2-1&quot;&gt;↩&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li id=&quot;fn-3&quot;&gt;
&lt;p&gt;There&#x27;s also the option of passing in kernel arguments, and using a service on the local network. However, since mounting &lt;code&gt;ext4&lt;&#x2F;code&gt; partitions is not a feasible option, I&#x27;ve decided against this approach. &lt;a href=&quot;#fr-3-1&quot;&gt;↩&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li id=&quot;fn-4&quot;&gt;
&lt;p&gt;You can install it with &lt;code&gt;brew install gptfdisk&lt;&#x2F;code&gt; &lt;a href=&quot;#fr-4-1&quot;&gt;↩&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;section&gt;
</content>
        
    </entry>
</feed>
