<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet type="text/xsl" href="https://perrotta.dev/rss.xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Alpine-Linux on ¬ just serendipity 🍀</title>
    <link>https://perrotta.dev/</link>
    <description>Recent content in Alpine-Linux on ¬ just serendipity 🍀</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <managingEditor>serendipity@perrotta.dev (Thiago Perrotta)</managingEditor>
    <webMaster>serendipity@perrotta.dev (Thiago Perrotta)</webMaster>
    <copyright>© 2013 - 2026 Thiago Perrotta ·
  a fork of [hugo ʕ•ᴥ•ʔ bear](https://github.com/janraasch/hugo-bearblog/)
</copyright>
    <lastBuildDate>Thu, 26 Mar 2026 18:46:44 +0100</lastBuildDate>
    <atom:link href="https://perrotta.dev/tags/alpine-linux/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Alpine Linux: update timezone
      </title>
      <link>https://perrotta.dev/2026/02/alpine-linux-update-timezone/</link>
      <pubDate>Fri, 20 Feb 2026 12:32:19 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <category>selfhosted</category>
      <guid>https://perrotta.dev/2026/02/alpine-linux-update-timezone/</guid>
      <description>&lt;p&gt;♠ Once upon a time, my alpine linux VPS was located in Toronto (Canada). For a&#xA;while now it&amp;rsquo;s been in Frankfurt (Germany).&lt;/p&gt;&#xA;&lt;p&gt;Linode makes changing the location of your VPS a breeze.&#xA;Recently though I realized I forgot to update the system timezone.&lt;/p&gt;&#xA;&lt;p&gt;In the &lt;code&gt;systemd&lt;/code&gt; world, we would do this:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;timedatectl set-timezone Europe/Berlin&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;And verify it with:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;timedatectl show --property=Timezone --value&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I appreciate the simplicity of &lt;code&gt;timedatectl&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;However, this is Alpine Linux, wherein we don&amp;rsquo;t say the &lt;a href=&#34;https://nosystemd.org/&#34;&gt;&lt;code&gt;s*&lt;/code&gt;&#xA;word&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;First, identify your desired timezone:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;ls /usr/share/zoneinfo&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;We&amp;rsquo;ll go with &lt;code&gt;Europe/Berlin&lt;/code&gt;. Symlink &lt;code&gt;/etc/localtime&lt;/code&gt; to it:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;doas ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;In addition, update &lt;code&gt;/etc/timezone&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;echo &amp;#39;Europe/Berlin&amp;#39; | doas tee /etc/timezone&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;That&amp;rsquo;s all.&lt;/p&gt;&#xA;&lt;p&gt;For completeness you may want to &lt;code&gt;reboot&lt;/code&gt; afterwards, but it&amp;rsquo;s not strictly&#xA;necessary.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine Linux: update timezone&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/selfhosted/&#34;&gt;#selfhosted&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ New APKBUILD: LLM edition
      </title>
      <link>https://perrotta.dev/2026/02/new-apkbuild-llm-edition/</link>
      <pubDate>Wed, 18 Feb 2026 11:23:05 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>ai</category>
      <category>alpine-linux</category>
      <category>bestof</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2026/02/new-apkbuild-llm-edition/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Problem statement&lt;/strong&gt;: package &lt;a href=&#34;http://prek.j178.dev/&#34;&gt;&lt;code&gt;prek&lt;/code&gt;&lt;/a&gt; for &lt;a href=&#34;https://alpinelinux.org/&#34;&gt;Alpine&#xA;Linux&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;prek&lt;/code&gt; is a modern replacement for &lt;a href=&#34;https://pre-commit.com/&#34;&gt;&lt;code&gt;pre-commit&lt;/code&gt;&lt;/a&gt;,&#xA;covered (&lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;many times&lt;/a&gt;) in this blog&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2026/02/new-apkbuild-llm-edition/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/&#34;&gt;Previously&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s already a breeze to create &lt;code&gt;APKBUILD&lt;/code&gt;s. I&amp;rsquo;ve been doing so for ages.&lt;/p&gt;&#xA;&lt;p&gt;Now, in 2026, with agentic LLMs, it&amp;rsquo;s even more so!&lt;/p&gt;&#xA;&lt;p&gt;I can be more useful to the Alpine Linux community by increasing my efficiency&#xA;as a package maintainer.&lt;/p&gt;&#xA;&lt;p&gt;Yesterday I used &lt;a href=&#34;https://ampcode.com&#34;&gt;Amp Free&lt;/a&gt; in &lt;em&gt;smart&lt;/em&gt; mode (Opus 4.6) to&#xA;scaffold the &lt;code&gt;APKBUILD&lt;/code&gt; for &lt;code&gt;prek&lt;/code&gt;, using the &lt;a href=&#34;https://aur.archlinux.org/packages/prek&#34;&gt;AUR&#xA;&lt;code&gt;PKGBUILD&lt;/code&gt;&lt;/a&gt; as a baseline. Prompt:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;write an APKBUILD for prek, inspired by https://aur.archlinux.org/packages/prek&#xA;&#xA;once written, copy it to knol (ssh knol)&#xA;aports dir: ~/workspace/aports (in knol)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The result:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# Maintainer: Thiago Perrotta &amp;lt;{redacted}&amp;gt;&#xA;pkgname=pre&#xA;pkgver=0.3.3&#xA;pkgrel=0&#xA;pkgdesc=&amp;#34;Better pre-commit, re-engineered in Rust&amp;#34;&#xA;url=&amp;#34;https://github.com/j178/prek&amp;#34;&#xA;arch=&amp;#34;all&amp;#34;&#xA;license=&amp;#34;MIT&amp;#34;&#xA;makedepends=&amp;#34;cargo cargo-auditable&amp;#34;&#xA;subpackages=&amp;#34;&#xA;&#x9;$pkgname-bash-completion&#xA;&#x9;$pkgname-fish-completion&#xA;&#x9;$pkgname-zsh-completion&#xA;&#x9;$pkgname-doc&#xA;&#x9;&amp;#34;&#xA;source=&amp;#34;$pkgname-$pkgver.tar.gz::https://github.com/j178/prek/archive/v$pkgver.tar.gz&amp;#34;&#xA;options=&amp;#34;net&amp;#34; # fetch dependencies&#xA;&#xA;prepare() {&#xA;&#x9;default_prepare&#xA;&#x9;cargo fetch --target=&amp;#34;$CTARGET&amp;#34; --locked&#xA;}&#xA;&#xA;build() {&#xA;&#x9;cargo auditable build --release --frozen&#xA;&#xA;&#x9;target/release/prek util generate-shell-completion bash &amp;gt; $pkgname.bash&#xA;&#x9;target/release/prek util generate-shell-completion fish &amp;gt; $pkgname.fish&#xA;&#x9;target/release/prek util generate-shell-completion zsh &amp;gt; $pkgname.zsh&#xA;}&#xA;&#xA;check() {&#xA;&#x9;cargo test --frozen&#xA;&#x9;cargo test --frozen -- --skip cache_gc_keeps_local_hook_env --skip cache_gc_removes_unreferenced_entries&#xA;}&#xA;&#xA;package() {&#xA;&#x9;install -Dm755 target/release/$pkgname -t &amp;#34;$pkgdir&amp;#34;/usr/bin/&#xA;&#xA;&#x9;install -Dm644 $pkgname.bash &amp;#34;$pkgdir&amp;#34;/usr/share/bash-completion/completions/$pkgname&#xA;&#x9;install -Dm644 $pkgname.fish &amp;#34;$pkgdir&amp;#34;/usr/share/fish/vendor_completions.d/$pkgname.fish&#xA;&#x9;install -Dm644 $pkgname.zsh &amp;#34;$pkgdir&amp;#34;/usr/share/zsh/site-functions/_$pkgname&#xA;&#xA;&#x9;install -Dm644 LICENSE -t &amp;#34;$pkgdir&amp;#34;/usr/share/licenses/$pkgname/&#xA;&#x9;install -Dm644 README.md -t &amp;#34;$pkgdir&amp;#34;/usr/share/doc/$pkgname/&#xA;}&#xA;&#xA;sha512sums=&amp;#34;&#xA;87f5b5905d0c95b7f55a789eb00b463bb987c5fc981d83500f34a41b8e9ea2e4a0ac9d45a0c3a029a7957de33f5ade866c396c399252eb4a7a9e09e32e270b99  prek-0.3.3.tar.gz&#xA;&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This was a great initial attempt, but&#xA;&lt;a href=&#34;https://gitlab.alpinelinux.org/thiagowfx/aports/-/jobs/2223891&#34;&gt;tests&lt;/a&gt; failed:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;Stopped on the first failure. Run `cargo insta test` to run all snapshots.&#xA;thread &amp;#39;cache_gc_removes_unreferenced_entries&amp;#39; (15179) panicked at /builds/thiagowfx/aports/testing/prek/tmp/cargo/registry/src/index.crates.io-1949cf8c6b5b557f/insta-1.46.3/src/runtime.rs:719:13:&#xA;snapshot assertion for &amp;#39;cache_gc_removes_unreferenced_entries-2&amp;#39; failed in line 234&#xA;failures:&#xA;    cache_gc_keeps_local_hook_env&#xA;    cache_gc_removes_unreferenced_entries&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;A follow-up edit yielded this diff:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-diff&#34;&gt;@@ -30,7 &amp;#43;30,7 @@ build() {&#xA;}&#xA;&#xA;check() {&#xA;-&#x9;cargo test --frozen&#xA;&amp;#43;&#x9;cargo test --frozen -- --skip cache_gc_keeps_local_hook_env --skip cache_gc_removes_unreferenced_entries&#xA;}&#xA;&#xA;package() {&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&amp;hellip;but that caused more failures. Then I got interrupted and the night ended.&lt;/p&gt;&#xA;&lt;p&gt;Today, I continued with &lt;a href=&#34;https://claude.com/product/claude-code&#34;&gt;Claude Code&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Original prompt to &lt;code&gt;claudey&lt;/code&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2026/02/new-apkbuild-llm-edition/#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; using Opus 4.6:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;we&amp;rsquo;re packaging prek for alpine linux (APKBUILD)&lt;/p&gt;&#xA;&lt;p&gt;we&amp;rsquo;re using &lt;a href=&#34;https://aur.archlinux.org/packages/prek&#34;&gt;https://aur.archlinux.org/packages/prek&lt;/a&gt; as reference&lt;/p&gt;&#xA;&lt;p&gt;we have access to an alpine system in knol (&lt;code&gt;ssh knol&lt;/code&gt;)&lt;/p&gt;&#xA;&lt;p&gt;the aports tree lives in ~/workspace/aports in knol&lt;/p&gt;&#xA;&lt;p&gt;we&amp;rsquo;re already in the right branch, the prek APKBUILD lives in testing/prek within knol&lt;/p&gt;&#xA;&lt;p&gt;the problem is that the test is failing in our MR pipeline: &lt;a href=&#34;https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/97666&#34;&gt;https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/97666&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;sample job: &lt;a href=&#34;https://gitlab.alpinelinux.org/thiagowfx/aports/-/jobs/2223963&#34;&gt;https://gitlab.alpinelinux.org/thiagowfx/aports/-/jobs/2223963&lt;/a&gt; (raw log: &lt;a href=&#34;https://gitlab.alpinelinux.org/thiagowfx/aports/-/jobs/2223963/raw&#34;&gt;https://gitlab.alpinelinux.org/thiagowfx/aports/-/jobs/2223963/raw&lt;/a&gt;)&lt;/p&gt;&#xA;&lt;p&gt;fix the APKBUILD!&lt;/p&gt;&#xA;&lt;p&gt;I think it&amp;rsquo;s a missing checkdep&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;A couple of burned tokens later:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;APKBUILD updated. Here&amp;rsquo;s what changed:&lt;/p&gt;&#xA;&lt;p&gt;Added checkdepends=&amp;ldquo;bash python3&amp;rdquo;:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;python3 — prek&amp;rsquo;s pygrep language handler uses uv to find a Python&#xA;interpreter. Without system Python, uv python find fails, causing all 11&#xA;pygrep tests to fail. Also needed for the windows_script_run test which uses&#xA;a #!/usr/bin/env python3 shebang.&lt;/li&gt;&#xA;&lt;li&gt;bash — the script tests use #!/usr/bin/env bash shebangs, and Alpine doesn&amp;rsquo;t&#xA;ship bash by default (only busybox ash).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Added &amp;ndash;skip for language integration tests needing unavailable runtimes:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;docker — needs Docker daemon (also covers docker_image via substring match)&lt;/li&gt;&#xA;&lt;li&gt;julia — Julia not available in Alpine&lt;/li&gt;&#xA;&lt;li&gt;lua:: — Lua interpreter not available&lt;/li&gt;&#xA;&lt;li&gt;haskell — GHC/cabal not available&lt;/li&gt;&#xA;&lt;li&gt;node:: — Node.js/npm not available&lt;/li&gt;&#xA;&lt;li&gt;ruby:: — Ruby not available&lt;/li&gt;&#xA;&lt;li&gt;rust:: — Rust toolchain tests (rustup) not available&lt;/li&gt;&#xA;&lt;li&gt;swift:: — Swift not available&lt;/li&gt;&#xA;&lt;li&gt;bun:: — Bun not available&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The unit tests (282) plus other integration suites (auto_update,&#xA;builtin_hooks, cache, hook_impl, identify, install) still run fully. You can&#xA;commit and push to re-trigger the pipeline.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;That&amp;rsquo;s spot on. Here&amp;rsquo;s the diff:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-diff&#34;&gt;🧙 thiago@knol ~/workspace/aports/testing/prek git:prek&#xA;❯ git --no-pager show&#xA;commit 8f702c5f1ee1b6d153a4f957c309f2badd452d31 (HEAD -&amp;gt; prek)&#xA;Author: Thiago Perrotta &amp;lt;{redacted}&amp;gt;&#xA;Date:   Wed Feb 18 05:30:14 2026 -0500&#xA;&#xA;    skip language integration tests&#xA;&#xA;diff --git testing/prek/APKBUILD testing/prek/APKBUILD&#xA;index 5e134457bc..d6080fa977 100644&#xA;--- testing/prek/APKBUILD&#xA;&amp;#43;&amp;#43;&amp;#43; testing/prek/APKBUILD&#xA;@@ -7,6 &amp;#43;7,7 @@ url=&amp;#34;https://github.com/j178/prek&amp;#34;&#xA; arch=&amp;#34;all&amp;#34;&#xA; license=&amp;#34;MIT&amp;#34;&#xA; makedepends=&amp;#34;cargo cargo-auditable&amp;#34;&#xA;&amp;#43;checkdepends=&amp;#34;bash python3&amp;#34;&#xA; subpackages=&amp;#34;&#xA; &#x9;$pkgname-bash-completion&#xA; &#x9;$pkgname-fish-completion&#xA;@@ -30,7 &amp;#43;31,18 @@ build() {&#xA; }&#xA;&#xA; check() {&#xA;-&#x9;cargo test --frozen -- --skip cache_gc_keeps_local_hook_env --skip cache_gc_removes_unreferenced_entries&#xA;&amp;#43;&#x9;cargo test --frozen -- \&#xA;&amp;#43;&#x9;&#x9;--skip cache_gc_keeps_local_hook_env \&#xA;&amp;#43;&#x9;&#x9;--skip cache_gc_removes_unreferenced_entries \&#xA;&amp;#43;&#x9;&#x9;--skip &amp;#34;docker&amp;#34; \&#xA;&amp;#43;&#x9;&#x9;--skip &amp;#34;julia&amp;#34; \&#xA;&amp;#43;&#x9;&#x9;--skip &amp;#34;lua::&amp;#34; \&#xA;&amp;#43;&#x9;&#x9;--skip &amp;#34;haskell&amp;#34; \&#xA;&amp;#43;&#x9;&#x9;--skip &amp;#34;node::&amp;#34; \&#xA;&amp;#43;&#x9;&#x9;--skip &amp;#34;ruby::&amp;#34; \&#xA;&amp;#43;&#x9;&#x9;--skip &amp;#34;rust::&amp;#34; \&#xA;&amp;#43;&#x9;&#x9;--skip &amp;#34;swift::&amp;#34; \&#xA;&amp;#43;&#x9;&#x9;--skip &amp;#34;bun::&amp;#34;&#xA; }&#xA;&#xA; package() {&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I had to continue with a few more follow-ups though. Eventually, that led to&#xA;Merge Request (MR)&#xA;&lt;a href=&#34;https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/97666&#34;&gt;!97666&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# Maintainer: Thiago Perrotta &amp;lt;{redacted}&amp;gt;&#xA;pkgname=prek&#xA;pkgver=0.3.3&#xA;pkgrel=0&#xA;pkgdesc=&amp;#34;Better pre-commit, re-engineered in Rust&amp;#34;&#xA;url=&amp;#34;https://github.com/j178/prek&amp;#34;&#xA;arch=&amp;#34;all&amp;#34;&#xA;license=&amp;#34;MIT&amp;#34;&#xA;makedepends=&amp;#34;cargo cargo-auditable&amp;#34;&#xA;subpackages=&amp;#34;&#xA;&#x9;$pkgname-bash-completion&#xA;&#x9;$pkgname-fish-completion&#xA;&#x9;$pkgname-zsh-completion&#xA;&#x9;$pkgname-doc&#xA;&#x9;&amp;#34;&#xA;source=&amp;#34;$pkgname-$pkgver.tar.gz::https://github.com/j178/prek/archive/v$pkgver.tar.gz&amp;#34;&#xA;options=&amp;#34;net&amp;#34; # fetch dependencies&#xA;&#xA;prepare() {&#xA;&#x9;default_prepare&#xA;&#x9;cargo fetch --target=&amp;#34;$CTARGET&amp;#34; --locked&#xA;}&#xA;&#xA;build() {&#xA;&#x9;cargo auditable build --release --frozen&#xA;&#xA;&#x9;target/release/prek util generate-shell-completion bash &amp;gt; $pkgname.bash&#xA;&#x9;target/release/prek util generate-shell-completion fish &amp;gt; $pkgname.fish&#xA;&#x9;target/release/prek util generate-shell-completion zsh &amp;gt; $pkgname.zsh&#xA;}&#xA;&#xA;check() {&#xA;&#x9;cargo test --frozen --lib&#xA;}&#xA;&#xA;package() {&#xA;&#x9;install -Dm755 target/release/$pkgname -t &amp;#34;$pkgdir&amp;#34;/usr/bin/&#xA;&#xA;&#x9;install -Dm644 $pkgname.bash &amp;#34;$pkgdir&amp;#34;/usr/share/bash-completion/completions/$pkgname&#xA;&#x9;install -Dm644 $pkgname.fish &amp;#34;$pkgdir&amp;#34;/usr/share/fish/vendor_completions.d/$pkgname.fish&#xA;&#x9;install -Dm644 $pkgname.zsh &amp;#34;$pkgdir&amp;#34;/usr/share/zsh/site-functions/_$pkgname&#xA;&#xA;&#x9;install -Dm644 LICENSE -t &amp;#34;$pkgdir&amp;#34;/usr/share/licenses/$pkgname/&#xA;&#x9;install -Dm644 README.md -t &amp;#34;$pkgdir&amp;#34;/usr/share/doc/$pkgname/&#xA;}&#xA;&#xA;sha512sums=&amp;#34;&#xA;87f5b5905d0c95b7f55a789eb00b463bb987c5fc981d83500f34a41b8e9ea2e4a0ac9d45a0c3a029a7957de33f5ade866c396c399252eb4a7a9e09e32e270b99  prek-0.3.3.tar.gz&#xA;&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&amp;hellip;wherein &lt;a href=&#34;https://gitlab.alpinelinux.org/thiagowfx/aports/-/pipelines/407211&#34;&gt;all tests&#xA;pass&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I can&amp;rsquo;t compile &lt;code&gt;prek&lt;/code&gt; on my little alpine linux VPS (1GB RAM). Apparently Rust&#xA;is a monster that requires a lot of RAM for compiling and linking software with&#xA;hundreds of dependencies.&lt;/p&gt;&#xA;&lt;p&gt;I tested it by grabbing the built &lt;code&gt;.apk&lt;/code&gt; package from the GitLab pipeline from&#xA;my MR:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;⏺ x86_64 passed. Let&amp;#39;s grab the artifact and install it:&#xA;&#xA;⏺ Bash(ssh knol &amp;#34;curl -L -o /tmp/prek-x86_64.zip&#xA;      &amp;#39;https://gitlab.alpinelinux.org/api/v4/projects/3998/jobs/2224483/artifacts&amp;#39; &amp;amp;&amp;amp; cd /tmp &amp;amp;&amp;amp; unzip -o&#xA;      prek-x86_64.zip &amp;amp;&amp;amp;…)&#xA;  ⎿    % Total    % Received % Xferd  Average Speed  Time    Time    Time   Current&#xA;                                      Dload  Upload  Total   Spent   Left   Speed&#xA;       0      0   0      0   0      0      0      0                              0100  3.51M 100  3.&#xA;     … &amp;#43;19 lines (ctrl&amp;#43;o to expand)&#xA;&#xA;⏺ Bash(ssh knol &amp;#34;sudo apk add --allow-untrusted /tmp/packages/testing/x86_64/prek-0.3.3-r0.apk &amp;amp;&amp;amp; prek&#xA;      --version&amp;#34;)&#xA;  ⎿  (1/1) Installing prek (0.3.3-r0)&#xA;     Executing busybox-1.37.0-r31.trigger&#xA;     OK: 1257.5 MiB in 579 packages&#xA;     prek 0.3.3&#xA;&#xA;⏺ Installed and working. prek 0.3.3 on knol. All 8 completed architectures passed (riscv64 is still&#xA;  building).&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The LLM tries to use &lt;code&gt;sudo&lt;/code&gt; instead of &lt;code&gt;doas&lt;/code&gt;, which is not correct on Alpine&#xA;Linux.&lt;/p&gt;&#xA;&lt;p&gt;It works anyway because I have the &lt;a href=&#34;https://pkgs.alpinelinux.org/package/edge/main/x86_64/doas-sudo-shim&#34;&gt;&lt;code&gt;doas-sudo-shim&lt;/code&gt;&lt;/a&gt; package installed:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% sudo -h&#xA;Usage:&#xA;  sudo (-i | -s) [-n] [-u &amp;lt;user&amp;gt;] [&amp;lt;command&amp;gt; [--] [&amp;lt;args&amp;gt;...]]&#xA;  sudo [-ins] [-u &amp;lt;user&amp;gt;] &amp;lt;command&amp;gt; [--] [&amp;lt;args&amp;gt;...]&#xA;  sudo [-h]&#xA;&#xA;Execute a command as another user using doas(1).&#xA;&#xA;This is not the original sudo, but the doas shim for sudo. It supports only&#xA;a subset of the sudo options (both short and long) that have an equivalent in&#xA;doas, plus option -i (--login). Refer to sudo(1) for more information.&#xA;&#xA;If you need some sudo features that are not supported in doas, replace&#xA;package &amp;#39;doas-sudo-shim&amp;#39; with &amp;#39;sudo&amp;#39;: apk add sudo !doas-sudo-shim.&#xA;&#xA;Please report bugs at &amp;lt;https://github.com/jirutka/doas-sudo-shim/issues&amp;gt;.&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Expect a post about &lt;code&gt;prek&lt;/code&gt; soon.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2026/02/new-apkbuild-llm-edition/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;&lt;code&gt;alias claudey=claude --dangerously-skip-permissions&lt;/code&gt;, with no annoying&#xA;confirmation prompts&amp;#160;&lt;a href=&#34;https://perrotta.dev/2026/02/new-apkbuild-llm-edition/#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: New APKBUILD: LLM edition&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/ai/&#34;&gt;#ai&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Alpine Linux: clear /tmp upon reboot
      </title>
      <link>https://perrotta.dev/2026/02/alpine-linux-clear-/tmp-upon-reboot/</link>
      <pubDate>Thu, 05 Feb 2026 18:02:12 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2026/02/alpine-linux-clear-/tmp-upon-reboot/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://chromic.org/blog/alpinelinux-clear-tmp-on-reboot/&#34;&gt;Clear /tmp at reboot on Alpine Linux&lt;/a&gt; via Chimo:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;By default, Alpine Linux doesn&amp;rsquo;t (completely) clear the /tmp directory on&#xA;reboot (unless you mount it as tmpfs). If you would like to enable this&#xA;feature, change &amp;ldquo;wipe_tmp&amp;rdquo; to &amp;ldquo;YES&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;[&amp;hellip;]&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;My current config:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;# List of /tmp directories we should clean up&#xA;clean_tmp_dirs=&amp;#34;/tmp&amp;#34;&#xA;&#xA;# Should we wipe the tmp paths completely or just selectively remove known&#xA;# locks / files / etc... ?&#xA;wipe_tmp=&amp;#34;NO&amp;#34;&#xA;&#xA;# Write the initial dmesg log into /var/log/dmesg after boot&#xA;# This may be useful if you need the kernel boot log afterwards&#xA;log_dmesg=&amp;#34;YES&amp;#34;&#xA;&#xA;# Save the previous dmesg log to dmesg.old&#xA;# This may be useful if you need to compare the current boot to the&#xA;# previous one.&#xA;#previous_dmesg=no&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I find that to be a sensible change indeed:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doasedit /etc/conf.d/bootmisc&#xA;# [...]&#xA;# update from NO:&#xA;# wipe_tmp=&amp;#34;YES&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I left my config like this:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;[...]&#xA;# Should we wipe the tmp paths completely or just selectively remove known&#xA;# locks / files / etc... ?&#xA;# wipe_tmp=&amp;#34;NO&amp;#34;&#xA;wipe_tmp=&amp;#34;YES&amp;#34;&#xA;[...]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I like to leave the previous / default value commented out, it is a good paper&#xA;trail&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2026/02/alpine-linux-clear-/tmp-upon-reboot/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; for my future self.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;tombstone&lt;/em&gt;, in software engineering parlance&amp;#160;&lt;a href=&#34;https://perrotta.dev/2026/02/alpine-linux-clear-/tmp-upon-reboot/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine Linux: clear /tmp upon reboot&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>&#39;New&#39; series: Raspberry Pi fun with DevOps, redux
      </title>
      <link>https://perrotta.dev/2026/01/new-series-raspberry-pi-fun-with-devops-redux/</link>
      <pubDate>Sat, 17 Jan 2026 23:14:21 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2026/01/new-series-raspberry-pi-fun-with-devops-redux/</guid>
      <description>&lt;p&gt;♠ Once upon a time, &lt;a href=&#34;https://www.youtube.com/watch?v=vNoKguSdy4Y&#34;&gt;a few mistakes&#xA;ago&lt;/a&gt;, I wanted to start a blog&#xA;series about doing projects with my two Raspberry Pi units at home.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://perrotta.dev/2024/10/new-series-raspberry-pi-fun-with-devops/&#34;&gt;initial post&lt;/a&gt; described my vision, but it was never followed upon. There was a mix of&#xA;reasons for that:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;lack of time and proper prioritization&lt;/li&gt;&#xA;&lt;li&gt;being overwhelmed by choice&lt;/li&gt;&#xA;&lt;li&gt;a desire to stay digitally disconnected&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2026/01/new-series-raspberry-pi-fun-with-devops-redux/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;&#xA;&lt;li&gt;lack of structure in this blog&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2026/01/new-series-raspberry-pi-fun-with-devops-redux/#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Needless to say, my motivation is still well alive, and I have an itch to move&#xA;forward with this project this year. Let&amp;rsquo;s see how it will flow.&lt;/p&gt;&#xA;&lt;p&gt;I want to start by commenting on my ideas from 15 months ago. New Thiago reviews&#xA;old Thiago.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;For a long time I&amp;rsquo;ve been wanting to do something useful with them, while&#xA;increasing my DevOps toolkit knowledge.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;And trying out new tools and workflows that I am not able to try at work –&#xA;either because they don&amp;rsquo;t make sense there, or due to lack of prioritization.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;The roadblock to do so was the most classic excuse: lack of non-interrupted&#xA;time.&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s plenty of motivation, and ideas. These will likely &lt;em&gt;never&lt;/em&gt; run out,&#xA;any time soon.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Still true! Hah!&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;There&amp;rsquo;s no guarantee I will follow up on all of these ideas but,&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;I can foresee the future! 🔮&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Guiding principles&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;a.k.a. rules of engagement&lt;/em&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;My revised goals:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;It must run unix → &lt;strong&gt;unchanged&lt;/strong&gt;.&lt;/li&gt;&#xA;&lt;li&gt;It must be vanilla / upstream → &lt;strong&gt;unchanged&lt;/strong&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Every software installation &lt;em&gt;must&lt;/em&gt; come from a package → packages are still&#xA;OK, but I am opening up the possibility for container images as well (e.g.&#xA;via docker or podman). Ad-hoc installations are still explicitly ruled out,&#xA;UNLESS they are scripted in an idempotent manner e.g. via Ansible or pyinfra,&#xA;but only as a last resort.&lt;/li&gt;&#xA;&lt;li&gt;It must have no X11 nor Wayland nor a graphical system → &lt;strong&gt;unchanged&lt;/strong&gt;. Web&#xA;server management interfaces are acceptable though.&lt;/li&gt;&#xA;&lt;li&gt;Software updates must happen with a single command → &lt;strong&gt;unchanged&lt;/strong&gt;. These&#xA;days I have&#xA;&lt;a href=&#34;https://github.com/thiagowfx/pancake/tree/master/sd_world&#34;&gt;sd_world&lt;/a&gt;. A&#xA;custom &lt;a href=&#34;https://just.systems/&#34;&gt;Justfile&lt;/a&gt; recipe would also be acceptable:&#xA;&lt;code&gt;just update&lt;/code&gt; or &lt;code&gt;just upgrade&lt;/code&gt; or &lt;code&gt;just world&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;It should be reasonably popular and well-supported → &lt;strong&gt;unchanged&lt;/strong&gt;. No&#xA;abandonware.&lt;/li&gt;&#xA;&lt;li&gt;It should support Raspberry Pi (the ARM architecture, for that matter) as a first-class citizen → &lt;strong&gt;unchanged&lt;/strong&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Revised distributions / operating systems:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Debian&#xA;— RaspberryPi OS&lt;/li&gt;&#xA;&lt;li&gt;Fedora&lt;/li&gt;&#xA;&lt;li&gt;Alpine Linux&lt;/li&gt;&#xA;&lt;li&gt;Arch Linux (Arch Linux ARM)&lt;/li&gt;&#xA;&lt;li&gt;Gentoo Linux&lt;/li&gt;&#xA;&lt;li&gt;NixOS&lt;/li&gt;&#xA;&lt;li&gt;FreeBSD&lt;/li&gt;&#xA;&lt;li&gt;OpenBSD&lt;/li&gt;&#xA;&lt;li&gt;&lt;del&gt;openSUSE&lt;/del&gt;: I decided that, if I invest in the &lt;code&gt;.rpm&lt;/code&gt; world, Fedora is the&#xA;way to go.&lt;/li&gt;&#xA;&lt;li&gt;&lt;del&gt;Ubuntu&lt;/del&gt;: I decided that, if I invest in the &lt;code&gt;.deb&lt;/code&gt; world, Debian is the way&#xA;to go. Ubuntu remains solid for commercial / enterprise usage, but this is a&#xA;personal project.&lt;/li&gt;&#xA;&lt;li&gt;&lt;del&gt;Void Linux&lt;/del&gt;: too niche, no enthusiasm for Void Linux in my circle these&#xA;days. If I ever tried it, I feel it would be used as a Desktop distro.&lt;/li&gt;&#xA;&lt;li&gt;&lt;del&gt;Slackware Linux&lt;/del&gt;: updated on 2025-09-11. Too niche, no enthusiasm for it in&#xA;my circle these days.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;This list is still quite large.&#xA;I will need to trim it down further in the following days. Stay tuned.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Now it&amp;rsquo;s shorter: 4 less distros. Progress!&lt;/p&gt;&#xA;&lt;p&gt;One big question is whether I&amp;rsquo;ll treat my Raspberries &lt;a href=&#34;https://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/0&#34;&gt;as pets or as&#xA;cattle&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;To be continued ■&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2026/01/new-series-raspberry-pi-fun-with-devops-redux/#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://sive.rs/n&#34;&gt;Life is short&lt;/a&gt;.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2026/01/new-series-raspberry-pi-fun-with-devops-redux/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;More specifically: linking related posts to each other, and having an easy&#xA;way to go from the previous to the next post in a series.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2026/01/new-series-raspberry-pi-fun-with-devops-redux/#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;Wishful thinking.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2026/01/new-series-raspberry-pi-fun-with-devops-redux/#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: &#39;New&#39; series: Raspberry Pi fun with DevOps, redux&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>PostgreSQL major version upgrade on Alpine Linux, redux
      </title>
      <link>https://perrotta.dev/2026/01/postgresql-major-version-upgrade-on-alpine-linux-redux/</link>
      <pubDate>Thu, 15 Jan 2026 22:25:09 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2026/01/postgresql-major-version-upgrade-on-alpine-linux-redux/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://perrotta.dev/2025/05/postgresql-major-version-upgrade-on-alpine-linux/&#34;&gt;Previously&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;problem-statement&#34;&gt;&#xA;  Problem statement&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2026/01/postgresql-major-version-upgrade-on-alpine-linux-redux/#problem-statement&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Given Alpine Linux 3.23 running PostgreSQL 17, upgrade to PostgreSQL 18. For&#xA;this specific example, consider a &lt;a href=&#34;https://miniflux.app/&#34;&gt;miniflux&lt;/a&gt; database.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://perrotta.dev/2025/05/postgresql-major-version-upgrade-on-alpine-linux/&#34;&gt;previous&lt;/a&gt; post still applies, with only one exception:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.credativ.de/en/blog/postgresql-en/postgresql-18-enables-datachecksums-by-default&#34;&gt;PostgreSQL 18 enables data‑checksums by default&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Version 18 enables data‑checksums by default. In earlier versions, initdb&#xA;required the &lt;code&gt;--data‑checksums&lt;/code&gt; flag. The new release notes explicitly list&#xA;the change in the incompatibilities section: &amp;ldquo;Change initdb default to enable&#xA;data checksums… Checksums can be disabled with the new &lt;code&gt;--no‑data‑checksums&lt;/code&gt;&#xA;option&amp;rdquo;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This is a better sensible default, but it breaks the normal upgrade flow:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;~/18/tmp $ pg_upgrade -b /usr/libexec/postgresql17/ -B /usr/libexec/postgresql18/ -d /var/lib/postgresql/17&#xA;/olddata/ -D /var/lib/postgresql/18/data&#xA;Performing Consistency Checks&#xA;-----------------------------&#xA;Checking cluster versions                                     ok&#xA;&#xA;old cluster does not use data checksums but the new one does&#xA;Failure, exiting&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The stable upgrade path is to initialize the new cluster database (v18) with&#xA;&lt;code&gt;--no-data-checksums&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;~/18/tmp $ initdb --no-data-checksums -D /var/lib/postgresql/18/data --locale=en_US.UTF-8 --encoding=UTF8&#xA;The files belonging to this database system will be owned by user &amp;#34;postgres&amp;#34;.&#xA;This user must also own the server process.&#xA;&#xA;The database cluster will be initialized with locale &amp;#34;en_US.UTF-8&amp;#34;.&#xA;The default text search configuration will be set to &amp;#34;english&amp;#34;.&#xA;&#xA;Data page checksums are disabled.&#xA;&#xA;fixing permissions on existing directory /var/lib/postgresql/18/data ... ok&#xA;creating subdirectories ... ok&#xA;selecting dynamic shared memory implementation ... posix&#xA;selecting default &amp;#34;max_connections&amp;#34; ... 100&#xA;selecting default &amp;#34;shared_buffers&amp;#34; ... 128MB&#xA;selecting default time zone ... America/Toronto&#xA;creating configuration files ... ok&#xA;running bootstrap script ... ok&#xA;performing post-bootstrap initialization ... ok&#xA;syncing data to disk ... ok&#xA;&#xA;initdb: warning: enabling &amp;#34;trust&amp;#34; authentication for local connections&#xA;initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.&#xA;&#xA;Success.&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Then perform the migration as usual. Once the migration is complete, for the&#xA;sake of improving the reliability of the database, enable checksums:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;~/18/tmp $ pg_checksums --enable -D /var/lib/postgresql/18/data&#xA;Checksum operation completed&#xA;Files scanned:   1380&#xA;Blocks scanned:  89609&#xA;Files written:  1151&#xA;Blocks written: 89605&#xA;pg_checksums: syncing data directory&#xA;pg_checksums: updating control file&#xA;Checksums enabled in cluster&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: You must run &lt;code&gt;pg_checksums&lt;/code&gt; while the cluster is off-line, otherwise:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;~/18/tmp $ pg_checksums --enable -D /var/lib/postgresql/18/data&#xA;pg_checksums: error: cluster must be shut down&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Remember to delete the previous package once the upgrade is fully complete:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;/var/lib/postgresql # doas apk del postgresql17&#xA;(1/3) Purging postgresql17-openrc (17.7-r1)&#xA;(2/3) Purging postgresql17 (17.7-r1)&#xA;  Executing postgresql17-17.7-r1.pre-deinstall&#xA;(3/3) Purging postgresql17-client (17.7-r1)&#xA;Executing busybox-1.37.0-r31.trigger&#xA;Executing postgresql-common-1.3-r0.trigger&#xA;OK: 1271.3 MiB in 577 packages&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;That&amp;rsquo;s all!&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: PostgreSQL major version upgrade on Alpine Linux, redux&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ .apk-new
      </title>
      <link>https://perrotta.dev/2025/12/.apk-new/</link>
      <pubDate>Mon, 22 Dec 2025 01:00:02 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>bestof</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2025/12/.apk-new/</guid>
      <description>&lt;p&gt;♠ After running an Alpine Linux server for a couple of months (or years), you&amp;rsquo;ll&#xA;inevitably find a couple of &lt;code&gt;.apk-new&lt;/code&gt; files scattered in your filesystem within&#xA;&lt;code&gt;/etc&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doas find / -name &amp;#34;*.apk-new&amp;#34;&#xA;/etc/rc.conf.apk-new&#xA;/etc/inittab.apk-new&#xA;/etc/fstab.apk-new&#xA;/etc/ssh/sshd_config.apk-new&#xA;/etc/passwd.apk-new&#xA;/etc/crontabs/root.apk-new&#xA;/etc/group.apk-new&#xA;/etc/ufw/ufw.conf.apk-new&#xA;/etc/ufw/user6.rules.apk-new&#xA;/etc/ufw/user.rules.apk-new&#xA;/etc/abuild.conf.apk-new&#xA;/etc/default/grub.apk-new&#xA;/etc/shadow.apk-new&#xA;/etc/network/interfaces.apk-new&#xA;/etc/profile.d/locale.sh.apk-new&#xA;/etc/doas.conf.apk-new&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;As per the &lt;a href=&#34;https://docs.alpinelinux.org/user-handbook/0.1a/Working/apk.html#_installing_packages&#34;&gt;alpine user&#xA;handbook&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Installing a package means that the package itself will be downloaded from a&#xA;repository (specified in /etc/apk/repositories) and unpacked to the system.&#xA;Existing files will be overwritten. An exception are configuration files in&#xA;/etc, which will be preserved. The configuration files (which comes from the&#xA;package) will be renamed to *.apk-new.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;It&amp;rsquo;s very similar to what happens in Arch Linux with&#xA;&lt;a href=&#34;https://wiki.archlinux.org/title/Pacman/Pacnew_and_Pacsave&#34;&gt;&lt;code&gt;.pacnew&lt;/code&gt;&lt;/a&gt; files.&#xA;Arch Linux has an utility called&#xA;&lt;a href=&#34;https://wiki.archlinux.org/title/Pacman/Pacnew_and_Pacsave#pacdiff&#34;&gt;&lt;code&gt;pacdiff&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;It will search for .pacnew, .pacsave and .pacorig files, and will then prompt&#xA;to take action upon them.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;&amp;hellip;and also in Gentoo Linux with&#xA;&lt;a href=&#34;https://wiki.gentoo.org/wiki/Etc-update&#34;&gt;&lt;code&gt;etc-update&lt;/code&gt;&lt;/a&gt; or&#xA;&lt;a href=&#34;https://wiki.gentoo.org/wiki/Dispatch-conf&#34;&gt;&lt;code&gt;dispatch-conf&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Alpine Linux lacks such an utility, to the best of my knowledge.&lt;/p&gt;&#xA;&lt;p&gt;As such, I vibe coded one in &lt;a href=&#34;https://github.com/thiagowfx/pancake&#34;&gt;&lt;code&gt;pancake&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I hereby introduce &lt;a href=&#34;https://github.com/thiagowfx/pancake/tree/master/apknew&#34;&gt;&lt;code&gt;apknew&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Reconcile .apk-new configuration files on Alpine Linux, similar to pacdiff on&#xA;Arch Linux.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;By default, it operates on &lt;code&gt;/etc&lt;/code&gt; only.&lt;/p&gt;&#xA;&lt;p&gt;A sample run looks like the following, prompting a pair of files at a time:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;==============================================&#xA;File: /etc/crontabs/root.apk-new&#xA;Original: /etc/crontabs/root&#xA;==============================================&#xA;&#xA;[v]iew diff  [k]eep original  [r]eplace with new  [m]erge  [s]kip&#xA;Action:&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;You&amp;rsquo;ll want to hit &lt;code&gt;v&lt;/code&gt; first to inspect the diff, and then decide whether to:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;keep the original file&lt;/li&gt;&#xA;&lt;li&gt;replace it with the newer package file&lt;/li&gt;&#xA;&lt;li&gt;interactively merge them (e.g. with &lt;code&gt;vimdiff&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;li&gt;do nothing (skip)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;It&amp;rsquo;s much easier than diffing each file manually.&lt;/p&gt;&#xA;&lt;p&gt;Another example:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;% apknew/apknew.sh&#xA;Searching for .apk-new files in /etc...&#xA;Found 14 file(s) to process.&#xA;&#xA;==============================================&#xA;File: /etc/passwd.apk-new&#xA;Original: /etc/passwd&#xA;==============================================&#xA;&#xA;[v]iew diff  [k]eep original  [r]eplace with new  [m]erge  [s]kip&#xA;Action: s&#xA;Skipping /etc/passwd.apk-new...&#xA;&#xA;==============================================&#xA;File: /etc/shells.apk-new&#xA;Original: /etc/shells&#xA;==============================================&#xA;&#xA;[v]iew diff  [k]eep original  [r]eplace with new  [m]erge  [s]kip&#xA;Action: v&#xA;--- /etc/shells&#x9;2025-12-21 11:07:42.531531775 -0500&#xA;&amp;#43;&amp;#43;&amp;#43; /etc/shells.apk-new&#x9;2025-12-15 09:00:47.000000000 -0500&#xA;@@ -1,5 &amp;#43;1,3 @@&#xA; # valid login shells&#xA; /bin/sh&#xA; /bin/ash&#xA;-/bin/zsh&#xA;-/bin/bash&#xA;&#xA;[v]iew diff  [k]eep original  [r]eplace with new  [m]erge  [s]kip&#xA;Action: k&#xA;Removing /etc/shells.apk-new...&#xA;Done. Kept original file.&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: .apk-new&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Alpine Linux: usr-merge
      </title>
      <link>https://perrotta.dev/2025/10/alpine-linux-usr-merge/</link>
      <pubDate>Wed, 15 Oct 2025 11:16:36 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2025/10/alpine-linux-usr-merge/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://alpinelinux.org/posts/2025-10-01-usr-merge.html&#34;&gt;Alpine Linux&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;The Alpine Linux Technical Steering Committee (TSC) has decided to change the&#xA;base filesystem hierarchy. In the future, /lib, /bin, and /sbin will be&#xA;symbolic links to their /usr counterparts, and every package shall be&#xA;installed under the /usr paths. For now, /usr/bin and /usr/sbin will continue&#xA;to be independent paths, but that might change if the Filesystem Hierarchy&#xA;Standard (FHS) gets updated.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Just like Arch Linux has been doing &lt;a href=&#34;https://www.archlinux.org/news/binaries-move-to-usrbin-requiring-update-intervention/&#34;&gt;for&#xA;ages&lt;/a&gt;&#xA;(2013).&lt;/p&gt;&#xA;&lt;p&gt;Fedora also did &lt;a href=&#34;https://fedoraproject.org/wiki/Changes/Unify_bin_and_sbin&#34;&gt;a similar&#xA;move&lt;/a&gt; this year.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve been running Alpine Linux since 3.15 (2021), its current version is 3.22.&#xA;The migration process has been quite straightforward.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doas apk upgrade -aU     # perform a full system upgrade&#xA;% doas apk add merge-usr   # install the migration script&#xA;% doas merge-usr --dryrun  # exec the migration script in dry-run mode&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;If the dry-run succeeds:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doas merge-usr           # exec the migration script&#xA;$ doas apk del merge-usr   # clean up&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Fin.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine Linux: usr-merge&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ zizmor
      </title>
      <link>https://perrotta.dev/2025/10/zizmor/</link>
      <pubDate>Fri, 03 Oct 2025 12:44:21 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>bestof</category>
      <category>dev</category>
      <category>fosdem</category>
      <category>security</category>
      <guid>https://perrotta.dev/2025/10/zizmor/</guid>
      <description>&lt;p&gt;♠ I originally heard of &lt;a href=&#34;https://docs.zizmor.sh/&#34;&gt;zizmor&lt;/a&gt; at &lt;a href=&#34;https://fosdem.org/2025/&#34;&gt;FOSDEM 2025&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;zizmor is a static analysis tool for GitHub Actions. It can find and fix many&#xA;common security issues in typical GitHub Actions CI/CD setups.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;One month later, I adopted it in the most important repos of our company.&lt;/p&gt;&#xA;&lt;p&gt;A few months later, I &lt;a href=&#34;https://perrotta.dev/2025/06/alpine-linux-packaging-track-new-software-releases/&#34;&gt;added&lt;/a&gt; it to the&#xA;Alpine Linux repositories, and am happily&#xA;&lt;a href=&#34;https://pkgs.alpinelinux.org/packages?name=zizmor&#34;&gt;maintaining&lt;/a&gt; it there.&lt;/p&gt;&#xA;&lt;p&gt;Now I use it &lt;em&gt;everywhere&lt;/em&gt;, via a git &lt;a href=&#34;https://pre-commit.com/&#34;&gt;pre-commit&lt;/a&gt; hook:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: https://github.com/woodruffw/zizmor-pre-commit&#xA;    rev: b933184438555436e38621f46ceb0c417cbed400 # frozen: v1.13.0&#xA;    hooks:&#xA;      - id: zizmor&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The project has recently reached a milestone: &lt;a href=&#34;https://blog.yossarian.net/2025/09/14/one-year-of-zizmor&#34;&gt;it has turned one year&#xA;old&lt;/a&gt;. Its adoption,&#xA;community and feature set have all significantly increased during that time, in&#xA;a good way.&lt;/p&gt;&#xA;&lt;p&gt;The goal, impact and spirit of the project are all really great. And I&amp;rsquo;m sure&#xA;this is mainly thanks to the initiative and dedication of its author, &lt;a href=&#34;https://blog.yossarian.net/&#34;&gt;William&#xA;Woodruff&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: zizmor&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/fosdem/&#34;&gt;#fosdem&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/security/&#34;&gt;#security&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Alpine Linux: remove broken packages
      </title>
      <link>https://perrotta.dev/2025/07/alpine-linux-remove-broken-packages/</link>
      <pubDate>Sun, 20 Jul 2025 10:52:11 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2025/07/alpine-linux-remove-broken-packages/</guid>
      <description>&lt;p&gt;♠ In a sunny Sunday morning:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doas apk upgrade&#xA;WARNING: The indexes contain broken packages which might not function properly.&#xA;[...]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;What?! Out of nowhere?&lt;/p&gt;&#xA;&lt;p&gt;I tried a couple of basic troubleshooting commands:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doas apk update --force-refresh&#xA;% doas apk fix&#xA;% doas apk cache clean&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&amp;hellip;to no avail.&lt;/p&gt;&#xA;&lt;p&gt;Next idea: search engines. Virtually no results on &lt;a href=&#34;https://kagi.com/&#34;&gt;Kagi&lt;/a&gt;. No&#xA;relevant results on Google either&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/07/alpine-linux-remove-broken-packages/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Next idea: LLMs. Nothing useful either.&lt;/p&gt;&#xA;&lt;p&gt;This is novel territory! That&amp;rsquo;s what makes it fun to self-host Alpine Linux&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/07/alpine-linux-remove-broken-packages/#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;p&gt;My intuition tells me to start by searching their&#xA;&lt;a href=&#34;https://gitlab.alpinelinux.org/alpine&#34;&gt;Gitlab&lt;/a&gt;. Eventually I got linked to&#xA;&lt;a href=&#34;https://sourcegraph.com/github.com/alpinelinux/apk-tools/-/blob/test/solver/error11.test?L3-5&#34;&gt;this&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;test/solver/error11.test:&#xA;&#xA;@ARGS add invalid&#xA;@REPO error.repo&#xA;@EXPECT&#xA;WARNING: The indexes contain broken packages which are not installable.&#xA;ERROR: unable to select packages:&#xA;  invalid-1:&#xA;    error: uninstallable&#xA;    satisfies: world[invalid]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;From this test file, it seems possible that there&amp;rsquo;s an invalid package in my&#xA;&lt;code&gt;/etc/apk/world&lt;/code&gt;. The error message is not quite the same, but it is similar:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% wc -l /etc/apk/world&#xA;293&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;There are too many packages there to hunt each of them by hand. And I didn&amp;rsquo;t&#xA;install any new packages recently.&lt;/p&gt;&#xA;&lt;p&gt;So I looked into &lt;code&gt;apk fix --help&lt;/code&gt; and discovered there is a &lt;code&gt;--verbose&lt;/code&gt; flag. I&#xA;did not save its output, but there were five packages there:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;- busybox-initscripts&#xA;- kmod-openrc&#xA;- libacl&#xA;- py3-pep517&#xA;- sensible-utils&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I ran &lt;code&gt;doas apk del&lt;/code&gt; to remove all of them. That was a sensible cleanup, but it&#xA;still has not resolved the issue.&lt;/p&gt;&#xA;&lt;p&gt;If I want to dive deeper into this, I&amp;rsquo;ll have to add some debugging logging to&#xA;&lt;a href=&#34;https://sourcegraph.com/github.com/alpinelinux/apk-tools@bffc60041447cadee5b69c291df8c90eb3b8fe82/-/blob/src/database.c?L2090&#34;&gt;&lt;code&gt;src/database.c&lt;/code&gt;&lt;/a&gt;&#xA;in &lt;code&gt;apk-tools&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt;if (db-&amp;gt;compat_depversions) {&#xA;    apk_warn(out,&#xA;        &amp;#34;The indexes contain broken packages which %s.&amp;#34;,&#xA;        db-&amp;gt;compat_notinstallable ? &amp;#34;are not installable&amp;#34; : &amp;#34;might not function properly&amp;#34;);&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Then one last thought occurred to me:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% apk --version&#xA;apk-tools 3.0.0_rc5_git20250715-r0, compiled for x86_64.&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Aha! I am running a release candidate version of &lt;code&gt;apk&lt;/code&gt;, because I am using&#xA;Alpine Linux edge repositories:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;http://dl-cdn.alpinelinux.org/alpine/edge/main&#xA;http://dl-cdn.alpinelinux.org/alpine/edge/community&#xA;http://dl-cdn.alpinelinux.org/alpine/edge/testing&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;From the&#xA;&lt;a href=&#34;https://wiki.alpinelinux.org/wiki/Draft_Release_Notes_for_Alpine_3.23.0&#34;&gt;Wiki&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;After 5 years of development in the master branch of apk-tools, apk v3 is now&#xA;ready for Alpine v3.23.0. This should be a safe and seamless upgrade from apk&#xA;v2, but might has some breaking changes if you use libapk.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;No wonder this is bleeding edge territory!&lt;/p&gt;&#xA;&lt;p&gt;I would say that there&amp;rsquo;s nothing to do here, other than waiting for the next&#xA;release of Alpine Linux (3.23) with apk-tools v3, and ignoring the warning in&#xA;the meantime.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Their AI overview insists on spitting out useless information about&#xA;Ubuntu, which is completely unrelated in this case.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/07/alpine-linux-remove-broken-packages/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;It is &lt;em&gt;fun&lt;/em&gt;. Right? Right?!&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/07/alpine-linux-remove-broken-packages/#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine Linux: remove broken packages&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Alpine Linux: Dockerfile: install package from edge
      </title>
      <link>https://perrotta.dev/2025/06/alpine-linux-dockerfile-install-package-from-edge/</link>
      <pubDate>Wed, 04 Jun 2025 12:37:47 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <category>docker</category>
      <guid>https://perrotta.dev/2025/06/alpine-linux-dockerfile-install-package-from-edge/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Problem statement&lt;/strong&gt;: Given a &lt;code&gt;Dockerfile&lt;/code&gt; based on a fixed release of Alpine&#xA;Linux (e.g. &lt;code&gt;FROM alpine:3.22&lt;/code&gt;), install a package from &lt;code&gt;edge&lt;/code&gt; (i.e. a package&#xA;that did not yet make it to a fixed release).&lt;/p&gt;&#xA;&lt;p&gt;The approach is similar to package &lt;a href=&#34;https://perrotta.dev/2025/01/alpine-linux-install-/-pin-old-packages/&#34;&gt;pinning&lt;/a&gt;, by providing a custom&#xA;&lt;code&gt;--repository&lt;/code&gt; to &lt;code&gt;apk add&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;RUN apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing gliderlabs-sigil vals&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Replace &amp;ldquo;testing&amp;rdquo; with &amp;ldquo;community&amp;rdquo; or &amp;ldquo;main&amp;rdquo; as needed.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine Linux: Dockerfile: install package from edge&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/docker/&#34;&gt;#docker&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Alpine Linux: package is gone?!
      </title>
      <link>https://perrotta.dev/2025/06/alpine-linux-package-is-gone/</link>
      <pubDate>Wed, 04 Jun 2025 11:43:05 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2025/06/alpine-linux-package-is-gone/</guid>
      <description>&lt;p&gt;♠ Upon upgrading one of our Dockerfiles from Alpine Linux 3.21 to 3.22, we noticed&#xA;that the &lt;code&gt;ruby-json&lt;/code&gt; package no longer exists:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% docker build -t myproject -f Dockerfile .&#xA;[...]&#xA; &amp;gt; [ 2/10] RUN apk add --update --no-cache ruby-json=~&amp;#34;3.4&amp;#34; [...]&#xA;[...]&#xA;2.371 ERROR: unable to select packages:&#xA;2.371   ruby-json (no such package):&#xA;2.371     required by: world[ruby-json~3.4]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Why??&lt;/p&gt;&#xA;&lt;p&gt;First, look into &lt;a href=&#34;https://pkgs.alpinelinux.org/packages&#34;&gt;Alpine Linux Packages&lt;/a&gt;:&#xA;&lt;a href=&#34;https://pkgs.alpinelinux.org/packages?name=ruby-json&amp;amp;branch=v3.22&amp;amp;repo=&amp;amp;arch=&amp;amp;origin=&amp;amp;flagged=&amp;amp;maintainer=&#34;&gt;query&lt;/a&gt;.&#xA;Ensure to set branch to v3.22 and architecture to All. And it&amp;rsquo;s indeed not&#xA;there anymore:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;No matching packages found&amp;hellip;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Next, do a bit of digging in the&#xA;&lt;a href=&#34;https://gitlab.alpinelinux.org/alpine/aports&#34;&gt;aports&lt;/a&gt; repository on the &lt;a href=&#34;https://gitlab.alpinelinux.org/&#34;&gt;Alpine&#xA;Linux GitLab&lt;/a&gt;:&#xA;&lt;a href=&#34;https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/?sort=created_date&amp;amp;state=all&amp;amp;search=ruby-json&amp;amp;first_page_size=20&#34;&gt;query&lt;/a&gt;.&#xA;Search for &lt;code&gt;ruby-json&lt;/code&gt; in &amp;ldquo;All&amp;rdquo; Merge requests.&lt;/p&gt;&#xA;&lt;p&gt;That leads to&#xA;&lt;a href=&#34;https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/80599&#34;&gt;&lt;code&gt;!80599&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;community/ruby-json: remove&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;ruby-json&lt;/code&gt; is already bundled with &lt;code&gt;main/ruby&lt;/code&gt;. The &lt;code&gt;community/ruby-json&lt;/code&gt;&#xA;could never be installed due to conflicts.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Aha, then there&amp;rsquo;s nothing to do here. Installing the base &lt;code&gt;ruby&lt;/code&gt; package should&#xA;be enough from now on.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://gitlab.alpinelinux.org/alpine/aports/-/issues/16938&#34;&gt;#16938&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;I think it&amp;rsquo;s safe to say community/ruby-json does literally nothing, has never been installed, and does not work.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Oh well.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine Linux: package is gone?!&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ Alpine Linux packaging: track new software releases
      </title>
      <link>https://perrotta.dev/2025/06/alpine-linux-packaging-track-new-software-releases/</link>
      <pubDate>Tue, 03 Jun 2025 01:36:53 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>bestof</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2025/06/alpine-linux-packaging-track-new-software-releases/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Problem statement&lt;/strong&gt;: You maintain packages for a Linux distribution, for&#xA;example, &lt;a href=&#34;https://www.alpinelinux.org/&#34;&gt;Alpine Linux&lt;/a&gt;. Find a way to keep track&#xA;of new software releases, to ensure your packages are kept up-to-date.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://release-monitoring.org/&#34;&gt;Release Monitoring&lt;/a&gt; project (codename&#xA;&lt;em&gt;Anitya&lt;/em&gt;), part of the &lt;a href=&#34;https://www.fedoraproject.org/&#34;&gt;Fedora Linux&lt;/a&gt; distro,&#xA;can accomplish that:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Watch for releases of your favorite projects&lt;/p&gt;&#xA;&lt;p&gt;We all have our favorite projects and we all like to use their latest versions&#xA;with their latest features. However, it is hard to keep in touch with all of&#xA;them and be informed when they release a new version. Anitya can help!&lt;/p&gt;&#xA;&lt;p&gt;We monitor upstream releases and broadcast them on Fedora messaging bus.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;The underlying project:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Anitya is a release monitoring project.&lt;/p&gt;&#xA;&lt;p&gt;Its goal is to regularly check if a project has made a new release. When&#xA;Anitya discovers a new release for a project, it publishes a RabbitMQ message&#xA;via fedora messaging. This makes it easy to integrate with Anitya and perform&#xA;actions when a new release is created for a project. For example, the Fedora&#xA;project runs a service called the-new-hotness which files a Bugzilla bug&#xA;against a package when the upstream project makes a new release.&lt;/p&gt;&#xA;&lt;p&gt;Anitya provides a user-friendly interface to add, edit, or browse projects.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Even though it is part of Fedora, we can use it for Alpine Linux just fine!&lt;/p&gt;&#xA;&lt;p&gt;Recently I packaged &lt;a href=&#34;https://github.com/zizmorcore/zizmor/issues/780&#34;&gt;zizmor&lt;/a&gt;. I&#xA;noticed there was no entry for it in Anitya. I added one. It&amp;rsquo;s very&#xA;straightforward:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;head to &lt;a href=&#34;https://release-monitoring.org/&#34;&gt;https://release-monitoring.org/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;ensure the project you want to monitor isn&amp;rsquo;t already present there&lt;/li&gt;&#xA;&lt;li&gt;log in (e.g. via GitHub)&lt;/li&gt;&#xA;&lt;li&gt;click &lt;a href=&#34;https://release-monitoring.org/project/new&#34;&gt;add project&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Fill in the form:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Project name: zizmor&lt;/li&gt;&#xA;&lt;li&gt;Homepage: &lt;a href=&#34;https://docs.zizmor.sh&#34;&gt;https://docs.zizmor.sh&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;BackEnd: GitHub&lt;/li&gt;&#xA;&lt;li&gt;Version scheme: Semantic&lt;/li&gt;&#xA;&lt;li&gt;Github owner/project: &lt;a href=&#34;https://github.com/zizmorcore/zizmor&#34;&gt;zizmorcore/zizmor&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Version prefix: &lt;code&gt;v&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Pre-release filter: &lt;code&gt;rc&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Version filter: &lt;code&gt;rc&lt;/code&gt; (excludes &lt;a href=&#34;https://github.com/zizmorcore/zizmor/releases/tag/v1.8.0-rc0&#34;&gt;&lt;code&gt;1.8.0-rc0&lt;/code&gt;&lt;/a&gt;) and the like&lt;/li&gt;&#xA;&lt;li&gt;Check off &amp;ldquo;check releases instead of tags&amp;rdquo;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Once the project is &lt;a href=&#34;https://release-monitoring.org/project/378497/&#34;&gt;created&lt;/a&gt;,&#xA;add a new distribution mapping:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Distribution: Alpine&lt;/li&gt;&#xA;&lt;li&gt;Package name: &lt;a href=&#34;https://pkgs.alpinelinux.org/package/edge/testing/x86_64/zizmor&#34;&gt;zizmor&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;And we&amp;rsquo;re done!&lt;/p&gt;&#xA;&lt;p&gt;As per &lt;a href=&#34;https://pkgs.alpinelinux.org/flagging&#34;&gt;https://pkgs.alpinelinux.org/flagging&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Package flagging&lt;/p&gt;&#xA;&lt;p&gt;Manual package flagging has been disabled in favour of automatic flagging&#xA;based on Anitya.&lt;/p&gt;&#xA;&lt;p&gt;If you want a package to be up-to-date or to inform the maintainer about the&#xA;package status, please feel free to create a package mapping on Anitya. Once&#xA;the mapping is made, it should automatically send an email to the maintainer&#xA;whenever a new version is available.&lt;/p&gt;&#xA;&lt;p&gt;For details on how to create a project mapping for Alpine Linux, please refer&#xA;to the &lt;a href=&#34;https://release-monitoring.org/static/docs/user-guide.html&#34;&gt;release monitoring&#xA;documentation&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine Linux packaging: track new software releases&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ PostgreSQL major version upgrade on Alpine Linux
      </title>
      <link>https://perrotta.dev/2025/05/postgresql-major-version-upgrade-on-alpine-linux/</link>
      <pubDate>Sat, 24 May 2025 16:00:23 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>bestof</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2025/05/postgresql-major-version-upgrade-on-alpine-linux/</guid>
      <description>&lt;h2 id=&#34;problem-statement&#34;&gt;&#xA;  Problem statement&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2025/05/postgresql-major-version-upgrade-on-alpine-linux/#problem-statement&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Given Alpine Linux 3.22 running PostgreSQL 16, upgrade to PostgreSQL 17. For&#xA;this specific example, consider a &lt;a href=&#34;https://miniflux.app/&#34;&gt;&lt;code&gt;miniflux&lt;/code&gt;&lt;/a&gt; database.&lt;/p&gt;&#xA;&lt;h2 id=&#34;procedure&#34;&gt;&#xA;  Procedure&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2025/05/postgresql-major-version-upgrade-on-alpine-linux/#procedure&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Install PostgreSQL 17:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doas apk add postgres&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It can coexist with &lt;code&gt;postgres16&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Stop the PostgreSQL daemon:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doas service postgresql stop&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;If you navigate to the Miniflux server with your browser, it will fail, as&#xA;expected:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;store: unable to create app session: dial tcp [::1]:5432: connect: connection refused&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Change the PostgreSQL system version to the desired one:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doas pg_versions set-default 17&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Change to the root user, for convenience, then change to the existing postgresql&#xA;directory:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doas su&#xA;% cd /var/lib/postgresql/&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Prepare files and directories for the upgrade:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% cd 16/&#xA;% mv data olddata&#xA;% cd ../&#xA;% mkdir 17/&#xA;% cd 17/&#xA;% mkdir data tmp&#xA;% chown postgres:postgres data tmp&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Change to the &lt;code&gt;postgres&lt;/code&gt; user:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% su postgres&#xA;% cd tmp/&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Initialize the new cluster:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% initdb -D /var/lib/postgresql/17/data --locale=en_US.UTF-8 --encoding=UTF8 [--data-checksums]&#xA;The files belonging to this database system will be owned by user &amp;#34;postgres&amp;#34;.&#xA;This user must also own the server process.&#xA;&#xA;The database cluster will be initialized with locale &amp;#34;en_US.UTF-8&amp;#34;.&#xA;The default text search configuration will be set to &amp;#34;english&amp;#34;.&#xA;&#xA;Data page checksums are disabled.&#xA;&#xA;fixing permissions on existing directory /var/lib/postgresql/17/data ... ok&#xA;creating subdirectories ... ok&#xA;selecting dynamic shared memory implementation ... posix&#xA;selecting default &amp;#34;max_connections&amp;#34; ... 100&#xA;selecting default &amp;#34;shared_buffers&amp;#34; ... 128MB&#xA;selecting default time zone ... America/Toronto&#xA;creating configuration files ... ok&#xA;running bootstrap script ... ok&#xA;performing post-bootstrap initialization ... ok&#xA;syncing data to disk ... ok&#xA;&#xA;initdb: warning: enabling &amp;#34;trust&amp;#34; authentication for local connections&#xA;initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.&#xA;&#xA;Success.&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;In the previous command I chose to omit &lt;code&gt;--data-checksums&lt;/code&gt;, otherwise I get the&#xA;following error:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;old cluster does not use data checksums but the new one does&#xA;Failure, exiting&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Upgrade the new cluster:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% pg_upgrade -b /usr/libexec/postgresql16/ -B /usr/libexec/postgresql17/ -d /var/lib/postgresql/16/olddata/ -D /var/lib/postgresql/17/data/&#xA;Performing Consistency Checks&#xA;-----------------------------&#xA;Checking cluster versions                                     ok&#xA;Checking database user is the install user                    ok&#xA;Checking database connection settings                         ok&#xA;Checking for prepared transactions                            ok&#xA;Checking for contrib/isn with bigint-passing mismatch         ok&#xA;Checking data type usage                                      ok&#xA;Creating dump of global objects                               ok&#xA;Creating dump of database schemas&#xA;                                                              ok&#xA;Checking for presence of required libraries                   ok&#xA;Checking database user is the install user                    ok&#xA;Checking for prepared transactions                            ok&#xA;Checking for new cluster tablespace directories               ok&#xA;&#xA;If pg_upgrade fails after this point, you must re-initdb the&#xA;new cluster before continuing.&#xA;&#xA;Performing Upgrade&#xA;------------------&#xA;Setting locale and encoding for new cluster                   ok&#xA;Analyzing all rows in the new cluster                         ok&#xA;Freezing all rows in the new cluster                          ok&#xA;Deleting files from new pg_xact                               ok&#xA;Copying old pg_xact to new server                             ok&#xA;Setting oldest XID for new cluster                            ok&#xA;Setting next transaction ID and epoch for new cluster         ok&#xA;Deleting files from new pg_multixact/offsets                  ok&#xA;Copying old pg_multixact/offsets to new server                ok&#xA;Deleting files from new pg_multixact/members                  ok&#xA;Copying old pg_multixact/members to new server                ok&#xA;Setting next multixact ID and offset for new cluster          ok&#xA;Resetting WAL archives                                        ok&#xA;Setting frozenxid and minmxid counters in new cluster         ok&#xA;Restoring global objects in the new cluster                   ok&#xA;Restoring database schemas in the new cluster&#xA;                                                              ok&#xA;Copying user relation files&#xA;                                                              ok&#xA;Setting next OID for new cluster                              ok&#xA;Sync data directory to disk                                   ok&#xA;Creating script to delete old cluster                         ok&#xA;Checking for extension updates                                ok&#xA;&#xA;Upgrade Complete&#xA;----------------&#xA;Optimizer statistics are not transferred by pg_upgrade.&#xA;Once you start the new server, consider running:&#xA;    /usr/libexec/postgresql17/vacuumdb --all --analyze-in-stages&#xA;Running this script will delete the old cluster&amp;#39;s data files:&#xA;    ./delete_old_cluster.sh&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Now clean up:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% ^D  # go back to the root user&#xA;% /var/lib/postgresql/17/tmp/delete_old_cluster.sh&#xA;% rmdir /var/lib/postgresql/16/  # it should be empty by now&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;If you&amp;rsquo;re curious about this script, it&amp;rsquo;s a simple one-liner:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh&#xA;&#xA;rm -rf &amp;#39;/var/lib/postgresql/16/olddata&amp;#39;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Finally, let&amp;rsquo;s get the cluster back up and vacuum it:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% ^D  # go back to your main user&#xA;% doas service postgresql start&#xA;% doas su - postgres -c &amp;#39;/usr/libexec/postgresql17/vacuumdb --all --analyze-in-stages&amp;#39;&#xA;vacuumdb: processing database &amp;#34;miniflux&amp;#34;: Generating minimal optimizer statistics (1 target)&#xA;vacuumdb: processing database &amp;#34;postgres&amp;#34;: Generating minimal optimizer statistics (1 target)&#xA;vacuumdb: processing database &amp;#34;template1&amp;#34;: Generating minimal optimizer statistics (1 target)&#xA;vacuumdb: processing database &amp;#34;miniflux&amp;#34;: Generating medium optimizer statistics (10 targets)&#xA;vacuumdb: processing database &amp;#34;postgres&amp;#34;: Generating medium optimizer statistics (10 targets)&#xA;vacuumdb: processing database &amp;#34;template1&amp;#34;: Generating medium optimizer statistics (10 targets)&#xA;vacuumdb: processing database &amp;#34;miniflux&amp;#34;: Generating default (full) optimizer statistics&#xA;vacuumdb: processing database &amp;#34;postgres&amp;#34;: Generating default (full) optimizer statistics&#xA;vacuumdb: processing database &amp;#34;template1&amp;#34;: Generating default (full) optimizer statistics&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;At this point, Miniflux should be working again.&#xA;Navigate to &lt;code&gt;/about&lt;/code&gt; and verify its database version: &lt;code&gt;Postgres version: 17.5&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Last step, delete the previous package:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doas apk del postgresql16 postgres16-contrib&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Double-check the newer version is still installed:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% apk list -I | grep postgres&#xA;libpq-17.5-r0 x86_64 {postgresql17} (PostgreSQL) [installed]&#xA;postgresql-common-1.2-r1 x86_64 {postgresql-common} (MIT) [installed]&#xA;postgresql-common-openrc-1.2-r1 x86_64 {postgresql-common} (MIT) [installed]&#xA;postgresql-zsh-completion-5.9-r5 x86_64 {zsh} (MIT-Modern-Variant AND GPL-2.0-only) [installed]&#xA;postgresql17-17.5-r0 x86_64 {postgresql17} (PostgreSQL) [installed]&#xA;postgresql17-client-17.5-r0 x86_64 {postgresql17} (PostgreSQL) [installed]&#xA;postgresql17-contrib-17.5-r0 x86_64 {postgresql17} (PostgreSQL) [installed]&#xA;postgresql17-openrc-17.5-r0 x86_64 {postgresql17} (PostgreSQL) [installed]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;And we&amp;rsquo;re done!&lt;/p&gt;&#xA;&lt;h2 id=&#34;references&#34;&gt;&#xA;  References&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2025/05/postgresql-major-version-upgrade-on-alpine-linux/#references&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://wiki.alpinelinux.org/wiki/Postgresql&#34;&gt;https://wiki.alpinelinux.org/wiki/Postgresql&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://beune.dev/posts/upgrade-alpine-postgresql/&#34;&gt;https://beune.dev/posts/upgrade-alpine-postgresql/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: PostgreSQL major version upgrade on Alpine Linux&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ Alpine Linux: the maintainer workflow
      </title>
      <link>https://perrotta.dev/2025/04/alpine-linux-the-maintainer-workflow/</link>
      <pubDate>Sun, 20 Apr 2025 15:12:09 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>bestof</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2025/04/alpine-linux-the-maintainer-workflow/</guid>
      <description>&lt;p&gt;♠ I maintain &lt;a href=&#34;https://pkgs.alpinelinux.org/packages?name=&amp;amp;branch=edge&amp;amp;repo=&amp;amp;arch=x86_64&amp;amp;origin=&amp;amp;flagged=&amp;amp;maintainer=Thiago&amp;#43;Perrotta&#34;&gt;a couple of&#xA;packages&lt;/a&gt;&#xA;on Alpine Linux.&lt;/p&gt;&#xA;&lt;p&gt;Every now and then, a new software version is released, and it&amp;rsquo;s my duty&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/04/alpine-linux-the-maintainer-workflow/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; as an&#xA;active package maintainer to update my packages.&lt;/p&gt;&#xA;&lt;p&gt;The typical workflow looks like the following.&lt;/p&gt;&#xA;&lt;p&gt;First I get an email notification from &amp;ldquo;Alpine Package DB&amp;rdquo;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Dear Thiago Perrotta&lt;/p&gt;&#xA;&lt;p&gt;This is an automatic message send from pkgs.alpinelinux.org&#xA;One or more of your aports have been flagged out of date based on&#xA;Anitya monitoring system &lt;a href=&#34;https://release-monitoring.org/&#34;&gt;https://release-monitoring.org/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;argocd current: 2.14.2-r1 new: 2.14.9&lt;/p&gt;&#xA;&lt;p&gt;To update the package you can use our helper script:&lt;/p&gt;&#xA;&lt;p&gt;abump aport-version&lt;/p&gt;&#xA;&lt;p&gt;If the provided information is incorrect, please let us know on IRC&#xA;or &lt;a href=&#34;mailto:alpine-infra@alpinelinux.org&#34;&gt;alpine-infra@alpinelinux.org&lt;/a&gt;. Thanks!&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;It&amp;rsquo;s important not to &lt;a href=&#34;https://opensource.com/business/15/12/avoid-burnout-live-happy&#34;&gt;burn oneself&#xA;out&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I treat these notifications as an FYI. They &lt;em&gt;are not&lt;/em&gt; prompts to make me&#xA;immediately go and update the packages&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/04/alpine-linux-the-maintainer-workflow/#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. For packages with frequent updates,&#xA;like &lt;code&gt;argocd&lt;/code&gt;, I&amp;rsquo;ll let a few patch versions accumulate before I take action.&#xA;Other times it is handy to batch package upgrades together so to do them all at&#xA;once. I use my personal judgment here. At the very least, this email will&#xA;trigger a TODO in my task list, so that it will not be forgotten. &lt;em&gt;At some&#xA;point&lt;/em&gt; it will be taken care of.&lt;/p&gt;&#xA;&lt;p&gt;Once I am ready to update the package, I &lt;code&gt;ssh&lt;/code&gt; to my Alpine Linux server. There&#xA;is absolutely no requirement to update alpine linux packages from alpine linux,&#xA;but it&amp;rsquo;s the most convenient to do, and I already have an alpine linux system&#xA;anyway.&lt;/p&gt;&#xA;&lt;p&gt;Then &lt;code&gt;cd aports/&lt;/code&gt;. This is a local git clone of the &lt;a href=&#34;https://gitlab.alpinelinux.org/alpine/aports&#34;&gt;aports&#xA;tree&lt;/a&gt;&#xA;(&lt;a href=&#34;https://wiki.alpinelinux.org/wiki/Aports_tree&#34;&gt;wiki&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;git pull&lt;/code&gt;. I tend to clean up after myself, so the last branch is likely&#xA;already &lt;code&gt;master&lt;/code&gt;. But, if not, then &lt;code&gt;git reset --hard &amp;amp;&amp;amp; git checkout master &amp;amp;&amp;amp; git pull&lt;/code&gt;. Amen.&lt;/p&gt;&#xA;&lt;p&gt;Now the real fun starts. And it&amp;rsquo;s surprisingly quick and simple, it feels like&#xA;cheating:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% abump argocd-2.14.9&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This will update the version and checksums in the corresponding &lt;code&gt;APKBUILD&lt;/code&gt; and&#xA;trigger a package build.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s sensible to check the package release notes or changelog to find potential&#xA;incompatibilities and/or updates to the build process. It&amp;rsquo;s also sensible to&#xA;look at the package diffs to inspect for potential malware, which is becoming&#xA;increasingly more common these days in open source packages. &amp;ldquo;Secure your supply&#xA;chain&amp;rdquo;, as they say it.&lt;/p&gt;&#xA;&lt;p&gt;If the build completes successfully, create a new branch (&lt;code&gt;git nb argocd&lt;/code&gt;),&#xA;commit, push it (&lt;code&gt;git pushm&lt;/code&gt;), then create a merge request (MR) on&#xA;&lt;a href=&#34;https://gitlab.alpinelinux.org/&#34;&gt;GitLab&lt;/a&gt;. I normally use the Web UI to do so,&#xA;but it&amp;rsquo;s also possible to do it with the CLI. A link to create the MR is printed&#xA;to stdout upon pushing the branch, which makes the process even easier.&lt;/p&gt;&#xA;&lt;p&gt;It could happen that the MR fails CI for some architectures (even if it works&#xA;locally on my machine™). These errors need to be dealt with.&lt;/p&gt;&#xA;&lt;p&gt;The commit message must follow a certain style. I have a pre-commit script set&#xA;up that does it for me. It is typically in this form:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;testing/argocd: upgrade to 2.14.9&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;There&amp;rsquo;s a rule: only one package per merge request.&lt;/p&gt;&#xA;&lt;p&gt;If there are more packages to upgrade, I run &lt;code&gt;git bd&lt;/code&gt; (&amp;ldquo;branch delete&amp;rdquo;) and then&#xA;restart this process. It&amp;rsquo;s quite manageable, as I don&amp;rsquo;t maintain a lot of&#xA;packages. Perhaps it could be further automated if there were more packages&#xA;and/or if the package upgrades were more frequent.&lt;/p&gt;&#xA;&lt;p&gt;The final MR looks like the following:&#xA;&lt;a href=&#34;https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/83026&#34;&gt;!83026&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Now I sit tight and wait for approval.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;On a &lt;a href=&#34;https://xkcd.com/2347/&#34;&gt;&lt;em&gt;best-effort&lt;/em&gt;&lt;/a&gt; basis.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/04/alpine-linux-the-maintainer-workflow/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;Unless it&amp;rsquo;s a security risk or incident (e.g. whenever there&amp;rsquo;s a CVE&#xA;associated with it).&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/04/alpine-linux-the-maintainer-workflow/#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine Linux: the maintainer workflow&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ First Alpine Linux package in Community
      </title>
      <link>https://perrotta.dev/2025/04/first-alpine-linux-package-in-community/</link>
      <pubDate>Tue, 01 Apr 2025 14:27:41 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>bestof</category>
      <category>dev</category>
      <category>serenity</category>
      <guid>https://perrotta.dev/2025/04/first-alpine-linux-package-in-community/</guid>
      <description>&lt;p&gt;♠ How wholesome: someone sent a MR (Merge Request)&#xA;&lt;a href=&#34;https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/82047&#34;&gt;!82047&lt;/a&gt; in&#xA;&lt;code&gt;alpine/aports&lt;/code&gt; with the &lt;a href=&#34;https://github.com/google/yamlfmt&#34;&gt;&lt;code&gt;yamlfmt&lt;/code&gt;&lt;/a&gt; package&#xA;I maintain, moving it from &lt;code&gt;testing/&lt;/code&gt; to &lt;code&gt;community/&lt;/code&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/04/first-alpine-linux-package-in-community/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This means the package will be included in the next release of Alpine (3.22) as&#xA;part of its official repositories (unlike &amp;ldquo;testing&amp;rdquo;, which is only included in&#xA;&amp;ldquo;edge&amp;rdquo;).&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;In other words&lt;/strong&gt;: This is the first time a package I maintain will make it to the&#xA;official repositories of a Linux distro I like!&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Next milestone&lt;/strong&gt;: Follow suit with a project I maintain.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;I did not initiate this: it has just happened organically / spontaneously.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/04/first-alpine-linux-package-in-community/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: First Alpine Linux package in Community&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/serenity/&#34;&gt;#serenity&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Alpine Linux: install / pin old packages
      </title>
      <link>https://perrotta.dev/2025/01/alpine-linux-install-/-pin-old-packages/</link>
      <pubDate>Wed, 08 Jan 2025 15:54:44 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2025/01/alpine-linux-install-/-pin-old-packages/</guid>
      <description>&lt;p&gt;♠ Let&amp;rsquo;s say you want to install an older version of &lt;code&gt;jq&lt;/code&gt; in Alpine Linux.&lt;/p&gt;&#xA;&lt;p&gt;For example:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Alpine v3.18 has &lt;code&gt;jq&lt;/code&gt; 1.6&lt;/li&gt;&#xA;&lt;li&gt;Alpine v3.19 has &lt;code&gt;jq&lt;/code&gt; 1.7&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;You&amp;rsquo;re probably using Alpine v3.21 or edge today, which has an even newer&#xA;version of &lt;code&gt;jq&lt;/code&gt;. Hence &lt;code&gt;doas apk add jq&lt;/code&gt; will not do.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s assume you do not want to edit your &lt;code&gt;/etc/apk/repositories&lt;/code&gt;, as this is an&#xA;one-off, for a single package. What can you do then?&lt;/p&gt;&#xA;&lt;p&gt;Use the &lt;code&gt;--repository&lt;/code&gt; flag from &lt;code&gt;apk&lt;/code&gt;!&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doas apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/v3.18/main jq=~1.6&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It&amp;rsquo;s also important to pin the package version (&lt;code&gt;~1.6&lt;/code&gt;), otherwise the latest&#xA;one available gets installed.&lt;/p&gt;&#xA;&lt;p&gt;Replace &amp;ldquo;main&amp;rdquo; with &amp;ldquo;community&amp;rdquo; or &amp;ldquo;testing&amp;rdquo; as needed.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine Linux: install / pin old packages&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Alpine Linux 3.21
      </title>
      <link>https://perrotta.dev/2024/12/alpine-linux-3.21/</link>
      <pubDate>Tue, 10 Dec 2024 19:52:33 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2024/12/alpine-linux-3.21/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://alpinelinux.org/posts/Alpine-3.21.0-released.html&#34;&gt;Alpine Linux 3.21.0&lt;/a&gt;&#xA;got released last Thursday.&lt;/p&gt;&#xA;&lt;p&gt;So what? I am credited as an aports commit contributor! :-)&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://pkgs.alpinelinux.org/packages?name=&amp;amp;branch=edge&amp;amp;repo=&amp;amp;arch=x86_64&amp;amp;origin=&amp;amp;flagged=&amp;amp;maintainer=Thiago&amp;#43;Perrotta&#34;&gt;Alpine package&#xA;search&lt;/a&gt;:&#xA;a list of all the packages I currently maintain, excluding dependencies:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;argocd&lt;/li&gt;&#xA;&lt;li&gt;autotrash&lt;/li&gt;&#xA;&lt;li&gt;bkt&lt;/li&gt;&#xA;&lt;li&gt;fpp&lt;/li&gt;&#xA;&lt;li&gt;gliderlabs-sigil&lt;/li&gt;&#xA;&lt;li&gt;kubeseal&lt;/li&gt;&#xA;&lt;li&gt;tfupdate&lt;/li&gt;&#xA;&lt;li&gt;typos&lt;/li&gt;&#xA;&lt;li&gt;urlwatch&lt;/li&gt;&#xA;&lt;li&gt;yamlfmt&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&amp;hellip;a total of &lt;strong&gt;10&lt;/strong&gt; packages.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine Linux 3.21&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ New series: Raspberry Pi fun with DevOps
      </title>
      <link>https://perrotta.dev/2024/10/new-series-raspberry-pi-fun-with-devops/</link>
      <pubDate>Sun, 13 Oct 2024 11:31:13 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>bestof</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2024/10/new-series-raspberry-pi-fun-with-devops/</guid>
      <description>&lt;h2 id=&#34;intro&#34;&gt;&#xA;  Intro&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2024/10/new-series-raspberry-pi-fun-with-devops/#intro&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I have two raspberry pi&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2024/10/new-series-raspberry-pi-fun-with-devops/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; units sitting idle at home, a 3B and a 4.&lt;/p&gt;&#xA;&lt;p&gt;For a long time I&amp;rsquo;ve been wanting to do something useful with them,&#xA;while increasing my DevOps toolkit knowledge.&lt;/p&gt;&#xA;&lt;p&gt;The roadblock to do so was the most classic excuse: lack of non-interrupted time.&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s plenty of motivation, and ideas. These will likely &lt;em&gt;never&lt;/em&gt; run out, any&#xA;time soon.&lt;/p&gt;&#xA;&lt;p&gt;That said: It&amp;rsquo;s very easy to get distracted and lose focus.&#xA;There is so much information today, in the form of&#xA;YouTube videos,&#xA;blog posts,&#xA;forums and communities (e.g. the likes of Hacker News and Reddit),&#xA;podcasts,&#xA;books…the list goes on, and it does not end.&lt;/p&gt;&#xA;&lt;p&gt;Therefore, for the sake of fixing a North Star path,&#xA;I wanted to make an initial blog post with some of the ideas that are currently in my head.&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s no guarantee I will follow up on all of these ideas but,&#xA;as long as their spirit is ingrained in semi-permanent written form,&#xA;I figured that shall be enough to make me accountable to myself.&lt;/p&gt;&#xA;&lt;h2 id=&#34;guiding-principles&#34;&gt;&#xA;  Guiding principles&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2024/10/new-series-raspberry-pi-fun-with-devops/#guiding-principles&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;em&gt;a.k.a. rules of engagement&lt;/em&gt;&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;It must run Unix&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;No Windows. No macOS. Anything else is fair game. Corollary: It must run either Linux or BSD.&lt;/p&gt;&#xA;&lt;ol start=&#34;2&#34;&gt;&#xA;&lt;li&gt;It must be vanilla / upstream&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;No spin-offs. For example: for Ubuntu, no Xubuntu. For Arch Linux, no Manjaro. For Gentoo, no Funtoo.&#xA;And so on. Stick to the &lt;em&gt;core&lt;/em&gt; / base Linux distributions.&#xA;For BSD, this is a non-issue.&lt;/p&gt;&#xA;&lt;ol start=&#34;3&#34;&gt;&#xA;&lt;li&gt;Every software installation &lt;em&gt;must&lt;/em&gt; come from a package&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;If there is no package, I will create one myself.&#xA;This is easier if I use Arch Linux or Alpine Linux, but I am willing to contribute to other distributions as well.&lt;/p&gt;&#xA;&lt;ol start=&#34;4&#34;&gt;&#xA;&lt;li&gt;It must have no X11 nor Wayland nor a graphical system&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;It should be a pure server.&#xA;In the past I ran RaspberryPi OS (neé Raspbian) and it wasn&amp;rsquo;t very useful, besides being super slow and sluggish.&lt;/p&gt;&#xA;&lt;ol start=&#34;5&#34;&gt;&#xA;&lt;li&gt;Software updates must happen with a single command&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;There is no need for auto-updates (these are often not well-supported anyways), but a human operator should be able to upgrade&#xA;&lt;em&gt;everything&lt;/em&gt; in a single shot. It doesn&amp;rsquo;t have to be a single command (e.g. &lt;code&gt;apt update&lt;/code&gt; + &lt;code&gt;apt upgrade&lt;/code&gt; is acceptable), but it&#xA;should be contained within a short script.&lt;/p&gt;&#xA;&lt;ol start=&#34;6&#34;&gt;&#xA;&lt;li&gt;It should be reasonably popular and well-supported&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;No obscure distributions.&#xA;I don&amp;rsquo;t particularly care about a sizeable community (I won&amp;rsquo;t join their Discord server nor Reddit community anyway),&#xA;but there should be at least one official support channel, and it would be preferred that it is old-school (BBS / Discourse / forums, mailing lists, IRC / Matrix).&#xA;Stack Exchange is also acceptable to an extent.&#xA;The problem with the modern stuff (Discord, Reddit) is that it is too proprietary, can / will disappear at any moment, and will be heavily used to train LLMs with no scrutiny.&#xA;Commercial support is fine. For example, Red Hat backing Fedora, Canonical backing Ubuntu, and SUSE backing openSUSE is a non-issue.&#xA;I would just avoid commercial enterprises that suffocate their open counterparts.&lt;/p&gt;&#xA;&lt;ol start=&#34;7&#34;&gt;&#xA;&lt;li&gt;It should support Raspberry Pi (the ARM architecture, for that matter) as a first-class citizen&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;If Raspberry Pi support is considered experimental, I would avoid the trouble at this time.&lt;/p&gt;&#xA;&lt;p&gt;Stopping briefly here for a moment, the following list comes to mind, using &lt;a href=&#34;https://distrowatch.com/&#34;&gt;DistroWatch&lt;/a&gt; and &lt;a href=&#34;https://en.m.wikipedia.org/wiki/File:Linux_Distribution_Timeline.svg&#34;&gt;Linux Distribution timeline&lt;/a&gt; as an aid:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Debian&#xA;— RaspberryPi OS&lt;/li&gt;&#xA;&lt;li&gt;Ubuntu&lt;/li&gt;&#xA;&lt;li&gt;Fedora&lt;/li&gt;&#xA;&lt;li&gt;openSUSE&lt;/li&gt;&#xA;&lt;li&gt;Alpine Linux&lt;/li&gt;&#xA;&lt;li&gt;Arch Linux (Arch Linux ARM)&lt;/li&gt;&#xA;&lt;li&gt;Void Linux&lt;/li&gt;&#xA;&lt;li&gt;Gentoo&lt;/li&gt;&#xA;&lt;li&gt;NixOS&lt;/li&gt;&#xA;&lt;li&gt;FreeBSD&lt;/li&gt;&#xA;&lt;li&gt;OpenBSD&lt;/li&gt;&#xA;&lt;li&gt;&lt;del&gt;Slackware Linux&lt;/del&gt; (Updated on 2025-09-11)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;This list is still quite large.&#xA;I will need to trim it down further in the following days. Stay tuned.&lt;/p&gt;&#xA;&lt;p&gt;Some observations before I make a final decision:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I have two units, so one decision to make is whether to choose the same distribution for both or distinct distributions for each. One Linux and one BSD, for example.&lt;/li&gt;&#xA;&lt;li&gt;I never used the following distros: Void Linux, NixOS, OpenBSD, Slackware Linux. There&amp;rsquo;s always an appeal to trying out something new, even if just ephemerally.&lt;/li&gt;&#xA;&lt;li&gt;I am heavily experienced in Debian / Ubuntu, Arch and Alpine (with a tad of Gentoo as well). There&amp;rsquo;s an appeal to using something I am already familiar with to get the OS out of the way, and thus focus more on DevOps.&lt;/li&gt;&#xA;&lt;li&gt;NixOS is very tempting for the purposes of reproducibility, but every time I look at it I feel lost in its sea of complexity. And it oftentimes feels bloated. Reproducibility comes with a non-trivial upfront cost.&lt;/li&gt;&#xA;&lt;li&gt;OpenBSD feels very tempting as a self-contained, &amp;ldquo;do one thing and do it well&amp;rdquo;, KISS &amp;amp; secure system. It lacks on integration with third-party software, but perhaps that&amp;rsquo;s a feature.&lt;/li&gt;&#xA;&lt;li&gt;Let&amp;rsquo;s be honest, as much as Slackware has its charm, realistically I am not choosing it. Its ecosystem is too small today. It doesn&amp;rsquo;t provide any value when compared to the rest of the list. Between Slack and Alpine, I&amp;rsquo;d easily pick Alpine with no effort.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Hence we can already eliminate one: &lt;del&gt;Slackware Linux&lt;/del&gt;.&lt;/p&gt;&#xA;&lt;p&gt;To be continued ■&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;Two units&lt;/em&gt; is a safer choice than &amp;ldquo;raspberry pies&amp;rdquo; or &amp;ldquo;raspberry pis&amp;rdquo;.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2024/10/new-series-raspberry-pi-fun-with-devops/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: New series: Raspberry Pi fun with DevOps&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>New APKBUILD: argocd
      </title>
      <link>https://perrotta.dev/2024/10/new-apkbuild-argocd/</link>
      <pubDate>Wed, 09 Oct 2024 23:03:32 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <category>kubernetes</category>
      <guid>https://perrotta.dev/2024/10/new-apkbuild-argocd/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://argo-cd.readthedocs.io/en/stable/&#34;&gt;ArgoCD&lt;/a&gt; is a widely used GitOps&#xA;software for Kubernetes Continuous Delivery (see&#xA;&lt;a href=&#34;https://github.com/argoproj/argo-cd/blob/master/USERS.md&#34;&gt;USERS.md&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;p&gt;I am quite surprised no one bothered to create an Alpine Linux package for it.&lt;/p&gt;&#xA;&lt;p&gt;Until&amp;hellip;&lt;a href=&#34;https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/73305&#34;&gt;now&lt;/a&gt;,&#xA;by yours truly.&lt;/p&gt;&#xA;&lt;p&gt;This &lt;code&gt;APKBUILD&lt;/code&gt; took a bit longer to create than the usual.&#xA;There were a couple of issues with &lt;code&gt;-buildmode=pie&lt;/code&gt;, addressed with&#xA;&lt;code&gt;export CGO_ENABLED=1&lt;/code&gt; (via &lt;code&gt;make CGO_FLAG=1&lt;/code&gt;).&lt;/p&gt;&#xA;&lt;p&gt;Also, not every architecture is compatible with it. The following error message&#xA;appears in ARM builds:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;cannot use math.MaxInt64 (untyped int constant 9223372036854775807) as int value in argument to env.ParseNumFromEnv (overflows)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Anyway, once it is merged upstream, enjoy!&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doas apk add argocd&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: New APKBUILD: argocd&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/kubernetes/&#34;&gt;#kubernetes&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>End of life package info
      </title>
      <link>https://perrotta.dev/2024/09/end-of-life-package-info/</link>
      <pubDate>Fri, 06 Sep 2024 17:41:30 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2024/09/end-of-life-package-info/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://endoflife.date/&#34;&gt;https://endoflife.date/&lt;/a&gt; is like &lt;a href=&#34;https://repology.org/&#34;&gt;Repology&lt;/a&gt; but, instead&#xA;of package versions, it tracks the &lt;em&gt;end of life&lt;/em&gt; of various packages, linux&#xA;distributions, frameworks, etc.&lt;/p&gt;&#xA;&lt;p&gt;For example, Alpine Linux: &lt;a href=&#34;https://endoflife.date/alpine&#34;&gt;https://endoflife.date/alpine&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: End of life package info&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>apk autoupdate on alpine linux
      </title>
      <link>https://perrotta.dev/2024/08/apk-autoupdate-on-alpine-linux/</link>
      <pubDate>Sun, 18 Aug 2024 17:32:48 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2024/08/apk-autoupdate-on-alpine-linux/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Problem statement&lt;/strong&gt;: Upon running &lt;code&gt;doas apk upgrade&lt;/code&gt; on Alpine Linux, select&#xA;packages with binaries backed by system services should be automatically&#xA;restarted.&lt;/p&gt;&#xA;&lt;p&gt;Deb-based systems have&#xA;&lt;a href=&#34;https://manpages.debian.org/bookworm/debian-goodies/checkrestart.8.en.html&#34;&gt;&lt;code&gt;checkrestart(8)&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;On Alpine, the best available approach as of today is &lt;a href=&#34;https://github.com/jirutka/apk-autoupdate/&#34;&gt;https://github.com/jirutka/apk-autoupdate/&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;doas apk add apk-autoupdate&#xA;$EDITOR /etc/apk/autoupdate.conf&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Then make the two following changes&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2024/08/apk-autoupdate-on-alpine-linux/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;# Because the default is &amp;#39;*&amp;#39;, which will prevent all services from restarting.&#xA;services_blacklist=&amp;#34;&amp;#34;&#xA;&#xA;# List of services that should be restarted upon package upgrades.&#xA;services_whitelist=&amp;#34;miniflux tailscale&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;From this point on, whenever there are system upgrades for the aforementioned&#xA;services (&lt;code&gt;doas apk upgrade&lt;/code&gt;), they will be automatically restarted. There&amp;rsquo;s no&#xA;need for &lt;code&gt;doas /etc/init.d/miniflux restart&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;h/t to @fossdd for replying to my&#xA;&lt;a href=&#34;https://github.com/jirutka/apk-autoupdate/issues/8&#34;&gt;https://github.com/jirutka/apk-autoupdate/issues/8&lt;/a&gt; thread.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2024/08/apk-autoupdate-on-alpine-linux/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: apk autoupdate on alpine linux&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Docker on Alpine Linux
      </title>
      <link>https://perrotta.dev/2024/07/docker-on-alpine-linux/</link>
      <pubDate>Tue, 30 Jul 2024 22:56:58 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <category>docker</category>
      <guid>https://perrotta.dev/2024/07/docker-on-alpine-linux/</guid>
      <description>&lt;p&gt;♠ In this post: how to properly start &lt;code&gt;docker&lt;/code&gt; on an Alpine Linux server.&lt;/p&gt;&#xA;&lt;p&gt;First, install &lt;code&gt;docker&lt;/code&gt; and friends:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;doas apk add docker docker-cli docker-compose&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Then start the &lt;code&gt;docker&lt;/code&gt; service:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;doas service docker start&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Check if it started successfully:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;service docker status&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;If not, then look at the logs:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;less /var/log/docker.log&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I got an error:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;failed to start daemon: error initializing graphdriver: driver not supported&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The suggestion was to change the driver to &lt;code&gt;overlay2&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% $EDITOR /etc/docker/daemon.json&#xA;{&#xA;  &amp;#34;storage-driver&amp;#34;: &amp;#34;overlay2&amp;#34;&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Then restart &lt;code&gt;docker&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I got another error:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;level=error msg=&amp;#34;failed to mount overlay: no such device&amp;#34; storage-driver=overlay2&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The suggestion was to reboot:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;doas reboot&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Then start &lt;code&gt;docker&lt;/code&gt; again:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;doas service docker start&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;And now everything works!&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Docker on Alpine Linux&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/docker/&#34;&gt;#docker&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Alpine Linux: How to install all manpages (idiomatically)
      </title>
      <link>https://perrotta.dev/2024/07/alpine-linux-how-to-install-all-manpages-idiomatically/</link>
      <pubDate>Sat, 20 Jul 2024 16:19:53 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2024/07/alpine-linux-how-to-install-all-manpages-idiomatically/</guid>
      <description>&lt;p&gt;♠ This post is a reply to&#xA;&lt;a href=&#34;https://tilde.town/~kzimmermann/articles/installing_alpine_manpages.html&#34;&gt;https://tilde.town/~kzimmermann/articles/installing_alpine_manpages.html&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The author describes their experience while attempting to install all man pages&#xA;for all the packages in use in their system.&lt;/p&gt;&#xA;&lt;p&gt;The breakdown progression has some valuable insights on how a typical Unix&#xA;sysadmin addresses a problem. I tend to adopt a similar approach when entering&#xA;unknown territory.&lt;/p&gt;&#xA;&lt;p&gt;However, in Alpine Linux, there is a better way.&lt;/p&gt;&#xA;&lt;h2 id=&#34;solution&#34;&gt;&#xA;  Solution&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2024/07/alpine-linux-how-to-install-all-manpages-idiomatically/#solution&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;There is a &lt;code&gt;docs&lt;/code&gt; metapackage:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% apk info docs&#xA;docs-0.2-r6 description:&#xA;Meta package for pulling in all documentation&#xA;&#xA;docs-0.2-r6 webpage:&#xA;https://alpinelinux.org&#xA;&#xA;docs-0.2-r6 installed size:&#xA;4096 B&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;All you have to do is:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doas apk add docs&#xA;(1/125) Installing mandoc-doc (1.14.6-r13)&#xA;(2/125) Installing docs (0.2-r6)&#xA;(3/125) Installing libseccomp-doc (2.5.5-r1)&#xA;(4/125) Installing busybox-doc (1.36.1-r31)&#xA;[...]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Likewise, it is trivial to get rid of all man pages:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doas apk del docs&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I would like to give a few other suggestions to the author, if we were to assume&#xA;there is no &lt;code&gt;docs&lt;/code&gt; metapackage:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;: You could also &lt;code&gt;cat /etc/apk/world&lt;/code&gt;&#xA;(&lt;a href=&#34;https://serverfault.com/questions/1032488/alpine-linux-apk-list-out-directly-installed-packages-by-apk-add&#34;&gt;reference&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;: &lt;code&gt;combine&lt;/code&gt; from &lt;a href=&#34;https://perrotta.dev/2022/05/tools-you-should-know-about-moreutils/&#34;&gt;moreutils&lt;/a&gt; is more&#xA;user-friendly than &lt;code&gt;comm&lt;/code&gt;. I need to look up how to use &lt;code&gt;comm&lt;/code&gt; every single&#xA;time, whereas &lt;code&gt;combine&lt;/code&gt; is much easier to remember.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;appendix&#34;&gt;&#xA;  Appendix&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2024/07/alpine-linux-how-to-install-all-manpages-idiomatically/#appendix&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;This was also a typical &lt;a href=&#34;https://perrotta.dev/2024/06/xy-problem-xyproblem/&#34;&gt;xyproblem&lt;/a&gt; example:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;What is the attempt? &amp;ldquo;I want to install, via &lt;code&gt;apk add&lt;/code&gt;, all &lt;code&gt;foo-doc&lt;/code&gt; packages&#xA;for every &lt;code&gt;foo&lt;/code&gt; package on my system&amp;rdquo;.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;What is the end goal? &amp;ldquo;I want to install all man pages for the installed&#xA;packages on my system&amp;rdquo;.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine Linux: How to install all manpages (idiomatically)&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ Terraforming a Linode: hello world
      </title>
      <link>https://perrotta.dev/2024/01/terraforming-a-linode-hello-world/</link>
      <pubDate>Tue, 23 Jan 2024 23:27:04 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>bestof</category>
      <category>dev</category>
      <category>terraform</category>
      <guid>https://perrotta.dev/2024/01/terraforming-a-linode-hello-world/</guid>
      <description>&lt;p&gt;♠ I host my own &lt;a href=&#34;https://miniflux.app/&#34;&gt;Miniflux&lt;/a&gt; instance, which happens to be&#xA;my favorite RSS reader. Currently it is hosted on Linode (Akamai Cloud)&#xA;running &lt;a href=&#34;https://www.alpinelinux.org/&#34;&gt;Alpine Linux&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;My current setup was performed manually. I was thinking that, for fun, it would&#xA;be cool to fully automate it under the principles of&#xA;&lt;a href=&#34;https://en.wikipedia.org/wiki/Infrastructure_as_code&#34;&gt;IaC&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The current setup does not use any containers. I had proudly made it as KISS as&#xA;possible at the time:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Linode is a very beginner-friendly (and cheap) VPS&lt;/li&gt;&#xA;&lt;li&gt;Alpine Linux is a first-class citizen on Linode&lt;/li&gt;&#xA;&lt;li&gt;There&amp;rsquo;s an &lt;code&gt;apk&lt;/code&gt; &lt;a href=&#34;https://pkgs.alpinelinux.org/packages?name=miniflux&#34;&gt;package&lt;/a&gt; for &lt;code&gt;miniflux&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;There&amp;rsquo;s an OpenRC&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2024/01/terraforming-a-linode-hello-world/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; script for &lt;code&gt;miniflux&lt;/code&gt; (so that it can be controlled via &lt;code&gt;service&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;For the first part of this automation we will look into provisioning a Linode&#xA;with an Alpine Linux installation. In order to do so we will use HashiCorp&#xA;&lt;a href=&#34;https://www.terraform.io/&#34;&gt;Terraform&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;requirements&#34;&gt;&#xA;  Requirements&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2024/01/terraforming-a-linode-hello-world/#requirements&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Provision a new Linode&lt;/li&gt;&#xA;&lt;li&gt;Deploy it in Europe&lt;/li&gt;&#xA;&lt;li&gt;Use the smallest shape (a so-called &lt;a href=&#34;https://www.linode.com/community/questions/211/what-is-a-nanode&#34;&gt;Nanode&lt;/a&gt;)&lt;/li&gt;&#xA;&lt;li&gt;Run Alpine Linux&lt;/li&gt;&#xA;&lt;li&gt;Set it up with my &lt;a href=&#34;https://github.com/thiagowfx.keys&#34;&gt;public ssh key&lt;/a&gt;, which is hosted on Github&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;terraform-setup&#34;&gt;&#xA;  Terraform setup&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2024/01/terraforming-a-linode-hello-world/#terraform-setup&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Install a provider for Linode: &lt;a href=&#34;https://registry.terraform.io/providers/linode/linode/latest/docs&#34;&gt;https://registry.terraform.io/providers/linode/linode/latest/docs&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Scaffold it like this, in a &lt;code&gt;main.tf&lt;/code&gt; file:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-terraform&#34;&gt;terraform {&#xA;  required_providers {&#xA;    linode = {&#xA;      source = &amp;#34;linode/linode&amp;#34;&#xA;    }&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Then run:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% terraform init&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Generate a Linode API token&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Go to &lt;a href=&#34;https://cloud.linode.com/profile/tokens&#34;&gt;https://cloud.linode.com/profile/tokens&lt;/a&gt;, create a new token called&#xA;&lt;code&gt;terraform&lt;/code&gt;. with the &amp;ldquo;Linodes&amp;rdquo; scope set to &amp;ldquo;Read/Write&amp;rdquo;.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Append this API token to &lt;code&gt;main.tf&lt;/code&gt;:&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-terraform&#34;&gt;provider &amp;#34;linode&amp;#34; {&#xA;  token = &amp;#34;&amp;lt;your token here&amp;gt;&amp;#34;&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Add a&#xA;&lt;a href=&#34;https://registry.terraform.io/providers/linode/linode/latest/docs/resources/instance&#34;&gt;&lt;code&gt;linode_instance&lt;/code&gt;&lt;/a&gt;&#xA;with the appropriate fields set according to the documentation:&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-terraform&#34;&gt;resource &amp;#34;linode_instance&amp;#34; &amp;#34;coruscant&amp;#34; {&#xA;  label  = &amp;#34;coruscant&amp;#34;&#xA;  image  = &amp;#34;linode/alpine3.19&amp;#34;&#xA;  region = &amp;#34;eu-central&amp;#34;&#xA;  type   = &amp;#34;g6-nanode-1&amp;#34;&#xA;  authorized_keys  = [&amp;#34;&amp;lt;your ssh public key here&amp;gt;&amp;#34;]&#xA;  backups_enabled  = &amp;#34;false&amp;#34;&#xA;  watchdog_enabled = &amp;#34;true&amp;#34;&#xA;  booted           = &amp;#34;true&amp;#34;&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Then run:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% terraform plan&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&amp;ldquo;Plan&amp;rdquo; is basically a dry-run. Terraform will output what it intends to do, but nothing will be done yet.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Analyze the output and double check that it looks correct.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;To actually perform the provisioning, run:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% terraform apply&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Then confirm the prompt.&lt;/p&gt;&#xA;&lt;p&gt;Within a few seconds (or maybe minutes), you should see your new Linode in the&#xA;&lt;a href=&#34;https://cloud.linode.com/&#34;&gt;Linode Console&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;We can test our deployment by ssh&amp;rsquo;ing to our new machine:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% ssh root@&amp;lt;public IP address&amp;gt; -i ~/.ssh/my_ssh_key&#xA;Welcome to Alpine!&#xA;&#xA;The Alpine Wiki contains a large amount of how-to guides and general&#xA;information about administrating Alpine systems.&#xA;See &amp;lt;https://wiki.alpinelinux.org/&amp;gt;.&#xA;&#xA;You can setup the system with the command: setup-alpine&#xA;&#xA;You may change this message by editing /etc/motd.&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s take a pause to appreciate how lightweight it is:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;localhost:~# df -h&#xA;Filesystem                Size      Used Available Use% Mounted on&#xA;devtmpfs                 10.0M         0     10.0M   0% /dev&#xA;shm                     487.8M         0    487.8M   0% /dev/shm&#xA;/dev/sda                 24.1G    238.1M     22.6G   1% /&#xA;tmpfs                   195.1M    268.0K    194.8M   0% /run&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Only 238 MiB!&lt;/p&gt;&#xA;&lt;p&gt;To deprovision it, run:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% terraform plan -destroy&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;If everything looks correct, run:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% terraform destroy&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: It turns out the &amp;ldquo;Linodes&amp;rdquo; scope was not enough to do the&#xA;deprovisioning. I needed to create a new scope, with more permissions, in order&#xA;to do so.&lt;/p&gt;&#xA;&lt;p&gt;As you can see, terraform makes it very trivial to deprovision systems.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Bonus points&lt;/strong&gt;: run &lt;code&gt;terraform fmt&lt;/code&gt; to format your file. Never go &lt;a href=&#34;https://www.youtube.com/watch?v=-CmadmM5cOk&#34;&gt;out of&#xA;style&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: At any point you can run &lt;code&gt;terraform validate&lt;/code&gt; to verify your &lt;code&gt;main.tf&lt;/code&gt;&#xA;file is syntactically correct.&lt;/p&gt;&#xA;&lt;p&gt;Two things could be improved in the previous setup:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;We could use &lt;code&gt;authorized_users&lt;/code&gt; to pass in our linode username. If we add an&#xA;SSH key to our linode account, then that key would be automatically deployed&#xA;to the system, thereby removing the need to specify &lt;code&gt;authorized_keys&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Alternatively, we could fetch our key from an URL endpoint with the use of&#xA;the &lt;code&gt;hashicorp/http&lt;/code&gt; provider, like so:&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-terraform&#34;&gt;terraform {&#xA;  required_providers {&#xA;    http = {&#xA;      source = &amp;#34;hashicorp/http&amp;#34;&#xA;    }&#xA;  }&#xA;}&#xA;&#xA;data &amp;#34;http&amp;#34; &amp;#34;thiagowfx_ssh_keys&amp;#34; {&#xA;  url = &amp;#34;https://github.com/thiagowfx.keys&amp;#34;&#xA;}&#xA;&#xA;resource &amp;#34;linode_instance&amp;#34; &amp;#34;coruscant&amp;#34; {&#xA;  # ...&#xA;  authorized_keys  = compact([for line in split(&amp;#34;\n&amp;#34;, data.http.thiagowfx_ssh_keys.response_body) : chomp(line)])&#xA;  # ...&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The &amp;ldquo;list comprehension&amp;rdquo; above does line splitting magic to convert them to a&#xA;list of string, and the &lt;code&gt;compact&lt;/code&gt; removes the empty new line at the end.&lt;/p&gt;&#xA;&lt;p&gt;We could improve the example above even further.&lt;/p&gt;&#xA;&lt;p&gt;For starters, let&amp;rsquo;s parameterize out the username to a variable:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-terraform&#34;&gt;variable &amp;#34;github_username&amp;#34; {&#xA;  type    = string&#xA;  default = &amp;#34;thiagowfx&amp;#34;&#xA;}&#xA;&#xA;data &amp;#34;http&amp;#34; &amp;#34;user_ssh_keys&amp;#34; {&#xA;  url = &amp;#34;https://github.com/${var.github_username}.keys&amp;#34;&#xA;}&#xA;&#xA;resource &amp;#34;linode_instance&amp;#34; &amp;#34;coruscant&amp;#34; {&#xA;  # ...&#xA;  authorized_keys  = compact([for line in split(&amp;#34;\n&amp;#34;, data.http.user_ssh_keys.response_body) : chomp(line)])&#xA;  # ...&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;We could then easily supply another username with &lt;code&gt;-var&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% terraform plan -var github_username=torvalds&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Note that the above example leverages &lt;a href=&#34;https://developer.hashicorp.com/terraform/language/expressions/strings&#34;&gt;string interpolation&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;We could also extract the SSH keys list to its own &amp;ldquo;variable&amp;rdquo; (&lt;a href=&#34;https://developer.hashicorp.com/terraform/language/values/locals&#34;&gt;locals&lt;/a&gt;):&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-terraform&#34;&gt;locals {&#xA;  ssh_keys = compact([for line in split(&amp;#34;\n&amp;#34;, data.http.user_ssh_keys.response_body) : chomp(line)])&#xA;}&#xA;&#xA;resource &amp;#34;linode_instance&amp;#34; &amp;#34;coruscant&amp;#34; {&#xA;  # ...&#xA;  authorized_keys  = local.ssh_keys&#xA;  # ...&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;A more robust (and stable) way to query the key though is through the Github API:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-terraform&#34;&gt;data &amp;#34;http&amp;#34; &amp;#34;github_keys&amp;#34; {&#xA;  url = &amp;#34;https://api.github.com/users/${var.github_username}/keys&amp;#34;&#xA;}&#xA;&#xA;locals {&#xA;  ssh_keys = jsondecode(data.http.github_keys.response_body)[*].key&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Note that a typical response body looks like the following:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;[&#xA;  {&#xA;    &amp;#34;id&amp;#34;: &amp;#34;&amp;lt;id&amp;gt;&amp;#34;,&#xA;    &amp;#34;key&amp;#34;: &amp;#34;&amp;lt;ssh key&amp;gt;&amp;#34;&#xA;  }&#xA;]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;API endpoint documentation:&#xA;&lt;a href=&#34;https://docs.github.com/en/rest/users/keys?apiVersion=2022-11-28#list-public-keys-for-a-user&#34;&gt;https://docs.github.com/en/rest/users/keys?apiVersion=2022-11-28#list-public-keys-for-a-user&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;If we use &lt;code&gt;output&lt;/code&gt; instead of &lt;code&gt;locals&lt;/code&gt;, then we can debug (inspect) it with&#xA;&lt;code&gt;terraform output&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;And that&amp;rsquo;s it for today! In a future post, we will continue from here by using&#xA;&lt;a href=&#34;https://www.ansible.com/&#34;&gt;Ansible&lt;/a&gt; to install and set up Miniflux in our new&#xA;Linode.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Alpine Linux does not use &lt;code&gt;systemd&lt;/code&gt;.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2024/01/terraforming-a-linode-hello-world/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Terraforming a Linode: hello world&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/terraform/&#34;&gt;#terraform&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ Alpine Linux: New APKBUILD Workflow
      </title>
      <link>https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/</link>
      <pubDate>Sun, 23 Jan 2022 19:15:02 -0500</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>bestof</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/</guid>
      <description>&lt;p&gt;♠ This document describes my workflow to manage &lt;code&gt;APKBUILDs&lt;/code&gt; for the&#xA;&lt;a href=&#34;https://gitlab.alpinelinux.org/alpine/aports&#34;&gt;aports&lt;/a&gt; repository in &lt;a href=&#34;https://alpinelinux.org/&#34;&gt;Alpine Linux&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;disclaimer&#34;&gt;&#xA;  Disclaimer&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/#disclaimer&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;First of all, this post is not a substitute to the &lt;a href=&#34;https://wiki.alpinelinux.org/wiki/Main_Page&#34;&gt;AlpineWiki&lt;/a&gt;&#xA;and it will likely get outdated at some point. In particular, refer to the&#xA;following articles for up-to-date documentation that will outlive this blog:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://wiki.alpinelinux.org/wiki/APKBUILD_Reference&#34;&gt;https://wiki.alpinelinux.org/wiki/APKBUILD_Reference&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://wiki.alpinelinux.org/wiki/Abuild_and_Helpers&#34;&gt;https://wiki.alpinelinux.org/wiki/Abuild_and_Helpers&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://wiki.alpinelinux.org/wiki/Aports_tree&#34;&gt;https://wiki.alpinelinux.org/wiki/Aports_tree&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package&#34;&gt;https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;This article is not a tutorial, as such it assumes you already know what an&#xA;&lt;code&gt;APKBUILD&lt;/code&gt; is and how to use &lt;code&gt;abuild&lt;/code&gt;. In particular, you should have the&#xA;&lt;code&gt;alpine-sdk&lt;/code&gt;, &lt;code&gt;atools&lt;/code&gt; and &lt;code&gt;spdx-licenses-list&lt;/code&gt; packages installed in your&#xA;system.&lt;/p&gt;&#xA;&lt;h2 id=&#34;structure&#34;&gt;&#xA;  Structure&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/#structure&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I manage my packages with &lt;code&gt;git&lt;/code&gt;. Create a GitLab account on&#xA;&lt;a href=&#34;https://gitlab.alpinelinux.org/&#34;&gt;https://gitlab.alpinelinux.org/&lt;/a&gt;, fork the &lt;a href=&#34;https://gitlab.alpinelinux.org/alpine/aports&#34;&gt;&lt;code&gt;aports&lt;/code&gt;&lt;/a&gt; tree, and &lt;code&gt;git clone&lt;/code&gt; your fork.&lt;/p&gt;&#xA;&lt;p&gt;The structure follows Alpine Linux repositories:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;$ git clone https://gitlab.alpinelinux.org/alpine/aports.git &amp;amp;&amp;amp; tree -L 1 aports&#xA;aports&#xA;├── CODINGSTYLE.md&#xA;├── COMMITSTYLE.md&#xA;├── README.md&#xA;├── community&#xA;├── main&#xA;├── non-free&#xA;├── scripts&#xA;├── testing&#xA;└── unmaintained&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;bootstrapping&#34;&gt;&#xA;  Bootstrapping&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/#bootstrapping&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I am going to illustrate with a package I added recently, &lt;a href=&#34;https://packages.debian.org/source/sensible-utils&#34;&gt;&lt;code&gt;sensible-utils&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Before you even begin, check if the package already exists, do a quick search in the &lt;a href=&#34;https://pkgs.alpinelinux.org/packages?name=sensible%2Dutils&#34;&gt;Alpine Repositories&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Start by scaffolding a new &lt;code&gt;APKBUILD&lt;/code&gt; from the base template:&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ cd aports/testing  # Always add new packages in testing/ first.&#xA;$ newapkbuild sensible-utils&#xA;$ cd sensible-utils&#xA;$ $EDITOR APKBUILD&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: If you have a language-specific package (e.g. perl, python, rust),&#xA;use the language-specific template instead of the base one. Run &lt;code&gt;newapkbuild -h&lt;/code&gt; to list available templates. There are also some &lt;code&gt;apkbuild-*&lt;/code&gt; helpers such&#xA;as &lt;code&gt;apkbuild-pypi&lt;/code&gt; and &lt;code&gt;apkbuild-cpan&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Fill in &lt;code&gt;APKBUILD&lt;/code&gt; metadata like &lt;code&gt;pkgname=&lt;/code&gt;, &lt;code&gt;url=&lt;/code&gt;, etc. Refer to the AlpineWiki for up-to-date best practices.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;By doing so, I produced the following &lt;code&gt;APKBUILD&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;pkgname=sensible-utils&#xA;pkgver=0.0.14&#xA;pkgrel=0&#xA;pkgdesc=&amp;#34;Utilities for sensible alternative selection&amp;#34;&#xA;url=&amp;#34;https://packages.debian.org/source/sensible-utils&amp;#34;&#xA;arch=&amp;#34;all&amp;#34;&#xA;license=&amp;#34;GPL-2.0-or-later&amp;#34;&#xA;makedepends=&amp;#34;po4a&amp;#34;&#xA;subpackages=&amp;#34;$pkgname-doc&amp;#34;&#xA;source=&amp;#34;http://ftp.debian.org/debian/pool/main/s/$pkgname/${pkgname}_$pkgver.tar.xz&amp;#34;&#xA;builddir=&amp;#34;$srcdir/$pkgname.git&amp;#34;&#xA;&#xA;build() {&#xA;&#x9;./configure --prefix=/usr&#xA;&#x9;make&#xA;}&#xA;&#xA;check() {&#xA;&#x9;make -k check&#xA;}&#xA;&#xA;package() {&#xA;&#x9;make DESTDIR=&amp;#34;$pkgdir/&amp;#34; install&#xA;&#xA;&#x9;# only works with update-alternatives, specific to debian&#xA;&#x9;rm &amp;#34;$pkgdir/usr/bin/select-editor&amp;#34;&#xA;}&#xA;&#xA;sha512sums=&amp;#34;&#xA;15ba996f811ab3a9c1f5726f35766d74aafdf925c5c2392b33c6643d6c439796a742f9d0f4625c79de640e6b5e4a6a032b768eb1bc4ac31b448f9767b0ceed44  sensible-utils_0.0.14.tar.xz&#xA;&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: &lt;code&gt;$srcdir&lt;/code&gt; refers to the &lt;code&gt;src/&lt;/code&gt; directory within &lt;code&gt;sensible-utils&lt;/code&gt;. &lt;code&gt;$pkgdir&lt;/code&gt;&#xA;refers to the &lt;code&gt;pkg/&lt;/code&gt; directory within &lt;code&gt;sensible-utils&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;If you&amp;rsquo;re used to Arch Linux &lt;code&gt;PKGBUILDs&lt;/code&gt; you&amp;rsquo;ll notice a striking similarity to&#xA;&lt;code&gt;APKBUILDs&lt;/code&gt;. I highlighted a few notable differences in a previous post, &lt;a href=&#34;https://perrotta.dev/2022/01/my-first-apkbuild/#build-comparison&#34;&gt;&lt;code&gt;My First APKBUILD&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;adjustments&#34;&gt;&#xA;  Adjustments&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/#adjustments&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Generate the checksums with &lt;code&gt;abuild checksum&lt;/code&gt;. It will automatically update the &lt;code&gt;APKBUILD&lt;/code&gt; inplace.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Download and extract package files with &lt;code&gt;abuild unpack&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;ls src/&lt;/code&gt; and check the directory structure. Update &lt;code&gt;$builddir&lt;/code&gt; in your&#xA;&lt;code&gt;APKBUILD&lt;/code&gt; to match it. Usually it will be &lt;code&gt;$srcdir/$pkgname-$pkgver&lt;/code&gt;, but&#xA;sometimes tiny adjustments are necessary. In this case, it was&#xA;&lt;code&gt;$srcdir/$pkgname.git&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Then run &lt;code&gt;abuild -r&lt;/code&gt;. If everything goes well, your package (and subpackages,&#xA;if any) will be successfully built&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; in an isolated environment and placed&#xA;in &lt;code&gt;~/packages&lt;/code&gt; (&lt;code&gt;sensible-utils-0.0.14-r0.apk&lt;/code&gt; and&#xA;&lt;code&gt;sensible-utils-doc-0.0.14-r0.apk&lt;/code&gt;), however that doesn&amp;rsquo;t mean it is a decent&#xA;package yet.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Run &lt;code&gt;apkbuild-lint APKBUILD&lt;/code&gt; and &lt;code&gt;abuild validate&lt;/code&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; to lint your package&#xA;and catch common errors. Fix the errors, if any.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;request-feedback-if-needed&#34;&gt;&#xA;  Request feedback if needed&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/#request-feedback-if-needed&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;If the package is only relevant to you, stop here. &lt;code&gt;git commit&lt;/code&gt;, &lt;code&gt;git push&lt;/code&gt;, and then you&amp;rsquo;re done. Install the package with &lt;code&gt;doas apk add &amp;lt;pkg&amp;gt;&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Otherwise, if the package might be potentially useful to other Alpine users, you could consider uploading it to the &lt;a href=&#34;https://gitlab.alpinelinux.org/alpine/aports&#34;&gt;aports&lt;/a&gt; repository.&lt;/p&gt;&#xA;&lt;p&gt;Before you do so, stop for a moment and make an honest judgment whether this is a high quality package and whether you&amp;rsquo;re confident it is clean and polished enough, following the best practices documented in the Wiki. The answer doesn&amp;rsquo;t need to be positive, it&amp;rsquo;s perfectly OK to commit mistakes and everyone is a newbie at some point.&lt;/p&gt;&#xA;&lt;p&gt;If the answer is negative, or if you&amp;rsquo;re new to this process and would like some help, fear no more! There are at least two decent community resources wherein to ask for help:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://wiki.alpinelinux.org/wiki/Alpine_Linux:IRC&#34;&gt;&lt;code&gt;#alpine-devel&lt;/code&gt; on OFTC IRC&lt;/a&gt; Drew DeVault wrote a good &lt;a href=&#34;https://drewdevault.com/2021/11/24/A-philosophy-for-instant-messaging.html&#34;&gt;post&lt;/a&gt; about IRC etiquette.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://wiki.alpinelinux.org/wiki/Alpine_Linux:Mailing_lists&#34;&gt;&lt;code&gt;alpine-devel&lt;/code&gt; mailing list&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;If you&amp;rsquo;re part of any other community (e.g. Reddit, Discord) feel free to ask therein as well. Avoid posting everywhere though, pick one community, draft your post and then patiently wait.&lt;/p&gt;&#xA;&lt;h2 id=&#34;publish-your-package&#34;&gt;&#xA;  Publish your package&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/#publish-your-package&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;If all is well, it&amp;rsquo;s time to publish your &lt;code&gt;APKBUILD&lt;/code&gt;. Follow the up-to-date&#xA;steps at&#xA;&lt;a href=&#34;https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package#Code_review&#34;&gt;https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package#Code_review&lt;/a&gt;. There are basically two options:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Send a gitlab merge request (MR). This follows the standard git forge workflow&#xA;(GitHub / BitBucket / GitLab) wherein you fork the main repository, create&#xA;a branch in your own clone, push it and then initiate a pull request&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Alternatively, send an email with your patch to the &lt;code&gt;aports&lt;/code&gt; mailing list&#xA;with &lt;a href=&#34;https://git-send-email.io/&#34;&gt;&lt;code&gt;git send-email&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ git config sendemail.to &amp;#34;alpine-aports@lists.alpinelinux.org&amp;#34;&#xA;$ git send-email -1  # Implicitly uses --to=alpine-linux@lists.alpinelinux.org as set above&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: The second approach has a steep learning curve, however once you&#xA;figure it out it&amp;rsquo;s actually faster, simpler and more streamlined. Whenever&#xA;a new email is sent to the aports mailing list, a MR is automatically created&#xA;on GitLab.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: If you adopt the email workflow and need to send a follow-up to your&#xA;initial patch, do not use &lt;code&gt;--in-reply-to&lt;/code&gt;. Instead, create a new email thread.&#xA;This is needed because as of this post new GitLab MRs are only created when new&#xA;email threads are created. Replies to existing email threads do not update the&#xA;MR patch.&lt;/p&gt;&#xA;&lt;p&gt;And that&amp;rsquo;s all! Other useful tips:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Use &lt;a href=&#34;https://repology.org/&#34;&gt;repology&lt;/a&gt; to look for preexisting packages in other Linux (or even BSD) distributions, it&amp;rsquo;s very handy as a starting point if you have no idea how to package a given package. In particular, Arch Linux &lt;code&gt;PKGBUILDs&lt;/code&gt; are very similar to &lt;code&gt;APKBUILDs&lt;/code&gt;. Gentoo &lt;code&gt;EBUILDs&lt;/code&gt; and FreeBSD &lt;code&gt;Makefiles&lt;/code&gt; are also reasonable approximations.&lt;/li&gt;&#xA;&lt;li&gt;Use &lt;code&gt;abump&lt;/code&gt; to bump pkgver in &lt;code&gt;APKBUILD&lt;/code&gt; files if the package gets an update to a newer upstream release.&lt;/li&gt;&#xA;&lt;li&gt;Use &lt;code&gt;apkgrel&lt;/code&gt; to bump or reset the &lt;code&gt;pkgrel&lt;/code&gt; value of your &lt;code&gt;APKBUILD&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Use &lt;a href=&#34;https://perrotta.dev/2022/01/arch-linux-new-pkgbuild-workflow/#bonus-track-upstream&#34;&gt;&lt;code&gt;urlwatch&lt;/code&gt;&lt;/a&gt; to track upstream updates.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;If you use &lt;a href=&#34;https://duckduckgo.com/&#34;&gt;https://duckduckgo.com/&lt;/a&gt;, query for &lt;code&gt;!alpine sensible-utils&lt;/code&gt;.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;Package debugging is out of scope of this post.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;&lt;strong&gt;Update(2025-05-10)&lt;/strong&gt;: Previously: &lt;code&gt;abuild sanitycheck&lt;/code&gt;.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;In GitLab it&amp;rsquo;s called Merge Request (MR). The list of all aports MRs is &lt;a href=&#34;https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests&#34;&gt;available on GitLab&lt;/a&gt;.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-new-apkbuild-workflow/#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine Linux: New APKBUILD Workflow&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Alpine / Arch Linux: .apk-new and .pacnew files
      </title>
      <link>https://perrotta.dev/2022/01/alpine-/-arch-linux-.apk-new-and-.pacnew-files/</link>
      <pubDate>Tue, 18 Jan 2022 14:25:20 -0500</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2022/01/alpine-/-arch-linux-.apk-new-and-.pacnew-files/</guid>
      <description>&lt;p&gt;♠ As packages are upgraded over time, updated configs files under &lt;code&gt;/etc&lt;/code&gt; may&#xA;arise. Different package managers treat this issue differently.&lt;/p&gt;&#xA;&lt;h2 id=&#34;alpine-linux&#34;&gt;&#xA;  Alpine Linux&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-/-arch-linux-.apk-new-and-.pacnew-files/#alpine-linux&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;apk&lt;/code&gt; creates &lt;code&gt;.apk-new&lt;/code&gt; files, which can be located and merged by running&#xA;&lt;code&gt;doas update-conf&lt;/code&gt;. Here is a recent example:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% doas update-conf&#xA;--- //etc/securetty&#xA;&amp;#43;&amp;#43;&amp;#43; //etc/securetty.apk-new&#xA;@@ -12,3 &amp;#43;12,6 @@&#xA; tty11&#xA; hvc0&#xA; ttyS0&#xA;&amp;#43;ttyS1&#xA;&amp;#43;ttyAMA0&#xA;&amp;#43;ttyAMA1&#xA;New //etc/securetty available:&#xA;Quit, Next, Show diff, Edit new, Zap new, Use new (q/n/s/e/z/u) [s] u&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;arch-linux&#34;&gt;&#xA;  Arch Linux&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-/-arch-linux-.apk-new-and-.pacnew-files/#arch-linux&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;pacman&lt;/code&gt; creates &lt;code&gt;.pacnew&lt;/code&gt; files, which can be located and merged by running&#xA;&lt;code&gt;sudo pacdiff&lt;/code&gt;. Here is a recent example:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% sudo pacdiff&#xA;==&amp;gt; pacnew file found for /etc/sudoers&#xA;:: (V)iew, (S)kip, (R)emove pacnew, (O)verwrite with pacnew, (Q)uit: [v/s/r/o/q]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: The &lt;a href=&#34;https://aur.archlinux.org/packages/pacdiff-pacman-hook-git/&#34;&gt;&lt;code&gt;pacdiff-pacman-hook-git&lt;/code&gt;&lt;/a&gt; package helpfully adds&#xA;a pacman hook that automatically checks whether there are any due &lt;code&gt;.pacnew&lt;/code&gt;&#xA;files upon upgrading the system (&lt;code&gt;pacman -Syu&lt;/code&gt;), being a simple and effective&#xA;way to automate this maintenance task. It looks like this:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;: Running post-transaction hooks...&#xA;(1/5) Reloading system manager configuration...&#xA;(2/5) Creating temporary files...&#xA;(3/5) Arming ConditionNeedsUpdate...&#xA;(4/5) Registering Haskell modules...&#xA;(5/5) Reviewing .pacnew files...&#xA;&#xA;/etc/sudoers.pacnew ⟶   /etc/sudoers&#xA;────────────────────────────────────────────────────────────────────────────────&#xA;&#xA;────┐&#xA;76: │&#xA;────┘&#xA; ##&#xA; ## User privilege specification&#xA; ##&#xA;-root ALL=(ALL:ALL) ALL&#xA;&amp;#43;root ALL=(ALL) ALL&#xA;&#xA; ## Uncomment to allow members of group wheel to execute any command&#xA;-# %wheel ALL=(ALL:ALL) ALL&#xA;&amp;#43;# %wheel ALL=(ALL) ALL&#xA;&#xA; ## Same thing without a password&#xA;-# %wheel ALL=(ALL:ALL) NOPASSWD: ALL&#xA;&amp;#43;# %wheel ALL=(ALL) NOPASSWD: ALL&#xA;&#xA; ## Uncomment to allow members of group sudo to execute any command&#xA;-# %sudo    ALL=(ALL:ALL) ALL&#xA;&amp;#43;# %sudo    ALL=(ALL) ALL&#xA;&#xA; ## Uncomment to allow any user to run sudo if they know the password&#xA; ## of the user they are running the command as (root by default).&#xA; # Defaults targetpw  # Ask for the password of the target user&#xA;-# ALL ALL=(ALL:ALL) ALL  # WARNING: only use this together with &amp;#39;Defaults targetpw&amp;#39;&#xA;&amp;#43;# ALL ALL=(ALL) ALL  # WARNING: only use this together with &amp;#39;Defaults targetpw&amp;#39;&#xA;&#xA; ## Read drop-in files from /etc/sudoers.d&#xA; @includedir /etc/sudoers.d&#xA; :: Searching databases for updates...&#xA; :: Searching AUR for updates...&#xA;  there is nothing to do&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine / Arch Linux: .apk-new and .pacnew files&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ Alpine Linux on Raspberry Pi: Diskless Mode with persistent storage
      </title>
      <link>https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/</link>
      <pubDate>Sat, 15 Jan 2022 23:18:56 -0500</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>bestof</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/</guid>
      <description>&lt;p&gt;♠ Use case: Given an Alpine Linux &lt;strong&gt;diskless&lt;/strong&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; installation meant for&#xA;a Raspberry Pi setup, we would like to add a persistent storage component to it&#xA;to make it survive across reboots.&lt;/p&gt;&#xA;&lt;h2 id=&#34;goal&#34;&gt;&#xA;  Goal&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#goal&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://wiki.alpinelinux.org/wiki/Installation&#34;&gt;Alpine Linux Wiki&lt;/a&gt; covers most of the installation process, hence I will only document the bits that were lacking and/or confusing therein.&lt;/p&gt;&#xA;&lt;p&gt;My use case is the following:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Given a Raspberry Pi 3B with an old 4GiB SD Card as CF storage&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, install Alpine Linux in diskless mode. Find a way to preserve modifications in &lt;code&gt;/etc&lt;/code&gt; and &lt;code&gt;/var&lt;/code&gt;, as well as any installed packages through its &lt;code&gt;apk&lt;/code&gt; package manager.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s follow the steps outlined in the wiki.&lt;/p&gt;&#xA;&lt;h2 id=&#34;copy-alpine-to-the-sd-card&#34;&gt;&#xA;  Copy Alpine to the SD Card&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#copy-alpine-to-the-sd-card&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Grab the SD card and install Alpine Linux in it.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Alpine provides officially supported images designed for the Raspberry Pi.&lt;/p&gt;&#xA;&lt;p&gt;Most Linux distributions provide an &lt;code&gt;.iso&lt;/code&gt; or &lt;code&gt;.img&lt;/code&gt; file to be installed with a tool like &lt;a href=&#34;https://www.balena.io/etcher/&#34;&gt;Balena Etcher&lt;/a&gt;, &lt;a href=&#34;https://rufus.ie/en/&#34;&gt;Rufus&lt;/a&gt;, &lt;a href=&#34;https://www.raspberrypi.com/news/raspberry-pi-imager-imaging-utility/&#34;&gt;&lt;strong&gt;Raspberry Pi Imager&lt;/strong&gt;&lt;/a&gt; or plain &lt;a href=&#34;https://www.gnu.org/software/coreutils/manual/html_node/dd-invocation.html&#34;&gt;&lt;code&gt;dd&lt;/code&gt;&lt;/a&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Alpine is not like most Linux distributions: Instead, it provides a &lt;code&gt;.tar.gz&lt;/code&gt; archive with files that should be copied directly to the SD card. Grab the latest version (3.15 at the time of this post) from &lt;a href=&#34;https://alpinelinux.org/downloads/&#34;&gt;https://alpinelinux.org/downloads/&lt;/a&gt;. There are 3 options:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;armhf&lt;/code&gt;: Works with all Pis, but may perform less optimally on recent versions.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;armv7&lt;/code&gt;: Works with the Pi 3B, 32-bit.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;aarch64&lt;/code&gt;: Works with the Pi 3B, 64-bit.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I opted for &lt;code&gt;aarch64&lt;/code&gt; to make it 64-bit, but &lt;code&gt;armv7&lt;/code&gt; would also have worked well for my setup. In fact, Raspberry Pi OS (Debian) uses &lt;code&gt;armv7&lt;/code&gt; (32-bit) at the time of this writing.&lt;/p&gt;&#xA;&lt;p&gt;Before copying files over, format the SD Card. As I was doing this&#xA;from a Windows machine because it was the only one I had readily available with&#xA;a SD card slot, I just used the native Windows Disk Management tool to do so.&#xA;I decided to allocate a 100MB&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; FAT32 partition. The rest of the SD card would be&#xA;blank for now. Alpine is surprisingly small, 100MB was more than enough for the kernel and other needed files.&lt;/p&gt;&#xA;&lt;p&gt;Once the SD card is formatted, copy the files over to it. It turns out Windows cannot extract tarballs (&lt;code&gt;.tar.gz&lt;/code&gt;); a tool like &lt;a href=&#34;https://www.7-zip.org/&#34;&gt;7-zip&lt;/a&gt; should do the job. Copy the files over to the root of the newly allocated FAT32 partition, and then safely eject the SD card.&lt;/p&gt;&#xA;&lt;h2 id=&#34;boot-alpine-from-the-sd-card&#34;&gt;&#xA;  Boot Alpine from the SD Card&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#boot-alpine-from-the-sd-card&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The next step is to insert the SD Card into the Pi and then boot. I had some trouble in this step and eventually figured out I didn&amp;rsquo;t mark the primary FAT32 partition as bootable. Unfortunately it&amp;rsquo;s not straightforward to mark the partition as bootable from Windows. On a Linux machine there&amp;rsquo;s a wide array of tools to do so: &lt;code&gt;fdisk&lt;/code&gt;, &lt;code&gt;cfdisk&lt;/code&gt; (TUI), &lt;code&gt;sfdisk&lt;/code&gt; (scriptable &lt;code&gt;fdisk&lt;/code&gt;), &lt;code&gt;parted&lt;/code&gt;, &lt;code&gt;gparted&lt;/code&gt; (GUI) are some of them. I worked around that by installing Raspberry Pi OS on the SD card with the Raspberry Pi imager, and then overwriting it with the Alpine files. This works because the Raspberry PI OS installation marks the FAT32 partition as bootable.&lt;/p&gt;&#xA;&lt;h2 id=&#34;install-alpine&#34;&gt;&#xA;  Install Alpine&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#install-alpine&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Installing Alpine is well documented in the &lt;a href=&#34;https://wiki.alpinelinux.org/wiki/Installation&#34;&gt;wiki&lt;/a&gt; thus it won&amp;rsquo;t be covered here. It basically comes down to invoking &lt;code&gt;setup-alpine&lt;/code&gt;, which then invokes other &lt;code&gt;setup-*&lt;/code&gt; scripts.&lt;/p&gt;&#xA;&lt;p&gt;Keep in mind we&amp;rsquo;re not really &amp;ldquo;installing&amp;rdquo; Alpine as this is a diskless installation. A more accurate term here would be &amp;ldquo;configuring&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;Before invoking the installation script, I created a second primary partition in the SD card, set to &lt;code&gt;ext4&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# Configure networking to get working internet access.&#xA;% setup-interfaces&#xA;&#xA;# Install some partitioning tools.&#xA;% apk add cfdisk e2fsprogs&#xA;&#xA;# Create a second partition (mmcblk0p2) and write it.&#xA;% cfdisk /dev/mmcblk0&#xA;&#xA;# Format the partition as ext4.&#xA;% mkfs.ext4 /dev/mmcblk0p2&#xA;&#xA;# Mount the partition under /media.&#xA;% mount /dev/mmcblk0p2 /media/mmcblk0p2&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The installation is straightforward, we just need to pay attention to a few select steps:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;setup-disk&lt;/code&gt;: Select &lt;code&gt;none&lt;/code&gt; to ensure a &lt;code&gt;diskless&lt;/code&gt; installation&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;setup-apkcache&lt;/code&gt;: Select &lt;code&gt;/media/mmcblk0p2/cache&lt;/code&gt; to persist downloaded &lt;code&gt;apk&lt;/code&gt; packages.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;setup-lbu&lt;/code&gt;: Edit &lt;code&gt;/etc/lbu/lbu.conf&lt;/code&gt; and set &lt;code&gt;LBU_MEDIA=&amp;quot;mmcblk0p2&amp;quot;&lt;/code&gt;. Note: Do not add &lt;code&gt;/media&lt;/code&gt; as it is implicit.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Once the installation is complete, run &lt;code&gt;lbu commit&lt;/code&gt; to persist the changes in the second partition. Once you do so, a &lt;code&gt;&amp;lt;hostname&amp;gt;.apkovl.tar.gz&lt;/code&gt;&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt; file should materialize on &lt;code&gt;/media/mmcblk0p2/&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This is a good moment to reboot. Before we do so, let&amp;rsquo;s cache the packages we had previously downloaded.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# Cache packages.&#xA;% apk cache download&#xA;&#xA;% reboot&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;after-the-first-reboot&#34;&gt;&#xA;  After the first reboot&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#after-the-first-reboot&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;If everything worked as expected, once you reboot all your previously installed packages should have been preserved and automatically restored / reinstalled, as well as your modifications done to &lt;code&gt;/etc&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;From this point on, whenever you install a new package that you want to be preserved for subsequent reboots, run &lt;code&gt;lbu commit&lt;/code&gt; afterwards. For example:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% apk add vim&#xA;% lbu commit&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;If you would like to see what is going to be committed, run &lt;code&gt;lbu status&lt;/code&gt; or &lt;code&gt;lbu diff&lt;/code&gt; before doing the actual commit. Whenever you commit, &lt;code&gt;/media/mmcblk0p2/&amp;lt;hostname&amp;gt;.apkovl.tar.gz&lt;/code&gt; gets overwritten with your most recent modifications.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s possible to keep more than one backup file by changing &lt;code&gt;BACKUP_LIMIT=&lt;/code&gt; in &lt;code&gt;/etc/lbu/lbu.conf&lt;/code&gt;. This is specially handy if you decide to revert to an earlier system snapshot / state later on. The stock config looks like this:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;# what cipher to use with -e option&#xA;DEFAULT_CIPHER=aes-256-cbc&#xA;&#xA;# Uncomment the row below to encrypt config by default&#xA;# ENCRYPTION=$DEFAULT_CIPHER&#xA;&#xA;# Uncomment below to avoid &amp;lt;media&amp;gt; option to &amp;#39;lbu commit&amp;#39;&#xA;# Can also be set to &amp;#39;floppy&amp;#39;&#xA;# LBU_MEDIA=usb&#xA;&#xA;# Set the LBU_BACKUPDIR variable in case you prefer to save the apkovls&#xA;# in a normal directory instead of mounting an external media.&#xA;# LBU_BACKUPDIR=/root/config-backups&#xA;&#xA;# Uncomment below to let lbu make up to 3 backups&#xA;# BACKUP_LIMIT=3&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: You can find the list of all explicitly installed packages in &lt;code&gt;/etc/apk/world&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-last-piece-make-var-persistent&#34;&gt;&#xA;  The last piece: make /var persistent&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#the-last-piece-make-var-persistent&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;There are three natural ways that come to mind to make &lt;code&gt;/var&lt;/code&gt; persistent:&lt;/p&gt;&#xA;&lt;h3 id=&#34;a-separate-partition-or-file&#34;&gt;&#xA;  A) Separate partition (or file)&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#a-separate-partition-or-file&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Instead of two partitions (FAT32 and ext4), create 3 partitions: FAT32, ext4 and ext4. Use the latter one to mount &lt;code&gt;/var&lt;/code&gt; on, saving this information in &lt;code&gt;/etc/fstab&lt;/code&gt;. The main disadvantage of this setup is that you&amp;rsquo;ll need to allocate a fixed amount of space of each of the ext4 partitions and it may be difficult to figure out how to split the space between them.&lt;/p&gt;&#xA;&lt;p&gt;A variant of this approach is to just create the third partition as a file:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 500MB file&#xA;% dd if=/dev/zero of=/media/mmcblk0p2/var.img bs=1M count=500 status=progress&#xA;% mkfs.ext4 /media/mmcblk0p2/var.img&#xA;% mount /media/mmcblk0p2/var.img /var&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This works because the Linux kernel supports mounting files as if they were device blocks, treating them as loop devices (pseudo-devices).&lt;/p&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t like these approaches because they shadow the preexisting &lt;code&gt;/var&lt;/code&gt; from the boot media, which in turn messes up with existing services that use it such as &lt;code&gt;cron&lt;/code&gt;: &lt;code&gt;% crontab -l&lt;/code&gt; would fail. One workaround would be to mount a &lt;code&gt;/var&lt;/code&gt; subdirectory instead: for example, &lt;code&gt;/var/lib/docker&lt;/code&gt; for docker.&lt;/p&gt;&#xA;&lt;h3 id=&#34;b-bind-mount&#34;&gt;&#xA;  B) Bind mount&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#b-bind-mount&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;This one is straightforward:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% mount --bind /media/mmcblk0p2/var/lib/docker /var/lib/docker&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The actual partition lives in the SD card, however we make a bind mount under&#xA;&lt;code&gt;/var&lt;/code&gt;, which is like an &lt;em&gt;alias&lt;/em&gt;. From &lt;a href=&#34;https://unix.stackexchange.com/questions/198590/what-is-a-bind-mount&#34;&gt;Stack Exchange&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;A bind mount is an alternate view of a directory tree. Classically, mounting creates a view of a storage device as a directory tree. A bind mount instead takes an existing directory tree and replicates it under a different point. The directories and files in the bind mount are the same as the original. Any modification on one side is immediately reflected on the other side, since the two views show the same data.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h3 id=&#34;c-overlay-mount&#34;&gt;&#xA;  C) Overlay mount&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#c-overlay-mount&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;From &lt;a href=&#34;https://wiki.archlinux.org/title/Overlay_filesystem&#34;&gt;ArchWiki&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Overlayfs allows one, usually read-write, directory tree to be overlaid onto another, read-only directory tree. All modifications go to the upper, writable layer. This type of mechanism is most often used for live CDs but there is a wide variety of other uses.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;It&amp;rsquo;s perfect for our use case, which uses a live bootable SD card for Alpine. It blends the preexisting, ephemeral, in-memory &lt;code&gt;/var&lt;/code&gt; with the persistent in-disk &lt;code&gt;/var&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I wanted to mount &lt;code&gt;/var&lt;/code&gt; directly but found it to be problematic for the same reasons mentioned earlier, therefore I just went with &lt;code&gt;/var/lib/docker&lt;/code&gt; instead:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# Create overlay upper and work directories.&#xA;% mkdir -p /media/mmcblk0p2/var/lib/docker /media/mmcblk0p2/var/lib/docker-work&#xA;&#xA;# Add mountpoint entry to fstab. Note: The work dir must be an empty directory in the same filesystem mount as the upper directory.&#xA;% echo &amp;#34;overlay /var/lib/docker overlay lowerdir=/var/lib/docker,upperdir=/media/mmcblk0p2/var/lib/docker,workdir=/media/mmcblk0p2/var/lib/docker-work 0 0&amp;#34; &amp;gt;&amp;gt; /etc/fstab&#xA;&#xA;# Mount all fstab entries, including our newly added one.&#xA;% mount -a&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;conclusion&#34;&gt;&#xA;  Conclusion&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#conclusion&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I opted for the third approach, using an overlay mount, it was the most&#xA;seamless one. A bind mount would have been fine as well.&lt;/p&gt;&#xA;&lt;p&gt;The final setup works surprisingly well:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Alpine Linux is very lightweight and runs mostly from RAM&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;apk&lt;/code&gt; cache is persistent to the ext4 partition&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;/var/&lt;/code&gt; is persistent to the ext4 partition&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;lbu commit&lt;/code&gt; persists changes in &lt;code&gt;/etc/&lt;/code&gt; and &lt;code&gt;/home/&lt;/code&gt; in the ext4 partition&lt;/li&gt;&#xA;&lt;li&gt;Every reboot fully resets the system sans persistent components above&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;references&#34;&gt;&#xA;  References&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#references&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://vincentserpoul.github.io/post/alpine-linux-rpi0/&#34;&gt;https://vincentserpoul.github.io/post/alpine-linux-rpi0/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;http://dahl-jacobsen.dk/tips/blog/2021-04-08-docker-on-alpine-linux/&#34;&gt;http://dahl-jacobsen.dk/tips/blog/2021-04-08-docker-on-alpine-linux/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;http://dahl-jacobsen.dk/tips/blog/2018-03-15-alpine-on-raspberry-pi/&#34;&gt;http://dahl-jacobsen.dk/tips/blog/2018-03-15-alpine-on-raspberry-pi/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Running (almost) fully from RAM.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;CF = Compact disk.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;On Linux I&amp;rsquo;d usually opt for &lt;code&gt;dd&lt;/code&gt;, on Windows the Raspberry Pi Imager is a sensible choice.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;100MB is overly conservative, but keep in mind I had a very small SD Card, with only 4GiB storage. 250MB or even 500MB should be a more sensible default if you have a bigger SD Card (e.g. 32GiB).&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:5&#34;&gt;&#xA;&lt;p&gt;An alternative is to select &lt;code&gt;data&lt;/code&gt; disk mode, but it didn&amp;rsquo;t work for me.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:6&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;ovl&lt;/em&gt; is short for &lt;em&gt;overlay&lt;/em&gt;. Not to be confused with &lt;em&gt;vol&lt;/em&gt; for &lt;em&gt;volume&lt;/em&gt;.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-on-raspberry-pi-diskless-mode-with-persistent-storage/#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine Linux on Raspberry Pi: Diskless Mode with persistent storage&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Alpine Linux: apk logs with etckeeper
      </title>
      <link>https://perrotta.dev/2022/01/alpine-linux-apk-logs-with-etckeeper/</link>
      <pubDate>Mon, 10 Jan 2022 23:14:43 -0500</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2022/01/alpine-linux-apk-logs-with-etckeeper/</guid>
      <description>&lt;p&gt;♠ &lt;code&gt;apk(8)&lt;/code&gt; is the Alpine Linux package manager. Surprisingly, it lacks native&#xA;logs. In this post we will learn how to work around this limitation.&lt;/p&gt;&#xA;&lt;p&gt;In a distribution like Arch Linux that uses &lt;code&gt;pacman(8)&lt;/code&gt;, one would typically&#xA;find logs in &lt;code&gt;/var/log/pacman.log&lt;/code&gt;. You would expect Alpine Linux to follow&#xA;suit and provide some &lt;code&gt;/var/log/apk.log&lt;/code&gt; or similar, however that&amp;rsquo;s not the&#xA;case.&lt;/p&gt;&#xA;&lt;p&gt;Logs are nowhere to be found, even in the &lt;code&gt;apk-*&lt;/code&gt; man pages. I double-checked&#xA;by asking on the &lt;code&gt;#alpine-linux&lt;/code&gt; IRC and someone confirmed this is indeed the&#xA;case, and there&amp;rsquo;s an (unconfirmed) possibility the next generation of apk may&#xA;add logging support.&lt;/p&gt;&#xA;&lt;p&gt;Meanwhile, we will use &lt;code&gt;etckeeper(8)&lt;/code&gt; to overcome this limitation.&lt;/p&gt;&#xA;&lt;h2 id=&#34;etckeeper-set-up&#34;&gt;&#xA;  etckeeper: set-up&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-apk-logs-with-etckeeper/#etckeeper-set-up&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://etckeeper.branchable.com/&#34;&gt;&lt;code&gt;etckeeper&lt;/code&gt;&lt;/a&gt; is&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;a collection of tools to let &lt;code&gt;/etc&lt;/code&gt; be stored in a git, mercurial, bazaar or&#xA;darcs repository. This lets you use git to review or revert changes that were&#xA;made to /etc. Or even push the repository elsewhere for backups or&#xA;cherry-picking configuration changes.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;by &lt;a href=&#34;https://joeyh.name/&#34;&gt;Joey Hess&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;It is available in the Alpine Linux &lt;a href=&#34;https://pkgs.alpinelinux.org/packages?name=etckeeper&#34;&gt;repositories&lt;/a&gt;, just install it:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;% apk add etckeeper&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;No configuration is needed, it works out-of-the-box, thanks to a &lt;a href=&#34;https://git.alpinelinux.org/aports/tree/main/etckeeper/etckeeper.post-install&#34;&gt;post-install&#xA;hook&lt;/a&gt; to initialize the git repository, and an &lt;a href=&#34;https://git.alpinelinux.org/aports/tree/main/etckeeper/apk-commit_hook&#34;&gt;apk commit&#xA;hook&lt;/a&gt; to update it upon &lt;code&gt;apk&lt;/code&gt; package operations.&lt;/p&gt;&#xA;&lt;h2 id=&#34;etckeeper-viewing-logs&#34;&gt;&#xA;  etckeeper: viewing logs&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-apk-logs-with-etckeeper/#etckeeper-viewing-logs&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Just run &lt;code&gt;git log&lt;/code&gt; as root. Root privilege is necessary because the git&#xA;repository is initialized under &lt;code&gt;/etc/etckeeper&lt;/code&gt;. Pick your poison:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ GIT_DIR=/etc/etckeeper doas git log&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ doas git -C /etc/etckeeper log&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ (cd /etc/etckeeper &amp;amp;&amp;amp; doas git log)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s what a typical log looks like, courtesy of &lt;code&gt;apk-autoupdate(1)&lt;/code&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-apk-logs-with-etckeeper/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;commit f06255c4be4657481082406b2050ecd88e3da768&#xA;Author: root &amp;lt;root@localhost.localdomain&amp;gt;&#xA;Date:   Tue Jan 11 00:00:24 2022 -0500&#xA;&#xA;    committing changes in /etc after apk run&#xA;&#xA;    Package changes:&#xA;    -libeconf-0.4.2-r0&#xA;    -libeconf-doc-0.4.2-r0&#xA;    &amp;#43;libeconf-0.4.4-r0&#xA;    &amp;#43;libeconf-doc-0.4.4-r0&#xA;    -mtools-4.0.36-r0&#xA;    -mtools-doc-4.0.36-r0&#xA;    &amp;#43;mtools-4.0.37-r0&#xA;    &amp;#43;mtools-doc-4.0.37-r0&#xA;    -perl-io-socket-ssl-2.073-r0&#xA;    -perl-io-socket-ssl-doc-2.073-r0&#xA;    &amp;#43;perl-io-socket-ssl-2.074-r0&#xA;    &amp;#43;perl-io-socket-ssl-doc-2.074-r0&#xA;    -py3-idna-3.3-r1&#xA;    &amp;#43;py3-idna-3.3-r2&#xA;    -py3-jinja2-3.0.1-r1&#xA;    -py3-jinja2-doc-3.0.1-r1&#xA;    &amp;#43;py3-jinja2-3.0.3-r0&#xA;    &amp;#43;py3-jinja2-doc-3.0.3-r0&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Which merely does &lt;code&gt;apk upgrade&lt;/code&gt;, automatically. More details on &lt;a href=&#34;https://github.com/jirutka/apk-autoupdate&#34;&gt;the apk-autoupdate GitHub repository&lt;/a&gt;.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-apk-logs-with-etckeeper/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine Linux: apk logs with etckeeper&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ My First APKBUILD
      </title>
      <link>https://perrotta.dev/2022/01/my-first-apkbuild/</link>
      <pubDate>Fri, 07 Jan 2022 20:03:07 -0500</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>bestof</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2022/01/my-first-apkbuild/</guid>
      <description>&lt;p&gt;♠ In the same spirit of my first PKGBUILD and &lt;a href=&#34;https://perrotta.dev/2014/09/my-first-ebuild/&#34;&gt;&lt;code&gt;Ebuild&lt;/code&gt;&lt;/a&gt;, herein I will describe my first &lt;code&gt;APKBUILD&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;at-a-glance&#34;&gt;&#xA;  At a glance&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/my-first-apkbuild/#at-a-glance&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Alpine Linux package management is very similar to Arch Linux, with tiny differences:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;PKGBUILD&lt;/code&gt; → &lt;code&gt;APKBUILD&lt;/code&gt;: The filename is obviously different. Their format are very similar though, both of them are bash scripts with variables and functions. In particular, there&amp;rsquo;s &lt;code&gt;check&lt;/code&gt;, &lt;code&gt;patch&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt; and &lt;code&gt;package&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;cp /usr/share/pacman/PKGBUILD.proto&lt;/code&gt; → &lt;code&gt;newapkbuild&lt;/code&gt;: Template versus scaffolding.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;pacman&lt;/code&gt; → &lt;code&gt;apk&lt;/code&gt;: The package manager is different.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;makepkg -s&lt;/code&gt; → &lt;code&gt;abuild -r&lt;/code&gt;: &lt;code&gt;makepkg&lt;/code&gt; drives all things package building for &lt;code&gt;pacman&lt;/code&gt;. &lt;code&gt;abuild&lt;/code&gt; drives package building for &lt;code&gt;apk&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;makepkg -i&lt;/code&gt; → &lt;code&gt;apk add &amp;lt;pkg&amp;gt;&lt;/code&gt;: &lt;code&gt;makepkg&lt;/code&gt; can also drive package installations whereas &lt;code&gt;abuild&lt;/code&gt; cannot, &lt;code&gt;apk&lt;/code&gt; must be used.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;namcap&lt;/code&gt; → &lt;code&gt;apkbuild-lint&lt;/code&gt; (from &lt;code&gt;atools&lt;/code&gt;) + &lt;code&gt;abuild sanitycheck&lt;/code&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/my-first-apkbuild/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;: Linters are different.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;updpkgsums&lt;/code&gt; → &lt;code&gt;abuild checksum&lt;/code&gt;: Generate hashes for package sources.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;figure&gt;&lt;a href=&#34;https://xkcd.com/927/&#34;&gt;&lt;img src=&#34;https://imgs.xkcd.com/comics/standards.png&#34;&#xA;    alt=&#34;Fortunately, the charging one has been solved now that we&amp;#39;ve all standardized on mini-USB. Or is it micro-USB? Shit.&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;&#xA;      &lt;p&gt;XKCD Courtesy of Randall Munroe&lt;/p&gt;&#xA;    &lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;Other than that, the process of writing an &lt;code&gt;APKBUILD&lt;/code&gt; is very similar to writing a &lt;code&gt;PKGBUILD&lt;/code&gt;. In fact, the Arch repositories (especially the &lt;a href=&#34;https://aur.archlinux.org/&#34;&gt;AUR&lt;/a&gt;) tend to be much more comprehensive than Alpine&amp;rsquo;s in terms of number of packages, so chances are if you want to write a new package for Alpine, check in Arch&amp;rsquo;s repos first, it&amp;rsquo;s a good starting point.&lt;/p&gt;&#xA;&lt;h2 id=&#34;my-first-package-fpp&#34;&gt;&#xA;  My first package: &lt;code&gt;fpp&lt;/code&gt;&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/my-first-apkbuild/#my-first-package-fpp&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;fpp&lt;/code&gt; stands for &amp;lsquo;Facebook Path Picker&amp;rsquo;.&lt;/p&gt;&#xA;&lt;p&gt;As of the time of this post, I maintain &lt;a href=&#34;https://aur.archlinux.org/packages/fpp-git/&#34;&gt;&lt;code&gt;fpp-git&lt;/code&gt;&lt;/a&gt; in the AUR. It looks like this:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;pkgname=fpp-git&#xA;pkgver=0.9.2.r130.ge0d5cfc&#xA;pkgrel=1&#xA;pkgdesc=&amp;#39;TUI that lets you pick paths out of its stdin and run arbitrary commands on them&amp;#39;&#xA;url=&amp;#39;https://facebook.github.io/PathPicker&amp;#39;&#xA;license=(&amp;#39;MIT&amp;#39;)&#xA;source=(&amp;#34;${pkgname%-git}::git&amp;#43;https://github.com/facebook/PathPicker.git&amp;#34;)&#xA;sha256sums=(&amp;#39;SKIP&amp;#39;)&#xA;arch=(&amp;#39;any&amp;#39;)&#xA;makedepends=(&amp;#39;git&amp;#39;)&#xA;depends=(&amp;#39;python&amp;#39;)&#xA;conflicts=(&amp;#34;${pkgname%-git}&amp;#34;)&#xA;provides=(&amp;#34;${pkgname%-git}&amp;#34;)&#xA;&#xA;prepare() {&#xA;  cd &amp;#34;$srcdir/${pkgname%-git}&amp;#34;&#xA;  rm -r &amp;#34;src/tests&amp;#34;&#xA;}&#xA;&#xA;pkgver() {&#xA;  cd &amp;#34;$srcdir/${pkgname%-git}&amp;#34;&#xA;  git describe --long --tags | sed &amp;#39;s/\([^-]*-g\)/r\1/;s/-/./g&amp;#39;&#xA;}&#xA;&#xA;package() {&#xA;  cd &amp;#34;$srcdir/${pkgname%-git}&amp;#34;&#xA;&#xA;  # library&#xA;  install -Dm755 &amp;#34;fpp&amp;#34; -t &amp;#34;$pkgdir/usr/share/fpp&amp;#34;&#xA;  cp -a src &amp;#34;$pkgdir/usr/share/fpp&amp;#34;&#xA;&#xA;  # entrypoint&#xA;  install -dm755 &amp;#34;$pkgdir/usr/bin&amp;#34;&#xA;  ln -s &amp;#34;/usr/share/fpp/fpp&amp;#34; &amp;#34;$pkgdir/usr/bin&amp;#34;&#xA;&#xA;  # documentation&#xA;  install -Dm644 LICENSE -t &amp;#34;$pkgdir/usr/share/licenses/$pkgname&amp;#34;&#xA;  install -Dm644 &amp;#34;debian/usr/share/man/man1/fpp.1&amp;#34; -t &amp;#34;$pkgdir/usr/share/man/man1&amp;#34;&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;code&gt;PKGBUILD&lt;/code&gt; guidelines and instructions:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://wiki.archlinux.org/title/PKGBUILD&#34;&gt;https://wiki.archlinux.org/title/PKGBUILD&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://wiki.archlinux.org/title/Arch_package_guidelines&#34;&gt;https://wiki.archlinux.org/title/Arch_package_guidelines&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The equivalent &lt;code&gt;APKBUILD&lt;/code&gt; I wrote looks like this:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;pkgname=fpp&#xA;pkgver=0.9.2&#xA;pkgrel=0&#xA;pkgdesc=&amp;#34;TUI that lets you pick paths out of its stdin and run arbitrary commands on them&amp;#34;&#xA;url=&amp;#34;https://facebook.github.io/PathPicker&amp;#34;&#xA;arch=&amp;#34;noarch&amp;#34;&#xA;license=&amp;#34;MIT&amp;#34;&#xA;depends=&amp;#34;bash python3&amp;#34;&#xA;subpackages=&amp;#34;$pkgname-doc&amp;#34;&#xA;source=&amp;#34;$pkgname-$pkgver.tar.gz::https://github.com/facebook/PathPicker/archive/$pkgver.tar.gz&amp;#34;&#xA;builddir=&amp;#34;$srcdir/PathPicker-$pkgver&amp;#34;&#xA;&#xA;check() {&#xA;&#x9;fpp --version&#xA;}&#xA;&#xA;prepare() {&#xA;&#x9;default_prepare&#xA;&#xA;&#x9;rm -r &amp;#34;src/__tests__&amp;#34;&#xA;}&#xA;&#xA;package() {&#xA;&#x9;# library&#xA;&#x9;install -Dm755 &amp;#34;fpp&amp;#34; -t &amp;#34;$pkgdir/usr/share/fpp&amp;#34;&#xA;&#x9;cp -a src &amp;#34;$pkgdir/usr/share/fpp&amp;#34;&#xA;&#xA;&#x9;# entrypoint&#xA;&#x9;install -dm755 &amp;#34;$pkgdir/usr/bin&amp;#34;&#xA;&#x9;ln -s &amp;#34;/usr/share/fpp/fpp&amp;#34; &amp;#34;$pkgdir/usr/bin&amp;#34;&#xA;&#xA;&#x9;# documentation&#xA;&#x9;install -Dm644 LICENSE -t &amp;#34;$pkgdir/usr/share/licenses/$pkgname&amp;#34;&#xA;&#x9;install -Dm644 &amp;#34;debian/usr/share/man/man1/fpp.1&amp;#34; -t &amp;#34;$pkgdir/usr/share/man/man1&amp;#34;&#xA;}&#xA;&#xA;sha512sums=&amp;#34;&#xA;65b6b077f437bd642ebf94c55be901aabc73f7b9c89e4522c4f51970c4d63d744ad8fa29cac06816851f63bcb81d0480e61d405231c582e9aca0f4e650949a97  fpp-0.9.2.tar.gz&#xA;&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;code&gt;APKBUILD&lt;/code&gt; guidelines and instructions:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package&#34;&gt;https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://wiki.alpinelinux.org/wiki/APKBUILD_Reference&#34;&gt;https://wiki.alpinelinux.org/wiki/APKBUILD_Reference&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;build-comparison&#34;&gt;&#xA;  Build Comparison&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/my-first-apkbuild/#build-comparison&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s highlight a few similarities and differences in them, excluding the fact that one is fetched from git and the other one fetches a point release directly&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/my-first-apkbuild/#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;package metadata, by the means of bash variables, are almost equivalent&#xA;one-to-one&#xA;— A notable difference is the architecture, Arch primarily supports&#xA;&lt;em&gt;x86_64&lt;/em&gt; whereas Alpine has support for multiple architectures. When&#xA;a package is architecture agnostic, Arch denotes it with &lt;code&gt;any&lt;/code&gt; whereas&#xA;alpine has both &lt;code&gt;noarch&lt;/code&gt; and &lt;code&gt;all&lt;/code&gt;, the latter is like &lt;code&gt;any&lt;/code&gt; (=all&#xA;architectures), the &lt;code&gt;former&lt;/code&gt; means it&amp;rsquo;s agnostic (=e.g. a pure bash script&#xA;or python package).&#xA;— &lt;code&gt;APKBUILDs&lt;/code&gt; use flat strings, whereas &lt;code&gt;PKGBUILDs&lt;/code&gt; use bash arrays&#xA;— Alpine encourages splitting larger packages into subpackages, as such&#xA;&lt;code&gt;APKBUILD&lt;/code&gt; has first-class support and syntactic sugar for that. &lt;code&gt;-dev&lt;/code&gt; and&#xA;&lt;code&gt;-doc&lt;/code&gt; subpackages are very common. On the other hand, Arch tends to have&#xA;monolithic packages in order to keep it simple, although it also supports&#xA;subpackages.&#xA;— Alpine supports setting &lt;code&gt;$builddir&lt;/code&gt; whereas Arch doesn&amp;rsquo;t. As a consequence, it&amp;rsquo;s often unneeded to &lt;code&gt;cd&lt;/code&gt; in &lt;code&gt;build()&lt;/code&gt; and &lt;code&gt;package()&lt;/code&gt; in Alpine, whereas in Arch one does need to manually change directories to &lt;code&gt;$srcdir/$pkgname&lt;/code&gt; before building.&#xA;— Alpine lacks optional dependencies, whereas Arch has &lt;code&gt;optdepends&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Alpine enforces the use of &lt;code&gt;check&lt;/code&gt; in test packages, otherwise it needs to be explicitly disabled and documented with &lt;code&gt;!check&lt;/code&gt; in &lt;code&gt;options=&lt;/code&gt;. That&amp;rsquo;s not the case in Arch.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;check()&lt;/code&gt;, &lt;code&gt;build()&lt;/code&gt; and &lt;code&gt;package()&lt;/code&gt; are pretty much similar in both formats. &lt;code&gt;$srcdir&lt;/code&gt; and &lt;code&gt;$pkgdir&lt;/code&gt; are provided in both.&lt;/li&gt;&#xA;&lt;li&gt;The &lt;a href=&#34;https://wiki.archlinux.org/title/Main_page&#34;&gt;ArchWiki&lt;/a&gt; is way more documented in terms of packaging guidelines and examples than Alpine&amp;rsquo;s. If you use DuckDuckGo, you can query for &lt;code&gt;!aw &amp;lt;foo&amp;gt;&lt;/code&gt; as a bang shortcut to search directly in the ArchWiki.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Last but not least, in Arch one can install package tarballs&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/my-first-apkbuild/#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; with &lt;code&gt;makepkg -i&lt;/code&gt; or &lt;code&gt;pacman -U&lt;/code&gt;. In Alpine that approach doesn&amp;rsquo;t seem to be directly supported. The workflow is to add a local repository directory in &lt;code&gt;/etc/apk/repositories&lt;/code&gt; (notice the last two lines):&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;# http://dl-cdn.alpinelinux.org/alpine/v3.15/main&#xA;# http://dl-cdn.alpinelinux.org/alpine/v3.15/community&#xA;# http://dl-cdn.alpinelinux.org/alpine/latest-stable/main&#xA;# http://dl-cdn.alpinelinux.org/alpine/latest-stable/community&#xA;http://dl-cdn.alpinelinux.org/alpine/edge/main&#xA;http://dl-cdn.alpinelinux.org/alpine/edge/community&#xA;http://dl-cdn.alpinelinux.org/alpine/edge/testing&#xA;/home/$USER/packages/community&#xA;/home/$USER/packages/testing&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;code&gt;abuild&lt;/code&gt; will place the resulting package tarball in &lt;code&gt;~/packages&lt;/code&gt;, in this case:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ ls ~/packages/testing/x86_64/fpp*&#xA;/home/$USER/packages/testing/x86_64/fpp-0.9.2-r0.apk&#xA;/home/$USER/packages/testing/x86_64/fpp-doc-0.9.2-r0.apk&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&amp;hellip;and then &lt;code&gt;apk add fpp&lt;/code&gt; will automagically recognize it&amp;rsquo;s in there and install it. The advantage of this approach is that it keeps a local package repository around and it&amp;rsquo;s well integrated with &lt;code&gt;apk&lt;/code&gt;, way differently from &lt;code&gt;pacman&lt;/code&gt; that has no integration with the AUR at all. One could also possibly set up a local repository in Arch, for example, with &lt;a href=&#34;https://github.com/graysky2/clean-chroot-manager&#34;&gt;&lt;code&gt;ccm&lt;/code&gt;&lt;/a&gt;, but it takes extra steps and it&amp;rsquo;s not officially supported.&lt;/p&gt;&#xA;&lt;h2 id=&#34;upstream-contributions&#34;&gt;&#xA;  Upstream Contributions&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/my-first-apkbuild/#upstream-contributions&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;On Arch, to contribute a &lt;code&gt;PKGBUILD&lt;/code&gt; upstream one just needs to create an account in the &lt;a href=&#34;https://aur.archlinux.org/&#34;&gt;AUR&lt;/a&gt;. Armed with a git + ssh infrastructure, all you need to do is &lt;code&gt;git push&lt;/code&gt;. There are no ACLs involved, anyone can do that&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/my-first-apkbuild/#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;p&gt;On Alpine there&amp;rsquo;s a bit more of politics involved&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/my-first-apkbuild/#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;: Anyone can send a &lt;code&gt;patch(1)&lt;/code&gt;, either via &lt;a href=&#34;mailto:alpine-aports@lists.alpinelinux.org&#34;&gt;mailing list&lt;/a&gt; or via a &lt;a href=&#34;https://gitlab.alpinelinux.org/alpine/aports&#34;&gt;Gitlab MR&lt;/a&gt; (merge request). Patch works well with &lt;code&gt;git send-email -1&lt;/code&gt;, being automatically cross-posted to a Gitlab MR. On the other hand the MR workflow is easier to be followed up on feedback from developers and other contributors (&lt;code&gt;git push --force&lt;/code&gt;), and it&amp;rsquo;s also cross-posted, to the mailing list. An Alpine developer with the appropriate permissions must approve your patch/MR before it becomes available to other Alpine users.&lt;/p&gt;&#xA;&lt;p&gt;Sadly at the time of this writing &lt;a href=&#34;https://lists.alpinelinux.org/~alpine/aports/patches/3799&#34;&gt;my patch&lt;/a&gt; hasn&amp;rsquo;t yet been approved (2 weeks later), however we&amp;rsquo;re in holiday season. This wouldn&amp;rsquo;t have been a problem in the AUR, where I could have just pushed it immediately, without any review. On the other hand the Alpine approach at least gives me some hope that the submitted packages have slightly higher quality than the average ones in the AUR, since they need to be manually reviewed/approved/vetted by at least one Alpine developer.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Install &lt;code&gt;spdx-licenses-list&lt;/code&gt; to lint the licenses, it&amp;rsquo;s used by &lt;code&gt;abuild sanitycheck&lt;/code&gt; as an optional dependency.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/my-first-apkbuild/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;The AUR tends to have both non-vcs and vcs versions of a software, whereas Alpine is focused a bit more on stability and tends to have non-vcs only. This is not a hard rule though, exceptions may exist.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/my-first-apkbuild/#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;&lt;code&gt;.tar.xz&lt;/code&gt; or, more recently, &lt;code&gt;.tar.zstd&lt;/code&gt;.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/my-first-apkbuild/#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;And this is one of the reasons why you should always inspect every &lt;code&gt;PKGBUILD&lt;/code&gt; you install from the Arch User Repository, as it could have been tampered with and/or contain malicious code.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/my-first-apkbuild/#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:5&#34;&gt;&#xA;&lt;p&gt;I&amp;rsquo;ll leave it open-ended whether that&amp;rsquo;s a bug or a feature. Depending on the lens you see through, it could be considered either gatekeeping (bureaucracy, control) or sanity (quality, stability). It has pros and cons, and even those are arguable.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/my-first-apkbuild/#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: My First APKBUILD&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ Alpine Linux: apk command not found hook
      </title>
      <link>https://perrotta.dev/2022/01/alpine-linux-apk-command-not-found-hook/</link>
      <pubDate>Tue, 04 Jan 2022 16:25:51 -0500</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>alpine-linux</category>
      <category>bestof</category>
      <category>dev</category>
      <guid>https://perrotta.dev/2022/01/alpine-linux-apk-command-not-found-hook/</guid>
      <description>&lt;p&gt;♠ In this post we will learn how to define a command-not-found hook to the &lt;code&gt;apk(8)&lt;/code&gt; package manager in Alpine Linux.&lt;/p&gt;&#xA;&lt;h2 id=&#34;sneak-peek&#34;&gt;&#xA;  Sneak peek&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-apk-command-not-found-hook/#sneak-peek&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Before:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ podman&#xA;zsh: correct &amp;#39;podman&amp;#39; to &amp;#39;pod2man&amp;#39; [nyae]? n&#xA;zsh: command not found: podman&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;After:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ podman&#xA;zsh: correct &amp;#39;podman&amp;#39; to &amp;#39;pod2man&amp;#39; [nyae]? n&#xA;podman may be found in the following packages:&#xA;  &amp;lt;cmd:podman&amp;gt; podman-3.4.4-r1 x86_64 {podman} (Apache-2.0)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;preamble&#34;&gt;&#xA;  Preamble&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-apk-command-not-found-hook/#preamble&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Whenever you type a command that is not in your &lt;code&gt;$PATH&lt;/code&gt;, usually your shell&#xA;will yell at you that it wasn&amp;rsquo;t found.&lt;/p&gt;&#xA;&lt;p&gt;The typical workflow in this scenario is to use the search functionality of your package manager in order to find which package provides the binary you&amp;rsquo;re interested in.&lt;/p&gt;&#xA;&lt;p&gt;In Alpine Linux, one would do:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ apk search podman&#xA;podman-doc-3.4.4-r1&#xA;podman-remote-3.4.4-r1&#xA;podman-docker-3.4.4-r1&#xA;openscap-1.3.5-r3&#xA;podman-3.4.4-r1&#xA;podman-compose-0.1.5-r4&#xA;podman-bash-completion-3.4.4-r1&#xA;podman-zsh-completion-3.4.4-r1&#xA;py3-podman-3.2.1-r1&#xA;podman-docker-doc-3.4.4-r1&#xA;podman-openrc-3.4.4-r1&#xA;podman-fish-completion-3.4.4-r1&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The output is a bit noisy, but with a bit of experience you could easily figure out the package you&amp;rsquo;re looking for is simply called &lt;code&gt;podman&lt;/code&gt;, given the output above.&lt;/p&gt;&#xA;&lt;p&gt;Surely this was an easy example, what if we tried something less obvious?&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ vidir&#xA;zsh: correct &amp;#39;vidir&amp;#39; to &amp;#39;vdir&amp;#39; [nyae]? n&#xA;zsh: command not found: vidir&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;There&amp;rsquo;s no &lt;code&gt;vidir&lt;/code&gt; binary, then surely there&amp;rsquo;s a &lt;code&gt;vidir&lt;/code&gt; package, right?&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ doas apk add vidir&#xA;ERROR: unable to select packages:&#xA;  vidir (no such package):&#xA;    required by: world[vidir]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Er, no. You&amp;rsquo;ll need to use search again:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ apk search vidir&#xA;moreutils-0.67-r0&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;There it is, &lt;code&gt;moreutils&lt;/code&gt;. Great piece of software, by the way&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-apk-command-not-found-hook/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;p&gt;What if we could automate this?&lt;/p&gt;&#xA;&lt;h2 id=&#34;automating-command-not-found-1st-try&#34;&gt;&#xA;  Automating command-not-found: 1st try&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-apk-command-not-found-hook/#automating-command-not-found-1st-try&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;In bash, one can define a &lt;code&gt;command_not_found_handle&lt;/code&gt; function. In zsh, one can define a &lt;code&gt;command_not_found_handler&lt;/code&gt; function. I know, why can&amp;rsquo;t it be the same function, right? Just one &lt;code&gt;r&lt;/code&gt; in the way. Regardless of whichever shell you use, the point is that the function is invoked whenever you run a command that is not in the &lt;code&gt;$PATH&lt;/code&gt; (or that isn&amp;rsquo;t a shell built-in).&lt;/p&gt;&#xA;&lt;p&gt;In principle, you could do:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;command_not_found_handle() {&#xA;  local cmd=&amp;#34;$1&amp;#34;&#xA;  apk search &amp;#34;$cmd&amp;#34;&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It&amp;rsquo;s a good first try, and it surely works as expected, but it can be a bit noisy sometimes. Look at the podman output above, it outputs several unrelated packages, none of which provide the &lt;code&gt;podman&lt;/code&gt; binary other than its homonym.&lt;/p&gt;&#xA;&lt;h2 id=&#34;automating-command-not-found-2nd-try&#34;&gt;&#xA;  Automating command-not-found: 2nd try&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-apk-command-not-found-hook/#automating-command-not-found-2nd-try&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;In Alpine, we can do slightly better. &lt;code&gt;apk(8)&lt;/code&gt; has the concept of providers:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ apk list -P | awk &amp;#39;{print $1}&amp;#39; | egrep &amp;#39;&amp;lt;\w&amp;#43;:&amp;#39; | cut -f 1 -d &amp;#39;:&amp;#39; | cut -c 2- | sort -u&#xA;cmd&#xA;dbus&#xA;pc&#xA;so&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;code&gt;-P&lt;/code&gt; above stands for &lt;code&gt;--providers&lt;/code&gt;. This roughly means one can search for a package that provides a given shared library (&lt;code&gt;so&lt;/code&gt;), or a package that provides a given binary (&lt;code&gt;cmd&lt;/code&gt;), and so on. We&amp;rsquo;re interested in the &lt;code&gt;cmd:&lt;/code&gt; provider.&lt;/p&gt;&#xA;&lt;p&gt;If we tried it with &lt;code&gt;podman&lt;/code&gt;, we would get the following output:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ apk list -P -- &amp;#34;cmd:podman&amp;#34;&#xA;&amp;lt;cmd:podman&amp;gt; podman-3.4.4-r1 x86_64 {podman} (Apache-2.0)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Look at how much shorter and direct it is, compared to the 1st approach!&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s what it looks like if we try it with a binary provided by multiple packages:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ apk list -P -- &amp;#34;cmd:docker&amp;#34;&#xA;&amp;lt;cmd:docker&amp;gt; docker-cli-20.10.11-r0 x86_64 {docker} (Apache-2.0) [installed]&#xA;&amp;lt;cmd:docker&amp;gt; podman-docker-3.4.4-r1 x86_64 {podman} (Apache-2.0)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It&amp;rsquo;s very easy to see that both &lt;code&gt;docker-cli&lt;/code&gt; and &lt;code&gt;podman-docker&lt;/code&gt; provide &lt;code&gt;docker&lt;/code&gt;. If you just did a simple search, you&amp;rsquo;d get a lot of noise:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ apk search docker&#xA;docker-bash-completion-20.10.11-r0&#xA;docker-cli-20.10.11-r0&#xA;docker-machine-driver-kvm2-1.24.0-r0&#xA;x11docker-6.9.0-r2&#xA;docker-volume-local-persist-1.3.0-r5&#xA;podman-docker-3.4.4-r1&#xA;openvswitch-2.12.3-r4&#xA;docker-engine-20.10.11-r0&#xA;docker-openrc-20.10.11-r0&#xA;dockerize-0.6.1-r9&#xA;docker-fish-completion-20.10.11-r0&#xA;openscap-1.3.5-r3&#xA;docker-py-5.0.3-r1&#xA;openvswitch-ovn-2.12.3-r4&#xA;docker-registry-openrc-2.7.1-r5&#xA;docker-doc-20.10.11-r0&#xA;rsyslog-imdocker-8.2108.0-r0&#xA;lazydocker-0.12-r2&#xA;docker-compose-bash-completion-1.29.2-r2&#xA;docker-compose-1.29.2-r2&#xA;py3-dockerpty-0.4.1-r4&#xA;docker-compose-zsh-completion-1.29.2-r2&#xA;docker-registry-2.7.1-r5&#xA;docker-credential-ecr-login-0.5.0-r2&#xA;dockerpy-creds-0.4.0-r3&#xA;docker-cli-compose-2.1.1-r0&#xA;docker-credential-ecr-login-doc-0.5.0-r2&#xA;podman-docker-doc-3.4.4-r1&#xA;docker-20.10.11-r0&#xA;docker-compose-fish-completion-1.29.2-r2&#xA;flannel-contrib-cni-0.15.1-r0&#xA;docker-zsh-completion-20.10.11-r0&#xA;docker-volume-local-persist-openrc-1.3.0-r5&#xA;docker-cli-buildx-0.7.1-r0&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;packaging-it-all-together&#34;&gt;&#xA;  Packaging&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-apk-command-not-found-hook/#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; it all together&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2022/01/alpine-linux-apk-command-not-found-hook/#packaging-it-all-together&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I wrote the following scripts, which I source in my respective interactive shells, to achieve this behavior out-of-the-box:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/bash&#xA;# apk(8) from Alpine Linux command not found hook for bash&#xA;&#xA;command_not_found_handle () {&#xA;        local cmd=&amp;#34;$1&amp;#34; pkgs&#xA;        mapfile -t pkgs &amp;lt; &amp;lt;(apk list -P -- &amp;#34;cmd:$cmd&amp;#34; 2&amp;gt;/dev/null)&#xA;&#xA;        if (( ${#pkgs[*]} )); then&#xA;                echo &amp;#34;$cmd may be found in the following packages:&amp;#34;&#xA;                printf &amp;#39;  %s\n&amp;#39; &amp;#34;${pkgs[@]}&amp;#34;&#xA;        else&#xA;                echo &amp;#34;bash: command not found: $cmd&amp;#34;&#xA;        fi 1&amp;gt;&amp;amp;2&#xA;&#xA;        return 127&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-zsh&#34;&gt;#!/bin/zsh&#xA;# apk(8) from Alpine Linux command not found hook for zsh&#xA;&#xA;command_not_found_handler() {&#xA;        local cmd=&amp;#34;$1&amp;#34;&#xA;        local pkgs=(${(f)&amp;#34;$(apk list -P -- &amp;#34;cmd:$cmd&amp;#34; 2&amp;gt;/dev/null)&amp;#34;})&#xA;&#xA;        if [[ -n &amp;#34;$pkgs&amp;#34; ]]; then&#xA;                echo &amp;#34;$cmd may be found in the following packages:&amp;#34;&#xA;                printf &amp;#39;  %s\n&amp;#39; &amp;#34;${pkgs[@]}&amp;#34;&#xA;        else&#xA;                echo &amp;#34;zsh: command not found: $cmd&amp;#34;&#xA;        fi 1&amp;gt;&amp;amp;2&#xA;&#xA;        return 127&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The snippets above are snapshots intended for this post.&#xA;I keep up-to-date versions of these files in my dotfiles repository, &lt;a href=&#34;https://github.com/thiagowfx/.dotfiles/search?q=filename%3Aapk-command-not-found&amp;amp;type=code&#34;&gt;try out this query&lt;/a&gt; in case I ever move them elsewhere.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://joeyh.name/code/moreutils/&#34;&gt;https://joeyh.name/code/moreutils/&lt;/a&gt;: moreutils is a collection of the unix tools that nobody thought to write long ago when unix was young.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-apk-command-not-found-hook/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;pun intended&amp;#160;&lt;a href=&#34;https://perrotta.dev/2022/01/alpine-linux-apk-command-not-found-hook/#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Alpine Linux: apk command not found hook&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/alpine-linux/&#34;&gt;#alpine-linux&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt;&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
