<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>/home/andrzejgor.ski</title>
    <link>https://andrzejgor.ski/</link>
    <description>Recent content on /home/andrzejgor.ski</description>
    <image>
      <title>/home/andrzejgor.ski</title>
      <url>https://andrzejgor.ski/metaimage.png</url>
      <link>https://andrzejgor.ski/metaimage.png</link>
    </image>
    <generator>Hugo -- 0.145.0</generator>
    <language>en-us</language>
    <lastBuildDate>Sat, 22 Feb 2025 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://andrzejgor.ski/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>How I (almost) rescued data from a failed ZFS pool</title>
      <link>https://andrzejgor.ski/posts/zfs_rescue_when_disk_failed/</link>
      <pubDate>Sat, 22 Feb 2025 00:00:00 +0000</pubDate>
      <guid>https://andrzejgor.ski/posts/zfs_rescue_when_disk_failed/</guid>
      <description>How I&amp;rsquo;ve managed to rescue some data from a failed ZFS pool, when one of the disks died and the metadata was corrupted.</description>
      <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Recently HDD storage pool in my Homelab started to work &ldquo;funny&rdquo;. Funkiness appeared as pool clogging when heavy traffic happened on it. I&rsquo;ve lived with that, restarting machine occasionally for a month or so, being busy with the life.</p>
<p>But one day I thought that maybe it&rsquo;s time to make a backup of the data on that pool. Oh boy, was I wrong and right at the same time. Making backups was definitely right thing to do, but not at that specific time.</p>
<p>After I&rsquo;ve started the backup process, the pool started to clog again. &ldquo;No problem, another reboot and we&rsquo;re alive&rdquo; I&rsquo;ve thought. And this time it was different - the pool didn&rsquo;t import anymore.</p>
<p>All disks in pool had clear SMART status before the malfunction. So I&rsquo;ve started to look into the zfs status output. And it was not good:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># zpool import hdd</span>
</span></span><span class="line"><span class="cl">              cannot import <span class="s1">&#39;hdd&#39;</span>: I/O error
</span></span><span class="line"><span class="cl">        Destroy and re-create the pool from
</span></span><span class="line"><span class="cl">        a backup source.
</span></span></code></pre></td></tr></table>
</div>
</div><p>One of the disks in the pool was dead. And it was dead that much, that it interfered with controller&rsquo;s operation. Moreover, last reboot not only didn&rsquo;t help, but caused the pool to be exported forcefully (I guess) and it broke the metadata.</p>
<h2 id="troubleshooting">Troubleshooting</h2>
<p>First, I&rsquo;ve tried to import the pool with <code>-f</code> flag, then <code>-f -F</code>, but it didn&rsquo;t help either. The pool was definitely dead.</p>
<p>Normally, I would just replace the broken disk with a new one, replace it in zfs and do a resilver. But the problem was that the metadata was corrupted. And pool with corrupted metadata can&rsquo;t be imported. And without importing the pool, I can&rsquo;t replace the disk.</p>
<p>After some Googling and more or less proper troubleshooting, I&rsquo;ve found a way to import the pool with broken metadata. These commands did the trick:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="m">1</span> &gt; /sys/module/zfs/parameters/zfs_max_missing_tvds
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="m">0</span> &gt; /sys/module/zfs/parameters/spa_load_verify_metadata
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="m">0</span> &gt; /sys/module/zfs/parameters/spa_load_verify_data
</span></span></code></pre></td></tr></table>
</div>
</div><p>First one makes it possible to import the pool with missing disk. The other two disable metadata and data verification. <strong>It&rsquo;s not recommended to use these commands in production</strong>. But in my case, I didn&rsquo;t have anything to lose.</p>
<p>After that, wiser with the knowledge gathered during Googling, I&rsquo;ve imported the pool in read-only mode:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">zpool import -f -o <span class="nv">readonly</span><span class="o">=</span>on hdd
</span></span></code></pre></td></tr></table>
</div>
</div><p>And it worked! The pool was imported in read-only mode. I&rsquo;ve checked the data and it was there. Mostly. Some files were corrupted, but most of them were intact.</p>
<h2 id="data-recovery">Data recovery</h2>
<p>After the pool was imported, I&rsquo;ve tried to copy the data to another disk. First with simple <code>cp</code>, then with <code>rsync</code>. But they all hanged on broken files.</p>
<p>Another research later I&rsquo;ve found a tool called <code>cpio</code>. It&rsquo;s a tool that can copy files to and from archives. But with some flags, and some pipes magic, I&rsquo;ve managed to use it to copy file-by-file.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">find /hdd -depth -print0 <span class="p">|</span> cpio -pdmv0 /target
</span></span></code></pre></td></tr></table>
</div>
</div><p>This command copies all files from <code>/hdd</code> to <code>/target</code>.</p>
<p>After a few hours, the data was copied. I&rsquo;ve managed to rescue about 70% of files intact. The rest was corrupted. But it was better than nothing :)</p>
<h2 id="conclusion">Conclusion</h2>
<h3 id="backups-people">Backups, people!</h3>
<p>Shame one me, cause it was second time when I lost some data because of broken disk. And it was second time when I didn&rsquo;t have a proper, automated backup. I&rsquo;ve learned my lesson and I&rsquo;ve started to make backups of my data. And I recommend you to do the same.</p>
<p>The good thing is, I&rsquo;ve had some old (manual) backup of the very same pool, so I&rsquo;ve managed to backfill some of the lost files from it.</p>
<h3 id="raid-or-zraid-is-not-a-backup">RAID (or ZRAID) is not a backup</h3>
<p>I knew that, but I didn&rsquo;t act on that knowledge. RAID is not a backup. It&rsquo;s a redundancy. It&rsquo;s good to have it, but it&rsquo;s not enough. You should have a backup of your data.</p>
<h3 id="smart-monitoring-wont-help-you-sometimes">SMART monitoring won&rsquo;t help you sometimes</h3>
<p>All disks in the pool had clear SMART status before the malfunction. It just happens that disk can die without any SMART errors. So don&rsquo;t rely only on SMART monitoring.</p>
<p>But it&rsquo;s still better to have them monitored than not. I&rsquo;ve had few disks that started showing SMART errors, and I wast fast enough to replace them before they died.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Backblaze B2 as a Terraform remote state storage</title>
      <link>https://andrzejgor.ski/posts/backblaze_b2_tf_state/</link>
      <pubDate>Fri, 21 Feb 2025 00:00:00 +0000</pubDate>
      <guid>https://andrzejgor.ski/posts/backblaze_b2_tf_state/</guid>
      <description>It seems that Backblaze B2 is enough S3-compatible to be used as a remote state storage for Terraform!</description>
      <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Recently I&rsquo;ve started The Big Migration™ from Ansible to Terraform in my homelab. But as soon as I started to write my first Terraform manifests, I&rsquo;ve started to think about remote state storage too.</p>
<p>First, I thought about storing the state file on selfhosted Minio instance. But the problem is, that Minio instance will be managed by the very same Terraform manifests.</p>
<p>Later on, I started researching what else can I manage with Terraform. I&rsquo;ve found out that Backblaze B2, where I keep backups anyway, is manageable with Terraform! That was the moment when it clicked in my head - all in all, B2 is S3 compatible, so I can use it as a remote state storage for Terraform!</p>
<h2 id="prerequisites">Prerequisites</h2>
<ol>
<li>Backblaze B2 account, with:
<ol>
<li>Bucket in which you want to store the state file</li>
<li>Application key with permissions to manage this bucket</li>
</ol>
</li>
<li>Terraform installed on your machine</li>
</ol>
<h2 id="terraform-configuration">Terraform configuration</h2>
<p>The basic config for storing the state file in S3 would look like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">terraform</span> { 
</span></span><span class="line"><span class="cl">  <span class="k">backend</span> <span class="s2">&#34;s3&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">    bucket</span>         <span class="o">=</span> <span class="s2">&#34;my-terraform-state-bucket&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">    key</span>            <span class="o">=</span> <span class="s2">&#34;terraform.tfstate&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">    region</span>         <span class="o">=</span> <span class="s2">&#34;us-east-1&#34;</span>
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></td></tr></table>
</div>
</div><p>One might think that it&rsquo;s enough to change the <code>region</code> to B2 substitute, add Backblaze <code>endpoint</code> and we&rsquo;re good to go. But it&rsquo;s not that simple - B2 is <strong>almost</strong> S3 compatible, so we have to do some extra steps.</p>
<p>What we need to do is to skip some checks and validations that Backblaze B2 doesn&rsquo;t support. And there&rsquo;s actually quite a lot of them.</p>
<p>A fully working example of Terraform configuration for storing the state file in Backblaze B2 would look like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">terraform</span> {
</span></span><span class="line"><span class="cl">  <span class="k">backend</span> <span class="s2">&#34;s3&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">    bucket</span>    <span class="o">=</span> <span class="s2">&#34;my-terraform-state-bucket&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">    key</span>       <span class="o">=</span> <span class="s2">&#34;terraform.tfstate&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">    region</span>    <span class="o">=</span> <span class="s2">&#34;us-west-004&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">    endpoints</span> <span class="o">=</span> {
</span></span><span class="line"><span class="cl"><span class="n">      s3</span> <span class="o">=</span> <span class="s2">&#34;https://s3.us-west-004.backblazeb2.com&#34;</span>
</span></span><span class="line"><span class="cl">    }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">    skip_credentials_validation</span> <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="cl"><span class="n">    skip_region_validation</span>      <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="cl"><span class="n">    skip_metadata_api_check</span>     <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="cl"><span class="n">    skip_requesting_account_id</span>  <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="cl"><span class="n">    skip_s3_checksum</span>            <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></td></tr></table>
</div>
</div><p>Of course, you&rsquo;ll have to replace <code>bucket</code>, <code>region</code>, and <code>endpoints.s3</code> values with proper ones according to your Backblaze B2 config.</p>
<p>As you can see, there&rsquo;s no <code>access_key</code> and <code>secret_key</code> provided. That&rsquo;s because I provide them through environment variables (and you should too!). B2&rsquo;s application key goes to <code>AWS_SECRET_ACCESS_KEY</code> and key ID goes to <code>AWS_ACCESS_KEY_ID</code> env var.</p>
<h2 id="some-security-considerations">Some security considerations</h2>
<h3 id="state-bucket">State bucket</h3>
<p>Keep it private. It&rsquo;s not a good idea to make your state file publicly available, as it might contain secrets.</p>
<p>You might also want to enable versioning on this bucket. With versioning you can easily revert to the previous state if something goes wrong. I&rsquo;ve seen Terraform go bananas a few times, so it&rsquo;s a good idea to have it enabled.</p>
<h3 id="application-key">Application key</h3>
<p>Don&rsquo;t use your master key for this. Create a separate application key with permissions to manage only this bucket. It&rsquo;s a good practice to have separate keys for different tasks.</p>
<p>Don&rsquo;t put your credentials in the Terraform code (or any code, really). Especially if you&rsquo;re gonna ever put that code publicly eg. on GitHub. One &ldquo;oops&rdquo; too far and your keys leaked. Use environment variables to provide them to Terraform. I personally load them into env vars from 1password with <code>op</code> cli tool.</p>
<h2 id="summary">Summary</h2>
<p>It seems that Backblaze B2 is enough S3-compatible to be used as a remote state storage for Terraform. It&rsquo;s good to keep your state file in some remote storage, as it&rsquo;s a good practice to have it versioned and not stored on your local machine. And if you already use B2 for backups, why not use it for Terraform state file as well?</p>
]]></content:encoded>
    </item>
    <item>
      <title>Compose Key</title>
      <link>https://andrzejgor.ski/posts/compose_key/</link>
      <pubDate>Tue, 13 Jun 2023 00:00:00 +0000</pubDate>
      <guid>https://andrzejgor.ski/posts/compose_key/</guid>
      <description>Compose Key is a special key on your keyboard that allows you to type special characters without mangling your keyboard settings all the time.</description>
      <content:encoded><![CDATA[<h2 id="what-is-a-compose-key">What is a Compose Key?</h2>
<p>Compose Key is a special key on your keyboard that allows you to type special characters like <code>→</code>, <code>°</code>, <code>€</code>, <code>ä</code>, <code>™</code>, <code>®</code>, <code>¿</code> and many more. It&rsquo;s very useful if you&rsquo;re writing in any foreign language that uses special characters and don&rsquo;t want to mangle your keyboard settings all the time.</p>
<h2 id="examples">Examples</h2>
<ul>
<li><kbd>Compose</kbd> + <kbd>-</kbd> + <kbd>&gt;</kbd> will produce a <code>→</code> character</li>
<li><kbd>Compose</kbd> + <kbd>o</kbd> + <kbd>o</kbd> will produce a <code>°</code> character</li>
<li><kbd>Compose</kbd> + <kbd>c</kbd> + <kbd>=</kbd> will produce a <code>€</code> character</li>
<li><kbd>Compose</kbd> + <kbd>a</kbd> + <kbd>&quot;</kbd> will produce a <code>ä</code> character</li>
<li><kbd>Compose</kbd> + <kbd>t</kbd> + <kbd>m</kbd> will produce a <code>™</code> character</li>
<li><kbd>Compose</kbd> + <kbd>r</kbd> + <kbd>o</kbd> will produce a <code>®</code> character</li>
<li><kbd>Compose</kbd> + <kbd>?</kbd> + <kbd>?</kbd> will produce a <code>¿</code> character</li>
</ul>
<p>As you can see in the examples above, the combinations are very thought out and easy to remember. Most of the time you don&rsquo;t need to know them by heart. You just have to think what two (or more) keys from the standard key set combined will give you the character that you want.</p>
<h2 id="how-to-enable-compose-key">How to enable Compose Key?</h2>
<p>As I use Gnome 3, I&rsquo;ll describe how to set it up in this environment. But don&rsquo;t worry, it can be enabled in pretty much every WM/DE.</p>
<p>So, at first you have to go to the <code>Settings</code> and then <code>Keyboard</code>:</p>
<p><img alt="Keyboard Settings screenshot" loading="lazy" src="/posts/compose_key/gnome_keyboard_settings.png"></p>
<p>Then you have to click the <code>Compose Key</code> option in the <code>Special Character Entry</code> section. There you can turn it on and choose which key you want to use as a Compose Key.</p>
<p>As you can see on the screenshot, I use the <kbd>Caps Lock</kbd> key, but choose whatever you like.</p>
<h2 id="lists-of-possible-combinations">Lists of possible combinations</h2>
<p>These two lists are the best that I found in the internet:</p>
<ol>
<li><a href="https://help.ubuntu.com/community/GtkComposeTable">GTKComposeTable</a> - rather short, very readable list of most useful combinations.</li>
<li><a href="https://cgit.freedesktop.org/xorg/lib/libX11/plain/nls/en_US.UTF-8/Compose.pre">libX11 documentation</a> - very long, hard to read but very complete list of all possible combinations.</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>My 1st post</title>
      <link>https://andrzejgor.ski/posts/hello_world/</link>
      <pubDate>Tue, 30 Aug 2022 00:00:00 +0000</pubDate>
      <guid>https://andrzejgor.ski/posts/hello_world/</guid>
      <description>I’m starting my personal blog today!</description>
      <content:encoded><![CDATA[<p>Hi guys &amp; gals!</p>
<p>I’m Andrzej, I live in Poland, Warsaw and at the time of writing these words, I’m 29 years old. I have 7 years of professional experience as a Python developer and 2 years as DevOps and a short episode as a team leader. In my free time, I’m maintaining a small homelab and I do various more or less strange things with my cars.</p>
<p><strong>I’m starting my personal blog today.</strong></p>
<p>What I’ll write about? Mostly about the things that I mentioned above, as I know something about them…</p>
<p>…and maybe even some other topics, you never know what you’ll learn by accident 🙂</p>
]]></content:encoded>
    </item>
    <item>
      <title>Projects</title>
      <link>https://andrzejgor.ski/projects/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://andrzejgor.ski/projects/</guid>
      <description>Projects I&amp;rsquo;ve worked on.</description>
      <content:encoded><![CDATA[<hr>
<h2 id="homelab">Homelab</h2>
<p>I&rsquo;m working constantly on my homelab setup. The goal is to have everything as code, so I can easily recreate my homelab from scratch if needed. There&rsquo;s a lot of stuff in there, from Ansible playbooks, through Kubernetes manifests, to Terraform configurations.</p>
<ul>
<li>GitHub repository: <a href="https://github.com/andrzej3393/homelab-v2/">andrzej3393/homelab-v2</a></li>
<li>Documentation: <a href="https://homelab.andrzejgor.ski/">homelab.andrzejgor.ski</a></li>
</ul>
<hr>
]]></content:encoded>
    </item>
  </channel>
</rss>
