{"version":"https://jsonfeed.org/version/1","title":"tudor’s website","home_page_url":"https://tudorr.ro/","feed_url":"https://tudorr.ro/feed.json","description":"","items":[{"id":"https://tudorr.ro/blog/2025-12-23-package-managers/","url":"https://tudorr.ro/blog/2025-12-23-package-managers/","title":"Package Managers: What Do They Do, Really?","content_html":"<p><em>This article considers OS package managers, but much of it\ncan also apply to programming language package managers, like npm\nand Cargo.</em></p>\n<p>In the pursuit of getting out of my tech comfort zone, I have been\nworking on my “zen garden”: a small, cheap virtual private server\nrunning FreeBSD that runs some services I self-host.\nI am mainly a Linux person, so being confronted with all the things\nthat I take for granted on Linux which work differently —\nor not at all! — on FreeBSD lets me learn more about what happens\nunder the hood on both platforms.</p>\n<p>One highlight of the newly released FreeBSD 15.0 is that\n<a href=\"https://www.freebsd.org/releases/15.0R/announce/#_packaged_base_system\">you can now let the package manager manage the operating system</a>.</p>\n<p>“<span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-7a65ae\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-42e474\" for=\"sidenote-checkbox-7a65ae\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">What? That's been the case on Linux since forever!</label><small id=\"sidenote-42e474\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>\n“Look at this kid, I used to install Slackware from floppies!”\n<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>” says the Linux user.</p>\n<p>Even Windows has its <a href=\"https://en.wikipedia.org/wiki/Windows_Imaging_Format\">own OS package manager</a>, what does\nFreeBSD do? Well, it uses, or used to use, the simplest package manager: <code>tar</code>.</p>\n<h2>Tar, the simplest package manager</h2>\n<p>Well, maybe not just tar, but any archive file tool.</p>\n<p>We want to be able to install packages on top of a “thing”,\nand maybe to remove them at some point. An archive file\nbundles files together and stores some attributes, like file name,\naccess bits, directory structure etc. It’s no wonder that more developed\npackage managers like <code>rpm</code> and <code>dpkg</code> are based on archives,\nas this is effectively the starting point.</p>\n<p>To install a “package”, we can just extract an archive in <code>/</code> with the <code>-x</code> flag (eXtract):</p>\n<pre class=\"language-shell-session\"><code class=\"language-shell-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">tar</span> <span class=\"token parameter variable\">-C</span> / <span class=\"token parameter variable\">-xjvf</span> base.txz</span></span>\n</span></code></pre>\n<p>(The <code>-j</code> flag tells <code>tar</code> that the archive is compressed with <code>xz</code>.)</p>\n<p>Uninstalling is interesting. You can get a list of all the files\nin an archive like so, with the <code>-t</code> flag (lisT?):</p>\n<pre class=\"language-shell-session\"><code class=\"language-shell-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">tar</span> <span class=\"token parameter variable\">-tjf</span> base.txz</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">./\n</span></span><span class=\"code-line\"><span class=\"token output\">./COPYRIGHT\n</span></span><span class=\"code-line\"><span class=\"token output\">./bin/\n</span></span><span class=\"code-line\"><span class=\"token output\">./bin/[\n</span></span><span class=\"code-line\"><span class=\"token output\">./bin/cat\n</span></span><span class=\"code-line\"><span class=\"token output\">./bin/chflags\n</span></span><span class=\"code-line\"><span class=\"token output\">./bin/chio\n</span></span><span class=\"code-line\"><span class=\"token output\">./bin/chmod\n</span></span><span class=\"code-line\"><span class=\"token output\">./bin/cp\n</span></span><span class=\"code-line\"><span class=\"token output\">./bin/cpuset\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span></code></pre>\n<p>Remember the file attributes I just mentioned? You can see them\nwith the <code>-v</code> flag:</p>\n<pre class=\"language-shell-session\"><code class=\"language-shell-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">tar</span> <span class=\"token parameter variable\">-tjvf</span> base.txz</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">drwxr-xr-x  0 root   wheel       0 Nov 28 03:51 ./\n</span></span><span class=\"code-line\"><span class=\"token output\">-r--r--r--  0 root   wheel    6070 Nov 28 03:51 ./COPYRIGHT\n</span></span><span class=\"code-line\"><span class=\"token output\">drwxr-xr-x  0 root   wheel       0 Nov 28 03:43 ./bin/\n</span></span><span class=\"code-line\"><span class=\"token output\">-r-xr-xr-x  0 root   wheel   11736 Nov 28 03:42 ./bin/[\n</span></span><span class=\"code-line\"><span class=\"token output\">-r-xr-xr-x  0 root   wheel   13808 Nov 28 03:42 ./bin/cat\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span><span class=\"code-line\"><span class=\"token output\">lrwxr-xr-x  0 root   wheel        0 Nov 28 03:43 ./sbin/nologin -&gt; ../usr/sbin/nologin\n</span></span><span class=\"code-line\"><span class=\"token output\">-r-sr-xr-x  0 root   wheel    26312 Nov 28 03:43 ./usr/bin/login\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span><span class=\"code-line\"><span class=\"token output\">hr-xr-xr-x  0 root   wheel        0 Nov 28 03:45 ./usr/bin/ssh link to ./usr/bin/slogin\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span></code></pre>\n<p>Besides the access flags and owner information of each file, there are also some\ninteresting attributes. <code>./sbin/nologin</code> is a symbolic link, <code>./usr/bin/login</code>\nhas the SETUID flag, and <code>./usr/bin/ssh</code> is a hard link to\n<code>./usr/bin/slogin</code>. Depending on the archive file format,\nthere can also be extended attributes and flags, but that’s\na discussion for another time…</p>\n<p>So, we have something we can pipe to <code>xargs rm</code>\nto remove the files from the system. We can see that the output\nfrom <code>tar -t</code> also contains directories. We need to filter them out\nbefore passing them to <code>rm</code>, because removing a directory with plain <code>rm</code> would lead to an error. And we cannot use <code>xargs rm -r</code> because\nthat could delete more things than we want! We get:</p>\n<pre class=\"language-shell-session\"><code class=\"language-shell-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token comment\"># remove files</span></span></span>\n</span><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token builtin class-name\">cd</span> / <span class=\"token operator\">&amp;&amp;</span> <span class=\"token function\">tar</span> <span class=\"token parameter variable\">-jtf</span> base.txz <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> <span class=\"token parameter variable\">-v</span> <span class=\"token string\">'/$'</span> <span class=\"token operator\">|</span> <span class=\"token function\">xargs</span> <span class=\"token function\">rm</span></span></span>\n</span><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token comment\"># remove empty directories</span></span></span>\n</span><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token builtin class-name\">cd</span> / <span class=\"token operator\">&amp;&amp;</span> <span class=\"token function\">tar</span> <span class=\"token parameter variable\">-jtf</span> base.txz <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> <span class=\"token string\">'/$'</span> <span class=\"token operator\">|</span> <span class=\"token function\">sort</span> <span class=\"token parameter variable\">-r</span> <span class=\"token operator\">|</span> <span class=\"token function\">xargs</span> <span class=\"token function\">rmdir</span></span></span>\n</span></code></pre>\n<p>This is more or less how the <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-df43a0\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-79c442\" for=\"sidenote-checkbox-df43a0\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">three main BSD operating systems</label><small id=\"sidenote-79c442\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>\nFreeBSD, OpenBSD, NetBSD.\n<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span> are distributed — in a couple tar archives. More or less — we\nwill get to that later.</p>\n<p>A couple of questions immediately come to mind:</p>\n<ul>\n<li>How do you handle package updates?</li>\n<li>How do we know what package each file comes from?\n<ul>\n<li>What if, when installing the package, we overwrite some existing files?</li>\n<li>What if, when removing the package, we remove a file that was\ninstalled from a different package?</li>\n</ul>\n</li>\n</ul>\n<p>These are table stakes for the package managers most people are used to, like\n<code>rpm</code>, <code>dpkg</code>, <code>apk</code>, <code>pacman</code> etc. The first question can be answered easily as\nlong as we have the file list of the old archive. You remove the old files, and\nthen you extract the new archive.</p>\n<p>And if we have the file list, then we can also answer the other questions. If we keep a database of files installed by each package,\nwe can manage some packages.</p>\n<h2>Tar + package file database</h2>\n<p>It is time for some disruptive innovation. Let’s store a database\nwith all the files that make up each package. Given the name of\nan installed package, we should get a list of all files that\nare part of that package.</p>\n<p>There are many ways of storing such a database. For example, RPM uses the\nBerkeley DB format, and pkg (from FreeBSD) keeps tables in an SQLite 3 database. I will use <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-ef94dc\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-476b3e\" for=\"sidenote-checkbox-ef94dc\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">Arch Linux’s pacman</label><small id=\"sidenote-476b3e\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>\nI don’t use Arch, btw.<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span> as an example, owing to its text-based simplicity.</p>\n<p>Pacman creates a directory for each installed package in <code>/var/lib/pacman/local</code>:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">ls</span> /var/lib/pacman/local/</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">acl-2.3.2-1\n</span></span><span class=\"code-line\"><span class=\"token output\">ALPM_DB_VERSION\n</span></span><span class=\"code-line\"><span class=\"token output\">archlinux-keyring-20251116-1\n</span></span><span class=\"code-line\"><span class=\"token output\">attr-2.5.2-1\n</span></span><span class=\"code-line\"><span class=\"token output\">audit-4.1.2-1\n</span></span><span class=\"code-line\"><span class=\"token output\">autoconf-2.72-1\n</span></span><span class=\"code-line\"><span class=\"token output\">automake-1.18.1-1\n</span></span><span class=\"code-line\"><span class=\"token output\">avahi-1:0.9rc2-1\n</span></span><span class=\"code-line\"><span class=\"token output\">base-3-2\n</span></span><span class=\"code-line\"><span class=\"token output\">base-devel-1-2\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span></code></pre>\n<p>There is also a regular text file named <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-3a852e\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-cf15ec\" for=\"sidenote-checkbox-3a852e\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\"><code>ALPM_DB_VERSION</code></label><small id=\"sidenote-cf15ec\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>\n<abbr>Alpm</abbr> stands for “Arch Linux Package Management”, according\nto <a href=\"https://man.archlinux.org/man/pacman.8.en\"><code>pacman(8)</code></a>.\n<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span> that holds the database format version. On my system, this is equal\nto 9.</p>\n<p>Let’s see what’s inside one of these directories:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">ls</span> /var/lib/pacman/local/pacman-7.1.0.r7.gb9f7d4a-1/</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">desc\n</span></span><span class=\"code-line\"><span class=\"token output\">files\n</span></span><span class=\"code-line\"><span class=\"token output\">mtree\n</span></span></code></pre>\n<p>Three files: <code>desc</code>, <code>files</code>, and <code>mtree</code>.\nIn the aptly named text file <code>files</code>\neach line is a path to a file originating from this package:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">head</span> /var/lib/pacman/local/pacman-7.1.0.r7.gb9f7d4a-1/files</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">%FILES%\n</span></span><span class=\"code-line\"><span class=\"token output\">etc/\n</span></span><span class=\"code-line\"><span class=\"token output\">etc/makepkg.conf\n</span></span><span class=\"code-line\"><span class=\"token output\">etc/makepkg.conf.d/\n</span></span><span class=\"code-line\"><span class=\"token output\">etc/makepkg.conf.d/fortran.conf\n</span></span><span class=\"code-line\"><span class=\"token output\">etc/makepkg.conf.d/rust.conf\n</span></span><span class=\"code-line\"><span class=\"token output\">etc/makepkg.d/\n</span></span><span class=\"code-line\"><span class=\"token output\">etc/pacman.conf\n</span></span><span class=\"code-line\"><span class=\"token output\">usr/\n</span></span><span class=\"code-line\"><span class=\"token output\">usr/bin/\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span></code></pre>\n<p>Now we have a clear idea of which package contains what, which\nallows us to answer a multitude of questions. We can now avoid conflicts,\ncorrectly upgrade or downgrade packages, and check which package\nmanages a file. Which package offers the file <code>/usr/bin/bash</code>?</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">grep</span> <span class=\"token parameter variable\">-lr</span> usr/bin/bash /var/lib/pacman/local/ <span class=\"token operator\">|</span> <span class=\"token function\">cut</span> -d<span class=\"token string\">'/'</span> <span class=\"token parameter variable\">-f1</span></span></span>\n</span><span class=\"code-line\"><span class=\"token output\">bash-5.3.9-1\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span></code></pre>\n<p>On FreeBSD, we can leverage pkg’s SQLite 3 database:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">#</span> <span class=\"token bash language-bash\">pkg shell</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">SQLite version 3.50.4 2025-07-30 19:33:53\n</span></span><span class=\"code-line\"><span class=\"token output\">Enter \".help\" for usage hints.\n</span></span><span class=\"code-line\"><span class=\"token output\">sqlite&gt; select packages.name from files\n</span></span><span class=\"code-line\"><span class=\"token output\">   ...&gt; left join packages on files.package_id = packages.id\n</span></span><span class=\"code-line\"><span class=\"token output\">   ...&gt; where files.path = '/usr/local/etc/rsync/rsyncd.conf.sample';\n</span></span><span class=\"code-line\"><span class=\"token output\">rsync\n</span></span></code></pre>\n<p>We can also check whether a package has been incorrectly applied, or whether\nfiles from the package are for some reason missing on the host. You might\nremember from a couple paragraphs up that pacman also stores an <code>mtree</code> file in\nits database. But what is it?</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">file</span> mtree</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">mtree: gzip compressed data, from Unix, original size modulo 2^32 29540\n</span></span></code></pre>\n<p>Hmm, let’s use <code>zcat</code> to uncompress it and pipe the result to <code>cat</code>:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\">zcat mtree <span class=\"token operator\">|</span> <span class=\"token function\">head</span></span></span>\n</span><span class=\"code-line\"><span class=\"token output\">#mtree\n</span></span><span class=\"code-line\"><span class=\"token output\">/set type=file uid=0 gid=0 mode=644\n</span></span><span class=\"code-line\"><span class=\"token output\">./.BUILDINFO time=1765404175.0 size=5292 sha256digest=c1e0872d1c7200038790e6e90c54ea58be4f9321eda478dde48af7f367c6c926\n</span></span><span class=\"code-line\"><span class=\"token output\">./.INSTALL time=1765404175.0 size=454 sha256digest=a041703891b00fc7c2109d004ac548de8e3f684a910887d7d463adcc4984548d\n</span></span><span class=\"code-line\"><span class=\"token output\">./.PKGINFO time=1765404175.0 size=633 sha256digest=766e91bb5b8d47f9b93d1f6367002e9bb28de43280f6ffa5b56814bd42566a14\n</span></span><span class=\"code-line\"><span class=\"token output\">./etc time=1765404175.0 mode=755 type=dir\n</span></span><span class=\"code-line\"><span class=\"token output\">./etc/bash.bash_logout time=1765404175.0 size=28 sha256digest=025bccfb374a3edce0ff8154d990689f30976b78f7a932dc9a6fcef81821811e\n</span></span><span class=\"code-line\"><span class=\"token output\">./etc/bash.bashrc time=1765404175.0 size=733 sha256digest=563e03eb4b40edfcc778f04efa2b4913bcdc6929c73d2bd4c07e1f799b98721a\n</span></span><span class=\"code-line\"><span class=\"token output\">./etc/skel time=1765404175.0 mode=755 type=dir\n</span></span><span class=\"code-line\"><span class=\"token output\">./etc/skel/.bash_logout time=1765404175.0 size=21 sha256digest=4330edf340394d0dae50afb04ac2a621f106fe67fb634ec81c4bfb98be2a1eb5\n</span></span></code></pre>\n<p>Oh, this is also a file list, but enhanced with hashes, access flags\nand ownership information. In fact, this listing is to be used by\n<a href=\"https://man.netbsd.org/NetBSD-10.0/mtree.8\"><code>mtree</code></a>, a tool which can either create such a file from a file\nhierarchy or compare a file hierarchy against a file listing of this kind.\nPerfect for checking that our system is consistent after installing packages.</p>\n<p>In theory, with only archive files and <code>mtree</code>, we now have enough tools to\ninstall independent packages that don’t depend on each other. Or packages that\nhave such simple dependency relations that the user can handle them,\nlike <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-d169c2\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-0ef286\" for=\"sidenote-checkbox-d169c2\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">installation sets on BSDs</label><small id=\"sidenote-0ef286\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>\nOn FreeBSD, the only required set is <code>base.txz</code>. You can throw on top\nother optional sets that don’t have any dependency other than <code>base</code>, such as\n<code>base-dbg</code> for debugging symbols and <code>src</code> for the OS source code.\n<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>. But if we\nwant to be able to automatically install and uninstall dependencies, we need to\nadd another puzzle piece: requirements.</p>\n<h2>Packages requiring packages</h2>\n<p>Here is what happens when I try to install NumPy with pacman:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> pacman <span class=\"token parameter variable\">-S</span> python-numpy</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">resolving dependencies...\n</span></span><span class=\"code-line\"><span class=\"token output\">looking for conflicting packages...\n</span></span><span class=\"code-line\"><span class=\"token output\">\n</span></span><span class=\"code-line\"><span class=\"token output\">Package (6)         New Version  Net Change\n</span></span><span class=\"code-line\"><span class=\"token output\">\n</span></span><span class=\"code-line\"><span class=\"token output\">extra/blas          3.12.1-2       0.74 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">extra/cblas         3.12.1-2       0.34 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">extra/lapack        3.12.1-2      15.06 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">core/mpdecimal      4.0.1-1        0.33 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">core/python         3.13.11-1     67.66 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">extra/python-numpy  2.4.0-1       46.42 MiB\n</span></span></code></pre>\n<p>(<code>-S</code> stands for <em>sync</em>. Pacman works with the <em>sync database</em> when running\nwith <code>-S</code>, i.e. information about all remote packages that is synchronised on\nyour system. This database can be found in <code>/var/lib/pacman/sync/</code>.)</p>\n<p>The dependency tree of <code>python-numpy</code> up to depth 2 looks something like this:</p>\n<figure class=\"mx-auto undefined\"><div class=\"block rounded-md bg-bg-lighter dark:bg-bg-darker\"><a class=\"block dark:hidden overflow-auto\" href=\"https://tudorr.ro/svg/python-numpy-deps-light.svg\"><img alt=\"Directed acyclic dependency graph that has a small height of 3, but is quite wide. Text representation follows this picture.\" loading=\"lazy\" src=\"https://tudorr.ro/svg/python-numpy-deps-light.svg\" class=\"max-w-none w-7xl\"></a><a class=\"hidden dark:block overflow-auto\" href=\"https://tudorr.ro/svg/python-numpy-deps-dark.svg\"><img alt=\"Directed acyclic dependency graph that has a small height of 3, but is quite wide. Text representation follows this picture.\" loading=\"lazy\" src=\"https://tudorr.ro/svg/python-numpy-deps-dark.svg\" class=\"max-w-none w-7xl\"></a></div><figcaption>Dependency graph for python-numpy up to depth = 2. Generated with pacgraph and graphviz.</figcaption></figure>\n<details><summary>Text representation of the graph.</summary><ul>\n<li><code>python-numpy</code>\n<ul>\n<li><code>cblas</code>\n<ul>\n<li><code>blas</code></li>\n<li><code>glibc</code></li>\n</ul>\n</li>\n<li><code>lapac</code>\n<ul>\n<li><code>blas</code></li>\n<li><code>gcc-libs</code></li>\n<li><code>glibc</code></li>\n</ul>\n</li>\n<li><code>python</code>\n<ul>\n<li><code>bzip2</code></li>\n<li><code>expat</code></li>\n<li><code>gdbm</code></li>\n<li><code>libffi</code></li>\n<li><code>libnsl</code></li>\n<li><code>libxcrypt</code></li>\n<li><code>openssl</code></li>\n<li><code>zlib</code></li>\n<li><code>tzdata</code></li>\n<li><code>mpdecimal</code></li>\n</ul>\n</li>\n</ul>\n</li>\n</ul></details>\n<p>In the pacman run from above, pacman computed the complete dependency graph, and\nkept only the nodes corresponding to packages that are not already installed.\n<code>python-numpy</code> obviously needs Python because it’s a Python library.\nIt also needs <a href=\"https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms\"><abbr>Blas</abbr></a>,\nthe de-facto standard in matrix computations.\n(<abbr>Blas</abbr> stands for Basic Linear Algebra Subprograms.)</p>\n<p>This dependency resolution works very well when installing packages!\nBut what happens if I want to uninstall a package that depends on\nother packages?</p>\n<pre class=\"language-shell-session\"><code class=\"language-shell-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> pacman <span class=\"token parameter variable\">-R</span> python-numpy</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">checking dependencies...\n</span></span><span class=\"code-line\"><span class=\"token output\">\n</span></span><span class=\"code-line\"><span class=\"token output\">Package (1)   Old Version  Net Change\n</span></span><span class=\"code-line\"><span class=\"token output\">\n</span></span><span class=\"code-line\"><span class=\"token output\">python-numpy  2.4.0-1      -46.42 MiB\n</span></span></code></pre>\n<p>Wait, where did Python and <abbr>Blas</abbr> go?\nWhy doesn’t pacman remove, them, too? I don’t need them any more!</p>\n<p><span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-928acd\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-bb1f85\" for=\"sidenote-checkbox-928acd\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">With pacman</label><small id=\"sidenote-bb1f85\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>But also with DNF, Zypper, APT, pkg…<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span> you need to explicitly mention that you\nwant to remove packages recursively using the <code>-s</code> flag:</p>\n<pre class=\"language-shell-session\"><code class=\"language-shell-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> pacman <span class=\"token parameter variable\">-Rs</span> python-numpy</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">Package (6)   Old Version  Net Change\n</span></span><span class=\"code-line\"><span class=\"token output\">\n</span></span><span class=\"code-line\"><span class=\"token output\">blas          3.12.1-2      -0.74 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">cblas         3.12.1-2      -0.34 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">lapack        3.12.1-2     -15.06 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">mpdecimal     4.0.1-1       -0.33 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">python        3.13.11-1    -67.66 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">python-numpy  2.4.0-1      -46.42 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span></code></pre>\n<p>Ah, just what I wanted! And it figured out that I don’t need\nPython and those weird sciency libraries.</p>\n<p>Well, how does pacman know that you need a package or not?\nThe answer lies in the local package database. You remember\nfor each package whether it was installed explicitly by the user,\nor implicitly. Explicit means that I spelled the name of the package\nwhen I ran pacman, like I did with <code>python-numpy</code> in the example above.\nImplicit means that I didn’t spell it, but I agreed to install it after\npacman figured out that I need it. Some package managers call implicit installation\n<em>automatic</em> installation. Pacman sets\nan internal <code>%REASON%</code> flag to 1 if the package was installed implicitly.</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">cat</span> /var/lib/pacman/local/cblas-3.12.1-2/desc</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">%NAME%\n</span></span><span class=\"code-line\"><span class=\"token output\">cblas\n</span></span><span class=\"code-line\"><span class=\"token output\">\n</span></span><span class=\"code-line\"><span class=\"token output\">%VERSION%\n</span></span><span class=\"code-line\"><span class=\"token output\">3.12.1-2\n</span></span><span class=\"code-line\"><span class=\"token output\">\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span><span class=\"code-line\"><span class=\"token output\">\n</span></span><span class=\"code-line\"><span class=\"token output\">%REASON%\n</span></span><span class=\"code-line\"><span class=\"token output\">1\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span></code></pre>\n<p>Say I want to keep <code>cblas</code> because I happen to use it for some\nscientific programming project of mine, but I still want\nto remove Numpy and whatever else it needs that I don’t.\nI can first tell pacman to mark <code>cblas</code> as an explicitly-installed\npackage — even though I initially installed it implicitly — and\nthen I can remove Numpy with the command from above.</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> pacman <span class=\"token parameter variable\">-D</span> <span class=\"token parameter variable\">--asexplicit</span> cblas</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">cblas: install reason has been set to 'explicitly installed'\n</span></span><span class=\"code-line\"><span class=\"token output\"></span><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> pacman <span class=\"token parameter variable\">-Rs</span> python-numpy</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">Package (4)   Old Version  Net Change\n</span></span><span class=\"code-line\"><span class=\"token output\">\n</span></span><span class=\"code-line\"><span class=\"token output\">lapack        3.12.1-2     -15.06 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">mpdecimal     4.0.1-1       -0.33 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">python        3.13.11-1    -67.66 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">python-numpy  2.4.0-1      -46.42 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span></code></pre>\n<p>(<code>pacman -D</code> is a special mode for modifying fields in the local package database.)</p>\n<p>Works as intended!</p>\n<p>OK, so now we can install and remove packages with dependencies,\nand we can determine whether packages are needed or not when\nwe want to free up some disk space. But what about the following:\nWhat if a package needs <em>something</em> that is available\nin more than one package? Say I want to install Steam to play\nvideo games. Steam for some reason needs a Vulkan driver to be present,\nmaybe it uses it for rendering. But there are multiple Vulkan drivers\nout there, made by different graphics card manufacturers.\nWhat dependencies does Steam have?</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> pacman <span class=\"token parameter variable\">-Si</span> steam</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">Repository      : multilib\n</span></span><span class=\"code-line\"><span class=\"token output\">Name            : steam\n</span></span><span class=\"code-line\"><span class=\"token output\">Version         : 1.0.0.85-1\n</span></span><span class=\"code-line\"><span class=\"token output\">Description     : Valve's digital software delivery system\n</span></span><span class=\"code-line\"><span class=\"token output\">Architecture    : x86_64\n</span></span><span class=\"code-line\"><span class=\"token output\">URL             : https://steampowered.com/\n</span></span><span class=\"code-line\"><span class=\"token output\">Licenses        : LicenseRef-steam-subscriber-agreement\n</span></span><span class=\"code-line\"><span class=\"token output\">Groups          : None\n</span></span><span class=\"code-line\"><span class=\"token output\">Provides        : None\n</span></span><span class=\"code-line\"><span class=\"token output\">Depends On      : bash  coreutils  curl  dbus  desktop-file-utils  diffutils  freetype2  gcc-libs  gdk-pixbuf2  glibc  hicolor-icon-theme  libxcrypt\n</span></span><span class=\"code-line\"><span class=\"token output\">                  libxcrypt-compat  libxkbcommon-x11  lsb-release  lsof  nss  python  ttf-font  usbutils  vulkan-driver  vulkan-icd-loader  xdg-user-dirs\n</span></span><span class=\"code-line\"><span class=\"token output\">                  xorg-xrandr  xz  zenity  lib32-alsa-plugins  lib32-fontconfig  lib32-gcc-libs  lib32-glibc  lib32-libgl  lib32-libgpg-error  lib32-libnm\n</span></span><span class=\"code-line\"><span class=\"token output\">                  lib32-libva  lib32-libx11  lib32-libxcrypt  lib32-libxcrypt-compat  lib32-libxinerama  lib32-libxss  lib32-nss  lib32-pipewire\n</span></span><span class=\"code-line\"><span class=\"token output\">                  lib32-systemd  lib32-vulkan-driver  lib32-vulkan-icd-loader\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span></code></pre>\n<p>(<code>-i</code> stands for “info”. You can find information about an already-installed\npackage with <code>-Qi</code>. <code>pacman -Q</code> <em>queries</em> the <em>local</em> database.)</p>\n<p>That’s a lot of dependencies! Can you spot the Vulkan driver that Steam\nwants?</p>\n<pre><code class=\"code-highlight\"><span class=\"code-line\">... ttf-font  usbutils  vulkan-driver  vulkan-icd-loader  xdg-user-dirs ...\n</span></code></pre>\n<p>Ah, there it is! It’s just that, <code>vulkan-driver</code>? But I thought there\nare multiple of them?</p>\n<p>There are, indeed, multiple. The <code>vulkan-driver</code> package is not real!</p>\n<h2>Fake packages, virtual packages, capabilities</h2>\n<p>We solve this problem by introducing fake packages that do not\ncontain anything: they are either installed, or not installed,\nbut otherwise they don’t do anything. Other names include\n“virtual packages” and “capabilities”. They act kind of like\ngeneral-purpose booleans in the package manager’s local database.\nOther packages can then assert these booleans to be true\nfor their dependencies to be satisfied.\n“Real” packages can claim that they <em>provide</em>\nthe fake package: the package upholds a guarantee; if you install it,\nthe boolean will be set, the requirement will be satisfied.</p>\n<p>Let’s continue with the Vulkan driver example. As of December 2025,\nthere are <em>ten</em> packages providing a Vulkan driver in the Arch Linux\nrepositories. There is one for Intel GPUs, <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-39ad06\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-dddfa8\" for=\"sidenote-checkbox-39ad06\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">two for Nvidia</label><small id=\"sidenote-dddfa8\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>One provided by Nvidia and one made by the Mesa project,\nwhich is called Nouveau. Clever.<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>, one for use in virtual machines etc.\nThe package containing the Steam client lists <code>vulkan-driver</code> as\na requirement. This is the fake package: there is no package\nnamed <code>vulkan-driver</code> you can download from the Arch Linux repos!\nBut if your computer for example has an Intel GPU, and you already have\nworking graphics, chances are that you installed the right Vulkan\ndriver together with the rest of the graphics stack — likely\nas an implicit dependency.</p>\n<p>But if you did not, and you want to install Steam, pacman will\nask you nicely: which of the following Vulkan drivers do you need?</p>\n<p>Let’s ask pacman some information about the <code>vulkan-driver</code> package\nI have on my system:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> pacman <span class=\"token parameter variable\">-Q</span> vulkan-driver</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">vulkan-intel 1:25.3.2-1\n</span></span></code></pre>\n<p>So I asked for <code>vulkan-driver</code> and I got <code>vulkan-intel</code> back!\nLet’s ask for more information:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> pacman <span class=\"token parameter variable\">-Qi</span> vulkan-driver</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">Name            : vulkan-intel\n</span></span><span class=\"code-line\"><span class=\"token output\">Version         : 1:25.3.2-1\n</span></span><span class=\"code-line\"><span class=\"token output\">Description     : Open-source Vulkan driver for Intel GPUs\n</span></span><span class=\"code-line\"><span class=\"token output\">Architecture    : x86_64\n</span></span><span class=\"code-line\"><span class=\"token output\">URL             : https://www.mesa3d.org/\n</span></span><span class=\"code-line\"><span class=\"token output\">Licenses        : MIT AND BSD-3-Clause AND SGI-B-2.0\n</span></span><span class=\"code-line\"><span class=\"token output\">Groups          : None\n</span></span><span class=\"code-line\"><span class=\"token output\">Provides        : vulkan-driver\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span></code></pre>\n<p>A-ha! <code>Provides: vulkan-driver</code>, that’s the line!\nYou can think of this line as telling pacman\n“mark in the local database that there is a Vulkan driver installed”.</p>\n<p>But wait, it can get trickier: programs are often linked against dynamic libraries.\nThey need files ending in <code>.so</code> to work. We can find out which using <code>ldd</code>:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\">ldd <span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token function\">which</span> <span class=\"token function\">bash</span><span class=\"token variable\">)</span></span></span></span>\n</span><span class=\"code-line\"><span class=\"token output\">linux-vdso.so.1 (0x00007f167b946000)\n</span></span><span class=\"code-line\"><span class=\"token output\">libreadline.so.8 =&gt; /usr/lib/libreadline.so.8 (0x00007f167b7ad000)\n</span></span><span class=\"code-line\"><span class=\"token output\">libc.so.6 =&gt; /usr/lib/libc.so.6 (0x00007f167b59b000)\n</span></span><span class=\"code-line\"><span class=\"token output\">libncursesw.so.6 =&gt; /usr/lib/libncursesw.so.6 (0x00007f167b52c000)\n</span></span><span class=\"code-line\"><span class=\"token output\">/lib64/ld-linux-x86-64.so.2 =&gt; /usr/lib64/ld-linux-x86-64.so.2 (0x00007f167b948000)\n</span></span></code></pre>\n<p>So, bash needs <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-d30097\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-75cb44\" for=\"sidenote-checkbox-d30097\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">every single one</label><small id=\"sidenote-75cb44\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>With the exception\nof <code>linux-vdso.so.1</code>, that one is special. It doesn’t even exist\non disk. You can read more about it from its <a href=\"https://man7.org/linux/man-pages/man7/vdso.7.html\">dedicated man page,\n<code>vdso(7)</code></a>.<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span> of these files to be present in a known\ndirectory in the system — <code>/usr/lib</code> — or it will not work at all. Sounds\nlike the Vulkan driver case! I know for example that <code>libreadline.so.8</code>\ncomes from the <code>readline</code> package, but the name of the exact file the program\nrequires is <code>libreadline.so.8</code>. Maybe there are packages that provide multiple\nlibrary files at once, do I then need to remember the name of the package\nproviding each <code>.so</code> file if I want to make my own package? What if the name changes,\nor if the package is split, or if some packages merge into one? All my program\ncares about is having the right <code>.so</code> present.</p>\n<p>Package capabilities to the rescue, again! On many platforms\n— Arch Linux, dpkg-based distros, rpm-based distros, Alpine Linux etc. —\npackages containing <code>.so</code> libraries advertise them as capabilities.</p>\n<p>Say I need to use some binary I downloaded off the internet, but it complains\nthat it needs <code>libasound.so.2</code>. Let’s find out whether pacman has it for us:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> pacman <span class=\"token parameter variable\">-Ss</span> libasound.so</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">extra/alsa-lib 1.2.14-2\n</span></span><span class=\"code-line\"><span class=\"token output\">    An alternative implementation of Linux sound support\n</span></span></code></pre>\n<p>Great! What does its <code>Provides</code> line look like?</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> pacman <span class=\"token parameter variable\">-Si</span> alsa-lib <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> Provides</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">Provides        : libasound.so=2-64  libatopology.so=2-64\n</span></span></code></pre>\n<p>We can see that it has a version attached. That is important,\nbecause an older version of <code>libasound.so</code> may not be compatible with the\nbinary I downloaded off the internet. This can be the case when a function’s prototype\nchanges, for example when its return type is different in a later version. Such incompatibilities are marked\nby incrementing the number in the filename. This is called a “<a href=\"https://en.wikipedia.org/wiki/Soname\">soname</a> bump”.</p>\n<p>Coming back to my random binary example, we want to make sure that we\ninstall a compatible <code>libasound.so</code>. Thanks to the version information,\nwe can be specific when asking pacman to install it:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> pacman <span class=\"token parameter variable\">-S</span> <span class=\"token assign-left variable\">libasound.so</span><span class=\"token operator\">=</span><span class=\"token number\">2</span></span></span>\n</span><span class=\"code-line\"><span class=\"token output\">resolving dependencies...\n</span></span><span class=\"code-line\"><span class=\"token output\">looking for conflicting packages...\n</span></span><span class=\"code-line\"><span class=\"token output\">\n</span></span><span class=\"code-line\"><span class=\"token output\">Package (3)               New Version  Net Change  Download Size\n</span></span><span class=\"code-line\"><span class=\"token output\">\n</span></span><span class=\"code-line\"><span class=\"token output\">extra/alsa-topology-conf  1.2.5.1-4      0.33 MiB       0.01 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">extra/alsa-ucm-conf       1.2.14-2       0.54 MiB       0.11 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">extra/alsa-lib            1.2.14-2       1.68 MiB       0.48 MiB\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span></code></pre>\n<p><span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-624ed0\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-4c116f\" for=\"sidenote-checkbox-624ed0\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">Red Hat-family Linux distros</label><small id=\"sidenote-4c116f\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>Red Hat Enterprise Linux,\nFedora, CentOS, CentOS Stream, Alma Linux, Rocky Linux etc.<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>\nmake heavy use of package capabilities. These distros use DNF and RPM to manage\npackages. I will show some examples from Alma Linux 9.</p>\n<p>Example 1, a Python library:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> dnf repoquery <span class=\"token parameter variable\">--provides</span> python3-numpy</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">libnpymath-static = 1:1.23.5-1.el9\n</span></span><span class=\"code-line\"><span class=\"token output\">libnpymath-static(x86-64) = 1:1.23.5-1.el9\n</span></span><span class=\"code-line\"><span class=\"token output\">numpy = 1:1.23.5-1.el9\n</span></span><span class=\"code-line\"><span class=\"token output\">numpy(x86-64) = 1:1.23.5-1.el9\n</span></span><span class=\"code-line\"><span class=\"token output\">python-numpy = 1:1.23.5-1.el9\n</span></span><span class=\"code-line\"><span class=\"token output\">python3-numpy = 1:1.23.5-1.el9\n</span></span><span class=\"code-line\"><span class=\"token output\">python3-numpy(x86-64) = 1:1.23.5-1.el9\n</span></span><span class=\"code-line\"><span class=\"token output\">python3.9-numpy = 1:1.23.5-1.el9\n</span></span><span class=\"code-line\"><span class=\"token output\">python3.9dist(numpy) = 1.23.5\n</span></span><span class=\"code-line\"><span class=\"token output\">python3dist(numpy) = 1.23.5\n</span></span></code></pre>\n<p>Here we have capabilities for the python library itself in different forms, with and without the\nCPU architecture, and capabilities with the Python interpreter’s version.</p>\n<p>Example 2, a C library, without headers:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> dnf repoquery <span class=\"token parameter variable\">--provides</span> readline</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">libhistory.so.8\n</span></span><span class=\"code-line\"><span class=\"token output\">libhistory.so.8()(64bit)\n</span></span><span class=\"code-line\"><span class=\"token output\">libreadline.so.8\n</span></span><span class=\"code-line\"><span class=\"token output\">libreadline.so.8()(64bit)\n</span></span><span class=\"code-line\"><span class=\"token output\">readline = 8.1-4.el9\n</span></span><span class=\"code-line\"><span class=\"token output\">readline(x86-32) = 8.1-4.el9\n</span></span><span class=\"code-line\"><span class=\"token output\">readline(x86-64) = 8.1-4.el9\n</span></span></code></pre>\n<p>Here we have two <code>.so</code> files with architecture information. The package also provides\nfiles for running 32-bit executables.</p>\n<p>Example 3, the development files of the same C library:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> dnf repoquery <span class=\"token parameter variable\">--provides</span> readline-devel</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">pkgconfig(readline) = 8.1\n</span></span><span class=\"code-line\"><span class=\"token output\">readline-devel = 8.1-4.el9\n</span></span><span class=\"code-line\"><span class=\"token output\">readline-devel(x86-32) = 8.1-4.el9\n</span></span><span class=\"code-line\"><span class=\"token output\">readline-devel(x86-64) = 8.1-4.el9\n</span></span></code></pre>\n<p>This package provides database files for <a href=\"https://www.freedesktop.org/wiki/Software/pkg-config/\">pkg-config</a>,\na tool which finds the right directories\nfor the header (<code>.h</code>) and library files (<code>.so</code>) when building C and C++ code\nand generates the right compiler flags to use them.</p>\n<p>Example 4, DNF itself:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> dnf repoquery <span class=\"token parameter variable\">--provides</span> dnf</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">dnf = 4.14.0-31.el9.alma.1\n</span></span><span class=\"code-line\"><span class=\"token output\">dnf-command(alias)\n</span></span><span class=\"code-line\"><span class=\"token output\">dnf-command(autoremove)\n</span></span><span class=\"code-line\"><span class=\"token output\">dnf-command(check-update)\n</span></span><span class=\"code-line\"><span class=\"token output\">dnf-command(clean)\n</span></span><span class=\"code-line\"><span class=\"token output\">dnf-command(distro-sync)\n</span></span><span class=\"code-line\"><span class=\"token output\">dnf-command(downgrade)\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span></code></pre>\n<p>DNF can be extended with plug-ins, which can, among other things, extend DNF with new subcommands.\nSay you found a command on a forum to fix the problem you’re having, and it uses <code>dnf diff</code>:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\">dnf <span class=\"token function\">diff</span></span></span>\n</span><span class=\"code-line\"><span class=\"token output\">No such command: diff. Please use /usr/bin/dnf --help\n</span></span><span class=\"code-line\"><span class=\"token output\">It could be a DNF plugin command, try: \"dnf install 'dnf-command(diff)'\"\n</span></span></code></pre>\n<p>You run the suggested command, and:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> dnf <span class=\"token function\">install</span> <span class=\"token string\">'dnf-command(diff)'</span></span></span>\n</span><span class=\"code-line\"><span class=\"token output\">...\n</span></span><span class=\"code-line\"><span class=\"token output\">Installing:\n</span></span><span class=\"code-line\"><span class=\"token output\">dnf-plugin-diff  noarch  2.0-1.el9    epel   21 k\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span></code></pre>\n<p>So easy, and you don’t need to know the name of the package at all! It could be named\nsomething like <code>dnf-plugin-magic-diff-command</code> or something (it is not).</p>\n<p>Example 5, Perl modules:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> dnf repoquery <span class=\"token parameter variable\">--provides</span> perl-IO</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">perl(IO) = 1.43\n</span></span><span class=\"code-line\"><span class=\"token output\">perl(IO::Dir) = 1.41\n</span></span><span class=\"code-line\"><span class=\"token output\">perl(IO::File) = 1.41\n</span></span><span class=\"code-line\"><span class=\"token output\">perl(IO::Handle) = 1.42\n</span></span><span class=\"code-line\"><span class=\"token output\">perl(IO::Pipe) = 1.41\n</span></span><span class=\"code-line\"><span class=\"token output\">perl(IO::Pipe::End)\n</span></span><span class=\"code-line\"><span class=\"token output\">perl(IO::Poll) = 1.41\n</span></span><span class=\"code-line\"><span class=\"token output\">perl(IO::Seekable) = 1.41\n</span></span><span class=\"code-line\"><span class=\"token output\">perl(IO::Select) = 1.42\n</span></span><span class=\"code-line\"><span class=\"token output\">perl(IO::Socket) = 1.43\n</span></span><span class=\"code-line\"><span class=\"token output\">perl(IO::Socket::INET) = 1.41\n</span></span><span class=\"code-line\"><span class=\"token output\">perl(IO::Socket::UNIX) = 1.42\n</span></span><span class=\"code-line\"><span class=\"token output\">perl-IO = 1.43-481.1.el9_6\n</span></span><span class=\"code-line\"><span class=\"token output\">perl-IO(x86-64) = 1.43-481.1.el9_6\n</span></span></code></pre>\n<p>Just like with <code>.so</code> files, you don’t have to know the name of\nthe package providing the modules.</p>\n<p>And the list can go on… You can read more about the various\ncapabilities used in these distros in the <a href=\"https://docs.fedoraproject.org/en-US/packaging-guidelines/\">Fedora Packaging Guidelines</a>.</p>\n<p>OK, now, one more feature that would be very convenient in a package manager…\nYou know how some programs also provide shell completion definitions? I love\nshell completion; I want them for as many programs as possible. But there are\nalso multiple shells around. It would be nice if the shell completions\nwould just install themselves automatically. It would also be nice if, when\nI install a different shell, all the completion definitions for the myriads\nof command line programs I use would also be installed automatically for that\nshell.</p>\n<h2>Auto-install rules</h2>\n<p>The only package manager I know of that supports this is apk,\nfrom <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-b27819\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-f48ddb\" for=\"sidenote-checkbox-b27819\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">Alpine Linux</label><small id=\"sidenote-f48ddb\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>Probably one of the most freeloaded Linux distros. I wonder how much critical infrastructure\nat big name companies runs on it…<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>. Apk is also\nused on Chimera Linux.</p>\n<p>An apk package can have a field named “install-if”, in order to define\nauto-install rules. An example from <code>helix-fish-completion</code>:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> apk info --install-if helix-fish-completion</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">helix-fish-completion-25.07.1-r2 has auto-install rule:\n</span></span><span class=\"code-line\"><span class=\"token output\">helix=25.07.1-r2\n</span></span><span class=\"code-line\"><span class=\"token output\">fish\n</span></span></code></pre>\n<p>When all the packages in that list will be installed,\n<code>helix-fish-completion</code> will also be installed automatically.</p>\n<p>If I now install Helix, <code>helix-bash-completion</code> will\nalso be installed, because I use bash:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> apk <span class=\"token function\">add</span> helix</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">(1/2) Installing helix (25.07.1-r2)\n</span></span><span class=\"code-line\"><span class=\"token output\">(2/3) Installing helix-bash-completion (25.07.1-r2)\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span></code></pre>\n<p>Later, if I want to install the Fish shell,\n<code>helix-fish-completion</code> will be installed automatically.</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> apk <span class=\"token function\">add</span> fish</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">(1/4) Installing fish (4.0.2-r0)\n</span></span><span class=\"code-line\"><span class=\"token output\">(2/4) Installing curl-fish-completion (8.17.0-r1)\n</span></span><span class=\"code-line\"><span class=\"token output\">(3/4) Installing fish-doc (4.0.2-r0)\n</span></span><span class=\"code-line\"><span class=\"token output\">(4/4) Installing helix-fish-completion (25.07.1-r2)\n</span></span><span class=\"code-line\"><span class=\"token output\">...\n</span></span></code></pre>\n<p>The same thing <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-9b4220\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-40416d\" for=\"sidenote-checkbox-9b4220\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">can happen in reverse</label><small id=\"sidenote-40416d\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>\nThis stems from the way apk works. In apk, the <code>add</code> and <code>del</code>\ncommands modify the “world file” at <code>/etc/apk/world</code>.\nThis is a regular text file where each line contains the name of a package\nthat was installed <em>explicitly</em>. <code>apk add</code> adds a line, <code>apk del</code> removes a line.\nAfter the file is modified, <code>apk</code> computes the dependency graph of all the files mentioned in that file —\ntaking into account the auto-install rules — compares it against the graph of all packages that are\n<em>actually</em> installed, and installs and uninstalls packages accordingly to make the second graph match the first.\n<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>: if a package\nwas installed implicitly, and the auto-install rule\ndoesn’t hold any more, then the package will be removed:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> apk del helix</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">(1/3) Purging helix-bash-completion (25.07.1-r2)\n</span></span><span class=\"code-line\"><span class=\"token output\">(2/3) Purging helix-fish-completion (25.07.1-r2)\n</span></span><span class=\"code-line\"><span class=\"token output\">(3/3) Purging helix (25.07.1-r2)\n</span></span></code></pre>\n<p>Giving shell completion as an example was a bit silly, I must admit.\nShell completion files are very small files; it’s not the end\nof the world if you have them around. Maybe it <em>is</em> the end\nof the world for some people, so it’s good to have this feature!</p>\n<p>This feature shines when you want an environment where\nyou do not wish to have man pages and documentation.\nOn Alpine Linux, documentation is split into separate\npackages with names ending with <code>-doc</code>. These packages\nhave auto-install rules:</p>\n<pre class=\"language-sh-session\"><code class=\"language-sh-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\">apk info --install-if zfs-doc</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">zfs-doc-2.4.0-r0 has auto-install rule:\n</span></span><span class=\"code-line\"><span class=\"token output\">docs\n</span></span><span class=\"code-line\"><span class=\"token output\">zfs=2.4.0-r0\n</span></span></code></pre>\n<p>So, if you want to get rid of all documentation, you just\nuninstall the <code>docs</code> package! Conversely, if you want documentation,\nyou install the <code>docs</code> package and the <code>-doc</code> packages corresponding\nto your installed packages will be magically installed.\nAnd whenever you will install a new package that has documentation\navailable, its corresponding <code>-doc</code> package will be installed with it.\nSo neat and elegant!</p>\n<p>OK, but I started this post with FreeBSD not being managed with a package\nmanager before. How does that work?</p>\n<h2>If it looks like a duck, swims like a duck…</h2>\n<p>So FreeBSD has only a handful of packages: <code>base</code>, <code>kernel-dbg</code>, <code>base-dbg</code>, <code>ports</code> etc.\nOnly <code>base</code> is required, and all the other packages depend on <code>base</code> only.\nSo dependency resolution is really simple, it fits in your head.</p>\n<p>But what about package upgrades? And, what about configuration files? Your\nOS comes with some default configs. What happens when\nyou modify a config, and a new version of the OS comes with new defaults?</p>\n<p>That’s so 1997, I hear you say. Haven’t you heard about <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-ba19cc\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-b1aab1\" for=\"sidenote-checkbox-ba19cc\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">having a default config file and a <em>new, different</em> file that contains your overrides</label><small id=\"sidenote-b1aab1\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>\nLike <code>/etc/ssh/sshd_config</code> and\n<code>/etc/ssh/sshd_config.d/*</code>. <span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>? And in 2025 we have these things\ncalled “container images”, we upgrade our OSes atomically. OK, a package\ninstalls a config and I override it. So what? When there’s a new package\nversion, I don’t apply it on top, I just reinstall the whole OS! Why would I\neven care…</p>\n<p>There used to be a time when sysadmins used to care about bandwidth,\nstorage and electricity and they didn’t reinstall their entire OS for each\npackage update — figuratively, that is pretty much what happens when you rebuild\na container image. As for why config files weren’t split in defaults and overrides,\nbeats me. I am too young to know that.</p>\n<p>But OK, this is an actual problem. You have the old default, the new default, and\nwhatever the user modified in, say, <code>/etc/resolv.conf</code>, or <code>/etc/passwd</code>. What do you do?</p>\n<p>RPM and <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-be1866\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-af9215\" for=\"sidenote-checkbox-be1866\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">pacman</label><small id=\"sidenote-af9215\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>\nSee <a href=\"https://wiki.archlinux.org/index.php?title=Pacman/Pacnew_and_Pacsave&oldid=835718\">Pacnew and Pacsave</a>.\n<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>, for example, just save the new version with a new extension and\nkeep your changes untouched. Hopefully you will see the warning on the screen\nnext to the other hundreds of scrolling lines and you will check\nwhether the file needs any manual intervention.</p>\n<p>On FreeBSD, there is a tool named <a href=\"https://man.freebsd.org/cgi/man.cgi?query=etcupdate&apropos=0&sektion=8&manpath=FreeBSD+15.0-RELEASE&arch=default&format=html\">etcupdate</a> that you are supposed\nto run after OS upgrades. But you usually don’t run it manually, unless you\ninstalled the OS from source or something weird like that. The conventional\nroute is to run <a href=\"https://docs.freebsd.org/en/books/handbook/cutting-edge/#updating-upgrading-freebsdupdate\"><code>freebsd-update</code></a> which will take care of\ndownloading the new archives, extracting them in root, removing old files and so\non. Hey, that looks like a package manager!</p>\n<p>Indeed it does, albeit a purpose-built one. It was made to install new versions of that couple\nof OS archives, rollback from an upgrade, and nothing else, basically.\nAnd it does some fancy magic to only download binary diff files to save space and bandwidth, neat.\nBut I assume that it must be pretty hard to maintain it, because the effort that goes into this tool\ndoesn’t translate into improvements for pkg, which was used — until now — only to install third-party software.\nAnd, you have a problem if you want a slim OS install. What if you don’t need everything that comes\nin the <code>base</code> package? On Linux distros you can install a small set of packages and be done with it.\nOn FreeBSD, you had to recompile the OS from source for that! That’s a bit of a waste, and it requires you\nto understand how the build system works before you even try to obtain a smaller install. That’s quite the overhead.\nWith the new pkg-based installation workflow, if I don’t need the ZFS filesystem for example, I can just\ndo <code>pkg delete FreeBSD-zfs</code> and it’s gone.</p>\n<p>How did we end up here? I didn’t live through the good old days when Linux just\nbecame a thing and the BSD world split into three, and I did not read any UNIX\nhistory book on this subject either — if it exists.\nBut I can speculate that the Linux world, being broken into multiple, seemingly independent projects\nthat <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-9dfc77\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-df9615\" for=\"sidenote-checkbox-9dfc77\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">compete with each other</label><small id=\"sidenote-df9615\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>Not literally, I hope that there is no\nKPI reporting with the conversion rate of Plasma users to Gnome.<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>, felt the need\nfor a program that allows users to mix and match software before the BSD world needed.\nUnlike Linux distros, BSD operating systems are one whole product, developed by one team per operating system,\nso they have easier control of the software developed under their umbrella. They built their own\ncustom tools — <code>etcupdate</code>, <code>freebsd-update</code> etc. — and those worked well, then they created\nimproved tools like <code>pkg</code>. To me it seems like a good, natural progression.</p>\n<p>If there is anything to be learned from this, is that package managers started from the basic requirement\nof installing (and removing!) software and slowly added smart features on the go.\nThere you have it: an overview of how package managers can be helpful!</p>","date_published":"Thu, 25 Dec 2025 18:52:11 GMT"},{"id":"https://tudorr.ro/blog/2025-03-29-faster-zypper/","url":"https://tudorr.ro/blog/2025-03-29-faster-zypper/","title":"How to Speed Up Package Installations on OpenSUSE","content_html":"<p><em>Currently, this article applies only to openSUSE Tumbleweed.</em></p>\n<p><span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-768f6e\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-68cddf\" for=\"sidenote-checkbox-768f6e\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">Zypper</label><small id=\"sidenote-68cddf\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>The default package manager on OpenSUSE and\nSUSE Linux Enterprise.<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span> <a href=\"https://news.opensuse.org/2025/03/27/zypper-adds-experimental-parallel-downloads/\">just added support for parallel\ndownloads</a>, which can make <a href=\"https://lists.opensuse.org/archives/list/factory@lists.opensuse.org/thread/LOCZIG43MFJSTUIQ3VH2CRSYRCBNR4O7/\">large upgrades become\ntwice as fast</a>. Here I will write about some more tweaks I\ndiscovered that make Zypper even quicker.</p>\n<h2>In short</h2>\n<ol>\n<li>\n<p>Make sure you are using the newest Zypper and libzypp:</p>\n<pre class=\"language-shell-session\"><code class=\"language-shell-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> <span class=\"token function\">zypper</span> update libzypp <span class=\"token function\">zypper</span></span></span>\n</span></code></pre>\n</li>\n<li>\n<p>Install the <code>openSUSE-repos</code> package to use CDN-based repositories:</p>\n<pre class=\"language-shell-session\"><code class=\"language-shell-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> <span class=\"token function\">zypper</span> <span class=\"token function\">install</span> openSUSE-repos</span></span>\n</span></code></pre>\n<p>This applies multiple tweaks in one, see below for a detailed explanation.</p>\n</li>\n<li>\n<p>Enable the <strong>experimental</strong> single RPM transaction mode:</p>\n<pre class=\"language-shell-session\"><code class=\"language-shell-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"export ZYPP_SINGLE_RPMTRANS=1\"</span> <span class=\"token operator\">&gt;</span> /etc/profile.d/my-zypp-settings.sh</span></span>\n</span></code></pre>\n</li>\n<li>\n<p>If you run Zypper with <code>sudo</code>: let <code>sudo</code> pass the values\nof <code>ZYPP_PCK_PRELOAD</code> and <code>ZYPP_SINGLE_RPMTRANS</code>.</p>\n<p>Run <code>sudo visudo -f /etc/sudoers.d/zypper-vars</code> to safely\nedit the file and write the following:</p>\n<pre class=\"language-plain\"><code class=\"language-plain code-highlight\"><span class=\"code-line\">Defaults env_keep += \"ZYPP_PCK_PRELOAD ZYPP_SINGLE_RPMTRANS\"\n</span></code></pre>\n</li>\n<li>\n<p>Log out and log back in for environment variable changes to take effect.</p>\n<p>You should not skip this step if you preferred to not turn on the experimental\nsingle RPM transaction mode from above. Step 2 also does some environment\nvariable changes behind the scenes.</p>\n</li>\n</ol>\n<h2><code>openSUSE-repos</code></h2>\n<p>Installing this package replaces the default Zypper repo definitions with ones\nderived from openSUSE’s <a href=\"https://en.opensuse.org/openSUSE:Standards_Repository_Index_Service\">Repository Index Service</a>, which is\na facility provided by Zypper to <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-7b3295\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-a069f8\" for=\"sidenote-checkbox-7b3295\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">keep repository definitions up to date</label><small id=\"sidenote-a069f8\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>\nAccording to the OpenSUSE Wiki page about Repository Index Service,\nthe protocol is an extension of <a href=\"https://en.opensuse.org/openSUSE:Standards_NU_Service\">Novell Update</a>,\npart of the <a href=\"https://www.novell.com/documentation/zenworks-24.4/zen_quickstart/data/brio3dh.html\">Zenworks Linux Management</a> “client-server protocol”.\nZenworks Linux Management is a configuration management product that\nallows one to “embrace and extend” Linux in their enterprise environment.\nThese might all sound unrelated, but openSUSE is partly developed by SUSE,\nwhich is a company that used to be part of the Attachmate Group,\nwhich contained Novell, and this group was purchased by Micro Focus,\nwhich is now named OpenText. You can see part of these companies’\nhistories in this inoffensive feature! (<a href=\"https://en.wikipedia.org/w/index.php?title=Micro_Focus&oldid=1262873732#History\">Source</a>.)\n<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>.\nThese repositories are basically the same as the default ones, the only difference\nbeing that they are <a href=\"https://news.opensuse.org/2023/07/31/try-out-cdn-with-opensuse-repos/\">served from a CDN</a>.\nFrom the <a href=\"https://news.opensuse.org/2025/03/27/zypper-adds-experimental-parallel-downloads/\">post that announces Zypper’s parallel download abilities</a>,\nwe learn that this package also enables both parallel downloads and the new\nfile download backend, which reduces overhead.</p>\n<p>After installing this package, you need to log out and log back in\nbecause parallel downloads are enabled by means of installing\na snippet in <code>/etc/profile.d/</code> which toggles the right environment variable.</p>\n<h2>Single RPM transaction mode</h2>\n<p>I haven’t looked into it yet, but Zypper for some reason runs a separate\nRPM transaction for every package it installs. This can quickly add overhead\nwhen you install a package that needs many dependencies, or when upgrading\nmany packages at once. <a href=\"https://lists.opensuse.org/archives/list/factory@lists.opensuse.org/thread/YEFSZZY7FRGJPGPWKNCUT3UXQWVENOCL/#3CKPRCIT2ZLJFOY24D76IQIGYCLOGIWN\">For a couple of years already</a>, Zypper has been offering\nan experimental mode in which all package installations done by a command\nwould be done in a single transaction, toggled by the <code>ZYPP_SINGLE_RPMTRANS</code>\nenvironment variable.</p>\n<p><strong>Be aware that some package installations can fail in this mode!</strong>\nPackages are not always tested for installing with this experimental feature turned on.\nIf this happens, run Zypper like so:</p>\n<pre class=\"language-shell-session\"><code class=\"language-shell-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token function\">sudo</span> <span class=\"token assign-left variable\">ZYPP_SINGLE_RPMTRANS</span><span class=\"token operator\">=</span><span class=\"token number\">0</span> <span class=\"token function\">zypper</span> <span class=\"token punctuation\">..</span>. <span class=\"token comment\"># the rest of your command</span></span></span>\n</span></code></pre>\n<p>Another catch is that <strong>YaST might not be able to do some package management operations</strong> any more,\nas highlighted in the <a href=\"https://lists.opensuse.org/archives/list/factory@lists.opensuse.org/thread/YEFSZZY7FRGJPGPWKNCUT3UXQWVENOCL/#3CKPRCIT2ZLJFOY24D76IQIGYCLOGIWN\">announcement of this feature</a>.</p>\n<h2><code>sudo</code> config</h2>\n<p>When run normally, <code>sudo</code> provides only a limited set of\nenvironment variables to the program you want to run,\neven when those variables are set system-wide in <code>/etc/profile.d</code>.\nYou can run <code>sudo</code> with the <code>-i</code> flag to run the command in a login shell,\nwhich will load files from <code>/etc/profile.d</code>, or you can configure\n<code>sudo</code> to pass the environment variables we use to tweak Zypper.\nI prefer the latter, because it brings fewer changes to the way\n<code>sudo</code> runs commands.</p>","date_published":"Sun, 30 Mar 2025 14:34:01 GMT"},{"id":"https://tudorr.ro/blog/2024-12-07-ansible-service-in-jail/","url":"https://tudorr.ro/blog/2024-12-07-ansible-service-in-jail/","title":"How to manage services in FreeBSD jails with Ansible","content_html":"<p><em>If you just want to see how to do it without the backstory,\nclick <a href=\"https://tudorr.ro/blog/2024-12-07-ansible-service-in-jail/#the-nice-way\">here</a> <strong>if you’re running Ansible ≥ 2.18.1</strong>,\nor click <a href=\"https://tudorr.ro/blog/2024-12-07-ansible-service-in-jail/#the-hack\">here</a> if your Ansible version is older.</em></p>\n<p>I didn’t write much about it yet, but a couple of months ago I scrapped my NixOS\nhomelab setup in favour of a FreeBSD VM. I’m enjoying it so far — it’s the\nexact opposite of NixOS in my opinion. Instead of playing with abstractions\nstacked into each other like a matryoshka doll, I have to make my own little\nabstractions to suit my own, simpler use cases.</p>\n<p>One of these abstractions is managing <em>jails</em>. They are comparable to Linux\nnamespaces: you can use them to make something akin to <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-433b36\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-023deb\" for=\"sidenote-checkbox-433b36\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">containers</label><small id=\"sidenote-023deb\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>\nThere have been various technologies called “containers” throughout\nhistory. The one we know and love the most today, <a href=\"https://opencontainers.org/\">OCI Containers</a>\n— or “Docker” containers — are a standardised way of\npackaging, distributing, and running software. It’s not just about running\nprograms isolated.\n<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span> — little isolated systems that live on the same host.\nIn the FreeBSD fan world, a “jail” can often mean some kind of container.</p>\n<p>Ansible modules made for FreeBSD usually have a <code>jail</code> argument, for example to\nset <code>rc</code> arguments in a jail, or to install a package with <code>pkg</code>. The <code>service</code>\nmodule, however, does not. What if you want to manage services inside such a jail?</p>\n<p>Luckily, I discovered a method that lets you do just\nthat, without reinventing the wheel! This method works only <strong>if you run\nAnsible ≥ 2.18.1</strong>. If you don’t, I also discovered a hack that works around\nthis version requirement.</p>\n<p><em>Why that version specifically?\nBecause it contains <a href=\"https://github.com/ansible/ansible/pull/81377\">this bugfix</a>.</em></p>\n<h2 id=\"the-nice-way\">The nice way (Ansible ≥ 2.18.1)</h2>\n<pre class=\"language-yaml\"><code class=\"language-yaml code-highlight\"><span class=\"code-line\"><span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Start SSHD in myjail\n</span><span class=\"code-line\">  <span class=\"token key atrule\">ansible.builtin.service</span><span class=\"token punctuation\">:</span>\n</span><span class=\"code-line\">    <span class=\"token key atrule\">arguments</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"-j myjail\"</span>\n</span><span class=\"code-line\">    <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> sshd\n</span><span class=\"code-line\">    <span class=\"token key atrule\">state</span><span class=\"token punctuation\">:</span> started\n</span></code></pre>\n<p>When <code>service</code> is called with the <code>-j</code> flag, it will work inside a jail;\nsee the <a href=\"https://man.freebsd.org/cgi/man.cgi?query=service&apropos=0&sektion=8&manpath=FreeBSD+14.2-RELEASE&arch=default&format=html\">man page</a>.</p>\n<p>Here, whatever is given to <code>arguments</code> is going to be then given to the <code>service</code> command,\nwhich Ansible calls behind the scenes. Ansible does something like this:</p>\n<pre class=\"language-bash\"><code class=\"language-bash code-highlight\"><span class=\"code-line\"><span class=\"token keyword\">if</span> <span class=\"token operator\">!</span> <span class=\"token function\">service</span> <span class=\"token parameter variable\">-j</span> myjail onestatus sshd<span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span> <span class=\"token comment\"># check if service is already running</span>\n</span><span class=\"code-line\">  <span class=\"token function\">service</span> <span class=\"token parameter variable\">-j</span> myjail onestart sshd <span class=\"token comment\"># if not, start it</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">fi</span>\n</span></code></pre>\n<p>This can be seen in <a href=\"https://github.com/ansible/ansible/blob/3223e442abbf889bffc68ee1b3f066c2e500ce2c/lib/ansible/modules/service.py#L1014\">Ansible’s source code</a>.</p>\n<p>If you want to enable a service, <strong>do not use the <code>enabled</code> parameter!</strong>\nInstead, call the <code>sysrc</code> module first:</p>\n<pre class=\"language-yaml\"><code class=\"language-yaml code-highlight\"><span class=\"code-line\"><span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Enable SSHD in myjail\n</span><span class=\"code-line\">  <span class=\"token key atrule\">community.general.sysrc</span><span class=\"token punctuation\">:</span>\n</span><span class=\"code-line\">    <span class=\"token key atrule\">jail</span><span class=\"token punctuation\">:</span> myjail\n</span><span class=\"code-line\">    <span class=\"token key atrule\">state</span><span class=\"token punctuation\">:</span> present\n</span><span class=\"code-line\">    <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> sshd_enable\n</span><span class=\"code-line\">    <span class=\"token key atrule\">value</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"YES\"</span>\n</span><span class=\"code-line\">\n</span><span class=\"code-line\"><span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Start SSHD in myjail\n</span><span class=\"code-line\">  <span class=\"token key atrule\">ansible.builtin.service</span><span class=\"token punctuation\">:</span>\n</span><span class=\"code-line\">    <span class=\"token key atrule\">arguments</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"-j myjail\"</span>\n</span><span class=\"code-line\">    <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> sshd\n</span><span class=\"code-line\">    <span class=\"token key atrule\">state</span><span class=\"token punctuation\">:</span> started\n</span></code></pre>\n<p>The reason for this is that there is a bug in the way Ansible sets the required <code>rc</code> variable.\nAnsible will run <code>sysrc</code>, <a href=\"https://github.com/ansible/ansible/blob/3223e442abbf889bffc68ee1b3f066c2e500ce2c/lib/ansible/modules/service.py#L1073\">but without the <code>-j</code> parameter which lets it work in a jail</a>\n(<a href=\"https://man.freebsd.org/cgi/man.cgi?query=sysrc&apropos=0&sektion=8&manpath=FreeBSD+14.2-RELEASE&arch=default&format=html\"><code>sysrc(8)</code></a>).</p>\n<h2 id=\"the-hack\">The hack (Ansible &lt; 2.18.1)</h2>\n<pre class=\"language-yaml\"><code class=\"language-yaml code-highlight\"><span class=\"code-line\"><span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Start SSHD in myjail\n</span><span class=\"code-line\">  <span class=\"token key atrule\">ansible.builtin.service</span><span class=\"token punctuation\">:</span>\n</span><span class=\"code-line\">    <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"-j myjail sshd\"</span>\n</span><span class=\"code-line\">    <span class=\"token key atrule\">state</span><span class=\"token punctuation\">:</span> started\n</span></code></pre>\n<p>Similarly, if you want to enable a service before starting it, you can use <code>sysrc</code>:</p>\n<pre class=\"language-yaml\"><code class=\"language-yaml code-highlight\"><span class=\"code-line\"><span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Enable SSHD in myjail\n</span><span class=\"code-line\">  <span class=\"token key atrule\">community.general.sysrc</span><span class=\"token punctuation\">:</span>\n</span><span class=\"code-line\">    <span class=\"token key atrule\">jail</span><span class=\"token punctuation\">:</span> myjail\n</span><span class=\"code-line\">    <span class=\"token key atrule\">state</span><span class=\"token punctuation\">:</span> present\n</span><span class=\"code-line\">    <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> sshd_enable\n</span><span class=\"code-line\">    <span class=\"token key atrule\">value</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"YES\"</span>\n</span><span class=\"code-line\">\n</span><span class=\"code-line\"><span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Start SSHD in myjail\n</span><span class=\"code-line\">  <span class=\"token key atrule\">ansible.builtin.service</span><span class=\"token punctuation\">:</span>\n</span><span class=\"code-line\">    <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"-j myjail sshd\"</span>\n</span><span class=\"code-line\">    <span class=\"token key atrule\">state</span><span class=\"token punctuation\">:</span> started\n</span></code></pre>\n<p>Why is this needed? Because of a bug, which was fixed in version 2.18.1.\nAnsible would add the value of the <code>arguments</code> parameter <em>after</em> the name\nof the service when calling <code>service</code>, which is not correct. Behind the scenes,\nAnsible would run:</p>\n<pre class=\"language-bash\"><code class=\"language-bash code-highlight\"><span class=\"code-line\"><span class=\"token comment\"># This is wrong!</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">if</span> <span class=\"token operator\">!</span> <span class=\"token function\">service</span> onestatus sshd <span class=\"token parameter variable\">-j</span> myjail<span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span> <span class=\"token comment\"># check if service is already running</span>\n</span><span class=\"code-line\">  <span class=\"token function\">service</span> onestart sshd <span class=\"token parameter variable\">-j</span> myjail <span class=\"token comment\"># if not, start it</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">fi</span>\n</span></code></pre>\n<p>Here, the <code>-j myjail</code> argument would be passed to the init script, in this case\n<code>/etc/rc.d/sshd</code>. This is not what we want! The <code>-j</code> argument is supposed to be\ngiven to the <code>service</code> tool, as you can see in <a href=\"https://man.freebsd.org/cgi/man.cgi?query=service&apropos=0&sektion=8&manpath=FreeBSD+14.2-RELEASE&arch=default&format=html\">the man page</a>.</p>","date_published":"Sat, 07 Dec 2024 12:35:19 GMT"},{"id":"https://tudorr.ro/blog/2024-09-23-september-2024-update/","url":"https://tudorr.ro/blog/2024-09-23-september-2024-update/","title":"September 2024 Update","content_html":"<figure class=\"max-w-2xl mx-auto undefined\"><a href=\"https://tudorr.ro/static/pictures/blackforest1.avif\"><img alt=\"Picture of a creek in a forest with moss-covered stones, a foot path, trees and other kinds of forest vegetation.\" loading=\"lazy\" src=\"https://tudorr.ro/static/pictures/blackforest1.avif\"></a><figcaption>A picture I took while hiking through the Black Forest.</figcaption></figure>\n<p>It’s been a while since I posted on my blog! I’ve been wanting to write some\narticles, but I did not have any fleshed-out ideas, hence the break. I keep\nseeing the same advice from bloggers and other writers on the Internet: <q>Just\nstart writing!</q> What to write about, though? <q>Anything!</q></p>\n<p>The first thought I had when I opened my text editor to write this post was\nthat it’s been a while since I wrote something, might as well write about what\nhappened in the meantime!</p>\n<p>Since I wrote my last blog post, I’ve been busy with finishing my bachelor’s\ndegree, summer holidays, and starting my master’s. I’m thankful for my thesis\nsupervisor, who gave me plenty of freedom, which led to a thesis about Ken\nThompson’s <a href=\"https://www.cs.cmu.edu/~rdriley/487/papers/Thompson_1984_ReflectionsonTrustingTrust.pdf\">“Trusting Trust” attack</a> and reproducible builds.\nI attacked the (then) latest version of the Go compiler to target Go’s own\nreproducibility checker took, <a href=\"https://pkg.go.dev/golang.org/x/build/cmd/gorebuild\">gorebuild</a>. Ironically enough,\nthere are no official, <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-20eb2f\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-2a82b2\" for=\"sidenote-checkbox-20eb2f\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">signed</label><small id=\"sidenote-2a82b2\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>Or at least hashed.<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span> gorebuild binaries, so targetting it with such an attack is\nactually practical: the “official” way to install gorebuild is to just compile\nit from source. My attack changed the compiler to make it detect when\nit’s compiling gorebuild to then change its logic to lie to the user that the\nattacked compiler actually matches the official hashes as published on the Go\nwebsite. It is on my list to write a nice post about this attack and challenges\nI encountered. If you find this interesting, you might be interested in reading\nmy thesis <a href=\"https://github.com/tudurom/bsc-thesis/releases/tag/final\">here</a>. There are some extras in there too, such as <code>evilgen</code>\n— <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-54134d\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-84b6d6\" for=\"sidenote-checkbox-54134d\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">a code regenerator generator</label><small id=\"sidenote-84b6d6\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>\nIt generates source code that can regenerate itself. The source code is <a href=\"https://github.com/tudurom/bsc-thesis/tree/master/evilgen\">in the repo</a>, together with some examples.\n<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span> — and some code examples.</p>\n<p>After I was done with the academic year, I went on a five week-long holiday\nto Romania and Germany. Of those five weeks, three were spent without my laptop,\nwhich really gave my mind some rest after all the studying. Visited family,\nsaw some nature, hiked; great holiday all-around.</p>\n<p>Another thing that I felt was lacking is that my blogging setup did not\nfully reflect the way I think, which made writing feel not that great. I took\ninspiration from other websites, pondered a bit, and I realised that what I\nneeded all this time were sidenotes. You can see them in action on this page!\nI wanted to have a way to mention all the tid-bits and fun facts and little\ndetails that I like to mention when I have a conversation, because these are\nall things that can make a discussion even more interesting.</p>\n<p>Normally one might use footnotes for that, but I find them quite distracting on\nwebsites, actually. On paper you can switch you gaze really quickly between the\nbody text and the footnotes, but on screens — especially little ones — that is\nnot the case, in my opinion<sup><a href=\"https://tudorr.ro/blog/2024-09-23-september-2024-update/#user-content-fn-1\" id=\"user-content-fnref-1\" data-footnote-ref aria-describedby=\"footnote-label\">1</a></sup>. A web page has an arbitrary length, of which\nonly <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-051097\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-d54e51\" for=\"sidenote-checkbox-051097\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">a part of</label><small id=\"sidenote-d54e51\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span><a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/Viewport_concepts\">The viewport</a>.<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span> is\nactually displayed to your eyes. I find it really easy to lose the position I’m\nat in the text when I look at a footnote on the web — it’s not like I can just\nleave my finger there and then take a quick look at the bottom of the screen.\nThere are ways to improve this, like having the footnote indicator be a link\nthat makes your browser scroll enough to bring it on the screen. Some\ncleverer implementations even have a link that sends you back right at the end of\nthe footnote. But then the line containing the footnote will be stuck to the top\nof your browser window, which is also very annoying!</p>\n<p>But this still doesn’t fix my main gripe I have with footnotes on\nthe web: they require you to move the page around! It’s annoying, it’s confusing and dizzying\neven, and this is the web after all; we can make good use of the differences\nbetween the two media. One of them is that desktop screens are wide: there is\nplenty of space for some notes that go along with the text. On phones, one may use CSS\nto make the sidenotes inline. I actually made them interactive on my website for a reason:\nphones are pretty small, and can only display a limited amount of text when using a decent\nfont size. I wanted to have the body text have priority when using this space, and let\nthe reader optionally reveal the notes by tapping on the associated text.\nI figured this may not be the most discoverable UI, so I just put a note at the\ntop of the page that is only visible when using a narrow screen.</p>\n<p>The sidenotes also render perfectly fine in reader mode, and\n<span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-4fca86\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-446334\" for=\"sidenote-checkbox-4fca86\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">should work well with screen readers, too</label><small id=\"sidenote-446334\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>I haven’t tested that, yet.<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>.\nMy main inspiration for my implementation was <a href=\"https://www.kooslooijesteijn.net/blog/sidenotes-without-js\">this article by Koos Looijesteijn</a>.\nI implemented the sidenotes as a JSX component, and I used Tailwindcss for the styling.\nMy website is completely statically-generated, there is no JSX on your browser right now,\ndon’t worry! You can find the source code for this component <a href=\"https://git.sr.ht/~tudor/tudorr.ro/tree/dcbe06237d5ce845b12977837496cdefbc5f058d/item/src/_components/Sidenote.tsx\">here</a>.</p>\n<p>If you have any suggestions, praise, (constructive) criticism or just want to say something\nabout my sidenotes, I’d be very happy to read about it!\nYou may send me an e-mail or a message on Mastodon, check <a href=\"https://tudorr.ro/#links\">the home page</a> for links!</p>\n<p>Next to all of that, I started a new study at the university: the <a href=\"https://vu.nl/csec\">Computer Security</a> master’s degree.\nIt’s about low-level systems security! It’s currently going well, I really like it, I feel like I’m already learning a ton.\nI’m excited for the courses that come next! I really want to write more here, I’ll see how that goes: the study\nis keeping me rather busy. Till next time!</p>\n<section data-footnotes class=\"footnotes\"><h2 class=\"sr-only\" id=\"footnote-label\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-1\">\n<p><em>See?</em> <a href=\"https://tudorr.ro/blog/2024-09-23-september-2024-update/#user-content-fnref-1\" data-footnote-backref=\"\" aria-label=\"Back to reference 1\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>","date_published":"Thu, 26 Sep 2024 09:32:31 GMT"},{"id":"https://tudorr.ro/blog/2024-04-04-zoomer-tries-rss/","url":"https://tudorr.ro/blog/2024-04-04-zoomer-tries-rss/","title":"Zoomer Tries RSS: In Praise of Yarr","content_html":"<p><a href=\"https://github.com/nkanaev/yarr\">Yarr</a> is, according to its GitHub page, <q>yet another rss reader</q>.\nBut I think this description doesn’t do it justice.</p>\n<p>I started using a feed reader less than half a year ago, to easily keep track\nof new content I enjoy on the internet without depending on social media.\nWeb feeds are, in 2024 terms, old technology: according to the <a href=\"https://www.rssboard.org/rss-history\">RSS Advisory Board</a>,\nRSS, one of the main web feed formats available, has been published in 1999, and it’s not\neven the first web syndication format ever made.</p>\n<p>I’m a <a href=\"https://en.wikipedia.org/wiki/Generation_Z\">Gen Z-er</a>: I was a toddler when RSS reached its 2.0 milestone,\nand when Apple popularised RSS for podcasts <a href=\"https://www.macworld.com/article/176125/podcastingfirstlook.html\">by adding support in iTunes</a>.\nI was in middle school when Google Reader was shut down. When I started using the internet\nby myself, it was already dominated by social media, which is still the dominant\nsource of information for many people around me, younger and older alike.</p>\n<p>Getting all my information from social media got really tiring; I was using Reddit mostly,\nand a carefully curated set of information-oriented Instagram profiles — so nice to read pictures of text! I didn’t really like wasting my time doom-scrolling\ninstead of doing other things that could generate some creative output — like\nwriting on this blog, maybe — or\ndoing some fun coding. Or maybe I could tidy up my kitchen, read a book and actually learn\nsome interesting, useful things laid out in a long-form structure. Heck, even getting bored\nis pleasant, it makes your brain think!</p>\n<p>I ditched Instagram altogether, and then I also uninstalled the Reddit client I was using on my phone.\nI still read some subreddits from time to time, but now it requires me to manually type in\nthe subreddit I want to read, scroll a bit,  and then become very frustrated with the new web UI —\nwhich is totally not intentionally bad to make you download the mobile app.\nNot only that, but I also change the URL to <code>old.reddit.com</code> when needed (even harder when doing it on my phone…).\nAnd then I only read without ever logging in, which is why using Old Reddit is a requirement.\nYou can easily see that it’s not the best reading experience for the web.</p>\n<p>With this in mind, I remembered RSS and feeds are a thing, so I figured I should set up a reader.\nI’ve never used one before, but the concept sounded promising: I can subscribe to blogs and news outlets, directly!</p>\n<p>I had three requirements for choosing one:</p>\n<ul>\n<li>Simple, non-distracting UI.</li>\n<li>Has some kind of feature to extract relevant content from web pages — many feeds truncate the content.</li>\n<li>Doesn’t require me to do any kind of syncing when using multiple devices to read (laptop and phone). A self-hosted web app should be fine.</li>\n</ul>\n<h2>First try: Feeder</h2>\n<p><a href=\"https://f-droid.org/packages/com.nononsenseapps.feeder/\">Feeder</a> is a pretty Android app, it implements the newest <a href=\"https://m3.material.io/\">Material 3</a> design language, and it’s generally very pleasant to use.\nIt definitely satisfies the first requirement. And it knows how to extract the content from a web page, very nice!\nUnfortunately, it’s only for your phone. If you want to change your reader app, it does support importing and exporting OPML files; that is pretty sweet,\nbut I don’t want to be tied to my phone to read the news and the blogs I want to follow.</p>\n<h2>Second try: Miniflux</h2>\n<p><a href=\"https://miniflux.app/\">Miniflux</a> is what I’ve been using up until I discovered Yarr, actually. It’s wonderful software: it’s a web application, very easy to self-host, and it’s <a href=\"https://miniflux.app/features.html\">quite featureful</a>.\nIt has a <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-4c50ef\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-d77051\" for=\"sidenote-checkbox-4c50ef\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">very simple UI</label><small id=\"sidenote-d77051\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>\nSome might say “arcane”.\n<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>, stays out of your way, no useless spacing, readable, accessible, it’s just great.\nLooks similar to <a href=\"https://lobste.rs/\">Lobsters</a> and <a href=\"https://sourcehut.org/\">sourcehut</a>, I really like this style. It even has the required manifests to make it a Progressive Web App,\nmaking it look like an app on your phone. It’s just an all-around pleasant experience to use Miniflux.\nIt’s very easy to self-host: being written in Go, the program is just a single statically-linked executable that you can drop on a server.</p>\n<p>I have one big gripe with Miniflux, though: it only supports PostgreSQL, though <a href=\"https://miniflux.app/opinionated.html#postgresql\">very understandably so</a>.\nIt’s much easier to support a single configuration, a very respectable one in this case.\nMiniflux also has a commercial offering, so it shouldn’t come as a surprise that it’s in its best interest to have a well-tested and reliable storage layer.</p>\n<p>It’s just that I prefer having as many services as possible use SQLite when it comes to my self-hosted services. One of the main downsides\nof self-hosting is that you have to take care of your own services — who would’ve thought! I really, really don’t want to take care of a PostgreSQL\ncluster, even if it’s a single-node one. You might argue that it’s just one node, what’s to be taken care of? Identity maps, backups, authentication…\nI can just use SQLite, have all my data in one file (per service), you cannot access it remotely, so that’s covered,\nbackups are a <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-d25d23\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-59242a\" for=\"sidenote-checkbox-d25d23\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">one-line command</label><small id=\"sidenote-59242a\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>\n<code>sqlite3 db.sqlite \".backup db.sqlite.bak\"</code>\n<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>, and the only authentication and authorisation is in the file system permissions.\nI don’t even have to think about <em>what needs to be taken care of</em>, there isn’t really anything I need to worry about!\nOf course, serious usage should make me worried about the throughput, write blocking, cache sizes, journal modes and so on. But at my scale, that’s not really an issue.\nIt reduces the cognitive complexity, let’s put it this way.</p>\n<p>The less I need to think about things when self-hosting<br>\n→ The easier it is to self-host<br>\n→ The more satisfied I am<br>\n→ The more I am encouraged to keep on doing it!</p>\n<p>It’s the same as: the cleaner and well-stocked my kitchen is, the more I am encouraged to cook and eat healthy.\nThe sharper my knives, the more I enjoy cutting onions.\nHence, sorry PostgreSQL, but I don’t want you here. If this is not a problem for you, then I strongly encourage you to give Miniflux a try.</p>\n<p>Which brings me to…</p>\n<h2>Third try: Yarr</h2>\n<figure class=\"max-w-2xl mx-auto undefined\"><a href=\"https://tudorr.ro/static/pictures/yarr.avif\"><img alt=\"A screenshot of my Yarr instance, showing some websites I follow and some posts from samwho.dev\" loading=\"lazy\" src=\"https://tudorr.ro/static/pictures/yarr.avif\"></a><figcaption>Yarr’s user interface.</figcaption></figure>\n<p><a href=\"https://github.com/nkanaev/yarr\">Yarr</a> is, in some regards, even more minimalistic than Miniflux.\nIt doesn’t provide fancy integrations, it doesn’t support multiple users (it does support authentication, though).\nIt doesn’t do much, really.</p>\n<p>But what it does, it does well. And it has a couple of niceties, such as the possibility to change\nthe auto-refresh interval — or disable it altogether —\nor to switch between the predefined colour palettes.\nThere are also some keyboard shortcuts, and the ability to save (star ⭐️) articles for later.\nIt has a small search box, too.</p>\n<p>What I find to be the best feature, though, is the UI: it’s simple, yet it looks good.\nAnd on a large enough screen, it has this three-column layout, with the feeds, articles, and\narticle view columns, really nice for reading the news. Yet, if your screen — or browser window — is smaller, it\nwill switch to a one-column layout. I find it clean and practical, and I like its choice of\nicons. It also has a couple dynamic elements, just enough to enhance it and not to be defined by it.\nFor example, when you manually refresh the feeds, it shows the progress in the bottom-left corner,\ntogether with a little animated spinner. And when you add a new feed, in the event that it finds multiple\nfeeds for the same websites, it will present the choices in the same “Add feed” modal.\nIt’s completely a single-page application, unlike Miniflux, which is the opposite: Miniflux\nis a completely server-side application. And I’m usually against that, but in the case of Yarr,\nthere isn’t any “routing”, there aren’t different screens, it’s just one large feed-reading window,\nand the dynamic elements only enhance the interaction.</p>\n<p>I have even more appreciation for the developer for publishing <a href=\"https://github.com/nkanaev/yarr/blob/master/doc/rationale.txt\">this text file</a>\nin the repo with the rationale behind Yarr and the sources of inspiration for the user interface.\nIt would be great if more people did this, to get a small glimpse of what they were thinking\nof when they started a project. The message of the commit that added this file is <q>let’s be honest</q>, nice touch.</p>\n<p>Yarr is a bit stuck in time: the author claims that the software is complete,\nthat it fulfils all their expectations, and that no pull requests with new features will be merged.\nI think this is good for a feed reader: it does what it says on the tin, without burdening\nanyone with maintaining the program, and without burdening users with upgrades, config changes and the like.\nThere isn’t much to be done, it just works; I applaud the lack of feature creep.\nContrast this to Miniflux, which is an actively maintained and updated project, with many features (not a bad thing!).</p>\n<p>Yarr can also be used as a desktop application: it supports being launched from the system tray, of all things.\nThis is a feature that I will never use, yet the author wanted to use their program this way,\nso they implemented it. For self-hosting, it supports only a single user account (or no account),\nprotected with a username and password.</p>\n<p>And when it comes to storage, everything is stored in a single SQLite database.\nThere are no other data folders kept around, just that <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-6d3198\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-3010ff\" for=\"sidenote-checkbox-6d3198\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">sole database file containing all the state</label><small id=\"sidenote-3010ff\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>\nThis might be a con if you care about cross-compiling the binary, or if anything else breaks because of cgo. Use of cgo is required,\nbecause SQLite is written in C, and is embedded in programs directly by linking to its shared library.<br>\nRead: <a href=\"https://dave.cheney.net/2016/01/18/cgo-is-not-go\">cgo is not Go</a>.\n<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>.\nI love it so much, it’s one of my favourite programs I use on a daily basis.\nOpen-source, useful, easy to self-host, easy to use, works with a mouse, works with a keyboard, works with a touchscreen. I don’t have to think about it.</p>\n<p>And whenever I’m thinking of it, it’s always out of gratefulness. This is the most I can wish\nfrom software, in my opinion.</p>\n<p>All in all, I think switching to feed readers has brought a net improvement in my quality of life, when it\ncomes to consuming information. If Yarr sounds compelling to you, I suggest you give it a try.\nYou don’t have to self-host it, you can just grab a binary release from GitHub and run it locally.</p>\n<h2>Appendix: The Feeds I Follow</h2>\n<h3>Link Aggregators</h3>\n<p><a href=\"http://www.daemonology.net/hn-daily/\">Hacker News Daily</a>: This is a feed containing the ten highest-rating articles on Hacker News which have not appeared on previous daily digests. I read HN from time to time, but the volume of submissions it receives is too high for me,\nthus I prefer visiting the website directly.</p>\n<p>My favourite link aggregator is <a href=\"https://lobste.rs/\">Lobsters</a>, yet I don’t have it in my feed reader, because I prefer just browsing\nthe website directly.</p>\n<h3>Blogs</h3>\n<p>Various blogs I enjoy reading. At the time of writing this article, in no particular order:</p>\n<ul>\n<li><strong>Artemis Everfree</strong>: <a href=\"https://artemis.sh/\">https://artemis.sh</a></li>\n<li><strong>Bartosz Chiechanowski</strong>: <a href=\"https://ciechanow.ski/\">https://ciechanow.ski</a></li>\n<li><strong>Drew DeVault</strong>: <a href=\"https://drewdevault.com/\">https://drewdevault.com</a></li>\n<li><strong>ENOSUCHBLOG</strong>: <a href=\"https://blog.yossarian.net/\">https://blog.yossarian.net</a></li>\n<li><strong>fasterthanli.me</strong>: <a href=\"https://fasterthanli.me/\">https://fasterthanli.me</a></li>\n<li><strong>j3s.sh</strong>: <a href=\"https://j3s.sh/\">https://j3s.sh</a></li>\n<li><strong>Julia Evans</strong>: <a href=\"https://jvns.ca/\">https://jvns.ca</a></li>\n<li><strong>Michael Stapelberg</strong>: <a href=\"https://michael.stapelberg.ch/\">https://michael.stapelberg.ch</a></li>\n<li><strong>Mozilla Hacks</strong>: <a href=\"https://hacks.mozilla.org/\">https://hacks.mozilla.org</a></li>\n<li><strong>Neil Panchal</strong>: <a href=\"https://neil.computer/\">https://neil.computer</a></li>\n<li><strong>samwho.dev</strong>: <a href=\"https://samwho.dev/\">https://samwho.dev</a></li>\n<li><strong>The Copetti site</strong>: <a href=\"https://www.copetti.org/\">https://www.copetti.org</a></li>\n<li><strong>tonsky.me</strong>: <a href=\"https://tonsky.me/\">https://tonsky.me</a></li>\n<li><strong>Winny’s Blog</strong>: <a href=\"https://blog.winny.tech/\">https://blog.winny.tech</a></li>\n<li><strong>Xesite</strong>: <a href=\"https://xeiaso.net/\">https://xeiaso.net</a></li>\n</ul>\n<h3>Comics</h3>\n<p>I only follow <a href=\"https://xkcd.com/\">xkcd</a>. I haven’t found other comics I would enjoy reading, yet.</p>\n<h3>The News</h3>\n<p>I use my feed reader to read both Dutch news — I live here, and it’s very good language immersion —\nand Romanian news, to keep an eye on what’s going on there.</p>\n<p>Dutch:</p>\n<ul>\n<li><a href=\"https://www.at5.nl/achtergrond\">AT5 Achtergrond</a>: AT5 is a local news outlet. I’m glad that they offer separate RSS feeds for each category on their website, so that I can only follow the investigations.</li>\n<li><a href=\"https://nos.nl/\">NOS</a> Algemeen: General news from the public broadcasting system. The <a href=\"https://nos.nl/teletekst\">online Teletext</a> (“Teletekst”) is the best, in my opinion, for getting a quick look at the news.</li>\n<li><a href=\"https://nos.nl/op3\">NOS op 3</a>: Mostly targeted at younger people, the NOS also makes nice explainer videos for various events happening in and out the country. They also make explainer apps, <a href=\"https://app.nos.nl/op3/stroomnet/\">such as this one about the electricity network</a>.</li>\n</ul>\n<p>As you can see, I mostly read news from the public service.\nFor investigative journalism and more detailed stories,\nI prefer purchasing magazines and newspapers. It’s always nice to read from paper!</p>\n<p>Romanian:</p>\n<ul>\n<li><a href=\"https://pressone.ro/\">PressOne</a>: Investigative journalism</li>\n<li><a href=\"https://riseproject.ro/en/\">Rise Project</a> (link in English): Investigative journalism, focused on organised crime and corruption.\nI had to be careful when adding this one into Yarr, because the meta tags on the website’s front page lead to the feed for their blog. To get the investigations feed, I fed Yarr with the web page containing all articles categorised with “investigation”.</li>\n<li><a href=\"https://recorder.ro/english/\">Recorder</a> (link in English): Investigative journalism, again… They have really nice videos on their website and YouTube channel.\nI am a bit of the opinion that they have a slight tendency to be negativistic and pessimstic, but their materials generally tend to be quite good.</li>\n<li><a href=\"https://buletin.de/bucuresti/\">Buletin de București</a>: The Bucharest Bulletin. Local publication, I like seeing what’s going on there.\nThe website has a funny address.</li>\n</ul>\n<h3>YouTube Channels</h3>\n<p>YouTube offers feeds for all their channels, which I use to subscribe to them without doing it from YouTube itself.\nYou should be able to just paste a YouTube channel in any reader, and it should just pick it up.</p>\n<h3>Online Magazines</h3>\n<p>Currently, I only follow <a href=\"https://arstechnica.com/\">Ars Technica</a> and <a href=\"https://tltxt.ro/\">Teletext</a> (not to be confused with the NOS Teletext).</p>\n<p>Teletext is, according to their website, <q>the newest platform dedicated to pop culture in Romania</q>.\nI’m more of a technical person, I don’t have all the cultural knowledge Teletext’s authors do,\nyet I really love reading their articles, precisely because it’s all new and different to me.</p>\n<h3>Others</h3>\n<p>As I have highlighted above, podcasts are actually based on RSS, so nothing stops you from putting them in your feed reader.\nI personally use dedicated apps for podcasts like <a href=\"https://apps.gnome.org/Podcasts/\">GNOME Podcasts</a> on my computer, and <a href=\"https://antennapod.org/\">AntennaPod</a> on Android.</p>\n<p>I also have a recipe website in there which I really like, <a href=\"https://recipetineats.com/\">RecipeTin Eats</a>.\nOf course the best way to cook is to train your intuition and just make something you’d fancy with the ingredients you have,\nbut it’s always good to get some inspiration from time to time, which is the reason why I have it in my feed reader.</p>","date_published":"Thu, 04 Apr 2024 12:22:58 GMT"},{"id":"https://tudorr.ro/blog/2024-01-04-easy-spell-checking/","url":"https://tudorr.ro/blog/2024-01-04-easy-spell-checking/","title":"Hacky Spell Checking for Blog Posts","content_html":"<p>While writing another post for this blog, I came up\nwith a dirty hack to quickly check the spelling and grammar of my text.</p>\n<p>I use <a href=\"https://helix-editor.com/\">Helix</a> as my text editor of choice, also when authoring blog posts.\nIt does, however, come with a major shortcoming: no support for spell checking whatsoever!\nOr maybe it does support spell checking: you can configure custom language servers,\nwhich should allow me to use <a href=\"https://valentjn.github.io/ltex/index.html\">LT<sub>E</sub>X</a> as an\n<span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-d68d87\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-0322c3\" for=\"sidenote-checkbox-d68d87\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\"><abbr>LSP</abbr></label><small id=\"sidenote-0322c3\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (abbreviation: </span>Language Server Protocol<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>\ninterface to <a href=\"https://languagetool.org/\">LanguageTool</a>,\na pretty decent spell and grammar checker.\nJens Getreu <a href=\"https://blog.getreu.net/20220828-tp-note-new8/\">wrote an article about it</a>, among a couple other things.</p>\n<p>But that’s too much work, and I’m very prone to <a href=\"https://xkcd.com/349/\">yak shaving</a>!</p>\n<h2>The actual hack</h2>\n<p>Set the <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable\"><code>contenteditable</code></a> attribute on your content.\nNow, your browser will check your content!</p>\n<figure class=\"max-w-2xl mx-auto undefined\"><a href=\"https://tudorr.ro/static/pictures/easy-spell-checking.avif\"><img alt=\"Screenshot of this blog post, with a poorly-spelled word intentionally inserted. The word has a red underline, signalling wrong spelling, and a context menu with a suggestion is shown next to it.\" loading=\"lazy\" src=\"https://tudorr.ro/static/pictures/easy-spell-checking.avif\"></a><figcaption>The hack in action.</figcaption></figure>\n<p>You can exploit this even further by installing extensions to alter the\nspell checking behaviour of your browser, such as\nthe <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/languagetool/\">LanguageTool browser extension</a>.</p>\n<p>Time it took to set this up: 30 seconds.</p>\n<h2>How I use this</h2>\n<p>As you can see from the picture above, my website has a “staging mode”.\nI can use a flag — which is descriptively called <code>release</code> —\nto show content only when the website is in one of the two modes: staging and release.\nTo “enable” spell checking, I only had to change this line:</p>\n<pre class=\"language-diff\"><code class=\"language-diff code-highlight\"><span class=\"code-line deleted\"><span class=\"token deleted-sign deleted\"><span class=\"token prefix deleted\">-</span><span class=\"token line\"> &lt;main class=\"p-2 text-lg\"&gt;\n</span></span></span><span class=\"code-line inserted\"><span class=\"token deleted-sign deleted\"><span class=\"token line\"></span></span><span class=\"token inserted-sign inserted\"><span class=\"token prefix inserted\">+</span><span class=\"token line\"> &lt;main class=\"p-2 text-lg\" {{ if !release }}contenteditable{{ /if }}&gt;\n</span></span></span></code></pre>\n<p>I gave myself a good pat on the back after discovering this!</p>","date_published":"Mon, 04 Mar 2024 14:27:26 GMT"},{"id":"https://tudorr.ro/blog/technical/2023/10/12/modern-shell-tools/","url":"https://tudorr.ro/blog/technical/2023/10/12/modern-shell-tools/","title":"Postmodern Command Line Tools","content_html":"<p>The standard UNIX command line tools are still enjoying a multi-decade-long reign,\nwhether we’re talking about the bare-bones <code>echo</code> and <code>cat</code>, the familiar <code>ls</code>,\n<code>date</code> and <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-c4f598\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-f460e6\" for=\"sidenote-checkbox-c4f598\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\"><code>cal</code></label><small id=\"sidenote-f460e6\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>I can’t be the only one who’s still using <code>cal</code>, right?<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>,\nthe weirdly-named text processing utilities <code>tr</code>, <code>sed</code>, <code>cut</code>, <code>grep</code> and\ntheir main nemesis, <code>awk</code>, or the ubiquitous text editors, <code>vi</code> and the 30+ year old Vi IMproved.</p>\n<p>Here are a couple “postmodern” CLI and TUI tools I like to use. They are mainly\nalternatives to the well-established tools that try to take on trivial problems,\nlike string processing, finding files, looking for strings in files and so on.</p>\n<p>These tools have a couple things in common, such as:</p>\n<ul>\n<li>Trying to look good in the terminal emulator, with pretty colours and icons,\nyet allowing for easy integration with other tools.</li>\n<li>Rarely written in C.</li>\n<li>Opinionated defaults, usually inspired from experiences with the established tools.</li>\n</ul>\n<h2>fd</h2>\n<p><a href=\"https://github.com/sharkdp/fd\">fd’s webpage</a>.</p>\n<p>I don’t know about you but I cannot remember for the love of what’s holy <code>find</code>’s flags.\nIt also grinds my gears heavily that it doesn’t automatically ignore files declared in <code>.gitignore</code>,\nalthough <code>find</code> was made when <code>git</code>’s creator was a toddler.</p>\n<p><code>fd</code>, when supplied with an argument, does what you probably want to do most of the time\nwhen looking for files in a directory: look for any files that contain a pattern in the filename.\nAnd it does that with pretty colours! It respects <code>LS_COLORS</code> even, so the colours have meaning!</p>\n<p>Another nicety is that it uses <a href=\"https://docs.rs/regex/latest/regex/\">Rust’s regex library</a>.\nGNU Find offers many regex types, which can be quite confusing. Run <code>find -regextype help</code> to see them all.</p>\n<p>Example usage, finding all Markdown files in the blog’s source code repo:</p>\n<pre class=\"language-shell-session\"><code class=\"language-shell-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\">fd .md$</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">404.md\n</span></span><span class=\"code-line\"><span class=\"token output\">README.md\n</span></span><span class=\"code-line\"><span class=\"token output\">_posts/2021-01-24-the-wayland-experience.md\n</span></span><span class=\"code-line\"><span class=\"token output\">_posts/2023-08-29-modern-shell-tools.md\n</span></span><span class=\"code-line\"><span class=\"token output\">_posts/2023-09-11-low-tech.md\n</span></span><span class=\"code-line\"><span class=\"token output\">about.md\n</span></span><span class=\"code-line\"><span class=\"token output\">index.md\n</span></span><span class=\"code-line\"><span class=\"token output\">technical.md\n</span></span><span class=\"code-line\"><span class=\"token output\">writing.md\n</span></span></code></pre>\n<p>You can find it in your package manager probably: <code>rust-fd-find</code> in Fedora, Ubuntu, and EPEL.\n<a href=\"https://repology.org/project/fd-find/versions\">Repology</a>.</p>\n<h2>sd</h2>\n<p><a href=\"https://github.com/chmln/sd\">sd’s webpage</a>.</p>\n<p><code>sd</code> is a lesser-known postmodern alternative to <code>sed</code>. It simplifies greatly the most common\nuse case for <code>sed</code> — to replace and extract data — and also uses <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-372275\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-be7693\" for=\"sidenote-checkbox-372275\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">Rust regexes</label><small id=\"sidenote-be7693\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>No more <code>sed -E</code>!<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>.\nThe examples also show nice ways to use it together with <code>fd</code>.</p>\n<p>Example usage:</p>\n<pre class=\"language-shell-session\"><code class=\"language-shell-session code-highlight\"><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token comment\"># -f i stands for `flags: case insensitive’</span></span></span>\n</span><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\">sd <span class=\"token parameter variable\">-f</span> i emacs VIM file.txt</span></span>\n</span><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token comment\"># sd does by default in-place substitution, so this will print nothing.</span></span></span>\n</span><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\"><span class=\"token comment\"># we can do, however, the following</span></span></span>\n</span><span class=\"code-line\"><span class=\"token command\"><span class=\"token shell-symbol important\">$</span> <span class=\"token bash language-bash\">sd <span class=\"token parameter variable\">-f</span> i <span class=\"token parameter variable\">-p</span> emacs VIM file.txt</span></span>\n</span><span class=\"code-line\"><span class=\"token output\">The original VIM did not have Lisp in it. The lower level language, the non-interpreted language—was PDP-10 Assembler.\n</span></span><span class=\"code-line\"><span class=\"token output\">The interpreter we wrote in that actually wasn’t written for VIM, it was written for TECO.\n</span></span></code></pre>\n<p><em>Text fragment taken from <a href=\"https://www.gnu.org/gnu/rms-lisp.html\">here</a>.</em></p>\n<p>This one is also available in the repositories of some popular distros: <code>rust-sd</code> for Fedora and Ubuntu (but no EPEL).\n<a href=\"https://repology.org/project/sd-find-replace/versions\">Repology</a>.</p>\n<h2>Helix</h2>\n<p><a href=\"https://github.com/helix-editor/helix\">Helix’s webpage</a>.</p>\n<p>Currently my text editor of choice. It’s a modal text editor, like {neo,}vim,\nbut it sacrificies extensibility — no plugin support! — for natively implementing\nvarious niceties, such as:</p>\n<ul>\n<li>Tree-sitter integration: syntax highlighting and text object manipulation,</li>\n<li>Language Server Protocol support,</li>\n<li>Nice command navigation: press space and see what other keys you can hit to do various things. Press, for example, <code>w</code> and you are presented window manipulation functions,</li>\n<li>Fuzzy file and buffer selector, and more.</li>\n</ul>\n<p>It’s also heavily inspired by <a href=\"https://github.com/mawww/kakoune\">Kakoune</a> in the way commands are used:\nfirst you press the key that selects the text object you operate on, and <span class=\"sidenote inline lg:cursor-auto\"><input aria-label=\"Show sidenote\" type=\"checkbox\" id=\"sidenote-checkbox-361d81\" class=\"max-lg:sr-only lg:hidden peer\"><label aria-describedby=\"sidenote-c452f4\" for=\"sidenote-checkbox-361d81\" class=\"underline decoration-wavy decoration-from-font cursor-pointer lg:cursor-auto after:inline-block after:content-['*'/''] max-lg:peer-checked:after:content-[''] max-lg:peer-focus:outline-dotted max-lg:peer-focus:outline-2 max-lg:peer-focus:outline-offset-2 max-lg:peer-focus:outline-fg-light max-lg:peer-focus:dark:outline-fg-dark\">then the action</label><small id=\"sidenote-c452f4\" class=\"max-lg:hidden max-lg:peer-checked:not-sr-only max-lg:peer-checked:block max-lg:peer-checked:float-left max-lg:peer-checked:min-w-full max-lg:peer-checked:mx-4 max-lg:peer-checked:px-2 max-lg:peer-checked:py-4 lg:before:content-['*'/''] lg:before:float-left lg:before:pr-0.5 lg:block lg:float-right lg:text-base lg:w-[20vw] lg:-mr-[25vw] 2xl:w-[20vw] 2xl:-mr-[30vw] bg-bg-light dark:bg-bg-dark p-2 rounded-md\"><span class=\"sidenote-content-parenthesis sr-only\"> (side note: </span>Unlike Vim, which does it in reverse.<span class=\"sidenote-content-parenthesis sr-only\">)</span></small></span>.\nSo, if I want to change the next word, I’d press <code>wc</code>. After pressing <code>w</code>, the selection is also highlighted.</p>\n<p>This one is not as widely-available in distro repositories (yet), but it’s easy to install\nif you happen to have the Rust toolset installed. <a href=\"https://repology.org/project/helix/versions\">Repology</a>.</p>\n<h2>Nushell</h2>\n<p><a href=\"https://www.nushell.sh/\">Nushell’s webpage</a>.</p>\n<p>I like to describe Nushell as “what if you take Powershell’s object oriented everything approach, but you apply it to UNIX”.\nInstead of using objects, Nushell operates on tables made of records reminescent of JSON’s data types.\nYou have mostly lists, key-value hash tables, numbers, strings, and booleans, plus a couple specific data types like\ndate, filesize, durations, but also ranges and closures.</p>\n<p>What I really like about Nu is that it strives to make all of this cross-platform.\nThe idea is that you learn once the Nu language and commands, and you use them on all these systems in a portable manner.\nSo, no platform-specific <code>ls</code> flags for example. Of course, this also means that Nu tries to avoid platform-specific commands in the first place.</p>\n<p>Besides coming with a very nice command language, it comes with many other niceties,\nsuch as a help system that has syntax highlighted examples and example outputs (for example, type <code>help ls</code>),\na command search mode (after typing a pipe, you can press <kbd>F1</kbd> and a menu pops up with a command search and small help overview),\nsyntax highlighted commands as you type, and quite decent completion.</p>\n<p>There aren’t many completion definitions for all the other “normal” commands you may use,\nbut you can easily declare completers in your config, such as <a href=\"https://www.nushell.sh/cookbook/external_completers.html#fish-completer\">using Fish as a completer</a>.\nFish comes with stellar completion support, so now Nu does too!</p>\n<p>Many string processing (and not only) tools can feel obsolete when using Nu, which\ncomes with its own <em>typed</em> functions and function programming-esque constructs.\nThe <code>sd</code> utility named above, for example, is built in, in the form of <code>str replace</code>.\nWe can run <code>help str replace</code> and we get a nice coloured, and syntax highlighted, help\npage right in the terminal. All these help pages are <a href=\"https://www.nushell.sh/commands/docs/str_replace.html\">also on the website</a>.</p>\n<p>Go ahead and try it out!</p>","date_published":"Thu, 12 Oct 2023 18:23:29 GMT"},{"id":"https://tudorr.ro/blog/technical/2021/01/26/the-wayland-experience/","url":"https://tudorr.ro/blog/technical/2021/01/26/the-wayland-experience/","title":"Should You Write a Wayland Compositor?","content_html":"<p><a href=\"https://wayland.freedesktop.org/\">Wayland</a> is the “new” (13 years old already) display server technology on Linux, which is supposed to replace\nthe antiquated X11. It promises better security, performance, portability, everything, compared to X11, and\nit sure does deliver, provided that you’re not using <a href=\"https://www.nvidia.com/\">unsupported graphics cards</a>.\nYou can watch <a href=\"https://www.youtube.com/watch?v=RIctzAQOe44\">this talk / rant about X11</a> to get an idea about how bad it is.</p>\n<p>Some power users also haven’t switched to Wayland because their window manager doesn’t have a\nWayland equivalent (XMonad, Awesome, Bspwm and the others, all having their unique feature-set). Some may wonder “hmm maybe I can port it myself”. I wrote this post to make you (re)consider that.</p>\n<p>One of the main ideas of Wayland is that it’s merely a specialised IPC protocol, and the communication is\nstrictly between the clients (applications on your screen mainly) and the server.</p>\n<p>The server is what we are interested in now. Unlike X11, where the server is X.Org and the window manager\nis just an X11 client with special privileges, on Wayland the window manager is also a server and a compositor. In fact, the correct terminology is “Wayland compositor”. This piece of software has the\ntask of doing everything it wants with the clients, which is usually among the lines of showing them\non the screen and giving them inputs events from your keyboards, mice, touchscreens etc. In fact, <a href=\"https://mir-server.io/\">no one\nstops you from leaving window management as an API</a>.</p>\n<p>On my laptop, I prefer using GNOME under Wayland, because it has the most stable, fully-featured and “just works”\nexperience there is. However, I wanted a better way of organising windows on my screen than leaving them\nshuffled around. Tiling was also not good, because every time you launch a new window, the others get\nshrunk to fit, which is not good on a small laptop display.</p>\n<p>Luckily I discovered <a href=\"https://github.com/paperwm/PaperWM\">PaperWM</a>. It’s perfect for laptops, instead of\nshrinking your windows, it just renders them off-screen. To switch between one or the other you can just\nflick three fingers on the touchpad. It’s great. It’s also “glossy” and polished and has great UI and animations.</p>\n<p>It has some disadvantages though, mainly concerning its speed. I find it sluggish, and it’s no surprise that\nlike all other GNOME Shell extensions, it’s a JavaScript behemoth. PaperWM still uses the GNOME Tweener\nframework for its animations, which is entirely written in JS. Because of that, it needs to communicate with the main\nGNOME compositor process on each operation. And because we’re talking animations, said operations happen\nfor <em>every frame</em>. That means there is JS executed for every frame, 60 times a second. It’s horrible!</p>\n<p>BTW nowadays GNOME uses animations implemented in C (since GNOME 3.34), the “GNOME runs JS on every frame” statement is for the most part false today.</p>\n<p>Because of the inefficiency of PaperWM, it also means that it drains my battery quickly, which is bad for a laptop!</p>\n<p>So, because I am a student with a lot of free time, together with <a href=\"https://alexge50.com/\">Alex</a> we figured we shall develop a scrollable tiling (PaperWM-like) Wayland compositor. It’s called <a href=\"https://gitlab.com/cardboardwm/cardboard\">Cardboard</a> (get it??). It also has the nicety of\nbeing controlled and configured by a remote control program, like bspwm.\nNeither Alex nor I had experience with any kind of Wayland development. However, I wrote <a href=\"https://tudorr.ro/software/windowchef/\">an X11 window manager</a>,\n<a href=\"https://github.com/tudurom/disputils/\">some XRandR querying utilities</a> and <a href=\"https://github.com/tudurom/ruler\">a window rule daemon</a>. Alex has experience\nwith <a href=\"https://git.alexge50.com/a/gie\">graphics programming</a> and <a href=\"https://github.com/alexge50/libwatchpoint\">C++ devilry</a>.</p>\n<p>Our “tech stack” was <a href=\"https://github.com/swaywm/wlroots\">wlroots</a>, which is an exceptional Wayland compositor library (apart from its lack of documentation). The description from the README is spot on:</p>\n<blockquote>\n<p>About 50,000 lines of code you were going to write anyway.</p>\n</blockquote>\n<p>I (because I wrote most of the Wayland-interfacing code) realised soon that there is still a lot of boilerplate involved in writing a compositor, much more than for an X11 window manager,\nnamely setting up your own rendering code, registering and storing input devices and screens in your own data structures, passing input events to windows, calculating bounds for bars\nand other overlays (courtesy of <a href=\"https://github.com/swaywm/wlr-protocols/blob/master/unstable/wlr-layer-shell-unstable-v1.xml\"><code>layer-shell</code></a>) and others. X11 handles all of this for you,\nthe window manager just reacts to events regarding window inputs to establish its behaviour. With Wayland, you handle everything, even with wlroots. The upside is that if you don’t like the way X11 does something\n(which is a given), not only that you can do it in your own way on Wayland, you are required to do so.</p>\n<p>Because we weren’t really prepared for what writing a compositor involved, we thought that it must be approached like a “normal” program: split code into modules, each with their own responsibilities, call wlroots to do its thing, the usual stuff.\nWe are writing a program in our way and wlroots lets us “interface with Wayland”. Or so we thought.</p>\n<p>We were as careful as possible to separate responsibilities into different structures / classes, yet we ended up with most functions taking a pointer to <code>Server</code> as their first parameter.\n<code>Server</code> is a singleton holding all the other “sub-modules” in it. It represents the compositor itself. The reason most functions need <code>Server</code> is that everything is related to everything,\nnot because of a mistake in structuring the code, but by design. This is what a compositor requires, the problem of writing a compositor is somewhat complex because it has a great deal variables ranging\nfrom input events, drawing on the screen, creating behaviours that the user leverages, reacting to application events. All of them can affect the other, you can not separate the thing into modules.\nThe best you can do is separate the code in different files and folders based on some criteria, like grouping data structures with related routines.</p>\n<p>Excerpt from <code>Seat.h</code>:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp code-highlight\"><span class=\"code-line\"><span class=\"token comment\">/// Hides the \\a view from the screen without unmapping. Happens when a Workspace is deactivated.</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">hide_view</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">,</span> View<span class=\"token operator\">&amp;</span> view<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\"><span class=\"token comment\">/// Gives keyboard focus to a plain surface (OR xwayland usually)</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">focus_surface</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">struct</span> <span class=\"token class-name\">wlr_surface</span><span class=\"token operator\">*</span> surface<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\"><span class=\"token comment\">/**\n</span></span><span class=\"code-line\"><span class=\"token comment\">  * \\brief Sets the focus state on \\a view. Auto-scrolls the Workspace if it's tiled.\n</span></span><span class=\"code-line\"><span class=\"token comment\">  *\n</span></span><span class=\"code-line\"><span class=\"token comment\">  * If \\a view is null, the previously focused view will be unfocused and no other view will be focused.\n</span></span><span class=\"code-line\"><span class=\"token comment\">  */</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">focus_view</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">,</span> OptionalRef<span class=\"token operator\">&lt;</span>View<span class=\"token operator\">&gt;</span> view<span class=\"token punctuation\">,</span> <span class=\"token keyword\">bool</span> condense_workspace <span class=\"token operator\">=</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\"><span class=\"token comment\">/// Marks the layer as receiving keyboard focus from this seat.</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">focus_layer</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">,</span> <span class=\"token keyword\">struct</span> <span class=\"token class-name\">wlr_layer_surface_v1</span><span class=\"token operator\">*</span> layer<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\"><span class=\"token comment\">/**\n</span></span><span class=\"code-line\"><span class=\"token comment\">  * \\brief Focus the most recently focused view on \\a column.\n</span></span><span class=\"code-line\"><span class=\"token comment\">  *\n</span></span><span class=\"code-line\"><span class=\"token comment\">  * \\param column - must be from this workspace\n</span></span><span class=\"code-line\"><span class=\"token comment\">  */</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">focus_column</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">,</span> Workspace<span class=\"token double-colon punctuation\">::</span>Column<span class=\"token operator\">&amp;</span> column<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\"><span class=\"token comment\">/// Removes the \\a view from the focus stack.</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">remove_from_focus_stack</span><span class=\"token punctuation\">(</span>View<span class=\"token operator\">&amp;</span> view<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\">\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">begin_move</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">,</span> View<span class=\"token operator\">&amp;</span> view<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">begin_resize</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">,</span> View<span class=\"token operator\">&amp;</span> view<span class=\"token punctuation\">,</span> <span class=\"token keyword\">uint32_t</span> edges<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">begin_workspace_scroll</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">,</span> Workspace<span class=\"token operator\">&amp;</span> workspace<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">process_cursor_motion</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">,</span> <span class=\"token keyword\">uint32_t</span> time <span class=\"token operator\">=</span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">process_cursor_move</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span><span class=\"token punctuation\">,</span> GrabState<span class=\"token double-colon punctuation\">::</span>Move move_data<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">process_cursor_resize</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span><span class=\"token punctuation\">,</span> GrabState<span class=\"token double-colon punctuation\">::</span>Resize resize_data<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">process_swipe_begin</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">,</span> <span class=\"token keyword\">uint32_t</span> fingers<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">process_swipe_update</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">,</span> <span class=\"token keyword\">uint32_t</span> fingers<span class=\"token punctuation\">,</span> <span class=\"token keyword\">double</span> dx<span class=\"token punctuation\">,</span> <span class=\"token keyword\">double</span> dy<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">process_swipe_end</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">end_interactive</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">end_touchpad_swipe</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\">\n</span><span class=\"code-line\"><span class=\"token comment\">/// Updates the scroll of the workspace during three-finger swipe, taking in account speed and friction.</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">update_swipe</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\">\n</span><span class=\"code-line\"><span class=\"token comment\">/// Returns true if the \\a view is currently in a grab operation.</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">bool</span> <span class=\"token function\">is_grabbing</span><span class=\"token punctuation\">(</span>View<span class=\"token operator\">&amp;</span> view<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\">\n</span><span class=\"code-line\"><span class=\"token comment\">/// Returns the workspace under the cursor.</span>\n</span><span class=\"code-line\">OptionalRef<span class=\"token operator\">&lt;</span>Workspace<span class=\"token operator\">&gt;</span> <span class=\"token function\">get_focused_workspace</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\">\n</span><span class=\"code-line\"><span class=\"token comment\">/// Moves the focus to a different workspace, if the workspace is already on a monitor, it focuses that monitor</span>\n</span><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token function\">focus</span><span class=\"token punctuation\">(</span>Server<span class=\"token operator\">&amp;</span> server<span class=\"token punctuation\">,</span> Workspace<span class=\"token operator\">&amp;</span> workspace<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// TODO: yikes, passing Server*</span>\n</span></code></pre>\n<p>As an example, let’s take damage tracking. For starters, because the compositor is also tasked with rendering the content and displaying it on the screen, we have some rendering code that runs on every frame.\nDamage tracking is tracking which areas of the screen have changed in an amount of time. An example would\nbe typing a letter in the terminal. The place where the cursor is changes to the letter you typed, and the cursor advances.\nIf there is no change, the frame is not rendered, as it would look\nexactly the same as the previous one, which would be a waste of processor time.\nThis way, instead of re-rendering everything 60 times a second (I assume that you use a common display), we can render and paint as little as possible to account for the changed region.\nYou can read <a href=\"https://emersion.fr/blog/2019/intro-to-damage-tracking/\">an introduction to damage tracking</a> written by one of the main developers of wlroots.</p>\n<p>I have just implemented the most basic form of damage tracking: do not render the frame if nothing on the screen changes.\nIt doesn’t track the damage itself, just that it exists.\nTo do this, I first added a <code>wlr_output_damage</code> object to my <code>Output</code> structure:</p>\n<pre class=\"language-diff\"><code class=\"language-diff code-highlight\"><span class=\"code-line\">struct Output {\n</span><span class=\"code-line\"><span class=\"token unchanged\"><span class=\"token prefix unchanged\"> </span><span class=\"token line\">   struct wlr_output* wlr_output;\n</span></span></span><span class=\"code-line\"><span class=\"token unchanged\"><span class=\"token line\"></span><span class=\"token prefix unchanged\"> </span><span class=\"token line\">   struct wlr_output_damage* wlr_output_damage;\n</span></span></span><span class=\"code-line inserted\"><span class=\"token unchanged\"><span class=\"token line\"></span></span><span class=\"token inserted-sign inserted\"><span class=\"token prefix inserted\">+</span><span class=\"token line\">   struct wlr_box usable_area;\n</span></span></span><span class=\"code-line\"><span class=\"token inserted-sign inserted\"><span class=\"token line\"></span></span><span class=\"token unchanged\"><span class=\"token prefix unchanged\"> </span><span class=\"token line\">   ...\n</span></span></span></code></pre>\n<p>This structure tracks the damage accumulated per-output, as rendering is also per-output (this means that you can use screens of different refresh rates, yay!). However, to make this initial attempt at\ndamage tracking easier, I decided to trigger rendering for all attached outputs. I added a <code>set_dirty()</code> function to the <code>OutputManager</code> class that does just that:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp code-highlight\"><span class=\"code-line\"><span class=\"token keyword\">void</span> <span class=\"token class-name\">OutputManager</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">set_dirty</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n</span><span class=\"code-line\"><span class=\"token punctuation\">{</span>\n</span><span class=\"code-line\">    <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">auto</span><span class=\"token operator\">&amp;</span> output <span class=\"token operator\">:</span> outputs<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n</span><span class=\"code-line\">        <span class=\"token function\">wlr_output_damage_add_whole</span><span class=\"token punctuation\">(</span>output<span class=\"token punctuation\">.</span>wlr_output_damage<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span><span class=\"code-line\">    <span class=\"token punctuation\">}</span>\n</span><span class=\"code-line\"><span class=\"token punctuation\">}</span>\n</span></code></pre>\n<p>This marks every output as entirely damaged, and as such triggers a render.</p>\n<p>With this function set into place, I had to identify when does the “screen” change, namely when a window “commits” (changes its contents) and when a window is moved. The window move is one example\nof the way everything is related to everything in the compositor. Before damage tracking, the <code>View::move()</code> (we call windows “views”) method just changed the <code>x</code> and <code>y</code> fields of the <code>View</code> structures.\nNow, a move must call a method of <code>OutputManager</code>, so we need to give that as a parameter. This is almost like giving <code>Server</code> as a parameter, as <code>OutputManager</code> is a singleton inside <code>Server</code>.</p>\n<pre class=\"language-diff\"><code class=\"language-diff code-highlight\"><span class=\"code-line deleted\"><span class=\"token deleted-sign deleted\"><span class=\"token prefix deleted\">-</span><span class=\"token line\">void View::move(int x_, int y_)\n</span></span></span><span class=\"code-line inserted\"><span class=\"token deleted-sign deleted\"><span class=\"token line\"></span></span><span class=\"token inserted-sign inserted\"><span class=\"token prefix inserted\">+</span><span class=\"token line\">void View::move(OutputManager&amp; output_manager, int x_, int y_)\n</span></span></span><span class=\"code-line\"><span class=\"token inserted-sign inserted\"><span class=\"token line\"></span></span>{\n</span><span class=\"code-line\"><span class=\"token unchanged\"><span class=\"token prefix unchanged\"> </span><span class=\"token line\">   x = x_;\n</span></span></span><span class=\"code-line\"><span class=\"token unchanged\"><span class=\"token line\"></span><span class=\"token prefix unchanged\"> </span><span class=\"token line\">   y = y_;\n</span></span></span><span class=\"code-line inserted\"><span class=\"token unchanged\"><span class=\"token line\"></span></span><span class=\"token inserted-sign inserted\"><span class=\"token prefix inserted\">+</span><span class=\"token line\">   output_manager.set_dirty();\n</span></span></span><span class=\"code-line\"><span class=\"token inserted-sign inserted\"><span class=\"token line\"></span></span>}\n</span></code></pre>\n<p>That’s when it hit me that wlroots is more of a framework and the compositor is one of its modules. Thinking that wlroots is an “interface to Wayland” is plain wrong, as the Wayland server is the\nprogram that I am writing. The next refactor is going to make the <code>Server</code> instance global…</p>\n<p>Now that we have a <code>wlr_output_damage</code> object in place and <code>set_dirty()</code> calls where they’re needed, we only need to call the render function when <code>wlr_output_damage</code> tells us instead of\nevery <code>1/60</code> seconds:</p>\n<pre class=\"language-diff\"><code class=\"language-diff code-highlight\"><span class=\"code-line\">@@ -46,11 +46,12 @@ void register_output(Server&amp; server, Output&amp;&amp; output_)\n</span><span class=\"code-line\"><span class=\"token unchanged\"><span class=\"token prefix unchanged\"> </span><span class=\"token line\">   server.output_manager-&gt;outputs.emplace_back(output_);\n</span></span></span><span class=\"code-line\"><span class=\"token unchanged\"><span class=\"token line\"></span><span class=\"token prefix unchanged\"> </span><span class=\"token line\">   auto&amp; output = server.output_manager-&gt;outputs.back();\n</span></span></span><span class=\"code-line\"><span class=\"token unchanged\"><span class=\"token line\"></span><span class=\"token prefix unchanged\"> </span><span class=\"token line\">   output.wlr_output-&gt;data = &amp;output;\n</span></span></span><span class=\"code-line inserted\"><span class=\"token unchanged\"><span class=\"token line\"></span></span><span class=\"token inserted-sign inserted\"><span class=\"token prefix inserted\">+</span><span class=\"token line\">   output.wlr_output_damage = wlr_output_damage_create(output.wlr_output);\n</span></span></span><span class=\"code-line\"><span class=\"token inserted-sign inserted\"><span class=\"token line\"></span></span>\n</span><span class=\"code-line\"><span class=\"token unchanged\"><span class=\"token prefix unchanged\"> </span><span class=\"token line\">   register_handlers(server,\n</span></span></span><span class=\"code-line\"><span class=\"token unchanged\"><span class=\"token line\"></span><span class=\"token prefix unchanged\"> </span><span class=\"token line\">                     &amp;output,\n</span></span></span><span class=\"code-line\"><span class=\"token unchanged\"><span class=\"token line\"></span><span class=\"token prefix unchanged\"> </span><span class=\"token line\">                     {\n</span></span></span><span class=\"code-line deleted\"><span class=\"token unchanged\"><span class=\"token line\"></span></span><span class=\"token deleted-sign deleted\"><span class=\"token prefix deleted\">-</span><span class=\"token line\">                         { &amp;output.wlr_output-&gt;events.frame, Output::frame_handler },\n</span></span></span><span class=\"code-line inserted\"><span class=\"token deleted-sign deleted\"><span class=\"token line\"></span></span><span class=\"token inserted-sign inserted\"><span class=\"token prefix inserted\">+</span><span class=\"token line\">                         { &amp;output.wlr_output_damage-&gt;events.frame, Output::frame_handler },\n</span></span></span><span class=\"code-line\"><span class=\"token inserted-sign inserted\"><span class=\"token line\"></span></span><span class=\"token unchanged\"><span class=\"token prefix unchanged\"> </span><span class=\"token line\">                         { &amp;output.wlr_output-&gt;events.present, Output::present_handler },\n</span></span></span><span class=\"code-line\"><span class=\"token unchanged\"><span class=\"token line\"></span><span class=\"token prefix unchanged\"> </span><span class=\"token line\">                         { &amp;output.wlr_output-&gt;events.commit, Output::commit_handler },\n</span></span></span><span class=\"code-line\"><span class=\"token unchanged\"><span class=\"token line\"></span><span class=\"token prefix unchanged\"> </span><span class=\"token line\">                         { &amp;output.wlr_output-&gt;events.mode, Output::mode_handler },\n</span></span></span></code></pre>\n<p>This is not complete code for a basic damage tracking implementation with wlroots. You can see <a href=\"https://gitlab.com/cardboardwm/cardboard/-/commit/f2ef2ff076ddbbd23994553b8eff131f9bd0207f\">the whole commit here</a>.</p>\n<p>This is an example of how wlroots provides yet another “module” that we can use in the grand scheme of the compositor. <code>wlr_output_damage</code> accumulates damaged rectangles in time and even turns these\nnumerous small rectangles into a big one as an optimisation. It also calls the frame handler when it’s needed, and this is not only just when something on the screen changed, but also when the underlying “backend”\nof the compositor changes. The simplest situation is when the compositor starts: it needs to render an initial frame so the screen isn’t pitch black.</p>\n<p>All in all, I do not recommend writing your own compositor if you only want some gimmicky user interface.\nIn the X world there is a WM for every single way of window tiling plus a couple more. It doesn’t work like\nthat with Wayland, you will spend more time implementing basic compositor duties than\non your compositor’s unique features. Instead, if I were to rewrite Cardboard, I would rather do it\nas a <a href=\"https://github.com/WayfireWM/wayfire/wiki/Plugin-architecture\">Wayfire plugin</a> or maybe as a KWin script. However, I think Wayfire is more “enthusiast-friendly”, as it uses <a href=\"https://github.com/swaywm/wlr-protocols\">protocols from\nwlroots</a> such as <code>layer-shell</code> (for panels, overlays and backgrounds), <code>gamma-control</code> (for Redshift), <code>screencopy</code> (for screenshots) and others,\nallowing people to write tools that are not specific to the compositor.</p>\n<p>Nevertheless, if you want to do it for the learning experience, I definitely recommend writing\na “full-fledged” compositor with wlroots, learning from other compositors (<a href=\"https://github.com/swaywm/sway\">Sway</a>,\n<a href=\"https://github.com/hjdskes/cage\">cage</a>, <a href=\"https://github.com/WayfireWM/wayfire\">Wayfire</a>, <a href=\"https://hikari.acmelabs.space/\">hikari</a> and others; cage is the simplest, hikari second simplest) and from their creators on\nIRC (<code>#sway-devel</code> on Freenode), they are very kind and knowledgeable.</p>\n<p>There is also a <a href=\"https://github.com/swaywm/wlroots/issues/1826\">discussion about introducing a high-level scene API in wlroots</a>. Maybe when it will arrive, I will change my opinion.</p>\n<p>Recommended lectures:</p>\n<ul>\n<li><a href=\"https://drewdevault.com/2018/02/17/Writing-a-Wayland-compositor-1.html\">Writing a Wayland Compositor</a>, by Drew DeVault, the author of Sway and wlroots</li>\n<li><a href=\"https://wayland-book.com/\">The Wayland Protocol Book</a>, also by Drew DeVault</li>\n<li><a href=\"https://wayland.freedesktop.org/docs/html/\">The Wayland Protocol</a>, the actual protocol (larger than the book)</li>\n<li>Posts from Simon Ser’s blog, maintainer of Sway and wlroots:\n<ul>\n<li><a href=\"https://emersion.fr/blog/2018/wayland-rendering-loop/\">Writing a Wayland rendering loop</a></li>\n<li><a href=\"https://emersion.fr/blog/2019/intro-to-damage-tracking/\">Introduction to damage tracking</a></li>\n<li><a href=\"https://emersion.fr/blog/2020/wayland-clipboard-drag-and-drop/\">Wayland clipboard and drag &amp; drop</a></li>\n</ul>\n</li>\n<li><a href=\"https://github.com/WayfireWM/wayfire/blob/cb7920187d2546ca375f00e7ef0a71d7a4995ba6/src/core/core.cpp#L173\">The init routine of Wayfire</a>. GTK is rather picky when it comes to the order of advertised Wayland protocols.\nDon’t waste hours of your life trying to figure out whether you did something wrong, it’s GTK…</li>\n</ul>\n<p>Also, I suggest you write the compositor in either C, C++ or Zig (with <a href=\"https://github.com/swaywm/zig-wlroots\">zig-wlroots</a> or just doing your thing, Zig is C compatible).\nSee this article on why the <a href=\"http://way-cooler.org/blog/2019/04/29/rewriting-way-cooler-in-c.html\">Rust bindings failed</a>.</p>","date_published":"Tue, 26 Jan 2021 08:59:23 GMT"}]}