<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Millefeuille&#x27;s blog</title>
      <link>https://blog.millefeuille42.fr</link>
      <description>A silly storage engineer. I also happen to make music and manage my homelab.</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://blog.millefeuille42.fr/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Thu, 23 Apr 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>How I recovered from a wrong lvremove</title>
          <pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate>
          <author>Millefeuille</author>
          <link>https://blog.millefeuille42.fr/blog/lvremove/</link>
          <guid>https://blog.millefeuille42.fr/blog/lvremove/</guid>
          <description xml:base="https://blog.millefeuille42.fr/blog/lvremove/">&lt;p&gt;Have you ever hit &lt;code&gt;Enter&lt;&#x2F;code&gt; on your keyboard and immediately felt the greatest anguish of your life?&lt;&#x2F;p&gt;
&lt;p&gt;It was late in the afternoon, around 17:00. My team and I were doing some routine maintenance on a production environment. We needed to clean up a corrupt VHD, it was supposed to be a standard short five minute job… Instead, it turned into the most stressful hours of my life during which we struggled to recover an entire Storage Repository (SR) after I accidentally deleted all Logical Volumes (LV) in a Volume Group (VG).&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;
	note
	
	
	
	
&quot;&gt;
	&lt;p class=&quot;alert-title&quot;&gt;
		&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;Note&lt;&#x2F;p&gt;
	&lt;p&gt;In the aforementioned production environment, each VM disk (in the VHD format) is stored as a LV and is exposed to the VM as a block device. A SR is the meta-name for a group of VHD managed by a specific driver, here LVM. One SR is represented by a single VG.&lt;&#x2F;p&gt;

&lt;&#x2F;blockquote&gt;
&lt;p&gt;Here’s the story of how a simple syntax error caused a massive outage and how reading the man (and some bash scripts) saved the day by transforming a data loss into a 2 hours service interruption without a single byte of data lost.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-mistake&quot;&gt;The Mistake&lt;&#x2F;h2&gt;
&lt;p&gt;The job was simple: remove a single, corrupted Logical Volume.&lt;&#x2F;p&gt;
&lt;p&gt;I executed the command: &lt;code&gt;lvremove VG_NAME LV_NAME&lt;&#x2F;code&gt;. Any reader that once manipulated LVM could spot where the error lies… Usually a &lt;code&gt;lvremove&lt;&#x2F;code&gt; command is written in one of these ways:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash z-code&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;lvremove&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; VG_NAME&#x2F;LV_NAME&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;lvremove&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &#x2F;dev&#x2F;VG_NAME&#x2F;LV_NAME&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Because I forgot a slash &#x2F; didn’t specify the whole path, the command’s scope was evaluated much broader than intended, essentially targeting all LVs within that VG.&lt;&#x2F;p&gt;
&lt;p&gt;To make matters worse, I’ve been asked asked for confirmation to delete the management (MGT) LV. I hit &lt;code&gt;n&lt;&#x2F;code&gt; (no) to abort that specific deletion, but instead of exiting, LVM happily proceeded to delete all the &lt;em&gt;other&lt;&#x2F;em&gt; logical volumes in the group without further confirmation.&lt;&#x2F;p&gt;
&lt;p&gt;As I witnessed the terminal print line after line &lt;code&gt;Logical volume &quot;...&quot; successfully removed&lt;&#x2F;code&gt;, my colleagues and I immediately realised what had happened. Production VMs suddenly lost their underlying disks. All VM I&#x2F;Os halted.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-panic-and-the-missing-backups&quot;&gt;The Panic (and the missing backups)&lt;&#x2F;h2&gt;
&lt;p&gt;After a small and contained panic attack, I managed to get control over myself pretty quickly considering what was at stake and that I couldn’t let my team take the stress for me.&lt;&#x2F;p&gt;
&lt;p&gt;My first thought was,&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;“No problem, LVM automatically backs up metadata.”&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Normally, when LVM configuration changes, it saves a backup in &lt;code&gt;&#x2F;etc&#x2F;lvm&#x2F;archive&lt;&#x2F;code&gt;. You can just run a &lt;code&gt;vgcfgrestore&lt;&#x2F;code&gt; and be back in business in minutes.&lt;&#x2F;p&gt;
&lt;p&gt;We checked the archive directory. &lt;strong&gt;It was empty.&lt;&#x2F;strong&gt; Due to an edge-case in how this specific environment was configured, the standard LVM filesystem archives weren’t there. We needed to find another way.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-solution-the-lvm-metadata-ring-buffer&quot;&gt;The Solution: The LVM Metadata Ring Buffer&lt;&#x2F;h2&gt;
&lt;p&gt;Since we couldn’t rely on the standard LVM backups, I decided to look at the raw block device. Upon reading the man (yes, the man) I learnt that LVM maintains a circular ring buffer of recent configuration changes in the first ~1MB of the Physical Volume (PV) header. Allowing to essentially revert to any state immediately preceding an issue, let’s say a catastrophic typo.&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;
	note
	
	
	
	
&quot;&gt;
	&lt;p class=&quot;alert-title&quot;&gt;
		&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;Note&lt;&#x2F;p&gt;
	&lt;p&gt;I wasn’t left handling this alone. In the meantime my colleagues were searching for alternative solutions.&lt;&#x2F;p&gt;

&lt;&#x2F;blockquote&gt;
&lt;p&gt;Here is the step-by-step process I used to recover the volumes.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-identify-the-physical-volume&quot;&gt;1. Identify the Physical Volume&lt;&#x2F;h3&gt;
&lt;p&gt;First, I needed the exact disk backing the PV for the lost VG.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash z-code&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; Identify the device ID backing the SR&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;pvs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-pipe z-shell&quot;&gt;|&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;grep&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; VG_NAME&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; Result: &#x2F;dev&#x2F;disk&#x2F;by-id&#x2F;DISK_SERIAL&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;2-extracting-the-metadata-records&quot;&gt;2. Extracting the Metadata Records&lt;&#x2F;h3&gt;
&lt;p&gt;Instead of blindly dumping the entire 1MB header with &lt;code&gt;dd&lt;&#x2F;code&gt;, I used &lt;code&gt;pvck -v&lt;&#x2F;code&gt; to pinpoint the exact offset and size of every metadata record stored in the ring buffer.&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;
	note
	
	
	
	
&quot;&gt;
	&lt;p class=&quot;alert-title&quot;&gt;
		&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;Note&lt;&#x2F;p&gt;
	&lt;p&gt;I got really lucky here, depending on the version of LVM and some factors, it is possible that &lt;code&gt;pvck&lt;&#x2F;code&gt; doesn’t find the metadata entries!&lt;&#x2F;p&gt;

&lt;&#x2F;blockquote&gt;
&lt;p&gt;I made this little bash script to extract each historical state into its own raw file, and simultaneously parsed out the VHDs present in that state into a text file for easy comparison.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash z-code&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;pvck&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;v&lt;&#x2F;span&gt; &#x2F;dev&#x2F;disk&#x2F;by-id&#x2F;DISK_SERIAL &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-file-descriptor z-shell&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-file-descriptor z-shell&quot;&gt;1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-pipe z-shell&quot;&gt;|&lt;&#x2F;span&gt; &lt;span class=&quot;z-punctuation z-separator z-continuation z-line z-shell&quot;&gt;\
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;grep&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-string z-quoted z-single z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;Found LVM2 metadata record&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-pipe z-shell&quot;&gt;|&lt;&#x2F;span&gt; &lt;span class=&quot;z-punctuation z-separator z-continuation z-line z-shell&quot;&gt;\
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;sed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-string z-quoted z-single z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;s&#x2F;.*offset=\([0-9]*\), size=\([0-9]*\).*&#x2F;\1 \2&#x2F;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-logical z-pipe z-shell&quot;&gt;|&lt;&#x2F;span&gt; &lt;span class=&quot;z-punctuation z-separator z-continuation z-line z-shell&quot;&gt;\
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-keyword z-control z-loop z-while z-shell&quot;&gt;while&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-support z-function z-read z-shell&quot;&gt;read&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; off sz&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-logical z-continue z-shell&quot;&gt;;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-loop z-do z-shell&quot;&gt;do&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;    &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-support z-function z-echo z-shell&quot;&gt;echo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Offset: &lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;off&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; | Size: &lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;sz&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; | &lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;    &lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; Dump the specific metadata block&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;    &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;dd&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; if=&#x2F;dev&#x2F;disk&#x2F;by-id&#x2F;DISK_SERIAL bs=1 skip=&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;off&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; count=&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;sz&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt; metadata_&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;off&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;.raw&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;    &lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; Parse the VHDs inside that block for comparison&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;    &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;strings&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; metadata_&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;off&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;.raw&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-pipe z-shell&quot;&gt;|&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;grep&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;VHD-&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-pipe z-shell&quot;&gt;|&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;sed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-string z-quoted z-single z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;s&#x2F; {.*&#x2F;&#x2F;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-pipe z-shell&quot;&gt;|&lt;&#x2F;span&gt; &lt;span class=&quot;z-punctuation z-separator z-continuation z-line z-shell&quot;&gt;\
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;    &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;sort&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt; vhds_&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;off&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;.txt&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-keyword z-control z-loop z-end z-shell&quot;&gt;done&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since I needed the entry to be precise to the byte, I used a block size of 1. This was not optimal but I wasn’t in the mood to do some bash arithmetic.&lt;&#x2F;p&gt;
&lt;p&gt;Each LV had a &lt;code&gt;VHD-&lt;&#x2F;code&gt; prefix. I used that to extract the names and count of LVs of each entry quickly.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-differential-analysis&quot;&gt;3. Differential Analysis&lt;&#x2F;h3&gt;
&lt;p&gt;Now I had a bunch of text files representing different points in time. We knew from the console output of my disastrous &lt;code&gt;lvremove&lt;&#x2F;code&gt; command exactly how many LVs were deleted (46) and the one that remained (1). Therefore, the pre-incident state should have 47 VHDs.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash z-code&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; 1. Sort by VHD count to find the candidate states.&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;wc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;l&lt;&#x2F;span&gt; vhds_&lt;span class=&quot;z-keyword z-operator z-regexp z-quantifier z-shell&quot;&gt;*&lt;&#x2F;span&gt;.txt&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-pipe z-shell&quot;&gt;|&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;sort&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;rn&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-pipe z-shell&quot;&gt;|&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;grep&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-string z-quoted z-single z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;47 &lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; 2. Verify the difference between our candidate (offset 5775360) and the current broken state&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;comm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;13&lt;&#x2F;span&gt; vhds_deleted.txt vhds_candidate.txt&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; 3. Get the seqno of each candidate state and compare against the very next transaction&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;strings&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; metadata_candidate.raw&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-pipe z-shell&quot;&gt;|&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;grep&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; seqno &lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; output: 347&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;strings&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; metadata_&lt;span class=&quot;z-keyword z-operator z-regexp z-quantifier z-shell&quot;&gt;*&lt;&#x2F;span&gt;.raw&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-pipe z-shell&quot;&gt;|&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;grep&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; 348&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; Check that the difference between the desired state (347) and the first deletion step (348) is exactly 1 VHD&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;comm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;13&lt;&#x2F;span&gt; vhds_candidate.txt vhds_348.txt&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This confirmed we had the exact offset (&lt;code&gt;5775360&lt;&#x2F;code&gt;) right before the deletion started.&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;
	note
	
	
	
	
&quot;&gt;
	&lt;p class=&quot;alert-title&quot;&gt;
		&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;Note&lt;&#x2F;p&gt;
	&lt;p&gt;In the case of this &lt;code&gt;lvremove&lt;&#x2F;code&gt; command, each LV got deleted one by one. This meant one metadata entry per LV. Luckily there wasn’t enough LV to overflow the ring buffer.&lt;&#x2F;p&gt;

&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;4-the-restoration&quot;&gt;4. The Restoration&lt;&#x2F;h3&gt;
&lt;p&gt;With the correct raw metadata file isolated, we converted it into a standard LVM config file and did a dry-run test.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash z-code&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; Convert raw strings to config file&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;strings&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; metadata_5775360.raw &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt; recovery.vg&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; ALWAYS DO A DRY RUN FIRST&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;vgcfgrestore&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; VG_NAME&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;test&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;file&lt;&#x2F;span&gt; recovery.vg&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; The real deal&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;vgcfgrestore&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; VG_NAME&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;file&lt;&#x2F;span&gt; recovery.vg&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;5-service-rescan&quot;&gt;5. Service Rescan&lt;&#x2F;h3&gt;
&lt;p&gt;Once LVM was aware of the structure again, we rescanned the VG to make sure our storage driver could find each VHD.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash z-code&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;&#x2F;usr&#x2F;bin&#x2F;vhd-util&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; scan&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;f&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;m&lt;&#x2F;span&gt; VHD-&lt;span class=&quot;z-keyword z-operator z-regexp z-quantifier z-shell&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;l&lt;&#x2F;span&gt; VG_NAME&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;a rel=&quot;noopener nofollow noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;inv.nadeko.net&#x2F;watch?v=P3ALwKeSEYs&quot;&gt;YES!!!&lt;&#x2F;a&gt; All LVs were restored and 100% of the data was intact. We rebooted the affected VMs and normal operations resumed roughly two hours after the incident started.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;&#x2F;h2&gt;
&lt;p&gt;Prod outages, besides being absolutely terrible are also incredible learning opportunities. Here is what I took away from this one:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;RTFM: The solution came from the technical specs and the man. I later found &lt;a rel=&quot;noopener nofollow noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;fy.blackhats.net.au&#x2F;blog&#x2F;2019-11-26-recovering-lvm-when-a-device-is-missing-with-a-cache-pool-lv&#x2F;&quot;&gt;another blog post about this recovery method&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Do dry-runs before destructive commands: Doing a dry-run before anything could have prevented us from running the command with the wrong syntax.&lt;&#x2F;li&gt;
&lt;li&gt;Beware of fatigue: It was the end of the day. A simple peer review of the command syntax, or a dry-run flag, would have prevented this entirely. I know I get lenient with commands I use all the time.&lt;&#x2F;li&gt;
&lt;li&gt;Always backup your data: If the LVM archive&#x2F;backups were properly configured we could have restored to the previous state quickly and make this a matter of minutes.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;In the end, we saved the production environment without data loss. It was a stressful two hours, but the custom scripts and expertise gained made it a valuable learning experience.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;handling-stress&quot;&gt;Handling stress&lt;&#x2F;h2&gt;
&lt;p&gt;Watching the command delete each volume one by one triggered an intense wave of stress and a minor panic attack. I somehow managed to overcome it and became strangely calm and hyper-focused during the actual recovery process.&lt;&#x2F;p&gt;
&lt;p&gt;It was only after the call ended that I truly understood the phrase “the silence is deafening.” Sitting alone, drowning in that silence, was the worst feeling of the entire afternoon. Fortunately, I already had dinner plans with some friends, so I rushed outside to get some fresh air and hit the road. I called one of my friends on the way because I really needed to talk it out. I managed to pull myself together later on and actually had a wonderful evening.&lt;&#x2F;p&gt;
&lt;p&gt;Unsurprisingly, I am now the target of constant jokes whenever the topic of removing LVs or deleting data comes up. I honestly deserve it&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Coder workspaces with XCP-ng and XOA</title>
          <pubDate>Thu, 20 Mar 2025 00:00:00 +0000</pubDate>
          <author>Millefeuille</author>
          <link>https://blog.millefeuille42.fr/blog/coder-xcp-ng-xoa/</link>
          <guid>https://blog.millefeuille42.fr/blog/coder-xcp-ng-xoa/</guid>
          <description xml:base="https://blog.millefeuille42.fr/blog/coder-xcp-ng-xoa/">&lt;h1 id=&quot;introduction&quot;&gt;Introduction&lt;&#x2F;h1&gt;
&lt;p&gt;It’s been a long time since I ditched powerful, bulky, and pricey laptops for “cloud”-based dev environments.&lt;br &#x2F;&gt;
I started by using VSCode &lt;a rel=&quot;noopener nofollow noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;containers.dev&#x2F;&quot;&gt;devcontainers&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener nofollow noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.jetbrains.com&#x2F;remote-development&#x2F;gateway&#x2F;&quot;&gt;JetBrains Gateway&lt;&#x2F;a&gt;, I ended up sharing my servers with friends. This created the need for a more industrial, dedicated dev-env manager. After extensive research, I adopted &lt;a rel=&quot;noopener nofollow noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;coder.com&quot;&gt;Coder&lt;&#x2F;a&gt;, which provides all the features I need in their community edition (though more open core than open source, but at least &lt;a rel=&quot;noopener nofollow noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;sso.tax&#x2F;&quot;&gt;the SSO part is included in the open version&lt;&#x2F;a&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;Initially, I focused on using devcontainers, but hosting them on ARM machines quickly revealed their limitations. Now, I’ve shifted to hosting a few x86 machines running &lt;a rel=&quot;noopener nofollow noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;xcp-ng.org&#x2F;&quot;&gt;XCP-ng&lt;&#x2F;a&gt;, all managed through &lt;a rel=&quot;noopener nofollow noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;xen-orchestra.com&#x2F;#!&#x2F;xo-home&quot;&gt;XOA&lt;&#x2F;a&gt;. This setup would theoretically allow me to fully make use of my infrastructure and create high-performance, scalable development environments tailored to our needs.&lt;&#x2F;p&gt;
&lt;p&gt;However, there was no official or community-supported template for creating environments with Coder. Although I had never worked with Terraform before, which initially held me back, I finally decided to take the leap.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;understanding-xoa-and-terraform&quot;&gt;Understanding XOA and Terraform&lt;&#x2F;h1&gt;
&lt;p&gt;I started by creating a VM from an XOA template, along with its disk and network, and initializing it using cloud-init. Thanks to the Terraform documentation and &lt;a rel=&quot;noopener nofollow noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;registry.terraform.io&#x2F;providers&#x2F;vatesfr&#x2F;xenorchestra&#x2F;latest&#x2F;docs&quot;&gt;XOA’s Provider&lt;&#x2F;a&gt;, the process was pretty straightforward, and I was able to get a VM up and running within minutes. After a few iterations, I successfully set everything “cleanly” up using variables, checks, and automatic fills.&lt;&#x2F;p&gt;
&lt;p&gt;Next, it was time to integrate Coder as a template.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;getting-my-hands-on-coder-templates&quot;&gt;Getting My Hands on Coder Templates&lt;&#x2F;h1&gt;
&lt;p&gt;I started with the “Scratch” template and the &lt;a rel=&quot;noopener nofollow noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;coder.com&#x2F;docs&#x2F;tutorials&#x2F;template-from-scratch&quot;&gt;Write a template from scratch&lt;&#x2F;a&gt; tutorial to get familiar with the &lt;a rel=&quot;noopener nofollow noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;registry.terraform.io&#x2F;providers&#x2F;coder&#x2F;coder&#x2F;latest&#x2F;docs&quot;&gt;Coder Terraform provider&lt;&#x2F;a&gt;. I initially tried to use my previous Terraform file almost as-is, adding the Coder agent and VSCode server with what’s provided. The native integration with variables made it quite easy to set up.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;from-poc-to-complete-integration&quot;&gt;From POC to Complete Integration&lt;&#x2F;h1&gt;
&lt;p&gt;I wasn’t satisfied with the “template-provided” variables because I wanted my users as well as myself to have the flexibility to set their vCPU number, XOA template, etc., as they wish. This is when I discovered the &lt;code&gt;coder_parameter&lt;&#x2F;code&gt; data source, which allows exactly that! A few iterations later, I made it automatically search for available hosts, vCPUs, memory, etc., to ensure users couldn’t input incorrect values. I also added &lt;code&gt;coder_workspace&lt;&#x2F;code&gt; labels to provide info and links about the VM (host, SR, network, etc.).&lt;&#x2F;p&gt;
&lt;p&gt;At the time of writing, there are still some hacky parts, like the SR and Network listing, which the XOA Provider doesn’t seem provide (I might submit a PR soon to add these features if they are indeed not present). There are also some wording changes and Quality of Life (QoL) improvements that may require feedback from usage.&lt;&#x2F;p&gt;
&lt;p&gt;The template is hosted on &lt;a rel=&quot;noopener nofollow noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Millefeuille42&#x2F;coder-xcp-xoa-template&#x2F;tree&#x2F;master&quot;&gt;GitHub&lt;&#x2F;a&gt;—feel free to contribute with feedback and improvements! The template is provided under the MIT license.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;current-features&quot;&gt;Current Features&lt;&#x2F;h1&gt;
&lt;p&gt;At the time of writing, the template:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Deploys a VM on Xen Orchestra (XOA) from a specified template&lt;&#x2F;li&gt;
&lt;li&gt;Allows the template creator and developer to select the pool and host from available options on XOA&lt;&#x2F;li&gt;
&lt;li&gt;Configures VM settings like CPU, memory, disk, and networking&lt;&#x2F;li&gt;
&lt;li&gt;Installs user-specified packages&lt;&#x2F;li&gt;
&lt;li&gt;Supports custom user setup scripts&lt;&#x2F;li&gt;
&lt;li&gt;Automatically starts a &lt;a rel=&quot;noopener nofollow noreferrer&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;coder.com&#x2F;&quot;&gt;coder.com&lt;&#x2F;a&gt; agent&lt;&#x2F;li&gt;
&lt;li&gt;Optionally installs VS Code Server&lt;&#x2F;li&gt;
&lt;li&gt;Provides metadata links to the VM and related resources&lt;&#x2F;li&gt;
&lt;li&gt;Supports changing non-critical variables on the fly (memory, CPU, network)&lt;&#x2F;li&gt;
&lt;li&gt;Allows starting, stopping, and deleting workspaces cleanly&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Rewriting malloc</title>
          <pubDate>Fri, 02 Aug 2024 00:00:00 +0000</pubDate>
          <author>Millefeuille</author>
          <link>https://blog.millefeuille42.fr/blog/coding-malloc/</link>
          <guid>https://blog.millefeuille42.fr/blog/coding-malloc/</guid>
          <description xml:base="https://blog.millefeuille42.fr/blog/coding-malloc/">&lt;h1 id=&quot;mandatory-part&quot;&gt;Mandatory part&lt;&#x2F;h1&gt;
&lt;p&gt;Rewrite &lt;code&gt;malloc&lt;&#x2F;code&gt;, &lt;code&gt;free&lt;&#x2F;code&gt;, and &lt;code&gt;realloc&lt;&#x2F;code&gt; functions.&lt;&#x2F;p&gt;
&lt;p&gt;Also, write a &lt;code&gt;show_alloc_mem&lt;&#x2F;code&gt; function to print memory allocations.&lt;&#x2F;p&gt;
&lt;p&gt;Like the real malloc, the allocated memory must be stored in pre-allocated
“zones” (&lt;strong&gt;bins&lt;&#x2F;strong&gt;), unless the requested size is too big, then the allocation must
be &lt;strong&gt;fitted&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;
	note
	
	
	
	
&quot;&gt;
	&lt;p class=&quot;alert-title&quot;&gt;
		&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;Note&lt;&#x2F;p&gt;
	&lt;p&gt;Fitted allocations are allocations that are outside of a bin,
meaning it’s allocated directly with a call to &lt;code&gt;mmap&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;

&lt;&#x2F;blockquote&gt;
&lt;p&gt;Two zone types are required:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Tiny zones of &lt;strong&gt;N&lt;&#x2F;strong&gt; size, storing allocations from 0 to &lt;strong&gt;n&lt;&#x2F;strong&gt; size&lt;&#x2F;li&gt;
&lt;li&gt;Small zones of &lt;strong&gt;M&lt;&#x2F;strong&gt; size, storing allocations from &lt;strong&gt;n&lt;&#x2F;strong&gt;+1 to &lt;strong&gt;m&lt;&#x2F;strong&gt; size&lt;&#x2F;li&gt;
&lt;li&gt;Large allocations, fitted allocations beginning from &lt;strong&gt;m&lt;&#x2F;strong&gt;+1&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Zones size must be a multiple of the page size, acquired with &lt;code&gt;sysconf(_SC_PAGESIZE)&lt;&#x2F;code&gt;.
Allocations (and internally required allocations) must be executed with &lt;code&gt;mmap&lt;&#x2F;code&gt;,
de-allocations with &lt;code&gt;munmap&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;bonus-part&quot;&gt;Bonus part&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot; checked=&quot;&quot;&#x2F;&gt;
Making mandatory functions thread-safe&lt;&#x2F;li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot; checked=&quot;&quot;&#x2F;&gt;
Additional info print (mem dump, allocations history…) through flags or extra functions&lt;&#x2F;li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot; checked=&quot;&quot;&#x2F;&gt;
De-fragmentation of the bins&lt;&#x2F;li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&#x2F;&gt;
Debug flags&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;mandatory-part-code-explanation&quot;&gt;Mandatory part code explanation&lt;&#x2F;h1&gt;
&lt;p&gt;Slightly inspired by &lt;a class=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;sources.debian.org&#x2F;src&#x2F;glibc&#x2F;2.28-10&#x2F;malloc&#x2F;malloc.c&#x2F;&quot;&gt;debian glibc malloc&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;
	
	
	important
	
	
&quot;&gt;
	&lt;p class=&quot;alert-title&quot;&gt;
		&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;Important&lt;&#x2F;p&gt;
	&lt;p&gt;When words related to allocation are put between double quotes,
it means that it is not necessarily an allocation. Attributing a portion
of a bin is not an allocation; a fitted allocation is indeed an allocation.&lt;&#x2F;p&gt;

&lt;&#x2F;blockquote&gt;
&lt;p&gt;Since most of the explanations are not linked to real memory allocations made
with &lt;code&gt;mmap&lt;&#x2F;code&gt; and applies to bin or fitted “allocations”, whether it is an
attribution or an allocation does not change how it is organized.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;brief-explanation-of-memory-alignment&quot;&gt;Brief explanation of memory alignment&lt;&#x2F;h2&gt;
&lt;p&gt;The computer reads memory by sections of &lt;strong&gt;4&#x2F;8 bytes&lt;&#x2F;strong&gt;, or &lt;strong&gt;32&#x2F;64 bits&lt;&#x2F;strong&gt;; this determines
the name of the architecture.&lt;&#x2F;p&gt;
&lt;p&gt;Aligning memory corresponds to setting variables at the start of &lt;strong&gt;4&#x2F;8 bytes&lt;&#x2F;strong&gt; sections.
If this is not respected, it raises:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Constraint violation&lt;&#x2F;strong&gt;: an alignment error interrupts the execution of
instructions.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Non-optimal alignment&lt;&#x2F;strong&gt;: twice 4 well-aligned bytes are read,
then the 4 misaligned bytes are extracted from these 8 bytes,
which takes much longer than reading 4 well-aligned bytes.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Considering this, compilers will automatically align memory when possible
by padding memory with &lt;code&gt;char&lt;&#x2F;code&gt;. When the fields of a structure
are not positioned properly or the total size of a structure is not a multiple
of 4&#x2F;8, it will take more memory than it should, due to padding.&lt;&#x2F;p&gt;
&lt;p&gt;However, when freely attributing values in memory, it is required to be
careful with alignment to prevent unnecessary slowdowns.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;chunks&quot;&gt;Chunks&lt;&#x2F;h2&gt;
&lt;p&gt;Every memory “allocation” (bin or fitted) is preceded by a &lt;strong&gt;header&lt;&#x2F;strong&gt;.
The combination of a memory “allocation” (&lt;strong&gt;data&lt;&#x2F;strong&gt;) and its header is called a &lt;strong&gt;chunk&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The header bears the following information:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Allocation size (&lt;code&gt;unsigned long&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;Allocation real size (&lt;code&gt;unsigned long&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;Pointer to the next chunk (&lt;code&gt;void *&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;Pointer to the previous chunk (&lt;code&gt;void *&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For a total of &lt;strong&gt;16&#x2F;32 bytes&lt;&#x2F;strong&gt; on &lt;strong&gt;32&#x2F;64 bits&lt;&#x2F;strong&gt; architectures. The structure
itself is memory aligned, and the start of the data is also aligned.
The data size is always set to a multiple of 16, forcing
memory alignment on every architecture.&lt;&#x2F;p&gt;
&lt;p&gt;The data size is often larger than what was asked, to keep memory aligned.
The portion of data that matches the size asked by the user (&lt;strong&gt;real size&lt;&#x2F;strong&gt;) is called
&lt;strong&gt;provided data&lt;&#x2F;strong&gt;, the remainder is called &lt;strong&gt;tail&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;To sum things up, this is how a chunk is organized in memory (assuming a line is 4&#x2F;8 bytes):&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Section&lt;&#x2F;th&gt;&lt;th&gt;Data stored&lt;&#x2F;th&gt;&lt;th&gt;Type of data&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Header&lt;&#x2F;td&gt;&lt;td&gt;size of data (size)&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;unsigned long&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Header&lt;&#x2F;td&gt;&lt;td&gt;size of provided data (real size)&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;unsigned long&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Header&lt;&#x2F;td&gt;&lt;td&gt;pointer to next chunk (next)&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;void *&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Header&lt;&#x2F;td&gt;&lt;td&gt;pointer to previous chunk (prev)&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;void *&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;data&lt;&#x2F;td&gt;&lt;td&gt;provided data (could be any size)&lt;&#x2F;td&gt;&lt;td&gt;N&#x2F;R&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;data&lt;&#x2F;td&gt;&lt;td&gt;tail (could be any size)&lt;&#x2F;td&gt;&lt;td&gt;N&#x2F;R&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The size of the provided data is only here for debug information and &lt;code&gt;show_alloc_mem&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;pros-and-cons&quot;&gt;Pros and cons&lt;&#x2F;h3&gt;
&lt;p&gt;The pros of overhead instead of exported bookkeeping are that it is faster
to retrieve data related to a chunk with a pointer (in &lt;code&gt;free&lt;&#x2F;code&gt; or &lt;code&gt;realloc&lt;&#x2F;code&gt;, for example)
since it only requires subtracting the header size from the pointer to retrieve its header.&lt;&#x2F;p&gt;
&lt;p&gt;It also takes less space since it does not contain pointers to data.&lt;&#x2F;p&gt;
&lt;p&gt;It also requires fewer calls to &lt;code&gt;mmap&lt;&#x2F;code&gt; (which are fairly slow) since it is not allocated elsewhere; data and header are allocated in a single call.&lt;&#x2F;p&gt;
&lt;p&gt;The cons are that a non-cautious user writing out of provided data bounds might ruin
the bookkeeping data and ruin malloc… But well, nobody is supposed to write out of bounds, right?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bins&quot;&gt;Bins&lt;&#x2F;h2&gt;
&lt;p&gt;Similarly to a chunk, bins have a preceding header.
The only differences are that the size corresponds to the full zone size, header included,
that the real size field is unused, and that pointers point to bins instead of chunks.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;malloc&quot;&gt;malloc&lt;&#x2F;h2&gt;
&lt;p&gt;When calling &lt;code&gt;malloc&lt;&#x2F;code&gt;, if the size is not too high for a bin (then it just performs a fitted allocation),
it will iterate in the proper bin (corresponding to the requested size) and find a free chunk with the proper size.
This helps to prevent bin fragmentation.
If it reaches the end of a bin and there is enough space left in the bin, it will attribute a chunk in the bin.
If there is not enough space left in the bin, it will create a new bin.&lt;&#x2F;p&gt;
&lt;p&gt;If several contiguous freed chunks’ total size matches the new chunk’s size,
they will be joined as a single chunk, again, to prevent fragmentation.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;free&quot;&gt;free&lt;&#x2F;h2&gt;
&lt;p&gt;When calling &lt;code&gt;free&lt;&#x2F;code&gt;, if the size is not too high for a bin (then it will just unmap the chunk and sort out next&#x2F;prev pointers),
it will check if the provided chunk is at the end of a bin. If so, the chunk will be
de-attributed, then it will iterate backward to find contiguous chunks to de-attribute them.
This helps to prevent bin fragmentation.&lt;&#x2F;p&gt;
&lt;p&gt;If the bin ends up being empty, it is de-allocated to reduce the program’s memory footprint.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;realloc&quot;&gt;realloc&lt;&#x2F;h2&gt;
&lt;p&gt;When calling &lt;code&gt;realloc&lt;&#x2F;code&gt;, if the size is not too high for a bin (which will be covered in a few lines), it will run a few checks:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If the new size equals the current real size, it will just return the provided pointer.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;If the new size is less than the current real size, it will adjust the chunk’s real size and return the provided pointer.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;If it is greater than the current real size but less than the chunk’s data size,
it will adjust the chunk’s real size and return the provided pointer.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In the two previous cases, if the chunk is at the end of a bin, the chunk will be expanded (if there is enough space left)
or trimmed to fit the new size.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;If the new size is greater than the chunk’s data size (or bin’s left size if it is at the end of a bin),
it will store the chunk’s data in a buffer, free the chunk, call &lt;code&gt;malloc&lt;&#x2F;code&gt; to get a new chunk, and copy the buffer
into the new chunk’s data.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Similarly, if the size is too big for a bin, it will try to expand or trim the chunk with &lt;code&gt;mmap&lt;&#x2F;code&gt;.
If it works, the chunk’s size and real size are changed accordingly and the provided pointer is returned.
If it does not work, it will copy the chunk’s data to the new chunk and return a pointer to the new chunk’s data.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;bonus-part-code-explanation&quot;&gt;Bonus part code explanation&lt;&#x2F;h1&gt;
&lt;p&gt;None of the bonuses really require an explanation, besides eventually de-fragmentation…&lt;&#x2F;p&gt;
&lt;p&gt;Preventing fragmentation is already covered in the mandatory part (to be fair, if one wants to do this bonus, it is recommended to code the mandatory part
with de-fragmentation in mind…).&lt;&#x2F;p&gt;
&lt;p&gt;Making the lib thread-safe is not that hard; the easiest implementation is to lock&#x2F;unlock a mutex before each call of the exposed functions.
It could eventually be optimized, but for little to no gain at my scale.&lt;&#x2F;p&gt;
&lt;p&gt;To make it fancier, I added macros to exclude thread-safe logic at build time when not required.&lt;&#x2F;p&gt;
&lt;p&gt;I also added some debug information on &lt;code&gt;show_alloc_mem&lt;&#x2F;code&gt; when building with a specific flag, it prints a dump of the chunks, along with more details about the sizes of the chunks and bins.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Overview of my infrastructure</title>
          <pubDate>Thu, 18 Jul 2024 00:00:00 +0000</pubDate>
          <author>Millefeuille</author>
          <link>https://blog.millefeuille42.fr/no_blog/overview-of-infra/</link>
          <guid>https://blog.millefeuille42.fr/no_blog/overview-of-infra/</guid>
          <description xml:base="https://blog.millefeuille42.fr/no_blog/overview-of-infra/">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;&#x2F;h2&gt;
&lt;blockquote class=&quot;
	
	
	important
	
	
&quot;&gt;
	&lt;p class=&quot;alert-title&quot;&gt;
		&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;Important&lt;&#x2F;p&gt;
	&lt;p&gt;This blog post presents my current infrastructure. An updated version will be published when major modifications are made. This article will then be categorized as obsolete.&lt;&#x2F;p&gt;

&lt;&#x2F;blockquote&gt;
&lt;p&gt;In this article, I’m going to present the infrastructure I’ve set up to self-host my services. This overview will give you an idea of the hardware and software I use, as well as the reasons that led me to make this choice.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-i-chose-self-hosting&quot;&gt;Why I chose self-hosting&lt;&#x2F;h2&gt;
&lt;p&gt;I chose to host my services for several reasons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Total control&lt;&#x2F;strong&gt;: To be able to configure and adjust every aspect of my infrastructure to my needs without depending on an external provider.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Confidentiality&lt;&#x2F;strong&gt;: My data belongs to me, and self-hosting ensures that there are fewer overly interested parties spying on my data.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Learning&lt;&#x2F;strong&gt;: Setting up and managing this infrastructure has helped me a lot acquiring my current knowledge.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;For fun&lt;&#x2F;strong&gt;: I like experimenting with technology and finding solutions to technical problems. It’s a passion that occupies my free time in a productive way.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;constraints&quot;&gt;Constraints&lt;&#x2F;h2&gt;
&lt;p&gt;I don’t have a lot of money to put into electricity bills, so it was and is important
that the hardware I chose is not too energy-intensive. Additionally,
low-energy equipment generally produces less heat, and I don’t like it when it’s hot in my apartment.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;my-infrastructure&quot;&gt;My infrastructure&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;hardware&quot;&gt;Hardware&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;A Raspberry Pi 4
&lt;ul&gt;
&lt;li&gt;8 GB RAM&lt;&#x2F;li&gt;
&lt;li&gt;16 GB SD card for the system&lt;&#x2F;li&gt;
&lt;li&gt;128 GB USB SSD for the &lt;code&gt;&#x2F;var&lt;&#x2F;code&gt; folder&lt;&#x2F;li&gt;
&lt;li&gt;3 TB HDDs for volume storage&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;!--
- Odroid H4+
    - 32 GB RAM
    - 16 GB eMMC for the system
    - 60 GB SATA SSD for the `&#x2F;var` folder
    - 2 x 3 TB HDDs in RAID 1
- One Raspberry Pi 3B+
    - X GB RAM TODO: see RAM
    - 16 GB SD card for the system
    - 60 GB USB SSD for the `&#x2F;var` folder
    - 3 TB NFS mounted for volume storage
--&gt;
&lt;blockquote class=&quot;
	note
	
	
	
	
&quot;&gt;
	&lt;p class=&quot;alert-title&quot;&gt;
		&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;Note&lt;&#x2F;p&gt;
	&lt;p&gt;I’m using an SSD for the &lt;code&gt;&#x2F;var&lt;&#x2F;code&gt; folder to preserve SD cards and eMMC,
while maintaining sufficient speed.&lt;&#x2F;p&gt;

&lt;&#x2F;blockquote&gt;
&lt;p&gt;Due to budget constraints, which will soon be resolved, I’m only using a Raspberry Pi 4 and have had to
remove certain elements such as monitoring.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;software&quot;&gt;Software&lt;&#x2F;h3&gt;
&lt;p&gt;I only use Docker and a few system tools on my Raspberry Pi. But here’s my “Must-Have” list:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;vim &#x2F; nano&lt;&#x2F;li&gt;
&lt;li&gt;htop &#x2F; btop &#x2F; top&lt;&#x2F;li&gt;
&lt;li&gt;git&lt;&#x2F;li&gt;
&lt;li&gt;curl &#x2F; wget&lt;&#x2F;li&gt;
&lt;li&gt;ping&lt;&#x2F;li&gt;
&lt;li&gt;ip&lt;&#x2F;li&gt;
&lt;li&gt;dig&lt;&#x2F;li&gt;
&lt;li&gt;nslookup&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;!--
- On the Raspberry Pi 4
    - Docker
- On the Odroid
    - OpenMediaVault &lt;TODO: Put link&gt; for storage management
    - Kubernetes node
- On the Raspberry Pi 3B+
    - Kubernetes node
--&gt;
&lt;p&gt;I dockerize many services, the main ones being:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Traefik&lt;&#x2F;strong&gt;: for reverse proxy routing to my services&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Authentik&lt;&#x2F;strong&gt;: for SSO on compatible services and forward proxy protection of services that don’t propose login&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Portainer&lt;&#x2F;strong&gt;: to manage my containers&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Gitea&lt;&#x2F;strong&gt;: for a “home” copy of my repositories. I also use it to store my private repositories for my infrastructure configuration (config files, compose files, etc.).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;As well as all the other services that aren’t useful for infrastructure management, which I’ll detail in other articles.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;security&quot;&gt;Security&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;access&quot;&gt;Access&lt;&#x2F;h3&gt;
&lt;p&gt;I only expose the ports required for the services I want to make accessible, i.e., 80 and 443 for HTTP and HTTPS.
If a service requires me to expose another port (such as my IRC server), I expose it if I want to access it from the outside.&lt;&#x2F;p&gt;
&lt;p&gt;SSH access to the machine is via the local network or my VPN.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ssl&quot;&gt;SSL&lt;&#x2F;h3&gt;
&lt;p&gt;To have HTTPS on my services, I use a wildcard certificate as well as a standard certificate (&lt;code&gt;*.millefeuille42.fr&lt;&#x2F;code&gt; and &lt;code&gt;millefeuille42.fr&lt;&#x2F;code&gt;).
My registrar (OVH) lets me obtain and renew them via their API, all free of charge.&lt;&#x2F;p&gt;
&lt;p&gt;If I can’t (Namecheap and Scaleway), I use the Let’s Encrypt resolver from Traefik
to obtain and renew them automatically and dynamically.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;forward-proxy&quot;&gt;Forward Proxy&lt;&#x2F;h3&gt;
&lt;p&gt;To protect my services that aren’t intended to be public (and&#x2F;or that don’t provide authentication on their own, like Homer),
I use Authentik’s Forward Proxy mode with Traefik. This allows me to manage access to these services from a single location.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;deployment-of-key-services&quot;&gt;Deployment of key services&lt;&#x2F;h2&gt;
&lt;p&gt;To lighten this article, I’ll detail the deployment of key tools (Traefik, Authentik, Portainer, Gitea)
in another article. I’ll put the link here when it’s done.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;managing-other-infrastructures&quot;&gt;Managing other infrastructures&lt;&#x2F;h2&gt;
&lt;p&gt;The services presented here permit me to manage other servers, particularly for close friends and family. For Portainer, all I need to do is to add an agent to the server, and Authentik provides branding according to the domain used for access. All that is left to do is to install and configure Traefik on the desired entry point. Authentik’s user management lets me isolate users of the different servers.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;Self-hosting is a rewarding adventure that permits you to regain control over your data and acquire many technical skills.
Although it takes time and effort, the benefits in terms of privacy, control, and personal satisfaction are well worth it.
If you’re passionate about technology and like a challenge, I strongly encourage you to give self-hosting a try. You won’t regret it.&lt;&#x2F;p&gt;
</description>
      </item>
    </channel>
</rss>
