<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on Technically some technical stuff.</title><link>https://technically.kakwalab.ovh/posts/</link><description>Recent content in Posts on Technically some technical stuff.</description><generator>Hugo</generator><language>en</language><managingEditor>carpentier.pf@gmail.com (Pierre-François Carpentier)</managingEditor><webMaster>carpentier.pf@gmail.com (Pierre-François Carpentier)</webMaster><copyright>&amp;copy; &lt;a href="https://github.com/kakwa">Pierre-Francois Carpentier&lt;/a> 2025</copyright><lastBuildDate>Mon, 20 Apr 2026 12:55:15 +0100</lastBuildDate><atom:link href="https://technically.kakwalab.ovh/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>Over-Engineered My Self-Hosting with Kubernetes</title><link>https://technically.kakwalab.ovh/posts/talos-k8s/</link><pubDate>Mon, 20 Apr 2026 12:55:15 +0100</pubDate><author>carpentier.pf@gmail.com (Pierre-François Carpentier)</author><guid>https://technically.kakwalab.ovh/posts/talos-k8s/</guid><description>&lt;h1 id="introduction">Introduction&lt;/h1>
&lt;h2 id="too-honest-for-cv-driven-development">Too Honest For CV Driven Development&lt;/h2>
&lt;p>Working at a &amp;ldquo;Big Tech&amp;rdquo; company has its perks: because of the scale such company operates at,
you typically don&amp;rsquo;t have to deal with basic services like DNS, authentication or CI/CD as
these services are managed by dedicated core teams.&lt;/p>
&lt;p>On one hand, it&amp;rsquo;s great: badly deployed by an overstretched dev or ops guy,
these core services can easily become huge time &amp;amp; efficiency sinks
distracting us from solving the problems our customers pay us for.&lt;/p>
&lt;p>On the other, it can leave gaps in your CV, especially if like me, you have scruples
about gratuitously over-engineering things just to learn new tools/frameworks on the
job at your employer&amp;rsquo;s expense.&lt;/p>
&lt;p>One such gap I currently have is deploying and managing Kubernetes clusters.&lt;/p>
&lt;p>To be honest, I kind of avoided diving into K8s. Maybe it&amp;rsquo;s an unfounded bias on my part,
but Kubernetes always felt a bit overcomplicated, clunky and not very pleasant to manage.&lt;/p>
&lt;p>But, well, it&amp;rsquo;s what the cool kids are doing these days, so here we go.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/talos-k8s/talos-k8s-intro-stock.jpg"
 alt="Complex Highway Interchange">&lt;figcaption>
 &lt;p>Is complexity always justified?&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;h2 id="what-i-wanted-out-of-this">What I Wanted Out Of This&lt;/h2>
&lt;p>By the end of this process, I wanted:&lt;/p>
&lt;ul>
&lt;li>A mostly automated base KVM hypervisor deployment&lt;/li>
&lt;li>A worker Kubernetes cluster, with all the control plane bits configured&lt;/li>
&lt;li>HTTPS load balancer + DNS&lt;/li>
&lt;li>CI/CD with Argo (and integration with GitHub)&lt;/li>
&lt;li>Docker/Container Registry&lt;/li>
&lt;/ul>
&lt;p>This infrastructure should also be managed through the usual &amp;ldquo;configuration as code&amp;rdquo; tools, namely Ansible, Tofu, and a bit of scripting. I want to be able to delete and recreate it at will.&lt;/p>
&lt;p>I&amp;rsquo;m making the actual code I&amp;rsquo;ve used available &lt;a href="https://github.com/kakwa/home.tf" target="_blank">here in my GitHub&lt;/a>. But be aware it is quite tightly coupled to my infrastructure, somewhat badly vibe-coded, and might not be easily reusable.&lt;/p>
&lt;h1 id="kubernetes-basics">Kubernetes Basics&lt;/h1>
&lt;h2 id="choosing-a-kubernetes-distribution">Choosing A Kubernetes Distribution&lt;/h2>
&lt;p>Like chocolate, k8s comes in various flavors. I contemplated deploying it on a traditional distribution, maybe dusting off my Gentoo skills for example.&lt;/p>
&lt;p>But finally, I picked the easier path of using a specialized K8S distribution.&lt;/p>
&lt;p>I considered the following ones:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://www.talos.dev/" target="_blank">Talos Linux&lt;/a>&lt;/strong> is an immutable, API-driven distribution. It has no SSH access, no shell, and everything is configured through declarative configs and the &lt;code>talosctl&lt;/code> CLI.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://www.flatcar.org/" target="_blank">Flatcar Container Linux&lt;/a>&lt;/strong> is a fork of the original CoreOS Container Linux backed by &lt;a href="https://kinvolk.io/" target="_blank">Kinvolk&lt;/a>, now part of Microsoft. It&amp;rsquo;s a minimal and immutable distribution but with SSH access.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://www.redhat.com/en/technologies/cloud-computing/openshift" target="_blank">OpenShift&lt;/a>&lt;/strong> and &lt;strong>&lt;a href="https://fedoraproject.org/coreos/" target="_blank">Fedora CoreOS&lt;/a>&lt;/strong> are Red Hat&amp;rsquo;s successors to the original CoreOS.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>I ended up choosing &lt;strong>&lt;a href="https://www.talos.dev/" target="_blank">Talos Linux&lt;/a>&lt;/strong>. It looked like the most common option on &lt;a href="https://www.reddit.com/r/kubernetes/" target="_blank">/r/kubernetes&lt;/a>, and it&amp;rsquo;s not linked (yet) to the usual corporate vampires.&lt;/p>
&lt;h2 id="kubernetes-base-architecture">Kubernetes Base Architecture&lt;/h2>
&lt;p>Kubernetes has three main categories of components. First is the &lt;code>Control Plane&lt;/code>, which coordinates the cluster. Second are the &lt;code>Workers&lt;/code>, i.e. the nodes actually running stuff.
The third and last category is the Cluster &lt;code>Add-Ons&lt;/code>, enabling optional (but commonly deployed) things like public DNS record management, load-balancing or audit tools.&lt;/p>
&lt;p>In the Control Plane, here are the main components:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://etcd.io/" target="_blank">etcd&lt;/a> (third party): Consistent and highly-available key value store for all API server data &amp;amp; states.&lt;/li>
&lt;li>&lt;a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/" target="_blank">kube-apiserver&lt;/a>: The core component server that exposes the Kubernetes HTTP API.&lt;/li>
&lt;li>&lt;a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kube-scheduler/" target="_blank">kube-scheduler&lt;/a>: Looks for Pods not yet bound to a node, and assigns each Pod (~container execution) to a suitable node.&lt;/li>
&lt;li>&lt;a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/" target="_blank">kube-controller-manager&lt;/a>: Runs the controller loops (replication, namespace, endpoint, etc).&lt;/li>
&lt;/ul>
&lt;p>On the Workers, you have:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/" target="_blank">kubelet&lt;/a>: Ensures that Pods are running, including their containers.&lt;/li>
&lt;li>container runtime (third party): Software responsible for running containers, in our case &lt;a href="https://containerd.io/" target="_blank">containerd&lt;/a>, but others are possible&lt;/li>
&lt;/ul>
&lt;p>And here are some cluster Add-ons we will deploy (non-exhaustive):&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://kubernetes.io/docs/concepts/services-networking/gateway/" target="_blank">Gateway API&lt;/a> (third party): OSI Layer 4 and 7 load balancer to connect our Pods to the outside, here, we will use Traefik, but &lt;a href="https://gateway-api.sigs.k8s.io/implementations/#gateway-controller-implementation-status" target="_blank">other implementations are available&lt;/a>. It replaces the old &lt;a href="https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/" target="_blank">Ingress Controllers&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://kubernetes-sigs.github.io/external-dns/latest" target="_blank">ExternalDNS&lt;/a> (third party): DNS record manager, which integrates with various DNS providers&amp;rsquo; APIs (AWS Route53, GCP DNS, OVH, Gandi, RFC2136) and gives names to your exposed services.&lt;/li>
&lt;/ul>
&lt;p>Here is how everything fits together (simplified):&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/talos-k8s/architecture.png"
 alt="Basic K8S architecture">&lt;figcaption>
 &lt;p>Kubernetes Architecture (simplified)&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>The cluster components talk to each other using HTTP and gRPC and usually authenticate each other using mutual TLS certificates.&lt;/p>
&lt;p>In addition, Talos adds its own &lt;a href="https://docs.siderolabs.com/talos/v1.6/learn-more/components" target="_blank">components (apid, machined, etc)&lt;/a> to configure the cluster and manage its idiosyncrasies (custom init, etc).&lt;/p>
&lt;h1 id="non-k8s-bits">Non-K8s Bits&lt;/h1>
&lt;h2 id="the-metal--a-few-nuts--bolts">The Metal + A Few Nuts &amp;amp; Bolts&lt;/h2>
&lt;p>First, there was the recommissioning of my old rig itself (i5 2500k/32GB DDR3) which required a bit of work, like installing some new storage with 3D printed adapters (&lt;a href="https://www.printables.com/model/1306664-35-to-525-hdd-silencer-bracket" target="_blank">5.25&amp;quot; to 3.5&amp;quot;&lt;/a> + &lt;a href="https://www.printables.com/model/229753-small-hdd-adapter-35-inch-to-25-inch" target="_blank">3.5&amp;quot; to 2.5&amp;quot;&lt;/a>).&lt;/p>
&lt;p>I also did some power consumption optimization (CPU down-clocking, removing/disabling unnecessary cards) to not have the thing draw ~120W continuously. It still draws ~50W however, which is&amp;hellip; &amp;ldquo;not great, not terrible&amp;rdquo;&amp;hellip;
(I am considering replacing it with a refurb mini-pc or an old laptop to be honest).&lt;/p>
&lt;p>After that, I installed the latest Debian and applied &lt;a href="https://github.com/kakwa/home.tf/blob/main/ansible/hypervisor.yml" target="_blank">the following Ansible playbook&lt;/a> to make a basic hypervisor out of it.&lt;/p>
&lt;p>I also deployed an &lt;a href="https://github.com/kakwa/ansible-openbsd" target="_blank">internal DNS&lt;/a> server with TSIG/RFC 2136 dynamic zones on a &lt;a href="https://technically.kakwalab.ovh/posts/silly-sun-server-software">SPARC V100&lt;/a> using OpenBSD for funsies.&lt;/p>
&lt;p>And finally, I&amp;rsquo;ve created a Debian &lt;code>utility&lt;/code> VM for support services like a Docker image &lt;code>registry&lt;/code> or an &lt;code>LDAP&lt;/code> server directory (VM created like in &lt;a href="https://technically.kakwalab.ovh/posts/virtualization-terraform-kvm">Cloud @Home&lt;/a> and configured through this &lt;a href="https://github.com/kakwa/home.tf/blob/main/ansible/utility.yml" target="_blank">utility.yml&lt;/a> Ansible playbook).&lt;/p>
&lt;h2 id="bad-tools-bad-worker">Bad Tools, Bad Worker&lt;/h2>
&lt;p>On our dev machine, we will need a few tools, namely:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.docker.com/" target="_blank">Docker&lt;/a>: Build, ship, and run containers.&lt;/li>
&lt;li>&lt;a href="https://opentofu.org/" target="_blank">OpenTofu&lt;/a> (&lt;code>tofu&lt;/code>): Open-source Terraform fork; describes and applies.&lt;/li>
&lt;li>&lt;a href="https://kubernetes.io/docs/reference/kubectl/" target="_blank">kubectl&lt;/a>: CLI to administer and debug k8s.&lt;/li>
&lt;li>&lt;a href="https://www.talos.dev/latest/talos-guides/install/talosctl/" target="_blank">talosctl&lt;/a>: CLI for bootstrapping and operating Talos.&lt;/li>
&lt;li>&lt;a href="https://helm.sh/" target="_blank">Helm&lt;/a>: &amp;ldquo;Package Manager&amp;rdquo; used to install k8s applications and components.&lt;/li>
&lt;/ul>
&lt;p>Assuming you are using a &lt;code>deb&lt;/code>-based distribution, here is how to install them (if you use macOS, Windows, or OS/2, I leave this exercise to the reader).&lt;/p>
&lt;p>&lt;code>docker&lt;/code> and &lt;code>kubectl&lt;/code> are usually available in Debian/Ubuntu and derivative repositories.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>apt install kubectl docker.io
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For &lt;code>talosctl&lt;/code>, I packaged it in my &lt;a href="https://github.com/kakwa/misc-pkg" target="_blank">own repository&lt;/a>, but you can directly &lt;a href="https://github.com/siderolabs/talos/releases" target="_blank">grab it from Talos releases&lt;/a> if you don&amp;rsquo;t trust me (protip: you shouldn&amp;rsquo;t):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Get the architecture and version&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>. /etc/os-release
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ARCH&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>dpkg --print-architecture&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wget -qO - https://kakwa.github.io/misc-pkg/GPG-KEY.pub | gpg --dearmor |sudo tee /etc/apt/keyrings/misc-pkg.gpg &amp;gt;/dev/null
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo tee /etc/apt/sources.list.d/misc-pkg.sources &amp;gt;/dev/null &lt;span style="color:#e6db74">&amp;lt;&amp;lt;EOF
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Types: deb
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">URIs: https://kakwa.github.io/misc-pkg/deb.${VERSION_CODENAME}.${ARCH}/
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Suites: ${VERSION_CODENAME}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Components: main
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Signed-By: /etc/apt/keyrings/misc-pkg.gpg
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Architectures: ${ARCH}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>apt update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>apt install talosctl
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>OpenTofu publishes &lt;code>tofu&lt;/code> packages in their own repository:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>curl -fsSL https://get.opentofu.org/opentofu.gpg | gpg --dearmor | sudo tee /etc/apt/keyrings/opentofu.gpg &amp;gt; /dev/null
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>curl -fsSL https://packages.opentofu.org/opentofu/tofu/gpgkey | gpg --dearmor | sudo tee /etc/apt/keyrings/opentofu-repo.gpg &amp;gt; /dev/null
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo tee /etc/apt/sources.list.d/opentofu.sources &amp;gt; /dev/null &lt;span style="color:#e6db74">&amp;lt;&amp;lt;EOF
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Types: deb
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">URIs: https://packages.opentofu.org/opentofu/tofu/any/
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Suites: any
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Components: main
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Signed-By: /etc/apt/keyrings/opentofu.gpg /etc/apt/keyrings/opentofu-repo.gpg
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>apt update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>apt install tofu
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And so does Helm:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor | sudo tee /etc/apt/keyrings/helm.gpg &amp;gt; /dev/null
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo tee /etc/apt/sources.list.d/helm-stable-debian.sources &amp;gt; /dev/null &lt;span style="color:#e6db74">&amp;lt;&amp;lt;EOF
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Types: deb
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">URIs: https://packages.buildkite.com/helm-linux/helm-debian/any/
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Suites: any
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Components: main
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">Signed-By: /etc/apt/keyrings/helm.gpg
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt install helm
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="deploy-that-damn-cluster-already">Deploy That Damn Cluster Already!&lt;/h1>
&lt;p>Like the Debian utility VM, I&amp;rsquo;ve also used Tofu to create the k8s/Talos cluster, except this time, we are &amp;ldquo;simply&amp;rdquo; creating nine nodes (3 control plane + 6 workers) instead of one (and I do mean &amp;ldquo;simply&amp;rdquo;, the &lt;code>for(each)&lt;/code> loop really feels like cheating sometimes).&lt;/p>
&lt;p>The &lt;a href="https://github.com/kakwa/home.tf/tree/main/terraform" target="_blank">full code is available on GitHub&lt;/a> and leverages the &lt;a href="https://search.opentofu.org/provider/dmacvicar/libvirt/latest/docs" target="_blank">KVM/libvirt&lt;/a>, the &lt;a href="https://search.opentofu.org/provider/siderolabs/talos/latest/docs/resources/image_factory_schematic" target="_blank">Talos&lt;/a> and the &lt;a href="https://search.opentofu.org/provider/hashicorp/dns/latest/docs" target="_blank">&lt;code>dns&lt;/code>&lt;/a> Tofu providers.&lt;/p>
&lt;p>Be aware that unlike the Tofu code from &lt;a href="https://technically.kakwalab.ovh/posts/virtualization-terraform-kvm">Hyperscaler Cloud @Home&lt;/a>, it&amp;rsquo;s more tied to my home network environment, and would need a fair bit of tweaking to run on your setup.&lt;/p>
&lt;h2 id="talos-image-management">Talos Image Management&lt;/h2>
&lt;p>Talos has an &lt;a href="https://factory.talos.dev/" target="_blank">Image Factory&lt;/a> for creating custom images with specific versions, architectures, and extensions.&lt;/p>
&lt;p>In my case, I left it nearly vanilla, but you can add things if you have special needs, for example, completely at random in these AI days, NVIDIA drivers for CUDA workloads.&lt;/p>
&lt;p>To get the list of customization/versions, simply go through the form and grab the generated &lt;code>schematic&lt;/code> at the end.&lt;/p>
&lt;p>The schematic can be used to configure and download the Talos image in the Tofu provider, and register it in Libvirt:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Create a schematic with custom extensions
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;talos_image_factory_schematic&amp;#34; &amp;#34;this&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> schematic &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">yamlencode&lt;/span>({
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> customization &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> systemExtensions &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> officialExtensions &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;siderolabs/binfmt-misc&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Build the image URL from the schematic
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">locals&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> talos_version &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;v1.12.6&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> talos_image_url &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;https://factory.talos.dev/image/${talos_image_factory_schematic.this.id}/${local.talos_version}/nocloud-amd64.qcow2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Download the image directly into libvirt
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_volume&amp;#34; &amp;#34;talos_base&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;talos-base.qcow2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pool &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;mid-pool&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> create &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">talos_image_url&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> target &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> format &lt;span style="color:#f92672">=&lt;/span> { type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;qcow2&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="cluster-network">Cluster Network&lt;/h2>
&lt;p>It&amp;rsquo;s not k8s, but we need a network for our cluster.&lt;/p>
&lt;p>Let&amp;rsquo;s create one quickly, behind a NAT and with DHCP:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_network&amp;#34; &amp;#34;talos_network&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;talos-network&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> autostart &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> forward &lt;span style="color:#f92672">=&lt;/span> { mode &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;nat&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bridge &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;virbr1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> stp &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;on&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> delay &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ips &lt;span style="color:#f92672">=&lt;/span> [{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> address &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;192.168.100.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> netmask &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;255.255.255.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dhcp &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ranges &lt;span style="color:#f92672">=&lt;/span> [{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;192.168.100.50&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> end &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;192.168.100.254&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="control-plane-nodes">Control Plane Nodes&lt;/h2>
&lt;p>Our Kubernetes control plane will use 3 nodes with the minimal Talos specs, i.e. 2 cores+2GB RAM as we are fairly limited in space here.&lt;/p>
&lt;p>For resiliency in production, this number is usually increased to 5.
On paper this could be further increased to any odd value, but at the cost of latency.
Underneath all that, the cluster states are backed by the &lt;a href="https://en.wikipedia.org/wiki/Raft_%28algorithm%29" target="_blank">raft&lt;/a>-based, strongly consistent, &lt;a href="https://etcd.io/" target="_blank">etcd&lt;/a> key/value store, which explains this behavior.&lt;/p>
&lt;p>Here is the definition of the nodes:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">locals&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> control_plane_nodes &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> for i in range(3) : &amp;#34;talos-cp-${i + 1}&amp;#34; &lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">&amp;gt;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> memory_mb &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">2048&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vcpu &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To bootstrap the nodes, in particular the network configuration, we need to pass a few &lt;code>cloud-init&lt;/code> parameters, which we can create with the &lt;a href="https://search.opentofu.org/provider/dmacvicar/libvirt/latest/docs/resources/cloudinit_disk" target="_blank">&lt;code>libvirt_cloudinit_disk&lt;/code>&lt;/a> resource from the libvirt Tofu provider:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_cloudinit_disk&amp;#34; &amp;#34;cp_seed&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> for_each &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">control_plane_nodes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;${each.key}-cloudinit&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> user_data &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">&amp;lt;&amp;lt;-&lt;/span>&lt;span style="color:#66d9ef">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">timezone&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#66d9ef">UTC&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> meta_data &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">&amp;lt;&amp;lt;-&lt;/span>&lt;span style="color:#66d9ef">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">instance&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">-&lt;/span>&lt;span style="color:#66d9ef">id&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#e6db74">${&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">each&lt;/span>.&lt;span style="color:#960050;background-color:#1e0010">key&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">local&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">-&lt;/span>&lt;span style="color:#66d9ef">hostname&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#e6db74">${&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">each&lt;/span>.&lt;span style="color:#960050;background-color:#1e0010">key&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> network_config &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">&amp;lt;&amp;lt;-&lt;/span>&lt;span style="color:#66d9ef">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">version&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ethernets&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">eth0&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">dhcp4&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Convert cloud-init to ISO volume
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_volume&amp;#34; &amp;#34;cp_seed_volume&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> for_each &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">control_plane_nodes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;${each.key}-cloudinit.iso&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pool &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;slow-pool&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> create &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;file://${libvirt_cloudinit_disk.cp_seed[each.key].path}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We also need some disks for our nodes, using the Talos image as a base:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Each node gets its own disk backed by the base image
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_volume&amp;#34; &amp;#34;cp_disk&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> for_each &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">control_plane_nodes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;${each.key}-disk.qcow2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pool &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;mid-pool&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> capacity &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">107374182400&lt;/span>&lt;span style="color:#75715e"> # 100GB
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> backing_store &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> path &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">libvirt_volume&lt;/span>.&lt;span style="color:#66d9ef">talos_base&lt;/span>.&lt;span style="color:#66d9ef">path&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> format &lt;span style="color:#f92672">=&lt;/span> { type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;qcow2&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> target &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> format &lt;span style="color:#f92672">=&lt;/span> { type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;qcow2&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And finally, we can create the VMs themselves, reusing these disks:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_domain&amp;#34; &amp;#34;control_plane&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> for_each &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">control_plane_nodes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">each&lt;/span>.&lt;span style="color:#66d9ef">key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> memory &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">each&lt;/span>.&lt;span style="color:#66d9ef">value&lt;/span>.&lt;span style="color:#66d9ef">memory_mb&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">*&lt;/span> &lt;span style="color:#ae81ff">1024&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vcpu &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">each&lt;/span>.&lt;span style="color:#66d9ef">value&lt;/span>.&lt;span style="color:#66d9ef">vcpu&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> running &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> autostart &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;hvm&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> type_arch &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;x86_64&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> type_machine &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;q35&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cpu &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;host-passthrough&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> devices &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> disks &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> volume &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pool &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">libvirt_volume&lt;/span>.&lt;span style="color:#66d9ef">cp_disk&lt;/span>[&lt;span style="color:#66d9ef">each&lt;/span>.&lt;span style="color:#66d9ef">key&lt;/span>].&lt;span style="color:#66d9ef">pool&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> volume &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">libvirt_volume&lt;/span>.&lt;span style="color:#66d9ef">cp_disk&lt;/span>[&lt;span style="color:#66d9ef">each&lt;/span>.&lt;span style="color:#66d9ef">key&lt;/span>].&lt;span style="color:#66d9ef">name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> target &lt;span style="color:#f92672">=&lt;/span> { dev &lt;span style="color:#f92672">=&lt;/span> &amp;#34;vda&amp;#34;, bus &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;virtio&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> driver &lt;span style="color:#f92672">=&lt;/span> { type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;qcow2&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> device &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;cdrom&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> volume &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pool &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;slow-pool&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> volume &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;${each.key}-cloudinit.iso&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> target &lt;span style="color:#f92672">=&lt;/span> { dev &lt;span style="color:#f92672">=&lt;/span> &amp;#34;sda&amp;#34;, bus &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;sata&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> interfaces &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;network&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model &lt;span style="color:#f92672">=&lt;/span> { type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;virtio&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> network &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> network &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">libvirt_network&lt;/span>.&lt;span style="color:#66d9ef">talos_network&lt;/span>.&lt;span style="color:#66d9ef">name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> wait_for_ip &lt;span style="color:#f92672">=&lt;/span> { timeout &lt;span style="color:#f92672">=&lt;/span> 300, source &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;any&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="worker-nodes">Worker Nodes&lt;/h2>
&lt;p>Similarly, we will deploy 6 nodes which will be used as workers.&lt;/p>
&lt;p>Here is a rough outline of the code, but since it&amp;rsquo;s mostly identical to the control plane, this is a cut down version of the code as it&amp;rsquo;s just more of the same:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">locals&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> worker_nodes &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> for i in range(6) : &amp;#34;talos-worker-${i + 1}&amp;#34; &lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">&amp;gt;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [...]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_cloudinit_disk&amp;#34; &amp;#34;worker_seed&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> for_each &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">worker_nodes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;${each.key}-cloudinit&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [...]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Convert cloud-init to ISO volume
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_volume&amp;#34; &amp;#34;worker_seed_volume&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> for_each &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">worker_nodes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;${each.key}-cloudinit.iso&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [...]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># main worker VM disks
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_volume&amp;#34; &amp;#34;worker_disk&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> for_each &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">worker_nodes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [...]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_domain&amp;#34; &amp;#34;workers&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> for_each &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">worker_nodes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [...]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="remember-remember-the-ips-of-our-cluster">Remember Remember, The IPs Of Our Cluster&lt;/h2>
&lt;p>Once we have our VMs, it would be nice to not have to go fishing for the IPs of our cluster nodes.&lt;/p>
&lt;p>So, through a simple template like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-tpl" data-lang="tpl">&lt;span style="display:flex;">&lt;span>export CONTROL_PLANE_IP=($&lt;span style="color:#75715e">{&lt;/span>&lt;span style="color:#a6e22e">join&lt;/span>&lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#e6db74">&amp;#34; &amp;#34;&lt;/span>&lt;span style="color:#f92672">,&lt;/span> &lt;span style="color:#f92672">[&lt;/span>&lt;span style="color:#a6e22e">for&lt;/span> &lt;span style="color:#a6e22e">ip&lt;/span> &lt;span style="color:#a6e22e">in&lt;/span> &lt;span style="color:#a6e22e">control_plane_ips&lt;/span> &lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#e6db74">&amp;#34;${&amp;#34;&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>&lt;span style="color:#75715e">}&lt;/span>$&lt;span style="color:#75715e">{&lt;/span>&lt;span style="color:#a6e22e">ip&lt;/span>&lt;span style="color:#75715e">}&lt;/span>$&lt;span style="color:#75715e">{&lt;/span>&lt;span style="color:#e6db74">&amp;#34;\&amp;#34;&amp;#34;&lt;/span>&lt;span style="color:#75715e">}&lt;/span>&amp;#34;])})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export WORKER_IP=($&lt;span style="color:#75715e">{&lt;/span>&lt;span style="color:#a6e22e">join&lt;/span>&lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#e6db74">&amp;#34; &amp;#34;&lt;/span>&lt;span style="color:#f92672">,&lt;/span> &lt;span style="color:#f92672">[&lt;/span>&lt;span style="color:#a6e22e">for&lt;/span> &lt;span style="color:#a6e22e">ip&lt;/span> &lt;span style="color:#a6e22e">in&lt;/span> &lt;span style="color:#a6e22e">worker_ips&lt;/span> &lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#e6db74">&amp;#34;${&amp;#34;&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>&lt;span style="color:#75715e">}&lt;/span>$&lt;span style="color:#75715e">{&lt;/span>&lt;span style="color:#a6e22e">ip&lt;/span>&lt;span style="color:#75715e">}&lt;/span>$&lt;span style="color:#75715e">{&lt;/span>&lt;span style="color:#e6db74">&amp;#34;\&amp;#34;&amp;#34;&lt;/span>&lt;span style="color:#75715e">}&lt;/span>&amp;#34;])})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export CONTROL_PLANE_VIP=&amp;#34;$&lt;span style="color:#75715e">{&lt;/span>&lt;span style="color:#a6e22e">control_plane_vip&lt;/span>&lt;span style="color:#75715e">}&lt;/span>&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And a bit of OpenTofu code leveraging the &lt;a href="https://search.opentofu.org/provider/dmacvicar/libvirt/latest/docs/datasources/domain_interface_addresses" target="_blank">data.libvirt_domain_interface_addresses&lt;/a> Tofu datasource:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Recovery of the IPs
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># might need some modification (FIXME hard coded indices are sketchy)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">locals&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> provider_talos_cp_ips &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> &lt;span style="color:#66d9ef">k&lt;/span> &lt;span style="color:#66d9ef">in&lt;/span> &lt;span style="color:#66d9ef">keys&lt;/span>(&lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">control_plane_nodes&lt;/span>) &lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> k &lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">&amp;gt;&lt;/span> &lt;span style="color:#66d9ef">try&lt;/span>(&lt;span style="color:#66d9ef">data&lt;/span>.&lt;span style="color:#66d9ef">libvirt_domain_interface_addresses&lt;/span>.&lt;span style="color:#66d9ef">control_plane&lt;/span>[&lt;span style="color:#66d9ef">k&lt;/span>].&lt;span style="color:#66d9ef">interfaces&lt;/span>[&lt;span style="color:#ae81ff">1&lt;/span>].&lt;span style="color:#66d9ef">addrs&lt;/span>[&lt;span style="color:#ae81ff">0&lt;/span>].&lt;span style="color:#66d9ef">addr&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> talos_worker_ips &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> &lt;span style="color:#66d9ef">k&lt;/span> &lt;span style="color:#66d9ef">in&lt;/span> &lt;span style="color:#66d9ef">keys&lt;/span>(&lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">worker_nodes&lt;/span>) &lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> k &lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">&amp;gt;&lt;/span> &lt;span style="color:#66d9ef">try&lt;/span>(&lt;span style="color:#66d9ef">data&lt;/span>.&lt;span style="color:#66d9ef">libvirt_domain_interface_addresses&lt;/span>.&lt;span style="color:#66d9ef">workers&lt;/span>[&lt;span style="color:#66d9ef">k&lt;/span>].&lt;span style="color:#66d9ef">interfaces&lt;/span>[&lt;span style="color:#ae81ff">1&lt;/span>].&lt;span style="color:#66d9ef">addrs&lt;/span>[&lt;span style="color:#ae81ff">0&lt;/span>].&lt;span style="color:#66d9ef">addr&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># We also define a VIP for the cluster, to more easily access it in the future.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">variable&lt;/span> &lt;span style="color:#e6db74">&amp;#34;control_plane_vip&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> description &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Virtual IP for the Talos/Kubernetes control plane API&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> default &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;192.168.100.10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Use the template and the IPs to generate the talos-env.sh inventory file
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;local_file&amp;#34; &amp;#34;env&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">templatefile&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;${path.module}/env.tpl&amp;#34;&lt;/span>, {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> control_plane_ips &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">provider_talos_cp_ips&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> worker_ips &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">talos_worker_ips&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> control_plane_vip &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">var&lt;/span>.&lt;span style="color:#66d9ef">control_plane_vip&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> filename &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;.//talos-env.sh&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> file_permission &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;0644&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We can let Tofu create an env file which once sourced, will provide convenient variables for the rest of this setup.&lt;/p>
&lt;h2 id="at-last-kubernetes--talos-setup">At Last, Kubernetes &amp;amp; Talos Setup&lt;/h2>
&lt;p>After a while, you should have a bunch of new VMs, booted-up into an unconfigured Talos.&lt;/p>
&lt;p>If you use virt-manager, you should see a dashboard like that in the first tty of each VM:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/talos-k8s/talos-unconfigured.png"
 alt="Talos dashboard on an unconfigured node: TYPE unknown, STAGE maintenance, CLUSTER n/a">&lt;figcaption>
 &lt;p>Fresh Talos VM in maintenance mode before apply-config&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>Note that on all nodes, currently, &lt;code>Type&lt;/code> is &lt;code>unknown&lt;/code>, &lt;code>Stage&lt;/code> is &lt;code>Maintenance&lt;/code>, &lt;code>Cluster&lt;/code> and various other parameters are &lt;code>n/a&lt;/code>.&lt;/p>
&lt;p>This means that right now, we have just a bunch of individual nodes not talking to each other.&lt;/p>
&lt;p>Also note that the cluster is in a vulnerable state at this point, with anybody able to take control of your freshly provisioned nodes.&lt;/p>
&lt;p>We must configure all the nodes in order to have a properly functioning and secured k8s cluster.&lt;/p>
&lt;h3 id="generating-the-cluster-config">Generating The Cluster Config&lt;/h3>
&lt;p>So, the first step is to generate a configuration auto-magically using &lt;code>talosctl gen config&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Set some variables&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>source ./talos-env.sh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>CLUSTER_NAME&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;kakwalab-talos-cluster&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>BOOTSTRAP_CP&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>CONTROL_PLANE_IP[0]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TEMP_ENDPOINT&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;https://&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>BOOTSTRAP_CP&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">:6443&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>talosctl gen config &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>CLUSTER_NAME&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>TEMP_ENDPOINT&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This creates a base configuration consisting of &lt;code>controlplane.yaml&lt;/code>, &lt;code>worker.yaml&lt;/code>, and &lt;code>talosconfig&lt;/code>.&lt;/p>
&lt;p>And for the most part, these mainly serve to create mTLS key pairs (the Certificate Authority) for authentication. And they also configure some internal network stuff for the cluster.&lt;/p>
&lt;h3 id="configuration-bootstrapping">Configuration bootstrapping&lt;/h3>
&lt;p>Apply the generated config to all nodes (control planes first, then workers), then bootstrap the cluster once:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># If not already done&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>source ./talos-env.sh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Apply control planes configuration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> ip in &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>CONTROL_PLANE_IP[@]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>; &lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> talosctl apply-config --insecure --nodes &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>ip&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> --file controlplane.yaml
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Apply workers configuration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> ip in &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>WORKER_IP[@]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>; &lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> talosctl apply-config --insecure --nodes &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>ip&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> --file worker.yaml
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Wait for nodes to come back up&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sleep &lt;span style="color:#ae81ff">120&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Bootstrap (only once, on the first control plane)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>talosctl bootstrap --endpoints &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>BOOTSTRAP_CP&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> --nodes &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>BOOTSTRAP_CP&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>While nodes restart and synchronize after &lt;code>apply-config&lt;/code>, the nodes could stay in booting state for a few minutes:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/talos-k8s/talos-worker-booting.png"
 alt="Talos worker dashboard during boot: STAGE booting, Kubernetes components not yet healthy">&lt;figcaption>
 &lt;p>Worker node still coming up after configuration&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>After the bootstrap, check the cluster health, and fetch the &lt;code>kubeconfig&lt;/code> file for &lt;code>kubectl&lt;/code> once it&amp;rsquo;s healthy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>talosctl health --endpoints &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>BOOTSTRAP_CP&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> --nodes &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>BOOTSTRAP_CP&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --control-plane-nodes &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>IFS&lt;span style="color:#f92672">=&lt;/span>,; echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>CONTROL_PLANE_IP[*]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --worker-nodes &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>IFS&lt;span style="color:#f92672">=&lt;/span>,; echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>WORKER_IP[*]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --wait-timeout 5m
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>talosctl kubeconfig . --endpoints &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>BOOTSTRAP_CP&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> --nodes &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>BOOTSTRAP_CP&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>On a control plane node TTY, a healthy dashboard looks like this:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/talos-k8s/talos-cp-ok.png"
 alt="Talos dashboard: control plane talos-cp-1 running, READY true, kubelet and API server healthy">&lt;figcaption>
 &lt;p>Healthy control plane after bootstrap&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>Workers should show a similar status in the dashboard:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/talos-k8s/talos-worker-ok.png"
 alt="Talos dashboard: healthy worker node with kubelet and container runtime ready">&lt;figcaption>
 &lt;p>Healthy worker node&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>From there, you should be able to run kubectl, and see your cluster:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>export KUBECONFIG&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>pwd&lt;span style="color:#66d9ef">)&lt;/span>/kubeconfig
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl get nodes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>NAME STATUS ROLES AGE VERSION
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>talos-cp-1 Ready control-plane 65m v1.35.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>talos-cp-2 Ready control-plane 65m v1.35.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>talos-cp-3 Ready control-plane 65m v1.35.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>talos-worker-1 Ready &amp;lt;none&amp;gt; 64m v1.35.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>talos-worker-2 Ready &amp;lt;none&amp;gt; 64m v1.35.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>talos-worker-3 Ready &amp;lt;none&amp;gt; 64m v1.35.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>talos-worker-4 Ready &amp;lt;none&amp;gt; 64m v1.35.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>talos-worker-5 Ready &amp;lt;none&amp;gt; 64m v1.35.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>talos-worker-6 Ready &amp;lt;none&amp;gt; 64m v1.35.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="vip-for-kubectl">VIP for kubectl&lt;/h3>
&lt;p>It&amp;rsquo;s optional, but let&amp;rsquo;s add a virtual IP on the control plane nodes to ease management.&lt;/p>
&lt;p>First, let&amp;rsquo;s patch the control plane node configuration to use this VIP:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> ip in &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>CONTROL_PLANE_IP[@]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>; &lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> talosctl patch mc --endpoints &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>ip&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> --nodes &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>ip&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> --patch &lt;span style="color:#e6db74">&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">machine:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> network:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> interfaces:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> - interface: eth0
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> dhcp: true
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> vip:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> ip: &lt;/span>&lt;span style="color:#e6db74">${&lt;/span>CONTROL_PLANE_VIP&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After VIP election, we can then set the control plane endpoint to it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> ip in &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>CONTROL_PLANE_IP[@]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>; &lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> talosctl patch mc --endpoints &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>ip&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> --nodes &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>ip&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> --patch &lt;span style="color:#e6db74">&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">cluster:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> controlPlane:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> endpoint: https://&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>CONTROL_PLANE_VIP&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">:6443
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And grab an updated kubeconfig&lt;/p>
&lt;pre tabindex="0">&lt;code>talosctl kubeconfig . --endpoints &amp;#34;${BOOTSTRAP_CP}&amp;#34; --nodes &amp;#34;${BOOTSTRAP_CP}&amp;#34;
&lt;/code>&lt;/pre>&lt;h1 id="not-so-optional-kubernetes-add-ons">(Not So) Optional Kubernetes Add-Ons&lt;/h1>
&lt;h2 id="metallb">MetalLB&lt;/h2>
&lt;p>In the cloud, Kubernetes leverages the providers&amp;rsquo; load balancer services such as NLB on AWS. But that is not something we have at home.&lt;/p>
&lt;p>&lt;a href="https://metallb.io/" target="_blank">MetalLB&lt;/a> fills that gap for on-premises deployments. In a simple deployment, it watches &lt;code>LoadBalancer&lt;/code> services and assigns addresses from a pool you define to be used by said &lt;code>LoadBalancer&lt;/code> services.&lt;/p>
&lt;p>First we need a bit of configuration to tweak the MetalLB namespace permissions (aka PSS/PSA or Pod Security Standards).&lt;/p>
&lt;p>&lt;code>metallb-namespace.yaml&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># PSA: MetalLB speaker/FRR need NET_ADMIN, hostNetwork, etc. — not compatible with restricted.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">apiVersion&lt;/span>: &lt;span style="color:#ae81ff">v1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">kind&lt;/span>: &lt;span style="color:#ae81ff">Namespace&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metadata&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">metallb&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">labels&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">pod-security.kubernetes.io/enforce&lt;/span>: &lt;span style="color:#ae81ff">privileged&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">pod-security.kubernetes.io/audit&lt;/span>: &lt;span style="color:#ae81ff">privileged&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">pod-security.kubernetes.io/warn&lt;/span>: &lt;span style="color:#ae81ff">privileged&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And from there, we need to declare the range of IPs we let MetalLB to play with:&lt;/p>
&lt;p>&lt;code>metallb-lan.yaml&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Apply after the MetalLB Helm release is healthy. Namespace must match the chart install.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">apiVersion&lt;/span>: &lt;span style="color:#ae81ff">metallb.io/v1beta1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">kind&lt;/span>: &lt;span style="color:#ae81ff">IPAddressPool&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metadata&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">lan-pool&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">namespace&lt;/span>: &lt;span style="color:#ae81ff">metallb&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">spec&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">addresses&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">192.168.1.48&lt;/span>&lt;span style="color:#ae81ff">/28&lt;/span> &lt;span style="color:#75715e"># Modify with your own IPs (be cautious about collision, especially with DHCP ranges)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">apiVersion&lt;/span>: &lt;span style="color:#ae81ff">metallb.io/v1beta1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">kind&lt;/span>: &lt;span style="color:#ae81ff">L2Advertisement&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metadata&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">lan-pool-l2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">namespace&lt;/span>: &lt;span style="color:#ae81ff">metallb&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">spec&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ipAddressPools&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">lan-pool&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>export KUBECONFIG&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>pwd&lt;span style="color:#66d9ef">)&lt;/span>&lt;span style="color:#e6db74">/kubeconfig&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export METALLB_NAMESPACE&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;metallb&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl apply -f ./metallb-namespace.yaml
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm repo add metallb https://metallb.github.io/metallb --force-update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm repo update metallb
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm upgrade --install metallb metallb/metallb --namespace &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$METALLB_NAMESPACE&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl rollout status deployment/metallb-controller -n &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$METALLB_NAMESPACE&lt;span style="color:#e6db74">&amp;#34;&lt;/span> --timeout&lt;span style="color:#f92672">=&lt;/span>300s
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl rollout status daemonset/metallb-speaker -n &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$METALLB_NAMESPACE&lt;span style="color:#e6db74">&amp;#34;&lt;/span> --timeout&lt;span style="color:#f92672">=&lt;/span>300s
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl apply -f ./metallb-lan.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="traefik-deployment">Traefik Deployment&lt;/h2>
&lt;p>On top of MetalLB we can now deploy &lt;a href="https://traefik.io/" target="_blank">Traefik&lt;/a>, which will handle our load balancing needs, both of TCP/UDP and HTTP directly.&lt;/p>
&lt;p>We also need some permissions on the Traefik namespace, which we will set with a bit of YAML:&lt;/p>
&lt;p>&lt;code>traefik-namespace.yaml&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># PSA baseline: avoids restricted seccomp warnings on Traefik controller without full privileged.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">apiVersion&lt;/span>: &lt;span style="color:#ae81ff">v1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">kind&lt;/span>: &lt;span style="color:#ae81ff">Namespace&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">metadata&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">traefik&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">labels&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">pod-security.kubernetes.io/enforce&lt;/span>: &lt;span style="color:#ae81ff">baseline&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">pod-security.kubernetes.io/audit&lt;/span>: &lt;span style="color:#ae81ff">baseline&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">pod-security.kubernetes.io/warn&lt;/span>: &lt;span style="color:#ae81ff">baseline&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And from there, we configure a fairly generic Helm values for Traefik:&lt;/p>
&lt;p>&lt;code>traefik-helm-values.yaml&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Traefik as cluster ingress (Ingress + IngressRoute CRD).&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">ingressClass&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">enabled&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">isDefaultClass&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">service&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">annotations&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">metallb.universe.tf/address-pool&lt;/span>: &lt;span style="color:#ae81ff">lan-pool&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">spec&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">type&lt;/span>: &lt;span style="color:#ae81ff">LoadBalancer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">providers&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">kubernetesCRD&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">enabled&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">kubernetesIngress&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">enabled&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Fill Ingress status.loadBalancer so ExternalDNS (and clients) see the Traefik LB IP/hostname.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">publishedService&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">enabled&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">pathOverride&lt;/span>: &lt;span style="color:#ae81ff">traefik/traefik&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">deployment&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">replicas&lt;/span>: &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We can then use these to create the Traefik namespace:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>export KUBECONFIG&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;./kubeconfig&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export TRAEFIK_NAMESPACE&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;traefik&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl apply -f ./traefik-namespace.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And finally add the Helm chart repository and install or upgrade Traefik:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>helm repo add traefik https://traefik.github.io/traefik-helm-chart --force-update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm repo update traefik
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm upgrade --install traefik traefik/traefik &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --namespace &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$TRAEFIK_NAMESPACE&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> -f ./traefik-helm-values.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After the controller is up, check that the load balancer received an address from your pool:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kubectl -n &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$TRAEFIK_NAMESPACE&lt;span style="color:#e6db74">&amp;#34;&lt;/span> get svc
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>From there, standard &lt;code>Ingress&lt;/code> objects with &lt;code>ingressClassName: traefik&lt;/code> (and optional Traefik annotations) are what connect hostnames to workloads—exactly what the first application chart below relies on.&lt;/p>
&lt;h2 id="dns-management-externaldns">DNS Management (ExternalDNS)&lt;/h2>
&lt;p>Thanks to Traefik, we have exposed and load-balanced our service, but usually you also want to give it &lt;strong>names&lt;/strong> in DNS. While it could be managed separately from Kubernetes, it is nicer to have the cluster own those records and declare the desired hostname next to the &lt;code>Ingress&lt;/code> in Helm. The usual tool for that is &lt;a href="https://kubernetes-sigs.github.io/external-dns/latest/" target="_blank">ExternalDNS&lt;/a>, which can integrate with many DNS providers (Route53, Gandi, OVH, and so on).&lt;/p>
&lt;p>In my case, I use the generic RFC2136/TSIG integration to let it manage records in a private &lt;code>int.kakwalab.ovh&lt;/code> zone.&lt;/p>
&lt;p>To enable ExternalDNS, start from a Helm values template with the parameters your server needs (zone name, key algorithm, DNS server IP and port, and so on):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">provider&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">rfc2136&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Replace with your DNS server&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">extraArgs&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - --&lt;span style="color:#ae81ff">rfc2136-host=192.168.1.25&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - --&lt;span style="color:#ae81ff">rfc2136-port=5353&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - --&lt;span style="color:#ae81ff">rfc2136-zone=int.kakwalab.ovh&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - --&lt;span style="color:#ae81ff">rfc2136-tsig-secret-alg=hmac-sha512&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">env&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">EXTERNAL_DNS_RFC2136_TSIG_SECRET&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">valueFrom&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">secretKeyRef&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">external-dns-rfc2136&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">key&lt;/span>: &lt;span style="color:#ae81ff">rfc2136-tsig-secret&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">EXTERNAL_DNS_RFC2136_TSIG_KEYNAME&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">valueFrom&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">secretKeyRef&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">external-dns-rfc2136&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">key&lt;/span>: &lt;span style="color:#ae81ff">rfc2136-tsig-keyname&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">txtOwnerId&lt;/span>: &lt;span style="color:#ae81ff">talos-home-tf&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Replace with your DNS zone&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">domainFilters&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">int.kakwalab.ovh&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">sources&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">service&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">ingress&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">policy&lt;/span>: &lt;span style="color:#ae81ff">upsert-only&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">logLevel&lt;/span>: &lt;span style="color:#ae81ff">info&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">interval&lt;/span>: &lt;span style="color:#ae81ff">1m&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">rbac&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">create&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">resources&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">requests&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">cpu&lt;/span>: &lt;span style="color:#ae81ff">50m&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">memory&lt;/span>: &lt;span style="color:#ae81ff">64Mi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">limits&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">memory&lt;/span>: &lt;span style="color:#ae81ff">128Mi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>From there, prepare a namespace and a secret with your TSIG key:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>export KUBECONFIG&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;./kubeconfig&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export EXTERNAL_DNS_NAMESPACE&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;external-dns&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># TSIG credentials (replace with your own)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>DNS_TSIG_KEY_SECRET&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;LTEzODcyLTE0NzIxCgLTEzODcyLTE0NzIxCgLTEzODcyLTE0NzIxCgQ==&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TSIG_KEYNAME&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;sec1_key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Create namespace for ExternalDNS&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl create namespace &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$EXTERNAL_DNS_NAMESPACE&lt;span style="color:#e6db74">&amp;#34;&lt;/span> --dry-run&lt;span style="color:#f92672">=&lt;/span>client -o yaml | kubectl apply -f -
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Create the service secrets&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl -n &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$EXTERNAL_DNS_NAMESPACE&lt;span style="color:#e6db74">&amp;#34;&lt;/span> create secret generic external-dns-rfc2136 &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --from-literal&lt;span style="color:#f92672">=&lt;/span>rfc2136-tsig-secret&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>$DNS_TSIG_KEY_SECRET&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --from-literal&lt;span style="color:#f92672">=&lt;/span>rfc2136-tsig-keyname&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>$TSIG_KEYNAME&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --dry-run&lt;span style="color:#f92672">=&lt;/span>client -o yaml | kubectl apply -f -
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And install with Helm:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Add Helm Repository&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/ &amp;gt;/dev/null
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm repo update external-dns &amp;gt;/dev/null
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Install it, using the upstream helm template and our configuration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm upgrade --install external-dns external-dns/external-dns &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --namespace &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$EXTERNAL_DNS_NAMESPACE&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> -f &lt;span style="color:#e6db74">&amp;#34;./external-dns-helm-values.yaml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="a-few-closing-items">A Few Closing Items&lt;/h1>
&lt;h2 id="deploying-a-private-docker-registry">Deploying A Private Docker Registry&lt;/h2>
&lt;p>To store and distribute our app container image, I&amp;rsquo;ve also deployed a private &lt;code>Docker Registry&lt;/code> on the utility VM.&lt;/p>
&lt;p>The deployment is done through this Ansible &lt;a href="https://github.com/kakwa/home.tf/tree/main/ansible/roles/docker_registry" target="_blank">&lt;code>docker_registry/&lt;/code>&lt;/a> role.&lt;/p>
&lt;p>The gist being that it deploys a &lt;a href="https://github.com/kakwa/misc-pkg/tree/main/docker-registry" target="_blank">&lt;code>docker-registry&lt;/code>&lt;/a> package I&amp;rsquo;ve built, configures plus enables the service and puts an nginx in front of it for Auth Basic and TLS.&lt;/p>
&lt;p>For TLS, it uses a proper Let&amp;rsquo;s Encrypt certificate as loading a custom, self-signed, CA in Talos seems tedious.&lt;/p>
&lt;p>As it&amp;rsquo;s a non-public service, the certificate was obtained using the &lt;a href="https://github.com/kakwa/home.tf/tree/main/ansible/roles/certbot_letsencrypt" target="_blank">DNS-01 challenge&lt;/a> with certbot&amp;rsquo;s &lt;a href="https://certbot-dns-ovh.readthedocs.io/en/stable/" target="_blank">OVH plugin&lt;/a> (my DNS provider).&lt;/p>
&lt;p>For our app, this is where our Kubernetes cluster will download its container images&lt;/p>
&lt;h2 id="our-first-app">Our First App!&lt;/h2>
&lt;p>Now that we have a cluster and its surrounding infrastructure, let&amp;rsquo;s finally do something useful.&lt;/p>
&lt;p>As it happened to be, I was getting a bit annoyed by the prevalence of AI posts in my Hacker News RSS feed.&lt;/p>
&lt;p>Through a bit of Q&amp;amp;D vibe-coding on &lt;a href="https://github.com/hnrss/hnrss" target="_blank">hnrss&lt;/a> doing some keyword matching (words like &lt;code>ai&lt;/code>, &lt;code>llm&lt;/code>, &lt;code>anthropic&lt;/code>&amp;hellip;), I&amp;rsquo;ve created a &lt;a href="https://github.com/kakwa/hnrss-ai-filtering" target="_blank">fork&lt;/a> adding &lt;code>/ai&lt;/code> and &lt;code>/noai&lt;/code> endpoints separating AI and non AI content.&lt;/p>
&lt;p>This filtering works great, but now, we must host this thing, and well&amp;hellip; we have a brand new Kubernetes cluster.&lt;/p>
&lt;p>This service is actually kind of ideal for k8s since it doesn&amp;rsquo;t have a persistent layer.&lt;/p>
&lt;p>In addition to the filtering mods, I&amp;rsquo;ve added the bits necessary for Kubernetes, namely:&lt;/p>
&lt;ul>
&lt;li>a &lt;a href="https://github.com/kakwa/hnrss-ai-filtering/blob/master/Dockerfile" target="_blank">Dockerfile&lt;/a> to create a container,&lt;/li>
&lt;li>a small &lt;a href="https://github.com/kakwa/hnrss-ai-filtering/blob/master/scripts/publish-image.sh" target="_blank">script to publish&lt;/a> our container image into our registry&lt;/li>
&lt;li>a minimal &lt;a href="https://github.com/kakwa/hnrss-ai-filtering/tree/master/helm" target="_blank">Helm Chart&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>To deploy it on our cluster, do the following.&lt;/p>
&lt;p>Clone the repository and enter it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>git clone https://github.com/kakwa/hnrss-ai-filtering.git
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd hnrss-ai-filtering
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Set a few env vars for docker and kubectl/helm:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>export REGISTRY_USER&lt;span style="color:#f92672">=&lt;/span>R_USER
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export REGISTRY_PASSWORD&lt;span style="color:#f92672">=&lt;/span>R_PASSWORD
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export REGISTRY_HOST&lt;span style="color:#f92672">=&lt;/span>registry.example.com
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export REGISTRY&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>REGISTRY_HOST&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export KUBECONFIG&lt;span style="color:#f92672">=&lt;/span>$HOME/.kubeconfig
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Publishing a new version of the docker image in our registry:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>./scripts/publish-image.sh
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Creating or updating the service definition:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Create a namespace for the service (if necessary)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl create namespace hnrss
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Go and tweak the helm configuration&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd helm/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>vim values.yaml &lt;span style="color:#75715e"># in particular, tweak the DNS domain.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Apply helm configuration (creation and updates)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm upgrade --install hnrss ./ --namespace hnrss --set registryAuth.password&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>$REGISTRY_PASSWORD&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Rolling out a new version after publishing in the docker registry:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kubectl rollout restart deployment -n hnrss
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>So, was this exercise worth it?&lt;/p>
&lt;p>&amp;hellip; Well&amp;hellip; kind of&amp;hellip; but not completely.&lt;/p>
&lt;p>For my personal stuff, I&amp;rsquo;m much more in the &amp;ldquo;just use a VPS&amp;rdquo; camp.
Just to illustrate, the hnrss app took me 15 minutes to deploy on my VPS. In k8s, with all the Helm template and Docker stuff, it was closer to two hours (granted, I was also fixing the last cluster setup issues).&lt;/p>
&lt;p>In a professional context, while I do appreciate what K8s brings (clean decoupling between infrastructure and app + easy instrumentation enabling stuff like CI/CD), I just want to remain being a user of the thing, not administer it.&lt;/p>
&lt;p>I like things often considered boring, LDAP and IAM topics for example, but here, I think I&amp;rsquo;ve reached my limits. There are simply too many moving pieces and too much magic glue going on. Kubernetes does solve problems, but it really feels like a tedious and clunky solution. To the point I&amp;rsquo;m wondering if something a bit more opinionated, complete out of the box and simpler will not pop-up and replace it in the future.&lt;/p>
&lt;p>Just to illustrate, this already lengthy deployment is in fact far from complete. From the goals I set out at the beginning, I&amp;rsquo;m still missing the &lt;strong>CI/CD&lt;/strong> part (for example &lt;a href="https://argo-cd.readthedocs.io/en/stable/" target="_blank">ArgoCD&lt;/a>). And beyond that, there are many other subjects which would need to be tackled in a production &amp;amp; non-solo environment:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>RBAC and access control&lt;/strong>, like described in &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/" target="_blank">Kubernetes RBAC&lt;/a> and &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/" target="_blank">authentication&lt;/a> (OIDC, webhooks, group claims).&lt;/li>
&lt;li>&lt;strong>Management UI&lt;/strong>, for example &lt;a href="https://headlamp.dev/" target="_blank">Headlamp&lt;/a>&lt;/li>
&lt;li>&lt;strong>Monitoring&lt;/strong>, probably levering &lt;a href="https://prometheus.io/" target="_blank">Prometheus&lt;/a>&lt;/li>
&lt;li>&lt;strong>Logging&lt;/strong>: probably leveraging &lt;a href="https://www.fluentd.org/" target="_blank">Fluentd&lt;/a> for log forwarding, and either &lt;a href="https://www.elastic.co/elasticsearch/" target="_blank">Elasticsearch&lt;/a> or &lt;a href="https://clickhouse.com/" target="_blank">ClickHouse&lt;/a> for indexing.&lt;/li>
&lt;li>&lt;strong>Persistent volumes&lt;/strong>, could for example leverage &lt;a href="https://kubernetes.io/docs/concepts/storage/volumes/#iscsi" target="_blank">iSCSI volumes&lt;/a>&lt;/li>
&lt;li>&lt;strong>Project templates&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>But it was not lost time either. Even if right now, I&amp;rsquo;m kind of fed up with it, I will probably revisit it in the future and do the CI/CD stuff (easily bootstrapping a new service is simply too appealing).&lt;/p>
&lt;p>It also gave me an appreciation for Kubernetes operators. Now I get why it&amp;rsquo;s a specialized and hard-to-fill role.&lt;/p>
&lt;p>And lastly, discovering what you want and &lt;strong>don&amp;rsquo;t&lt;/strong> want to do before fully committing to it is always valuable.
Managing Kubernetes clusters all day long is simply not for me.&lt;/p>
&lt;h2 id="relevant-links">Relevant Links&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://kubernetes.io/docs/home/" target="_blank">Kubernetes Documentation&lt;/a>: The star of the show.&lt;/li>
&lt;li>&lt;a href="https://www.talos.dev/" target="_blank">Talos Linux&lt;/a>: API-driven, immutable OS for Kubernetes.&lt;/li>
&lt;li>&lt;a href="https://docs.siderolabs.com" target="_blank">Talos Documentation&lt;/a>: Official documentation for Talos.&lt;/li>
&lt;li>&lt;a href="https://factory.talos.dev/" target="_blank">Talos Image Factory&lt;/a>: Custom Talos images builder.&lt;/li>
&lt;li>&lt;a href="https://opentofu.org/" target="_blank">OpenTofu&lt;/a>: Open-source infrastructure as code tool (Terraform Fork).&lt;/li>
&lt;li>&lt;a href="https://kubernetes.io/docs/reference/kubectl/" target="_blank">kubectl&lt;/a>: CLI tool for k8s administration.&lt;/li>
&lt;li>&lt;a href="https://helm.sh/" target="_blank">Helm&lt;/a>: &amp;ldquo;package manager&amp;rdquo; &amp;amp; service specification for k8s&lt;/li>
&lt;li>&lt;a href="https://search.opentofu.org/provider/dmacvicar/libvirt/latest" target="_blank">OpenTofu libvirt provider&lt;/a>: KVM/libvirt provider for Tofu.&lt;/li>
&lt;li>&lt;a href="https://search.opentofu.org/provider/siderolabs/talos/latest" target="_blank">OpenTofu Talos provider&lt;/a>: Talos provider for Tofu.&lt;/li>
&lt;li>&lt;a href="https://metallb.io/" target="_blank">MetalLB&lt;/a>: K8S On-prem IPs management add-on for supporting LoadBalancer.&lt;/li>
&lt;li>&lt;a href="https://traefik.io/" target="_blank">Traefik&lt;/a>: K8S LoadBalancer/Service Gateway add-on.&lt;/li>
&lt;li>&lt;a href="https://kubernetes-sigs.github.io/external-dns/latest/" target="_blank">ExternalDNS&lt;/a>: K8S add-ons to manage DNS records.&lt;/li>
&lt;li>&lt;a href="https://argo-cd.readthedocs.io/en/stable/" target="_blank">Argo CD&lt;/a>: GitOps continuous delivery for K8S applications.&lt;/li>
&lt;li>&lt;a href="https://headlamp.dev/" target="_blank">Headlamp&lt;/a>: Management UI for K8s.&lt;/li>
&lt;li>&lt;a href="https://www.fluentd.org/" target="_blank">Fluentd&lt;/a>: Unified logging layer.&lt;/li>
&lt;li>&lt;a href="https://github.com/kakwa/home.tf" target="_blank">home.tf&lt;/a>: The Ansible and OpenTofu code used in this article.&lt;/li>
&lt;/ul></description></item><item><title>Hyperscaler Cloud @Home</title><link>https://technically.kakwalab.ovh/posts/virtualization-terraform-kvm/</link><pubDate>Fri, 17 Apr 2026 12:55:15 +0100</pubDate><author>carpentier.pf@gmail.com (Pierre-François Carpentier)</author><guid>https://technically.kakwalab.ovh/posts/virtualization-terraform-kvm/</guid><description>&lt;h1 id="introduction">Introduction&lt;/h1>
&lt;p>These days, at work, we don&amp;rsquo;t really have to care about server racking, cabling, powering, and the many other *ings required when dealing with hardware.&lt;/p>
&lt;p>Logistic companies like Amazon/AWS have managed to hide it all behind convenient APIs, and frankly, they do it better than we could.&lt;/p>
&lt;p>That&amp;rsquo;s only valid in a professional context however. At home, unless you are &lt;code>/r/wallstreetbets&lt;/code> inclined financially, putting your credit card into Vercel, AWS or GCP is a bit risky for your bank account (see &lt;a href="https://www.reddit.com/r/aws/comments/lbqcos/my_forgotten_account_has_a_20000_bill_how_screwed/" target="_blank">these&lt;/a> &lt;a href="https://www.reddit.com/r/googlecloud/comments/1kimlc8/ddos_98k_firebase_bill_guy_the_billing_support/" target="_blank">billing&lt;/a> &lt;a href="https://www.reddit.com/r/nextjs/comments/1o1zs2u/unexpected_1100_vercel_bill_im_just_an_employee_i/" target="_blank">horror&lt;/a> &lt;a href="https://www.youtube.com/watch?v=ihnGot4nUS4" target="_blank">stories&lt;/a>).&lt;/p>
&lt;p>Homelabing with an old rig in the closet or a fancy 10&amp;quot; rack of refurb mini-PCs often are more sensible options.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/cloud-at-home/meme.jpg"
 alt="meme cloud at home">&lt;figcaption>
 &lt;p>Cloud @Home&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>But at first glance, it comes at a cost: manual setup is in and things are no longer abstracted away&amp;hellip;&lt;/p>
&lt;p>&amp;hellip; or so it seems&amp;hellip;&lt;/p>
&lt;p>In reality, we can actually re-use at home most of the same tools available for the Cloud(™).&lt;/p>
&lt;p>This post will present one of them: &lt;strong>Terraform/Tofu&lt;/strong> and show how it can manage a basic &lt;code>Libvirtd&lt;/code>/&lt;code>KVM&lt;/code> hypervisor.&lt;/p>
&lt;h1 id="locally-sourced-tofu">Locally Sourced Tofu&lt;/h1>
&lt;p>Making a hypervisor out of a Debian install is extremely easy: install Debian, and run &lt;code>apt install libvirt-daemon&lt;/code>.&lt;/p>
&lt;p>But past that, wouldn&amp;rsquo;t it be nice to be able to manage our &lt;code>libvirtd&lt;/code>/&lt;code>kvm&lt;/code> hypervisor with &lt;code>Tofu&lt;/code>/&lt;code>HCL&lt;/code> like we would on AWS?&lt;/p>
&lt;p>Well, thanks to dmacvicar&amp;rsquo;s &lt;a href="https://search.opentofu.org/provider/dmacvicar/libvirt/latest" target="_blank">libvirt provider&lt;/a>, we can, and here is how.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Notes:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Don&amp;rsquo;t hesitate to play with this code. You can download the &lt;a href="https://technically.kakwalab.ovh/files/kvm-terraform/main.tf">full main.tf&lt;/a>, and with a few tweaks, you should be able to apply it on your own hypervisor.&lt;/li>
&lt;li>To apply this configuration, run &lt;code>tofu init&lt;/code> (once) and &lt;code>tofu apply&lt;/code> as root in the same dir as &lt;code>main.tf&lt;/code>.&lt;/li>
&lt;li>OpenTofu can be installed from the &lt;a href="https://opentofu.org/docs/intro/install/" target="_blank">official website&lt;/a>.&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;h2 id="libvirt-provider-setup">Libvirt Provider Setup&lt;/h2>
&lt;p>First, declare the &lt;code>libvirt&lt;/code> provider and point it at the local hypervisor:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">terraform&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">required_providers&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> libvirt &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;dmacvicar/libvirt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> version &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;~&amp;gt; 0.9.7&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">provider&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> uri &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;qemu:///system&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;strong>Notes&lt;/strong>: &lt;code>qemu:///system&lt;/code> connects to the local hypervisor.
You can also point it at a remote host over SSH with &lt;code>qemu+ssh://&amp;lt;USER&amp;gt;@&amp;lt;HOST&amp;gt;/system&lt;/code>.
But be aware that it could make image management fiddly (local FS vs remote FS).&lt;/p>
&lt;/blockquote>
&lt;h2 id="networks">Networks&lt;/h2>
&lt;p>We probably need some LAN segments to put our VMs in, at least a private network and also, usually, a bridge to our home network.&lt;/p>
&lt;p>The &lt;code>libvirt&lt;/code> provider can manage these networks through its &lt;code>libvirt_network&lt;/code> resources.&lt;/p>
&lt;p>Here is private NATed network declaration:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">locals&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> network_name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;homelab-nat&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> network_cidr &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;192.168.150.0/24&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_network&amp;#34; &amp;#34;MY_NETWORK&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">network_name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> autostart &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> forward &lt;span style="color:#f92672">=&lt;/span> { mode &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;nat&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bridge &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;virbr1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> stp &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;on&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> delay &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ips &lt;span style="color:#f92672">=&lt;/span> [{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> address &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">cidrhost&lt;/span>(&lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">network_cidr&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> netmask &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">cidrnetmask&lt;/span>(&lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">network_cidr&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dhcp &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ranges &lt;span style="color:#f92672">=&lt;/span> [{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> start &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">cidrhost&lt;/span>(&lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">network_cidr&lt;/span>, &lt;span style="color:#ae81ff">50&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> end &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">cidrhost&lt;/span>(&lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">network_cidr&lt;/span>, &lt;span style="color:#ae81ff">254&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Outside of Tofu &amp;amp; libvirt we will also need to create a &lt;code>br0&lt;/code> NIC bridge for direct access to our home network.&lt;/p>
&lt;p>To create it temporarily:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ip link add name br0 type bridge
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ip link set br0 up
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To make it persistent on Debian, edit &lt;code>/etc/network/interfaces&lt;/code> and add something like:&lt;/p>
&lt;pre tabindex="0">&lt;code>auto br0
iface br0 inet dhcp
 bridge_ports eth0
 bridge_stp off
 bridge_fd 0
&lt;/code>&lt;/pre>&lt;p>Replace &lt;code>eth0&lt;/code> with your actual physical NIC.&lt;/p>
&lt;h2 id="storage-pools">Storage Pools&lt;/h2>
&lt;p>Before creating any disks/volumes, we need storage pools first. A pool is just a directory that &lt;code>libvirt&lt;/code> manages.&lt;/p>
&lt;p>At its simplest, a declaration looks like that:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_pool&amp;#34; &amp;#34;MY_DISK_POOL&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;MY_DISK_POOL&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;dir&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> target &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> path &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;/var/lib/libvirt/images/MY_DISK_POOL&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="base-vm-image">Base VM Image&lt;/h2>
&lt;p>We could install the VM using a modified &lt;code>.iso&lt;/code> or with &lt;code>pxe&lt;/code> netboot setup, like we would with bare metal machines.&lt;/p>
&lt;p>But, since we are already past the bare metal, a more convenient option is to directly download and use official images from &lt;a href="https://cloud.debian.org/images/cloud/" target="_blank">Debian&lt;/a>, &lt;a href="https://download.rockylinux.org/pub/rocky/10/images/x86_64/" target="_blank">Rocky&lt;/a> or your preferred distribution.&lt;/p>
&lt;p>By just pointing at the URL, the &lt;code>libvirt&lt;/code> &lt;code>Tofu&lt;/code> module can download and register this base image in our hypervisor&amp;rsquo;s libvirt:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">locals&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> debian_image_url &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;https://cloud.debian.org/images/cloud/trixie/latest/debian-13-generic-amd64.qcow2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_volume&amp;#34; &amp;#34;debian_base&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;debian-base.qcow2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pool &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">libvirt_pool&lt;/span>.&lt;span style="color:#66d9ef">MY_DISK_POOL&lt;/span>.&lt;span style="color:#66d9ef">name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> create &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">debian_image_url&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> target &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> format &lt;span style="color:#f92672">=&lt;/span> { type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;qcow2&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And from there, with the &lt;code>backing_store&lt;/code> directive, this image can be used as the foundation for multiple VMs.&lt;/p>
&lt;h2 id="cloud-init">Cloud Init&lt;/h2>
&lt;p>Cloud images usually ship with &lt;a href="https://docs.cloud-init.io/en/latest/index.html" target="_blank">cloud-init&lt;/a>, which we can leverage to bootstrap the VMs.&lt;/p>
&lt;p>At this stage, it&amp;rsquo;s advisable to do as little as possible, but we will typically want to configure:&lt;/p>
&lt;ul>
&lt;li>some network settings&lt;/li>
&lt;li>a &lt;a href="https://docs.saltproject.io/en/latest/contents.html" target="_blank">Salt&lt;/a>/&lt;a href="https://help.puppet.com/core/current/Content/PuppetCore/puppet_index.htm" target="_blank">Puppet&lt;/a>/&lt;a href="https://docs.chef.io/" target="_blank">Chef&lt;/a> agent or some SSH credentials for &lt;a href="https://docs.ansible.com/" target="_blank">Ansible&lt;/a>&lt;/li>
&lt;li>usually, a default account for troubleshooting&lt;/li>
&lt;/ul>
&lt;p>One of the nice feature of this &lt;code>tofu&lt;/code>/&lt;code>libvirt&lt;/code> provider is its ability to create &lt;code>cloud-init&lt;/code> payloads/volumes.&lt;/p>
&lt;p>With &lt;code>tofu apply&lt;/code>, this will create a small (&amp;lt; 1MB) &lt;code>.iso&lt;/code> volume which can be attached to the VM cdrom, and consumed at first boot:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Cloud-init configuration for the YOUR_VM VM
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_cloudinit_disk&amp;#34; &amp;#34;YOUR_VM_seed&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;YOUR_VM-cloudinit&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> user_data &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">&amp;lt;&amp;lt;-&lt;/span>&lt;span style="color:#66d9ef">EOF&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> #cloud-config
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">users&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">-&lt;/span> &lt;span style="color:#66d9ef">name&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#66d9ef">admin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">groups&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> [&lt;span style="color:#66d9ef">sudo&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">shell&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">/&lt;/span>&lt;span style="color:#66d9ef">bin&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">/&lt;/span>&lt;span style="color:#66d9ef">bash&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ssh_authorized_keys&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">-&lt;/span> &lt;span style="color:#66d9ef">ssh&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">-&lt;/span>&lt;span style="color:#66d9ef">ed25519&lt;/span> &lt;span style="color:#66d9ef">AAAAC3&lt;/span>... &lt;span style="color:#66d9ef">your&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">-&lt;/span>&lt;span style="color:#66d9ef">key&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">-&lt;/span>&lt;span style="color:#66d9ef">here&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">write_files&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">-&lt;/span> &lt;span style="color:#66d9ef">path&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">/&lt;/span>&lt;span style="color:#66d9ef">etc&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">/&lt;/span>&lt;span style="color:#66d9ef">sudoers&lt;/span>.&lt;span style="color:#66d9ef">d&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">/&lt;/span>&lt;span style="color:#66d9ef">admin&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">permissions&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">0440&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">content&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> %admin ALL&lt;span style="color:#f92672">=&lt;/span>(&lt;span style="color:#66d9ef">ALL&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>&lt;span style="color:#66d9ef">ALL&lt;/span>) &lt;span style="color:#66d9ef">NOPASSWD&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#66d9ef">ALL&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">chpasswd&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">list&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">root&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>&lt;span style="color:#66d9ef">password&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">expire&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#66d9ef">false&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ssh_pwauth&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">packages&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">-&lt;/span> &lt;span style="color:#66d9ef">openssh&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">-&lt;/span>&lt;span style="color:#66d9ef">server&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">-&lt;/span> &lt;span style="color:#66d9ef">qemu&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">-&lt;/span>&lt;span style="color:#66d9ef">guest&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">-&lt;/span>&lt;span style="color:#66d9ef">agent&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">runcmd&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">-&lt;/span> &lt;span style="color:#66d9ef">systemctl&lt;/span> &lt;span style="color:#66d9ef">enable&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">--&lt;/span>&lt;span style="color:#66d9ef">now&lt;/span> &lt;span style="color:#66d9ef">qemu&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">-&lt;/span>&lt;span style="color:#66d9ef">guest&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">-&lt;/span>&lt;span style="color:#66d9ef">agent&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">timezone&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#66d9ef">UTC&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> meta_data &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">&amp;lt;&amp;lt;-&lt;/span>&lt;span style="color:#66d9ef">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">instance&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">-&lt;/span>&lt;span style="color:#66d9ef">id&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#66d9ef">YOUR_VM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">local&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">-&lt;/span>&lt;span style="color:#66d9ef">hostname&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#66d9ef">YOUR_VM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> network_config &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">&amp;lt;&amp;lt;-&lt;/span>&lt;span style="color:#66d9ef">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">version&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">ethernets&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">enp1s0&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">addresses&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">-&lt;/span> &lt;span style="color:#ae81ff">192&lt;/span>.&lt;span style="color:#ae81ff">168&lt;/span>.&lt;span style="color:#ae81ff">1&lt;/span>.&lt;span style="color:#ae81ff">13&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">/&lt;/span>&lt;span style="color:#ae81ff">24&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">gateway4&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#ae81ff">192&lt;/span>.&lt;span style="color:#ae81ff">168&lt;/span>.&lt;span style="color:#ae81ff">1&lt;/span>.&lt;span style="color:#ae81ff">254&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">nameservers&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">addresses&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">-&lt;/span> &lt;span style="color:#ae81ff">192&lt;/span>.&lt;span style="color:#ae81ff">168&lt;/span>.&lt;span style="color:#ae81ff">1&lt;/span>.&lt;span style="color:#ae81ff">25&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">enp2s0&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">dhcp4&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># create cloud-init to ISO volume
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_volume&amp;#34; &amp;#34;YOUR_VM_seed_volume&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;YOUR_VM-cloudinit.iso&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pool &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">libvirt_pool&lt;/span>.&lt;span style="color:#66d9ef">MY_DISK_POOL&lt;/span>.&lt;span style="color:#66d9ef">name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> create &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;file://${libvirt_cloudinit_disk.YOUR_VM_seed.path}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;strong>Notes&lt;/strong>: we also install and enable &lt;code>qemu-guest-agent&lt;/code> here.
Installing it helps a lot with IP management in the &lt;code>libvirt&lt;/code> provider.&lt;/p>
&lt;/blockquote>
&lt;h2 id="vm-creation">VM Creation&lt;/h2>
&lt;p>Once we have the networks, the pools, the base OS image and some cloud-init configuration, we can at last create the VM itself.&lt;/p>
&lt;p>Let&amp;rsquo;s start with the main disk:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_volume&amp;#34; &amp;#34;YOUR_VM_disk&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;YOUR_VM-disk.qcow2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pool &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">libvirt_pool&lt;/span>.&lt;span style="color:#66d9ef">MY_DISK_POOL&lt;/span>.&lt;span style="color:#66d9ef">name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> capacity &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">53687091200&lt;/span>&lt;span style="color:#75715e"> # 50GB
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> # Reuse the cloud image we got previously
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> backing_store &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> path &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">libvirt_volume&lt;/span>.&lt;span style="color:#66d9ef">debian_base&lt;/span>.&lt;span style="color:#66d9ef">path&lt;/span>&lt;span style="color:#75715e"> # Base VM Image we downloaded earlier.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> format &lt;span style="color:#f92672">=&lt;/span> { type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;qcow2&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> target &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> format &lt;span style="color:#f92672">=&lt;/span> { type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;qcow2&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And from there, we can actually create the VM:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_domain&amp;#34; &amp;#34;YOUR_VM&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;YOUR_VM&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;kvm&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> memory &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1048576&lt;/span>&lt;span style="color:#75715e"> # 1GB (1024 * 1024)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> vcpu &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> running &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> autostart &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;hvm&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> type_arch &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;x86_64&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> type_machine &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;q35&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> # Weirdly in bios mode, virtio on disks was not working.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> # The VM was not booting completely.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> # So EFI it is.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> firmware &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;efi&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cpu &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;host-passthrough&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> # Necessary for EFI
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> features &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> acpi &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> apic &lt;span style="color:#f92672">=&lt;/span> {}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> devices &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pci &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> { type &lt;span style="color:#f92672">=&lt;/span> &amp;#34;pci&amp;#34;, model &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;pcie-root&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> disks &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> volume &lt;span style="color:#f92672">=&lt;/span> {&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> # our disk
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> pool &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">libvirt_volume&lt;/span>.&lt;span style="color:#66d9ef">YOUR_VM_disk&lt;/span>.&lt;span style="color:#66d9ef">pool&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> volume &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">libvirt_volume&lt;/span>.&lt;span style="color:#66d9ef">YOUR_VM_disk&lt;/span>.&lt;span style="color:#66d9ef">name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> target &lt;span style="color:#f92672">=&lt;/span> { dev &lt;span style="color:#f92672">=&lt;/span> &amp;#34;vda&amp;#34;, bus &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;virtio&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> driver &lt;span style="color:#f92672">=&lt;/span> { type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;qcow2&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> device &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;cdrom&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> volume &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pool &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">libvirt_pool&lt;/span>.&lt;span style="color:#66d9ef">MY_DISK_POOL&lt;/span>.&lt;span style="color:#66d9ef">name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> volume &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">libvirt_volume&lt;/span>.&lt;span style="color:#66d9ef">YOUR_VM_seed_volume&lt;/span>.&lt;span style="color:#66d9ef">name&lt;/span>&lt;span style="color:#75715e"> # Use the cloud-init iso/image we defined earlier
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> target &lt;span style="color:#f92672">=&lt;/span> { dev &lt;span style="color:#f92672">=&lt;/span> &amp;#34;sda&amp;#34;, bus &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;sata&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> # Two network interfaces: bridge br0 + MY_NETWORK
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> interfaces &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;bridge&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model &lt;span style="color:#f92672">=&lt;/span> { type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;virtio&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source &lt;span style="color:#f92672">=&lt;/span> { bridge &lt;span style="color:#f92672">=&lt;/span> { bridge &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;br0&amp;#34;&lt;/span> } }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> wait_for_ip &lt;span style="color:#f92672">=&lt;/span> { timeout &lt;span style="color:#f92672">=&lt;/span> 300, source &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;any&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;network&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> model &lt;span style="color:#f92672">=&lt;/span> { type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;virtio&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> network &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> network &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">libvirt_network&lt;/span>.&lt;span style="color:#66d9ef">MY_NETWORK&lt;/span>.&lt;span style="color:#66d9ef">name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> wait_for_ip &lt;span style="color:#f92672">=&lt;/span> { timeout &lt;span style="color:#f92672">=&lt;/span> 300, source &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;any&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> # Channel for qemu-guest-agent communication between VM and hypervisor
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> # mostly to report back the VM&amp;#39;s IPs
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> channels &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> target &lt;span style="color:#f92672">=&lt;/span> { virt_io &lt;span style="color:#f92672">=&lt;/span> { name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;org.qemu.guest_agent.0&amp;#34;&lt;/span> } }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source &lt;span style="color:#f92672">=&lt;/span> { unix &lt;span style="color:#f92672">=&lt;/span> { mode &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;bind&amp;#34;&lt;/span> } }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> # Useful to get a console without vnc/spice
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> # run &amp;gt; sudo screen `virsh ttyconsole YOUR_VM`
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> consoles &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;pty&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> target &lt;span style="color:#f92672">=&lt;/span> { type &lt;span style="color:#f92672">=&lt;/span> &amp;#34;serial&amp;#34;, port &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;0&amp;#34;&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> # GUI/spice troubleshooting (through virt-manager for example)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> videos &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> { model &lt;span style="color:#f92672">=&lt;/span> { type &lt;span style="color:#f92672">=&lt;/span> &amp;#34;virtio&amp;#34;, heads &lt;span style="color:#f92672">=&lt;/span> 1, primary &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;yes&amp;#34;&lt;/span> } }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> graphics &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> spice &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> autoport &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;no&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> port &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">5930&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> listen &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;127.0.0.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> listeners &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> type &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;address&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> address &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> address &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;127.0.0.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="dns-records">DNS Records&lt;/h2>
&lt;p>Lastly, as it&amp;rsquo;s nicer to deal with proper hostname rather than IP addresses, let&amp;rsquo;s configure a Tofu DNS provider.&lt;/p>
&lt;p>First, you need to configure have a DNS server allowing &lt;strong>RFC 2136&lt;/strong> dynamic updates to a zone.&lt;/p>
&lt;p>I will not detail this configuration much here, but with &lt;code>Bind&lt;/code>/&lt;code>Named&lt;/code>, it could be done like that:&lt;/p>
&lt;p>First, generate a key:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ dnssec-keygen -a HMAC-SHA512 -b &lt;span style="color:#ae81ff">512&lt;/span> -n HOST example.com.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># it creates two files:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ ls K*
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Kexample.com.+165+27879.key Kexample.com.+165+27879.private
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># content&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ cat K*.private
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>...&lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Key: dGVzdC1leGFtcGxlLXRzaWctc2VjcmV0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>...&lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Second, allow the zone to be updated by said key:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">key&lt;/span> &lt;span style="color:#66d9ef">example&lt;/span>.&lt;span style="color:#66d9ef">com&lt;/span>. {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">algorithm&lt;/span> &lt;span style="color:#66d9ef">hmac&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">-&lt;/span>&lt;span style="color:#66d9ef">sha512&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">secret&lt;/span> &lt;span style="color:#e6db74">&amp;#34;odGVzdC1leGFtcGxlLXRzaWctc2VjcmV0&amp;#34;&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;span style="color:#960050;background-color:#1e0010">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">zone&lt;/span> &lt;span style="color:#e6db74">&amp;#34;example.com&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">type&lt;/span> &lt;span style="color:#66d9ef">master&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">file&lt;/span> &lt;span style="color:#e6db74">&amp;#34;/var/lib/bind/db.example.com&amp;#34;&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">allow&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">-&lt;/span>&lt;span style="color:#66d9ef">update&lt;/span> { &lt;span style="color:#66d9ef">key&lt;/span> &lt;span style="color:#e6db74">&amp;#34;example.com.&amp;#34;&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">;&lt;/span> }&lt;span style="color:#960050;background-color:#1e0010">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;span style="color:#960050;background-color:#1e0010">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And check that the &lt;code>bind&lt;/code> user is able to write the &lt;code>/var/lib/bind/&lt;/code> directory and the zone file.&lt;/p>
&lt;p>Once you have your DNS server setup, you can again use Tofu and its &lt;a href="https://search.opentofu.org/provider/hashicorp/dns/latest/docs" target="_blank">&lt;code>dns&lt;/code>&lt;/a> provider to manage the DNS records:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># datasource in the libvirt provider to recover the IP addresses
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">data&lt;/span> &lt;span style="color:#e6db74">&amp;#34;libvirt_domain_interface_addresses&amp;#34; &amp;#34;YOUR_VM&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> domain &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">libvirt_domain&lt;/span>.&lt;span style="color:#66d9ef">YOUR_VM&lt;/span>.&lt;span style="color:#66d9ef">name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;agent&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># set some variables,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># also the libvirt_domain_interface_addresses data might need some filtering there
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># (ex: pick the correct nic, filter out localhost, etc)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">locals&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dns_zone &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;example.com.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> my_hosts &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;your-vm&amp;#34; &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">data&lt;/span>.&lt;span style="color:#66d9ef">libvirt_domain_interface_addresses&lt;/span>.&lt;span style="color:#66d9ef">YOUR_VM&lt;/span>.&lt;span style="color:#66d9ef">interfaces&lt;/span>[&lt;span style="color:#ae81ff">2&lt;/span>].&lt;span style="color:#66d9ef">addrs&lt;/span>[&lt;span style="color:#ae81ff">0&lt;/span>].&lt;span style="color:#66d9ef">addr&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">provider&lt;/span> &lt;span style="color:#e6db74">&amp;#34;dns&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">update&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> server &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;192.168.100.100&amp;#34;&lt;/span>&lt;span style="color:#75715e"> # Replace with your DNS server 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> port &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">53&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> key_name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;tofu-homelab.&amp;#34;&lt;/span>&lt;span style="color:#75715e"> # Replace with your zone
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> key_algorithm &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;hmac-sha512&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> key_secret &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;dGVzdC1leGFtcGxlLXRzaWctc2VjcmV0&amp;#34;&lt;/span>&lt;span style="color:#75715e"> # base64 TSIG secret from your nameserver config
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># loop over your VMs
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;dns_a_record_set&amp;#34; &amp;#34;lan&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> for_each &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">my_hosts&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> zone &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">dns_zone&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">each&lt;/span>.&lt;span style="color:#66d9ef">key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> addresses &lt;span style="color:#f92672">=&lt;/span> [&lt;span style="color:#66d9ef">each&lt;/span>.&lt;span style="color:#66d9ef">value&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ttl &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">300&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># CNAME/Alias example
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;dns_cname_record&amp;#34; &amp;#34;alias&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> zone &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">local&lt;/span>.&lt;span style="color:#66d9ef">dns_zone&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;alias-myvm&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cname &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;myvm.${local.dns_zone}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ttl &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">300&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="generating-ansible-inventory">Generating Ansible Inventory&lt;/h2>
&lt;p>The last bit I like to do is to have Tofu generate an Ansible inventory file.&lt;/p>
&lt;p>This enables me to bridge the &lt;code>.tf&lt;/code>/infrastructure creation step and the server configuration step more naturally.&lt;/p>
&lt;p>In terms of implementation, it&amp;rsquo;s nothing fancy, just a basic template leveraging the output of the other resources:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-hcl" data-lang="hcl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">locals&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> your_vm_ip &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">data&lt;/span>.&lt;span style="color:#66d9ef">libvirt_domain_interface_addresses&lt;/span>.&lt;span style="color:#66d9ef">YOUR_VM&lt;/span>.&lt;span style="color:#66d9ef">interfaces&lt;/span>[&lt;span style="color:#ae81ff">2&lt;/span>].&lt;span style="color:#66d9ef">addrs&lt;/span>[&lt;span style="color:#ae81ff">0&lt;/span>].&lt;span style="color:#66d9ef">addr&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">resource&lt;/span> &lt;span style="color:#e6db74">&amp;#34;local_file&amp;#34; &amp;#34;inventory&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">&amp;lt;&amp;lt;-&lt;/span>&lt;span style="color:#66d9ef">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [&lt;span style="color:#66d9ef">debian_vms&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> your-vm ansible_host&lt;span style="color:#f92672">=&lt;/span>${local.your_vm_ip} ansible_user&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">local&lt;/span>.&lt;span style="color:#960050;background-color:#1e0010">debian_admin_user&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> filename &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;../ansible/inventory/inventory.txt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> file_permission &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;0644&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="closing-thoughts">Closing Thoughts&lt;/h1>
&lt;p>Honestly, after struggling a bit (mostly my fault, I was using an old and buggy version for a while, make sure you are at least on &lt;code>v0.9&lt;/code>),
even if it&amp;rsquo;s a little finicky at times, I was surprised how well I managed to get it working.&lt;/p>
&lt;p>Being able to nuke entire stacks and re-create them at will, with just a &lt;code>tofu destroy;tofu apply&lt;/code> is extremely convenient and strangely satisfying.&lt;/p>
&lt;p>Plus, the base hypervisor is extremely simple: just a base headless Debian with &lt;code>libvirtd&lt;/code> installed.&lt;/p>
&lt;p>Sure, I could have installed Proxmox, use one of the &lt;a href="https://search.opentofu.org/provider/bpg/proxmox/latest" target="_blank">Proxmox Tofu Providers&lt;/a>, and have a more AWS/GCP-like experience.
But, in truth, this simple setup is already more than enough to cosplay as your typical &amp;ldquo;latte drinking bigtech/GAFAM SRE/DevSecOps/CloudEng&amp;rdquo;.&lt;/p>
&lt;p>It surely helped me a lot when deploying my @home &lt;a href="https://technically.kakwalab.ovh/posts/talos-k8s">k8s cluster&lt;/a>.&lt;/p>
&lt;p>Once again, don&amp;rsquo;t hesitate to download the full &lt;a href="https://technically.kakwalab.ovh/files/kvm-terraform/main.tf">main.tf&lt;/a>, and play with it.&lt;/p>
&lt;p>Kudos to &lt;a href="https://www.mac-vicar.eu/" target="_blank">Duncan Mac-Vicar P.&lt;/a> for implementing and sharing this provider.&lt;/p></description></item><item><title>PCIe x16 to x1 GPU Adapter - Feels wrong, but does work</title><link>https://technically.kakwalab.ovh/posts/gpu-pciex1/</link><pubDate>Fri, 02 Jan 2026 18:29:53 +0100</pubDate><author>carpentier.pf@gmail.com (Pierre-François Carpentier)</author><guid>https://technically.kakwalab.ovh/posts/gpu-pciex1/</guid><description>&lt;h1 id="why-would-you-do-that">Why Would You Do That?&lt;/h1>
&lt;p>I have an old PC tower lying around that I use as a lab/KVM hypervisor from time to time. The thing is completely headless like a server should&amp;hellip; maybe a bit too headless actually.&lt;/p>
&lt;p>The motherboard doesn&amp;rsquo;t have any video port, and the two PCIEx16 ports are used by some old OCZ Velodrives storage cards, so there is no way to plug a screen.&lt;/p>
&lt;p>To a degree, it&amp;rsquo;s possible to make do with the serial port, but it gets old really fast when trying to troubleshoot pre-grub boot issues or reinstalling the OS.&lt;/p>
&lt;h1 id="the-setup">The Setup&lt;/h1>
&lt;p>I vaguely remembered seeing some mining rigs using PCIe risers and even horror stories like dremeling some of the lines off the cards directly.
So, I tried my luck with one of &lt;a href="https://www.amazon.com/cablecc-Extender-Converter-Extension-Graphics/dp/B0BYNH176D?crid=HG6EBTKK3OS7&amp;amp;dib=eyJ2IjoiMSJ9.EwnOUmfceQIKLekBlQwb7DhmqXkUDZoML0Z-INretXkaww0q6qO7xoX9isxXmQZlCoiKp00o3tYh5-Ip3B0qrKfeQ17aI6t0FqgW8Mh8Abc-3ssOW86xO-AzkEhdJgOQBHJTQ--8qid0rbXw-tvL0Ywf6DP8vGB7Q0QEasrv8NjEdFxiucDhMTTGlbE9Y_QtO-aT0xZ7PALB5etOItTPkf2nrrza_JhcIKN1aXLuCMQ.9yqEpdMJmknQ-CnYzQRt5RRSBE3iFHN-yWP7FzbfE2g&amp;amp;dib_tag=se&amp;amp;keywords=PCI-E%2BExpress%2B1%2Bx%2Bto%2B16%2Bx&amp;amp;qid=1768948246&amp;amp;sprefix=pci-e%2Bexpress%2B1%2Bx%2Bto%2B16%2Bx%2Caps%2C147&amp;amp;sr=8-2&amp;amp;th=1" target="_blank">these adapters from Amazon&lt;/a>.&lt;/p>
&lt;p>Next to the card I had lying around (an old Nvidia GT 710), it looks like this:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/pciex1/parts.jpg"
 alt="New bracket design">&lt;figcaption>
 &lt;p>The card &amp;amp; the adapter&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>With the added height, the stock bracket no longer fitted, so I designed a new one.&lt;/p>
&lt;p>I &lt;a href="https://www.printables.com/model/599970-pc-extension-card-slot-brackets-in-30-variation-fo/" target="_blank">started from this great collection&lt;/a> (from an absolute chad also using FreeCAD).&lt;/p>
&lt;p>From there, the design was fairly straightforward, using a photo as reference for drawing the offset cutouts.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/pciex1/new_bracket.jpg"
 alt="New bracket design">&lt;figcaption>
 &lt;p>new bracket&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>With the new bracket, things looked like that.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/pciex1/pciex1_slot.jpg"
 alt="PCIe x1 slot">&lt;figcaption>
 &lt;p>PCIEx1 GPU, Shiny!&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>And here how it looks with all the cards back in the case:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/pciex1/alotofcards.jpg"
 alt="Multiple GPU cards">&lt;figcaption>
 &lt;p>Yep, that&amp;rsquo;s full&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;h1 id="some-closing-thoughts">Some Closing Thoughts&lt;/h1>
&lt;p>It feels like it should not work, but it does!&lt;/p>
&lt;p>That being said, be aware of the slot power limits. I&amp;rsquo;m not sure why (since additional PCIe lines don&amp;rsquo;t add power pins), but &lt;a href="https://en.wikipedia.org/wiki/PCI_Express#Power" target="_blank">PCIe x1 slots&lt;/a> can draw significantly less power than PCIe x16 slots.
Consequently, I would only do it with the lowest power possible GPUs, like an Nvidia GT 710 or an AMD R5.&lt;/p>
&lt;p>The cards also need to be low profile for this assembly to fit in the case.&lt;/p>
&lt;p>With these caveats in place, this hack does work, however wrong it feels, and is definitely more convenient than having to open the case and juggle cards around whenever this machine is misbehaving.&lt;/p>
&lt;p>In the unlikely case you are in the same boat (no video port, or some for AI setup maybe?), it&amp;rsquo;s definitely an option worth trying, as long as you are not hoping to play Crysis on the poor thing.&lt;/p></description></item><item><title>GANSS GS87 - Keyboard Factory Reset</title><link>https://technically.kakwalab.ovh/posts/keyboard/</link><pubDate>Mon, 24 Nov 2025 21:00:37 +0100</pubDate><author>carpentier.pf@gmail.com (Pierre-François Carpentier)</author><guid>https://technically.kakwalab.ovh/posts/keyboard/</guid><description>&lt;p>Just a really quick one. I&amp;rsquo;m kind of cheap, so years ago, I bought a &lt;code>Hello Ganss&lt;/code> &lt;code>GS87-C&lt;/code> mechanical keyboard.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/keyboard/logo.png"
 alt="ganss logo">&lt;figcaption>
 &lt;p>Hello Ganss&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>Never heard about this brand before or since, but it has served me well enough. Not sure if it has genuine
MX Brown Cherries, but these, along with the overall build quality, are good enough.&lt;/p>
&lt;p>However, recently, my &lt;code>Alt&lt;/code> and &lt;code>Super/Win&lt;/code> keys got swapped accidentally and I struggled a bit to solve the issue as I thought initially it was some kind of short in the matrix.&lt;/p>
&lt;p>In the end, it was just a setting issue&amp;hellip; at the keyboard level, and with a bit of research, I managed to find this &lt;a href="https://document.ganss.cn/HS%20%E8%8B%B1%E6%96%87%E7%89%88/HS75T%20RGB%20Manual%20Electronic%20Version%2020221221.pdf" target="_blank">manual&lt;/a>, and in particular:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Shortcut&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>FN&lt;/code> + &lt;code>Caps&lt;/code>&lt;/td>
&lt;td>Switch the position of Caps and Left Ctrl&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>FN&lt;/code> + &lt;code>Win_L&lt;/code>&lt;/td>
&lt;td>Lock/Unlock WIN key&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>FN&lt;/code> + &lt;code>A&lt;/code>&lt;/td>
&lt;td>Hold down for 3s – Windows system support (Default)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>FN&lt;/code> + &lt;code>S&lt;/code>&lt;/td>
&lt;td>Hold down for 3s – Mac system support&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>FN&lt;/code> + &lt;code>Tab&lt;/code>&lt;/td>
&lt;td>On/off the macro functions in the driver&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>FN&lt;/code> + &lt;code>Del&lt;/code>&lt;/td>
&lt;td>Ins&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>FN&lt;/code> + &lt;code>PgUp&lt;/code>&lt;/td>
&lt;td>Home&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>FN&lt;/code> + &lt;code>PgDw&lt;/code>&lt;/td>
&lt;td>End&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>FN&lt;/code> + &lt;code>N&lt;/code>&lt;/td>
&lt;td>Hold down for 3s – Power saving function off/on (default off). The indicator flashes once (off) or three times (on)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>FN&lt;/code> + &lt;code>Space&lt;/code>&lt;/td>
&lt;td>Hold down for 5s – Reset to factory settings&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;code>FN&lt;/code> + &lt;code>Space&lt;/code> did the trick&amp;hellip; If you told me 20 years ago that, I would need to do a factory reset on a freaking run of the mill keyboard&amp;hellip;&lt;/p>
&lt;p>I hope it could help someone, or at least feed the AI that helps the same someone.&lt;/p></description></item><item><title>Nut Embedding: Threading for People Too Cheap to Buy Inserts</title><link>https://technically.kakwalab.ovh/posts/3d-printing_nut_embedding/</link><pubDate>Tue, 07 Oct 2025 02:27:43 +0200</pubDate><author>carpentier.pf@gmail.com (Pierre-François Carpentier)</author><guid>https://technically.kakwalab.ovh/posts/3d-printing_nut_embedding/</guid><description>&lt;h1 id="introduction">Introduction&lt;/h1>
&lt;p>While the usual way to add threads to a 3D printing part is to use threaded inserts, another often overlooked option is nut embedding.&lt;/p>
&lt;p>Inserts usually are the best option, but for onesies/twosies it can be annoying to have to buy and wait a whole bag of those to arrive, especially considering the average tinkerer most likely already has the perfectly sized nut in their collection.&lt;/p>
&lt;p>In such cases, nut embedding is a good alternative, here&amp;rsquo;s how it works:&lt;/p>
&lt;ul>
&lt;li>Leave a pocket in the model for the nut (with appropriate tolerances).&lt;/li>
&lt;li>In the slicer, add a pause at the top of the pocket layer.&lt;/li>
&lt;li>During the print, when the pause is reached, manually insert the nut into the pocket.&lt;/li>
&lt;li>Resume the print and let the printer cover the nut.&lt;/li>
&lt;/ul>
&lt;h1 id="how-to-do-it">How To Do It&lt;/h1>
&lt;h2 id="part-design---a-sacrifice-to-bridge-the-void">Part Design - A Sacrifice To Bridge The Void&lt;/h2>
&lt;p>Designing the pocket is straightforward: measure your nut and create a pocket with appropriate clearance on each side (+0.5mm on the nut dimensions should do it).&lt;/p>
&lt;p>In addition, I recommend adding one thin (~0.2mm) &amp;ldquo;sacrificial layer&amp;rdquo; directly into the model.&lt;/p>
&lt;p>This sacrificial layer is here to ease bridging over the nut once the latter as been inserted. This layer will be punched through when screwing in the bolt for the first time.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/nut-embedding/model-pocket-sacrificical-layer.png"
 alt="3D model for nut insert">&lt;figcaption>
 &lt;p>3D model with pocket and sacrificial layer&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>Some slicers fill-in closed cavities by default. No doubt there is an option somewhere to disable it, but instead of searching for it, an easy hack is to insert a really tiny (~1µm) hole in the sacrificial layer.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/nut-embedding/model-hole-hack.png"
 alt="Tiny hole hack in sacrificial layer">&lt;figcaption>
 &lt;p>Add a tiny 1µm hole to prevent the slicer from filling the void&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>Here how it looks in 3D:&lt;/p>





&lt;figure>
 &lt;div style="width: 500px; height: 500px; position: relative; margin: 0;">
 &lt;div id="stl-container-2" style="width: 100%; height: 100%; border-radius: 8px; overflow: hidden;">&lt;/div>
 &lt;div id="stl-loading-2" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #fff;">
 &lt;div style="border: 4px solid rgba(255, 255, 255, 0.3); border-top: 4px solid #00d4ff; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 0 auto;">&lt;/div>
 &lt;p style="margin-top: 1rem;">Loading 3D model...&lt;/p>
 &lt;/div>
 &lt;div style="position: absolute; bottom: 10px; right: 10px; display: flex; gap: 5px; z-index: 10;">
 &lt;button id="zoom-in-2" style="width: 40px; height: 40px; border: none; border-radius: 5px; background: rgba(0, 0, 0, 0.6); color: white; font-size: 20px; cursor: pointer; backdrop-filter: blur(5px);">+&lt;/button>
 &lt;button id="zoom-out-2" style="width: 40px; height: 40px; border: none; border-radius: 5px; background: rgba(0, 0, 0, 0.6); color: white; font-size: 20px; cursor: pointer; backdrop-filter: blur(5px);">−&lt;/button>
 &lt;/div>
 &lt;/div>
 
 &lt;figcaption>
 &lt;p>3D knob model with embedding pocket&lt;/p>
 &lt;/figcaption>
 
&lt;/figure>

&lt;script src="https://technically.kakwalab.ovh/js/three.min.js">&lt;/script>
&lt;script>
(function() {
 const containerId = 'stl-container-2';
 const loadingId = 'stl-loading-2';
 const zoomInId = 'zoom-in-2';
 const zoomOutId = 'zoom-out-2';
 
 const container = document.getElementById(containerId);
 const loading = document.getElementById(loadingId);
 const zoomInBtn = document.getElementById(zoomInId);
 const zoomOutBtn = document.getElementById(zoomOutId);
 
 let scene, camera, renderer, mesh, controls;

 function init() {
 scene = new THREE.Scene();
 scene.background = new THREE.Color(0x0a1628); 

 const width = container.clientWidth;
 const height = container.clientHeight;

 camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
 camera.position.set(0, 0, 50);

 renderer = new THREE.WebGLRenderer({ antialias: true });
 renderer.setSize(width, height);
 renderer.setPixelRatio(window.devicePixelRatio);
 container.appendChild(renderer.domElement);

 const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
 scene.add(ambientLight);

 const directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.8);
 directionalLight1.position.set(1, 1, 1);
 scene.add(directionalLight1);

 const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.4);
 directionalLight2.position.set(-1, -1, -1);
 scene.add(directionalLight2);

 setupControls();
 loadSTL('\/images\/nut-embedding\/model.stl');

 window.addEventListener('resize', onWindowResize);
 
 
 zoomInBtn.addEventListener('click', () => {
 controls.zoom = Math.max(10, controls.zoom - 5);
 });
 
 zoomOutBtn.addEventListener('click', () => {
 controls.zoom = Math.min(200, controls.zoom + 5);
 });
 }

 function setupControls() {
 controls = {
 isDragging: false,
 isPanning: false,
 previousMousePosition: { x: 0, y: 0 },
 rotation: { x: Math.PI / 4, y: Math.PI / 4 }, 
 pan: { x: 0, y: 0 },
 zoom: 50
 };

 renderer.domElement.addEventListener('mousedown', onMouseDown);
 renderer.domElement.addEventListener('mousemove', onMouseMove);
 renderer.domElement.addEventListener('mouseup', onMouseUp);
 renderer.domElement.addEventListener('contextmenu', (e) => e.preventDefault());
 }

 function onMouseDown(e) {
 if (e.button === 0) controls.isDragging = true;
 else if (e.button === 2) controls.isPanning = true;
 controls.previousMousePosition = { x: e.clientX, y: e.clientY };
 }

 function onMouseMove(e) {
 if (controls.isDragging) {
 const deltaX = e.clientX - controls.previousMousePosition.x;
 const deltaY = e.clientY - controls.previousMousePosition.y;
 controls.rotation.y += deltaX * 0.01;
 controls.rotation.x += deltaY * 0.01;
 } else if (controls.isPanning) {
 const deltaX = e.clientX - controls.previousMousePosition.x;
 const deltaY = e.clientY - controls.previousMousePosition.y;
 controls.pan.x += deltaX * 0.05;
 controls.pan.y -= deltaY * 0.05;
 }
 controls.previousMousePosition = { x: e.clientX, y: e.clientY };
 }

 function onMouseUp() {
 controls.isDragging = false;
 controls.isPanning = false;
 }

 function loadSTL(url) {
 fetch(url)
 .then(response => response.arrayBuffer())
 .then(data => {
 const geometry = parseSTL(data);
 geometry.computeVertexNormals();
 
 const material = new THREE.MeshPhongMaterial({
 color: 0x00d4ff,
 specular: 0x111111,
 shininess: 200,
 transparent: true,
 opacity: 0.7,
 side: THREE.DoubleSide
 });
 
 mesh = new THREE.Mesh(geometry, material);
 
 geometry.computeBoundingBox();
 const center = new THREE.Vector3();
 geometry.boundingBox.getCenter(center);
 geometry.translate(-center.x, -center.y, -center.z);
 
 
 mesh.rotation.z = Math.PI / 4;
 
 scene.add(mesh);
 loading.style.display = 'none';
 animate();
 })
 .catch(error => {
 loading.innerHTML = 'Error loading STL: ' + error.message;
 });
 }

 function parseSTL(data) {
 const view = new DataView(data);
 const isASCII = isASCIISTL(data);
 return isASCII ? parseASCIISTL(data) : parseBinarySTL(view);
 }

 function isASCIISTL(data) {
 const text = new TextDecoder().decode(new Uint8Array(data, 0, Math.min(1000, data.byteLength)));
 return text.indexOf('solid') === 0;
 }

 function parseBinarySTL(view) {
 const faces = view.getUint32(80, true);
 const geometry = new THREE.BufferGeometry();
 const vertices = [];
 
 for (let i = 0; i &lt; faces; i++) {
 const offset = 84 + i * 50;
 for (let j = 0; j &lt; 3; j++) {
 const vertexOffset = offset + 12 + j * 12;
 vertices.push(
 view.getFloat32(vertexOffset, true),
 view.getFloat32(vertexOffset + 4, true),
 view.getFloat32(vertexOffset + 8, true)
 );
 }
 }
 
 geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
 return geometry;
 }

 function parseASCIISTL(data) {
 const geometry = new THREE.BufferGeometry();
 const vertices = [];
 const text = new TextDecoder().decode(data);
 const lines = text.split('\n');
 
 for (let line of lines) {
 line = line.trim();
 if (line.startsWith('vertex')) {
 const parts = line.split(/\s+/);
 vertices.push(parseFloat(parts[1]), parseFloat(parts[2]), parseFloat(parts[3]));
 }
 }
 
 geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
 return geometry;
 }

 function onWindowResize() {
 const width = container.clientWidth;
 const height = container.clientHeight;
 camera.aspect = width / height;
 camera.updateProjectionMatrix();
 renderer.setSize(width, height);
 }

 function animate() {
 requestAnimationFrame(animate);
 
 if (mesh) {
 mesh.rotation.x = controls.rotation.x;
 mesh.rotation.y = controls.rotation.y;
 }
 
 if (mesh) {
 mesh.position.x = controls.pan.x;
 mesh.position.y = controls.pan.y;
 }
 
 camera.position.z = controls.zoom;
 renderer.render(scene, camera);
 }

 init();
})();
&lt;/script>

&lt;style>
@keyframes spin {
 0% { transform: rotate(0deg); }
 100% { transform: rotate(360deg); }
}
&lt;/style>

&lt;h2 id="slicing-printing-and-seasoning">Slicing, Printing and Seasoning&lt;/h2>
&lt;h3 id="slicer">Slicer&lt;/h3>
&lt;p>Once your model is ready, import it into your slicer, slice it, and add a &lt;code>PAUSE&lt;/code> at the top of the nut pocket (the start of the sacrificial layer).&lt;/p>
&lt;p>The process varies between slicers, but here&amp;rsquo;s how to do it in PrusaSlicer:&lt;/p>
&lt;ul>
&lt;li>Click &lt;code>Slice now&lt;/code>.&lt;/li>
&lt;li>Using the vertical slider, navigate to the sacrificial layer.&lt;/li>
&lt;li>Right-click on the &lt;code>+&lt;/code> icon and select &lt;code>Add Pause&lt;/code>.&lt;/li>
&lt;li>Export the G-code.&lt;/li>
&lt;/ul>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/nut-embedding/slicer-sacrificial-layer.png"
 alt="PrusaSlicer showing pause insertion">&lt;figcaption>
 &lt;p>Adding a pause in PrusaSlicer&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>&lt;strong>Note 1:&lt;/strong> The &lt;code>PAUSE&lt;/code> occurs at the start of the selected layer. Don&amp;rsquo;t be confused by the default horizontal cursor position, which is set at the end of the layer.&lt;/p>
&lt;p>&lt;strong>Note 2:&lt;/strong> Make sure your toolhead moves out of the way, either through your printer&amp;rsquo;s PAUSE macro or by using G-code like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-lisp" data-lang="lisp">&lt;span style="display:flex;">&lt;span>M25 &lt;span style="color:#75715e">; pause SD print&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>G91 &lt;span style="color:#75715e">; switch to relative positioning&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>G1 X25 Y25 Z25 F3000 &lt;span style="color:#75715e">; move nozzle +25mm in X, Y, and Z (away from print)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>M0 &lt;span style="color:#75715e">; wait for user to press button / resume&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>G1 X-25 Y-25 Z-25 F3000 &lt;span style="color:#75715e">; move nozzle back -25mm to original position&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>G90 &lt;span style="color:#75715e">; return to absolute positioning&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>M24 &lt;span style="color:#75715e">; resume SD print&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Or, more simply, if your printer supports it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-lisp" data-lang="lisp">&lt;span style="display:flex;">&lt;span>M600 X25 Y25 Z25 &lt;span style="color:#75715e">; &amp;#34;color change&amp;#34; without retraction.&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="printing">Printing&lt;/h3>
&lt;p>Once you have your G-code, start the print and wait.&lt;/p>
&lt;p>When the &lt;code>PAUSE&lt;/code> occurs, insert the nut carefully (be cautious not to detach the part from the build plate&amp;hellip; don&amp;rsquo;t ask me how I know&amp;hellip;).&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/nut-embedding/print-pause.jpg"
 alt="Print paused with nut inserted">&lt;figcaption>
 &lt;p>Insert the nut during the pause&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>Then resume the print. The printer will add the sacrificial layer and bridge over the nut.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/nut-embedding/print-sacrificial.jpg"
 alt="Printing sacrificial layer over nut">&lt;figcaption>
 &lt;p>Sacrificial layer being printed over the nut&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>Let the print complete. If everything goes well, you&amp;rsquo;ll end up with a clean part and an invisible embedded nut.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/nut-embedding/print-end.jpg"
 alt="Completed 3D printed part with embedded nut">&lt;figcaption>
 &lt;p>Finished part with embedded nut&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;h1 id="conclusion">Conclusion&lt;/h1>
&lt;p>Embedding nuts in prints is a useful technique that&amp;rsquo;s definitely handy in a pinch. It can even be marginally &lt;a href="https://www.cnckitchen.com/blog/helicoils-threaded-insets-and-embedded-nuts-in-3d-prints-strength-amp-strength-assessment" target="_blank">more resistant&lt;/a> than inserts in some situation.&lt;/p>
&lt;p>Plus, this technique isn&amp;rsquo;t limited to nuts. It can be leveraged in many other situations, such as adding mass to a part, inserting magnets, or adding rebar for reinforcement.&lt;/p>
&lt;p>Hope you found this technique interesting, and happy printing.&lt;/p>
&lt;h1 id="links">Links&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://github.com/kakwa/misc-3d-models/tree/main/knob-generic-4mm-nut" target="_blank">Model&amp;rsquo;s link (FreeCAD)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://marlinfw.org/docs/gcode/M600.html" target="_blank">Marlin GCode Documentation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.cnckitchen.com/blog/helicoils-threaded-insets-and-embedded-nuts-in-3d-prints-strength-amp-strength-assessment" target="_blank">CNC Kitchen&amp;rsquo;s insert testing article/video&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=oF1SdIR-Kow" target="_blank">3D Printing Nerd&amp;rsquo;s magnet embedding video&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>My Silly Sun Server - Part 3/3: The Software</title><link>https://technically.kakwalab.ovh/posts/silly-sun-server-software/</link><pubDate>Wed, 01 Oct 2025 00:01:49 +0200</pubDate><author>carpentier.pf@gmail.com (Pierre-François Carpentier)</author><guid>https://technically.kakwalab.ovh/posts/silly-sun-server-software/</guid><description>&lt;h1 id="the-software-side">The Software Side&lt;/h1>
&lt;p>In &lt;a href="https://technically.kakwalab.ovh/posts/silly-sun-server-intro/">Part 1/3 (intro)&lt;/a> and &lt;a href="https://technically.kakwalab.ovh/posts/silly-sun-server-hardware/">Part 2/3 (hardware)&lt;/a>, we dealt with the hardware side, and rebuilt our cute V100 into a more manageable format.&lt;/p>
&lt;p>Now it&amp;rsquo;s time to resuscitate this server.&lt;/p>
&lt;p>For that we will need to interact with the server&amp;rsquo;s firmware (LOMlite2 &amp;amp; Open Firmware), set up a netboot server, install a usable OS, and finally, configure some services.&lt;/p>
&lt;h1 id="lomlite2">LOMlite2&lt;/h1>
&lt;h2 id="presentation">Presentation&lt;/h2>
&lt;p>LOM stands for Lights Out Management. It fulfills the same role as &lt;a href="https://en.wikipedia.org/wiki/Intelligent_Platform_Management_Interface" target="_blank">IPMI&lt;/a> compatible devices.&lt;/p>
&lt;p>It&amp;rsquo;s a small Baseboard Management Controller (BMC), similar to HP&amp;rsquo;s iLO or Dell&amp;rsquo;s iDRAC, monitoring the hardware (fans, PSUs), setting the boot sequence, and of course, &lt;a href="https://www.youtube.com/watch?v=5UT8RkSmN4k" target="_blank">turning it off and on again&lt;/a>.&lt;/p>
&lt;p>On this V100, we have the LOMlite2 version, which is only accessible through serial (bigger &amp;amp; newer servers, like V210s or T2000s, have ALOM &amp;amp; ILOM with network &amp;amp; telnet/SSH capabilities).&lt;/p>
&lt;h2 id="serial-cabling">Serial Cabling&lt;/h2>
&lt;p>To access LOM, you need one of these blue &amp;lsquo;Cisco&amp;rsquo; cables with DB9 + RJ45 connectors and a serial adapter:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-cable-serial-usb.jpg"
 alt="DB9 to RJ45 console cable with USB serial adapter">&lt;figcaption>
 &lt;p>Blue Cisco console cable with USB serial adapter&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>Connect it to the upper port on the server, and use your favorite serial terminal software.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-serial-port.jpg"
 alt="Sun V100 RJ45 serial console port">&lt;figcaption>
 &lt;p>Upper RJ45 serial console (LOM) on the Sun V100 rear panel&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;h2 id="a-sparc-of-life">A SPARC of Life!&lt;/h2>
&lt;p>The serial connection settings are the common &lt;code>9600 baud&lt;/code>, &lt;code>no parity&lt;/code>, &lt;code>one stop bit&lt;/code> and &lt;code>full duplex&lt;/code> mode (should be the default of your preferred software).&lt;/p>
&lt;p>Because I&amp;rsquo;m lazy, I&amp;rsquo;m using good old &lt;code>screen&lt;/code> but you could use something else like &lt;code>minicom&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>screen /dev/ttyUSB0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You should see something like the following when plugging in the server, or at least a &lt;code>lom&amp;gt;&lt;/code> prompt:&lt;/p>
&lt;pre tabindex="0">&lt;code>LOMlite starting up.

CPU type: H8/3437S, mode 3
Ram-test: 2048 bytes OK
Initialising i2c bus: OK
Searching for EEPROMs: 50(cfg) 
I2c eeprom @50: OK
i2c bus speed code 01... OK
Probing for lm80s: none
Probing for lm75s: 48
System functions: PSUs fans breakers rails gpio temps host CLI ebus clock 

LOMlite console
lom&amp;gt;
LOM event: +0h0m0s LOM booted
lom&amp;gt;
&lt;/code>&lt;/pre>&lt;h2 id="switching-between-okos--lom-prompts">Switching Between &lt;code>ok&amp;gt;&lt;/code>/&lt;code>OS&lt;/code> &amp;amp; &lt;code>lom&amp;gt;&lt;/code> Prompts&lt;/h2>
&lt;p>By default the Serial Port is shared between the &lt;code>lom&amp;gt;&lt;/code> prompt and the main server &lt;code>ok&lt;/code> (Open Firmware)/OS shell prompt.&lt;/p>
&lt;p>Here are a few useful instructions from Sun documentation to navigate between these:&lt;/p>
&lt;pre tabindex="0">&lt;code>There are three prompts.

 ok&amp;gt; -------------------- (normal prompt when the OS is not running)
 lom&amp;gt; -------------------- (available whether OS is running or not)
 # -------------------- (the OS prompt)

To move between the &amp;#34;ok&amp;gt;&amp;#34; prompt and the &amp;#34;lom&amp;gt;&amp;#34; prompt, type:
 ok&amp;gt; #. There must be less than 1 second between the &amp;#34;#&amp;#34; and &amp;#34;.&amp;#34;
 lom&amp;gt; ------&amp;gt; This is the prompt you get

To move between the &amp;#34;lom&amp;gt;&amp;#34; prompt and the &amp;#34;ok&amp;gt;&amp;#34; prompt type:
 lom&amp;gt; console
&lt;/code>&lt;/pre>&lt;h2 id="factory-eeprom-reset">Factory EEPROM Reset&lt;/h2>
&lt;p>I initially had issues getting to &lt;a href="https://en.wikipedia.org/wiki/Open_Firmware" target="_blank">Open Firmware&lt;/a>.
In its past life, it seems this server was configured to not share LOM and tty on the same serial port.&lt;/p>
&lt;p>I was getting LOM access via the upper port all right, but the Open Firmware/OS on the lower port remained silent.&lt;/p>
&lt;pre tabindex="0">&lt;code>lom&amp;gt; break
Console not shared.

lom&amp;gt; console -f
Console not shared.
&lt;/code>&lt;/pre>&lt;p>I settled on attempting a factory reset, but it proved somewhat challenging. I tried to use procedures &lt;a href="https://dogemicrosystems.ca/pub/Sun/System_Handbook/Sun_syshbk_V3.4/collections/PROBLEMRESOLUTIONSURE/1-72-1018251.1-1.html" target="_blank">like this one&lt;/a>, without luck.&lt;/p>
&lt;p>I also tried using the &lt;code>bootmode -u&lt;/code> and &lt;code>console -f&lt;/code> flags, again, without luck.&lt;/p>
&lt;p>I was about to give up when I stumbled &lt;a href="https://marc.info/?l=classiccmp&amp;amp;m=123195610818394" target="_blank">upon this email&lt;/a> from a former Sun employee.
(Thanks Mr. Andy for posting this on a random mailing list 16 years ago, the Internet is truly amazing sometimes).&lt;/p>
&lt;p>So, as indicated by this mail, I ran the following commands to reset the eeprom:&lt;/p>
&lt;pre tabindex="0">&lt;code>lom&amp;gt; set extra-cmds on

Extra commands are reserved for SUN service personnel.
Unauthorised use invalidates machine service warranty.

lom&amp;gt; eepromreset

lom&amp;gt; reset -l
&lt;/code>&lt;/pre>&lt;p>And bingo, I was in business.&lt;/p>
&lt;p>I was getting the &lt;code>ok&amp;gt;&lt;/code> prompt and could switch back and forth between &lt;code>lom&amp;gt;&lt;/code> and &lt;code>ok&amp;gt;&lt;/code>&lt;/p>
&lt;p>However, when trying to &lt;code>boot net&lt;/code>, I was getting:&lt;/p>
&lt;pre tabindex="0">&lt;code>ok boot net
Fast Data Access MMU Miss
&lt;/code>&lt;/pre>&lt;p>The following reset seems to have solved the issue:&lt;/p>
&lt;pre tabindex="0">&lt;code>ok set-defaults
Setting NVRAM parameters to default values.
ok reset-all
&lt;/code>&lt;/pre>&lt;p>I&amp;rsquo;m kind of curious which other magic commands are available.&lt;/p>
&lt;h1 id="open-firmware">Open Firmware&lt;/h1>
&lt;p>&lt;a href="https://github.com/openbios/openfirmware" target="_blank">Open Firmware&lt;/a> is the Forth-powered (a fun little language) boot firmware used by Sun servers and other non‑x86 platforms from that period, like PowerPC Macs and IBM POWER servers.
FYI, Sun had its own implementation: &lt;a href="https://github.com/openbios/openboot" target="_blank">OpenBoot&lt;/a>.&lt;/p>
&lt;h2 id="useful-commands">Useful Commands&lt;/h2>
&lt;p>Here are a few useful commands:&lt;/p>
&lt;p>Main help:&lt;/p>
&lt;pre tabindex="0">&lt;code>ok help

Enter &amp;#39;help command-name&amp;#39; or &amp;#39;help category-name&amp;#39; for more help
(Use ONLY the first word of a category description) 
Examples: help select -or- help line
 Main categories are: 
[...]
System and boot configuration parameters
[...]
nvramrc (making new commands permanent)
&lt;/code>&lt;/pre>&lt;p>Sub-help (note: need to use lowercase):&lt;/p>
&lt;pre tabindex="0">&lt;code>ok help system

devalias - Display all device aliases
devalias &amp;lt;name&amp;gt; &amp;lt;value&amp;gt; - Create or change a device alias
printenv Show all configuration parameters
 numbers are shown in decimal
setenv &amp;lt;name&amp;gt; &amp;lt;value&amp;gt; Change a configuration parameter
 changes are permanent but only take effect after a reset
 Examples:
 setenv input-device ttya - use ttya input next time
 setenv screen-#rows 0x1e - use 30 rows of display ( hex 1e )
 setenv boot-device net - specify network as boot device
 setenv auto-boot? false - disable automatic boot
set-defaults Revert to factory configuration
See also: nvramrc
&lt;/code>&lt;/pre>&lt;p>List device aliases (boot devices):&lt;/p>
&lt;pre tabindex="0">&lt;code>ok devalias

dload /pci@1f,0/ethernet@c:,
net0 /pci@1f,0/ethernet@c
net /pci@1f,0/ethernet@c
cdrom /pci@1f,0/ide@d/cdrom@3,0:f
disk3 /pci@1f,0/ide@d/disk@3,0
disk2 /pci@1f,0/ide@d/disk@2,0
disk1 /pci@1f,0/ide@d/disk@1,0
disk0 /pci@1f,0/ide@d/disk@0,0
[...]
&lt;/code>&lt;/pre>&lt;p>Get the current environment variables:&lt;/p>
&lt;pre tabindex="0">&lt;code>ok printenv

auto-boot? true true
watchdog-reboot? false false
diag-file 
diag-device net net
boot-file 
boot-device disk0 disk net
local-mac-address? false false
net-timeout 0 0
ansi-terminal? true true
screen-#columns 80 80
screen-#rows 34 34
silent-mode? false false
&lt;/code>&lt;/pre>&lt;p>Set the boot device (persistent):&lt;/p>
&lt;pre tabindex="0">&lt;code>ok setenv boot-device disk0
boot-device = disk0

ok reset
&lt;/code>&lt;/pre>&lt;p>One-time boot of another device:&lt;/p>
&lt;pre tabindex="0">&lt;code>ok boot net
&lt;/code>&lt;/pre>&lt;h1 id="netboot--os-install">Netboot &amp;amp; OS Install&lt;/h1>
&lt;p>Open Firmware on these machines is able to boot over the network a bit like PXE.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-install-setup.jpg"
 alt="Network install setup with cables">&lt;figcaption>
 &lt;p>Ad‑hoc netboot lab setup for Open Firmware testing&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>The major difference is the lack of DHCP support: it instead relies on RARP (static MAC -&amp;gt; IP mapping).&lt;/p>
&lt;p>Also, the netboot server needs to be on the same LAN subnet (or plugged directly into) as our cute Sun server.
And the netboot server must also be a TFTP server hosting the boot file at a set location (derived from the IP we give to our Sun server).&lt;/p>
&lt;h2 id="netboot-server-setup---the-annoying--legacy-way">Netboot Server Setup - The Annoying &amp;amp; Legacy Way&lt;/h2>
&lt;p>Let&amp;rsquo;s set up a netboot server (Debian/Ubuntu based) as our Sunny God intended.&lt;/p>
&lt;p>First, get a &lt;code>root&lt;/code> terminal:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># pick your poison&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo -i
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># or&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>su -
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Install the necessary software:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>apt install atftpd rarpd
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Set Server NIC &amp;amp; IPs&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export BOOT_SERVER_NIC&lt;span style="color:#f92672">=&lt;/span>enp0s25
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export SUN_V100_IP&lt;span style="color:#f92672">=&lt;/span>172.24.42.51
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Must be .150 (?)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export BOOT_SERVER_IP&lt;span style="color:#f92672">=&lt;/span>172.24.42.150
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Download &amp;amp; put the boot image in the correct TFTP location (IP address in hex):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>cd /srv/tftp/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wget https://technically.kakwalab.ovh/files/ofwboot.kakwa.test
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># calculate the file name expected by Open Firmware (IP address in hexadecimal)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>arg&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;`echo &lt;/span>&lt;span style="color:#e6db74">${&lt;/span>SUN_V100_IP&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> | sed &amp;#39;s/\./ /g&amp;#39;`&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>img_name&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>printf &lt;span style="color:#e6db74">&amp;#34;%.2X%.2X%.2X%.2X\n&amp;#34;&lt;/span> $arg&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># create hardlink to it&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ln -f ofwboot.kakwa.test &lt;span style="color:#e6db74">${&lt;/span>img_name&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Start the TFTP server:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>systemctl start atftpd.service
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Set the server IP:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>ip addr add &lt;span style="color:#e6db74">${&lt;/span>BOOT_SERVER_IP&lt;span style="color:#e6db74">}&lt;/span>/24 dev &lt;span style="color:#e6db74">${&lt;/span>BOOT_SERVER_NIC&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Launch rarpd in the foreground:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>rarpd -e -dv &lt;span style="color:#e6db74">${&lt;/span>BOOT_SERVER_NIC&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>From the LOM connected console, start the V100:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Set bootmode to ok/ofw prompt&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Note: if nothing is installed, defaults to boot net anyway&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>lom&amp;gt; bootmode forth
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>lom&amp;gt; reset
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># if necessary&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>lom&amp;gt; poweron
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After a few minutes, you should get the following prompt:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>LOM event: +3h36m30s host power on
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Aborting startup sequence because of lom bootmode &lt;span style="color:#e6db74">&amp;#34;forth&amp;#34;&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Input and output on ttya.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Type fexit to resume normal startup sequence.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Type help &lt;span style="color:#66d9ef">for&lt;/span> more information
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ok 
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>From the &lt;code>ok&amp;gt;&lt;/code> prompt, enter the following to initiate netbooting:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>ok boot net
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Timeout waiting &lt;span style="color:#66d9ef">for&lt;/span> ARP/RARP packet
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You should see log messages like on your rarpd server:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>rarpd&lt;span style="color:#f92672">[&lt;/span>16222&lt;span style="color:#f92672">]&lt;/span>: RARP request from 00:03:ba:5b:ae:b3 on enp0s25
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rarpd&lt;span style="color:#f92672">[&lt;/span>16222&lt;span style="color:#f92672">]&lt;/span>: not found in /etc/ethers
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Replace with your MAC address:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>export SUN_V100_MAC&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;00:03:ba:5b:ae:b3&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Normalize MAC (uppercase, no colons)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>MAC_UPPER&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$SUN_V100_MAC&lt;span style="color:#e6db74">&amp;#34;&lt;/span> | tr &lt;span style="color:#e6db74">&amp;#39;[:lower:]&amp;#39;&lt;/span> &lt;span style="color:#e6db74">&amp;#39;[:upper:]&amp;#39;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>MAC_NOPUNCT&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$MAC_UPPER&lt;span style="color:#e6db74">&amp;#34;&lt;/span> | tr -d &lt;span style="color:#e6db74">&amp;#39;:&amp;#39;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Generate a hostname for rarp &amp;amp; ethers/hosts mapping&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Note: could be any name, just avoid collisions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>HOSTNAME&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;sparc-&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>MAC_NOPUNCT&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Make sure ethers file exists&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>touch /etc/ethers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Add to /etc/ethers if not already present&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>grep -q -F &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$MAC_UPPER&lt;span style="color:#e6db74"> &lt;/span>$HOSTNAME&lt;span style="color:#e6db74">&amp;#34;&lt;/span> /etc/ethers &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$MAC_UPPER&lt;span style="color:#e6db74"> &lt;/span>$HOSTNAME&lt;span style="color:#e6db74">&amp;#34;&lt;/span> | sudo tee -a /etc/ethers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Add to /etc/hosts if not already present&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>grep -q -F &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$SUN_V100_IP&lt;span style="color:#e6db74"> &lt;/span>$HOSTNAME&lt;span style="color:#e6db74">&amp;#34;&lt;/span> /etc/hosts &lt;span style="color:#f92672">||&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$SUN_V100_IP&lt;span style="color:#e6db74"> &lt;/span>$HOSTNAME&lt;span style="color:#e6db74">&amp;#34;&lt;/span> | sudo tee -a /etc/hosts
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After a while, the server should get an IP and retrieve the boot file:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>Boot device: /pci@1f,0/ethernet@c File and args: 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>27a00 &amp;gt;&amp;gt; kakwa&lt;span style="color:#960050;background-color:#1e0010">&amp;#39;&lt;/span>s OFW BOOT 1.29
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Using RARP protocol: ip address: 172.24.42.1, netmask: 255.255.0.0, server: 172.24.42.150
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TFTP IP address: 172.24.42.150
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Fast Data Access MMU Miss
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It works! Not sure if we have created Paradise or Hell, however&amp;hellip;&lt;/p>
&lt;h2 id="netboot-server-setup---the-modern--masochistic-way">Netboot Server Setup - The Modern &amp;amp; Masochistic Way&lt;/h2>
&lt;p>In truth, I&amp;rsquo;m an atheist, I don&amp;rsquo;t believe in God, even the Sunnier ones.&lt;/p>
&lt;p>And why is that? Well, this setup is really messy, and I&amp;rsquo;m kind of sorry if you read
through it&amp;hellip; or worse, if you actually tried to implement it. Also, spoiler,
for our NetBSD/OpenBSD netbooting target, even more services are required.&lt;/p>
&lt;p>So I&amp;rsquo;ve committed blasphemy and created my &lt;a href="https://github.com/kakwa/ofw-install-server" target="_blank">own all-in-one Go netboot server&lt;/a> directly providing the RARP + TFTP combo (plus, spoiler, also BOOTP + NFSv2 + HTTP).&lt;/p>
&lt;p>I must confess I&amp;rsquo;ve sinned even more by letting the twin d(a)emons
Claude &amp;amp; ChatGPT do most of the work, especially the network protocol implementation.
So, expect a few bugs.&lt;/p>
&lt;p>On top of that, this netboot server is, by design, very limited.
It only provides a single bootstrap path/set of boot files and should only be used within a dedicated LAN segment.
Don&amp;rsquo;t rely on this server if you are still bootstrapping hundreds of SPARC servers like in the good old &lt;a href="https://docs.oracle.com/cd/E26505_01/html/E28039/customjumpsample-5.html#scrolltoc" target="_blank">Jumpstart&lt;/a> days.
However, for the onesies/twosies like here, don&amp;rsquo;t hesitate to give it a try.&lt;/p>
&lt;p>But enough about why my masochistic tendencies led me to develop a full netboot server for such a dead platform.
Here&amp;rsquo;s how to set up this damn piece of software.&lt;/p>
&lt;p>Building the server:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># build dependencies&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt install golang git make
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># clone sources&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git clone https://github.com/kakwa/ofw-install-server
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd ofw-install-server/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>make
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># check the help&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>./ofw-install-server -h
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If required, setup your nic&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># NIC &amp;amp; IP to use for the boot server&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export BOOT_SERVER_NIC&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;enp0s25&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export BOOT_SERVER_IP&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;172.24.42.150&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Configure the NIC if necessary&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo ip addr add &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>BOOT_SERVER_IP&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">/24&amp;#34;&lt;/span> dev &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>BOOT_SERVER_NIC&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Also, optionally, make the deploy server a router:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>export WAN_NIC&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;wlp3s0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#ae81ff">1&lt;/span> &amp;gt; /proc/sys/net/ipv4/ip_forward
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>iptables -F
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>iptables -t nat -F
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>iptables -A FORWARD -i &lt;span style="color:#e6db74">${&lt;/span>BOOT_SERVER_NIC&lt;span style="color:#e6db74">}&lt;/span> -s &lt;span style="color:#e6db74">${&lt;/span>BOOT_SERVER_IP&lt;span style="color:#e6db74">}&lt;/span>/24 -j ACCEPT
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>iptables -A FORWARD -i &lt;span style="color:#e6db74">${&lt;/span>WAN_NIC&lt;span style="color:#e6db74">}&lt;/span> -d &lt;span style="color:#e6db74">${&lt;/span>BOOT_SERVER_IP&lt;span style="color:#e6db74">}&lt;/span>/24 -j ACCEPT
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>iptables -t nat -A POSTROUTING -o &lt;span style="color:#e6db74">${&lt;/span>WAN_NIC&lt;span style="color:#e6db74">}&lt;/span> -j MASQUERADE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>iptables -t nat -A POSTROUTING -o &lt;span style="color:#e6db74">${&lt;/span>WAN_NIC&lt;span style="color:#e6db74">}&lt;/span> -j MASQUERADE
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Start the server with the TFTP &amp;amp; RARP module enabled:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Retrieve something to boot:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wget https://technically.kakwalab.ovh/files/ofwboot.kakwa.test
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Start the server&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo ./ofw-install-server -iface &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>BOOT_SERVER_NIC&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> -rarp &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> -tftp -tftp-file ./ofwboot.kakwa.test
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="netbooting-debian-6">Netbooting Debian 6&lt;/h2>
&lt;p>Just for reference, here is how to get and netboot the last working Debian installer:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Recover the boot.img file from the netboot installer `.deb` package.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wget https://archive.debian.org/debian/pool/main/d/debian-installer-netboot-images/debian-installer-6.0-netboot-sparc_20110106.squeeze4.b6_all.deb
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir debtmp &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> dpkg-deb -x *.deb debtmp/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>find ./debtmp/ -name &lt;span style="color:#e6db74">&amp;#39;boot.img&amp;#39;&lt;/span> -exec cp &lt;span style="color:#f92672">{}&lt;/span> ./boot-debian-6.img &lt;span style="color:#ae81ff">\;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rm -rf -- debian-installer-6.0-netboot-sparc_20110106.squeeze4.b6_all.deb ./debtmp/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Start the server with the Debian boot image&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo ./ofw-install-server -iface &lt;span style="color:#e6db74">${&lt;/span>BOOT_SERVER_NIC&lt;span style="color:#e6db74">}&lt;/span> -rarp &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> -tftp -tftp-file ./boot-debian-6.img
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>From there, you should be able to install Debian 6, albeit with a few headaches like pointing to the archive.debian.org repository or handling expired GPG keys.&lt;/p>
&lt;p>Note: there is a Debian 7 version of the installer, but it kernel-oopsed on me.&lt;/p>
&lt;h2 id="bsd---more-netboot-setup">&lt;code>*BSD&lt;/code> == More Netboot Setup&amp;hellip;&lt;/h2>
&lt;p>For &lt;a href="https://www.netbsd.org/docs/network/netboot/" target="_blank">NetBSD&lt;/a>/&lt;a href="https://ftp.openbsd.org/pub/OpenBSD/7.7/sparc64/INSTALL.sparc64" target="_blank">OpenBSD&lt;/a>, this is &amp;ldquo;slightly&amp;rdquo; more complex than the Debian case:&lt;/p>
&lt;ul>
&lt;li>Open Firmware first RARPs &amp;amp; TFTP‑loads a tiny second‑stage loader (&lt;code>ofwboot.net&lt;/code>).&lt;/li>
&lt;li>That loader then speaks BOOTPARAMS and/or BOOTP to learn its IP and the NFS root path&lt;/li>
&lt;li>It then mounts that root over good old NFSv2,&lt;/li>
&lt;li>And finally &lt;a href="https://www.netbsd.org/docs/network/netboot/local.install.html#diskimage" target="_blank">loads the kernel &amp;amp; ramdisk&lt;/a> from there.&lt;/li>
&lt;/ul>
&lt;p>Yes: RARP + TFTP + BOOTP + NFSv2 on the same LAN segment&amp;hellip;&lt;/p>
&lt;p>Oh, and to add a bit of fun, NFSv2 is kind of obsolete (removed from Debian since 2022-03-13 in nfs-utils 1:2.6.1-1~exp1).&lt;/p>
&lt;p>But once again, with a bit of vibe coding and a few tweaks, we are able to add these services to our custom netboot server.&lt;/p>
&lt;h2 id="openbsd-install">OpenBSD Install&lt;/h2>
&lt;p>In the end, I opted for NetBSD so I didn&amp;rsquo;t fully install OpenBSD. But given I managed to start the installer, I&amp;rsquo;m fairly confident I would be able to install it if needed.&lt;/p>
&lt;p>Also, just for kicks, given all the services we have in our netboot server, I&amp;rsquo;ve taken the liberty to add one last bit: an HTTP server to serve an OpenBSD &lt;a href="https://man.openbsd.org/autoinstall.8" target="_blank">autoinstall&lt;/a> configuration file.&lt;/p>
&lt;p>Lastly, it&amp;rsquo;s worth mentioning that the OpenBSD version of &lt;code>ofwboot.net&lt;/code> gave me quite a lot of headaches. I never quite managed to feed it the NFS server IP and file path/name given by BOOTP (typo? bug? wrong BOOTP option? magic values?).&lt;/p>
&lt;p>I also tried to tweak the OpenBSD &lt;code>ofwboot.net&lt;/code>, but without luck. If you want to give it a try, the code is available &lt;a href="https://github.com/kakwa/silly-sunv100-server/tree/main/ofwboot" target="_blank">here&lt;/a> and can be built under Linux (see README.md instructions). I also tried to make it use RARP and TFTP for the second boot stage instead of NFS+BOOTP.&lt;/p>
&lt;p>In the end, I chose to use the NetBSD&amp;rsquo;s &lt;code>ofwboot.net&lt;/code> version for both NetBSD and OpenBSD and it seems to work fine. FYI, both versions come from the same source, but the NetBSD one seems marginally more modern.&lt;/p>
&lt;p>But enough said, here&amp;rsquo;s the setup:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Tweak it to the latest versions&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export OPENBSD_VERSION&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;7.7&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export NETBSD_VERSION&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;10.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># If you want to try your luck with the OpenBSD ofwboot.net&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># wget &amp;#34;https://ftp.openbsd.org/pub/OpenBSD/${OPENBSD_VERSION}/sparc64/ofwboot.net&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Recover bootloader from NetBSD&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wget https://cdn.netbsd.org/pub/NetBSD/NetBSD-&lt;span style="color:#e6db74">${&lt;/span>NETBSD_VERSION&lt;span style="color:#e6db74">}&lt;/span>/sparc64/installation/netboot/ofwboot.net
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Recover OpenBSD install RamDisk&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wget &lt;span style="color:#e6db74">&amp;#34;https://ftp.openbsd.org/pub/OpenBSD/&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>OPENBSD_VERSION&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">/sparc64/bsd.rd&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Recover an autoinstall file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wget https://raw.githubusercontent.com/kakwa/silly-sun-server/f881b5427f1b103411285ac169dbb102ad37f874/misc/openbsd-install.conf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Start the install server:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>sudo ./ofw-install-server -rarp -tftp -tftp-file ./ofwboot.net -bootp -nfs -nfs-file ./bsd.rd -http -http-file ./openbsd-install.conf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="netbsd-install">NetBSD Install&lt;/h2>
&lt;p>So in the end, I finally settled on installing NetBSD:&lt;/p>
&lt;p>Recover &amp;amp; prepare the files:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Tweak it to the latest version&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export NETBSD_VERSION&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;10.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wget &lt;span style="color:#e6db74">&amp;#34;https://cdn.netbsd.org/pub/NetBSD/NetBSD-&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>NETBSD_VERSION&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">/sparc64/installation/netboot/ofwboot.net&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wget &lt;span style="color:#e6db74">&amp;#34;https://cdn.netbsd.org/pub/NetBSD/NetBSD-&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>NETBSD_VERSION&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">/sparc64/binary/kernel/netbsd-INSTALL.gz&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>gunzip netbsd-INSTALL.gz
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Start the server:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>sudo ./ofw-install-server -rarp -tftp -tftp-file ./ofwboot.net -bootp -nfs -nfs-file ./netbsd-INSTALL
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="one-last-bit">One Last Bit&lt;/h2>
&lt;p>And then I just did this final tweak in the &lt;code>ok&lt;/code> prompt to make it boot on disk persistently:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>ok setenv boot-device disk0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>reset
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Not sure why the default boot-device config (&lt;code>disk net&lt;/code>) didn&amp;rsquo;t work. Maybe the &lt;code>disk&lt;/code> dev alias was missing?&lt;/p>
&lt;p>But frankly, by this point, I could not care less.&lt;/p>
&lt;p>This cute little Sun server is finally working!&lt;/p>
&lt;h1 id="a-bit-of-netbsd-sysadmin">A Bit of NetBSD SysAdmin&lt;/h1>
&lt;p>I will not delve too deep into software configuration as this is not a blog post about NetBSD administration.&lt;/p>
&lt;p>But nonetheless, I will present a few useful bits.&lt;/p>
&lt;h2 id="hardware-monitoring">Hardware Monitoring&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>netra-x1# envstat
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Current CritMax WarnMax WarnMin CritMin Unit
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>admtemp0&lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> internal: 37.000 95.000 -55.000 degC
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> external: 58.000 105.000 -55.000 degC
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>lom0&lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Fault LED: FALSE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Alarm1: FALSE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Alarm2: FALSE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Alarm3: TRUE
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="sshd">SSHD&lt;/h2>
&lt;p>Enabling SSH access, because network access is nicer than serial:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>grep -q &lt;span style="color:#e6db74">&amp;#39;^sshd=YES&amp;#39;&lt;/span> /etc/rc.conf &lt;span style="color:#f92672">||&lt;/span> echo &lt;span style="color:#e6db74">&amp;#39;sshd=YES&amp;#39;&lt;/span> &amp;gt;&amp;gt; /etc/rc.conf
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>/etc/rc.d/sshd start
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="ntp">NTP&lt;/h2>
&lt;p>On such an old server, the clock might be out of date, leading to weird errors:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Force synchronization&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ntpdate 2.netbsd.pool.ntp.org
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Enable NTP&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>grep -q &lt;span style="color:#e6db74">&amp;#39;^ntpd=YES&amp;#39;&lt;/span> /etc/rc.conf &lt;span style="color:#f92672">||&lt;/span> echo &lt;span style="color:#e6db74">&amp;#39;ntpd=YES&amp;#39;&lt;/span> &amp;gt;&amp;gt; /etc/rc.conf
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>/etc/rc.d/ntpd start
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="getting-a-package-manager">Getting A Package Manager&lt;/h2>
&lt;p>Install pkgin:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>pkg_add https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/&lt;span style="color:#e6db74">`&lt;/span>uname -m&lt;span style="color:#e6db74">`&lt;/span>/&lt;span style="color:#e6db74">`&lt;/span>uname -r&lt;span style="color:#e6db74">`&lt;/span>/All/pkgin
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Update the index:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>pkgin update
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Search for packages:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>pkgin search neovim
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Install packages:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>pkgin install neovim
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="configuring-services">Configuring Services&lt;/h2>
&lt;p>Now that I had a working server, I used Ansible to configure a bunch of services:&lt;/p>
&lt;ul>
&lt;li>Reverse proxies + basic auth for my 3D printers &amp;amp; other IoTs.&lt;/li>
&lt;li>A bit of public static hosting.&lt;/li>
&lt;li>A personal FreshRSS instance.&lt;/li>
&lt;/ul>
&lt;p>I managed to get everything working, but my NetBSD experience has been a bit rough around the edges.&lt;/p>
&lt;p>In particular, the binary packages are rather inconsistent, and frequently have dependencies/linking issues or are really outdated.
I had to fall back on &lt;code>pkgsrc&lt;/code> quite often, and well&amp;hellip; compiling big projects like &lt;code>php&lt;/code> or &lt;code>nginx&lt;/code> feels like torturing this poor old machine.
Given I&amp;rsquo;ve already angered our Machine Overlords (sketchy story involving compiling Gentoo on a PowerBook Titanium 1 GHz&amp;hellip; inside a fridge), it&amp;rsquo;s something I would prefer to avoid.&lt;/p>
&lt;p>If you are in the same SPARCy boat, I&amp;rsquo;ve published &lt;a href="https://netbsd.kakwalab.ovh/pkgsrc/packages/NetBSD/sparc64/10/All/" target="_blank">the resulting binary packages here&lt;/a>.&lt;/p>
&lt;p>But I managed to make everything work in the end. &lt;a href="https://github.com/kakwa/ansible-netbsd" target="_blank">Here are my Ansible playbook &amp;amp; roles&lt;/a> if you are interested.
They are a bit buggy and not fully idempotent, but they can help you get started if you want to work on NetBSD, even with other CPU architectures.&lt;/p>
&lt;p>Lastly, as a final touch, I thought it was fitting to host a copy of the &lt;a href="https://sun.kakwalab.ovh/" target="_blank">Sun System Handbook&lt;/a> on this server.&lt;/p>
&lt;h1 id="conclusion">Conclusion&lt;/h1>
&lt;p>It was a long project, lasting several months. The CAD/case design part in particular took me quite a while, which was
not unexpected given my starting skills. Yet, I&amp;rsquo;m really pleased with the result, and I&amp;rsquo;ve learned a lot,
from drawing properly constrained sketches to part assembly.
Firing up FreeCAD is no longer a dreaded experience, and I&amp;rsquo;m now much quicker at designing accurate parts.&lt;/p>
&lt;p>It also led me to numerous side projects, like playing with USB-C PD, rebuilding my Ender 3 with a new board and Klipper, or learning about laser cutting.&lt;/p>
&lt;p>On the software side, it made me discover NetBSD and in the end, I managed to host everything I wanted on this small server.&lt;/p>
&lt;p>I hope this cute thing will serve me well enough for at least a few years.
SPARC may be on borrowed time, and things may get harder and harder to run,
but I trust the NetBSD folks to not drop it soon (they have a reputation to uphold after all).&lt;/p>
&lt;p>In any case, I had a lot of fun doing this project, and I hope you had some of this fun reading it.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-double-trouble.jpg"
 alt="two 10 inch Sun V100">&lt;figcaption>
 &lt;p>Prepare for trouble, make it double&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;h1 id="links">Links&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://technically.kakwalab.ovh/posts/silly-sun-server-intro/">Part 1/3 - Introduction&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://technically.kakwalab.ovh/posts/silly-sun-server-hardware/">Part 2/3 - Hardware&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://github.com/kakwa/silly-sun-server" target="_blank">This project&amp;rsquo;s git (scripts, programs &amp;amp; 3D models)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://sun.kakwalab.ovh/Systems/SunFireV100/SunFireV100.html" target="_blank">Sun&amp;rsquo;s V100 Handbook Documentation&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://docs.oracle.com/cd/E19088-01/v100.srvr/index.html" target="_blank">Misc Sun/Oracle&amp;rsquo;s PDFs&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://docs.oracle.com/cd/E19102-01/n20.srvr/806-7334-13/LW2&amp;#43;User.LOM.html" target="_blank">Sun&amp;rsquo;s LOMlite2 Documentation&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://dogemicrosystems.ca/wiki/Sun_Fire_V100" target="_blank">DogeMicroSystems Wiki&lt;/a>.&lt;/li>
&lt;li>Various blogs like: Eerie&amp;rsquo;s &lt;a href="https://eerielinux.wordpress.com/2019/09/22/a-sparc-in-the-night-sunfire-v100-exploration/" target="_blank">blog post 1&lt;/a> and &lt;a href="https://eerielinux.wordpress.com/2019/10/30/illumos-v9os-on-sparc64-sunfire-v100/" target="_blank">2&lt;/a>, Scott Alan Miller&amp;rsquo;s &lt;a href="https://sheepguardingllama.com/2007/09/sunfire-v100-server/" target="_blank">series&lt;/a> or Andrew Rawlins&amp;rsquo;s &lt;a href="https://www.fermit.org.uk/green_computing/solar_power_solaris/" target="_blank">solar-powered Sun V100&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://www.netbsd.org/docs/network/netboot/" target="_blank">NetBSD netboot&lt;/a> &amp;amp; &lt;a href="https://www.netbsd.org/docs/network/netboot/intro.sun.ofw.html" target="_blank">SPARC64 specific&lt;/a> install instructions&lt;/li>
&lt;li>&lt;a href="https://man.openbsd.org/diskless" target="_blank">OpenBSD diskless&lt;/a> &amp;amp; &lt;a href="https://ftp.eu.openbsd.org/pub/OpenBSD/7.7/sparc64/INSTALL.sparc64" target="_blank">SPARC64 specific&lt;/a> install instructions&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=5OyGwbWKWZU" target="_blank">Obligatory Clabretro&amp;rsquo;s video&lt;/a>.&lt;/li>
&lt;/ul></description></item><item><title>My Silly Sun Server - Part 2/3: The Hardware</title><link>https://technically.kakwalab.ovh/posts/silly-sun-server-hardware/</link><pubDate>Tue, 30 Sep 2025 23:01:49 +0200</pubDate><author>carpentier.pf@gmail.com (Pierre-François Carpentier)</author><guid>https://technically.kakwalab.ovh/posts/silly-sun-server-hardware/</guid><description>&lt;h1 id="a-hardware-reinvention">A Hardware Reinvention&lt;/h1>
&lt;p>In the &lt;a href="https://technically.kakwalab.ovh/posts/silly-sun-server-intro/">introduction (Part 1/3)&lt;/a>, we outlined the four goals we have for our server.&lt;/p>
&lt;p>In this part, we will deal with goals 1 and 2, i.e., the hardware rework — PSU, hard drive, cooling, and creating a custom enclosure that nods to Sun’s original design.&lt;/p>
&lt;h1 id="new-parts">New Parts!&lt;/h1>
&lt;h2 id="the-shopping-list">The Shopping List&lt;/h2>
&lt;p>Here is the shopping list to reach our goals:&lt;/p>
&lt;ul>
&lt;li>The original IDE hard drives are too big and noisy, so I&amp;rsquo;m replacing them with a &lt;a href="https://shop.sandisk.com/products/memory-cards/cfast-cfexpress-compactflash/sandisk-extreme-compactflash?sku=SDCFXSB-064G-G46" target="_blank">SanDisk 64 GB CF card&lt;/a> + &lt;a href="https://www.startech.com/en-us/hdd/35baycf2ide" target="_blank">adapter thingy&lt;/a> (CF cards are generally pin-compatible with IDE, giving good odds that it will work with decent performance).&lt;/li>
&lt;li>The original PSU is quite bulky, but relatively small at 80 W. So I will also try my luck with a &lt;a href="https://www.rgeek.com/portfolio-item/rgeek-pico-dc-psu-rp-120lq-dc-12v-24pin-power-supply-module/" target="_blank">PicoPSU&lt;/a> + external 12 V power brick.&lt;/li>
&lt;li>Let&amp;rsquo;s also try our luck with a &lt;a href="https://www.anker.com/products/b2679-nano-100w-usb-c-charger" target="_blank">GaN USB‑C charger&lt;/a> + &lt;a href="https://aliexpress.com/item/1005004356272196.html" target="_blank">USB‑C PD trigger board&lt;/a> + &lt;a href="https://www.mini-itx.com/~picoPSU-120-WI-25" target="_blank">Wide Input 12–25 V PicoPSU&lt;/a>. On paper, it could provide us with a great little PSU instead of a rather large and sketchy black brick from an unknown manufacturer.&lt;/li>
&lt;li>The original 40 × 40 mm 12 V fans are getting the &lt;a href="https://noctua.at/en/products/fan/nf-a4x20-flx" target="_blank">Noctua treatment&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>I also want to design a new and far more compact case for our cute server.&lt;/p>
&lt;p>If you ask: yes, all that is not cheap, but saving money isn&amp;rsquo;t the goal here. If you need a budget‑friendly option, a second‑hand Dell/HP/Lenovo micro PC or an ARM SBC is far cheaper, reliable, efficient, and powerful.&lt;/p>
&lt;h2 id="hard-drive">Hard Drive&lt;/h2>
&lt;p>The original Sun V100 came with a pair of 3.5&amp;quot; IDE hard drives. These were a bit too bulky, and depending on the brand, to noisy.&lt;/p>
&lt;p>So I chose to replace them with CompactFlash Card. &lt;a href="https://en.wikipedia.org/wiki/CompactFlash" target="_blank">CF cards&lt;/a> are generally pin compatible with PATA/IDE, and only requires very basic and mostly passive adapter which makes then decent (and compact) replacement for old spinning rust.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-ide-cf.jpg"
 alt="ide adapter &amp;#43; cf card">&lt;figcaption>
 &lt;p>Hard Drive replacement&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>The only unknown is how long these CF cards will last when subject to more regular IOs. If it becomes an issue, I might just bit the bullet, and buy &lt;a href="https://www.mouser.ie/c/embedded-solutions/memory-data-storage/memory-modules-memory-cards/memory-cards/?nand%20flash%20technology=SLC&amp;amp;product=Compact%20Flash%20Cards&amp;amp;sort=memory%20size%7C1" target="_blank">a rather expensive industrial SLC variant&lt;/a>.&lt;/p>
&lt;h2 id="psu">PSU&lt;/h2>
&lt;p>The Sun V100 uses an 80 W PSU. It has the usual Molex IDE and 20‑pin ATX
connectors of the PCs from its era. While it could probably be made quieter with the Noctua treatment, it’s simply too bulky.&lt;/p>
&lt;p>So I chose to replace it. I initially tried to use a 120 W 12 V PicoPSU board combined
with a USB‑C PD trigger board and a GaN charger.&lt;/p>
&lt;p>Unfortunately I didn’t read the fine print closely enough. While
&lt;a href="https://en.wikipedia.org/wiki/USB_hardware#USB_Power_Delivery" target="_blank">USB‑C Power Delivery&lt;/a>
does define a 12 V level, it’s optional and seems uncommon — at least in the
very scientific sample size of two chargers I had on hand.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-trigger-board.jpg"
 alt="USB-C Trigger Board">&lt;figcaption>
 &lt;p>USB-C Trigger Board&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>Worse, despite being set to 12 V, when I plugged the trigger board into the
PSU, it delivered 15 V… Lesson learned: always check the output voltage on these.&lt;/p>
&lt;p>So it was back to a cheap no‑name 12 V brick. But this option is still not quite right.
The server often fails to start (switch-on current surge?), and need to be unplugged/plugged several times to get it working.&lt;/p>
&lt;p>I will need to revisit the PSU part, this time a multi‑voltage model PicoPSU.
Being able to use USB-C would be really nice, both to get quality chargers and
maybe to use USB-C power banks as makeshift UPS.&lt;/p>
&lt;p>But for now, let’s forge ahead.&lt;/p>
&lt;h1 id="case-modeling">Case Modeling&lt;/h1>
&lt;h2 id="caveman-meets-cad">Caveman Meets CAD&lt;/h2>
&lt;p>I’m a firm believer in open source (and maybe a bit of a masochist), so FreeCAD it is, even if it can be a tad frustrating at times.&lt;/p>
&lt;p>Truth be told, FreeCAD is the only CAD software I’ve ever dabbled in, and even then mostly in a &amp;ldquo;smash some basic shapes together and call it a model&amp;rdquo; kind of way.&lt;/p>
&lt;p>I&amp;rsquo;m kind of CAD‑illiterate, and my experience so far has felt like being a caveman wandering into a machinist’s workshop with all its lathes, mills, saws, and drills… only to end up bashing them together like rocks.&lt;/p>
&lt;p>This project is actually a good excuse to finally learn CAD properly and move beyond my caveman methods.&lt;/p>
&lt;p>Side note: If you are also on that path, I highly recommend the great &amp;amp; numerous tutorials from &lt;a href="https://www.youtube.com/@MangoJellySolutions" target="_blank">Mango Jelly&amp;rsquo;s YouTube channel&lt;/a>.&lt;/p>
&lt;h2 id="panels">Panels&lt;/h2>
&lt;p>In the end, I settled on the following dimension: 254x220x44.50 mm. It leaves enough room for the main board and its RAM sticks plus a 30mm buffer on the front for fans and a PicoPSU board.&lt;/p>
&lt;p>The most complex part to sketch-out was the back panel and the IO ports. But using a scanner, I managed to get an accurate layout.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-board-scanner.jpg"
 alt="scanning board IO ports">&lt;figcaption>
 &lt;p>Is it physical nmap?&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>From there, I was able to use this scan to sketch out the panel:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-back-panel.png"
 alt="back panel design in CAD">&lt;figcaption>
 &lt;p>Using the scan for the design&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>The other panels, apart maybe from the fan cutouts, were far simpler to model: a rectangle with four chamferred holes and some recesses for the top panel.&lt;/p>
&lt;p>Initially, due to the lack of space (254mm width for a 250mm board), I tried to use laser cut 2mm PMMA (aka plexiglas). But it proved to be too thin, flexible and britle:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-cracked-2mm-pmma.jpg"
 alt="Cracked 2mm PMMA panel">&lt;figcaption>
 &lt;p>2 mm PMMA cracked – too flimsy&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>In the end, I switched back to 3mm PLA printed panels, with reliefs in places for the board and ram sticks.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-3mm-panel.jpg"
 alt="3mm panel &amp;#43; reliefs">&lt;figcaption>
 &lt;p>Switching to 3mm + reliefs&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>This option is far more rigid, and I might do something similar to the top and bottom panels (these are still in 2mm PMMA).&lt;/p>
&lt;h2 id="air-duct">Air Duct&lt;/h2>
&lt;p>Silencing our little beast requires reworking the CPU heatsink.&lt;/p>
&lt;p>In particular, the CPU FAN needs to go and fortunately it (and its plastic shroud can easily be detached from the heatsink itself.&lt;/p>
&lt;p>But we need to replace it and have some forced draft on the heatsink to keep our venerable server cool.&lt;/p>
&lt;p>To do so, I&amp;rsquo;ve designed an air duct that fits on the heatsink on one end, and to a case mounted 40mm fan on the other.&lt;/p>
&lt;p>This was by far the most complex pieces I designed for this project, and it even required mocking the main board to check the fit,
specially given the limited space, and the need to leave room for the cabling.&lt;/p>
&lt;p>I ended up with a three-part design, constituted of separate top and bottom parts (for easy printing), and a fan shroud, designed to simply slide, and also adapt to different fan depths.&lt;/p>
&lt;p>Here&amp;rsquo;s how it looks in CAD:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-duct-design.png"
 alt="Fan Duct Design">&lt;figcaption>
 &lt;p>Final Fan Duct Design&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;h2 id="bezel">Bezel&lt;/h2>
&lt;p>In all honesty, I don&amp;rsquo;t quite like the V100 front bezel. I don&amp;rsquo;t know, maybe it&amp;rsquo;s a bit too stubby, or too cheap?
I much prefer the V210/V240 front, so I chose to model my bezel after it:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-V100-bezel.jpg"
 alt="V100 Front Bezel">&lt;figcaption>
 &lt;p>V100 Front Bezel&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-V210-bezel.jpg"
 alt="V210 Front Bezel">&lt;figcaption>
 &lt;p>V210 Front Bezel&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>To get the basic shape, I imported the front image, resized to fit 254 × 44.50 mm, and then sketched out the front and back outline:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-bezel-design1.png"
 alt="Sketches Bezel">&lt;figcaption>
 &lt;p>Sketches for bezel&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>From there, it was just a matter of lofting the two sketches:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-bezel-design2.png"
 alt="Bezel Lofting">&lt;figcaption>
 &lt;p>Loft between the two sketches&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>And finally, adding the remaining features (holes, fillets, cut‑out for the logo):&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-bezel-design3.png"
 alt="Bezel Final">&lt;figcaption>
 &lt;p>Bezel final design&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>I&amp;rsquo;m quite happy with the result. I&amp;rsquo;m actually wondering if the concept could be extended to other servers (Dell, HP, Fujitsu, IBM) to pimp out 10‑inch racks.&lt;/p>
&lt;h2 id="logo">Logo&lt;/h2>
&lt;p>The process to create the logo is a bit tedious, mainly due to FreeCAD performance issues, but works as follows:&lt;/p>
&lt;ul>
&lt;li>I started by downloading the Sun logo in the SVG format from wikipedia:&lt;/li>
&lt;/ul>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/Sun-Logo.svg"
 alt="sun logo svg" width="300px">
&lt;/figure>

&lt;ul>
&lt;li>Then, opened it in Inkscape and cleaned-it up (closing path in particular)&lt;/li>
&lt;li>I opened it in FreeCAD&lt;/li>
&lt;li>Switched to the &lt;code>Draft&lt;/code> Workbench&lt;/li>
&lt;li>Select all the parts -&amp;gt; &lt;code>Modification&lt;/code> -&amp;gt; &lt;code>Draft to Sketch&lt;/code> to transform it into a sketch&lt;/li>
&lt;li>Closed the sketches whenever necessary&lt;/li>
&lt;/ul>
&lt;p>From there, I created a body, a base support (ellipse), ticked &lt;code>Allow Compound&lt;/code> in the body properties, padded the ellipse at 1mm, padded the sketch of the logo at 2mm, and added an outter border also at 2mm.&lt;/p>
&lt;p>To print it, I did a &lt;a href="https://marlinfw.org/docs/gcode/M600.html" target="_blank">M600&lt;/a> filament/color change mid-print. Here is a (&lt;a href="https://help.prusa3d.com/article/color-change_1687" target="_blank">PrusaSlicer article on how to do it&lt;/a>).&lt;/p>
&lt;p>And here is the result (printed with a 0.25 nozzle):&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-logo-3dprint.jpg"
 alt="3D printed Sun logo">&lt;figcaption>
 &lt;p>3D‑printed Sun logo with filament color change&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;h1 id="lets-build">Let&amp;rsquo;s Build&lt;/h1>
&lt;h2 id="final-design--assembly">Final Design &amp;amp; Assembly&lt;/h2>
&lt;p>Finally I created the remaining bits (stanoffs, corner brackets, etc), and used the rather new FreeCAD assembly workbench.&lt;/p>
&lt;p>It worked and enabled me to validate and fix numerous dimension &amp;amp; positioning mistakes (holes missaligned, cutouts in the wrong place, parts colliding, etc).&lt;/p>
&lt;p>The Workbench was a really valuable tool, but quite unstable (save as often as possible). Performance also left something to be desired (I fairness, I didn&amp;rsquo;t exactly helped it when for completeness, I added all the nuts and bolts to the assembly).&lt;/p>
&lt;p>But enough complaining, here how the design looks:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-case-design.png"
 alt="Sun V100 Case Design">&lt;figcaption>
 &lt;p>Final Case Design&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>And now, it&amp;rsquo;s time to actually build the thing!&lt;/p>
&lt;p>After a bit of 3D printers go BRRRRH, I was left with this collection of part:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-parts.jpg"
 alt="Sun V100 10‑inch parts">&lt;figcaption>
 &lt;p>All the parts&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>I started with the CPU duct assembly by fitting the inserts. Here, we must install them carefully, as they play double duty: fixation point for the heatsink and used to join the bottom and top parts of the air duct:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-duct-insert.jpg"
 alt="duct insert">&lt;figcaption>
 &lt;p>double duty inserts: attach to heatsink and join upper and lower parts&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>From there, we just screw the assembly from below the heatsink.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-duct-heatsink.jpg"
 alt="duct attached to heatsink">&lt;figcaption>
 &lt;p>Heatsink attachement&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>After that, I just fixed the last part of the air duct to the 40mm FAN, itself screwed to the front panel alongside its twin.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-front-fan.jpg"
 alt="front panel with fan">&lt;figcaption>
 &lt;p>Front panel with fan&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>And then it was the tedious job of installing the inserts into all the brackets and stand-offs.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-bracket-insert.jpg"
 alt="bracket plus insert">&lt;figcaption>
 &lt;p>5 inserts done, 46 to go&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>From there, and with a few screws, things started to take shape.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-partial-case.jpg"
 alt="partially assembled case">&lt;figcaption>
 &lt;p>Partially assembled case&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>And with a bit of epoxy glue to fix the standoffs to the bottom panel (and also to the top for the CF card), the boards were finally secured inside the case, and with a bit of cable management, things looked like that:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-case-inside.jpg"
 alt="sun case inside and cable management">&lt;figcaption>
 &lt;p>final assembly&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>Time to close it up and admire the result.&lt;/p>
&lt;h2 id="look-at-this-beauty">Look At This Beauty!&lt;/h2>
&lt;p>Overall, I’m really happy with the result. This new case is much more compact, quiet, and fit to be used in my small apartment.&lt;/p>
&lt;p>Plus, it still looks like a Sun Server:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-final-front.jpg"
 alt="SunFire V100 Custom Case - Front">&lt;figcaption>
 &lt;p>Silly Sun Server - Front&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>Round the back, the I/O cut‑outs also fit nicely:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-final-back.jpg"
 alt="SunFire V100 Custom Case - Back">&lt;figcaption>
 &lt;p>Silly Sun Server - Back&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>In case that interests you, I&amp;rsquo;ve made the CAD files for this project available &lt;a href="https://github.com/kakwa/sun-v100-case" target="_blank">on Github&lt;/a>.&lt;/p>
&lt;p>To be honest, it’s not perfect. The top/bottom panels still flex a bit and the CPU temperature is somewhat high at ~70 °C.&lt;/p>
&lt;p>On the power‑consumption side, it&amp;rsquo;s “not great — not terrible”.
This server draws 20–25 W, vs the typical 5–10 W of mini‑PC or laptop.&lt;/p>
&lt;p>But these are far from deal‑breakers, and now this server can actually become useful… if we manage to install something onto it.&lt;/p>
&lt;p>Believe it or not, &lt;a href="https://technically.kakwalab.ovh/posts/silly-sun-server-software/">it&amp;rsquo;s exactly what we will address in the next post (Part 3/3)&lt;/a> (huge plot twist, I know&amp;hellip;).&lt;/p>
&lt;h1 id="links">Links&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://technically.kakwalab.ovh/posts/silly-sun-server-intro/">Part 1/3 - Introduction&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://technically.kakwalab.ovh/posts/silly-sun-server-software/">Part 3/3 - Software&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://github.com/kakwa/sun-v100-case" target="_blank">My 10 inch case CAD models&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://www.freecad.org/" target="_blank">FreeCAD Project&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/@MangoJellySolutions" target="_blank">Mango Jelly&amp;rsquo;s FreeCAD Tutorials&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/CompactFlash" target="_blank">CompactFlact Wikipedia&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/USB_hardware#USB_Power_Delivery" target="_blank">USB Power Delivery Wikipedia&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://hackaday.com/2023/01/09/all-about-usb-c-power-delivery/" target="_blank">All About USB-C: Power Delivery (Hackaday)&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>My Silly Sun Server - Part 1/3: Introduction</title><link>https://technically.kakwalab.ovh/posts/silly-sun-server-intro/</link><pubDate>Wed, 27 Aug 2025 19:01:49 +0200</pubDate><author>carpentier.pf@gmail.com (Pierre-François Carpentier)</author><guid>https://technically.kakwalab.ovh/posts/silly-sun-server-intro/</guid><description>&lt;h1 id="obsolete-tech-in-a-modern-age">Obsolete Tech In A Modern Age&lt;/h1>
&lt;h2 id="something-old-is-new-again">Something Old Is New Again&lt;/h2>
&lt;p>One of my pet peeves is to bring new life into old hardware.&lt;/p>
&lt;p>Here, I&amp;rsquo;m not thinking in a retro‑computing kind of way. Software is not exactly like fine wine, in my opinion—it usually doesn&amp;rsquo;t age well.&lt;/p>
&lt;p>Also, old software tends to be isolated in its own bubble—amusing, but unless you are &lt;a href="https://www.youtube.com/watch?v=X5REM-3nWHg" target="_blank">George R. R. Martin rocking WordStar 4.0 on MS‑DOS&lt;/a>, it&amp;rsquo;s not really useful.&lt;/p>
&lt;p>I prefer the challenge of trying to install modern software on these antiquities, make them able to interact with the internet, and do something useful with them.&lt;/p>
&lt;p>Sure, it will not run a Kubernetes cluster, but these old machines generally still have enough oomph for lightweight use cases such as:&lt;/p>
&lt;ul>
&lt;li>MPD/Music server (for example, coupled with an analog hi‑fi system)&lt;/li>
&lt;li>Basic web hosting (blog, RSS aggregator, etc)&lt;/li>
&lt;li>Mail server&lt;/li>
&lt;li>Test/lab machines&lt;/li>
&lt;/ul>
&lt;h2 id="the-little-server-that-could-not">The Little Server That Could (Not)&lt;/h2>
&lt;p>Enter the Sun V100, the 2001 entry‑level server from Sun Microsystems.&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-v100-original.jpg"
 alt="Sun V100 Front">&lt;figcaption>
 &lt;p>Sun V100 1U Server&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>It boasts impressive specs such as:&lt;/p>
&lt;ul>
&lt;li>UltraSPARC‑IIe CPU @ 548 MHz&lt;/li>
&lt;li>No GPU whatsoever (not great for AI, I guess :p)&lt;/li>
&lt;li>2 GB RAM (if maxed out)&lt;/li>
&lt;li>2 × 80 GB IDE hard drives&lt;/li>
&lt;li>Two 100 Mbit/s NICs&lt;/li>
&lt;li>LOMlite management over serial&lt;/li>
&lt;li>Second serial port&lt;/li>
&lt;/ul>
&lt;p>In truth, this beast is a little asthmatic, even by 2001 standards.
Still, the SPARC CPU is interesting, especially for testing endianness and memory alignment issues.
And unlike most servers of that era, it doesn’t need a huge amount of power (~15 W TDP).&lt;/p>
&lt;p>I got a few of these years ago for cheap, but, in truth, never did anything really useful with them.
While not as bad as other monsters from the era, these cuties are simply a bit too big and loud.
So, they sat in my cupboard for ages, in the dark, lonely, unused and far away from the information highways.&lt;/p>
&lt;p>But let&amp;rsquo;s try to change that, have a fun project, and maybe learn a few things along the way.&lt;/p>
&lt;h1 id="a-two-front-project">A Two-Front Project&lt;/h1>
&lt;p>So yes, I aim to do something with this cute little server from another time.
Not very rational and probably a bit challenging—but that&amp;rsquo;s what makes it fun!&lt;/p>
&lt;p>But enough said! Now, we need to address the two broad complaints we have about this relic:&lt;/p>
&lt;ol>
&lt;li>Make this server way smaller and quieter&lt;/li>
&lt;li>Find and install a modern OS + up‑to‑date software to make it useful&lt;/li>
&lt;/ol>
&lt;h2 id="the-hardware-side">The Hardware Side&lt;/h2>
&lt;h3 id="make-it-smaller">Make It Smaller&lt;/h3>
&lt;p>This (not so) tiny server is well&amp;hellip; a server. If it fits onto a small and standard 1U height (44.50 mm), it&amp;rsquo;s also kind of big in the other dimensions: 19&amp;quot;/482.60 mm by 17.55&amp;quot;/445 mm.&lt;/p>
&lt;p>Fortunately, once opened, the server looks like this:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-inside.jpg"
 alt="Sun Fire V100 opened showing mainboard and layout in original case">&lt;figcaption>
 &lt;p>Sun Fire V100 with top cover removed – original layout&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>This cute beast could probably be a lot more compact. The main board, including the RAM sticks (low‑height variant), is 250 × 190 mm.
If we ditch the original (and noisy) hard drives and PSU and cheat a little, we could even make it fit into a 254 mm (10&amp;quot;) case and mount it in one of these fancy small &lt;a href="https://mini-rack.jeffgeerling.com/" target="_blank">10‑inch racks&lt;/a>&amp;hellip; if we also manage to make it short enough (less than 300 mm).&lt;/p>
&lt;p>This gives goal number one:&lt;/p>
&lt;ul>
&lt;li>make it fit into 254 × 250 × 44 mm.&lt;/li>
&lt;/ul>
&lt;p>And also, if possible, as goal number one‑bis:&lt;/p>
&lt;ul>
&lt;li>retain the original &amp;ldquo;Sun vibe&amp;rdquo; look.&lt;/li>
&lt;/ul>
&lt;h3 id="make-it-quieter">Make It Quieter&lt;/h3>
&lt;p>Aside from that, we also need to silence this small beast.&lt;/p>
&lt;p>In particular, we need to take care of these little bastards:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-fan.jpg"
 alt="Sun V100 40×40 mm 12 V fan">&lt;figcaption>
 &lt;p>Original 40×40 mm 12 V fan&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>And also their lord and master:&lt;/p>
&lt;figure>&lt;img src="https://technically.kakwalab.ovh/images/sun/sun-cpu-fan.jpg"
 alt="Sun V100 CPU 12 V fan">&lt;figcaption>
 &lt;p>Original CPU 12 V fan&lt;/p>
 &lt;/figcaption>
&lt;/figure>

&lt;p>Fortunately, we have options here—especially one that begins with an N.&lt;/p>
&lt;p>Since I live in a small apartment, noise levels need to be reasonable—roughly at small NAS or fan‑cooled Wi‑Fi router levels.&lt;/p>
&lt;p>That&amp;rsquo;s goal number two:&lt;/p>
&lt;ul>
&lt;li>bring the noise down to something fit for a living space.&lt;/li>
&lt;/ul>
&lt;h2 id="the-software-side">The Software Side&lt;/h2>
&lt;h3 id="operating-system-choice">Operating System Choice&lt;/h3>
&lt;p>The sad reality is SPARC is a dying architecture. So our choices, as of 2025, are limited:&lt;/p>
&lt;ul>
&lt;li>(Open)Solaris -&amp;gt; dead&lt;/li>
&lt;li>Illumos (OpenSolaris successor) -&amp;gt; &lt;a href="https://github.com/illumos/ipd/blob/master/ipd/0019/README.md" target="_blank">Support is being dropped&lt;/a>&lt;/li>
&lt;li>Linux -&amp;gt; SPARC64 is still supported by the kernel, but hardly any mainstream distribution supports it (&lt;a href="https://wiki.debian.org/Sparc64" target="_blank">Debian unofficially&lt;/a> and &lt;a href="https://wiki.gentoo.org/wiki/Project:SPARC" target="_blank">Gentoo&lt;/a>).&lt;/li>
&lt;li>FreeBSD -&amp;gt; Support dropped with &lt;a href="https://www.freebsd.org/platforms/sparc/" target="_blank">FreeBSD 13&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>This leaves more or less two choices:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://wiki.netbsd.org/ports/sparc64/" target="_blank">NetBSD&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.openbsd.org/sparc64.html" target="_blank">OpenBSD&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Since burning CDs is kind of annoying, and given we will probably ditch the CD drive anyway, we will need to net‑install the OS.&lt;/p>
&lt;p>That&amp;rsquo;s goal number three:&lt;/p>
&lt;ul>
&lt;li>Deploy a net‑install server (similar to a &lt;a href="https://docs.oracle.com/cd/E26505_01/html/E28039/customjumpsample-5.html#scrolltoc" target="_blank">JumpStart&lt;/a> setup) and install NetBSD and/or OpenBSD.&lt;/li>
&lt;/ul>
&lt;h3 id="do-something-useful">Do Something Useful&lt;/h3>
&lt;p>Here, nothing fancy: we will go for basic web hosting (nginx, Let&amp;rsquo;s Encrypt, maybe a bit of Postgres &amp;amp; PHP), but since I&amp;rsquo;m an infra guy who likes infrastructure‑as‑code, let&amp;rsquo;s set goal number four:&lt;/p>
&lt;ul>
&lt;li>Create a bit of Ansible to do something with our new toy.&lt;/li>
&lt;/ul>
&lt;h1 id="the-next-steps">The Next Steps&lt;/h1>
&lt;p>So to recap, we have the following four goals:&lt;/p>
&lt;ol>
&lt;li>Make the server quieter&lt;/li>
&lt;li>Make the server smaller&lt;/li>
&lt;li>Net‑install NetBSD and/or OpenBSD&lt;/li>
&lt;li>Configure some basic web hosting&lt;/li>
&lt;/ol>
&lt;p>Next, we&amp;rsquo;ll focus on the &lt;a href="https://technically.kakwalab.ovh/posts/silly-sun-server-hardware/">hardware track (Part 2/3)&lt;/a> (1 and 2): taming the noise and re‑housing the V100 into a compact 10‑inch chassis while keeping the original Sun aesthetic.&lt;/p>
&lt;p>After that, we&amp;rsquo;ll dive into the &lt;a href="https://technically.kakwalab.ovh/posts/silly-sun-server-software/">software track (Part 3/3)&lt;/a> (3 and 4): net‑installing NetBSD/OpenBSD and automating a minimal web stack (nginx, Let&amp;rsquo;s Encrypt, maybe a dash of Postgres/PHP) with Ansible.&lt;/p></description></item><item><title>Reversing WoWs Resource Format - Part 5/5: Tidying-Up The Project</title><link>https://technically.kakwalab.ovh/posts/wows_depack_part5/</link><pubDate>Wed, 27 Aug 2025 00:04:00 +0200</pubDate><author>carpentier.pf@gmail.com (Pierre-François Carpentier)</author><guid>https://technically.kakwalab.ovh/posts/wows_depack_part5/</guid><description>&lt;ul>
&lt;li>Part 1 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part1/">Searching The Data&lt;/a>&lt;/li>
&lt;li>Part 2 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part2/">Looking For The Metadata&lt;/a>&lt;/li>
&lt;li>Part 3 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part3/">Dissecting The Index&lt;/a>&lt;/li>
&lt;li>Part 4 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part4/">Reading Everything&lt;/a>&lt;/li>
&lt;li>Part 5 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part5/">Tidying-Up The Project&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="tidying-up-the-project">Tidying-Up The Project&lt;/h1>
&lt;p>In the last part we got a first rough implementation.&lt;/p>
&lt;p>In this final part, we will clean up things, correct a few shortcuts we took, and create an actual project out of this.&lt;/p>
&lt;h2 id="creating-unit-tests">Creating Unit Tests&lt;/h2>
&lt;p>I did this project right around the release of ChatGPT 3.5. Initially I didn&amp;rsquo;t plan to add unit tests. But after giving the struct definitions and the format documentation, ChatGPT was able to generate test cases which, while not completely functional, were close enough to start with. It seems evident now, but at the time I was kind of blown away by it.&lt;/p>
&lt;p>In the end, you can probably thank our AI overlords for the unit tests of this project. And it was a lifesaver when I significantly reworked the data loading from &lt;code>mmap&lt;/code> to a proper unpacking taking endianness into account.&lt;/p>
&lt;p>Also, I&amp;rsquo;ve used the usual suspects of GitHub Actions + CUnit + lcov for CI and code coverage measurement.&lt;/p>
&lt;h2 id="fuzzing">Fuzzing&lt;/h2>
&lt;p>C being the both-ways shotgun it is, you are most likely to get things wrong, especially in the unhappy paths.&lt;/p>
&lt;p>In that regard, leveraging fuzzing &amp;amp; &lt;a href="https://github.com/AFLplusplus/AFLplusplus" target="_blank">AFL++&lt;/a> greatly helps in catching memory issues. It works by taking a collection of valid input files (here, the &lt;code>.idx&lt;/code> files) and tweaking them to try triggering crashes.&lt;/p>
&lt;p>Here is the gist of using it:&lt;/p>
&lt;ul>
&lt;li>Install AFL++ and build with AFL++ Instrumentation:&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Debian/Ubuntu&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>apt install afl-clang
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cmake -DCMAKE_C_COMPILER&lt;span style="color:#f92672">=&lt;/span>afl-clang -DCMAKE_CXX_COMPILER&lt;span style="color:#f92672">=&lt;/span>afl-clang++ .
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>make
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>Run with a collection of valid &lt;code>.idx&lt;/code> files&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>INDEX_DIR&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;/path/to/WoWs/bin/6831266/idx/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>afl-fuzz -i &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$INDEX_DIR&lt;span style="color:#e6db74">&amp;#34;&lt;/span> -o ./out -t &lt;span style="color:#ae81ff">10000&lt;/span> -- ./wows-depack-cli -i &lt;span style="color:#e6db74">&amp;#39;@@&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Crashes go to &lt;code>out/crashes/&lt;/code> and can then be investigated using &lt;code>gdb&lt;/code>, and potentially used as test/non-regression tests.&lt;/p>
&lt;h2 id="api-documentation">API Documentation&lt;/h2>
&lt;p>Here, we simply call good old &lt;a href="https://doxygen.nl/" target="_blank">Doxygen&lt;/a> to the rescue.&lt;/p>
&lt;p>The Doxygen annotations are easy to write these days using LLMs: if your naming scheme is decent enough, simply feeding the header definitions (structs and functions) will get you 90% of the way there. Add a few fixes, and you are in business.&lt;/p>
&lt;p>Prettier docs are also slightly more likely to be read, so I&amp;rsquo;m using &lt;a href="https://github.com/jothepro/doxygen-awesome-css" target="_blank">this nice theme&lt;/a>. Just point to it in &lt;code>Doxyfile.in&lt;/code> (&lt;code>HTML_EXTRA_STYLESHEET&lt;/code>).&lt;/p>
&lt;p>And lastly, to keep it up to date, I simply combined GitHub Actions &amp;amp; GitHub Pages to maintain and publish it.&lt;/p>
&lt;h2 id="proper-unpacking">Proper Unpacking&lt;/h2>
&lt;p>Initially, I did the unpacking using &lt;code>mmap&lt;/code> + &amp;ldquo;casting&amp;rdquo; to structs. While it works, it&amp;rsquo;s a bit dangerous as endianness can become an issue, as is data alignment in structs (forces &lt;code>#pragma pack 1&lt;/code> which might not work on every architecture).&lt;/p>
&lt;p>So I significantly reworked the project to properly read the file field by field, handling endianness along the way. It was a bit painful to do (having unit tests helped a lot in avoiding regressions there), but now the project is much cleaner on that front.&lt;/p>
&lt;h1 id="annex-1---a-few-links">Annex 1 - A Few Links&lt;/h1>
&lt;ul>
&lt;li>&lt;strong>Game&lt;/strong>: &lt;a href="https://store.steampowered.com/app/552990/World_of_Warships/" target="_blank">WoWs&lt;/a>&lt;/li>
&lt;li>&lt;strong>Closed Source Utility&lt;/strong>: &lt;a href="https://raw.githubusercontent.com/wowsinfo/wowsunpack/refs/heads/master/src/wowsunpack/wowsunpack.exe" target="_blank">wowsunpack.exe&lt;/a>&lt;/li>
&lt;li>&lt;strong>My Project&lt;/strong>: &lt;a href="https://github.com/kakwa/wows-depack" target="_blank">wows-depack (GitHub)&lt;/a>&lt;/li>
&lt;li>&lt;strong>Similar Project in Rust&lt;/strong>: &lt;a href="https://github.com/landaire/wowsunpack" target="_blank">wowsunpack (GitHub)&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="annex-2---misc-reverse-engineering-tips">Annex 2 - Misc Reverse Engineering Tips&lt;/h1>
&lt;h2 id="data-type-identification">Data Type Identification&lt;/h2>
&lt;p>These are more rule-of-thumb patterns and need to be used while looking at the surrounding data.&lt;/p>
&lt;h3 id="unsigned-integers">Unsigned integers&lt;/h3>
&lt;p>32-bit integers generally look like: &lt;code>02 2F 00 00&lt;/code>: higher bytes tend to be &lt;code>00&lt;/code>, lower bytes tend to be used.&lt;/p>
&lt;p>The same thing applies to 64-bit integers.&lt;/p>
&lt;p>They are typically used to describe the following elements:&lt;/p>
&lt;ul>
&lt;li>counts&lt;/li>
&lt;li>size&lt;/li>
&lt;li>offsets&lt;/li>
&lt;/ul>
&lt;p>Offsets tend to have values divided by 4 or 8 (32 or 64 bits blocks), they also tend to be 64 bits these days (&lt;code>size_t&lt;/code>).&lt;/p>
&lt;p>32-bit low-value integers tend to be counts or string sizes.&lt;/p>
&lt;h3 id="float">Float&lt;/h3>
&lt;p>32-bit floats generally have all 4 bytes used, with nearly no zero nibbles, for example: &lt;code>b1 61 33 78&lt;/code>.&lt;/p>
&lt;p>These are difficult to distinguish from random ints at a glance; they need to be parsed and checked to see if the values are consistent (similar to other fields, within set boundaries, etc.).&lt;/p>
&lt;h3 id="rgba">RGBA&lt;/h3>
&lt;p>RGBA look like: &lt;code>7f 7f fe 00&lt;/code> or &lt;code>00 ff fe 00&lt;/code>. It&amp;rsquo;s an array of 4 bytes &lt;code>{R,G,B,A}&lt;/code> which tend to have recurring values, and often with 1 or 2 bytes in the extreme (&lt;code>00&lt;/code> or &lt;code>ff&lt;/code>).&lt;/p>
&lt;p>example:&lt;/p>
&lt;pre tabindex="0">&lt;code>0000b9a0 7f 7f fe 00 82 4b f0 3e 11 f7 02 3f 98 11 9e bf |.....K.&amp;gt;...?....|
0000ba00 7f 7f 00 00 67 cf c5 3e 10 f7 02 3f 98 11 9e bf |....g..&amp;gt;...?....|
0000ba10 00 7f 7f 00 67 cf c5 3e 15 f7 02 3f 36 e0 8c bf |....g..&amp;gt;...?6...|
&lt;/code>&lt;/pre>&lt;p>Here &lt;code>7f 7f fe 00&lt;/code>, &lt;code>7f 7f 00 00&lt;/code> and &lt;code>00 7f 7f 00&lt;/code> are like RGBA values (the rest being floats).&lt;/p>
&lt;h3 id="strings">Strings&lt;/h3>
&lt;p>A bunch of printable characters, often &lt;code>00&lt;/code>-terminated.&lt;/p>
&lt;pre tabindex="0">&lt;code>00015280 7f fe 7f 00 43 4d 5f 50 41 5f 75 6e 69 74 65 64 |....CM_PA_united|
00015290 2e 61 72 6d 6f 72 00 |.armor.|
&lt;/code>&lt;/pre>&lt;h2 id="tools">Tools&lt;/h2>
&lt;h3 id="file">File&lt;/h3>
&lt;p>Just a very simple utility to check for known file signatures:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>file *
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="strings-1">Strings&lt;/h3>
&lt;p>Tool to try extracting the strings contained in a given file:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>strings -n &amp;lt;MIN_STR_LENGTH&amp;gt; &amp;lt;FILE&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There will be a bit of noise (increasing MIN_STR_LENGTH reduces it), but it should give you interesting human-readable strings contained in a given file.&lt;/p>
&lt;h3 id="hexdump">Hexdump&lt;/h3>
&lt;p>hexdump is my go-to tool for investigating binary data. I especially like the &lt;code>hexdump -C FILE | less&lt;/code> combo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>hexdump -C &amp;lt;FILE&amp;gt; | less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you are investigating a specific section of a file, you can start at a given offset with the &lt;code>-s &amp;lt;SKIPPED_BYTES&amp;gt;&lt;/code> option; this will make things easier to read and navigate and help determine the data alignment:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>hexdump -s &amp;lt;SKIPPED_BYTES&amp;gt; -C &amp;lt;FILE&amp;gt; | less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To get a general feel, don&amp;rsquo;t hesitate to loop over files and display the first bits of a collection:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>find ./ -name &lt;span style="color:#e6db74">&amp;#39;*.geometry&amp;#39;&lt;/span> | &lt;span style="color:#66d9ef">while&lt;/span> read file;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> hexdump -C $file | head -n 6;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="imhex">ImHex&lt;/h3>
&lt;p>While I&amp;rsquo;ve not used it here, you should give a try to &lt;a href="https://github.com/WerWolv/ImHex" target="_blank">ImHex&lt;/a>. I&amp;rsquo;ve used it in subsequent works, and it&amp;rsquo;s an amazing tool that greatly helps in determining and validating the data structure of binary files.&lt;/p>
&lt;h1 id="annex-3---file-specification">Annex 3 - File Specification&lt;/h1>
&lt;h2 id="general-information">General Information&lt;/h2>
&lt;p>The WoWs resources are packed into something similar to a &lt;code>.zip&lt;/code> archive (WoTs and WoWPs actually use ZIP files).&lt;/p>
&lt;p>Each archive is actually separated into two files:&lt;/p>
&lt;ul>
&lt;li>a &lt;code>.pkg&lt;/code> containing the compressed files concatenated together. This file is in the &lt;code>res_packages/&lt;/code> directory.&lt;/li>
&lt;li>a &lt;code>.idx&lt;/code> containing the index of the files contained in the &lt;code>.pkg&lt;/code> and their metadata (name, path, type, etc). This file is located in the &lt;code>./bin/&amp;lt;build_number&amp;gt;/idx/&lt;/code> directory.&lt;/li>
&lt;/ul>
&lt;h2 id="convention">Convention&lt;/h2>
&lt;p>A byte/8 bits is represented as follows:&lt;/p>
&lt;pre tabindex="0">&lt;code>+====+
| XX |
+====+
&lt;/code>&lt;/pre>&lt;p>A variable length field (ex: strings) is represented as follows:&lt;/p>
&lt;pre tabindex="0">&lt;code>|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
| Field |
|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
&lt;/code>&lt;/pre>&lt;p>The boundary between two fields is marked as follows:&lt;/p>
&lt;pre tabindex="0">&lt;code>...=++=...
 ||
...=++=...
&lt;/code>&lt;/pre>&lt;h2 id="misc">Misc&lt;/h2>
&lt;p>Integers (offset and size in particular) are &lt;code>Big Endian&lt;/code>.&lt;/p>
&lt;p>Strings seem limited to ASCII and are &lt;code>\0&lt;/code> terminated.&lt;/p>
&lt;h2 id="index-file">Index file&lt;/h2>
&lt;h3 id="general-layout">General Layout&lt;/h3>
&lt;p>The index file is composed of 5 sections:&lt;/p>
&lt;pre tabindex="0">&lt;code>|~~~~~~~~~~~~~~~~~~~~~~~~~~~~| ^
| Header | } index header (number of files, pointers to sections, etc)
|~~~~~~~~~~~~~~~~~~~~~~~~~~~~| v

|~~~~~~~~~~~~~~~~~~~~~~~~~~~~| ^
| File metadata 1 | |
|----------------------------| |
| [...] | } metadata section (pointer to name, type, etc)
|----------------------------| |
| File metadata Nfd | |
|~~~~~~~~~~~~~~~~~~~~~~~~~~~~| v

|~~~~~~~~~~~~~~~~~~~~~~~~~~~~| ^
| File Name 1 | |
|----------------------------| |
| [...] | } file names (`\0` separated strings)
|----------------------------| |
| File Name Nfd | |
|~~~~~~~~~~~~~~~~~~~~~~~~~~~~| v

|~~~~~~~~~~~~~~~~~~~~~~~~~~~~| ^
| File `.pkg` pointers 1 | |
|----------------------------| |
| [...] | } pkg pointers section in the `.pkg` file (offsets)
|----------------------------| |
| File `.pkg` pointers Nf | |
|~~~~~~~~~~~~~~~~~~~~~~~~~~~~| v

|~~~~~~~~~~~~~~~~~~~~~~~~~~~~| ^
| Footer | } index footer (corresponding `.pkg` file name)
|~~~~~~~~~~~~~~~~~~~~~~~~~~~~| V
&lt;/code>&lt;/pre>&lt;h3 id="header">Header&lt;/h3>
&lt;h4 id="layout">Layout&lt;/h4>
&lt;pre tabindex="0">&lt;code>+====+====+====+====++====+====+====+====++====+====+====+====++====+====+====+====+
| MA | MA | MA | MA || 00 | 00 | 00 | 02 || ID | ID | ID | ID || 40 | 00 | 00 | 00 |
+====+====+====+====++====+====+====+====++====+====+====+====++====+====+====+====+
|&amp;lt;----- magic -----&amp;gt;||&amp;lt;--- endianess ---&amp;gt;||&amp;lt;------- id ------&amp;gt;||&amp;lt;--- unknown_2 ---&amp;gt;|
| 32 bits || 32 bits || 32 bits || 32 bits |

+====+====+====+====++====+====+====+====++====+====+====+====++====+====+====+====+
| FD | FD | FD | FD || FO | FO | FO | FO || 01 | 00 | 00 | 00 || 00 | 00 | 00 | 00 |
+====+====+====+====++====+====+====+====++====+====+====+====++====+====+====+====+
|&amp;lt;- file_dir_count &amp;gt;||&amp;lt;-- file_count ---&amp;gt;||&amp;lt;-------------- unknown_3 -------------&amp;gt;|
| 32 bits || 32 bits || 64 bits |

+====+====+====+====+====+====+====+=====++=====+====+====+====+====+====+====+====+
| HS | HS | HS | HS | HS | HS | HS | HS || OF | OF | OF | OF | OF | OF | OF | OF |
+====+====+====+====+====+====+====+=====++=====+====+====+====+====+====+====+====+
|&amp;lt;------------- header_size ------------&amp;gt;||&amp;lt;------- offset_idx_data_section ------&amp;gt;|
| 64 bits || 64 bits |

+====+====+====+====+====+====+====+=====+
| OE | OE | OE | OE | OE | OE | OE | OE |
+====+====+====+====+====+====+====+=====+
|&amp;lt;----- offset_idx_footer_section ------&amp;gt;|
| 64 bits |
&lt;/code>&lt;/pre>&lt;h4 id="field-descriptions">Field descriptions&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Field&lt;/th>
&lt;th>size&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>magic&lt;/code>&lt;/td>
&lt;td>32 bits&lt;/td>
&lt;td>Magic bytes, always &amp;ldquo;ISFP&amp;rdquo;&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>endianess&lt;/code>&lt;/td>
&lt;td>32 bits&lt;/td>
&lt;td>Endianess marker, always 0x2000000 if LE&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>id&lt;/code>&lt;/td>
&lt;td>32 bits&lt;/td>
&lt;td>Unsure, unique per index file, not referenced anywhere else&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>unknown_2&lt;/code>&lt;/td>
&lt;td>32 bits&lt;/td>
&lt;td>Unknown, always 0x40, maybe some offset&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>file_dir_count&lt;/code>&lt;/td>
&lt;td>32 bits&lt;/td>
&lt;td>Number of files + directories (Nfd), also number of entries in the metadata section and the file names section&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>file_count&lt;/code>&lt;/td>
&lt;td>32 bits&lt;/td>
&lt;td>Number of files (Nf), also the number of entries in the file pkg pointers section&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>unknown_3&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>Unknown, always &amp;lsquo;1&amp;rsquo;, maybe the number of &lt;code>.pkg&lt;/code> file the index file references (the format hints that it might be supported, but it&amp;rsquo;s not used)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>header_size&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>Most likely the header size, always 40&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>offset_idx_data_section&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>Offset to the pkg data section, the offset is computed from &lt;code>file_plus_dir_count&lt;/code> so &lt;code>0x10&lt;/code> needs to be added&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>offset_idx_footer_section&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>Offset to the footer section, the offset is computed from &lt;code>file_plus_dir_count&lt;/code> so &lt;code>0x10&lt;/code> needs to be added&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="file-metadata">File Metadata&lt;/h3>
&lt;p>This section is repeated for each file and directory (&lt;code>header-&amp;gt;file_dir_count&lt;/code>).&lt;/p>
&lt;h4 id="layout-1">Layout&lt;/h4>
&lt;pre tabindex="0">&lt;code>+====+====+====+====+====+====+====+=====++=====+====+====+====+====+====+====+====+
| NS | NS | NS | NS | NS | NS | NS | NS || OF | OF | OF | OF | OF | OF | OF | OF |
+====+====+====+====+====+====+====+=====++=====+====+====+====+====+====+====+====+
|&amp;lt;---------- file_name_size ------------&amp;gt;||&amp;lt;-------- offset_idx_file_name --------&amp;gt;|
| 64 bits || 64 bits |

+====+====+====+====+====+====+====+=====++=====+====+====+====+====+====+====+====+
| UN | UN | UN | UN | UN | UN | UN | UN || T2 | T2 | T2 | T2 | T2 | T2 | T2 | T2 |
+====+====+====+====+====+====+====+=====++=====+====+====+====+====+====+====+====+
|&amp;lt;----------------- id -----------------&amp;gt;||&amp;lt;------------- parent_id -------------&amp;gt;|
| 64 bits || 64 bits |

[...repeat...]
&lt;/code>&lt;/pre>&lt;h4 id="field-descriptions-1">Field descriptions&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Field&lt;/th>
&lt;th>Size&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>file_name_size&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>Size of the file name string&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>offset_idx_file_name&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>Offset from the start of the current metadata record to the start of the file name string&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>id&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>Unique ID of the metadata record&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>parent_id&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>ID of the potential parent record (in particular, a directory record)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="file-names-section">File names section&lt;/h3>
&lt;p>This section is just &lt;code>\0&lt;/code> separated list of strings:&lt;/p>
&lt;h4 id="layout-2">Layout&lt;/h4>
&lt;pre tabindex="0">&lt;code>+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+====+
| file name string | 00 |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+====+
[...repeat...]
&lt;/code>&lt;/pre>&lt;h3 id="file-pkg-pointers">File &amp;ldquo;.pkg&amp;rdquo; Pointers&lt;/h3>
&lt;p>This section is repeated for each file (&lt;code>header-&amp;gt;file_count&lt;/code>).&lt;/p>
&lt;h4 id="layout-3">Layout&lt;/h4>
&lt;pre tabindex="0">&lt;code>+====+====+====+====+====+====+====+====++=====+====+====+====+====+====+====+====+
| UO | UO | UO | UO | UO | UO | UO | UO || UT | UT | UT | UT | UT | UT | UT | UT |
+====+====+====+====+====+====+====+====++=====+====+====+====+====+====+====+====+
|&amp;lt;----------- metadata_id -------------&amp;gt;||&amp;lt;------------- footer_id --------------&amp;gt;|
| 64 bits || 64 bits |

+====+====+====+====+====+====+====+====++====+====+====+====++====+====+====+====+
| OF | OF | OF | OF | OF | OF | OF | OF || T1 | T1 | T1 | T1 || T2 | T2 | T2 | T2 |
+====+====+====+====+====+====+====+====++====+====+====+====++====+====+====+====+
|&amp;lt;--------- offset_pkg_data -----------&amp;gt;||&amp;lt;---- type_1 -----&amp;gt;||&amp;lt;----- type_2 ----&amp;gt;|
| 64 bits || 32 bits || 32 bits |

+====+====+====+====++====+====+====+====+====+====+====+====++====+====+====+====+
| OE | OE | OE | OE || ID | ID | ID | ID | ID | ID | ID | ID || 00 | 00 | 00 | 00 |
+====+====+====+====++====+====+====+====+====+====+====+====++====+====+====+====+
|&amp;lt;- size_pkg_data -&amp;gt;||&amp;lt;------------ id_pkg_data ------------&amp;gt;||&amp;lt;---- padding ----&amp;gt;|
| 32 bits || 64 bits || 32 bits |
[...repeat...]
&lt;/code>&lt;/pre>&lt;h4 id="field-descriptions-2">Field Descriptions&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Field&lt;/th>
&lt;th>Size&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>metadata_id&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>ID of the corresponding metadata entry&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>footer_id&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>ID of the footer entry (only one entry possible in practice)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>offset_pkg_data&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>Offset to the compressed data from the start of the &lt;code>.pkg&lt;/code> file&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>type_1&lt;/code>&lt;/td>
&lt;td>32 bits&lt;/td>
&lt;td>Compression param 1 (&lt;code>0x0&lt;/code> for uncompressed, &lt;code>0x5&lt;/code> for deflate)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>type_2&lt;/code>&lt;/td>
&lt;td>32 bits&lt;/td>
&lt;td>Compression param 2 (&lt;code>0x0&lt;/code> for uncompressed, &lt;code>0x1&lt;/code> for deflate)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>size_pkg_data&lt;/code>&lt;/td>
&lt;td>32 bits&lt;/td>
&lt;td>Size of the compressed data section in the &lt;code>.pkg&lt;/code> file&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>id_pkg_data&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>ID of the data section in the &lt;code>.pkg&lt;/code> file&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>padding&lt;/code>&lt;/td>
&lt;td>32 bits&lt;/td>
&lt;td>Always &lt;code>0x00000000&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="footer">Footer&lt;/h3>
&lt;h4 id="layout-4">Layout&lt;/h4>
&lt;pre tabindex="0">&lt;code>+====+====+====+====+====+====+====+====++=====+====+====+====+====+====+====+====+
| UO | UO | UO | UO | UO | UO | UO | UO || U3 | U3 | U3 | U3 | U3 | U3 | U3 | U3 |
+====+====+====+====+====+====+====+====++=====+====+====+====+====+====+====+====+
|&amp;lt;--------- pkg_file_name_size --------&amp;gt;||&amp;lt;-------------- unknown_7 -------------&amp;gt;|
| 64 bits || 64 bits |

+====+====+====+====+====+====+====+====++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~...
| UT | UT | UT | UT | UT | UT | UT | UT || pkg_file_name_string
+====+====+====+====+====+====+====+====++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~...
|&amp;lt;----------------- id -----------------&amp;gt;|
| 64 bits |
&lt;/code>&lt;/pre>&lt;h4 id="field-descriptions-3">Field descriptions&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Field&lt;/th>
&lt;th>Size&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>pkg_file_name_size&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>Size of the corresponding &lt;code>.pkg&lt;/code> file name string&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>unknown_7&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>unknown, looks like an ID&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>id&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>ID of the footer entry&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="pkg-format">PKG format&lt;/h2>
&lt;h3 id="pkg-entry">PKG Entry&lt;/h3>
&lt;p>The &lt;code>.pkg&lt;/code> format is rather simple, it&amp;rsquo;s bunch of concatenated compressed (RFC 1951/Deflate) or raw/uncompressed data blobs (one for each file) separated by an ID.&lt;/p>
&lt;h4 id="layout-5">Layout&lt;/h4>
&lt;pre tabindex="0">&lt;code>+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
| |
| Compressed (RFC 1951/Deflate) or Raw Data |
| |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
+====+====+====+====++====+====+====+====+====+====+====+====++====+====+====+====+
| 00 | 00 | 00 | 00 || XX | XX | XX | XX | XX | XX | 00 | 00 || 00 | 00 | 00 | 00 |
+====+====+====+====++====+====+====+====+====+====+====+====++====+====+====+====+
|&amp;lt;--- padding_1 ---&amp;gt;||&amp;lt;---------------- id -----------------&amp;gt;||&amp;lt;--- padding_2 ---&amp;gt;|
| 32 bits || 64 bits || 32 bits |

[...repeat...]
&lt;/code>&lt;/pre>&lt;h4 id="field-descriptions-4">Field descriptions&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Field&lt;/th>
&lt;th>Size&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>padding_1&lt;/code>&lt;/td>
&lt;td>32 bits&lt;/td>
&lt;td>Always &lt;code>0x00000000&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>id_pkg_data&lt;/code>&lt;/td>
&lt;td>64 bits&lt;/td>
&lt;td>ID of the data blob&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>padding_2&lt;/code>&lt;/td>
&lt;td>32 bits&lt;/td>
&lt;td>Always &lt;code>0x00000000&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table></description></item><item><title>Reversing WoWs Resource Format - Part 4/5: Reading Everything</title><link>https://technically.kakwalab.ovh/posts/wows_depack_part4/</link><pubDate>Tue, 26 Aug 2025 00:03:00 +0200</pubDate><author>carpentier.pf@gmail.com (Pierre-François Carpentier)</author><guid>https://technically.kakwalab.ovh/posts/wows_depack_part4/</guid><description>&lt;ul>
&lt;li>Part 1 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part1/">Searching The Data&lt;/a>&lt;/li>
&lt;li>Part 2 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part2/">Looking For The Metadata&lt;/a>&lt;/li>
&lt;li>Part 3 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part3/">Dissecting The Index&lt;/a>&lt;/li>
&lt;li>Part 4 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part4/">Reading Everything&lt;/a>&lt;/li>
&lt;li>Part 5 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part5/">Tidying-Up The Project&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="the-implementation">The Implementation&lt;/h1>
&lt;p>In the last part, we discovered and got a fairly good idea of the metadata/IDX format.&lt;/p>
&lt;p>In this part, we will create a rough implementation to extract the content.&lt;/p>
&lt;h2 id="data-structures">Data Structures&lt;/h2>
&lt;p>First, define C structures matching our reverse-engineered format:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// INDEX file header
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">typedef&lt;/span> &lt;span style="color:#66d9ef">struct&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">char&lt;/span> magic[&lt;span style="color:#ae81ff">4&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uint32_t&lt;/span> unknown_1;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uint32_t&lt;/span> id;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uint32_t&lt;/span> unknown_2;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uint32_t&lt;/span> file_plus_dir_count;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uint32_t&lt;/span> file_count;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uint64_t&lt;/span> unknown_3;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uint64_t&lt;/span> header_size;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uint64_t&lt;/span> offset_idx_data_section;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uint64_t&lt;/span> offset_idx_footer_section;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>} WOWS_INDEX_HEADER;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="parser-implementation">Parser Implementation&lt;/h2>
&lt;p>&lt;strong>Note:&lt;/strong> These examples omit error handling for clarity. Production code requires bounds checking and validation.&lt;/p>
&lt;h3 id="file-mapping">File Mapping&lt;/h3>
&lt;p>Memory map the index file:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Open the index file
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">int&lt;/span> fd &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">open&lt;/span>(args.input, O_RDONLY);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Recover the file size
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">struct&lt;/span> stat s;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">fstat&lt;/span>(fd, &lt;span style="color:#f92672">&amp;amp;&lt;/span>s);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">size_t&lt;/span> index_size &lt;span style="color:#f92672">=&lt;/span> s.st_size;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Map the whole content in memory
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>index_content &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">mmap&lt;/span>(&lt;span style="color:#ae81ff">0&lt;/span>, index_size, PROT_READ, MAP_PRIVATE, fd, &lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The second step is to have an entry point to actually parse the thing:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span> WOWS_CONTEXT context;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> context.debug &lt;span style="color:#f92672">=&lt;/span> true;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Start the parsing
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#a6e22e">wows_parse_index&lt;/span>(index_content, index_size, &lt;span style="color:#f92672">&amp;amp;&lt;/span>context);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Here, I pass the memory-mapped content of the index, its size (will be used in the future to avoid overflows), and a &lt;code>context&lt;/code>, which will be used to pass parsing options and maintain &amp;ldquo;states&amp;rdquo; in the parsing if necessary.&lt;/p>
&lt;h3 id="parsing-the-header-section">Parsing the Header Section&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">wows_parse_index&lt;/span>(&lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>contents, &lt;span style="color:#66d9ef">size_t&lt;/span> length, WOWS_CONTEXT &lt;span style="color:#f92672">*&lt;/span>context) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// header section
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> WOWS_INDEX_HEADER &lt;span style="color:#f92672">*&lt;/span>header &lt;span style="color:#f92672">=&lt;/span> (WOWS_INDEX_HEADER &lt;span style="color:#f92672">*&lt;/span>)contents;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We can print it with a few &lt;code>printf&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">print_header&lt;/span>(WOWS_INDEX_HEADER &lt;span style="color:#f92672">*&lt;/span>header) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Index Header Content:&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;* magic: %.4s&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, (&lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>)&lt;span style="color:#f92672">&amp;amp;&lt;/span>header&lt;span style="color:#f92672">-&amp;gt;&lt;/span>magic);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;* unknown_1: 0x%x&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, header&lt;span style="color:#f92672">-&amp;gt;&lt;/span>unknown_1);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [...]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Output:&lt;/p>
&lt;pre tabindex="0">&lt;code>Index Header Content:
* magic: ISFP
* unknown_1: 0x2000000
* id: 0xb4399d91
* unknown_2: 0x40
* file_plus_dir_count: 311
* file_count: 284
* unknown_3: 1
* header_size: 40
* offset_idx_data_section: 0x3bf6
* offset_idx_footer_section: 0x7136
&lt;/code>&lt;/pre>&lt;h4 id="metadata-entries">Metadata entries&lt;/h4>
&lt;p>Then, we can do a bunch of pointer arithmetic operations to extract the other sections of the index file:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Recover the start of the metadata array
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> WOWS_INDEX_METADATA_ENTRY &lt;span style="color:#f92672">*&lt;/span>metadatas;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> metadatas &lt;span style="color:#f92672">=&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (WOWS_INDEX_METADATA_ENTRY &lt;span style="color:#f92672">*&lt;/span>)(contents &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#66d9ef">sizeof&lt;/span>(WOWS_INDEX_HEADER));
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then, we do something with these sections, like for example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Parse &amp;amp; print each entry in the metadata section
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">for&lt;/span> (i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; i &lt;span style="color:#f92672">&amp;lt;&lt;/span> header&lt;span style="color:#f92672">-&amp;gt;&lt;/span>file_plus_dir_count; i&lt;span style="color:#f92672">++&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (context&lt;span style="color:#f92672">-&amp;gt;&lt;/span>debug) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">print_metadata_entry&lt;/span>(&lt;span style="color:#f92672">&amp;amp;&lt;/span>metadatas[i], i);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With &lt;code>print_metadata_entry&lt;/code> looking like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">print_metadata_entry&lt;/span>(WOWS_INDEX_METADATA_ENTRY &lt;span style="color:#f92672">*&lt;/span>entry, &lt;span style="color:#66d9ef">int&lt;/span> index) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Metadata entry %d:&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, index);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;* file_type: %lu&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, entry&lt;span style="color:#f92672">-&amp;gt;&lt;/span>file_type_1);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;* offset_idx_file_name: 0x%lx&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, entry&lt;span style="color:#f92672">-&amp;gt;&lt;/span>offset_idx_file_name);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;* unknown_4: 0x%lx&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, entry&lt;span style="color:#f92672">-&amp;gt;&lt;/span>unknown_4);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;* file_type_2: 0x%lx&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, entry&lt;span style="color:#f92672">-&amp;gt;&lt;/span>file_type_2);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="re-evaluating-some-of-the-fields-meaning">Re-Evaluating some of the fields meaning:&lt;/h4>
&lt;p>Once done, it gives us a more comfortable read:&lt;/p>
&lt;pre tabindex="0">&lt;code>[...]
Metadata entry 0:
* file_type: 14
* offset_idx_file_name: 0x26e0
* unknown_4: 0x93b6404fba9a0c8f
* file_type_2: 0xdbb1a1d1b108b927

Metadata entry 1:
* file_type: 19
* offset_idx_file_name: 0x26ce
* unknown_4: 0xc7f7d0284a87ec8f
* file_type_2: 0x74d821503e1beba4

Metadata entry 2:
* file_type: 18
* offset_idx_file_name: 0x26c1
* unknown_4: 0x6b4f2cace7a270ad
* file_type_2: 0xdbb1a1d1b108b927

Metadata entry 3:
[...]

Metadata entry 310:
* file_type: 19
* offset_idx_file_name: 0x14fb
* unknown_4: 0xce7afff48d1bd174
* file_type_2: 0x74d821503e1beba4
&lt;/code>&lt;/pre>&lt;p>This permits us to review our previous reverse and right away there are two interesting things to note:&lt;/p>
&lt;ul>
&lt;li>There was a bit of an unknown regarding the number of metadata chunks: was it &lt;code>file_count&lt;/code> or &lt;code>file_plus_dir_count&lt;/code>? Now we are more certain it&amp;rsquo;s &lt;code>file_plus_dir_count&lt;/code> as it&amp;rsquo;s the larger value. If it was not, we would try to parse a section past the metadatas as metadata with funky results (garbage or crash). This is not the case.&lt;/li>
&lt;li>&lt;code>file_type&lt;/code> in metadata is not a file type/enum. The values are small but quite varied; it&amp;rsquo;s more likely the length of the file name.&lt;/li>
&lt;/ul>
&lt;p>Lets check with the last entry:&lt;/p>
&lt;pre tabindex="0">&lt;code>Metadata entry 310:
* file_type: 19
[...]
&lt;/code>&lt;/pre>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux 6775398/idx » hexdump -C system_data.idx | less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
00003be0 61 72 69 61 74 69 6f 6e 5f 64 75 6d 6d 79 2e 64 |ariation_dummy.d|
00003bf0 64 73 00 77 61 76 65 73 5f 68 65 69 67 68 74 73 |ds.waves_heights|
00003c00 30 2e 64 64 73 00 8f 0c 9a ba 4f 40 b6 93 70 11 |0.dds.....O@..p.|
00003c10 03 07 0d 33 ed 77 00 00 00 00 00 00 00 00 05 00 |...3.w..........|
&lt;/code>&lt;/pre>&lt;p>The last file name is &lt;code>waves_heights0.dds&lt;/code>, 18 characters long, with the &lt;code>\0&lt;/code>, we have our 19 value.&lt;/p>
&lt;p>So let&amp;rsquo;s rename this field.&lt;/p>
&lt;p>Now that we have fixed that, we can recover the file names of each entry:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>filename &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>)entry;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>filename &lt;span style="color:#f92672">+=&lt;/span> entry&lt;span style="color:#f92672">-&amp;gt;&lt;/span>offset_idx_file_name;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;* filename: %.*s&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, (&lt;span style="color:#66d9ef">int&lt;/span>)entry&lt;span style="color:#f92672">-&amp;gt;&lt;/span>file_name_size, filename);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Nice:&lt;/p>
&lt;pre tabindex="0">&lt;code>Metadata entry 0:
* file_name_size: 14
* offset_idx_file_name: 0x26e0
* unknown_4: 0x93b6404fba9a0c8f
* file_type_2: 0xdbb1a1d1b108b927
* filename: KDStorage.bin

Metadata entry 1:
* file_name_size: 19
* offset_idx_file_name: 0x26ce
* unknown_4: 0xc7f7d0284a87ec8f
* file_type_2: 0x74d821503e1beba4
* filename: waves_heights1.dds
&lt;/code>&lt;/pre>&lt;p>We have file names and directory names, for example:&lt;/p>
&lt;pre tabindex="0">&lt;code>[...]
Metadata entry 10:
* file_name_size: 11
* offset_idx_file_name: 0x2654
* unknown_4: 0x46c008ccf65395e0
* file_type_2: 0x46e29969bd85cf06
* filename: space_defs

Metadata entry 11:
* file_name_size: 13
* offset_idx_file_name: 0x263f
* unknown_4: 0x7213702d5e6899e0
* file_type_2: 0x13d93873302ed14c
* filename: aid_null.dds

Metadata entry 12:
* file_name_size: 16
* offset_idx_file_name: 0x262c
* unknown_4: 0xa1a829d8713f89e0
* file_type_2: 0xdbb1a1d1b108b927
* filename: camouflages.xml
[...]
&lt;/code>&lt;/pre>&lt;p>There is something which should enable us to differentiate between the two, maybe one of the unknown fields.&lt;/p>
&lt;p>Also, we still need to figure out how the directory system works:&lt;/p>
&lt;ul>
&lt;li>How directories &amp;amp; subdirectories are composed (to get &lt;code>&amp;lt;dir&amp;gt;/&amp;lt;sub dir&amp;gt;/&amp;lt;sub sub dir&amp;gt;/&lt;/code> paths)&lt;/li>
&lt;li>How the path goes back to the root (&lt;code>/&lt;/code>)&lt;/li>
&lt;/ul>
&lt;p>We will not look at it here, but that&amp;rsquo;s something to keep in mind.&lt;/p>
&lt;h4 id="footer-parsing">Footer Parsing&lt;/h4>
&lt;p>Extracting footer information:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span>WOWS_INDEX_FOOTER &lt;span style="color:#f92672">*&lt;/span>footer &lt;span style="color:#f92672">=&lt;/span> (WOWS_INDEX_FOOTER &lt;span style="color:#f92672">*&lt;/span>)(contents &lt;span style="color:#f92672">+&lt;/span> header&lt;span style="color:#f92672">-&amp;gt;&lt;/span>offset_idx_footer_section);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">print_footer&lt;/span>(footer);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The results were incorrect due to an offset miscalculation.&lt;/p>
&lt;pre tabindex="0">&lt;code>Index Footer Content:
* size_pkg_file_name: 50b0bd0300002d0b
* unknown_7: 0xe967
* unknown_6: 0x15
&lt;/code>&lt;/pre>&lt;p>A file name size of &lt;code>50b0bd0300002d0b&lt;/code>? I don&amp;rsquo;t think so.&lt;/p>
&lt;p>So let&amp;rsquo;s look at it more closely.&lt;/p>
&lt;p>In the header, we have:&lt;/p>
&lt;pre tabindex="0">&lt;code>Index Header Content:
[...]
* offset_idx_footer_section: 0x7136
[...]
&lt;/code>&lt;/pre>&lt;p>The hexdump gives:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux 6775398/idx » hexdump -s &lt;span style="color:#ae81ff">6&lt;/span> -C system_data.idx| less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
00007116 21 67 ac 70 22 ec ca b8 70 11 03 07 0d 33 ed 77 |!g.p&amp;#34;...p....3.w|
00007126 28 f9 15 0a 00 00 00 00 05 00 00 00 01 00 00 00 |(...............|
00007136 0b 2d 00 00 03 bd b0 50 67 e9 00 00 00 00 00 00 |.-.....Pg.......|
00007146 15 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00 |................|
00007156 70 11 03 07 0d 33 ed 77 73 79 73 74 65 6d 5f 64 |p....3.wsystem_d|
00007166 61 74 61 5f 30 30 30 31 2e 70 6b 67 00 |ata_0001.pkg.|
&lt;/code>&lt;/pre>&lt;p>If our previous interpretation was correct, a simple offset from the start of the index file should be &lt;code>0x7146&lt;/code>, not &lt;code>0x7136&lt;/code>.&lt;/p>
&lt;p>Maybe we are missing some fields in the footer, but given the previous 128 bits at offset &lt;code>0x7136&lt;/code> really look like the end of a pkg metadata entry, I doubt it.&lt;/p>
&lt;p>A more plausible explanation is that the offset is relative to the header &lt;code>id&lt;/code> field at &lt;code>0x10&lt;/code>.
Maybe the &lt;code>magic&lt;/code> + &lt;code>unknown_1&lt;/code> bits, i.e., the first 128 bits, are considered to be a separate section.&lt;/p>
&lt;p>Anyway, let&amp;rsquo;s just offset by 128 bits.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define MAGIC_SECTION_OFFSET sizeof(uint32_t) * 4
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Get the footer section
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>WOWS_INDEX_FOOTER &lt;span style="color:#f92672">*&lt;/span>footer &lt;span style="color:#f92672">=&lt;/span> (WOWS_INDEX_FOOTER &lt;span style="color:#f92672">*&lt;/span>)(contents &lt;span style="color:#f92672">+&lt;/span> header&lt;span style="color:#f92672">-&amp;gt;&lt;/span>offset_idx_footer_section &lt;span style="color:#f92672">+&lt;/span> MAGIC_SECTION_OFFSET);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s better:&lt;/p>
&lt;pre tabindex="0">&lt;code>Index Footer Content:
* size_pkg_file_name: 23
* unknown_7: 0x18
* unknown_6: 0xb5a4fa9349d9fd0d
&lt;/code>&lt;/pre>&lt;p>We can also recover the pkg file name as follows:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>pkg_filename &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>)footer;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pkg_filename &lt;span style="color:#f92672">+=&lt;/span> &lt;span style="color:#66d9ef">sizeof&lt;/span>(WOWS_INDEX_FOOTER);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;* pkg filename: %.*s&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#66d9ef">int&lt;/span>)footer&lt;span style="color:#f92672">-&amp;gt;&lt;/span>size_pkg_file_name, pkg_filename);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="safety-considerations">Safety Considerations&lt;/h4>
&lt;p>The current implementation lacks bounds checking and trusts all offsets—this needs fixing in production code.&lt;/p>
&lt;h4 id="pkg-data-entries">PKG Data Entries&lt;/h4>
&lt;p>Similar process for the data section:&lt;/p>
&lt;p>And lets add the 128 bits from the start (hexdump gives us &lt;code>0x3c06&lt;/code>, which is again a &lt;code>0x10&lt;/code> difference with &lt;code>0x3bf6&lt;/code>).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Get pkg data pointer section
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> WOWS_INDEX_DATA_FILE_ENTRY &lt;span style="color:#f92672">*&lt;/span>data_file_entry &lt;span style="color:#f92672">=&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (WOWS_INDEX_DATA_FILE_ENTRY &lt;span style="color:#f92672">*&lt;/span>)(contents &lt;span style="color:#f92672">+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> header&lt;span style="color:#f92672">-&amp;gt;&lt;/span>offset_idx_data_section &lt;span style="color:#f92672">+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MAGIC_SECTION_OFFSET);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>From there, we are not sure if we have &lt;code>header-&amp;gt;file_plus_dir_count&lt;/code> or &lt;code>header-&amp;gt;file_count&lt;/code> entries. The latter seems more likely as this section points to the pkg files, but that&amp;rsquo;s not a given.&lt;/p>
&lt;p>Also, we are unsure how one entry there is paired with a metadata entry. Maybe the order is simply the same in this array, maybe the matching is done through one of the unknown field.&lt;/p>
&lt;p>But first, lets dump the content with some &lt;code>printf&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code>Data file entry [0]:
* unknown_5: 0x93b6404fba9a0c8f
* unknown_6: 0x77ed330d07031170
* offset_pkg_data_chunk: 0x0
* type_1: 0x5
* type_2: 0x1
* size_pkg_data_chunk: 0x21f5
* id_pkg_data_chunk: 0x366c
* padding: 0x4a87ec8f

Data file entry [1]:
* unknown_5: 0x77ed330d07031170
* unknown_6: 0x5ef9b1e
* offset_pkg_data_chunk: 0x100000005
* type_1: 0x11515
* type_2: 0x97637703
* size_pkg_data_chunk: 0x2ab3e
* id_pkg_data_chunk: 0x6b4f2cace7a270ad
* padding: 0x7031170
[...]
&lt;/code>&lt;/pre>&lt;p>Humm, that doesn&amp;rsquo;t look righ&amp;hellip; Why is the padding not the expected &lt;code>0x0&lt;/code>? Also why the first entry looks mostly ok, except the padding, and the rest is garbage.&lt;/p>
&lt;p>First, I double-check the &lt;code>WOWS_INDEX_DATA_FILE_ENTRY&lt;/code> field sizes, and it was ok.&lt;/p>
&lt;p>Then, I remembered that compilers can add padding to have all the fields properly aligned in memory, this helps with performances.&lt;/p>
&lt;p>To avoid that, we need to add:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#pragma pack(1)
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now the output looks like that:&lt;/p>
&lt;pre tabindex="0">&lt;code>* unknown_5: 0x93b6404fba9a0c8f
* unknown_6: 0x77ed330d07031170
* offset_pkg_data_chunk: 0x0
* type_1: 0x5
* type_2: 0x1
* size_pkg_data_chunk: 0x21f5
* id_pkg_data_chunk: 0x366c5c4500bf
* padding: 0x0

Data file entry [1]:
* unknown_5: 0xc7f7d0284a87ec8f
* unknown_6: 0x77ed330d07031170
* offset_pkg_data_chunk: 0x5ef9b1e
* type_1: 0x5
* type_2: 0x1
* size_pkg_data_chunk: 0x11515
* id_pkg_data_chunk: 0x2ab3e97637703
* padding: 0x0

Data file entry [2]:
* unknown_5: 0x6b4f2cace7a270ad
* unknown_6: 0x77ed330d07031170
* offset_pkg_data_chunk: 0x2205
* type_1: 0x5
* type_2: 0x1
* size_pkg_data_chunk: 0x1cb
* id_pkg_data_chunk: 0xcadc1deb96d
* padding: 0x0
&lt;/code>&lt;/pre>&lt;p>That&amp;rsquo;s much better.&lt;/p>
&lt;p>But this small issue raises a number of issues with my method of parsing. Casting to structs comes with numerous issues, from overflows to endianness.&lt;/p>
&lt;p>This is not that critical here since we are just trying to have a rough prototype, but on more critical software, that&amp;rsquo;s not a good idea.&lt;/p>
&lt;p>After this prototype, it might be a good idea to start learning Rust ^^.&lt;/p>
&lt;p>Also, if we try to parse &lt;code>header-&amp;gt;file_plus_dir_count&lt;/code> entries, we get the following:&lt;/p>
&lt;pre tabindex="0">&lt;code>Data file entry [283]:
* unknown_5: 0xb8caec2270ac6721
* unknown_6: 0x77ed330d07031170
* offset_pkg_data_chunk: 0xa15f928
* type_1: 0x5
* type_2: 0x1
* size_pkg_data_chunk: 0x2d0b
* id_pkg_data_chunk: 0xe96750b0bd03
* padding: 0x0

Data file entry [284]:
* unknown_5: 0x15
* unknown_6: 0x18
* offset_pkg_data_chunk: 0x77ed330d07031170
* type_1: 0x74737973
* type_2: 0x645f6d65
* size_pkg_data_chunk: 0x5f617461
* id_pkg_data_chunk: 0x676b702e31303030
* padding: 0x0

Data file entry [285]:
* unknown_5: 0x0
* unknown_6: 0x0
* offset_pkg_data_chunk: 0x0
* type_1: 0x0
* type_2: 0x0
* size_pkg_data_chunk: 0x0
* id_pkg_data_chunk: 0x0
* padding: 0x0
&lt;/code>&lt;/pre>&lt;p>The entry &lt;code>283&lt;/code> is OK. This is the 284th entry since we start at &lt;code>0&lt;/code>, which is exactly &lt;code>header-&amp;gt;file_count&lt;/code>. The next one has weird values and the rest just &lt;code>0&lt;/code>.&lt;/p>
&lt;p>So &lt;code>header-&amp;gt;file_count&lt;/code> is indeed the number of entries in this section.&lt;/p>
&lt;h4 id="entry-matching">Entry Matching&lt;/h4>
&lt;p>The fact that on one side we have &lt;code>header-&amp;gt;file_count&lt;/code> and on the other &lt;code>header-&amp;gt;file_plus_dir_count&lt;/code> means it&amp;rsquo;s not a simple index matching.&lt;/p>
&lt;p>Lets investigate the unknown fields:&lt;/p>
&lt;pre tabindex="0">&lt;code>[...]
Data file entry [279]:
* unknown_5: 0xce7afff48d1bd174
* unknown_6: 0x77ed330d07031170
[...]

Data file entry [280]:
* unknown_5: 0x199e99feb0c986f8
* unknown_6: 0x77ed330d07031170
[...]
&lt;/code>&lt;/pre>&lt;p>&lt;code>unknown_6&lt;/code> is always the same, not really interesting.&lt;/p>
&lt;p>&lt;code>unknown_5&lt;/code> on the contrary is specific to each entry:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux GitHub/wows-depack &lt;span style="color:#f92672">(&lt;/span>main&lt;span style="color:#f92672">)&lt;/span> » ./wows-depack-cli &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> -i ~/Games/World&lt;span style="color:#ae81ff">\ &lt;/span>of&lt;span style="color:#ae81ff">\ &lt;/span>Warships/bin/6775398/idx/system_data.idx | &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> grep &lt;span style="color:#e6db74">&amp;#39;unknown_5&amp;#39;&lt;/span> | sort | uniq -c
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>...&lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">1&lt;/span> * unknown_5: 0x14b002d7c2835863
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">1&lt;/span> * unknown_5: 0x15a7b41a61f65f9c
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">1&lt;/span> * unknown_5: 0x15fcab5401f27f56
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">1&lt;/span> * unknown_5: 0x18a0d0dc4b05f8fa
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">1&lt;/span> * unknown_5: 0x192a05120f00553e
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>...&lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The values, however, are present two times—one in the metadata entry, the other in the data file entry:&lt;/p>
&lt;pre tabindex="0">&lt;code>Metadata entry [72]:
[...]
* unknown_4: 0x1011b17d9304bb39
[...]
&lt;/code>&lt;/pre>&lt;pre tabindex="0">&lt;code>Data file entry [65]:
[...]
* unknown_5: 0x1011b17d9304bb39
[...]
&lt;/code>&lt;/pre>&lt;p>So the link is established through these fields. These are simply random, unique IDs for each entry.&lt;/p>
&lt;p>In fact &lt;code>unknown_4&lt;/code> and &lt;code>unknown_5&lt;/code> are not the only fields leveraging this.&lt;/p>
&lt;p>Looking at &lt;code>file_type_2&lt;/code> values, we get something like that:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux GitHub/wows-depack &lt;span style="color:#f92672">(&lt;/span>main *&lt;span style="color:#f92672">)&lt;/span> » ./wows-depack-cli &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> -i ~/Games/World&lt;span style="color:#ae81ff">\ &lt;/span>of&lt;span style="color:#ae81ff">\ &lt;/span>Warships/bin/6775398/idx/system_data.idx | &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> grep &lt;span style="color:#e6db74">&amp;#39;0x937f155e4baaf562\|filename:&amp;#39;&lt;/span> | grep -A &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#e6db74">&amp;#39;0x937f155e4baaf562&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
--
* file_type_2: 0x937f155e4baaf562
* filename: LowerAftTrans.dds
--
* file_type_2: 0x937f155e4baaf562
* filename: MidBarbette.dds
--
* file_type_2: 0x937f155e4baaf562
* filename: Bulkhead.dds
--
* file_type_2: 0x937f155e4baaf562
* filename: MidBelt.dds
--
[...]
--
* unknown_4: 0x937f155e4baaf562
* filename: armour
--
[...]
--
* file_type_2: 0x937f155e4baaf562
* filename: Bottom.dds
--
* file_type_2: 0x937f155e4baaf562
* filename: ConstrBig.dds
--
* file_type_2: 0x937f155e4baaf562
* filename: ConstrMid.dds
--
* file_type_2: 0x937f155e4baaf562
* filename: ConstrSm.dds
--
* file_type_2: 0x937f155e4baaf562
* filename: DoubleBottom.dds
--
[...]
&lt;/code>&lt;/pre>&lt;p>Ok, &lt;code>file_type_2&lt;/code> is not a file type at all; it&amp;rsquo;s the &lt;code>id&lt;/code> (&lt;code>unknown_4&lt;/code> right now) of just one node that really looks like a directory.&lt;/p>
&lt;p>&lt;code>file_type_2&lt;/code> should probably be renamed &lt;code>parent_id&lt;/code> or something.&lt;/p>
&lt;p>Also, &lt;code>unknown_6&lt;/code> follows the same logic: it&amp;rsquo;s the id of the footer entry (side note: maybe the format supports having one index for several files).&lt;/p>
&lt;h4 id="small-tangent">Small Tangent&lt;/h4>
&lt;p>By this point, I was a bit intrigued by the &lt;code>type_1&lt;/code> and &lt;code>type_2&lt;/code> fields in the &lt;code>pkg&lt;/code> pointer sections.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux GitHub/wows-depack &lt;span style="color:#f92672">(&lt;/span>main&lt;span style="color:#f92672">)&lt;/span> » &lt;span style="color:#66d9ef">for&lt;/span> i in ~/Games/World&lt;span style="color:#ae81ff">\ &lt;/span>of&lt;span style="color:#ae81ff">\ &lt;/span>Warships/bin/6775398/idx/*;&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ./wows-depack-cli -i &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$i&lt;span style="color:#e6db74">&amp;#34;&lt;/span> | grep &lt;span style="color:#e6db74">&amp;#39;type_[12]:&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">done&lt;/span> | sort -n | uniq -c | sort -n
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code> 57265 * type_1: 0x0
 57265 * type_2: 0x0
 232089 * type_1: 0x5
 232089 * type_2: 0x1
&lt;/code>&lt;/pre>&lt;p>Ok, it seems that &lt;code>(type_1, type_2)&lt;/code> can either have the &lt;code>(0x0, 0x0)&lt;/code> values, or the &lt;code>(0x5, 0x1)&lt;/code> values. In most cases, it&amp;rsquo;s the latter.&lt;/p>
&lt;p>Looking at a few &lt;code>(0x0, 0x0)&lt;/code>, a common file with such values are &lt;code>.png&lt;/code>.&lt;/p>
&lt;p>It&amp;rsquo;s a bit of a wild guess, but these might be compression levels. &lt;code>(0x0, 0x0)&lt;/code>, i.e. no compression would be logical for &lt;code>.png&lt;/code> as these files are already compressed. Compressing them would actually only cost CPU resources with no space gains.&lt;/p>
&lt;p>For now, lets just keep that in mind, we will revisit it later.&lt;/p>
&lt;h4 id="glueing-the-entries-together">Glueing the entries together&lt;/h4>
&lt;p>So, we have metadata entries which can be linked together, we have pkg pointer entries which are linked to metadata entries and footer entries.&lt;/p>
&lt;p>It&amp;rsquo;s time to link all that together.&lt;/p>
&lt;p>To do that, the obvious choice is to feed these IDs into an hash map, this will make look-ups easier and quicker.&lt;/p>
&lt;p>Once done, the output now looks like that:&lt;/p>
&lt;pre tabindex="0">&lt;code>File entry [259]:
* metadata_id: 0xc90b3d356989c551
* footer_id: 0x77ed330d07031170
* offset_pkg_data: 0x5d30db8
* type_1: 0x5
* type_2: 0x1
* size_pkg_data: 0x89667
* id_pkg_data: 0xaab740ef4f6a6
* padding: 0x0
* file_name_size: 18
* offset_idx_file_name: 0x164e
* id: 0xc90b3d356989c551
* parent_id: 0xeb7ddcfb5178376
* filename: snow_tiles_ah.dds
parent [1]:
* file_name_size: 8
* offset_idx_file_name: 0x19cf
* id: 0xeb7ddcfb5178376
* parent_id: 0xb220a7743c83e638
* filename: weather
parent [2]:
* file_name_size: 5
* offset_idx_file_name: 0x16a3
* id: 0xb220a7743c83e638
* parent_id: 0x3837637adc4586b1
* filename: maps
parent [3]:
* file_name_size: 7
* offset_idx_file_name: 0x1746
* id: 0x3837637adc4586b1
* parent_id: 0xdbb1a1d1b108b927
* filename: system
&lt;/code>&lt;/pre>&lt;p>This entry is for the following path: &lt;code>/system/maps/weather/snow_tiles_ah.dds&lt;/code>&lt;/p>
&lt;p>This should be enough now to start thinking about the actual tool and how it will be implemented.&lt;/p>
&lt;h4 id="another-tangent">Another tangent&lt;/h4>
&lt;p>The goal is in the end is to parse all the index files, so it got me curious about the IDs across the different files.&lt;/p>
&lt;p>Looking at the dumps, &lt;code>content&lt;/code> is a fairly common directory name, present in a lot of the index files.&lt;/p>
&lt;p>And if we look at these records, we get:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux GitHub/wows-depack &lt;span style="color:#f92672">(&lt;/span>main *%&lt;span style="color:#f92672">)&lt;/span> » &lt;span style="color:#66d9ef">for&lt;/span> i in ~/Games/World&lt;span style="color:#ae81ff">\ &lt;/span>of&lt;span style="color:#ae81ff">\ &lt;/span>Warships/bin/6775398/idx/*;&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#66d9ef">do&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> echo $i; &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> ./wows-depack-cli -i &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$i&lt;span style="color:#e6db74">&amp;#34;&lt;/span> | grep -B &lt;span style="color:#ae81ff">5&lt;/span> &lt;span style="color:#e6db74">&amp;#39;* filename: content$&amp;#39;&lt;/span>; &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#66d9ef">done&lt;/span> | less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
/home/kakwa/Games/World of Warships/bin/6775398/idx/camouflage.idx
parent [5]:
* file_name_size: 8
* offset_idx_file_name: 0xecae
* id: 0xa33046442d8327fc
* parent_id: 0xdbb1a1d1b108b927
* filename: content
--
parent [4]:
* file_name_size: 8
* offset_idx_file_name: 0xecae
* id: 0xa33046442d8327fc
* parent_id: 0xdbb1a1d1b108b927
* filename: content
--
parent [5]:
* file_name_size: 8
* offset_idx_file_name: 0xecae
* id: 0xa33046442d8327fc
* parent_id: 0xdbb1a1d1b108b927
* filename: content
[...]
&lt;/code>&lt;/pre>&lt;p>Interestingly &lt;code>id&lt;/code> is always &lt;code>0xa33046442d8327fc&lt;/code> (and also &lt;code>parent_id&lt;/code> is &lt;code>0xdbb1a1d1b108b927&lt;/code>). This will make implementation a bit easier.&lt;/p>
&lt;p>However, it raises an interesting question: how this &lt;code>id&lt;/code> is generated? Is it completely random? Or is it derived from the path/name?&lt;/p>
&lt;p>It&amp;rsquo;s not really critical to read files, but might be important to write content if we ever get to that.&lt;/p>
&lt;h3 id="file-system-tree">File System Tree&lt;/h3>
&lt;p>Build a tree structure using:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>HashMap&lt;/strong> for fast ID lookups (&lt;a href="https://github.com/tidwall/hashmap.c" target="_blank">hashmap.c&lt;/a>)&lt;/li>
&lt;li>&lt;strong>Inode types&lt;/strong>: files and directories&lt;/li>
&lt;li>&lt;strong>Tree construction&lt;/strong>: Start with PKG data chunks (files), resolve names via metadata, build directory hierarchy using &lt;code>parent_id&lt;/code> relationships&lt;/li>
&lt;/ol>
&lt;p>The result: a complete archive tree with root node and children.&lt;/p>
&lt;p>With a bit more work, adding a path/tree printer function, I now get something like that:&lt;/p>
&lt;pre tabindex="0">&lt;code>/postfx_animations.xml
/settings/Default_v3.settings
/settings/Default_v1.settings
/settings/Default_v2.settings
/scripts/user_data_object_defs/Barge.def
/scripts/user_data_object_defs/SpatialUIDebugTool.def
/scripts/user_data_object_defs/FogPoint.def
/scripts/user_data_object_defs/StaticSoundEmitter.def
[...]
/helpers/maps/green_hemisphere.dds
/helpers/maps/lev_dirt01.dds
/helpers/maps/fat_disc_quarter.dds
/helpers/maps/lev_grass_01.dds
/helpers/maps/lev_grassflowers.dds
/helpers/maps/red_ruler.dds
/helpers/maps/hemisphere.dds
/helpers/maps/disc_quarter.dds
/helpers/maps/red_hemisphere_ring.dds
/server_stats.xml
&lt;/code>&lt;/pre>&lt;p>Or that in (ugly) tree form:&lt;/p>
&lt;pre tabindex="0">&lt;code>-./
 |-* postfx_animations.xml
 |--settings/
 | |-* Default_v3.settings
 | |-* Default_v1.settings
 | |-* Default_v2.settings
 |--scripts/
 | |--user_data_object_defs/
 | | |-* Barge.def
 | | |-* SpatialUIDebugTool.def
 | | |-* FogPoint.def
 | | |-* StaticSoundEmitter.def
 | | |-* Minefield.def
 | | |-* SoundedEffect.def
 | | |-* SquadronReticleTool.def
 | | |-* Trigger.def
 | | |-* WayPoint.def
[...]
&lt;/code>&lt;/pre>&lt;h1 id="recap-part-4">Recap (Part 4)&lt;/h1>
&lt;ul>
&lt;li>We defined the C structs for the metadata/index&lt;/li>
&lt;li>We resolved a few loose ends: filename lengths, parent IDs, unique identifiers for linking&lt;/li>
&lt;li>We implemented a file tree using hashmaps and parent-child relationships&lt;/li>
&lt;li>We identified compression type patterns&lt;/li>
&lt;/ul>
&lt;p>In the &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part5/">next and last part&lt;/a> we will tie things together and wrap up the implementation.&lt;/p></description></item><item><title>Reversing WoWs Resource Format - Part 3/5: Dissecting The Index</title><link>https://technically.kakwalab.ovh/posts/wows_depack_part3/</link><pubDate>Mon, 25 Aug 2025 12:02:00 +0200</pubDate><author>carpentier.pf@gmail.com (Pierre-François Carpentier)</author><guid>https://technically.kakwalab.ovh/posts/wows_depack_part3/</guid><description>&lt;ul>
&lt;li>Part 1 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part1/">Searching The Data&lt;/a>&lt;/li>
&lt;li>Part 2 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part2/">Looking For The Metadata&lt;/a>&lt;/li>
&lt;li>Part 3 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part3/">Dissecting The Index&lt;/a>&lt;/li>
&lt;li>Part 4 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part4/">Reading Everything&lt;/a>&lt;/li>
&lt;li>Part 5 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part5/">Tidying-Up The Project&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="index-file-reverse-engineering">Index File Reverse Engineering&lt;/h1>
&lt;p>In the last part, we found the location of the index and determine the general layout.&lt;/p>
&lt;p>Now, let&amp;rsquo;s do the detailed reverse engineering.&lt;/p>
&lt;h2 id="metadata-chunk-format">Metadata Chunk Format&lt;/h2>
&lt;p>The first section contains file metadata entries:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux 6775398/idx » hexdump -C system_data.idx | less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>00000000 49 53 46 50 00 00 00 02 91 9d 39 b4 40 00 00 00 |ISFP......9.@...|
00000010 37 01 00 00 1c 01 00 00 01 00 00 00 00 00 00 00 |7...............|
00000020 28 00 00 00 00 00 00 00 f6 3b 00 00 00 00 00 00 |(........;......|
00000030 36 71 00 00 00 00 00 00 0e 00 00 00 00 00 00 00 |6q..............|
00000040 e0 26 00 00 00 00 00 00 8f 0c 9a ba 4f 40 b6 93 |.&amp;amp;..........O@..|
00000050 27 b9 08 b1 d1 a1 b1 db 13 00 00 00 00 00 00 00 |&amp;#39;...............|
00000060 ce 26 00 00 00 00 00 00 8f ec 87 4a 28 d0 f7 c7 |.&amp;amp;.........J(...|
00000070 a4 eb 1b 3e 50 21 d8 74 12 00 00 00 00 00 00 00 |...&amp;gt;P!.t........|
00000080 c1 26 00 00 00 00 00 00 ad 70 a2 e7 ac 2c 4f 6b |.&amp;amp;.......p...,Ok|
00000090 27 b9 08 b1 d1 a1 b1 db 0e 00 00 00 00 00 00 00 |&amp;#39;...............|
000000a0 b3 26 00 00 00 00 00 00 4e 84 a5 6a 94 dc 1f 7f |.&amp;amp;......N..j....|
000000b0 62 f5 aa 4b 5e 15 7f 93 10 00 00 00 00 00 00 00 |b..K^...........|
000000c0 a1 26 00 00 00 00 00 00 4e b0 fe 23 62 40 a5 65 |.&amp;amp;......N..#b@.e|
000000d0 27 b9 08 b1 d1 a1 b1 db 20 00 00 00 00 00 00 00 |&amp;#39;....... .......|
000000e0 91 26 00 00 00 00 00 00 8e c7 6a 58 7c 86 62 33 |.&amp;amp;........jX|.b3|
000000f0 27 b9 08 b1 d1 a1 b1 db 0f 00 00 00 00 00 00 00 |&amp;#39;...............|
00000100 91 26 00 00 00 00 00 00 8e 43 3d e9 cf 49 52 a4 |.&amp;amp;.......C=..IR.|
00000110 62 f5 aa 4b 5e 15 7f 93 16 00 00 00 00 00 00 00 |b..K^...........|
00000120 80 26 00 00 00 00 00 00 0e 3c 9a 6d 22 de 7b da |.&amp;amp;.......&amp;lt;.m&amp;#34;.{.|
00000130 4e b0 fe 23 62 40 a5 65 0b 00 00 00 00 00 00 00 |N..#b@.e........|
00000140 76 26 00 00 00 00 00 00 0e 48 58 ea 50 44 1a 47 |v&amp;amp;.......HX.PD.G|
00000150 df 61 50 67 c7 3a dd 7a 13 00 00 00 00 00 00 00 |.aPg.:.z........|
00000160 61 26 00 00 00 00 00 00 e0 9f c3 bd d2 12 20 04 |a&amp;amp;............ .|
00000170 09 28 f1 df 2d 04 93 de 0b 00 00 00 00 00 00 00 |.(..-...........|

00002690 a4 eb 1b 3e 50 21 d8 74 0c 00 00 00 00 00 00 00 |...&amp;gt;P!.t........|
000026a0 21 15 00 00 00 00 00 00 00 40 cc c7 49 c4 54 09 |!........@..I.T.|
000026b0 a4 eb 1b 3e 50 21 d8 74 14 00 00 00 00 00 00 00 |...&amp;gt;P!.t........|
000026c0 0d 15 00 00 00 00 00 00 ce 6a 28 bc cf e7 79 c8 |.........j(...y.|
000026d0 a4 eb 1b 3e 50 21 d8 74 1a 00 00 00 00 00 00 00 |...&amp;gt;P!.t........|
000026e0 01 15 00 00 00 00 00 00 6c c0 c9 f7 7e 00 03 05 |........l...~...|
000026f0 a4 eb 1b 3e 50 21 d8 74 13 00 00 00 00 00 00 00 |...&amp;gt;P!.t........|
00002700 fb 14 00 00 00 00 00 00 74 d1 1b 8d f4 ff 7a ce |........t.....z.|
00002710 a4 eb 1b 3e 50 21 d8 74 4b 44 53 74 6f 72 61 67 |...&amp;gt;P!.tKDStorag|
00002720 65 2e 62 69 6e 00 77 61 76 65 73 5f 68 65 69 67 |e.bin.waves_heig|
00002730 68 74 73 31 2e 64 64 73 00 61 6e 69 6d 61 74 65 |hts1.dds.animate|
00002740 64 4d 69 73 63 73 2e 78 6d 6c 00 4c 6f 77 65 72 |dMiscs.xml.Lower|
00002750 44 65 63 6b 2e 64 64 73 00 63 6f 6d 6d 61 6e 64 |Deck.dds.command|
&lt;/code>&lt;/pre>&lt;p>Staring at the hexdump long enough and we can start to see some patterns.&lt;/p>
&lt;p>At regular intervals, every 256 bits, we get a 64-bit integer with a relatively low value, hinting at individual metadata sets of 256 bits.&lt;/p>
&lt;p>This looks suspiciously like some kind of constant enum coded into a 64-bit integer.
My best guess right now would be some kind of file type code, itself defined as a constant in the game engine like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define FILE_TYPE_1 0x01
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define FILE_TYPE_2 0x02
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">// [...]
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Let&amp;rsquo;s call it &lt;code>file_type&lt;/code> for now.&lt;/p>
&lt;p>Looking at the end of the first chunk (right before we get &lt;code>KDStorage.bin&lt;/code>), we have to go back 4 x 64 bits to get something that looks like a &lt;code>file_type&lt;/code>.&lt;/p>
&lt;p>This means &lt;code>file_type&lt;/code> is the first field in the 256-bit structure.&lt;/p>
&lt;p>Let&amp;rsquo;s look at the next 64 bits: &lt;code>ce 26 00 00 00 00 00 00&lt;/code>, &lt;code>c1 26 00 00 00 00 00 00&lt;/code>, &lt;code>80 26 00 00 00 00 00 00&lt;/code>, etc. These values are again rather small.&lt;/p>
&lt;p>Also these values, at least for the first ones, are suspiciously close to &lt;code>00002718&lt;/code>, i.e. right where the section containing the file names starts.&lt;/p>
&lt;p>The last ones, &lt;code>01 15 00 00 00 00 00 00&lt;/code>, &lt;code>fb 14 00 00 00 00 00 00&lt;/code>, etc., are smaller, and suspiciously, they have similar values to the length of the file name section (approximately &lt;code>0x00003c00 - 0x00002710 = 0x0000014f0&lt;/code>).&lt;/p>
&lt;p>The second field is then probably some kind of offset. Most likely from the start of one 256-bit chunk to the start of one of the file names.&lt;/p>
&lt;p>Looking at other &lt;code>.idx&lt;/code> files seems to confirm that.&lt;/p>
&lt;p>Let&amp;rsquo;s call it &lt;code>offset&lt;/code> for now.&lt;/p>
&lt;p>Next, let&amp;rsquo;s look at the remaining 128 bits.&lt;/p>
&lt;pre tabindex="0">&lt;code>8f 0c 9a ba 4f 40 b6 93 | 27 b9 08 b1 d1 a1 b1 db
8f ec 87 4a 28 d0 f7 c7 | a4 eb 1b 3e 50 21 d8 74
ad 70 a2 e7 ac 2c 4f 6b | 27 b9 08 b1 d1 a1 b1 db
4e 84 a5 6a 94 dc 1f 7f | 62 f5 aa 4b 5e 15 7f 93
4e b0 fe 23 62 40 a5 65 | 27 b9 08 b1 d1 a1 b1 db
8e c7 6a 58 7c 86 62 33 | 27 b9 08 b1 d1 a1 b1 db
&lt;/code>&lt;/pre>&lt;p>First thing to note: all the bits are used, which disqualifies offsets or simple enum IDs like before.&lt;/p>
&lt;p>Right now, we are not even sure these 128 bits are part of one 128-bit field (for example a hash), two 64-bit integers, four 32-bit integers, or any combination of 16, 32 or 64 bits that ends up making a 128-bit chunk.&lt;/p>
&lt;p>Looking at it more closely, the first 64 bits looks rather random, the last 64 bits however? we see quite a few values repeating themselves (ex: &lt;code>27 b9 08 b1 d1 a1 b1 db&lt;/code>).&lt;/p>
&lt;p>Let&amp;rsquo;s check the whole file:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux 6775398/idx » hexdump -C system_data.idx | grep &lt;span style="color:#e6db74">&amp;#39;00 00 00 00 00 00 00&amp;#39;&lt;/span> | &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> sed &lt;span style="color:#e6db74">&amp;#39;s/^..........//&amp;#39;&lt;/span> | sed &lt;span style="color:#e6db74">&amp;#39;s/ .*//&amp;#39;&lt;/span> | sort | uniq -c | sort -n
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code># note: first number is the number of occurrences
 1 00 00 00 00 00 00 00 af
 1 1c 6a 8d 7f df 8e b3 35
 1 28 00 00 00 00 00 00 00
 1 36 71 00 00 00 00 00 00
 1 37 01 00 00 1c 01 00 00
 1 7d e3 1c a4 35 3e 98 8b
 1 e0 95 53 f6 cc 08 c0 46
 2 12 46 58 36 c8 ec 47 8b
 2 4e b0 fe 23 62 40 a5 65
 2 d0 d7 a5 ce a8 86 0e ae
 3 1a aa c7 3c 4e 76 ad 94
 3 4c d1 2e 30 73 38 d9 13
 3 4c f0 ea c1 d5 5a 8d 12
 3 88 57 fc 1c 72 f3 84 fa
 3 ac 82 d5 f6 9e db 47 f9
 3 b1 86 45 dc 7a 63 37 38
 3 fc 27 83 2d 44 46 30 a3
 6 76 83 17 b5 cf dd b7 0e
 8 06 cf 85 bd 69 99 e2 46
 9 09 28 f1 df 2d 04 93 de
 10 a4 eb 1b 3e 50 21 d8 74
 10 d7 22 2f fc 0a 67 7a 0d
 14 aa db f0 18 01 89 b6 d8
 15 df 61 50 67 c7 3a dd 7a
 18 19 ac 65 3f 91 78 97 dc
 19 38 e6 83 3c 74 a7 20 b2
 19 59 dc e0 43 fc 88 b7 7c
 23 d3 9e 86 23 25 42 27 45
 24 0e 48 58 ea 50 44 1a 47
 33 d7 19 f3 03 3e 6e 59 03
 34 27 b9 08 b1 d1 a1 b1 db
 38 62 f5 aa 4b 5e 15 7f 93
&lt;/code>&lt;/pre>&lt;p>Indeed, the repetitions are quite frequent, and running the same command on other files yields roughly the same values:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux 6775398/idx » hexdump -C particles.idx | grep &lt;span style="color:#e6db74">&amp;#39;00 00 00 00 00 00 00&amp;#39;&lt;/span> | &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> sed &lt;span style="color:#e6db74">&amp;#39;s/^..........//&amp;#39;&lt;/span> | sed &lt;span style="color:#e6db74">&amp;#39;s/ .*//&amp;#39;&lt;/span> | sort | uniq -c | sort -n
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code> 1 27 b9 08 b1 d1 a1 b1 db
 1 28 00 00 00 00 00 00 00
 1 8e ae 00 00 00 00 00 00
 1 bd 01 00 00 b7 01 00 00
 4 ca 2c b7 97 24 b8 1c 86
 5 1f 19 a6 0c a2 9b 7f b3
 16 94 a1 23 f4 c5 41 b8 42
 20 91 cc 55 52 25 2a 42 d4
 81 de 3e 45 0e 99 dc 30 14
 317 66 52 00 d6 89 64 1d 2e
&lt;/code>&lt;/pre>&lt;p>Moreover, &lt;code>sound_music.idx&lt;/code>, which as the name implies probably only contains sound files, returns mostly one type:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux 6775398/idx » hexdump -C sound_music.idx | grep &lt;span style="color:#e6db74">&amp;#39;00 00 00 00 00 00 00&amp;#39;&lt;/span> | &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> sed &lt;span style="color:#e6db74">&amp;#39;s/^..........//&amp;#39;&lt;/span> | sed &lt;span style="color:#e6db74">&amp;#39;s/ .*//&amp;#39;&lt;/span> | sort | uniq -c | sort -n
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code> 513 93 63 67 56 c2 97 75 69
&lt;/code>&lt;/pre>&lt;p>Note: these commands are by no means accurate; they are likely to catch garbage and miscount. But these are quick and dirty ways to validate hypotheses.&lt;/p>
&lt;p>So it seems we are dealing with another &lt;code>file_type&lt;/code> field. Let&amp;rsquo;s call it &lt;code>file_type2&lt;/code>, and rename the first one &lt;code>file_type1&lt;/code>.&lt;/p>
&lt;p>So in the end, making a few assumptions for now, we have figured out the rough format of this section:&lt;/p>
&lt;pre tabindex="0">&lt;code>+====+====+====+====+====+====+====+====++====+====+====+====+====+====+====+====+
| T1 | T1 | T1 | T1 | T1 | T1 | T1 | T1 || OF | OF | OF | OF | OF | OF | OF | OF |
+====+====+====+====+====+====+====+====++====+====+====+====+====+====+====+====+
|&amp;lt;---------- file type 1 --------------&amp;gt;||&amp;lt;------------ offset -----------------&amp;gt;|
| 64 bits || 64 bits |
+====+====+====+====+====+====+====+====++====+====+====+====+====+====+====+====+
| UN | UN | UN | UN | UN | UN | UN | UN || T2 | T2 | T2 | T2 | T2 | T2 | T2 | T2 |
+====+====+====+====+====+====+====+====++====+====+====+====+====+====+====+====+
|&amp;lt;------------ unknown ----------------&amp;gt;||&amp;lt;---------- file type 2 --------------&amp;gt;|
| 64 bits || 64 bits |
&lt;/code>&lt;/pre>&lt;h2 id="header-section-analysis">Header Section Analysis&lt;/h2>
&lt;p>The &lt;code>.idx&lt;/code> file header contains:&lt;/p>
&lt;pre tabindex="0">&lt;code>00000000 49 53 46 50 00 00 00 02 91 9d 39 b4 40 00 00 00 |ISFP......9.@...|
00000010 37 01 00 00 1c 01 00 00 01 00 00 00 00 00 00 00 |7...............|
00000020 28 00 00 00 00 00 00 00 f6 3b 00 00 00 00 00 00 |(........;......|
00000030 36 71 00 00 00 00 00 00 0e 00 00 00 00 00 00 00 |6q..............|
00000040 e0 26 00 00 00 00 00 00 8f 0c 9a ba 4f 40 b6 93 |.&amp;amp;..........O@..|
00000050 27 b9 08 b1 d1 a1 b1 db 13 00 00 00 00 00 00 00 |&amp;#39;...............|
00000060 ce 26 00 00 00 00 00 00 8f ec 87 4a 28 d0 f7 c7 |.&amp;amp;.........J(...|
00000070 a4 eb 1b 3e 50 21 d8 74 12 00 00 00 00 00 00 00 |...&amp;gt;P!.t........|
00000080 c1 26 00 00 00 00 00 00 ad 70 a2 e7 ac 2c 4f 6b |.&amp;amp;.......p...,Ok|
00000090 27 b9 08 b1 d1 a1 b1 db 0e 00 00 00 00 00 00 00 |&amp;#39;...............|
&lt;/code>&lt;/pre>&lt;p>These first bytes don&amp;rsquo;t look like the &lt;code>section&lt;/code> previously mentioned; we have a magic number (&lt;code>ISFP&lt;/code>), and then the content doesn&amp;rsquo;t look like a &lt;code>section&lt;/code> at first (too many low-value 32-bit integers).&lt;/p>
&lt;p>This means we most likely have a header section containing things like:&lt;/p>
&lt;ul>
&lt;li>magic numbers&lt;/li>
&lt;li>types&lt;/li>
&lt;li>sizes&lt;/li>
&lt;li>number of entries/files&lt;/li>
&lt;/ul>
&lt;p>The first thing to determine is the size of the header. Looking at it, the first &lt;code>section&lt;/code> starts at &lt;code>0x38&lt;/code> (recognisable by the full 64-bit integers).&lt;/p>
&lt;p>This means the header is 7 × 64 bits.&lt;/p>
&lt;p>Let&amp;rsquo;s analyze the content.&lt;/p>
&lt;p>Looking at all the files, for the first 128 bits, we get:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux 6775398/idx » &lt;span style="color:#66d9ef">for&lt;/span> i in *;&lt;span style="color:#66d9ef">do&lt;/span> hexdump -C $i | head -n 1;&lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
00000000 49 53 46 50 00 00 00 02 1b 73 f9 d5 40 00 00 00 |ISFP.....s..@...|
00000000 49 53 46 50 00 00 00 02 78 f0 2c 09 40 00 00 00 |ISFP....x.,.@...|
00000000 49 53 46 50 00 00 00 02 b8 fe ba b9 40 00 00 00 |ISFP........@...|
00000000 49 53 46 50 00 00 00 02 06 24 fa 2d 40 00 00 00 |ISFP.....$.-@...|
00000000 49 53 46 50 00 00 00 02 1e 7e f6 d9 40 00 00 00 |ISFP.....~..@...|
00000000 49 53 46 50 00 00 00 02 dd 21 74 c2 40 00 00 00 |ISFP.....!t.@...|
00000000 49 53 46 50 00 00 00 02 33 28 63 bd 40 00 00 00 |ISFP....3(c.@...|
00000000 49 53 46 50 00 00 00 02 cb 5c e2 0d 40 00 00 00 |ISFP.....\..@...|
00000000 49 53 46 50 00 00 00 02 cb e8 8a fd 40 00 00 00 |ISFP........@...|
00000000 49 53 46 50 00 00 00 02 6e 04 b1 62 40 00 00 00 |ISFP....n..b@...|
00000000 49 53 46 50 00 00 00 02 15 1c a2 f9 40 00 00 00 |ISFP........@...|
[...]
&lt;/code>&lt;/pre>&lt;p>As we can see, the 1st, 2nd, and 4th 32-bit chunks are always the same, and looking at the values, we have respectively:&lt;/p>
&lt;ul>
&lt;li>a magic number (&lt;code>ISFP&lt;/code>),&lt;/li>
&lt;li>&lt;code>00 00 00 02&lt;/code> which is rather weird (it could be some kind of ID if we were little-endian, but the format is big-endian). Maybe it is actually part of the magic number. As it doesn&amp;rsquo;t vary, it&amp;rsquo;s not too important for the task at hand here.&lt;/li>
&lt;li>&lt;code>40 00 00 00&lt;/code> which, like &lt;code>type 1&lt;/code>, looks like a low-value enum, and given its position in the index file, within the header, we are most likely dealing with an archive type. Again, as it doesn&amp;rsquo;t vary, it&amp;rsquo;s not really important.&lt;/li>
&lt;/ul>
&lt;p>The 3rd 32-bit integer uses all the available bits, so it&amp;rsquo;s unlikely a size. Maybe it&amp;rsquo;s a CRC32 or a unique ID for the archives.&lt;/p>
&lt;p>Edit: the &lt;code>40 00 00 00&lt;/code> value upon closer inspection might not be an archive type; its value is 64 in decimal, which might be a header size, or simply storing the size of an integer.&lt;/p>
&lt;p>So we have:&lt;/p>
&lt;pre tabindex="0">&lt;code>+====+====+====+====++====+====+====+====++====+====+====+====++====+====+====+====+
| MA | MA | MA | MA || 00 | 00 | 00 | 02 || ID | ID | ID | ID || 40 | 00 | 00 | 00 |
+====+====+====+====++====+====+====+====++====+====+====+====++====+====+====+====+
|&amp;lt;----- magic -----&amp;gt;||&amp;lt;----- ???? ------&amp;gt;||&amp;lt;---- id/crc -----&amp;gt;||&amp;lt;----- ??????? ---&amp;gt;|
&lt;/code>&lt;/pre>&lt;p>Now, let&amp;rsquo;s look at the next 128 bits (second line in the hexdump).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux 6775398/idx » &lt;span style="color:#66d9ef">for&lt;/span> i in *;&lt;span style="color:#66d9ef">do&lt;/span> hexdump -C $i | head -n &lt;span style="color:#ae81ff">2&lt;/span> | tail -n 1;&lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>00000010 bd 01 00 00 b7 01 00 00 01 00 00 00 00 00 00 00 |................|
00000010 35 01 00 00 1d 01 00 00 01 00 00 00 00 00 00 00 |5...............|
00000010 5b 00 00 00 57 00 00 00 01 00 00 00 00 00 00 00 |[...W...........|
00000010 5c 00 00 00 58 00 00 00 01 00 00 00 00 00 00 00 |\...X...........|
00000010 10 18 00 00 ff 17 00 00 01 00 00 00 00 00 00 00 |................|
00000010 29 3b 00 00 0b 3b 00 00 01 00 00 00 00 00 00 00 |);...;..........|
00000010 04 02 00 00 02 02 00 00 01 00 00 00 00 00 00 00 |................|
00000010 1e 05 00 00 c2 03 00 00 01 00 00 00 00 00 00 00 |................|
00000010 a5 01 00 00 36 01 00 00 01 00 00 00 00 00 00 00 |....6...........|
00000010 13 04 00 00 fb 02 00 00 01 00 00 00 00 00 00 00 |................|
00000010 79 03 00 00 a9 02 00 00 01 00 00 00 00 00 00 00 |y...............|
&lt;/code>&lt;/pre>&lt;p>So here, we recognize two 32-bit integers due to the &lt;code>00 00&lt;/code>, and then either a fixed 64-bit integer with always a &lt;code>01 00 00 00 00 00 00 00&lt;/code> value, or something like two 32-bit integers with value &lt;code>01 00 00 00&lt;/code> and &lt;code>00 00 00 00&lt;/code> (as the value never varies, again, it&amp;rsquo;s not that important).&lt;/p>
&lt;p>Let&amp;rsquo;s try to determine the two 32-bit values. Let&amp;rsquo;s look at one of the files in particular:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux 6775398/idx » hexdump -C system_data.idx | less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
00000010 37 01 00 00 1c 01 00 00 01 00 00 00 00 00 00 00 |7...............|
[...]
&lt;/code>&lt;/pre>&lt;p>The first value is &lt;code>37 01 00 00&lt;/code>, i.e. converted to decimal, &lt;code>311&lt;/code>. Doing a &lt;code>strings system_data.idx &amp;gt; listing&lt;/code> and removing the garbage (ex: &lt;code>w6~n&lt;/code>) as best as possible, plus the &lt;code>.pkg&lt;/code> file name, only keeping files and directory names, we get &lt;code>310&lt;/code> entries, a remarkably close value.&lt;/p>
&lt;p>Looking at other files, story is similar, this field roughly matches the number of strings we get from &lt;code>strings&lt;/code> (never perfectly however, but if the names are too short, &lt;code>strings&lt;/code> will ignore them, most likely explaining the small delta we have each time).&lt;/p>
&lt;p>Consequently we can deduce it&amp;rsquo;s most likely the number of entries (files and directories) in the index file.&lt;/p>
&lt;p>Next, we have &lt;code>1c 01 00 00&lt;/code>, i.e. converted to decimal, &lt;code>284&lt;/code>. This value is suspiciously close to the previous value. As we have both directories and file names, this number probably represents the number of items that are actual files.&lt;/p>
&lt;p>Let&amp;rsquo;s validate that:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Q&amp;amp;D filtering out names without an extension (no &amp;#39;.&amp;#39;)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kakwa@linux 6775398/idx » cat listing | grep &lt;span style="color:#e6db74">&amp;#39;\.&amp;#39;&lt;/span> | wc -l
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>284
&lt;/code>&lt;/pre>&lt;p>Bingo, we have the exact number we were looking for.&lt;/p>
&lt;p>The last 64 bits could simply be ignored for now since they always have the same value.&lt;/p>
&lt;p>So we have:&lt;/p>
&lt;pre tabindex="0">&lt;code>+====+====+====+====++====+====+====+====++====+====+====+====++====+====+====+====+
| FD | FD | FD | FD || FI | FI | FI | FI || 01 | 00 | 00 | 00 || 00 | 00 | 00 | 00 |
+====+====+====+====++====+====+====+====++====+====+====+====++====+====+====+====+
|&amp;lt;file + dir count &amp;gt;||&amp;lt;-- file count ---&amp;gt;||&amp;lt;-------------- ???? ------------------&amp;gt;|
&lt;/code>&lt;/pre>&lt;p>Next 128 bits:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux 6775398/idx » &lt;span style="color:#66d9ef">for&lt;/span> i in *;&lt;span style="color:#66d9ef">do&lt;/span> hexdump -C $i | head -n &lt;span style="color:#ae81ff">3&lt;/span> | tail -n 1;&lt;span style="color:#66d9ef">done&lt;/span> | less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
00000020 28 00 00 00 00 00 00 00 58 8d 00 00 00 00 00 00 |(.......X.......|
00000020 28 00 00 00 00 00 00 00 7c 3f 02 00 00 00 00 00 |(.......|?......|
00000020 28 00 00 00 00 00 00 00 77 2f 00 00 00 00 00 00 |(.......w/......|
00000020 28 00 00 00 00 00 00 00 bc e9 01 00 00 00 00 00 |(...............|
00000020 28 00 00 00 00 00 00 00 c6 ea 02 00 00 00 00 00 |(...............|
00000020 28 00 00 00 00 00 00 00 a9 0f 00 00 00 00 00 00 |(...............|
00000020 28 00 00 00 00 00 00 00 c4 22 00 00 00 00 00 00 |(........&amp;#34;......|
00000020 28 00 00 00 00 00 00 00 b6 2b 00 00 00 00 00 00 |(........+......|
00000020 28 00 00 00 00 00 00 00 d6 41 00 00 00 00 00 00 |(........A......|
00000020 28 00 00 00 00 00 00 00 fd 21 00 00 00 00 00 00 |(........!......|
00000020 28 00 00 00 00 00 00 00 e1 25 00 00 00 00 00 00 |(........%......|
00000020 28 00 00 00 00 00 00 00 f5 31 00 00 00 00 00 00 |(........1......|
00000020 28 00 00 00 00 00 00 00 1e 42 00 00 00 00 00 00 |(........B......|
00000020 28 00 00 00 00 00 00 00 70 42 02 00 00 00 00 00 |(.......pB......|
[...]
&lt;/code>&lt;/pre>&lt;p>So here, we have two 64-bit integers. The first one is &lt;code>28 00 00 00 00 00 00 00&lt;/code> and always has the same value. Not sure what it represents; the value is somewhat close to the header size in bytes: 40 for this value, 56 for the full header size.&lt;/p>
&lt;p>Maybe the header could vary in size in certain situations, and this represents its size minus some fixed part (like the first 16 bytes/128 bits). I&amp;rsquo;m also kind of betting that if the previous 64-bit integer (&lt;code>01 00 00 00 00 00 00 00&lt;/code>) changes, this will also change.&lt;/p>
&lt;p>But as it never varies in the set of index files we have here, we cannot really make any deduction, only guesses. So once again, let&amp;rsquo;s ignore it.&lt;/p>
&lt;p>At this point, the idea of downloading other Wargaming games like World of Tanks or World of Warplanes popped up; maybe this will give complementary information regarding the unknown fields that start to pile up.&lt;/p>
&lt;p>But let&amp;rsquo;s continue for now.&lt;/p>
&lt;p>EDIT: It&amp;rsquo;s no help; World of Tanks and World of Warplanes simply pack their resources in &lt;code>.zip&lt;/code> files&amp;hellip;
It&amp;rsquo;s a wild guess, but I kind of expect WoWs to be the same in the future.
The WoWs packing format feels, in fact, somewhat legacy, custom, and far less efficient than a standard run-of-the-mill &lt;code>.zip&lt;/code> file. Not to mention using &lt;code>.zip&lt;/code> files means removing one bit of code to maintain.&lt;/p>
&lt;p>The next value is again a 64-bit integer; it changes between each file.&lt;/p>
&lt;p>Let&amp;rsquo;s focus on one file:&lt;/p>
&lt;pre tabindex="0">&lt;code>[...]
00000020 28 00 00 00 00 00 00 00 f6 3b 00 00 00 00 00 00 |(........;......|
[...]
&lt;/code>&lt;/pre>&lt;p>At first, I thought it might be a file size, but quickly checking the index file size, I got:&lt;/p>
&lt;ul>
&lt;li>index file size: 29043&lt;/li>
&lt;li>value of this field in decimal: 15350&lt;/li>
&lt;/ul>
&lt;p>Checking another file, I got 77461 and 44807.&lt;/p>
&lt;p>So no, it&amp;rsquo;s not the index size. However it is suspiciously ~1/2 of the file size, and after having stared at hexdumps for hours, I had another idea.&lt;/p>
&lt;p>The third chunk of the file is right after the bundle of dir/file name strings which varies in length wildly (i.e., it&amp;rsquo;s not a fixed length or a multiple of a fixed length).&lt;/p>
&lt;p>We probably need an offset pointing to the start of this section in the header.&lt;/p>
&lt;p>And sure enough, looking where the bundle of strings stops, we get:&lt;/p>
&lt;pre tabindex="0">&lt;code>00003bd0 6f 69 73 65 2e 64 64 73 00 73 70 61 63 65 5f 76 |oise.dds.space_v|
00003be0 61 72 69 61 74 69 6f 6e 5f 64 75 6d 6d 79 2e 64 |ariation_dummy.d|
00003bf0 64 73 00 77 61 76 65 73 5f 68 65 69 67 68 74 73 |ds.waves_heights|
00003c00 30 2e 64 64 73 00 8f ec 87 4a 28 d0 f7 c7 70 11 |0.dds....J(...p.|
00003c10 03 07 0d 33 ed 77 1e 9b ef 05 00 00 00 00 05 00 |...3.w..........|
&lt;/code>&lt;/pre>&lt;p>Okay, the string bundle ends at 00003c05; that&amp;rsquo;s quite near 3bf6, so this is certainly the offset to this third section or the end of the bundle of strings.&lt;/p>
&lt;p>Most likely, the offset is not from the start of the file but from a specific point in the header (this field? end of header?); that&amp;rsquo;s why we get a -15 difference (0x00003c05 - 0x3bf6 = 0xF = 15). This -15 value is constant between files.&lt;/p>
&lt;p>So we have:&lt;/p>
&lt;pre tabindex="0">&lt;code>+====+====+====+====+====+====+====+====++====+====+====+====+====+====+====+====+
| HS | HS | HS | HS | HS | HS | HS | HS || OF | OF | OF | OF | OF | OF | OF | OF |
+====+====+====+====+====+====+====+====++====+====+====+====+====+====+====+====+
|&amp;lt;------------ header size (?) --------&amp;gt;||&amp;lt;---- offset third section start -----&amp;gt;|
&lt;/code>&lt;/pre>&lt;p>Last 64 bits:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux 6623042/idx » &lt;span style="color:#66d9ef">for&lt;/span> i in *;&lt;span style="color:#66d9ef">do&lt;/span> hexdump -C $i | head -n &lt;span style="color:#ae81ff">4&lt;/span> | tail -n 1;&lt;span style="color:#66d9ef">done&lt;/span> | less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
00000030 40 af 03 00 00 00 00 00 1b 00 00 00 00 00 00 00 |@...............|
00000030 54 8d 1f 00 00 00 00 00 21 00 00 00 00 00 00 00 |T.......!.......|
00000030 8e ae 00 00 00 00 00 00 17 00 00 00 00 00 00 00 |................|
00000030 02 81 00 00 00 00 00 00 13 00 00 00 00 00 00 00 |................|
00000030 c9 24 00 00 00 00 00 00 1f 00 00 00 00 00 00 00 |.$..............|
[...]
&lt;/code>&lt;/pre>&lt;p>So, we have a 64-bit integer, which is a relatively low value. This means it&amp;rsquo;s most likely a size or an offset.&lt;/p>
&lt;p>If we pick one:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux 6623042/idx » hexdump -C system_data.idx | less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
00000030 36 71 00 00 00 00 00 00 13 00 00 00 00 00 00 00 |6q..............|
[...]
&lt;/code>&lt;/pre>&lt;p>We can see that the &lt;code>36 71 00 00 00 00 00 00&lt;/code>, 28982 once converted to decimal, is remarkably close to the file size (29043 bytes).&lt;/p>
&lt;p>From there, we can guess it might be three things:&lt;/p>
&lt;ul>
&lt;li>the actual index file size&lt;/li>
&lt;li>an offset to something at the end of the file&lt;/li>
&lt;li>pointer to the end of the third section&lt;/li>
&lt;/ul>
&lt;p>Let&amp;rsquo;s note that for now, and figure out the finer details at implementation time.&lt;/p>
&lt;p>So, to recap, here is the header section format:&lt;/p>
&lt;pre tabindex="0">&lt;code>+====+====+====+====++====+====+====+====++====+====+====+====++====+====+====+====+
| MA | MA | MA | MA || 00 | 00 | 00 | 02 || ID | ID | ID | ID || 40 | 00 | 00 | 00 |
+====+====+====+====++====+====+====+====++====+====+====+====++====+====+====+====+
|&amp;lt;----- magic -----&amp;gt;||&amp;lt;----- ???? ------&amp;gt;||&amp;lt;---- id/crc -----&amp;gt;||&amp;lt;----- ??????? ---&amp;gt;|

+====+====+====+====++====+====+====+====++====+====+====+====++====+====+====+====+
| FD | FD | FD | FD || FI | FI | FI | FI || 01 | 00 | 00 | 00 || 00 | 00 | 00 | 00 |
+====+====+====+====++====+====+====+====++====+====+====+====++====+====+====+====+
|&amp;lt;file + dir count &amp;gt;||&amp;lt;-- file count ---&amp;gt;||&amp;lt;-------------- ???? ------------------&amp;gt;|

+====+====+====+====+====+====+====+=====++=====+====+====+====+====+====+====+====+
| HS | HS | HS | HS | HS | HS | HS | HS || OF | OF | OF | OF | OF | OF | OF | OF |
+====+====+====+====+====+====+====+=====++=====+====+====+====+====+====+====+====+
|&amp;lt;------------ header size (?) ---------&amp;gt;||&amp;lt;----- offset third section start -----&amp;gt;|

+====+====+====+====+====+====+====+=====+
| OE | OE | OE | OE | OE | OE | OE | OE |
+====+====+====+====+====+====+====+=====+
|&amp;lt;---- offset third section end --------&amp;gt;|
&lt;/code>&lt;/pre>&lt;h2 id="filename-section">Filename Section&lt;/h2>
&lt;p>Not much to say there.&lt;/p>
&lt;p>Here&amp;rsquo;s what it looks like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux 6775398/idx » hexdump -C system_data.idx| less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
00002700 fb 14 00 00 00 00 00 00 74 d1 1b 8d f4 ff 7a ce |........t.....z.|
00002710 a4 eb 1b 3e 50 21 d8 74 4b 44 53 74 6f 72 61 67 |...&amp;gt;P!.tKDStorag|
00002720 65 2e 62 69 6e 00 77 61 76 65 73 5f 68 65 69 67 |e.bin.waves_heig|
00002730 68 74 73 31 2e 64 64 73 00 61 6e 69 6d 61 74 65 |hts1.dds.animate|
[...]
000028c0 74 73 2e 62 69 6e 00 6d 69 73 63 53 65 74 74 69 |ts.bin.miscSetti|
000028d0 6e 67 73 2e 78 6d 6c 00 64 61 6d 61 67 65 5f 64 |ngs.xml.damage_d|
000028e0 65 63 5f 32 5f 64 2e 64 64 32 00 64 61 6d 61 67 |ec_2_d.dd2.damag|
000028f0 65 5f 64 65 63 5f 31 5f 65 2e 64 64 73 00 63 72 |e_dec_1_e.dds.cr|
[...]
00003be0 61 72 69 61 74 69 6f 6e 5f 64 75 6d 6d 79 2e 64 |ariation_dummy.d|
00003bf0 64 73 00 77 61 76 65 73 5f 68 65 69 67 68 74 73 |ds.waves_heights|
00003c00 30 2e 64 64 73 00 8f 0c 9a ba 4f 40 b6 93 70 11 |0.dds.....O@..p.|
00003c10 03 07 0d 33 ed 77 00 00 00 00 00 00 00 00 05 00 |...3.w..........|
00003c20 00 00 01 00 00 00 f5 21 00 00 bf 00 45 5c 6c 36 |.......!....E\l6|
&lt;/code>&lt;/pre>&lt;p>So it&amp;rsquo;s a bunch of &lt;code>\0&lt;/code> separated strings. The only thing interesting to note is that it&amp;rsquo;s not a fixed size section.&lt;/p>
&lt;h2 id="pkg-pointer-section">PKG Pointer Section&lt;/h2>
&lt;p>Here what it looks like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux 6775398/idx » hexdump -C system_data.idx| less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
00003bf0 64 73 00 77 61 76 65 73 5f 68 65 69 67 68 74 73 |ds.waves_heights|
00003c00 30 2e 64 64 73 00 8f 0c 9a ba 4f 40 b6 93 70 11 |0.dds.....O@..p.|
00003c10 03 07 0d 33 ed 77 00 00 00 00 00 00 00 00 05 00 |...3.w..........|
00003c20 00 00 01 00 00 00 f5 21 00 00 bf 00 45 5c 6c 36 |.......!....E\l6|
00003c30 00 00 00 00 00 00 8f ec 87 4a 28 d0 f7 c7 70 11 |.........J(...p.|
00003c40 03 07 0d 33 ed 77 1e 9b ef 05 00 00 00 00 05 00 |...3.w..........|
00003c50 00 00 01 00 00 00 15 15 01 00 03 77 63 97 3e ab |...........wc.&amp;gt;.|
00003c60 02 00 00 00 00 00 ad 70 a2 e7 ac 2c 4f 6b 70 11 |.......p...,Okp.|
00003c70 03 07 0d 33 ed 77 05 22 00 00 00 00 00 00 05 00 |...3.w.&amp;#34;........|
00003c80 00 00 01 00 00 00 cb 01 00 00 6d b9 de c1 ad 0c |..........m.....|
00003c90 00 00 00 00 00 00 8e c7 6a 58 7c 86 62 33 70 11 |........jX|.b3p.|
00003ca0 03 07 0d 33 ed 77 e0 23 00 00 00 00 00 00 05 00 |...3.w.#........|
00003cb0 00 00 01 00 00 00 65 00 00 00 f1 d2 87 5a d2 00 |......e......Z..|
00003cc0 00 00 00 00 00 00 8e 43 3d e9 cf 49 52 a4 70 11 |.......C=..IR.p.|
00003cd0 03 07 0d 33 ed 77 4d 1f 6a 05 00 00 00 00 05 00 |...3.wM.j.......|
00003ce0 00 00 01 00 00 00 bb 19 00 00 f7 53 4a b1 38 ab |...........SJ.8.|
00003cf0 00 00 00 00 00 00 0e 3c 9a 6d 22 de 7b da 70 11 |.......&amp;lt;.m&amp;#34;.{.p.|
00003d00 03 07 0d 33 ed 77 55 24 00 00 00 00 00 00 05 00 |...3.wU$........|
00003d10 00 00 01 00 00 00 72 0d 00 00 83 0a 72 88 a3 5c |......r.....r..\|
00003d20 00 00 00 00 00 00 98 45 00 7a 16 6e 84 21 70 11 |.......E.z.n.!p.|
00003d30 03 07 0d 33 ed 77 73 ce cb 06 00 00 00 00 05 00 |...3.ws.........|
00003d40 00 00 01 00 00 00 f9 b6 b7 00 ff 7e f7 7b 5b 30 |...........~.{[0|
00003d50 b8 00 00 00 00 00 98 51 4a 00 2c 12 71 ad 70 11 |.......QJ.,.q.p.|
00003d60 03 07 0d 33 ed 77 7e 63 75 05 00 00 00 00 05 00 |...3.w~cu.......|
[...]
00007100 00 00 01 00 00 00 6f 09 00 00 fc 56 94 f8 9a 37 |......o....V...7|
00007110 00 00 00 00 00 00 21 67 ac 70 22 ec ca b8 70 11 |......!g.p&amp;#34;...p.|
00007120 03 07 0d 33 ed 77 28 f9 15 0a 00 00 00 00 05 00 |...3.w(.........|
00007130 00 00 01 00 00 00 0b 2d 00 00 03 bd b0 50 67 e9 |.......-.....Pg.|
00007140 00 00 00 00 00 00 15 00 00 00 00 00 00 00 18 00 |................|
00007150 00 00 00 00 00 00 70 11 03 07 0d 33 ed 77 73 79 |......p....3.wsy|
00007160 73 74 65 6d 5f 64 61 74 61 5f 30 30 30 31 2e 70 |stem_data_0001.p|
00007170 6b 67 00 |kg.|
00007173
(END)
&lt;/code>&lt;/pre>&lt;p>This section contains 384-bit records (48 bytes each) linking files to their data in &lt;code>.pkg&lt;/code> files through IDs and offsets.&lt;/p>
&lt;p>Each record contains metadata IDs, PKG file offsets, data sizes, and compression types.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># let&amp;#39;s skip the first 6 bytes to align hexdump output&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kakwa@linux 6775398/idx » hexdump -s &lt;span style="color:#ae81ff">6&lt;/span> -C system_data.idx| less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
00003be6 6f 6e 5f 64 75 6d 6d 79 2e 64 64 73 00 77 61 76 |on_dummy.dds.wav|
00003bf6 65 73 5f 68 65 69 67 68 74 73 30 2e 64 64 73 00 |es_heights0.dds.|
00003c06 8f 0c 9a ba 4f 40 b6 93 70 11 03 07 0d 33 ed 77 |....O@..p....3.w|
00003c16 00 00 00 00 00 00 00 00 05 00 00 00 01 00 00 00 |................|
00003c26 f5 21 00 00 bf 00 45 5c 6c 36 00 00 00 00 00 00 |.!....E\l6......|
00003c36 8f ec 87 4a 28 d0 f7 c7 70 11 03 07 0d 33 ed 77 |...J(...p....3.w|
00003c46 1e 9b ef 05 00 00 00 00 05 00 00 00 01 00 00 00 |................|
00003c56 15 15 01 00 03 77 63 97 3e ab 02 00 00 00 00 00 |.....wc.&amp;gt;.......|
00003c66 ad 70 a2 e7 ac 2c 4f 6b 70 11 03 07 0d 33 ed 77 |.p...,Okp....3.w|
[...]
&lt;/code>&lt;/pre>&lt;p>Much nicer!&lt;/p>
&lt;p>With that, we immediately notice a cycle, with the &lt;code>70 11 03 07 0d 33 ed 77&lt;/code> value.&lt;/p>
&lt;p>There are 6 × 64 = 384 bits between each &lt;code>70 11 03 07 0d 33 ed 77&lt;/code> value.&lt;/p>
&lt;p>Let&amp;rsquo;s try to confirm that with the IDs.&lt;/p>
&lt;p>We are spotting the &lt;code>bf 00 45 5c 6c 36&lt;/code> seen previously in the &lt;code>.pkg&lt;/code> file. 384 bits later, we see &lt;code>03 77 63 97 3e ab 02&lt;/code>.&lt;/p>
&lt;p>With a bit of digging (it&amp;rsquo;s right in the middle of the &lt;code>.pkg&lt;/code> file, and this ID is not neatly in a 64-bit aligned chunk because of the variable size of the DEFLATE blocks), we indeed find it.&lt;/p>
&lt;p>We have indeed a 384-bit cycle, which neatly fits in 3 lines of hexdump!&lt;/p>
&lt;p>So each of these is one record:&lt;/p>
&lt;pre tabindex="0">&lt;code>00003c06 8f 0c 9a ba 4f 40 b6 93 70 11 03 07 0d 33 ed 77 |....O@..p....3.w|
00003c16 00 00 00 00 00 00 00 00 05 00 00 00 01 00 00 00 |................|
00003c26 f5 21 00 00 bf 00 45 5c 6c 36 00 00 00 00 00 00 |.!....E\l6......|
&lt;/code>&lt;/pre>&lt;pre tabindex="0">&lt;code>00003c36 8f ec 87 4a 28 d0 f7 c7 70 11 03 07 0d 33 ed 77 |...J(...p....3.w|
00003c46 1e 9b ef 05 00 00 00 00 05 00 00 00 01 00 00 00 |................|
00003c56 15 15 01 00 03 77 63 97 3e ab 02 00 00 00 00 00 |.....wc.&amp;gt;.......|
&lt;/code>&lt;/pre>&lt;pre tabindex="0">&lt;code>00003c66 ad 70 a2 e7 ac 2c 4f 6b 70 11 03 07 0d 33 ed 77 |.p...,Okp....3.w|
00003c76 05 22 00 00 00 00 00 00 05 00 00 00 01 00 00 00 |.&amp;#34;..............|
00003c86 cb 01 00 00 6d b9 de c1 ad 0c 00 00 00 00 00 00 |....m...........|
&lt;/code>&lt;/pre>&lt;pre tabindex="0">&lt;code>00003c96 8e c7 6a 58 7c 86 62 33 70 11 03 07 0d 33 ed 77 |..jX|.b3p....3.w|
00003ca6 e0 23 00 00 00 00 00 00 05 00 00 00 01 00 00 00 |.#..............|
00003cb6 65 00 00 00 f1 d2 87 5a d2 00 00 00 00 00 00 00 |e......Z........|
&lt;/code>&lt;/pre>&lt;pre tabindex="0">&lt;code>00003cc6 8e 43 3d e9 cf 49 52 a4 70 11 03 07 0d 33 ed 77 |.C=..IR.p....3.w|
00003cd6 4d 1f 6a 05 00 00 00 00 05 00 00 00 01 00 00 00 |M.j.............|
00003ce6 bb 19 00 00 f7 53 4a b1 38 ab 00 00 00 00 00 00 |.....SJ.8.......|
&lt;/code>&lt;/pre>&lt;p>All these records are from the same file,&lt;/p>
&lt;p>let&amp;rsquo;s grab a few from other files:&lt;/p>
&lt;p>File 2:&lt;/p>
&lt;pre tabindex="0">&lt;code>0000f6fd 58 fe 65 b4 59 b6 b0 77 a4 8c 78 6a 58 aa 65 84 |X.e.Y..w..xjX.e.|
0000f70d 00 00 00 00 00 00 00 00 05 00 00 00 01 00 00 00 |................|
0000f71d bc 99 05 00 f4 3a 67 8b 80 00 08 00 00 00 00 00 |.....:g.........|
&lt;/code>&lt;/pre>&lt;pre tabindex="0">&lt;code>0000f72d a0 e5 c7 22 cc 49 d3 31 a4 8c 78 6a 58 aa 65 84 |...&amp;#34;.I.1..xjX.e.|
0000f73d 3e 9e 7e 05 00 00 00 00 05 00 00 00 01 00 00 00 |&amp;gt;.~.............|
0000f74d 79 c6 03 00 dc b8 5f 80 80 00 08 00 00 00 00 00 |y....._.........|
&lt;/code>&lt;/pre>&lt;p>File 3:&lt;/p>
&lt;pre tabindex="0">&lt;code>00002d0c ed cf 33 f8 a5 94 53 56 0d a7 9c b9 bf 60 f5 3e |..3...SV.....`.&amp;gt;|
00002d1c 40 a8 08 07 00 00 00 00 00 00 00 00 00 00 00 00 |@...............|
00002d2c 38 ab 00 00 5d cf 4e b6 38 ab 00 00 00 00 00 00 |8...].N.8.......|
&lt;/code>&lt;/pre>&lt;pre tabindex="0">&lt;code>00002d3c ed ea da 52 4e 8f 70 ed 0d a7 9c b9 bf 60 f5 3e |...RN.p......`.&amp;gt;|
00002d4c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00002d5c 98 00 00 00 a4 63 f2 9b 98 00 00 00 00 00 00 00 |.....c..........|
&lt;/code>&lt;/pre>&lt;p>Lets study:&lt;/p>
&lt;pre tabindex="0">&lt;code>00003c06 8f 0c 9a ba 4f 40 b6 93 70 11 03 07 0d 33 ed 77 |....O@..p....3.w|
00003c16 00 00 00 00 00 00 00 00 05 00 00 00 01 00 00 00 |................|
00003c26 f5 21 00 00 bf 00 45 5c 6c 36 00 00 00 00 00 00 |.!....E\l6......|
&lt;/code>&lt;/pre>&lt;p>For the first 128 bits we get:&lt;/p>
&lt;pre tabindex="0">&lt;code>00003c06 8f 0c 9a ba 4f 40 b6 93 70 11 03 07 0d 33 ed 77 |....O@..p....3.w|
&lt;/code>&lt;/pre>&lt;p>From the fact the last 64 bits are constant, we can deduce we have probably two 64 bits integers in the first 128 bits&lt;/p>
&lt;p>Once again, these are using all the available bits and seem rather random. It&amp;rsquo;s difficult to link these to their role, so&amp;hellip; lets simply ignore these for now.&lt;/p>
&lt;pre tabindex="0">&lt;code>+====+====+====+====+====+====+====+====++====+====+====+====+====+====+====+====+
| UO | UO | UO | UO | UO | UO | UO | UO || UT | UT | UT | UT | UT | UT | UT | UT |
+====+====+====+====+====+====+====+====++====+====+====+====+====+====+====+====+
|&amp;lt;------------ unknown 1 --------------&amp;gt;||&amp;lt;------------- unknow 2 --------------&amp;gt;|
| 64 bits || 64 bits |
&lt;/code>&lt;/pre>&lt;p>Next 64 bits, we have a low value 64 bits integer, so most likely an offset. It is also suspiciously at &lt;code>0x0&lt;/code> for the first record which is also the first data chunk in the &lt;code>.pkg&lt;/code> file.&lt;/p>
&lt;p>So, it&amp;rsquo;s most likely the start of a data chunk in a &lt;code>.pkg&lt;/code> file. Looking at other records confirms that.&lt;/p>
&lt;p>Next, we have two extremely low value 32 bits (0x5 and 0x1). So once again, most likely some kind of enum, lets call them &lt;code>type1&lt;/code> and &lt;code>type2&lt;/code>.&lt;/p>
&lt;pre tabindex="0">&lt;code>+====+====+====+====+====+====+====+====++====+====+====+====++====+====+====+====+
| OF | OF | OF | OF | OF | OF | OF | OF || T1 | T1 | T1 | T1 || T2 | T2 | T2 | T2 |
+====+====+====+====+====+====+====+====++====+====+====+====++====+====+====+====+
|&amp;lt;----------start offset pkg ----------&amp;gt;||&amp;lt;---- type 1 -----&amp;gt;||&amp;lt;----- type 2 ----&amp;gt;|
| 64 bits || 32 bits || 32 bits |
&lt;/code>&lt;/pre>&lt;p>For the Last 128 bits, we get the following:&lt;/p>
&lt;pre tabindex="0">&lt;code>00003c26 f5 21 00 00 bf 00 45 5c 6c 36 00 00 00 00 00 00 |.!....E\l6......|
&lt;/code>&lt;/pre>&lt;p>So, here, we see our &lt;code>.pkg&lt;/code> ID (&lt;code>bf 00 45 5c 6c 36&lt;/code>) right in the middle. Given its size, this ID is probably stored on 64 bits.&lt;/p>
&lt;p>After that, last 32 bits, we get a bunch of &lt;code>00&lt;/code>, maybe a reserved field, but more likely some kind of padding.&lt;/p>
&lt;p>Before that, we get a low value 32 bits integer. When comparing with the &lt;code>.pkg&lt;/code> file, &lt;code>f5 21 00 00&lt;/code> is the offset where the first data chunk ends.&lt;/p>
&lt;p>So it&amp;rsquo;s the end offset. But 32 bits seems rather small to store such offset (specially given the start offset is 64 bits. Also, for other data chunks, this doesn&amp;rsquo;t line-up.&lt;/p>
&lt;p>However, it could very much be a relative offset (to the start of the data chunk).&lt;/p>
&lt;p>Lets validate that with the second record:&lt;/p>
&lt;pre tabindex="0">&lt;code>00003c36 8f ec 87 4a 28 d0 f7 c7 70 11 03 07 0d 33 ed 77 |...J(...p....3.w|
00003c46 1e 9b ef 05 00 00 00 00 05 00 00 00 01 00 00 00 |................|
00003c56 15 15 01 00 03 77 63 97 3e ab 02 00 00 00 00 00 |.....wc.&amp;gt;.......|
&lt;/code>&lt;/pre>&lt;p>More hexdump! (searching the data chunk using the &lt;code>03 77 63 97 3e ab 02&lt;/code> ID and the start offset &lt;code>1e 9b ef 05 00 00 00 00&lt;/code>, or &lt;code>0x05ef9b1e&lt;/code> once we take endianess into account):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux World of Warships/res_packages » hexdump -C system_data_0001.pkg | less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
05ef9b10 00 00 17 4b 28 42 80 40 00 00 00 00 00 00 8c 7d |...K(B.@.......}|
05ef9b20 3b 50 5d d9 b6 dd 7d b6 3e 76 15 b2 5f 20 89 04 |;P]...}.&amp;gt;v.._ ..|
05ef9b30 55 bd 00 44 82 aa ec 7a 9c bd f7 79 45 57 39 40 |U..D...z...yEW9@|
05ef9b40 90 d0 4e 8c c0 01 9d 21 48 e8 0c 41 a2 ce 10 24 |..N....!H..A...$|
[...]
05f0b010 d1 d7 52 b0 de cc fa 9c 2b 8d b5 57 a4 02 ff 1a |..R.....+..W....|
05f0b020 43 5d 2d 43 3f cc d1 0b f8 88 92 a8 9f 5a 70 64 |C]-C?........Zpd|
05f0b030 46 fb 7f 00 00 00 00 03 77 63 97 3e ab 02 00 00 |F.......wc.&amp;gt;....|
05f0b040 00 00 00 94 b7 67 90 db 68 9e e6 d9 1f 36 62 6f |.....g..h....6bo|
05f0b050 6f 22 76 f6 62 e3 62 77 67 7a 7a ba ab bb 8c aa |o&amp;#34;v.b.bwgzz.....|
05f0b060 4a 52 95 5c ca a5 cf 64 d2 24 bd 27 41 d0 00 04 |JR.\...d.$.&amp;#39;A...|
[...]
&lt;/code>&lt;/pre>&lt;p>We indeed find the start of our data chunk at &lt;code>05ef9b1e&lt;/code> (&lt;code>8c&lt;/code> after a bunch of &lt;code>00&lt;/code> on the first line).&lt;/p>
&lt;p>And looking for the &lt;code>00 00 00 00 ID ID [...]&lt;/code> pattern in between the data chunks, we can determine the end of this chunk to be at &lt;code>0x05f0b032&lt;/code>.&lt;/p>
&lt;p>Doing &lt;code>0x05f0b032 - 0x05ef9b1e&lt;/code>, we get &lt;code>0x11514&lt;/code>, that&amp;rsquo;s almost our &lt;code>15 15 01 00&lt;/code> once we swap endianess, and add &lt;code>1&lt;/code>.&lt;/p>
&lt;pre tabindex="0">&lt;code>+====+====+====+====++====+====+====+====+====+====+====+====++====+====+====+====+
| OE | OE | OE | OE || ID | ID | ID | ID | ID | ID | ID | ID || 00 | 00 | 00 | 00 |
+====+====+====+====++====+====+====+====+====+====+====+====++====+====+====+====+
|&amp;lt;-- offset end ---&amp;gt;||&amp;lt;------------- ID &amp;#39;.pkg&amp;#39; -------------&amp;gt;||&amp;lt;---- padding ----&amp;gt;|
| 32 bits || 64 bits || 32 bits |
&lt;/code>&lt;/pre>&lt;p>So to recap, we have:&lt;/p>
&lt;pre tabindex="0">&lt;code>+====+====+====+====+====+====+====+====++=====+====+====+====+====+====+====+====+
| UO | UO | UO | UO | UO | UO | UO | UO || UT | UT | UT | UT | UT | UT | UT | UT |
+====+====+====+====+====+====+====+====++=====+====+====+====+====+====+====+====+
|&amp;lt;------------ unknown 1 --------------&amp;gt;||&amp;lt;-------------- unknown 2 -------------&amp;gt;|
| 64 bits || 64 bits |
+====+====+====+====+====+====+====+====++====+====+====+====++====+====+====+====+
| OF | OF | OF | OF | OF | OF | OF | OF || T1 | T1 | T1 | T1 || T2 | T2 | T2 | T2 |
+====+====+====+====+====+====+====+====++====+====+====+====++====+====+====+====+
|&amp;lt;--------- start offset pkg ----------&amp;gt;||&amp;lt;---- type 1 -----&amp;gt;||&amp;lt;----- type 2 ----&amp;gt;|
| 64 bits || 32 bits || 32 bits |
+====+====+====+====++====+====+====+====+====+====+====+====++====+====+====+====+
| OE | OE | OE | OE || ID | ID | ID | ID | ID | ID | ID | ID || 00 | 00 | 00 | 00 |
+====+====+====+====++====+====+====+====+====+====+====+====++====+====+====+====+
|&amp;lt;-- offset end ---&amp;gt;||&amp;lt;------------- ID &amp;#39;.pkg&amp;#39; -------------&amp;gt;||&amp;lt;---- padding ----&amp;gt;|
| 32 bits || 64 bits || 32 bits |
&lt;/code>&lt;/pre>&lt;h2 id="the-last-bits">The Last Bits&lt;/h2>
&lt;p>So, okay, we have the core of the 3 parts of the file.&lt;/p>
&lt;p>But we can see the last few bits don&amp;rsquo;t follow this pattern (especially with the &lt;code>.pkg&lt;/code> file name), which means we have a footer:&lt;/p>
&lt;pre tabindex="0">&lt;code># Last block
00007116 21 67 ac 70 22 ec ca b8 70 11 03 07 0d 33 ed 77 |!g.p&amp;#34;...p....3.w|
00007126 28 f9 15 0a 00 00 00 00 05 00 00 00 01 00 00 00 |(...............|
00007136 0b 2d 00 00 03 bd b0 50 67 e9 00 00 00 00 00 00 |.-.....Pg.......|
# Footer
00007146 15 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00 |................|
00007156 70 11 03 07 0d 33 ed 77 73 79 73 74 65 6d 5f 64 |p....3.wsystem_d|
00007166 61 74 61 5f 30 30 30 31 2e 70 6b 67 00 |ata_0001.pkg.|
&lt;/code>&lt;/pre>&lt;p>If we look at the content, we have 3 × 64 bits before the name starts (&lt;code>73 79 73 74 65 6d 5f 64&lt;/code> for &lt;code>system_d&lt;/code>)&lt;/p>
&lt;p>Looking at it more closely, given all the &lt;code>00&lt;/code>, it seems we have three 64-bit integers there.&lt;/p>
&lt;ul>
&lt;li>&lt;code>15 00 00 00 00 00 00 00&lt;/code>&lt;/li>
&lt;li>&lt;code>18 00 00 00 00 00 00 00&lt;/code>&lt;/li>
&lt;li>&lt;code>70 11 03 07 0d 33 ed 77&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>70 11 03 07 0d 33 ed 77&lt;/code> is the &amp;ldquo;unknown 2&amp;rdquo; we saw previously, so still no luck, but let&amp;rsquo;s name it the same way.&lt;/p>
&lt;p>&lt;code>18 00 00 00 00 00 00 00&lt;/code> seems to have the same value across all files, so probably not that important.&lt;/p>
&lt;p>The only one that varies across files is the &lt;code>15 00 00 00 00 00 00 00&lt;/code>, but always in the same kind of values around 0x15. In decimal it&amp;rsquo;s 21.&lt;/p>
&lt;p>Strangely, &lt;code>system_data_0001.pkg&lt;/code> is 20 chars long, 21 if we include the &lt;code>\0&lt;/code> at the end.&lt;/p>
&lt;p>So we can deduce it&amp;rsquo;s actually the &lt;code>.pkg&lt;/code> file name string size.&lt;/p>
&lt;p>So we have:&lt;/p>
&lt;pre tabindex="0">&lt;code>+====+====+====+====+====+====+====+====++=====+====+====+====+====+====+====+====+
| UO | UO | UO | UO | UO | UO | UO | UO || U3 | U3 | U3 | U3 | U3 | U3 | U3 | U3 |
+====+====+====+====+====+====+====+====++=====+====+====+====+====+====+====+====+
|&amp;lt;--------- size pkg file name --------&amp;gt;||&amp;lt;-------------- unknown 3 -------------&amp;gt;|
| 64 bits || 64 bits |
+=====+====+====+====+====+====+====+====+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~...
| UT | UT | UT | UT | UT | UT | UT | UT | file name string
+=====+====+====+====+====+====+====+====+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~...
|&amp;lt;-------------- unknown 2 -------------&amp;gt;|
| 64 bits |
&lt;/code>&lt;/pre>&lt;h1 id="recap-part-3">Recap (Part 3)&lt;/h1>
&lt;ul>
&lt;li>Mapped overall &lt;code>.idx&lt;/code> layout into 4 parts:
&lt;ul>
&lt;li>Header: magic &lt;code>ISFP&lt;/code>, counts (entries and files), header-size-like field, offsets to start/end of the third section, and a per-file id/crc-like value.&lt;/li>
&lt;li>Names: &lt;code>\0&lt;/code>-separated file and directory names.&lt;/li>
&lt;li>Records section (per-record 384 bits): two 64-bit unknowns (likely ids, one enabling parent/child hierarchy), 64-bit start offset in &lt;code>.pkg&lt;/code>, 2×32-bit types, 32-bit relative end size, 64-bit &lt;code>.pkg&lt;/code> chunk id, and padding.&lt;/li>
&lt;li>Footer: &lt;code>.pkg&lt;/code> filename length, an unknown 64-bit value, a repeated 64-bit constant, then the &lt;code>.pkg&lt;/code> filename string.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Established the link between names and &lt;code>.pkg&lt;/code> chunks via IDs and offsets, enough to reconstruct directories and locate data.&lt;/li>
&lt;/ul>
&lt;p>In &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part4/">the next part&lt;/a> of this series we will start the actual implementation.&lt;/p></description></item><item><title>Reversing WoWs Resource Format - Part 2/5: Looking For The Metadata</title><link>https://technically.kakwalab.ovh/posts/wows_depack_part2/</link><pubDate>Mon, 25 Aug 2025 00:02:00 +0200</pubDate><author>carpentier.pf@gmail.com (Pierre-François Carpentier)</author><guid>https://technically.kakwalab.ovh/posts/wows_depack_part2/</guid><description>&lt;ul>
&lt;li>Part 1 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part1/">Searching The Data&lt;/a>&lt;/li>
&lt;li>Part 2 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part2/">Looking For The Metadata&lt;/a>&lt;/li>
&lt;li>Part 3 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part3/">Dissecting The Index&lt;/a>&lt;/li>
&lt;li>Part 4 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part4/">Reading Everything&lt;/a>&lt;/li>
&lt;li>Part 5 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part5/">Tidying-Up The Project&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="searching--reading-the-metadata">Searching &amp;amp; Reading The Metadata&lt;/h1>
&lt;p>In Part 1, we discovered:&lt;/p>
&lt;ul>
&lt;li>Data lives in &lt;code>res_packages/&lt;/code> as custom &lt;code>.pkg&lt;/code> archives.&lt;/li>
&lt;li>Each &lt;code>.pkg&lt;/code> is a sequence of DEFLATE-compressed blobs separated by 64-bit IDs with zero padding.&lt;/li>
&lt;li>No file names inside &lt;code>.pkg&lt;/code>; names/paths must exist elsewhere (in indexes).&lt;/li>
&lt;/ul>
&lt;p>Now we need to find where and how the file and directory structure is stored.&lt;/p>
&lt;h2 id="back-to-file-exploration">Back To File Exploration&lt;/h2>
&lt;p>The metadata hopefully isn&amp;rsquo;t embedded in executables. Let&amp;rsquo;s search:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># List all files&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># then remove uninteresting bits like replays, crashes, DLLs, logs, .pkg&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># or CEF stuff (embedded Chrome used for the armory, inventory, dockyard and clan base))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kakwa@linux Games/World of Warships » find ./ -type f | grep -v cef | grep -v replays &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> | grep -v crashes | grep -v &lt;span style="color:#e6db74">&amp;#39;.pkg&amp;#39;&lt;/span> | grep -v &lt;span style="color:#e6db74">&amp;#39;.dll&amp;#39;&lt;/span> | grep -v &lt;span style="color:#e6db74">&amp;#39;.log&amp;#39;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> | grep -v &lt;span style="color:#e6db74">&amp;#39;\.exe&amp;#39;&lt;/span> | less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
./bin/6775398/res/texts/pl/LC_MESSAGES/global.mo
./bin/6775398/res/texts/zh_tw/LC_MESSAGES/global.mo
./bin/6775398/res/texts/fr/LC_MESSAGES/global.mo
./bin/6775398/res/texts/zh_sg/LC_MESSAGES/global.mo
./bin/6775398/res/camerasConsumer.xml
./bin/6775398/bin32/paths.xml
./bin/6775398/bin32/Licenses.txt
./bin/6775398/bin32/monitor_config.json
./bin/6775398/bin64/paths.xml
./bin/6775398/bin64/Licenses.txt
./bin/6775398/bin64/monitor_config.json
[...]
./bin/6775398/idx/spaces_dock_dry.idx
./bin/6775398/idx/spaces_dock_hsf.idx
./bin/6775398/idx/spaces_sea_hope.idx
./bin/6775398/idx/vehicles_pve.idx
./bin/6775398/idx/vehicles_level8_fr.idx
./bin/6775398/idx/vehicles_level5_panasia.idx
[...]
&lt;/code>&lt;/pre>&lt;p>The &lt;code>.idx&lt;/code> files look promising, especially their names match the &lt;code>.pkg&lt;/code> files:&lt;/p>
&lt;ul>
&lt;li>&lt;code>spaces_dock_hsf.idx&lt;/code> → &lt;code>spaces_dock_hsf_0001.pkg&lt;/code>&lt;/li>
&lt;li>&lt;code>vehicles_level9_ned.idx&lt;/code> → &lt;code>vehicles_level9_ned_0001.pkg&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="checking-we-have-the-metadata">Checking We Have The Metadata&lt;/h2>
&lt;p>Let&amp;rsquo;s take a look:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux bin/6775398/idx » strings -n &lt;span style="color:#ae81ff">5&lt;/span> system_data.idx
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>#%B&amp;#39;E
E)zj&amp;#39;
FM&amp;#39;lb
%}n:b
(	?A+
c|&amp;#39;lY
zc78&amp;#39;
tKDStorage.bin
waves_heights1.dds
animatedMiscs.xml
LowerDeck.dds
[...]
maps
highlight_noise.dds
space_variation_dummy.dds
waves_heights0.dds
4F$)p
:M+Xp
F?mep
wsystem_data_0001.pkg
[...]
&lt;/code>&lt;/pre>&lt;p>Bingo, we have all the file names, and at the end, the name of the corresponding &lt;code>.pkg&lt;/code> file.&lt;/p>
&lt;p>These &lt;code>.idx&lt;/code> files, as the extension indicates, are our indexes containing all the file names and metadata.&lt;/p>
&lt;p>Also, note that we have a few names (like &lt;code>maps&lt;/code> or &lt;code>helpers&lt;/code>) without extensions, these are probably directory names.&lt;/p>
&lt;p>&lt;strong>Note&lt;/strong>: &lt;code>bin/&lt;/code> directory and game versions&lt;/p>
&lt;p>The &lt;code>./bin&lt;/code> directory contains build-numbered subdirectories (5241351, 6081105, etc.), with WoWs keeping the current and previous builds. We&amp;rsquo;ll use the highest number for the latest indexes.&lt;/p>
&lt;h2 id="general-layout-of-the-index-file">General Layout Of the Index File&lt;/h2>
&lt;p>Let&amp;rsquo;s examine an index file:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux 6775398/idx » hexdump -C system_data.idx | less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>00000000 49 53 46 50 00 00 00 02 91 9d 39 b4 40 00 00 00 |ISFP......9.@...|
00000010 37 01 00 00 1c 01 00 00 01 00 00 00 00 00 00 00 |7...............|
00000020 28 00 00 00 00 00 00 00 f6 3b 00 00 00 00 00 00 |(........;......|
00000030 36 71 00 00 00 00 00 00 0e 00 00 00 00 00 00 00 |6q..............|
00000040 e0 26 00 00 00 00 00 00 8f 0c 9a ba 4f 40 b6 93 |.&amp;amp;..........O@..|
00000050 27 b9 08 b1 d1 a1 b1 db 13 00 00 00 00 00 00 00 |&amp;#39;...............|
00000060 ce 26 00 00 00 00 00 00 8f ec 87 4a 28 d0 f7 c7 |.&amp;amp;.........J(...|
00000070 a4 eb 1b 3e 50 21 d8 74 12 00 00 00 00 00 00 00 |...&amp;gt;P!.t........|
00000080 c1 26 00 00 00 00 00 00 ad 70 a2 e7 ac 2c 4f 6b |.&amp;amp;.......p...,Ok|
00000090 27 b9 08 b1 d1 a1 b1 db 0e 00 00 00 00 00 00 00 |&amp;#39;...............|
000000a0 b3 26 00 00 00 00 00 00 4e 84 a5 6a 94 dc 1f 7f |.&amp;amp;......N..j....|
000000b0 62 f5 aa 4b 5e 15 7f 93 10 00 00 00 00 00 00 00 |b..K^...........|
000000c0 a1 26 00 00 00 00 00 00 4e b0 fe 23 62 40 a5 65 |.&amp;amp;......N..#b@.e|
000000d0 27 b9 08 b1 d1 a1 b1 db 20 00 00 00 00 00 00 00 |&amp;#39;....... .......|
000000e0 91 26 00 00 00 00 00 00 8e c7 6a 58 7c 86 62 33 |.&amp;amp;........jX|.b3|
000000f0 27 b9 08 b1 d1 a1 b1 db 0f 00 00 00 00 00 00 00 |&amp;#39;...............|
00000100 91 26 00 00 00 00 00 00 8e 43 3d e9 cf 49 52 a4 |.&amp;amp;.......C=..IR.|
00000110 62 f5 aa 4b 5e 15 7f 93 16 00 00 00 00 00 00 00 |b..K^...........|
00000120 80 26 00 00 00 00 00 00 0e 3c 9a 6d 22 de 7b da |.&amp;amp;.......&amp;lt;.m&amp;#34;.{.|
00000130 4e b0 fe 23 62 40 a5 65 0b 00 00 00 00 00 00 00 |N..#b@.e........|
00000140 76 26 00 00 00 00 00 00 0e 48 58 ea 50 44 1a 47 |v&amp;amp;.......HX.PD.G|
00000150 df 61 50 67 c7 3a dd 7a 13 00 00 00 00 00 00 00 |.aPg.:.z........|
00000160 61 26 00 00 00 00 00 00 e0 9f c3 bd d2 12 20 04 |a&amp;amp;............ .|
00000170 09 28 f1 df 2d 04 93 de 0b 00 00 00 00 00 00 00 |.(..-...........|
00000180 54 26 00 00 00 00 00 00 e0 95 53 f6 cc 08 c0 46 |T&amp;amp;........S....F|
00000190 06 cf 85 bd 69 99 e2 46 0d 00 00 00 00 00 00 00 |....i..F........|
000001a0 3f 26 00 00 00 00 00 00 e0 99 68 5e 2d 70 13 72 |?&amp;amp;........h^-p.r|
000001b0 4c d1 2e 30 73 38 d9 13 10 00 00 00 00 00 00 00 |L..0s8..........|
[...]
000026c0 0d 15 00 00 00 00 00 00 ce 6a 28 bc cf e7 79 c8 |.........j(...y.|
000026d0 a4 eb 1b 3e 50 21 d8 74 1a 00 00 00 00 00 00 00 |...&amp;gt;P!.t........|
000026e0 01 15 00 00 00 00 00 00 6c c0 c9 f7 7e 00 03 05 |........l...~...|
000026f0 a4 eb 1b 3e 50 21 d8 74 13 00 00 00 00 00 00 00 |...&amp;gt;P!.t........|
00002700 fb 14 00 00 00 00 00 00 74 d1 1b 8d f4 ff 7a ce |........t.....z.|
00002710 a4 eb 1b 3e 50 21 d8 74 4b 44 53 74 6f 72 61 67 |...&amp;gt;P!.tKDStorag|
00002720 65 2e 62 69 6e 00 77 61 76 65 73 5f 68 65 69 67 |e.bin.waves_heig|
00002730 68 74 73 31 2e 64 64 73 00 61 6e 69 6d 61 74 65 |hts1.dds.animate|
00002740 64 4d 69 73 63 73 2e 78 6d 6c 00 4c 6f 77 65 72 |dMiscs.xml.Lower|
00002750 44 65 63 6b 2e 64 64 73 00 63 6f 6d 6d 61 6e 64 |Deck.dds.command|
[...]
00003bc0 2e 64 64 73 00 68 69 67 68 6c 69 67 68 74 5f 6e |.dds.highlight_n|
00003bd0 6f 69 73 65 2e 64 64 73 00 73 70 61 63 65 5f 76 |oise.dds.space_v|
00003be0 61 72 69 61 74 69 6f 6e 5f 64 75 6d 6d 79 2e 64 |ariation_dummy.d|
00003bf0 64 73 00 77 61 76 65 73 5f 68 65 69 67 68 74 73 |ds.waves_heights|
00003c00 30 2e 64 64 73 00 8f 0c 9a ba 4f 40 b6 93 70 11 |0.dds.....O@..p.|
00003c10 03 07 0d 33 ed 77 00 00 00 00 00 00 00 00 05 00 |...3.w..........|
00003c20 00 00 01 00 00 00 f5 21 00 00 bf 00 45 5c 6c 36 |.......!....E\l6|
00003c30 00 00 00 00 00 00 8f ec 87 4a 28 d0 f7 c7 70 11 |.........J(...p.|
00003c40 03 07 0d 33 ed 77 1e 9b ef 05 00 00 00 00 05 00 |...3.w..........|
00003c50 00 00 01 00 00 00 15 15 01 00 03 77 63 97 3e ab |...........wc.&amp;gt;.|
00003c60 02 00 00 00 00 00 ad 70 a2 e7 ac 2c 4f 6b 70 11 |.......p...,Okp.|
00003c70 03 07 0d 33 ed 77 05 22 00 00 00 00 00 00 05 00 |...3.w.&amp;#34;........|
00003c80 00 00 01 00 00 00 cb 01 00 00 6d b9 de c1 ad 0c |..........m.....|
[...]
000070a0 00 00 01 00 00 00 90 24 00 00 62 c1 d9 06 f8 d8 |.......$..b.....|
000070b0 00 00 00 00 00 00 57 b3 82 06 56 f0 2a e6 70 11 |......W...V.*.p.|
000070c0 03 07 0d 33 ed 77 ea cc 15 0a 00 00 00 00 05 00 |...3.w..........|
000070d0 00 00 01 00 00 00 7c 01 00 00 84 a8 12 1d 61 04 |......|.......a.|
000070e0 00 00 00 00 00 00 57 64 91 29 c9 3c f0 96 70 11 |......Wd.).&amp;lt;..p.|
000070f0 03 07 0d 33 ed 77 a9 ef 15 0a 00 00 00 00 05 00 |...3.w..........|
00007100 00 00 01 00 00 00 6f 09 00 00 fc 56 94 f8 9a 37 |......o....V...7|
00007110 00 00 00 00 00 00 21 67 ac 70 22 ec ca b8 70 11 |......!g.p&amp;#34;...p.|
00007120 03 07 0d 33 ed 77 28 f9 15 0a 00 00 00 00 05 00 |...3.w(.........|
00007130 00 00 01 00 00 00 0b 2d 00 00 03 bd b0 50 67 e9 |.......-.....Pg.|
00007140 00 00 00 00 00 00 15 00 00 00 00 00 00 00 18 00 |................|
00007150 00 00 00 00 00 00 70 11 03 07 0d 33 ed 77 73 79 |......p....3.wsy|
00007160 73 74 65 6d 5f 64 61 74 61 5f 30 30 30 31 2e 70 |stem_data_0001.p|
00007170 6b 67 00 |kg.|
&lt;/code>&lt;/pre>&lt;p>The general layout of the file appears to be at least in 3 chunks:&lt;/p>
&lt;ul>
&lt;li>A first chunk of metadata&lt;/li>
&lt;li>All the file name strings &lt;code>\0&lt;/code>-separated, but also a few strings without extensions, probably directory names&lt;/li>
&lt;li>A second chunk of metadata&lt;/li>
&lt;/ul>
&lt;p>Let&amp;rsquo;s look for the IDs we found in the corresponding &lt;code>.pkg&lt;/code> (here &lt;code>system_data_0001.pkg&lt;/code>), for example &lt;code>00 00 00 00 | bf 00 45 5c | 6c 36 00 00 | 00 00 00 00&lt;/code>.&lt;/p>
&lt;pre tabindex="0">&lt;code>[......................] 00 8f 0c 9a ba 4f 40 b6 93 70 11 |0.dds.....O@..p.|
00003c10 03 07 0d 33 ed 77 00 00 00 00 00 00 00 00 05 00 |...3.w..........|
00003c20 00 00 01 00 00 00 f5 21 00 00 bf 00 45 5c 6c 36 |.......!....E\l6|
00003c30 00 00 00 00 00 00 8f ec [...]
&lt;/code>&lt;/pre>&lt;p>Ok, it&amp;rsquo;s there, in the second chunk. And it also works if we test for other IDs. We have at least a link by ID between the &lt;code>.idx&lt;/code> and the &lt;code>.pkg&lt;/code> file.&lt;/p>
&lt;p>We will come back later to the second chunk, remembering that, but let&amp;rsquo;s focus on the first chunk for now.&lt;/p>
&lt;h1 id="recap-part-2">Recap (Part 2)&lt;/h1>
&lt;ul>
&lt;li>Identified &lt;code>.idx&lt;/code> files in &lt;code>./bin/&amp;lt;latest&amp;gt;/idx/&lt;/code> as indexes for &lt;code>.pkg&lt;/code> archives and matched them by name.&lt;/li>
&lt;li>Determine that that the index is constituted of 5 parts:
&lt;ul>
&lt;li>Header&lt;/li>
&lt;li>Metadata section&lt;/li>
&lt;li>Names&lt;/li>
&lt;li>Records section&lt;/li>
&lt;li>Footer&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>In &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part3/">the next part&lt;/a> of this series we will reverse the index file format in details.&lt;/p></description></item><item><title>Reversing WoWs Resource Format - Part 1/5: Searching The Data</title><link>https://technically.kakwalab.ovh/posts/wows_depack_part1/</link><pubDate>Sun, 24 Aug 2025 01:00:00 +0200</pubDate><author>carpentier.pf@gmail.com (Pierre-François Carpentier)</author><guid>https://technically.kakwalab.ovh/posts/wows_depack_part1/</guid><description>&lt;ul>
&lt;li>Part 1 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part1/">Searching The Data&lt;/a>&lt;/li>
&lt;li>Part 2 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part2/">Looking For The Metadata&lt;/a>&lt;/li>
&lt;li>Part 3 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part3/">Dissecting The Index&lt;/a>&lt;/li>
&lt;li>Part 4 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part4/">Reading Everything&lt;/a>&lt;/li>
&lt;li>Part 5 — &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part5/">Tidying-Up The Project&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="introduction">Introduction&lt;/h1>
&lt;p>Firstly, a disclaimer: this is the first time I&amp;rsquo;m doing this kind of exercise, so the process described here is far from ideal, and the tools used are probably less than adequate.&lt;/p>
&lt;p>Also, I&amp;rsquo;m writing this after various findings, so the process seems quite straightforward. In reality, it was full of dead ends and flows of ideas (good and bad) that came up while staring at hexdumps for hours.&lt;/p>
&lt;h2 id="motivation">Motivation&lt;/h2>
&lt;p>I&amp;rsquo;ve always wanted to play around with World of Warships game content for various reasons, from extracting things like armor layouts or in-game parameters to the 3D models themselves.&lt;/p>
&lt;p>There is already a closed-source Windows tool doing that: &lt;a href="https://forum.worldofwarships.eu/topic/113847-all-wows-unpack-tool-unpack-game-client-resources/" target="_blank">wows-unpack&lt;/a>&lt;/p>
&lt;p>But being a pro-OSS Linux user, this tool doesn&amp;rsquo;t suit me well as it is annoying to use (&lt;code>Wine&lt;/code>) and cannot be readily integrated into other programs.&lt;/p>
&lt;p>I also wanted to do it as an intellectual &amp;amp; learning exercise of reverse engineering.&lt;/p>
&lt;h2 id="goals">Goals&lt;/h2>
&lt;ul>
&lt;li>Reverse engineer the format sufficiently for data extraction&lt;/li>
&lt;li>Document the specification for others to build upon&lt;/li>
&lt;li>Create an OSS CLI tool&lt;/li>
&lt;li>Develop a reusable OSS library&lt;/li>
&lt;/ul>
&lt;h1 id="reverse-engineering-process">Reverse Engineering Process&lt;/h1>
&lt;h2 id="initial-analysis">Initial Analysis&lt;/h2>
&lt;h3 id="game-file-structure">Game File Structure&lt;/h3>
&lt;p>Unfortunately, I only have very fuzzy memories of the initial steps I took since they came from an initial effort 2 years before the bulk of the reversing.&lt;/p>
&lt;p>The gist of it was to look at the game files and see where most of the data was:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux Games/World of Warships » ls
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...] api-ms-win-crt-runtime-l1-1-0.dll concrt140.dll msvcp140_codecvt_ids.dll Reports vcruntime140_1.dll
[...] api-ms-win-crt-stdio-l1-1-0.dll crashes msvcp140.dll res_packages vcruntime140.dll
[...] api-ms-win-crt-time-l1-1-0.dll GameCheck placeholder.txt screenshot WorldOfWarships.exe
[...] bin msvcp140_atomic_wait.dll replays user_preferences.xml
&lt;/code>&lt;/pre>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux Games/World of Warships » du -hd &lt;span style="color:#ae81ff">1&lt;/span> | sort -h
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>4.0K ./Reports
12K ./patche
2.8M ./Wallpapers
4.2M ./GameCheck
4.4M ./screenshot
49M ./replays
55M ./lib
464M ./profile
593M ./crashes
1.2G ./bin
62G ./res_packages
73G .
&lt;/code>&lt;/pre>&lt;p>So here, most of the data is in the &lt;code>res_packages/&lt;/code> directory. Let&amp;rsquo;s take a closer look:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux Games/World of Warships » ls res_packages
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
spaces_dock_1_april_0001.pkg spaces_faroe_0001.pkg spaces_ridge_0001.pkg
vehicles_level10_panamerica_0001.pkg vehicles_level3_panasia_0001.pkg vehicles_level6_jap_0001.pkg
vehicles_level8_it_0001.pkg z_vehicles_events_0001.pkg
[...]
&lt;/code>&lt;/pre>&lt;p>Let&amp;rsquo;s use &lt;code>file&lt;/code> to see what type of files we are dealing with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux Games/World of Warships » cd res_packages
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kakwa@linux World of Warships/res_packages » file *
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
spaces_dock_ny_0001.pkg: data
spaces_dock_ocean_0001.pkg: data
spaces_dock_prem_0001.pkg: Microsoft DirectDraw Surface (DDS): 4 x 4, compressed using DX10
spaces_dock_rio_0001.pkg: data
spaces_dock_spb_0001.pkg: data
spaces_exterior_0001.pkg: data
spaces_faroe_0001.pkg: OpenPGP Secret Key
spaces_labyrinth_0001.pkg: data
spaces_lepve_0001.pkg: data
spaces_military_navigation_0001.pkg: data
spaces_naval_base_0001.pkg: DOS executable (COM), maybe with interrupt 22h, start instruction 0x8cbc075c 534dd337
spaces_naval_defense_0001.pkg: data
[...]
&lt;/code>&lt;/pre>&lt;p>So mostly &lt;code>data&lt;/code> (i.e., unknown binary format), and looking at the files which are not &lt;code>data&lt;/code>, they are in fact most likely false positives. So we are dealing with a custom format.&lt;/p>
&lt;h3 id="file-analysis">File Analysis&lt;/h3>
&lt;p>Next, let&amp;rsquo;s try to see if we have some clear-text strings in the files using the &lt;code>strings&lt;/code> utility:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux World of Warships/res_packages » strings *
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>YyHIKzR
+!?&amp;lt;
m:C-
h4.1
~s3o
]2bm
]$ }
O=z$
&amp;lt;27P
=C=k]
dQz{4
$Zm|
$ZOc
nV&amp;amp;
&amp;lt;4n5
r&amp;gt;Zs%
6?Iw
KqM&amp;amp;u
[...]
&lt;/code>&lt;/pre>&lt;p>Nada, that&amp;rsquo;s just garbage. So we are dealing with a completely binary format.&lt;/p>
&lt;p>Next, let&amp;rsquo;s try to compress a file:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Size before&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kakwa@linux World of Warships/res_packages » ls -l vehicles_level4_usa_0001.pkg
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-rwxr-xr-x &lt;span style="color:#ae81ff">1&lt;/span> kakwa kakwa &lt;span style="color:#ae81ff">15356139&lt;/span> Jan &lt;span style="color:#ae81ff">17&lt;/span> 19:01 vehicles_level4_usa_0001.pkg
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Compress&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kakwa@linux World of Warships/res_packages » gzip vehicles_level4_usa_0001.pkg
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Size After&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ls -l vehicles_level4_usa_0001.pkg.gz
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>-rwxr-xr-x &lt;span style="color:#ae81ff">1&lt;/span> kakwa kakwa &lt;span style="color:#ae81ff">15332196&lt;/span> Jan &lt;span style="color:#ae81ff">17&lt;/span> 19:01 vehicles_level4_usa_0001.pkg.gz
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Ok, barely any change in size, which means the data is probably compressed (not a big surprise since a lot of formats such as images are compressed).&lt;/p>
&lt;p>Then, the process is a little fuzzy in my memory. But if I recall correctly, I did the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux World of Warships/res_packages » hexdump -C vehicles_level4_usa_0001.pkg | less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>00000000 95 58 7f 50 9b e7 7d 7f f4 2a 24 e2 55 64 7c bb |.X.P..}..*$.Ud|.|
00000010 a4 be 5b 77 8d e6 45 0e 08 83 ba 5d b1 b7 b8 35 |..[w..E....]...5|
00000020 4a 2f e9 71 d9 3f c4 e5 45 2a 05 a1 92 eb 9d 2a |J/.q.?..E*.....*|
00000030 77 41 73 43 ab c3 31 bc c8 97 3b 41 c2 d0 f5 82 |wAsC..1...;A....|
00000040 fd 2e 69 e3 77 22 84 97 57 01 d1 b4 04 82 8d 90 |..i.w&amp;#34;..W.......|
00000050 f1 fe 68 bc 76 f3 ed ac c0 75 ae 51 c9 b9 21 72 |..h.v....u.Q..!r|
00000060 1d 58 36 05 59 46 7a f7 fd 3c 90 6d d7 6d 7f 4c |.X6.YFz..&amp;lt;.m.m.L|
00000070 77 f8 e3 e7 f7 f7 c7 e7 f9 7e bf cf fb e4 93 5f |w........~....._|
[...]
&lt;/code>&lt;/pre>&lt;p>I hexdumped one of the files, looking for some pattern that would help me determine the type of compression used. I was looking for things like padding or signatures repeating within the &lt;code>.pkg&lt;/code> file. I&amp;rsquo;m not sure how, but I finally determined the compression used was &lt;code>DEFLATE&lt;/code> (RFC 1951) (I vaguely remember &lt;code>7f f0&lt;/code> being a marker, but I might be mistaken).&lt;/p>
&lt;p>In any case, &lt;a href="https://en.wikipedia.org/wiki/List_of_file_signatures" target="_blank">The Wikipedia page listing file signatures&lt;/a> is really useful, as well as Googling around candidate patterns.&lt;/p>
&lt;p>I ended up creating &lt;a href="https://github.com/kakwa/brute-force-deflate" target="_blank">this tool&lt;/a> which tries to brute-force deflate all the sections of the file, and sure enough, I was able to extract some interesting files:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Extracting stuff&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kakwa@linux World of Warships/res_packages » bf-deflate -i system_data_0001.pkg -o systemout
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Look at the file types we just extracted&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kakwa@linux World of Warships/res_packages » file systemout/* | tail
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>systemout/000A15D8ED-000A15D8F3: ISO-8859 text, with no line terminators
systemout/000A15D8F7-000A15EF9A: XML 1.0 document, ASCII text
systemout/000A15EF9E-000A15EFA7: ISO-8859 text, with CR line terminators
systemout/000A15EFAA-000A15F919: XML 1.0 document, ASCII text
systemout/000A15F929-000A162634: exported SGML document, Unicode text, UTF-8 text, with CRLF line terminators
systemout/000A162644-000A165D7C: ASCII text, with CRLF line terminators
systemout/000A165D8C-000A16C774: ASCII text, with CRLF line terminators
systemout/000A16C784-000A16D41A: exported SGML document, ASCII text, with CRLF line terminators
systemout/000A16D41E-000A16D426: data
systemout/bf-Xe4fzss: empty
&lt;/code>&lt;/pre>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Look if we indeed got what &amp;#34;file&amp;#34; says it is&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kakwa@linux World of Warships/res_packages » cat systemout/000A15EFAA-000A15F919
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34; standalone=&amp;#34;no&amp;#34;?&amp;gt;
&amp;lt;root&amp;gt;
 &amp;lt;Implements&amp;gt;
 &amp;lt;Interface&amp;gt;VisionOwner&amp;lt;/Interface&amp;gt;
 &amp;lt;Interface&amp;gt;AtbaOwner&amp;lt;/Interface&amp;gt;
 &amp;lt;Interface&amp;gt;AirDefenceOwner&amp;lt;/Interface&amp;gt;
 &amp;lt;Interface&amp;gt;BattleLogicEntityOwner&amp;lt;/Interface&amp;gt;
 &amp;lt;Interface&amp;gt;DamageDealerOwner&amp;lt;/Interface&amp;gt;
 &amp;lt;Interface&amp;gt;DebugDrawEntity&amp;lt;/Interface&amp;gt;
 &amp;lt;/Implements&amp;gt;
&amp;lt;/root&amp;gt;
&lt;/code>&lt;/pre>&lt;p>Okay, we actually are able to extract real files!&lt;/p>
&lt;p>&amp;hellip;But without the names, it&amp;rsquo;s not that interesting.&lt;/p>
&lt;p>Note that in my &amp;ldquo;brute-force deflate&amp;rdquo; tool, I chose to name the files I managed to extract with the (approximate) corresponding start and end offsets of what my tool managed to uncompress (ex: &lt;code>000A165D8C-000A16C774&lt;/code>, start offset is &lt;code>000A165D8C&lt;/code>, end is &lt;code>000A16C774&lt;/code>). This makes it simpler to correlate each extracted file to a section in the original file.&lt;/p>
&lt;p>Going back to the reverse engineering, that was progress, but I then lost interest and didn&amp;rsquo;t follow up for two years.&lt;/p>
&lt;h3 id="follow-up">Follow-up&lt;/h3>
&lt;p>Two years later, I regained interest when I finally tested the Windows &lt;code>wows_unpack&lt;/code> tool. It revealed that files have individual names and paths—indicating a custom archive format probably similar to a &lt;code>.zip&lt;/code> file and most likely containing:&lt;/p>
&lt;ul>
&lt;li>Compressed data blobs&lt;/li>
&lt;li>Index containing file paths, types, IDs, and offsets&lt;/li>
&lt;/ul>
&lt;h3 id="pkg-file-format">PKG File Format&lt;/h3>
&lt;p>Let&amp;rsquo;s stare at more hexdumps:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>kakwa@linux World of Warships/res_unpack » hexdump -C system_data_0001.pkg | less
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>[...]
000021e0 b7 df f2 d7 ff e4 4f bd 52 f6 39 be f5 4a 92 2f |......O.R.9..J./|
000021f0 d9 8a f4 ff 01 00 00 00 00 bf 00 45 5c 6c 36 00 |...........E\l6.|
00002200 00 00 00 00 00 dd 97 cf 6e e2 30 10 c6 cf 54 ea |........n.0...T.|
00002210 3b 44 79 00 20 09 09 20 41 a5 22 ca 9f 83 b7 08 |;Dy. .. A.&amp;#34;.....|
00002220 38 ec cd 72 93 61 6b 6d 6c 47 ce a4 85 b7 5f 3b |8..r.akmlG...._;|
00002230 25 0b 6d 29 d5 ae ba 12 9b 63 26 33 e3 df f7 79 |%.m).....c&amp;amp;3...y|
00002240 ec 28 03 26 b9 60 08 09 e1 79 9c 37 b7 22 bd b9 |.(.&amp;amp;.`...y.7.&amp;#34;..|
[...]
000023b0 92 a0 6d bc 47 a6 f3 58 03 13 17 37 f7 16 d0 3b |..m.G..X...7...;|
000023c0 06 1c 31 44 f3 55 fa 2f dd af 44 bf fe 2f f9 05 |..1D.U./..D../..|
000023d0 00 00 00 00 6d b9 de c1 ad 0c 00 00 00 00 00 00 |....m...........|
000023e0 7d cf cd 0d 80 20 0c 80 d1 b3 26 ce c2 02 84 8b |}.... ....&amp;amp;.....|
000023f0 c6 01 dc 00 b5 fe 24 85 12 5a f6 57 8c 92 70 f1 |......$..Z.W..p.|
00002400 f8 b5 ef d0 ea 48 24 a6 6b 1b 0d de ce 08 ab 19 |.....H$.k.......|
00002410 2d 32 68 f5 e5 b3 42 70 e0 85 73 94 32 5b 4c 2c |-2h...Bp..s.2[L,|
00002420 c9 f5 78 86 9b bf c3 4a 2c e4 82 65 fe 11 7c 9c |..x....J,..e..|.|
00002430 61 20 c4 1f b2 27 3f 91 58 a1 98 11 57 aa 44 3e |a ...&amp;#39;?.X...W.D&amp;gt;|
00002440 4d ab e7 97 0b 00 00 00 00 f1 d2 87 5a d2 00 00 |M...........Z...|
00002450 00 00 00 00 00 ad 9c db 6e db b8 16 86 af 67 03 |........n.....g.|
00002460 fb 1d 3c b9 2f 3a 3e e4 50 20 0d c0 48 8c ad 89 |..&amp;lt;./:&amp;gt;.P ..H...|
00002470 2c 69 28 c9 4e 7a 23 b8 89 3b 35 26 89 03 c7 99 |,i(.Nz#..;5&amp;amp;....|
00002480 ee be fd 26 75 32 29 2e 52 4b 72 2f 0a a4 16 fd |...&amp;amp;u2).RKr/....|
00002490 7f bf 28 72 69 f1 e4 cb d7 dd fa 6d bd bf fa ef |..(ri......m....|
[...]
&lt;/code>&lt;/pre>&lt;p>I noticed the 128 bits pattern &lt;code>00 00 00 00 | xx xx xx xx | xx xx 00 00 | 00 00 00 00&lt;/code> repeating within the file (&lt;code>|&lt;/code> used to cut every 32 bits).&lt;/p>
&lt;p>The starts and ends of these small sections line up pretty well with the data sections I managed to extract:&lt;/p>
&lt;pre tabindex="0">&lt;code>0000000001-00000021F6
0000002206-00000023D1
00000023E1-0000002446
0000002456-00000031C8
&lt;/code>&lt;/pre>&lt;p>We indeed have the first &lt;code>00 00 [...]&lt;/code> pattern starting at offset 000021f4 and ending 00002205, which nearly matches 00000021F6 (end of first extracted file) and 0000002206 (start of second extracted file).
The next pattern starts at 000023d0 and ends at 000023df, which again lines up roughly with 00000023D1 and 00000023E1. And so on for all the sections.&lt;/p>
&lt;p>Note, my brute-force tool is most likely a bit buggy and probably adds a few +1 offsets here and there; also it is likely that the uncompression overflows a bit beyond the actual compressed data. But it is good enough for the purpose.&lt;/p>
&lt;p>Looking at the end of the file, we have this pattern repeating one final time at the very end:&lt;/p>
&lt;pre tabindex="0">&lt;code>0a16d3c0 79 b0 e2 a0 8e e3 a8 3c 4b d5 d4 51 a0 7b b2 a1 |y......&amp;lt;K..Q.{..|
0a16d3d0 32 6b 36 bf fc ce 46 b6 1e d6 2d b8 94 98 ea 74 |2k6...F...-....t|
0a16d3e0 ac 57 92 19 a0 2f 7a c5 43 23 1e 46 0e 1d a8 6f |.W.../z.C#.F...o|
0a16d3f0 9a fe f2 85 0e 6c 2f a4 8b 87 71 d4 5e 9a 8f d4 |.....l/...q.^...|
0a16d400 41 0f eb 85 aa b4 41 f7 ab af 86 39 6a d7 db af |A.....A....9j...|
0a16d410 2a 24 8b 8f 8d 7f 6a f6 3f 00 00 00 00 6b ba c9 |*$....j.?....k..|
0a16d420 70 eb 6c 00 00 00 00 00 00 |p.l......|
0a16d429
(END)
&lt;/code>&lt;/pre>&lt;p>Furthermore, the first uncompressed block seems to start right at the beginning of the file, so there is probably no header section.&lt;/p>
&lt;h3 id="pkg-structure">PKG Structure&lt;/h3>
&lt;p>Looking at a few other &lt;code>.pkg&lt;/code>, this pattern seems to be shared across all files.&lt;/p>
&lt;p>So we can deduce the &lt;code>.pkg&lt;/code> format is a concatenated list of sections like the following:&lt;/p>
&lt;pre tabindex="0">&lt;code>+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
| |
| Compressed Data (RFC 1951/Deflate) |
| |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
+====+====+====+====+====+====+====+====+====+====+====+====+====+====+====+====+
| 00 | 00 | 00 | 00 | XX | XX | XX | XX | XX | XX | 00 | 00 | 00 | 00 | 00 | 00 |
+====+====+====+====+====+====+====+====+====+====+====+====+====+====+====+====+
|&amp;lt;---- 0 padding --&amp;gt;|&amp;lt;--------- some kind of ID -----------&amp;gt;|&amp;lt;--- 0 padding ---&amp;gt;|
&lt;/code>&lt;/pre>&lt;p>Note that by this point, I&amp;rsquo;m making a lot of assumptions:&lt;/p>
&lt;ul>
&lt;li>I&amp;rsquo;m assuming that &lt;code>0 padding&lt;/code> 32-bit blocks are actually padding, but they could be fields that happened to be set to 0 most of the time&lt;/li>
&lt;li>I&amp;rsquo;m a bit puzzled by the 16-bit &lt;code>00 00&lt;/code> at the end of &lt;code>some kind of ID&lt;/code>&lt;/li>
&lt;li>I&amp;rsquo;m also not completely sure if the block containing the data is always compressed using DEFLATE&lt;/li>
&lt;li>I&amp;rsquo;m not even sure if this general file format is actually shared across all files.&lt;/li>
&lt;/ul>
&lt;p>But let&amp;rsquo;s go forward, this format seems common enough to still yield good results. Plus we can always go back and revisit this interpretation.&lt;/p>
&lt;h3 id="understanding-ids">Understanding IDs&lt;/h3>
&lt;p>The 64-bit values between data blocks appear random and high-value—too short for hashes, too large for offsets. These are likely simple random unique identifiers:&lt;/p>
&lt;pre tabindex="0">&lt;code>00 00 00 00 | bf 00 45 5c | 6c 36 00 00 | 00 00 00 00
00 00 00 00 | 6d b9 de c1 | ad 0c 00 00 | 00 00 00 00
00 00 00 00 | f1 d2 87 5a | d2 00 00 00 | 00 00 00 00
00 00 00 00 | 83 0a 72 88 | a3 5c 00 00 | 00 00 00 00
00 00 00 00 | 92 ab 31 63 | 91 2a 00 00 | 00 00 00 00
&lt;/code>&lt;/pre>&lt;h3 id="recap">Recap&lt;/h3>
&lt;p>We&amp;rsquo;ve identified the data storage format:&lt;/p>
&lt;ul>
&lt;li>Game data resides in &lt;code>res_packages/&lt;/code> directory&lt;/li>
&lt;li>&lt;code>.pkg&lt;/code> files are custom archives containing (mostly) DEFLATE-compressed blobs separated by 64-bit IDs&lt;/li>
&lt;li>File names and paths are probably stored separately&lt;/li>
&lt;/ul>
&lt;p>Getting the file metadata will be explored in &lt;a href="https://technically.kakwalab.ovh/posts/wows_depack_part2/">the next part&lt;/a> of this series.&lt;/p></description></item></channel></rss>