<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title>Nemin's Blog</title><id>https://nemin.hu/feed.xml</id><subtitle>Recent Posts</subtitle><updated>2026-04-11T17:30:25Z</updated><link href="https://nemin.hu/feed.xml" rel="self" /><link href="https://nemin.hu" /><entry><title>Solar power</title><id>https://nemin.hu/solar.html</id><author><name>Nemin</name></author><updated>2026-04-11T10:38:00Z</updated><link href="https://nemin.hu/solar.html" rel="alternate" /><content type="html">&lt;div role=&quot;doc-toc&quot; id=&quot;table-of-contents&quot;&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;div role=&quot;doc-toc&quot; id=&quot;text-table-of-contents&quot;&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#the-plan&quot;&gt;1. The plan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#parts-of-a-solar-setup&quot;&gt;2. Parts of a solar setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#my-particular-setup&quot;&gt;3. My particular setup&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#power-station&quot;&gt;3.1. Power station&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#solar-panel&quot;&gt;3.2. Solar panel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#cabling&quot;&gt;3.3. Cabling&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#the-initial-results&quot;&gt;4. The initial results&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#shortcomings-of-the-river-3&quot;&gt;4.1. Shortcomings of the RIVER 3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#a-hasty-but-needed-upgrade&quot;&gt;5. A hasty, but needed upgrade&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#shortcomings-of-the-anker-solix&quot;&gt;5.1. Shortcomings of the Anker SOLIX&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;6. Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
I've been long fascinated by the concept of solar power. The idea of
putting some weird blue plate in your garden or on your rooftop and
getting free electricity sounds almost like a joke and yet, not only
is it serious, it's a reasonably cheap project to accomplish as long
as your requirements aren't super high.
&lt;/p&gt;

&lt;p&gt;
That being said, I had a slight apprehension to messing with solar.
Well, not even necessarily strictly with solar, rather with anything
electric. My late grandfather was an electric engineer and he used to
tinker on a small couple-dozen watts panel as part of his many
retirement projects. I don't remember the exact details because I was
very young, but I recall seeing a bunch of huge boxes and
incomprehensible cabling, which put the subconscious idea into my mind
that this is far too complicated for someone without a proper
education in the field.
&lt;/p&gt;

&lt;p&gt;
However, fate would see to it that just a couple of days before I
started writing this post, nearly fifteen years after I was first
enamoured with photovoltaics, I was reading the excellent LOW-TECH
MAGAZINE's &lt;a href=&quot;https://solar.lowtechmagazine.com/&quot;&gt;&lt;i&gt;About the Solar Powered Website&lt;/i&gt;&lt;/a&gt; article, which details
how they managed to wire up a self-sufficient Raspberry Pi(-like)
based system as their server, which could, technically, run out of
power if the weather was unkind.
&lt;/p&gt;

&lt;p&gt;
What struck me as somewhat shocking is that the setup itself didn't
seem all that complicated. Yes, there were several parts, including
stuff whose purpose I wasn't aware of yet, but as a whole it seemed
remarkably approachable.
&lt;/p&gt;

&lt;p&gt;
This ultimately led me to the Youtube channel &lt;a href=&quot;https://www.youtube.com/@footprinthero&quot;&gt;Footprint Hero&lt;/a&gt;, which
features a guy called Alex Beale, whose entire shtick is to make easy
to understand videos about solar power and how a complete beginner
could start utilizing it themselves.
&lt;/p&gt;

&lt;p&gt;
I was so impressed by how turn-key the entire process seemed based on
his videos, that I became convinced that I want to try this stuff.
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;Disclaimer:&lt;/b&gt; I am a programmer by trade, not an electrician. While I
have a generally decent understanding of software, hardware is a whole
another beast and I am by all means pretty much a complete beginner. I
tried to ensure my post contains correct information, but I cannot
claim that I haven't made any mistakes in this post.
&lt;/p&gt;

&lt;p&gt;
Please keep this in mind while reading and do your own research! It's
well worth it, solar is a hella cool topic. Also corrections are very
welcome in comments, if you find this post on &lt;a href=&quot;https://ohai.social/@nemin&quot;&gt;social media&lt;/a&gt;.
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div id=&quot;outline-container-the-plan&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;the-plan&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;1.&lt;/span&gt; The plan&lt;/h2&gt;
&lt;div id=&quot;text-1&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
Still, before jumping into doing things, one needs a reasonable target
to aim for. Obviously, I wasn't going to just start filling my roof
with panels and running my entire home off solar. Such a project would
cost many thousands of euros, which I simply don't have, would require
hiring a bunch of engineers and workers, and finally it likely
wouldn't even pay for itself in a reasonable amount of time due to the
solar-unfriendly laws in my country.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.1&quot; href=&quot;#fn.1&quot; class=&quot;footref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
So, I decided to aim quite a bit lower. I have previously bought
myself a plug-in watt-meter, mostly just out of curiosity to see how
much power the different devices in my room use. The most interesting
finding was when I noticed that my PC rarely seems to use more than
150-200W and, when I'm not using it for any strenuous tasks, it
usually dips even lower into the 90-110W territory.
&lt;/p&gt;

&lt;p&gt;
There is a law in place that mandates that below a certain threshold
households pay a discounted price for electricity. We happen to be
usually below this line, but sometimes things don't work out quite how
we hoped and the meter creeps up slightly above the threshold. When
this happens, the bill is suddenly a lot higher than it should be.
&lt;/p&gt;

&lt;p&gt;
I figured, hey, if we only just go slightly higher every once in a
while, perhaps by taking my PC out of the equation we could ensure we
only ever stay in the discounted range.
&lt;/p&gt;

&lt;p&gt;
The jury is still out to see whether my theory works in practice, but
this was enough of an excuse for me to start looking into buying the
necessary things to build a small system.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-parts-of-a-solar-setup&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;parts-of-a-solar-setup&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;2.&lt;/span&gt; Parts of a solar setup&lt;/h2&gt;
&lt;div id=&quot;text-2&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
If you already understand how PV works, feel free to skip this part, I
just want to get everyone up to speed first. To run a tiny solar
system, one needs the following parts at the very least:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;b&gt;A battery:&lt;/b&gt; Stores excess energy in the form of chemical energy.
Nowadays batteries are usually made from Lithium-ion salts and range
from a couple hundred up to thousands of watt-hours worth of
capacity.&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
&lt;b&gt;An inverter:&lt;/b&gt; As solar panels generate direct current, while most
appliances use alternating current, you need a so-called &lt;a href=&quot;https://en.wikipedia.org/wiki/Power_inverter&quot;&gt;inverter&lt;/a&gt;
which can turn one into the other.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.2&quot; href=&quot;#fn.2&quot; class=&quot;footref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-03-28-solar/hbridge.avif&quot; alt=&quot;hbridge.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 1: &lt;/span&gt;Schematic of an inverter. Notice how the path of electricity keeps turning around (i.e. &lt;i&gt;inverting&lt;/i&gt;) with every switch. By &lt;a href=&quot;https://commons.wikimedia.org/w/index.php?curid=90469768&quot;&gt;C J Cowie&lt;/a&gt; - Own work, CC BY-SA 3.0.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Inverters use a set of &lt;a href=&quot;https://hu.wikipedia.org/wiki/IGBT&quot;&gt;transistor-pairs&lt;/a&gt;, which can be switched on
and off extremely fast. By flipping between these at twice the speed
of the intended frequency of the grid (50Hz in the US, 60Hz in th
EU), the current is forced to rapidly switch directions, thus
inducing an alternating wave, i.e. AC electricity.
&lt;/p&gt;

&lt;p&gt;
Sadly this process is not 100% efficient. Generally inverter
efficiency ranges between 85-95%, which means the true, useful
capacity of your battery can easily be 15% less than stated in the
specs.
&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
&lt;b&gt;A solar panel:&lt;/b&gt; Stars of the show. Generates electricity based on
more or less the same principle as LEDs just in the opposite way.
Did you know that you can actually use &lt;a href=&quot;https://hackaday.com/2024/07/23/you-can-use-leds-as-sensors-too/&quot;&gt;LEDs as an ad-hoc light
sensor&lt;/a&gt;? I found it a pretty cool piece of trivia.
&lt;/p&gt;

&lt;p&gt;
Photons radiated from the sun knock electrons free, which do a lap
around the circuit the panel is wired into. This is usable
electricity.
&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
&lt;b&gt;MPPT:&lt;/b&gt; Also known as a &amp;quot;&lt;a href=&quot;https://en.wikipedia.org/wiki/Maximum_power_point_tracking&quot;&gt;Maximum Power Point Tracker&lt;/a&gt;&amp;quot;. It is a small
device that connects to both your battery and your solar panel and
can, as the name implies, track the panel's maximum power point.
&lt;/p&gt;

&lt;p&gt;
The maximum power point is the intersection of the ideal amount of
voltage and current with regards to the solar panel's &lt;a href=&quot;https://en.wikipedia.org/wiki/Solar-cell_efficiency#Fill_factor&quot;&gt;&amp;quot;Fill factor&amp;quot;&lt;/a&gt;
(which basically quantifies the panel's quality) that provides the
highest amount of usable power.
&lt;/p&gt;

&lt;p&gt;
In an ideal world, where the panel is always in constant sunlight
and isn't affected by temperature, this device would not be needed,
as with those parameters frozen, the panel would generate a
practically constant voltage and current (without regards to
degradation, which would slowly worsen the output over the course of
a couple decades).
&lt;/p&gt;

&lt;p&gt;
However, realistically this is never the case. As seen on the graph
below, the hotter a panel gets, the less voltage it's able to
generate (see also &lt;a href=&quot;#nmot&quot;&gt;the section on NMOT&lt;/a&gt;):
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-03-28-solar/iv_curve.avif&quot; alt=&quot;iv_curve.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 2: &lt;/span&gt;The current-voltage curve of my particular panel. At 50˚C it provides nearly 6V less voltage, than at 0˚C. Taken from the &lt;a href=&quot;https://risenenergy.com.au/wp-content/uploads/RSM40-8-390-410M-IEC1500V-30mm-2022H1-1-EN_Black-frame-AU.pdf&quot;&gt;reference sheet&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
What's worse the output also varies massively by how much of the
panel is shaded. If you only used the raw output of a solar panel
you'd find that even if only one or two of its cells were shaded,
you would not only lose, say, 5-10% of your expected power, but up
to a quarter of &lt;a href=&quot;https://www.youtube.com/watch?v=Ya_DPtNj6Og&quot;&gt;even half of it&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
This is because the cells on a panel &lt;a href=&quot;https://www.solarchoice.net.au/learn/design-guide/does-shade-effect-solar-panels/&quot;&gt;are wired&lt;/a&gt; in a shape kind of
like a &amp;quot;string&amp;quot; or &amp;quot;pipe&amp;quot; and even a single shaded cell can act as a
blockage in this &amp;quot;pipe&amp;quot;, because not only do they not generate
current, they also act as resistors and thus allow very little
current to flow through them.
&lt;/p&gt;

&lt;p&gt;
To prevent completely choking the circuit, modern panels have
so-called &lt;a href=&quot;https://www.electronics-tutorials.ws/diode/bypass-diodes.html#:~:text=Diodes%20in%20Photovoltaic%20Arrays&quot;&gt;bypass diodes&lt;/a&gt; built in, which allow going around entire
rows of cells in case one of them becomes shaded.
&lt;/p&gt;

&lt;p&gt;
While this allows for the rest of the panel to operate unhindered
(and the shaded cell won't become damaged from the huge amounts of
current trying to flow through), it also means that a single shaded
cell can knock out at minimum an entire row, but with cheaper panels
(which don't use nearly as many diodes) easily a third of the panel
and thus drop the voltage considerably.
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-03-28-solar/mppt.avif&quot; alt=&quot;mppt.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 3: &lt;/span&gt;Power/Voltage curve of a solar system, with both shaded and unshaded curves. By &lt;a href=&quot;https://commons.wikimedia.org/w/index.php?curid=70987413&quot;&gt;Staberder&lt;/a&gt; - Own work, CC BY-SA 4.0.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
MPPTs exploit &lt;a href=&quot;https://en.wikipedia.org/wiki/Ohm%27s_law&quot;&gt;Ohm's law&lt;/a&gt;. By utilizing a DC-DC converter, it can
freely vary the apparent resistance (and thus the amount of voltage)
of the system and, per the aforementioned law, a change in voltage
will induce a change in current as well. By careful modification of
these values, the MPPT ensures that the panel is always operating at
the exact voltage that generates the greatest power and thus
delivers the highest wattage based on the current conditions.
&lt;/p&gt;

&lt;p&gt;
And as an added bonus, MPPTs can even account for things like the
composition of your battery and make sure it only ever charges with
safe parameters of input, as certain materials require vastly
different &amp;quot;charge profiles&amp;quot; (i.e. mix of voltage and current) to
operate safely.
&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
&lt;b&gt;Cabling and adapters:&lt;/b&gt; Solar panels generally use &lt;a href=&quot;https://en.wikipedia.org/wiki/MC4_connector&quot;&gt;MC4 connectors&lt;/a&gt;.
These are deep plugs, which slot tightly into each other, providing
a good amount of insulation against water and dust as these panels
and cables are expected to be exposed to the weather all year long.
&lt;/p&gt;

&lt;p&gt;
However, power stations (see below) generally do not come with MC4
plugs. Rather they are most often equipped with &lt;a href=&quot;https://en.wikipedia.org/wiki/DC_connector#XT_connectors&quot;&gt;XT60 connectors&lt;/a&gt;.
These are easiest to recognise by their unique shape and yellow /
orange coloration. From what I understand, these were originally
made to connect car parts, but I couldn't really find any good
sources on them.
&lt;/p&gt;

&lt;p&gt;
Generally (unless you're doing things fully DIY, in which case you
don't need this explanation in the first place) you will likely need
an MC4-XT60 adapter and these are usually &lt;b&gt;not&lt;/b&gt; part of the package.
Some solar panels do come with them, but you're largely expected to
pick them up yourself. From what I've found, they are surprisingly
expensive.
&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
&lt;b&gt;Power station:&lt;/b&gt; If you don't want to DIY everything, you can also
get a power station, which will serve as your battery, inverter, and
MPPT at the same time.
&lt;/p&gt;

&lt;p&gt;
On one hand going this route is much easier as you get a complete
device that you just have to plug in and use. It is also far safer
for a beginner, because you don't have to measure things out and
make sure everything is properly grounded, insulated, and well
integrated. Finally, you also get a warranty and even a couple of
shiny toys in the form of a companion app that can allow you to
monitor and manipulate the station from afar. This is the option I
ultimately went with.
&lt;/p&gt;

&lt;p&gt;
On the other hand, it's usually more expensive to buy a complete
solution compared to building one from parts. I've seen videos where
people mentioned that they can easily get double or nearly triple
the capacity from building their batteries from scratch for the same
price as a pre-built station would cost. You also learn a lot more
this way and can swap out any parts if you want to upgrade, which is
not something a complete station can usually accommodate.
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-my-particular-setup&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;my-particular-setup&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;3.&lt;/span&gt; My particular setup&lt;/h2&gt;
&lt;div id=&quot;text-3&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
With the basics out of the way, here's an itemized list of everything
I've bought for this &amp;quot;experiment&amp;quot; / pet project along with my
comments and reasoning for them. The list will start of small and get
extended as new parts are introduced.
&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-power-station&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;power-station&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.1.&lt;/span&gt; Power station&lt;/h3&gt;
&lt;div id=&quot;text-3-1&quot; class=&quot;outline-text-3&quot;&gt;
&lt;table rules=&quot;groups&quot; frame=&quot;hsides&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; border=&quot;2&quot;&gt;


&lt;colgroup&gt;
&lt;col class=&quot;org-left&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Item&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Price (HUF)&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Price (EUR)&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.3&quot; href=&quot;#fn.3&quot; class=&quot;footref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;EcoFlow RIVER 3 Plus&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;107380&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;275&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Sum&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;107380&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;275&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
My first purchase was an &lt;a href=&quot;https://www.ecoflow.com/us/river-3-plus-portable-power-station&quot;&gt;EcoFlow RIVER 3 Plus&lt;/a&gt; (which I'll refer to as
&amp;quot;RIVER 3&amp;quot; for the rest of the post as it's a mouthful). This is a
surprisingly cheap power station, that can consume 220W of solar
power, which pretty much the highest I found in this price category.
There are stations that can be charged with 300W or even as high as
1000W, but seeing how my PC rarely goes above 200W, I figured 220W
should be plenty for the time being.
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;Note from the future:&lt;/b&gt; As it later turns out, it wasn't quite enough.
&lt;/p&gt;

&lt;p&gt;
I'll touch on the &amp;quot;why&amp;quot; later, but first I'd still like to make it
clear that I think the RIVER 3 is an excellent product if you don't
have major capacity needs (you only really need an uninterruptible
power supply or want to run minor appliances in places where you have
no access to electricity) or if you are able to get good solar
coverage for most of your day.
&lt;/p&gt;

&lt;p&gt;
It's small, cheap, and has a quite impressive phone application with a
lot of useful data and controls. Furthermore it's easily lightweight
enough to carry around even in a backpack if you happen to need much
more power than a powerbank could provide.
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-solar-panel&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;solar-panel&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.2.&lt;/span&gt; Solar panel&lt;/h3&gt;
&lt;div id=&quot;text-3-2&quot; class=&quot;outline-text-3&quot;&gt;
&lt;table rules=&quot;groups&quot; frame=&quot;hsides&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; border=&quot;2&quot;&gt;
&lt;caption class=&quot;t-above&quot;&gt;&lt;span class=&quot;table-number&quot;&gt;Table 1:&lt;/span&gt; Adding in my initial choice of panel.&lt;/caption&gt;

&lt;colgroup&gt;
&lt;col class=&quot;org-left&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Item&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Price (HUF)&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Price (EUR)&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.3.3&quot; href=&quot;#fn.3&quot; class=&quot;footref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;EcoFlow RIVER 3 Plus&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;107380&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;275&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;220W Solar Panel&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;46790&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;120&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Sum&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;154170&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;395&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
I was originally going for a 220W panel by &lt;a href=&quot;https://voltpolska.pl/fotowoltaika/panel-fotowoltaiczny-mono-220w-195v-1480x670x35mm-przewod-mc4-90cm-ramka-.html&quot;&gt;VOLT Polska&lt;/a&gt;, since it felt
logical that I should match my expected wattage to my power station's.
I also liked, that the panel itself is relatively &amp;quot;small&amp;quot; (147x67
centimetres). The price itself was good too, at only around 120€, so I
placed an order and started waiting.
&lt;/p&gt;

&lt;p&gt;
However, not a whole day has passed and I quickly realized I may have
been a bit too hasty. The more I continued to read about the topic,
the more it seemed like it may not be such a good idea to perfectly
match the two ratings, because you only really get 4-6 hours of max
power. Which means the moment those ideal conditions pass, there is
nothing guaranteeing my panel's output could keep up with the PC's
requirements.
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-03-28-solar/panel.avif&quot; alt=&quot;panel.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 4: &lt;/span&gt;My current solar-setup. The panel stands on stacked bricks, with a piece of cardboard taking the brunt of its weight, so that the cables are unburdened.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Instead, I learned what one should do is to &amp;quot;overpanel&amp;quot;. What this
means is to find a panel (or multiple) whose voltage (and preferably
current too) is within the limits and its wattage above the power
station's maximum, so that even if the panel is operating only at a
fraction of of its full strength, you're still getting as much power
as your station can take.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.4&quot; href=&quot;#fn.4&quot; class=&quot;footref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
I am lucky enough to live in a home with a relatively large garden, so
I had enough space for a much larger panel than I was originally going
to go with. This is good news for my budget, because solar panels tend
to have an inverse relation between size and price. This only makes
sense: The more panels you can place in the same area, the more power
you can generate.
&lt;/p&gt;

&lt;table rules=&quot;groups&quot; frame=&quot;hsides&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; border=&quot;2&quot;&gt;
&lt;caption class=&quot;t-above&quot;&gt;&lt;span class=&quot;table-number&quot;&gt;Table 2:&lt;/span&gt; Swapping for the panel I ultimately went with.&lt;/caption&gt;

&lt;colgroup&gt;
&lt;col class=&quot;org-left&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Item&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Price (HUF)&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Price (EUR)&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.3.3&quot; href=&quot;#fn.3&quot; class=&quot;footref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;EcoFlow RIVER 3 Plus&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;107380&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;275&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;410W Solar Panel&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;24999&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;64&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Sum&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;132379&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;339&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
Because of this I was able to find a panel from &lt;a href=&quot;https://risenenergy.com.au/wp-content/uploads/RSM40-8-390-410M-IEC1500V-30mm-2022H1-1-EN_Black-frame-AU.pdf&quot;&gt;RISEN Energy&lt;/a&gt; that is
rated for a whopping 410W and which costs only half the price of the
previous one at 64€. Excellent deal in almost all aspects, with the
only ugly detail being that this panel is a monster in terms of size:
176x110 centimetres. A simple napkin-calculation tells me that it's
almost 1.2m&lt;sup&gt;2&lt;/sup&gt; bigger than the old candidate.
&lt;/p&gt;

&lt;p&gt;
I'm not a big car guy in any meanings of the words, so I only drive an
old 20+ years old Ford Fiesta. It's a tiny car, but I figured I don't
need no shipping for my panel, &amp;quot;I'll just put the panel in the trunk
and drive it home!&amp;quot; Yeah… A tip for future generations: A 2003 Ford
Fiesta cannot accommodate a solar panel of this size.
&lt;/p&gt;

&lt;p&gt;
Don't ask me how I learned.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-cabling&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;cabling&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.3.&lt;/span&gt; Cabling&lt;/h3&gt;
&lt;div id=&quot;text-3-3&quot; class=&quot;outline-text-3&quot;&gt;
&lt;table rules=&quot;groups&quot; frame=&quot;hsides&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; border=&quot;2&quot;&gt;


&lt;colgroup&gt;
&lt;col class=&quot;org-left&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Item&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Price (HUF)&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Price (EUR)&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.3.3&quot; href=&quot;#fn.3&quot; class=&quot;footref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;EcoFlow RIVER 3 Plus&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;107380&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;275&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;410W Solar Panel&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;24999&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;64&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;MC4-XT60i adaptor&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;9489&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Sum&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;141868&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;363&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
I also found out that the RIVER 3 does not come with a solar cable,
which was a little frustrating to learn, but I quickly found a store
where such an adaptor is sold. It came in three different lengths,
2.5m, 3.5m, and 5m.
&lt;/p&gt;

&lt;p&gt;
I ultimately picked the smallest, 2.5m one. Anything over that
would've been overkill, since I was going to place the solar panel
right next to my window anyway, which is right next to where I
intended to store the battery.
&lt;/p&gt;

&lt;table rules=&quot;groups&quot; frame=&quot;hsides&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; border=&quot;2&quot;&gt;


&lt;colgroup&gt;
&lt;col class=&quot;org-left&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Item&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Price (HUF)&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Price (EUR)&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.3.3&quot; href=&quot;#fn.3&quot; class=&quot;footref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;EcoFlow RIVER 3 Plus&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;107380&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;275&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;410W Solar Panel&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;24999&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;64&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;MC4-XT60i adaptor&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;9489&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;24&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;MC4 &amp;quot;Super-Flat&amp;quot; cord&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;8080&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Sum&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;149948&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;384&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
I also ended up buying a so-called &amp;quot;Super-Flat&amp;quot; cable, which allows
you to run it under either a door or (as in my case) through a
window-frame without needing to cut a hole.
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-03-28-solar/superflat.avif&quot; alt=&quot;superflat.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 5: &lt;/span&gt;The cable slips under the window's insulating foam, allowing for the window itself to stay closed.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
It was a bit annoying to dish out another 20€ or so, but I figured if
this whole thing doesn't pan out, I'd be left with a hole on my
window-frame, which (even if small) would likely negatively affect the
comfort of my room.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-the-initial-results&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;the-initial-results&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;4.&lt;/span&gt; The initial results&lt;/h2&gt;
&lt;div id=&quot;text-4&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
It was late in the afternoon when I finally received all the necessary
parts. I was very eager to try things out, but when we plugged
everything in and I excitedly checked the RIVER 3's display, I was a
little horrified to find that the panel (which, to remind you, was
supposedly able to output 410W in ideal conditions) was only giving me
a measly five watts. What's worse, I also saw some weird discoloration
on the solar cells and the protective cardboard cover that the panel
came in had a footprint on it, so I immediately started thinking of
the worst. Not to mention how I'd get the panel back to the store.
&lt;/p&gt;

&lt;p&gt;
Still, we figured it'd be worth waiting at least till the next day to
see if the low power is just caused by the lethargic weather or if we
really received a malfunctioning panel.
&lt;/p&gt;

&lt;p&gt;
The night passed and during the next morning, to my great relief and
joy, I saw that the station was charging at 30W and the amount was
rapidly climbing in tandem with the sun. I messed with the panel's
position and angle a little and soon enough (around 10-11AM) I started
getting a constant 220W, the maximum the RIVER 3 is capable of
receiving.
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-03-28-solar/river3.avif&quot; alt=&quot;river3.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 6: &lt;/span&gt;The RIVER 3 in operation at peak performance.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
I waited until the power station was fully charged and then
disconnected my PC and monitor from the grid and switched them over to
the battery.
&lt;/p&gt;

&lt;p&gt;
At first the PC didn't boot. Even after I pressed the button a couple
of times, it seemed completely dead. This was a bit puzzling, but then
I realized I just needed to turn on the station's &amp;quot;X-Boost&amp;quot; mode
(which allows power surges of up to 1200W) and suddenly it sprang into
life without issue.
&lt;/p&gt;

&lt;p&gt;
My assumption is that during boot the PC draws in way more electricity
and (without the special mode enabled) the resulting load may have
been so large, that the power station thought it's an electric fault
and blocked it with surge protection.
&lt;/p&gt;

&lt;table rules=&quot;groups&quot; frame=&quot;hsides&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; border=&quot;2&quot;&gt;
&lt;caption class=&quot;t-above&quot;&gt;&lt;span class=&quot;table-number&quot;&gt;Table 3:&lt;/span&gt; My best-effort measurements over a day of (early spring) use.&lt;/caption&gt;

&lt;colgroup&gt;
&lt;col class=&quot;org-left&quot; /&gt;

&lt;col class=&quot;org-center&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Weather condition&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-center&quot;&gt;Time of day&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Minimum wattage&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Maximum wattage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Direct, strong sunlight&lt;/td&gt;
&lt;td class=&quot;org-center&quot;&gt;9AM - 14PM&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;190&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;220&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Early afternoon&lt;/td&gt;
&lt;td class=&quot;org-center&quot;&gt;14PM - 15PM&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;30&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;60&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Cloudy / indirect sunlight&lt;/td&gt;
&lt;td class=&quot;org-center&quot;&gt;16PM - 17PM&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;15&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;40&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Minimal sunlight / after sundown&lt;/td&gt;
&lt;td class=&quot;org-center&quot;&gt;17PM - 9AM&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;0&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-shortcomings-of-the-river-3&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;shortcomings-of-the-river-3&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;4.1.&lt;/span&gt; Shortcomings of the RIVER 3&lt;/h3&gt;
&lt;div id=&quot;text-4-1&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
Here's the list of negatives I've experienced using the RIVER 3:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;p&gt;
While the mobile app of the RIVER 3 is far superior to the
replacement I went with, I really don't like that it is an always
online IoT device.
&lt;/p&gt;

&lt;p&gt;
I'm not super paranoid or anything, nor do I necessarily think that
someone could hack the home network through a power station, but
it's still not really nice that it's always out there listening.
&lt;/p&gt;

&lt;p&gt;
In an ideal world you'd have a physical switch whether you want IoT
capabilities or not (the replacement has this) and pairing should
work without any online account necessary, with purely local
authentication (think the LED screen showing a code that you need to
enter on your phone or similar).
&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
Charging with 220W proved to be not nearly enough. While I do get
many generally sunny hours, the panel is only in direct sunlight for
a fraction of these.
&lt;/p&gt;

&lt;p&gt;
Therefore, because only these hours provide the maximum power the
panel is capable of, I am forced to utilize this period to get as
much energy as I can. And the fact that my station &amp;quot;artificially&amp;quot;
caps my panel at pretty much half it should be capable of kinda
sucks and severely limits my daily net wattage.
&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
But even if it was capable of collecting more from the panel the
biggest bottleneck of this setup was the size of the battery inside
the RIVER 3.
&lt;/p&gt;

&lt;p&gt;
Much of the 220W was wasted at peak hours as the station was already
fully charged and, once the low-light hours arrived, the battery's
268Wh worth of capacity could only power my PC for just about two
hours.
&lt;/p&gt;

&lt;p&gt;
Now, this didn't mean I was out of luck in the afternoon, because
the RIVER 3 comes with a feature that allows the user to set a
certain percentage below which the station switches over to mains. I
set this to 30% capacity, to ensure whatever happens I still have
some power left.
&lt;/p&gt;

&lt;p&gt;
The rub was, however, that with this setup I wasn't fully
&amp;quot;sun-sufficient&amp;quot;.
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-a-hasty-but-needed-upgrade&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;a-hasty-but-needed-upgrade&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;5.&lt;/span&gt; A hasty, but needed upgrade&lt;/h2&gt;
&lt;div id=&quot;text-5&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
This didn't sit too well with me. The idea that I'd pretty much just
waste a large share of the power I generate because the battery was
too small felt like a half-victory at best and an annoying complete
failure to accomplish my original goal at worst.
&lt;/p&gt;

&lt;p&gt;
I quickly looked up how much it'd cost to get an expansion battery for
the RIVER 3, but what I found to my horror is that it'd actually cost
more than the base device itself. It goes without saying, that it
would have also provided an almost 3x increase to my max stored
wattage (bringing it from 268Wh to 840Wh), but at that point it
genuinely felt like it'd make sense to simply look for a device that
can both store more power &lt;i&gt;and&lt;/i&gt; take more input from my panel at the
same time.
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-03-28-solar/anker.avif&quot; alt=&quot;anker.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 7: &lt;/span&gt;The Anker SOLIX C1000 sitting on a tiny chair. It provides power to two PCs at the same time.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
And, if I wanted to be that, there was little better choice (with my
budget and circumstances anyway) than the &lt;a href=&quot;https://www.ankersolix.com/products/c1000&quot;&gt;Anker SOLIX C1000&lt;/a&gt;, which is
a beast on a whole another level. Not only is its capacity near five
times the RIVER 3's (meaning the Anker outdoes it even with the
extension battery's additional storage), but it can also be charged
with up to 600W of solar.
&lt;/p&gt;

&lt;p&gt;
Obviously it's also quite a bit more expensive (costing about 2.5x the
price of the RIVER 3), but at this point, I was pretty much all-in:
&lt;/p&gt;

&lt;table rules=&quot;groups&quot; frame=&quot;hsides&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; border=&quot;2&quot;&gt;
&lt;caption class=&quot;t-above&quot;&gt;&lt;span class=&quot;table-number&quot;&gt;Table 4:&lt;/span&gt; Swapped the old station to the new one. Ouch.&lt;/caption&gt;

&lt;colgroup&gt;
&lt;col class=&quot;org-left&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Item&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Price (HUF)&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Price (EUR)&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.3.3&quot; href=&quot;#fn.3&quot; class=&quot;footref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Anker SOLIX C1000&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;240000&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;615&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;410W Solar Panel&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;24999&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;64&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;MC4-XT60i adaptor&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;9489&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;24&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;MC4 &amp;quot;Super-Flat&amp;quot; cord&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;8080&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Sum&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;282568&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;724&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
Sadly this does mean I almost doubled my initial budget, but 725€ is
still not exactly a major expense compared to a proper solar setup.
&lt;/p&gt;

&lt;p&gt;
However, the changes are immediately obvious and almost all positive.
Due to the size of the new battery, I'm able to run not only my own
but also my partner's PC and by the station's own measurements, both
should be good for nearly the whole day &lt;b&gt;without&lt;/b&gt; accounting for the
solar input.
&lt;/p&gt;

&lt;table rules=&quot;groups&quot; frame=&quot;hsides&quot; cellspacing=&quot;0&quot; cellpadding=&quot;6&quot; border=&quot;2&quot;&gt;
&lt;caption class=&quot;t-above&quot;&gt;&lt;span class=&quot;table-number&quot;&gt;Table 5:&lt;/span&gt; Results with the Anker SOLIX C1000&lt;/caption&gt;

&lt;colgroup&gt;
&lt;col class=&quot;org-left&quot; /&gt;

&lt;col class=&quot;org-center&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;

&lt;col class=&quot;org-right&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-left&quot;&gt;Weather condition&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-center&quot;&gt;Time of day&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Minimum wattage&lt;/th&gt;
&lt;th scope=&quot;col&quot; class=&quot;org-right&quot;&gt;Maximum wattage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Dawn&lt;/td&gt;
&lt;td class=&quot;org-center&quot;&gt;6AM - 7AM&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;50&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;100&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Early morning&lt;/td&gt;
&lt;td class=&quot;org-center&quot;&gt;7AM - 8AM&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;150&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;180&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Peak sunlight hours&lt;/td&gt;
&lt;td class=&quot;org-center&quot;&gt;9AM - 14PM&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;200&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;350&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Afternoon / Past peak hours&lt;/td&gt;
&lt;td class=&quot;org-center&quot;&gt;14PM - 18PM&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;40&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;100&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class=&quot;org-left&quot;&gt;Minimal sunlight / after sundown&lt;/td&gt;
&lt;td class=&quot;org-center&quot;&gt;18PM - 6AM&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;0&lt;/td&gt;
&lt;td class=&quot;org-right&quot;&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;A quick note about my results:&lt;/b&gt; Look, this is the n=1 measurement of
a single guy living in Budapest, taking random samples of his solar
setup in a single season and in a very short time period.
&lt;/p&gt;

&lt;p&gt;
Please do not use these numbers to guesstimate your expected
production / setup needs. Solar depends on a ton of variables,
including sunny hours, placement of your panels, temperature,
latitude, materials used, length of your cabling, quality of your
inverter, etc.
&lt;/p&gt;

&lt;p&gt;
My experiences might give you an alright idea what to expect, if you
live in similar conditions, but that's about it. If you get a
hankering for that sweet free electricity, please spend your first
week just reading up on stuff (including laws) before jumping in. In
the best case, you just gave yourself a huge head start, in the worst
case you spared yourself a lot of headache and several hundred euros.
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
As for the solar, at the time of writing I was able to get a
consistent 300W and above through the sunny hours, which is a
night-and-day change compared to the 180W averages I've had with the
RIVER 3. While it is still not quite the 410W you see on the tin, I'm
not disappointed. As it turns out manufacturers generally provide two
different metrics:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;p&gt;
Output at &lt;b&gt;Standard Test Conditions&lt;/b&gt; (or STC): Basically ideal,
lab-made conditions where the panel can deliver as much power as
it's able. Think measuring what power is generated when a very
bright light is aimed dead-on a reasonably cold panel.
&lt;/p&gt;

&lt;p&gt;
STC serves as a good measurement at the maximum possible output you
can expect (this can be handy to pick the right wires and safety
mechanisms) and it is the one number you'll definitely see on all
solar panels, no matter their manufacturer or place of origin.
However, it does not necessarily give a realistic idea about the
panel's general performance.
&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
&lt;a id=&quot;nmot&quot;&gt;&lt;/a&gt; Output at &lt;b&gt;Nominal Module Operating Temperature&lt;/b&gt; (or NMOT):
What STC fails to account for is that the output of solar panels has
an inverse relationship with their temperature.
&lt;/p&gt;

&lt;p&gt;
This means the hotter your panel gets (and it will get hotter, after
all, the sun is shining on it all day long), the less power you'll
be able to harvest.
&lt;/p&gt;

&lt;p&gt;
Solar.com &lt;a href=&quot;https://www.solar.com/learn/does-solar-panel-temperature-coefficient-matter/&quot;&gt;found&lt;/a&gt; that for every degree Celsius above 25˚C, your
panel's efficiency will drop by around 0.3-0.5%. Which means on a
hot summer, where your panel might heat up to about 35˚C, you can
expect a 3-5% drop in efficiency. Considering most panels generally
operate between 20-25% efficiency, this drop can be significant.
&lt;/p&gt;

&lt;p&gt;
NMOT is the temperature which your panel is expected to reach under
the conditions of 20˚C ambient temperature, slight wind, and
800W/m&lt;sup&gt;2&lt;/sup&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Irradiance&quot;&gt;irradiance&lt;/a&gt;. In my panel's case, this is 44˚C and the
manufacturer reports a 0.34% drop in power per degree. Therefore, if
I understand things correctly, I can expect a &lt;code&gt;(44-25) * 0.34% ≈
  6.5%&lt;/code&gt; drop in efficiency at peak temperature.
&lt;/p&gt;

&lt;p&gt;
By RISEN's own reckoning this means the panel will provide in
general about 310W if I both get a ton of sunlight and high
temperatures. Indeed, while I was able to get higher wattage already
(even hitting higher than 410W for very brief periods), my general
sustained peak wattage does seem to be around 325-350W.
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Beyond the station itself, the package also came with a
parallel-connection MC4 cable, so if I ever want to upgrade my setup
by buying a second panel, that's one less item on the list. It's by no
means a must-have nor is it nearly the most expensive part, but it's a
nice gesture from the company, especially compared to the RIVER 3's
bare-bones accessories.
&lt;/p&gt;

&lt;p&gt;
In terms of non-solar charging, the station is able to be charged by a
whopping 1000W using a wall socket (1300W if you enable &amp;quot;UltraFast
charging&amp;quot;, though the manufacturer warns against using it too much as
it can harm the battery in the long run), which is a lot more than the
RIVER 3's 600W and would charge a completely drained battery in
practically an hour (and a bit, due to inefficiencies), which is
excellent for its relatively large capacity.
&lt;/p&gt;

&lt;p&gt;
I still intend to stick with solar-only (this was the main reason I
switched in the first place), but if I'm ever in a pinch, this kind of
charging speed could give me a serious second wind.
&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-shortcomings-of-the-anker-solix&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;shortcomings-of-the-anker-solix&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;5.1.&lt;/span&gt; Shortcomings of the Anker SOLIX&lt;/h3&gt;
&lt;div id=&quot;text-5-1&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
This is, however, where the panel's not insignificant list of
negatives start too:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;p&gt;
You cannot set min-max values for the battery. These are a set of
percentages below which the station automatically shuts its outputs
down and above which it'll refuse to charge.
&lt;/p&gt;

&lt;p&gt;
People usually set these to 20% and 80%, which give you ample usage,
while also serving a dual purpose: The limit allows some leeway in
case you are caught with your pants down in a blackout. Even if you
were using your battery, you still have 20-30% to keep your machines
on for a little while. The other reason is to protect the
Lithium-ion cells, as both completely discharging them or filling
them to full causes excessive wear.
&lt;/p&gt;

&lt;p&gt;
Anker &lt;a href=&quot;https://support.ankersolix.com/s/article/Is-an-In-App-80-Charging-Limit-Required-for-My-Power-Station&quot;&gt;claims&lt;/a&gt;, that the second part is a non-issue, as the battery
they use doesn't suffer from such limitations. However, if you
happen to use your battery constantly like I do, you have to make
sure yourself there's always enough juice in it in case a blackout
hits. It's not a complete deal-breaker, but it's a bit of a shame.
&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
You also cannot set the same &amp;quot;only charge from AC if the battery
dips below X%&amp;quot; logic as with the RIVER 3.
&lt;/p&gt;

&lt;p&gt;
I've seen posts that this was introduced in the &lt;a href=&quot;https://www.ankersolix.com/eu/products/c1000-gen2&quot;&gt;Gen 2&lt;/a&gt; version of the
SOLIX C1000, but that's not the one I have and while they're
similarly priced, I'm not too keen on sending yet another station
back, paying even more, and then waiting for a new one to ship.
&lt;/p&gt;

&lt;p&gt;
Not to mention the Gen 2 is also not expandable like the Gen 1 is,
which would currently not be an issue, but if I ever want to
upgrade, I would like to have the option.
&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
The phone app's metrics are not nearly as informative. The RIVER 3
counted how many watt-hours you've used and how much you've charged
back by type of input, which was perfect for my purposes of seeing
how much electricity I can use without relying on the grid. Its
charts were also a lot longer and higher-resolution, while the
Anker's feels more like an afterthought / gadget to gawk at instead
of something you could seriously keep an eye on.
&lt;/p&gt;

&lt;p&gt;
In general, the app is a bit unreliable. The battery's status
(percent charged and current input/output) updates slowly and gives
no indication that the data you see is outdated. I've had instances
where the app said the battery was around 80% charged, while the box
itself said 95%. Similarly, the reported charge speeds can vary a
lot too. It can take 5-10 seconds for the box to transmit new data
and update to the real values, so I tend to just check the box
itself.
&lt;/p&gt;

&lt;p&gt;
I also had a couple weird disconnection issues, where I had to reset
the box completely and delete then reconnect from the phone app to
properly establish a connection between the two again.
&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;The fan of the box is somewhat louder, especially when you hit peak
solar hours and get performance above 300W. I wouldn't necessarily
call it annoying (I don't even hear it in headphones), but if you
need a quiet accumulator, because you either cannot bear a constant
fan noise or your work requires silence, the RIVER 3 wins by a long
shot.&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
The box is also large and heavy. Which is not really a surprise, nor
is a fair comparison, considering the difference in capacity, but
it's still worth emphasizing.
&lt;/p&gt;

&lt;p&gt;
You can throw the RIVER 3 on your desk and it'll not take too much
space, nor is it terribly heavy (at 4.5kgs). You absolutely cannot
do the same with the SOLIX, as it's nearly 13kgs heavy and also the
size of two shoe boxes on top of each other.
&lt;/p&gt;

&lt;p&gt;
I'd still ultimately classify it well within &amp;quot;easy to carry around&amp;quot;
compared to even larger stations, but it definitely requires far
more planning where to place it compared to the RIVER 3.
&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
Finally, the box tries to be very smart about the power it wastes,
so whenever you're not using one of the ports, be it input or
output, it automatically closes them down.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.5&quot; href=&quot;#fn.5&quot; class=&quot;footref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
This sounds great on paper, but during late dusk and early daybreak
the panel has &lt;i&gt;juuust&lt;/i&gt; enough sunlight to fluctuate between 0 and
1W. The station, being ever so diligent, switches its input port on
and off whenever it perceives that electricity starts or stops
flowing. And since these ports are isolated using &lt;a href=&quot;https://en.wikipedia.org/wiki/Relay&quot;&gt;relays&lt;/a&gt; which make
an audible metallic pop with every actuation, you get about 20
minutes of rhythmic clicking which is both very grating in general
and loud enough to wake me whenever I forget to pull the cord.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.6&quot; href=&quot;#fn.6&quot; class=&quot;footref&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
Apparently this is &lt;a href=&quot;https://www.reddit.com/r/anker/comments/1nsuqza/solix_c800_makes_clicks/&quot;&gt;known and &amp;quot;expected&amp;quot;&lt;/a&gt; behaviour. I intend to fix
it by purchasing a circuit breaker (which will also serve as a
convenient fuse to protect the battery) and just turn it off at
night and thus prevent these blips, but I really wish this was as
convenient as with the RIVER 3 which just completely avoids the
issue by not reacting to such low inputs.
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-conclusion&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;6.&lt;/span&gt; Conclusion&lt;/h2&gt;
&lt;div id=&quot;text-6&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
And yet, despite all these issues, I still think I made the right
choice and, if my rough calculations are correct, I'll be able to save
around 60-80kWh a month by driving the two PCs by solar.
&lt;/p&gt;

&lt;p&gt;
Now, 60-80kWh might not be a life-changing amount, but considering
that building and owning this project gave me a lot of joy &lt;i&gt;and&lt;/i&gt; atop
that it's paying for itself (even if extremely slowly) I think it was
more than worth it.
&lt;/p&gt;

&lt;p&gt;
Another interesting side-effect is that I'm feeling a lot more
conscious about my PC usage. Now, I'm not expecting some miracle here,
where I'll reconnect with nature and barely if ever turn on my PC, I
am fairly dependent on my computer. But turning electricity from
something you don't ever think about into something that I have a
nominally &amp;quot;finite&amp;quot; supply of gives me at least some pause before
turning it on and incentivizes me to take longer breaks to allow for
the battery to charge more / faster.
&lt;/p&gt;

&lt;p&gt;
Finally, I learned a lot about electricity while working on this post
and project. While I did have physics classes both in middle school
and university (also an actual electrics class in the latter), the
materials there sadly mostly entered in one ear and left through the
other. I don't think it was the fault of my teachers, more me being a
bit too checked out to care. Now that I'm learning at my own leisure
and interest, I find things are sticking a lot better and I'm building
intuition for things I kinda-sorta understood on a surface-level
before, but never to the extent to really comprehend what's going on.
Physics is actually really cool!
&lt;/p&gt;

&lt;p&gt;
Take care and thanks for reading!
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;h2 class=&quot;footnotes&quot;&gt;Footnotes: &lt;/h2&gt;
&lt;div id=&quot;text-footnotes&quot;&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.1&quot; href=&quot;#fnr.1&quot; class=&quot;footnum&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
While the power company is obligated by law to buy your excess
solar power, they only pay about 5 HUF / kWh. At the time of writing
that is just &lt;i&gt;barely&lt;/i&gt; above a single euro cent.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
Assuming it'd cost about 10.000€ for a complete solar setup for a home
(there are grants you can apply for, but for the sake of simplicity,
let's ignore those) and about 40 kWh / day excess, it'd take you 68
years to make that money back without accounting for your power bill.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
And if you do account for that (our household generally pays about
310-350€ a year for electricity) it'd still take 30 years to make your
money back. Which far less terrible, but is still a far cry from the
US's 5-10 years.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.2&quot; href=&quot;#fnr.2&quot; class=&quot;footnum&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
And if you're curious how to turn AC back into DC, look no
further than &lt;a href=&quot;https://www.youtube.com/watch?v=sI5Ftm1-jik&quot;&gt;FULL BRIDGE RECTIFIERS&lt;/a&gt;.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.3&quot; href=&quot;#fnr.3&quot; class=&quot;footnum&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Assuming 390:1 HUFEUR exchange rate.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.4&quot; href=&quot;#fnr.4&quot; class=&quot;footnum&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Every article about overpaneling will stress this and so will
I:
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
It is generally okay to go over the current limit, because (besides
malfunctions) your power station will only ever pull as much as it's
able to and the rest is just wasted. Do consider fuses though, they're
far easier and cheaper to replace in case of an issue.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
It is &lt;b&gt;NOT&lt;/b&gt; okay to go over the voltage limit. If current is &amp;quot;pull&amp;quot;,
voltage is &amp;quot;push&amp;quot; and it &lt;b&gt;will&lt;/b&gt; push as hard as it can. Meaning at
best you'll blow your station's internals and waste a bunch of money,
at worst you'll burn down your home. Electronics are not a toy.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.5&quot; href=&quot;#fnr.5&quot; class=&quot;footnum&quot;&gt;5&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
More specifically input ports are opened and close
automatically always, while output ports are fully manual by default,
but you can set up either one-time &amp;quot;shut it off after X hours&amp;quot; or
general &amp;quot;shut it off after X minutes/hours of inactivity&amp;quot; timers for
them.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.6&quot; href=&quot;#fnr.6&quot; class=&quot;footnum&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Despite my annoyance, I'm more than glad that this feature
exists, even if I think it could be implemented in a smarter way.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
Solar panels can technically cause &amp;quot;backflow&amp;quot;, meaning electricity
flows backwards and is wasted through the panel in the form of heat
and (invisible) light. By physically severing the connection, such
issues are avoided.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
I just wish they implemented some sort of exponential retry. I.e. if
you freshly plug in something, it immediately latches and tries to
start charging. If it finds power, then that's that. If it doesn't, it
retries a second later, then two seconds later, then four, etc. until
it reaches some sane upper limit or the user manually pulls out and
reconnects the cable.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
This would ultimately still allow patchy inputs, still protect against
backflow and would solve the constant annoying popping.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;/div&gt;</content></entry><entry><title>Be aware of your Locale</title><id>https://nemin.hu/locale.html</id><author><name>Nemin</name></author><updated>2026-03-14T15:00:00Z</updated><link href="https://nemin.hu/locale.html" rel="alternate" /><content type="html">&lt;div role=&quot;doc-toc&quot; id=&quot;table-of-contents&quot;&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;div role=&quot;doc-toc&quot; id=&quot;text-table-of-contents&quot;&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#the-problem&quot;&gt;1. The problem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#the-solution&quot;&gt;2. The solution&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
I happened to have &lt;a href=&quot;./guix-one-month-later.html&quot;&gt;reinstalled&lt;/a&gt; my PC recently. While I have set things
up to pretty much out of the box, I have left one glaring omission
which came back to haunt me (for a single afternoon or two, after
which I've devised and quickly deployed a fix).
&lt;/p&gt;
&lt;div id=&quot;outline-container-the-problem&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;1.&lt;/span&gt; The problem&lt;/h2&gt;
&lt;div id=&quot;text-1&quot; class=&quot;outline-text-2&quot;&gt;
&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;Note:&lt;/b&gt; This post will follow mostly the same path I took to
investigate. Except a couple dead ends before I &amp;quot;figure out&amp;quot; the
solution.
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
I was writing my previous article about &lt;a href=&quot;./lasik.html&quot;&gt;getting LASIK&lt;/a&gt;, when I suddenly
noticed all the Euro signs in the article were replaced with &amp;quot;???&amp;quot;.
This perturbed me quite a bit, chiefly because I would've found it
&lt;i&gt;really&lt;/i&gt; unbelievable to think that even a simple SSG like &lt;a href=&quot;https://dthompson.us/projects/haunt.html&quot;&gt;Haunt&lt;/a&gt; would
not handle special characters properly.
&lt;/p&gt;

&lt;p&gt;
And yet &amp;quot;that's exactly what happened&amp;quot; (obviously not, but bear with
me for a moment): The encoding of the source code was correct. The
parsed SXML was correct. The final HTML was not correct.
&lt;/p&gt;

&lt;p&gt;
Now, I'm not exactly privy to the inner workings of Haunt. I have
dabbled in its source, mostly to figure out defaults, but I haven't
really dug deep. Because of that, I only had vague ideas where to
start looking.
&lt;/p&gt;

&lt;p&gt;
Based on the documentation for &lt;a href=&quot;https://files.dthompson.us/docs/haunt/latest/Reader.html&quot;&gt;Readers&lt;/a&gt;, I knew whichever format you
use originally, Haunt will first parse it into SXML and then render
that SXML into HTML and write it onto the disk.
&lt;/p&gt;

&lt;p&gt;
My first assumption was that it must have been one of the extensions I
used. &lt;a href=&quot;https://github.com/jeffkreeftmeijer/ox-html-stable-ids.el&quot;&gt;Ox-html stable IDs&lt;/a&gt; didn't touch the parsed output in the
slightest and is so simple, that I figured it's unlikely it could be
the cause.
&lt;/p&gt;

&lt;p&gt;
I'm also using &lt;a href=&quot;https://jakob.space/&quot;&gt;Jakob L. Kreuze&lt;/a&gt;'s &lt;a href=&quot;https://git.sr.ht/~jakob/blog&quot;&gt;Org-mode reader&lt;/a&gt; for Haunt, which is
quite a bit larger and more complex. But, after adding a debug print
line and checking the output, the generated SXML was fine.
&lt;/p&gt;

&lt;p&gt;
Okay, so the extensions are fine. Then it &lt;i&gt;must be&lt;/i&gt; Haunt. By grepping
the source code for &lt;code&gt;sxml&lt;/code&gt;, I found &lt;a href=&quot;https://git.dthompson.us/haunt/tree/haunt/html.scm&quot;&gt;a file&lt;/a&gt; called &lt;code&gt;html.scm&lt;/code&gt;, that
contains the following:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-scheme&quot;&gt;(&lt;span class=&quot;org-keyword&quot;&gt;define*&lt;/span&gt; (&lt;span class=&quot;org-function-name&quot;&gt;sxml-&amp;gt;html&lt;/span&gt; tree &lt;span class=&quot;org-builtin&quot;&gt;#:optional&lt;/span&gt; (port (current-output-port)))
&lt;span class=&quot;org-string&quot;&gt;&amp;quot;Write the serialized HTML form of @var{tree} to @var{port}.&amp;quot;&lt;/span&gt;
(&lt;span class=&quot;org-keyword&quot;&gt;define&lt;/span&gt; (&lt;span class=&quot;org-function-name&quot;&gt;write-escaped-string&lt;/span&gt; str)
  (&lt;span class=&quot;org-keyword&quot;&gt;define&lt;/span&gt; (&lt;span class=&quot;org-function-name&quot;&gt;escape&lt;/span&gt; ch)
    (&lt;span class=&quot;org-keyword&quot;&gt;case&lt;/span&gt; ch
      ((#\&amp;quot;) (put-string port &lt;span class=&quot;org-string&quot;&gt;&amp;quot;&amp;amp;quot;&amp;quot;&lt;/span&gt;))
      ((#\&amp;amp;) (put-string port &lt;span class=&quot;org-string&quot;&gt;&amp;quot;&amp;amp;amp;&amp;quot;&lt;/span&gt;))
      ((#\&amp;lt;) (put-string port &lt;span class=&quot;org-string&quot;&gt;&amp;quot;&amp;amp;lt;&amp;quot;&lt;/span&gt;))
      ((#\&amp;gt;) (put-string port &lt;span class=&quot;org-string&quot;&gt;&amp;quot;&amp;amp;gt;&amp;quot;&lt;/span&gt;))
      (&lt;span class=&quot;org-keyword&quot;&gt;else&lt;/span&gt; (put-char port ch))))
  (string-for-each escape str))

&lt;span class=&quot;org-comment-delimiter&quot;&gt;;; &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;&amp;lt;... The rest elided because it's not relevant ...&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Well, that was easy, right? The code just isn't escaping &amp;quot;€&amp;quot; right.
Except, no, that explanation doesn't track in the slightest due to the
&lt;code&gt;else&lt;/code&gt; clause of that function. Haunt only escapes these four special
cases, everything else is passed through as-is.
&lt;/p&gt;

&lt;p&gt;
At this point I figured, well, I might as well try out this function.
I fired up &lt;a href=&quot;https://elpa.nongnu.org/nongnu/geiser.html&quot;&gt;Geiser&lt;/a&gt;, &lt;a href=&quot;https://www.gnu.org/software/emacs/&quot;&gt;Emacs&lt;/a&gt;'s integrated Scheme REPL. Because I'm also
using &lt;a href=&quot;https://github.com/direnv/direnv&quot;&gt;direnv&lt;/a&gt; and its accompanying extension for Emacs &lt;a href=&quot;https://github.com/wbolster/emacs-direnv&quot;&gt;emacs-direnv&lt;/a&gt;, it
automatically picked up the right version of &lt;a href=&quot;https://www.gnu.org/software/guile/&quot;&gt;Guile&lt;/a&gt; and also the right
environment variables for me to be able to import Haunt's modules from
the &lt;code&gt;.envrc&lt;/code&gt; file found in the root of my site repository.
&lt;/p&gt;

&lt;p&gt;
As my first attempt, I called the function with a very simple SXML tree:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-geiser&quot;&gt;scheme@(guile-user)&amp;gt; (&lt;span class=&quot;org-keyword&quot;&gt;use-modules&lt;/span&gt; (haunt html))
scheme@(guile-user)&amp;gt; (sxml-&amp;gt;html-string '(html &lt;span class=&quot;org-string&quot;&gt;&amp;quot;€&amp;quot;&lt;/span&gt;))
$6 = &lt;span class=&quot;org-string&quot;&gt;&amp;quot;&amp;lt;html&amp;gt;\ufffd\ufffd\ufffd&amp;lt;/html&amp;gt;&amp;quot;&lt;/span&gt;
scheme@(guile-user)&amp;gt; 
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
It sure seems like &lt;code&gt;sxml-&amp;gt;html&lt;/code&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.1&quot; href=&quot;#fn.1&quot; class=&quot;footref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; is the problem, huh? Let's
simplify it even further:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-geiser&quot;&gt;scheme@(guile-user)&amp;gt; (sxml-&amp;gt;html-string &lt;span class=&quot;org-string&quot;&gt;&amp;quot;€&amp;quot;&lt;/span&gt;)
$7 = &lt;span class=&quot;org-string&quot;&gt;&amp;quot;\ufffd\ufffd\ufffd&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
With a sudden stroke of inspiration, I figured what if we drop &lt;code&gt;sxml-&amp;gt;html&lt;/code&gt;?
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-geiser&quot;&gt;scheme@(guile-user)&amp;gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;€&amp;quot;&lt;/span&gt;
$8 = &lt;span class=&quot;org-string&quot;&gt;&amp;quot;\ufffd\ufffd\ufffd&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;i&gt;A-ha!&lt;/i&gt; So the problem is in-fact Guile, not Haunt. On one hand, this
was good, because I could rule out an entire project. On the other
hand, now I fell into the even bigger bog of figuring what Guile was
doing wrong.
&lt;/p&gt;

&lt;p&gt;
One thing that struck me as odd is that not only were the files
written to the disk off, I also noticed that the little arrows Haunt
usually prints when copying files were also replaced by question marks.
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 1: &lt;/span&gt;An example of the little arrow.&lt;/label&gt;&lt;pre class=&quot;src src-bash&quot;&gt;copy assets/imgs/2026-03-10-lasik/drops.avif → /assets/imgs/2026-03-10-lasik/drops.avif
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
This already made me have a hunch, but the Unicode code-point &lt;code&gt;\ufffd&lt;/code&gt;
was also a glowing hint what was going. This character is called the
&lt;a href=&quot;https://en.wikipedia.org/wiki/Specials_(Unicode_block)#:~:text=5%5D-,Replacement%20character&quot;&gt;Replacement Character&lt;/a&gt;, because it is what UTF-8 rendering software
show when the character-code cannot be properly decoded.
&lt;/p&gt;

&lt;p&gt;
Okay, so not even the terminal is able to encode things. Since I'm
using &lt;a href=&quot;https://alacritty.org/&quot;&gt;Alacritty&lt;/a&gt; at the moment, which is a very modern terminal
emulator, it not supporting something as basic as a Euro-sign was
impossible.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-the-solution&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;the-solution&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;2.&lt;/span&gt; The solution&lt;/h2&gt;
&lt;div id=&quot;text-2&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
And that was when I finally realized the issue: &lt;b&gt;My locale was
busted.&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;
If you're using a mainstream distro (and haven't done anything overly
fancy), you might not even know what a &lt;a href=&quot;https://wiki.archlinux.org/title/Locale&quot;&gt;Locale&lt;/a&gt; is. In a nutshell, it's
a set of rules that defines how locale-aware applications format
things that differ on a language-by-language basis. Examples include
dates (YYYY-MM-DD or DD-MM-YYYY?), fractions (1.5 or 1,5?), money,
etc.
&lt;/p&gt;

&lt;p&gt;
All of these rules are stored in locale files, which usually come
pre-generated with your distro. However, if you happen to be using
Arch (or one of its derivatives), you have to manually specify which
locales you intend to use and then generate them, before they become
usable.
&lt;/p&gt;

&lt;p&gt;
If you forget to do this (like I have), your system will fall back to
the &amp;quot;C/POSIX Locale&amp;quot;. Here's a great quote I found on Stack Exchange
to explain what it does (emphasis mine):
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;&lt;a href=&quot;https://unix.stackexchange.com/questions/87745/what-does-lc-all-c-do&quot;&gt;Stéphane Chazelas&lt;/a&gt;:&lt;/b&gt; The C locale is a special locale that is meant to
be the simplest locale. You could also say that while the other
locales are for humans, the C locale is for computers.
&lt;/p&gt;

&lt;p&gt;
In the C locale, characters are single bytes, &lt;b&gt;the charset is ASCII&lt;/b&gt;
(well, is not required to, but in practice will be in the systems most
of us will ever get to use), […] &lt;b&gt;and things like currency symbols
are not defined.&lt;/b&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
This explained everything. Now I only had to figure out how to fix it.
&lt;/p&gt;

&lt;p&gt;
What followed was a long (and ultimately pointless) goose-chase, where
I first checked how &lt;a href=&quot;https://www.gnu.org/software/guile/manual/html_node/Locales.html&quot;&gt;Guile handles locales&lt;/a&gt;, then &lt;a href=&quot;https://forum.systemcrafters.net/t/gnu-guile-warning-failed-to-install-locale/205&quot;&gt;how to make sure&lt;/a&gt; the
locale is actually loaded, making sure my terminal was set to (what I
thought was) the right &lt;code&gt;LANG&lt;/code&gt; value, etc.
&lt;/p&gt;

&lt;p&gt;
Nothing seemed to help, when I realized I haven't checked the most
obvious thing: What language/region is set in KDE. I opened the
settings, went to the right category, and I found it's set to
&lt;i&gt;British&lt;/i&gt; English, or &lt;code&gt;en_GB.UTF-8&lt;/code&gt;.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.2&quot; href=&quot;#fn.2&quot; class=&quot;footref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
I did indeed want my PC to be using an English locale, but I only had
&lt;code&gt;en_US.UTF-8&lt;/code&gt; and &lt;code&gt;hu_HU.UTF-8&lt;/code&gt; (my native language) enabled in my
&lt;code&gt;locale.gen&lt;/code&gt; file. Somehow, I completely failed to enable
&lt;code&gt;en_GB.UTF-8&lt;/code&gt;!
&lt;/p&gt;

&lt;p&gt;
With some shaking of my head, I changed my locale to &lt;code&gt;en_US.UTF-8&lt;/code&gt;,
rebooted my PC, regenerated my site, and guess what, everything was
working nicely.
&lt;/p&gt;

&lt;p&gt;
Moral of the story? If your applications are behaving in silly ways
when it comes to Unicode, your first assumption should probably not be
that they're faulty, but that there is probably a misconfiguration
somewhere along the chain.
&lt;/p&gt;

&lt;p&gt;
I also must emphasise the boon that is FOSS again. Had Haunt and Guile
been closed source programs, I probably would have had a far rougher
adventure figuring out what's set badly… or I would have had to
abandon using them in the first place.
&lt;/p&gt;

&lt;p&gt;
Thanks for reading!
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;h2 class=&quot;footnotes&quot;&gt;Footnotes: &lt;/h2&gt;
&lt;div id=&quot;text-footnotes&quot;&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.1&quot; href=&quot;#fnr.1&quot; class=&quot;footnum&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
&lt;code&gt;sxml-&amp;gt;html-string&lt;/code&gt; is basically just a wrapper around
&lt;code&gt;sxml-&amp;gt;html&lt;/code&gt;, whose only purpose is to tie the output port to
&lt;code&gt;stdout&lt;/code&gt;.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.2&quot; href=&quot;#fnr.2&quot; class=&quot;footnum&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
I am genuinely unsure how this blunder slipped through. I'm
fairly sure I set the right language in the installer (I'm using
&lt;a href=&quot;https://wiki.cachyos.org/&quot;&gt;CachyOS&lt;/a&gt;, which comes with a graphical installer), but surely it's not
the machine that made a mistake.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;/div&gt;</content></entry><entry><title>I got LASIK Surgery</title><id>https://nemin.hu/lasik.html</id><author><name>Nemin</name></author><updated>2026-03-13T14:27:00Z</updated><link href="https://nemin.hu/lasik.html" rel="alternate" /><content type="html">&lt;div role=&quot;doc-toc&quot; id=&quot;table-of-contents&quot;&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;div role=&quot;doc-toc&quot; id=&quot;text-table-of-contents&quot;&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#the-issue-that-broke-the-camel-s-back&quot;&gt;1. The issue that broke the camel's back&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#the-breakthrough&quot;&gt;2. The breakthrough&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#pre-op-tests&quot;&gt;3. Pre-op tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#the-day-of-the-operation&quot;&gt;4. The day of the operation&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#the-moments-before-the-operation&quot;&gt;4.1. The moments before the operation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#the-operation&quot;&gt;4.2. The operation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#the-moments-after-the-operation&quot;&gt;4.3. The moments after the operation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#at-home&quot;&gt;4.4. At home&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#the-day-after&quot;&gt;5. The day after&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#checkup&quot;&gt;5.1. Checkup&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#day-3&quot;&gt;6. Day 3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#day-4&quot;&gt;7. Day 4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#day-5-and-conclusion&quot;&gt;8. Day 5 and Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
I didn't exactly win the genetic lottery when it comes to vision. Both
my parents wear thick glasses, my late grandparents were short-sighted
too, and a lot of my relatives don't see super well either.
&lt;/p&gt;

&lt;p&gt;
So it wasn't too much of a surprise to anyone, when I started watching
TV from maybe a meter from the screen, that I most likely will need
glasses too. I don't exactly remember my original prescription, but
based the fact that I had to sit this close to see clearly means it
probably was several dioptres from the get go.
&lt;/p&gt;

&lt;p&gt;
Over the years my issues compounded. My myopia got worse, I was also
diagnosed with astigmatism, and eventually also with &lt;i&gt;strabismus
divergens.&lt;/i&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.1&quot; href=&quot;#fn.1&quot; class=&quot;footref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; All of these were things glasses could mitigate and,
for around 18 years of my life, that was fine by me.
&lt;/p&gt;

&lt;p&gt;
However, by the time I was in my mid-twenties my eyesight worsened to
-7.5 / -6.75 dioptres, with -0.75 cylinders on each eye. In practical
terms, this meant that I was able to see clearly about 20 centimetres
in front of me, after which the world quickly blurred into vague
shapes and difficult to discern blobs. My glasses too grew thicker and
thicker, my last pair having lenses that were easily almost a
centimetre thick.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.2&quot; href=&quot;#fn.2&quot; class=&quot;footref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
Even this would've been fine, if not for…
&lt;/p&gt;
&lt;div id=&quot;outline-container-the-issue-that-broke-the-camel-s-back&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;the-issue-that-broke-the-camel-s-back&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;1.&lt;/span&gt; The issue that broke the camel's back&lt;/h2&gt;
&lt;div id=&quot;text-1&quot; class=&quot;outline-text-2&quot;&gt;

&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-03-10-lasik/chromatic_aberration.avif&quot; alt=&quot;chromatic_aberration.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 1: &lt;/span&gt;Simulation of what I've seen. This effect is a type of &lt;a href=&quot;https://en.wikipedia.org/wiki/Chromatic_aberration&quot;&gt;Chromatic aberration&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
I'm a frontend developer by trade. Not exactly a graphics designer,
mind you, I still pretty much just bash rocks together until they look
&lt;i&gt;nice&lt;/i&gt; enough, but visuals are still part of my job.
&lt;/p&gt;

&lt;p&gt;
Well imagine my shock when, during some project I'm not even wholly
remembering anymore, I noticed that all the red and blue text on my
site were a tiny bit misaligned. Not horribly so, a millimetre or two
at most, but enough to bother me. What's worse, no matter how much I
scoured my code, I couldn't find a reason for this discrepancy.
&lt;/p&gt;

&lt;p&gt;
While different fonts can wildly differ with the same settings due to
their inherent line-heights and such, the same font randomly floating
higher or lower due to a colour difference sounded plain silly.
&lt;/p&gt;

&lt;p&gt;
So silly in fact, that I started to see if I could find any other
places where this happened. It happened on &lt;i&gt;every&lt;/i&gt; other site I
checked.
&lt;/p&gt;

&lt;p&gt;
And, what's worse, nobody else I asked could see it!
&lt;/p&gt;

&lt;p&gt;
I realized with some horror, that this was not an issue with CSS, nor
even with my PC. &lt;b&gt;The problem was with my vision.&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;
I was a little devastated and a bit scared too. Bad eyesight is one
thing. Sure, stuff is blurry and hard to make out, but reality is
still reality. But having things move in impossible ways? That's a
whole another ballpark and one I was not happy to be a member of.
&lt;/p&gt;

&lt;p&gt;
And sure, we're talking about millimetres, but that's millimetres too
much. I mentioned this to my local optometrist, but they couldn't
really figure out anything, largely due to their lack of tools. I was
running out of ideas when I noticed that if I take my glasses off, I
could no longer see this issue.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.3&quot; href=&quot;#fn.3&quot; class=&quot;footref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
My feelings were twofold: On one hand, massive relief. &amp;quot;Thank
goodness, it's not my eyes.&amp;quot; On the other hand, I also felt a sense of
disappointment and worry. Glasses were no longer wholly-reliable
tools. From now on, they'd be their own potential cause of issues.
&lt;/p&gt;

&lt;p&gt;
There were, of course, a couple of options to resolve this problem,
such as:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;p&gt;
&lt;b&gt;Get glasses with thinner lenses:&lt;/b&gt; Prohibitively expensive. A set of
good, mid-range glasses cost about 400€ where I live. This means a
decent frame, moderately thinned lenses, maybe even a protective
coating. Nothing fancy, but it also won't snap off your face at the
worst moment.
&lt;/p&gt;

&lt;p&gt;
&amp;quot;Good&amp;quot; glasses in comparison can easily cost 600 to 800€.
Technically, I could afford this, having a programmer's salary helps
a lot, but it just feels a bit too much for a device you most likely
need to replace in 1-2 years anyway.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.4&quot; href=&quot;#fn.4&quot; class=&quot;footref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
&lt;b&gt;Contact lenses:&lt;/b&gt; I'll be honest, I was never too good with touching
my eyes. I hate eye-drops and I instinctively shut my eyes real
tight whenever anything gets close to them. This, along with the
fact that contacts aren't exactly the safest option long-term and
the recurring cost kept me away.
&lt;/p&gt;

&lt;p&gt;
I know normal contact lenses aren't that expensive, but because of
how, erm, 'special' my eyes were, I couldn't just get off the shelf
ones. They gave me a quote when I asked and said it'd be about 100€
/ month for my supply, with no real option to involve social
insurance.
&lt;/p&gt;

&lt;p&gt;
Considering it would've cost me nearly 1200€ a year to get a pack of
tiny disks to splat on my eyes, I was not rushing to get them.
&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
&lt;b&gt;Laser eye surgery:&lt;/b&gt; If you've read the title and description of
this post, you already know this was my ultimate choice, but it
wasn't one I've made easily.
&lt;/p&gt;

&lt;p&gt;
This is partly my fault for not informing myself better earlier,
partly my environment's, but I used to believe LASIK was still a
much more primitive procedure than what they actually do today. My
only second-hand experience was my grandfather, who underwent
cataract surgery (which isn't even the same thing!), so I thought
LASIK was all about doctors hacking and slashing your eyes until
they can correct your vision.
&lt;/p&gt;

&lt;p&gt;
Also, as is the common thing with all of these options, splurging
around 2000€ isn't exactly something one does willy-nilly.
Especially when the average person here makes about 1300€
post-taxes.
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-the-breakthrough&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;the-breakthrough&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;2.&lt;/span&gt; The breakthrough&lt;/h2&gt;
&lt;div id=&quot;text-2&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
For a while, I learned to live with this issue, always being slightly
bothered by it, but never to the amount that I wanted a solution
&lt;i&gt;now.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
That is, until I was doomscrolling on YouTube one night and I saw a
video about LASIK. I'm not even sure why I got a recommendation like
that. I don't tend to watch medical videos much (well, except for good
old Dr. Bernard at &lt;a href=&quot;https://www.youtube.com/@chubbyemu&quot;&gt;ChubbyEmu&lt;/a&gt;) and especially not eyesight related
stuff, but one way or another the algorithm figured it might be
something I'd be interested in and morbid curiosity got the better of
me.
&lt;/p&gt;

&lt;p&gt;
The realisation was a little shocking, but I found out that not only
is the operation a lot less gruesome than I thought, I was also not
nearly as grossed out as I thought I'd be.
&lt;/p&gt;

&lt;p&gt;
This sent me down a path of research:
&lt;/p&gt;

&lt;p&gt;
I read a ton of posts on &lt;a href=&quot;https://www.reddit.com/r/lasik/&quot;&gt;/r/lasik&lt;/a&gt;
and (beyond a couple of bad cases) people were raving about the
life-changing effect this operation had on them. I read a couple of
medical journal summaries,&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.5&quot; href=&quot;#fn.5&quot; class=&quot;footref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt; watched videos of doctors talking
about the procedure and read their comment sections. I also talked
with some friends and colleagues who opted to do LASIK and they all
sounded very enthused.
&lt;/p&gt;

&lt;p&gt;
I also visited the &amp;quot;other side&amp;quot; to get a better picture, so I went to
&lt;a href=&quot;https://www.reddit.com/r/Lasiksupport/&quot;&gt;/r/lasiksupport&lt;/a&gt;, which is
a subreddit dedicated to people who had problems with their eyes after
undergoing the procedure. I admit, much of what I read there was
daunting, but the sub also had an air of fanaticism around it, with
people calling the surgery &amp;quot;mutilating your most delicate organ&amp;quot; and
the doctors who do such procedures &amp;quot;butchers&amp;quot;.
&lt;/p&gt;

&lt;p&gt;
It's not that I want to dismiss anyone who had bad experiences. I'm
sure many of the people writing there are genuinely distraught. My
heart goes out to them and I hope their problems can eventually be
fixed. I also don't believe LASIK is 100% safe, there is no such thing
as surgery without possible complications.
&lt;/p&gt;

&lt;p&gt;
However, what I read everywhere else simply did not add up with what I
read on the sub. If LASIK truly was this horrifically dangerous and
unpredictable thing, then surely it wouldn't have FDA or EU approval.
I also read that there are a lot of lawyers and grifters that blew the
rare unfortunate cases out of proportion and turned what started as
healthy scepticism into straight up &lt;a href=&quot;https://en.wikipedia.org/wiki/Fear,_uncertainty,_and_doubt&quot;&gt;FUD&lt;/a&gt; for profits' sake, so I
figured I can't really get an unbiased view of the potential dangers
from non-professionals.
&lt;/p&gt;

&lt;p&gt;
After a week or so of reading/watching/discussing the topic, I felt
convinced enough to want to give this whole thing a shot. I started
looking up prices and ultimately settled on a &lt;a href=&quot;https://sasszemklinika.hu/&quot;&gt;local clinic&lt;/a&gt; that seemed
both &amp;quot;affordable&amp;quot;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.6&quot; href=&quot;#fn.6&quot; class=&quot;footref&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; and quite reputable, as they claim to have
completed 150 thousand successful operations and whom had a lot of
positive reviews on Google Maps.
&lt;/p&gt;

&lt;p&gt;
They also had a promotion that promised to forego the price of post-op
consultation and even provide you with a reasonably priced set of
sunglasses (a must, as UV rays can revert the improvement for the
first three-six months), so I felt like it's now or never.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.7&quot; href=&quot;#fn.7&quot; class=&quot;footref&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-pre-op-tests&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;pre-op-tests&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;3.&lt;/span&gt; Pre-op tests&lt;/h2&gt;
&lt;div id=&quot;text-3&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
I registered on the clinic's site and the next day I got a call back
from them. I dictated in my dioptres and cylinder values, that I don't
suffer from high blood pressure or diabetes, and that I'd like an
appointment as soon as possible. They offered one two weeks from the
time of the call. This matched what their website claimed (they
explain due to the popularity of the clinic, it's unlikely that you
can get an appointment with less than two weeks wait time), so I
happily accepted.
&lt;/p&gt;

&lt;p&gt;
As the nurses and the doctor explained, due to my severely bad
eyesight, I had to undergo a &amp;quot;pre-candidacy&amp;quot; test. Meaning they needed
to see whether it's even theoretically possible for me to undergo this
surgery, so that nobody's time is wasted with the more involved tests
in case it turns out my eyes aren't compatible.
&lt;/p&gt;

&lt;p&gt;
Thankfully I passed this with flying colours. Hell, the doctor even
remarked that my eyes are unusually thick. This is not really relevant
in most areas of life, but for LASIK it's a huge benefit, because
people with too thin corneas can only do a subset (or even none) of
the surgeries.
&lt;/p&gt;

&lt;p&gt;
There was, however, one issue and a big one at that. The doctor said
that while LASIK can treat myopia and astigmatism quite well, it is
completely ineffective when it comes to strabismus. She explained that
while my glasses seemingly &amp;quot;fix&amp;quot; the condition, I still have what's
called &amp;quot;hidden strabismus&amp;quot;.
&lt;/p&gt;

&lt;p&gt;
What this means is that even if it's not visible to the untrained eye,
my eyes still slightly point in different directions and that, even if
they make my other problems go away, it's simply caused by a different
issue (a weakness of the eye muscles). She also said, because I wasn't
treated for it when I was little,&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.8&quot; href=&quot;#fn.8&quot; class=&quot;footref&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; she cannot guarantee that I
won't start seeing double once my glasses are off. Which is not only
annoying, but also a source of migraine-like headaches as the brain is
unable to properly comprehend the signals the eyes are sending to it.
&lt;/p&gt;

&lt;p&gt;
Before I could go on to the actual in-depth eligibility test, I was
asked to seek out a specialist that could inform me better about my
chances and what to do in case my strabismus becomes permanent. I was
lucky to be able to book an appointment for the very same day through
my company's health insurance provider, so I didn't even have to pay
for the examination.
&lt;/p&gt;

&lt;p&gt;
The optometrist informed me that it is her belief, that since I can
see fine with glasses, my strabismus issues should also be minimized
if I undergo LASIK.
&lt;/p&gt;

&lt;p&gt;
She also told me I'm lacking depth perception. This took me by some
surprise, because it never really caused any issues and I always just
assumed I had it, but apparently I don't. I asked whether this would
affect me in any shape or form, she said if it didn't bother me so
far, the LASIK won't affect it one way or another.
&lt;/p&gt;

&lt;p&gt;
Having my worries assuaged, I applied to the second set of tests and
got another appointment for next week.
&lt;/p&gt;

&lt;p&gt;
The days ticked by in anticipation. While my initial results were
promising, they were not enough to decisively say if I'm eligible or
not. I had neither the resigned relief of knowing I stand no chance,
nor the joy of knowing for sure that I am in fact able to partake.
&lt;/p&gt;

&lt;p&gt;
Finally the day came and I went to the clinic to be subjected to a
regimen of tests, some familiar, some completely new. They measured my
glasses, my eyesight, the pressure in my eyes,&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.9&quot; href=&quot;#fn.9&quot; class=&quot;footref&quot;&gt;9&lt;/a&gt;&lt;/sup&gt; and did some
fancy 3D scans using machines that looked straight out of some tacky
sci-fi show.
&lt;/p&gt;

&lt;p&gt;
After all this was done, they applied pupil-dilating eye drops and
re-did all of the tests as before. I must say, I never expected these
drops to be this bad. I quite literally felt like the doctor splashed
acid in my eyes. It stung and burned and I had to clutch the hem of my
shirt to maintain my composure.
&lt;/p&gt;

&lt;p&gt;
But at least it was done fast. And the result? Based on my
measurements alone, I was in fact eligible to &lt;i&gt;all&lt;/i&gt; potential eye
surgeries: &lt;a href=&quot;https://en.wikipedia.org/wiki/Photorefractive_keratectomy&quot;&gt;PRK&lt;/a&gt;, FEMTO-&lt;a href=&quot;https://en.wikipedia.org/wiki/LASIK&quot;&gt;LASIK&lt;/a&gt;, and &lt;a href=&quot;https://en.wikipedia.org/wiki/Small_incision_lenticule_extraction&quot;&gt;SMILE&lt;/a&gt;!
&lt;/p&gt;

&lt;p&gt;
However, before I could get any choice paralysis, the doctor quickly
ruled out PRK, saying with dioptres this high, it is unlikely that PRK
could give me 100% eyesight.
&lt;/p&gt;

&lt;p&gt;
We then also ruled out SMILE. Though it is technically more modern
than FEMTO-LASIK, I was told that those who undergo SMILE treatment
can no longer have corrective surgery in the future if their eyesight
regresses and the procedure doesn't really provide anything over
normal LASIK in my case.
&lt;/p&gt;

&lt;p&gt;
While I do hope I won't need to ever get my eyes operated again, I
don't want to risk going back to glasses in case the worst comes to
pass, so I chose FEMTO-LASIK.
&lt;/p&gt;

&lt;p&gt;
The doctor suggested clinic's robot-assisted version, which works by
pretty much &amp;quot;sliding onto your eyes&amp;quot;, compared to the traditional
version, where a vacuum-ring is used to keep the eye taut. This
variant is slightly more expensive, but comes with two great benefits:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;One, your eyes won't get bruised, which is a common side-effect of
the vacuum-rings. This is both quite ugly and I've read some
previous reports where people claimed it caused them discomfort both
during and after the surgery.&lt;/li&gt;
&lt;li&gt;Two, as I mentioned earlier, my eyes instinctively shut really hard
when I feel something approaching them. This can potentially
dislodge the vacuum-ring, which can cause side-effects or the
failure of the surgery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
With the plan worked out, I got my final appointment for the surgery
set to a week from my visit. I was ordered to get a special kind of
eye drops that promote tear generation and start using it three times
a day for three days before the operation to make sure my eyes are
lubricated enough to work on safely.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-the-day-of-the-operation&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;the-day-of-the-operation&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;4.&lt;/span&gt; The day of the operation&lt;/h2&gt;
&lt;div id=&quot;text-4&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
I arrived early in the morning a bit nervous, but just as excited. My
paperwork was taken, I had to read a legal disclaimer, where the
clinic informed me of all the wonderful ways things could go wrong. It
wasn't exactly a pleasant list and I do find it a bit grim, that you
only get this paper when you're 95% of the way there already.
&lt;/p&gt;

&lt;p&gt;
Obviously, it's done so that most people will say &amp;quot;screw it&amp;quot; and
proceed, but if I were making the rules, I'd force companies to put
this disclaimer front and centre. Informed consent should involve
enough time and a neutral environment to make your choice.
&lt;/p&gt;

&lt;p&gt;
Four signatures later, I was taken for another set of tests. The nurse
explained that they work only with fresh measurements in case
something happened between my last visit and now.
&lt;/p&gt;

&lt;p&gt;
As iffy as I found the waiver above, in this aspect I'm very glad
things work this way. While it might seem a bit tedious to have your
tests taken three times, in my opinion it's better to do it more times
and end up not using some of the measurements, than doing it less and
ending up relying on out of date stuff.
&lt;/p&gt;

&lt;p&gt;
This surgery is meant to last a lifetime (or at the very least quite a
few years), so that half hour spent to make sure things are still
valid and correct is well-worth it.
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-03-10-lasik/glasses.avif&quot; alt=&quot;glasses.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 2: &lt;/span&gt;The glasses I chose.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
I was asked to visit the clinic's glasses shop and select a pair of
sunglasses for myself. The selection was a bit mediocre, I must admit.
Most of the glasses looked plain silly and of the few that actually
looked my style were almost all too small to cover my eyes adequately.
Ultimately, I settled on a simple, black, medium-thick set of glasses.
I'm not head over heels about it, but it's fine.
&lt;/p&gt;

&lt;p&gt;
Next I was given a small Xanax pill, which helps both lower anxiety
and also relaxes the eye muscles. I tend to shake my legs when I'm
nervous and I was surprised to notice that, after at most five
minutes, I calmed down enough to have my legs stop shaking. Soon they
said it's time and I headed to the final waiting room, right outside
the surgical theatre.
&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-the-moments-before-the-operation&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;the-moments-before-the-operation&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;4.1.&lt;/span&gt; The moments before the operation&lt;/h3&gt;
&lt;div id=&quot;text-4-1&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
The nurse there gave me some plastic bags to cover my shoes, requested
that I deposit all my belongings in a nearby box, put a surgical gown
and hair cover on me, and then finally I was asked to sit in a large
chair. There she administered numbing drops, then artificial tears,
then washed the area with iodine solution to disinfect it. The numbing
drops were horrible. They stung quite a bit (though thankfully not
nearly as much as the pupil-dilating ones did) and I felt a sensation
that I can only describe as my eyelids &amp;quot;wrinkling up&amp;quot;. I'm sure no
such thing actually happened, but that's just how it felt like.
&lt;/p&gt;

&lt;p&gt;
Afterwards, I had to wait around 15 minutes for the surgeon to arrive.
When that happened, I was given one final set of numbing drops and was
ushered in to the surgery room.
&lt;/p&gt;

&lt;p&gt;
I'd love to detail what I saw, but the reality of the situation is
that I didn't see too much. The room itself was kept very dim, I was a
little out of it from the Xanax, and I also wasn't seeing too well due
to no longer having my glasses on and because of the numbing drops.
&lt;/p&gt;

&lt;p&gt;
All I know is that I had to lay down on a big machine, surrounded by a
bunch of people, then a nearby assistant helped prop my legs up with a
triangular pillow.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-the-operation&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;the-operation&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;4.2.&lt;/span&gt; The operation&lt;/h3&gt;
&lt;div id=&quot;text-4-2&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
Once I was settled on the bed, I was ordered to close my eyes and I
heard something sliding in front of me. Someone taped down my right
eye and I was told to stare intensely into the light above me. It was
a small green light very out of focus. The machine slid closer and
suddenly the light became very clear. A moment later I heard a screech
of sorts and it looked like someone flashed a camera in my eye.
&lt;/p&gt;

&lt;p&gt;
This was only an illusion. What really happened was the creation of
the so-called &amp;quot;flap&amp;quot;. An extremely fast laser activated and carved a
small &amp;quot;lid&amp;quot; into my cornea. This &amp;quot;lid&amp;quot; is then used by the surgeon to
protect the area underneath after it was operated on.
&lt;/p&gt;

&lt;p&gt;
I was told to close my eyes, the tape was removed and then placed on
my other eye and the process was repeated. Another &amp;quot;camera flash&amp;quot;
later, I was told to close my eyes again and I was slid under another
machine. One of the nurses placed a kind of &amp;quot;bag&amp;quot; over my head, which
concealed my entire face, except for the single eye that was currently
operated on. I figure it was to protect my head so that the laser
could not affect anything but where it was intended to go.
&lt;/p&gt;

&lt;p&gt;
I was asked to open my eye again and stare into another green light.
What I saw was very unusual and my words fail to capture it correctly.
&lt;/p&gt;

&lt;p&gt;
It was as if the lamp was under foamy water, which stirred and
bubbled. In reality this was the laser ablating my cornea and
reshaping it to be able to focus correctly. I felt a tiny bit of
pressure, but no pain. Honestly, it was kind of mesmerizing and,
despite all my initial worries, I was not afraid in the slightest. I
also smelled hints of burning hair, which is not surprising
considering our nails, hair, and corneas are made up of the same
material.
&lt;/p&gt;

&lt;p&gt;
The only negative part was the surgeon. She was very condescending and
a bit rude, and she kept yanking my head around. I get that it is
probably very stressful to operate on someone, but I am at the end of
the day a paying customer, who is dazed and restrained, there is
little point in using any kind of coercion when I was trying to
cooperate already.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-the-moments-after-the-operation&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;the-moments-after-the-operation&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;4.3.&lt;/span&gt; The moments after the operation&lt;/h3&gt;
&lt;div id=&quot;text-4-3&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
A few seconds later my other eye was also subjected to the procedure
and we were done. Somebody grabbed my hand and guided me out to the
waiting room, where I was sat in another chair.
&lt;/p&gt;

&lt;p&gt;
The same nurse who gave me the drops appeared and removed my hair
cover before giving me a glass of water, which I gladly gulped down. I
had to spend around 45 minutes in that chair afterwards, giving myself
eye-drops every fifteen minutes.
&lt;/p&gt;

&lt;p&gt;
It must have looked very funny from the outside, but I kept staring at
my hands. While my vision was still quite a bit blurry, I was shocked
at how good I could see already. I was seeing details on my hands and
in the room I couldn't with my glasses when I arrived.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.10&quot; href=&quot;#fn.10&quot; class=&quot;footref&quot;&gt;10&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
One thing I didn't anticipate, but ended up being quite annoying is
that my nose started running real bad. I am not one to get too snotty
usually, but after the operation I'm pretty sure I went through a pack
of tissues in the first ten minutes. The nurse reassured me that this
is perfectly normal and a common side effect of the eye (and thus the
tear ducts) being messed with.
&lt;/p&gt;

&lt;p&gt;
Aside from the nurse and me, there was also another patient in the
room, who went in (and thus came out) right after me. We chatted a
little to pass the time, he seemed just as elated as I was and
mentioned not feeling anything at all. Lucky guy.
&lt;/p&gt;

&lt;p&gt;
After the 45 minutes were up I was led into a side room, where a
doctor checked me quickly. This was done with a very bright lamp,
which was quite uncomfortable, but thankfully also very brief. I was
told everything looks alright and that was that.
&lt;/p&gt;

&lt;p&gt;
Once this was all done, I was allowed to go out to the reception. I
was given a couple of instructions and a prescription, and I was asked
to pay. Once we were done, I relinquished my glasses,&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.11&quot; href=&quot;#fn.11&quot; class=&quot;footref&quot;&gt;11&lt;/a&gt;&lt;/sup&gt; and with
the help of my partner, made my way home.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-at-home&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;at-home&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;4.4.&lt;/span&gt; At home&lt;/h3&gt;
&lt;div id=&quot;text-4-4&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
It was a little rough. I had to use artificial tears every fifteen
minutes and also every hour anti-inflammatory steroid drops. Oddly
enough my left eye was completely fine from the get go. It didn't hurt
or itch or anything. My right one, however, was another story. There
were three different kinds of sensations I felt. First it was the kind
of itch you feel when your eyes are irritated by onions. Then it was
as if an eyelash got into my eye. Then finally that too dissipated and
I felt as if there was sand or grit in my eye.
&lt;/p&gt;

&lt;p&gt;
I was told that my eyes would be vulnerable to bright lights for the
first day, so I spent most of the rest of the day in a darkened room
in my sunglasses, which made things a lot more bearable. Beyond the
discomfort of the drops and the slight pain, I was remarkably fine. By
the early afternoon I was even able to start using my PC,&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.12&quot; href=&quot;#fn.12&quot; class=&quot;footref&quot;&gt;12&lt;/a&gt;&lt;/sup&gt;
though my vision tended to lose and gain focus randomly.
&lt;/p&gt;

&lt;p&gt;
As the optometrist said my strabismus pretty much resolved itself.
While this sadly doesn't mean it's completely gone (once my eyes got
tired, they started diverging a little again), I didn't really have
any double vision, nor headache.
&lt;/p&gt;

&lt;p&gt;
The rest of the day went by fairly uneventfully. The last &amp;quot;exciting&amp;quot;
thing was sleeping. I have been a stomach-sleeper all my life. Whether
it's a physical or mental thing I'm not sure, but I was never able to
fall asleep in any other pose. The problem? The clinic ordered me to
sleep on my back and without a pillow.
&lt;/p&gt;

&lt;p&gt;
Yet, despite all my worries, it didn't take me &lt;i&gt;that&lt;/i&gt; long to fall
asleep. I figure I was pretty exhausted from all that happened. As a
precaution, I kept my sunglasses on while I slept. I figured I'd
rather break it and pay another 100 or so€, than risk dislodging my
flap and need another surgery.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-the-day-after&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;the-day-after&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;5.&lt;/span&gt; The day after&lt;/h2&gt;
&lt;div id=&quot;text-5&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
I woke fairly rested and, luckily, with my glasses and eyes intact.
There was a bit of itchiness in my right eye, which went away after I
used my artificial tears.
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-03-10-lasik/drops.avif&quot; alt=&quot;drops.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 3: &lt;/span&gt;My eye drops. Revihyal is artificial tears (with extra stuff in it to promote tear generation), while Tobradex is a steroidal anti-inflammation drops.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
From this day forwards, I only have to use my steroid eye drops five
times a day (with two hours in between each drop) and the artificial
tears either hourly or whenever I feel like my eyes are dry.
&lt;/p&gt;

&lt;p&gt;
It's really weird (though well-documented), but I experienced an
almost complete reversal of how my eyes used to work. Whereas before I
had trouble seeing farther than 20cm, now I am able to see quite far
(several houses away) with abject clarity. At the same time, close
things are slightly blurry and hazy.
&lt;/p&gt;

&lt;p&gt;
It's evident that I still need a lot of time to heal before things can
normalise. I also see a lot of halos around lights. It's not super
bothersome, but I hope they'll too disappear in time.
&lt;/p&gt;

&lt;p&gt;
My strabismus is also still almost completely gone, so I am fairly
hopeful it will stay that way.
&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-checkup&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;checkup&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;5.1.&lt;/span&gt; Checkup&lt;/h3&gt;
&lt;div id=&quot;text-5-1&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
I had my first checkup this day too. Apparently my flap is healing
nicely and the doctor couldn't really see any issues.
&lt;/p&gt;

&lt;p&gt;
My vision was measured too. Apparently it's currently 100% in my right
eye and 95% in my left eye. Personally, I'm not so sure. I felt like
the doctor was rushing through the procedure and not acknowledging
when I struggled to properly read off the chart.
&lt;/p&gt;

&lt;p&gt;
I'm not too worried just yet, because there will be four checkups in
total, so I still have plenty of chances to complain or ask for
advice, and (more importantly) the clinic claims that your vision only
stabilises to its final and hopefully best state in three months. So
expecting perfection on day two is simply unreasonable.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-day-3&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;day-3&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;6.&lt;/span&gt; Day 3&lt;/h2&gt;
&lt;div id=&quot;text-6&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
On day three I finally gave &amp;quot;washing&amp;quot; my eyes a shot. What this
entailed was boiling some water in the kettle, pouring a tiny bit into
a bowl, soaking some fresh cotton pads in this water and then very
gently brushing down my eyelids with top to bottom motions.
&lt;/p&gt;

&lt;p&gt;
I am not 100% happy with the results as I was unable to get all of the
gunk off (i.e. the drops that didn't go quite where they needed) from
my eyelashes, but at the very least I was able to clean the area
around my eyes.
&lt;/p&gt;

&lt;p&gt;
It appears that my eye dryness has gotten a bit worse. I haven't
really counted, so I cannot give an objective comparison, but I feel
like I need more drops to keep my eyes lubricated. It's a bit
unfortunate, but the good news, however, is that once I do put drops
in my eyes, my vision improves even further.
&lt;/p&gt;

&lt;p&gt;
The same cannot be said when it comes to the steroid drops. They cause
quite a bit of burning and discomfort and I'm not quite sure how much
I really need them, considering I don't have inflammation at all. I
will continue taking them, because thinking you're smarter than your
doctor is a great way to endanger your life, but I really hope they'll
allow me to stop after the next checkup.
&lt;/p&gt;

&lt;p&gt;
I had a couple of close-calls where things almost got into my eyes:
Bedsheets, my shirt, hair, etc. I don't believe anything actually
caused issues, but I'd be lying if I said it's not making me just a
tad nervous.
&lt;/p&gt;

&lt;p&gt;
I believe my light-sensitivity has improved. I obviously still keep my
eyes out of direct sunlight (including keeping the curtains partially
drawn as my PC is right next to my window), but whenever I look
outside or venture out into the garden in my sunglasses, I feel a lot
less discomfort.
&lt;/p&gt;

&lt;p&gt;
My biggest issue so far is the halos I see when I look at the computer
screen. Dark modes help mitigate the worst of it, but do not entirely
solve the problems. I do see everything sharp, but everywhere where I
don't focus has a kind of distracting &amp;quot;glow&amp;quot; to it.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-day-4&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;day-4&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;7.&lt;/span&gt; Day 4&lt;/h2&gt;
&lt;div id=&quot;text-7&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
I woke early in the morning and, while taking a trip to the bathroom
on autopilot, rubbed my eyes. I immediately started worrying because
eye rubbing is one of those things they quite specifically mentioned
that one shouldn't do in the first few weeks, but luckily it seems
like I was gentle enough not to cause any issues.
&lt;/p&gt;

&lt;p&gt;
I still feel a lot of itchiness in my eyes, leading to frequent usage
of artificial tears. It's tolerable, but I'm hoping by the end of the
week it'll go away on its own.
&lt;/p&gt;

&lt;p&gt;
Funnily enough the procedure also forced me to learn to be able to
sleep on my back. The doctor said after the first day you're allowed
to once again sleep on your stomach, but I'm not taking any chances
and are trying to sleep on my back as much as I can for the first week
at least.
&lt;/p&gt;

&lt;p&gt;
Day 4 was the first day I ventured outside for several hours. I am not
quite sure how much the recovery was to blame and how much just came
down to a random bad mood, but I found myself easily irritable and
generally a bit down. I suspect the reason may have been partially due
to me not being able to use my eye drops as much as I wanted, which
led to my eyes drying out, that not only made my eyesight slightly
worse, but also exacerbated the itch and general discomfort.
&lt;/p&gt;

&lt;p&gt;
Once I was able to properly treat myself, most of these symptoms went
away. I guess moral of the story is that keeping drops on you at all
times during the early days of post-op is a very wise choice.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-day-5-and-conclusion&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;day-5-and-conclusion&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;8.&lt;/span&gt; Day 5 and Conclusion&lt;/h2&gt;
&lt;div id=&quot;text-8&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
More of the usual. Itching eyes, some halos and blur, great eyesight
at a distance. Using drops is as uncomfortable as before. As my
experience seems to be stabilizing for the time being, I'm going to
end this post here and summarize things a little.
&lt;/p&gt;

&lt;p&gt;
In a nutshell, I am very glad to have undergone this procedure. I
found it very easy to adapt to a non-glasses lifestyle, with only the
occasional idle thought in the mornings about where I could've
misplaced my glasses or reaching towards my shelf to put them on.
&lt;/p&gt;

&lt;p&gt;
If there is one thing I could've done differently, it's probably going
on a more involved eye-related diet. I'm thinking of taking stuff like
Omega-3 fatty-acid and other eye-protecting supplements.
&lt;/p&gt;

&lt;p&gt;
I also should have cut my hair before getting the operation. I have
luckily avoided getting locks in my eyes so far, but the danger is
always there.
&lt;/p&gt;

&lt;p&gt;
One more thing that might have been good to figure out before going in
was better methods at avoiding touching (near) my eyes. While I
haven't had any flap-related issues, I had several close calls with
clothing, bedsheets, me or others being clumsy, and general
sleep-related exposure. Perhaps shelling out for an eye shield
would've been wise. I'm hoping that, since nothing happened in the
most vulnerable days, I'm past the worst of it, but with some
additional planning more worries could've been avoided.
&lt;/p&gt;

&lt;p&gt;
If you're thinking of also undergoing this procedure, please be sure
to weigh your options carefully and talk with multiple doctors. Your
eyes are a delicate organ and deserve as much care and protection as
possible. I hope you learned something new and enjoyed your time with
my article.
&lt;/p&gt;

&lt;p&gt;
Thanks for reading!
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;h2 class=&quot;footnotes&quot;&gt;Footnotes: &lt;/h2&gt;
&lt;div id=&quot;text-footnotes&quot;&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.1&quot; href=&quot;#fnr.1&quot; class=&quot;footnum&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
In less medical terms: My eyes couldn't exactly agree which
direction to look, so (without my glasses to correct their behaviour)
one was staring forwards, the other was always wandering about a
little.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.2&quot; href=&quot;#fnr.2&quot; class=&quot;footnum&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Look, I know that doesn't sound &lt;i&gt;that&lt;/i&gt; much, but as you'll see
from the next section, it caused more issues than one might think.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.3&quot; href=&quot;#fnr.3&quot; class=&quot;footnum&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
It's a bit embarrassing that I didn't think of this before.
But, in my defence, neither did the optometrist and with how many
defects my eyes had otherwise, it was well within the realm of
possibility, that I could have this kind of refractive issue too.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.4&quot; href=&quot;#fnr.4&quot; class=&quot;footnum&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Yes, I know there are people who can wear their glasses for
5-10 years. I'm not really one of them, because I'm both clumsy and
have had eyes that rapidly worsened year-over-year.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.5&quot; href=&quot;#fnr.5&quot; class=&quot;footnum&quot;&gt;5&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
I know the right approach would be to read the journals
themselves, but I'm a lay person when it comes to such things and I
don't think thick medical jargon would've helped me too much.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.6&quot; href=&quot;#fnr.6&quot; class=&quot;footnum&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Again, we are talking about up to two or three months worth of
average wages here. This is not a cheap surgery. But with it costing
easily double-triple this price in other countries, I count myself
lucky to have only paid around 2500€.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.7&quot; href=&quot;#fnr.7&quot; class=&quot;footnum&quot;&gt;7&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
As it turns out I fell for the oldest trick in the book, though
I'm not really mad about it. The moment this &amp;quot;timed promotion&amp;quot; ran
out, they started another one with the very same benefits, just with a
different end date.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
I suppose the implied urgency and scarcity is not entirely ethical,
but pretty much everyone wins with it (more customers for the company,
an incentive for the consumer), so I'm not going to start clutching my
pearls.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.8&quot; href=&quot;#fnr.8&quot; class=&quot;footnum&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Apparently when you're about 6-8 years old, they can cover your
stronger eye and force the weaker one to adapt. Once you're older than
this window, you can no longer treat this condition in a non-invasive
way.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.9&quot; href=&quot;#fnr.9&quot; class=&quot;footnum&quot;&gt;9&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Also known as the horrible &amp;quot;spits air in your eyes&amp;quot; machine.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.10&quot; href=&quot;#fnr.10&quot; class=&quot;footnum&quot;&gt;10&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Full disclosure, at the time of the surgery my glasses were
already two years old and apparently almost a full dioptre weaker than
I actually needed.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.11&quot; href=&quot;#fnr.11&quot; class=&quot;footnum&quot;&gt;11&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
The clinic collects glasses from patients, who no longer need
them and gives them to those in need. I was happy to participate,
though I'm not sure there are many people who could use glasses
exactly like mine.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.12&quot; href=&quot;#fnr.12&quot; class=&quot;footnum&quot;&gt;12&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Before you get horrified at my irresponsible behaviour, this
is actually recommended by the clinic so that your eyes can quickly
start adapting to their new shape.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;/div&gt;</content></entry><entry><title>Guix System - One Month Later</title><id>https://nemin.hu/guix-one-month-later.html</id><author><name>Nemin</name></author><updated>2026-03-02T08:58:00Z</updated><link href="https://nemin.hu/guix-one-month-later.html" rel="alternate" /><content type="html">&lt;div role=&quot;doc-toc&quot; id=&quot;table-of-contents&quot;&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;div role=&quot;doc-toc&quot; id=&quot;text-table-of-contents&quot;&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#community&quot;&gt;1. Community&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#irc&quot;&gt;1.1. IRC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#mailing-lists&quot;&gt;1.2. Mailing lists&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#subreddit&quot;&gt;1.3. Subreddit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#fediverse&quot;&gt;1.4. Fediverse&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#packaging-wows-and-woes&quot;&gt;2. Packaging wows and woes&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#i-really-love-the-go-getter-attitude-of-guix&quot;&gt;2.1. I really love the go-getter attitude of Guix&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#a-side-note-about-speed&quot;&gt;2.1.1. A side-note about speed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#debugging-is-an-unpleasant-experience&quot;&gt;2.2. Debugging is an unpleasant experience&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#missing-parentheses&quot;&gt;2.2.1. Missing parentheses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#non-existent-variables&quot;&gt;2.2.2. Non-existent variables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#circular-dependencies&quot;&gt;2.2.3. Circular dependencies&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#a-cornucopia-of-styles&quot;&gt;2.3. A cornucopia of styles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#changelog-is-confusing&quot;&gt;2.4. ChangeLog is confusing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#system-ups-and-downs&quot;&gt;3. System ups and downs&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#stability-of-my-config&quot;&gt;3.1. Stability of my config&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#interfacing-with-my-phone&quot;&gt;3.2. Interfacing with my phone&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#blunderbird&quot;&gt;3.3. Blunderbird&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#nvidia-drivers-and-non-lts-kernels&quot;&gt;3.4. NVIDIA drivers and non-LTS kernels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#no-easy-way-of-seeing-what-is-updated&quot;&gt;3.5. No easy way of seeing what is updated&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#sleep-causes-crashes&quot;&gt;3.6. Sleep causes crashes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#odd-freezes-and-crashes&quot;&gt;3.7. Odd freezes and crashes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;4. Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
It's been about a month since I &lt;a href=&quot;./guix.html&quot;&gt;installed Guix System&lt;/a&gt; on my PC and now that I had some time to play around with it more seriously, I figured I'd record my additional thoughts, positive and negative, and why I ultimately chose not to stick with it.
&lt;/p&gt;
&lt;div id=&quot;outline-container-community&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;community&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;1.&lt;/span&gt; Community&lt;/h2&gt;
&lt;div id=&quot;text-1&quot; class=&quot;outline-text-2&quot;&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-irc&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;irc&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;1.1.&lt;/span&gt; IRC&lt;/h3&gt;
&lt;div id=&quot;text-1-1&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
I am happy to report, that the &lt;a href=&quot;https://logs.guix.gnu.org/guix&quot;&gt;IRC chat&lt;/a&gt; of Guix is both surprisingly active and very welcoming. I was able to join in and participate in discussions as a complete newcomer and I never once felt like people were ignoring me or valuing my opinion less due to my newness. This is excellent and a high standard for any other community to strive for.
&lt;/p&gt;

&lt;p&gt;
My general impression was that there is plenty of good-hearted banter, people helping each other figure stuff out, people discussing their configs and such. Hell, I even got a few shout outs for my previous post, which was super encouraging as a new author.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-mailing-lists&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;mailing-lists&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;1.2.&lt;/span&gt; Mailing lists&lt;/h3&gt;
&lt;div id=&quot;text-1-2&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
I feel like it's much harder to engage with Guix discussions on a longer-term scale. By this I mean that there are a few &lt;a href=&quot;https://guix.gnu.org/en/contact/&quot;&gt;mailing lists&lt;/a&gt;, which seem quite active, but as someone who grew up &lt;i&gt;after&lt;/i&gt; mailing lists were cool, they're a bit intimidating to me. This doesn't mean I cannot find my way around (with some effort), but it's definitely a barrier compared to having a conventional forum.
&lt;/p&gt;

&lt;p&gt;
However, I recognise that this is a me / my generation problem and that likely for many others forums would prove just as cumbersome. Mailing lists are a very common thing in GNU/FSF related projects and, without even looking at the larger GNU projects and just by perusing the Guix archives, it's very clear that they're an effective way of communication, that requires no account and is compatible with a ton of configurations.
&lt;/p&gt;

&lt;p&gt;
I just wonder if there could be a better way of handling them, that doesn't feel as daunting to people who didn't grow up with this form of communication. I think even something as simple as a bit of CSS applied to the online interface of the mailing list could make it immediately a lot more appealing, but perhaps I'm simply being shallow.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-subreddit&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;subreddit&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;1.3.&lt;/span&gt; Subreddit&lt;/h3&gt;
&lt;div id=&quot;text-1-3&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
&lt;a href=&quot;https://reddit.com/r/GUIX/&quot;&gt;r/GUIX&lt;/a&gt; is not an official community, but it doesn't seem bad. People generally seem to only post questions, with the exception of the occasional screenshot of their config. It is a little barren, with only a couple posts a month, but Guix System is a niche distro and most communication happens on either the mailing list or IRC (or on the Codeberg issue tracker), which leaves only a small subset of people who are both interested enough in Guix too want to talk about it, but are also willing to put up with a closed and not exactly sqeaky-clean platform like Reddit.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-fediverse&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;fediverse&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;1.4.&lt;/span&gt; Fediverse&lt;/h3&gt;
&lt;div id=&quot;text-1-4&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
Guix has a presence on &lt;a href=&quot;https://hachyderm.io/@guix&quot;&gt;Hachyderm&lt;/a&gt;, which is one of the more known Mastodon servers out there. I have little experience with the Fediverse (I've never even used Twitter much, which is the mainstream equivalent to Mastodon), but I'm glad that it exists. The account seems quite active too, the team running it is diligently sharing Guix-related posts and even makes some posts of their own.
&lt;/p&gt;

&lt;p&gt;
They actually &lt;a href=&quot;https://hachyderm.io/@guix/116000285085032513&quot;&gt;shared&lt;/a&gt; my own article on their page, which I'm super happy about. If you're reading this, thank you very much! Interactions like this make me want to look deeper into this corner of the internet.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-packaging-wows-and-woes&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;packaging-wows-and-woes&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;2.&lt;/span&gt; Packaging wows and woes&lt;/h2&gt;
&lt;div id=&quot;text-2&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
After my success with packaging &lt;a href=&quot;./guix-packaging.html&quot;&gt;WezTerm&lt;/a&gt;, I decided to jump into upgrading some other packages too. These upgrades were mostly minor stuff, not really worth their own posts, but they did come with recurring issues and notes, that I figured would be worth collecting here.
&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-i-really-love-the-go-getter-attitude-of-guix&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;i-really-love-the-go-getter-attitude-of-guix&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;2.1.&lt;/span&gt; I really love the go-getter attitude of Guix&lt;/h3&gt;
&lt;div id=&quot;text-2-1&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
You want to upgrade a package? Go ahead, do it. With the exception of trying to do something so big that it requires a &lt;a href=&quot;https://consensus.guix.gnu.org/&quot;&gt;team consensus&lt;/a&gt; or is so huge that it requires a dedicated team (think GNOME or KDE), nobody will say &amp;quot;That's not your authority!&amp;quot;. You can just open a PR, get some eyes on your change and that's it.
&lt;/p&gt;

&lt;p&gt;
At the time I was writing this, I was &lt;a href=&quot;https://codeberg.org/guix/guix/pulls/6726&quot;&gt;packaging&lt;/a&gt; the &lt;a href=&quot;https://odin-lang.org/&quot;&gt;Odin language&lt;/a&gt; for Guix. Apparently nobody did it before me, their compilation guide seemed simple enough, so I went and did it myself. As someone who works in a corporate environment, this sort of freedom is exhilarating, especially since you're not just simply helping yourself, but all the other people who might want to try this language in the future and happen to be using Guix too.
&lt;/p&gt;

&lt;p&gt;
This is very much unlike Nix, where every package has one or more so-called maintainers, who have first say in how a package is developed and upgraded. On one hand, this does provide a bit of accountability. People will feel stronger about keeping stuff up to date and tidy if it's at least superficially &amp;quot;theirs&amp;quot;. On the other hand, it's yet another hurdle for those who would like to help out, but aren't yet in the maintainers list and it makes Nix feel more private than it really is.
&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-a-side-note-about-speed&quot; class=&quot;outline-4&quot;&gt;
&lt;h4 id=&quot;a-side-note-about-speed&quot;&gt;&lt;span class=&quot;section-number-4&quot;&gt;2.1.1.&lt;/span&gt; A side-note about speed&lt;/h4&gt;
&lt;div id=&quot;text-2-1-1&quot; class=&quot;outline-text-4&quot;&gt;
&lt;p&gt;
The only real negative part of this whole contributing experience is the speed of reviews. There are PRs open from eight months ago. While such is not unusual in corporate environments (hell, make it years), I believe it's not healthy for a rolling release Linux distro, except in very specific cases (projects of such magnitude that you really need a year-ish time to develop them).
&lt;/p&gt;

&lt;p&gt;
By the time people actually take a look at the PR, the project had moved on and is substantially different from when the PR was opened. At best this will result in headache-inducing merge conflicts, at worst the PR is no longer relevant, because some other development had obsoleted it. While I haven't experienced such myself yet, I bet it'd be a pretty disheartening feeling.
&lt;/p&gt;

&lt;p&gt;
Now, I'll freely admit, my PRs so far had generally speedy review processes, so I personally don't have anything to complain about. My point more is that this is not a given thing, but rather something that comes down to the maintainers free time and available effort. And long delays can result from something as mild as some irritation to actual breakages (see the section about &lt;a href=&quot;#blunderbird&quot;&gt;Thunderbird&lt;/a&gt; of this article).
&lt;/p&gt;

&lt;p&gt;
This is nothing new though. The Guix team itself &lt;a href=&quot;https://slides.com/futurile/survey-says-5-guix-dev-priorities&quot;&gt;identified&lt;/a&gt; the slowness of reviews as one of the main things people find cumbersome about the project. This presentation is from a year, so hopefully the gears are turning in the background as I'm writing this and soon enough this problem will be a thing of the past. Until then, any prospective packagers should be aware that they are more than welcome, but they should also come prepared for long waits of up to a week or more, before their package is considered.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-debugging-is-an-unpleasant-experience&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;debugging-is-an-unpleasant-experience&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;2.2.&lt;/span&gt; Debugging is an unpleasant experience&lt;/h3&gt;
&lt;div id=&quot;text-2-2&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
In my experience, errors messages in Guix are not very helpful. This is not super surprising seeing Guix is a massive build system built on a completely dynamic language, but regardless it makes debugging a real pain in the neck. Which is a shame, because one of the main selling points of Lisp(likes) is the fact that their REPL experience is unparalleled and that all other languages lag behind with their tacked-on / simplistic debuggers.
&lt;/p&gt;

&lt;p&gt;
In Guix' case, while it is &lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Using-Guix-Interactively.html&quot;&gt;possible to use&lt;/a&gt; the Guile REPL to interface with the build system, so far I didn't find it useful enough to replace the basic &amp;quot;Edit in editor -&amp;gt; Compile in terminal -&amp;gt; Try to figure out errors&amp;quot; loop.
&lt;/p&gt;

&lt;p&gt;
The three most confusing issues I've experienced so far were:
&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-missing-parentheses&quot; class=&quot;outline-4&quot;&gt;
&lt;h4 id=&quot;missing-parentheses&quot;&gt;&lt;span class=&quot;section-number-4&quot;&gt;2.2.1.&lt;/span&gt; Missing parentheses&lt;/h4&gt;
&lt;div id=&quot;text-2-2-1&quot; class=&quot;outline-text-4&quot;&gt;
&lt;p&gt;
For this first example, I'll include the entire stack trace. In the other two, it will be truncated, but be aware that with each error we get this much on our screen.
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-nil&quot;&gt;  /home/nemin/programming/projects/guix/gnu/packages/guile-xyz.scm:7973:1: unexpected end of input while searching for: )
error: googletest: unbound variable
hint: Did you forget a `use-modules' form?

error: bzip2: unbound variable
hint: Did you forget a `use-modules' form?

error: libusb: unbound variable
hint: Did you forget a `use-modules' form?

error: make-lld-wrapper: unbound variable
hint: Did you forget `(use-modules (gnu packages llvm))' or `#:use-module (gnu packages llvm)'?

error: tcc: unbound variable
hint: Did you forget a `use-modules' form?

error: cross-gcc-toolchain: unbound variable
hint: Did you forget `(use-modules (gnu packages cross-base))' or `#:use-module (gnu packages
cross-base)'?

error: gnu-make: unbound variable
hint: Did you forget a `use-modules' form?

error: tar: unbound variable
hint: Did you forget a `use-modules' form?

error: gcc-toolchain: unbound variable
hint: Did you forget a `use-modules' form?

error: xdgpp: unbound variable
hint: Did you forget a `use-modules' form?

error: cross-binutils: unbound variable
hint: Did you forget `(use-modules (gnu packages cross-base))' or `#:use-module (gnu packages
cross-base)'?

Throw to key `unbound-variable' with args `(&amp;quot;resolve-interface&amp;quot; &amp;quot;no binding `~A' in module ~A&amp;quot; (shared-mime-info (gnu packages freedesktop)) #f)'.
Backtrace:
In guix/status.scm:
    842:4 19 (call-with-status-report _ _)
In ice-9/boot-9.scm:
  1752:10 18 (with-exception-handler _ _ #:unwind? _ # _)
In guix/store.scm:
   504:37 17 (thunk)
   1119:8 16 (call-with-build-handler #&amp;lt;procedure 7f6d9e2ab390 at g…&amp;gt; …)
In guix/scripts/build.scm:
    646:2 15 (_)
   695:42 14 (loop _ _ ())
In gnu/packages.scm:
    512:2 13 (%find-package &amp;quot;haunt-next&amp;quot; &amp;quot;haunt-next&amp;quot; #f)
    392:6 12 (find-best-packages-by-name _ _)
   322:56 11 (_ &amp;quot;haunt-next&amp;quot; _)
In unknown file:
          10 (force #&amp;lt;promise #&amp;lt;procedure 7f6d9e306700 at gnu/packag…&amp;gt;)
In gnu/packages.scm:
   244:33  9 (fold-packages #&amp;lt;procedure 7f6d9d90b778 at gnu/package…&amp;gt; …)
In guix/discovery.scm:
   158:11  8 (all-modules _ #:warn _)
In srfi/srfi-1.scm:
   460:18  7 (fold #&amp;lt;procedure 7f6d9e3437e0 at guix/discovery.scm:1…&amp;gt; …)
In guix/discovery.scm:
   148:19  6 (_ _ ())
    115:5  5 (scheme-modules _ _ #:warn _)
In srfi/srfi-1.scm:
   691:23  4 (filter-map #&amp;lt;procedure 7f6d9e343680 at guix/discove…&amp;gt; . #)
In guix/discovery.scm:
   123:24  3 (_ . _)
In guix/ui.scm:
    365:2  2 (report-unbound-variable-error _ #:frame _)
In ice-9/boot-9.scm:
  1685:16  1 (raise-exception _ #:continuable? _)
  1685:16  0 (raise-exception _ #:continuable? _)

ice-9/boot-9.scm:1685:16: In procedure raise-exception:
Throw to key `match-error' with args `(&amp;quot;match&amp;quot; &amp;quot;no matching pattern&amp;quot; (unbound-variable &amp;quot;resolve-interface&amp;quot; &amp;quot;no binding `~A' in module ~A&amp;quot; (shared-mime-info (gnu packages freedesktop)) #f))'.
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Admittedly in this case it's not too bad, because Guile does actually inform you that a closing parenthesis is missing, but the error is still buried under a load of completely irrelevant error messages.
&lt;/p&gt;

&lt;p&gt;
Additionally, if this happens, &lt;code&gt;guix edit&lt;/code&gt; no longer works and you need to manually open the file that contains the issue and find the package. Not a big deal, but a bummer.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-non-existent-variables&quot; class=&quot;outline-4&quot;&gt;
&lt;h4 id=&quot;non-existent-variables&quot;&gt;&lt;span class=&quot;section-number-4&quot;&gt;2.2.2.&lt;/span&gt; Non-existent variables&lt;/h4&gt;
&lt;div id=&quot;text-2-2-2&quot; class=&quot;outline-text-4&quot;&gt;
&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-nil&quot;&gt;error: reversion: unbound variable
hint: Did you forget a `use-modules' form?

error: googletest: unbound variable
hint: Did you forget a `use-modules' form?

error: bzip2: unbound variable
hint: Did you forget a `use-modules' form?

&amp;lt;... a lot of other stuff ...&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
It happened to me once or twice that I was either still referencing a variable that I previously cut or I made an accidental typo.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.1&quot; href=&quot;#fn.1&quot; class=&quot;footref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
Just like with the missing parentheses, you technically get an error, but it's buried and blends in with all the other completely irrelevant errors (which are actually not even legitimate errors, they're just caused by the process buckling on itself).
&lt;/p&gt;

&lt;p&gt;
Of course, it's hard to blame Guix here. There is no programmatic way of telling if the user was just silly or if there really is a variable somewhere in some yet unused module. But it'd still be so much better if the error stopped right after the first (and only legitimate) missing variable.
&lt;/p&gt;

&lt;p&gt;
Also &lt;code&gt;guix edit&lt;/code&gt; breaks as before.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-circular-dependencies&quot; class=&quot;outline-4&quot;&gt;
&lt;h4 id=&quot;circular-dependencies&quot;&gt;&lt;span class=&quot;section-number-4&quot;&gt;2.2.3.&lt;/span&gt; Circular dependencies&lt;/h4&gt;
&lt;div id=&quot;text-2-2-3&quot; class=&quot;outline-text-4&quot;&gt;
&lt;p&gt;
This one in my opinion is the most problematic common issue a package author might face. Chiefly because the error message you get in this case is not even useful:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-nil&quot;&gt;error: gcc-toolchain: unbound variable
hint: Did you forget a `use-modules' form?

error: xdgpp: unbound variable
hint: Did you forget a `use-modules' form?

error: cross-binutils: unbound variable
hint: Did you forget `(use-modules (gnu packages cross-base))' or `#:use-module (gnu packages
cross-base)'?

Throw to key `unbound-variable' with args `(&amp;quot;resolve-interface&amp;quot; &amp;quot;no binding `~A' in module ~A&amp;quot; (shared-mime-info (gnu packages freedesktop)) #f)'.

&amp;lt;... a lot of other irrelevant junk ...&amp;gt;

ice-9/boot-9.scm:1685:16: In procedure raise-exception:
Throw to key `match-error' with args `(&amp;quot;match&amp;quot; &amp;quot;no matching pattern&amp;quot; (unbound-variable &amp;quot;resolve-interface&amp;quot; &amp;quot;no binding `~A' in module ~A&amp;quot; (shared-mime-info (gnu packages freedesktop)) #f))'.
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The untrained packager might start pulling in stuff like &lt;code&gt;gcc-toolchain&lt;/code&gt; or &lt;code&gt;xdgpp&lt;/code&gt;, not understanding why they even need these or start poking around &lt;code&gt;(gnu packages freedesktop)&lt;/code&gt; to no avail. As far as I know, there is no good method for figuring out what is causing a circular dependency beyond good intuition and bisecting your code until you find the culprit.
&lt;/p&gt;

&lt;p&gt;
I've struggled quite a bit with this while &lt;a href=&quot;https://codeberg.org/guix/guix/pulls/6398#issuecomment-10783709&quot;&gt;packaging Hare 0.26.0&lt;/a&gt; and, while I did manage to solve the issue I faced there, I wasn't extremely happy with the solution:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-scheme&quot;&gt;(build-system hare-build-system)
(native-inputs
   (list scdoc
         (module-ref (resolve-interface '(gnu packages hare)) 'harec)
         qbe))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
In this instance, as you may have guessed, &lt;code&gt;harec&lt;/code&gt; is the culprit. I'm still not 100% on all the details, but I believe the problem is caused by &lt;code&gt;hare-build-system&lt;/code&gt; pulling in the hare module (which &lt;code&gt;harec&lt;/code&gt; belongs to) and so when we're trying to import it in the package definition, it can't work out the right order.
&lt;/p&gt;

&lt;p&gt;
Whatever's the reason, the solution isn't super difficult, but it is also not exactly obvious. Firstly, we need to get the module &amp;quot;behind Guix's back&amp;quot;:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-info&quot;&gt;-- Scheme Procedure: resolve-interface name [#:select=#f] [#:hide='()]
        [#:prefix=#f] [#:renamer=#f] [#:version=#f]
   Find the module named NAME as with ‘resolve-module’ and return its
   interface.  The interface of a module is also a module object, but
   it contains only the exported bindings.
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Next, we need to extract &lt;code&gt;harec&lt;/code&gt; from it:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-info&quot;&gt;-- Scheme Procedure: module-ref module name
   Look up the value bound to NAME in MODULE.  Like ‘module-variable’,
   but also does a ‘variable-ref’ on the resulting variable, raising
   an error if NAME is unbound.
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
What we're ultimately left with is the same as if we simply imported the module normally and then put &lt;code&gt;harec&lt;/code&gt; into the list. The trick here is that the various inputs Guix takes (including &lt;code&gt;native-inputs&lt;/code&gt;) are &lt;i&gt;&lt;a href=&quot;https://stackoverflow.com/questions/925365/what-is-a-thunk-as-used-in-scheme-or-in-general&quot;&gt;thunked&lt;/a&gt;&lt;/i&gt;. This means the code inside these forms isn't immediately run, but is instead delayed to a later moment, unlike the build system which is already resolved during the start of evaluation.
&lt;/p&gt;

&lt;p&gt;
In effect, by only ever referring to &lt;code&gt;harec&lt;/code&gt; in a &lt;i&gt;thunked&lt;/i&gt; environment, we've completely side-stepped the whole &amp;quot;where should we import the module&amp;quot; problem by forcing the package definition to wait.
&lt;/p&gt;

&lt;p&gt;
I'll freely admit that I would have not figured this out had there not been many other packages whose authors have faced this exact problem before and before the kind help of &lt;a href=&quot;https://codeberg.org/tinystar&quot;&gt;tinystar&lt;/a&gt;, who guided me towards targeting the &lt;code&gt;native-inputs&lt;/code&gt; instead of my original idea.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.2&quot; href=&quot;#fn.2&quot; class=&quot;footref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-a-cornucopia-of-styles&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;a-cornucopia-of-styles&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;2.3.&lt;/span&gt; A cornucopia of styles&lt;/h3&gt;
&lt;div id=&quot;text-2-3&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
Guix is a very flexible system. This is usually a great thing, because the last thing you want is your build system to constrain you from including a package that otherwise fits into the repo. However, this also bring with itself the fact that (beyond what the linting script offers) there is little guidance on how your package should be organised.
&lt;/p&gt;

&lt;p&gt;
This shows up both in small and big things. A good example is that the order of fields in the package definition is arbitrary. People generally put &lt;code&gt;name&lt;/code&gt; first, but there is nothing actually stopping you from putting it last. Sure, this probably wouldn't pass peer-review, but with other fields, the situation is far less obvious.
&lt;/p&gt;

&lt;p&gt;
For instance, should you introduce all static metadata first or should you go by the usual order and put &lt;code&gt;arguments&lt;/code&gt; before the description and license? How do you break lists aesthetically? Do you use &lt;code&gt;(arguments (list stuff))&lt;/code&gt; or &lt;code&gt;(arguments `(stuff))&lt;/code&gt;?
&lt;/p&gt;

&lt;p&gt;
And then there's the even larger differences:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;p&gt;
&lt;b&gt;Path construction:&lt;/b&gt; When it comes to constructing paths,
&lt;/p&gt;

&lt;div class=&quot;sameline&quot;&gt;
&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 1: &lt;/span&gt;Do you use &lt;code&gt;format&lt;/code&gt;?&lt;/label&gt;&lt;pre class=&quot;src src-scheme&quot;&gt;(format #f &lt;span class=&quot;org-string&quot;&gt;&amp;quot;~a/path/to/bin&amp;quot;&lt;/span&gt; #$output)
&lt;/pre&gt;
&lt;/div&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 2: &lt;/span&gt;Or &lt;code&gt;string-append&lt;/code&gt;?&lt;/label&gt;&lt;pre class=&quot;src src-scheme&quot;&gt;(string-append #$output &lt;span class=&quot;org-string&quot;&gt;&amp;quot;/path/to/bin&amp;quot;&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 3: &lt;/span&gt;Or perhaps &lt;code&gt;in-vicinity&lt;/code&gt;?&lt;/label&gt;&lt;pre class=&quot;src src-scheme&quot;&gt;(in-vicinity #$output &lt;span class=&quot;org-string&quot;&gt;&amp;quot;path/to/bin&amp;quot;&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;/div&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
&lt;b&gt;Code repetition:&lt;/b&gt; When installing completions,
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 4: &lt;/span&gt;Do you spell out each shell?&lt;/label&gt;&lt;pre class=&quot;src src-scheme&quot;&gt;(&lt;span class=&quot;org-keyword&quot;&gt;with-output-to-file&lt;/span&gt; bash-file
  (&lt;span class=&quot;org-keyword&quot;&gt;lambda&lt;/span&gt; _ (invoke &lt;span class=&quot;org-string&quot;&gt;&amp;quot;binary&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;output-bash&amp;quot;&lt;/span&gt;)))
(&lt;span class=&quot;org-keyword&quot;&gt;with-output-to-file&lt;/span&gt; zsh-file
  (&lt;span class=&quot;org-keyword&quot;&gt;lambda&lt;/span&gt; _ (invoke &lt;span class=&quot;org-string&quot;&gt;&amp;quot;binary&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;output-zsh&amp;quot;&lt;/span&gt;)))
(&lt;span class=&quot;org-keyword&quot;&gt;with-output-to-file&lt;/span&gt; fish-file
  (&lt;span class=&quot;org-keyword&quot;&gt;lambda&lt;/span&gt; _ (invoke &lt;span class=&quot;org-string&quot;&gt;&amp;quot;binary&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;output-fish&amp;quot;&lt;/span&gt;)))
&lt;/pre&gt;
&lt;/div&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 5: &lt;/span&gt;Or do you just delegate it to a &lt;code&gt;for-each&lt;/code&gt;-&lt;code&gt;match-lambda&lt;/code&gt; pair?&lt;/label&gt;&lt;pre class=&quot;src src-scheme&quot;&gt;
(&lt;span class=&quot;org-keyword&quot;&gt;for-each&lt;/span&gt;
 (&lt;span class=&quot;org-keyword&quot;&gt;match-lambda&lt;/span&gt;
   ((file-name . command)
    (&lt;span class=&quot;org-keyword&quot;&gt;with-output-to-file&lt;/span&gt; file-name
      (&lt;span class=&quot;org-keyword&quot;&gt;lambda&lt;/span&gt; _ (invoke &lt;span class=&quot;org-string&quot;&gt;&amp;quot;binary&amp;quot;&lt;/span&gt; command))))
   '((bash-file . &lt;span class=&quot;org-string&quot;&gt;&amp;quot;output-bash&amp;quot;&lt;/span&gt;)
     (zsh-file . &lt;span class=&quot;org-string&quot;&gt;&amp;quot;output-zsh&amp;quot;&lt;/span&gt;)
     (fish-file . &lt;span class=&quot;org-string&quot;&gt;&amp;quot;output-fish&amp;quot;&lt;/span&gt;))))
&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
&lt;b&gt;Environment variables:&lt;/b&gt; If you need to set environment variables,
&lt;/p&gt;

&lt;div class=&quot;sameline&quot;&gt;
&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 6: &lt;/span&gt;Do you make a whole phase just for this purpose?&lt;/label&gt;&lt;pre class=&quot;src src-scheme&quot;&gt;(modify-phases %standard-phases
  (add-before 'build 'set-environment
    (&lt;span class=&quot;org-keyword&quot;&gt;lambda&lt;/span&gt; _
      (setenv &lt;span class=&quot;org-string&quot;&gt;&amp;quot;VAR1&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;VALUE1&amp;quot;&lt;/span&gt;)
      (setenv &lt;span class=&quot;org-string&quot;&gt;&amp;quot;VAR2&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;VALUE2&amp;quot;&lt;/span&gt;)))
  (replace 'build
    (&lt;span class=&quot;org-keyword&quot;&gt;lambda&lt;/span&gt; _ ...)))
&lt;/pre&gt;
&lt;/div&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 7: &lt;/span&gt;Or do you use &lt;code&gt;setenv&lt;/code&gt; inside the &lt;code&gt;build&lt;/code&gt; phase?&lt;/label&gt;&lt;pre class=&quot;src src-scheme&quot;&gt;(modify-phases %standard-phases
  (replace 'build
    (&lt;span class=&quot;org-keyword&quot;&gt;lambda&lt;/span&gt; _
      (setenv &lt;span class=&quot;org-string&quot;&gt;&amp;quot;VAR1&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;VALUE1&amp;quot;&lt;/span&gt;)
      (setenv &lt;span class=&quot;org-string&quot;&gt;&amp;quot;VAR2&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;VALUE2&amp;quot;&lt;/span&gt;)
      ...)))
&lt;/pre&gt;
&lt;/div&gt;

&lt;/div&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;
&lt;b&gt;Post-processing:&lt;/b&gt; If you need your built binary for post-processing (e.g. installing completions, generating extra files, etc.),
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 8: &lt;/span&gt;Do you call it from your build folder?&lt;/label&gt;&lt;pre class=&quot;src src-scheme&quot;&gt;(&lt;span class=&quot;org-keyword&quot;&gt;let&lt;/span&gt; ((bin &lt;span class=&quot;org-string&quot;&gt;&amp;quot;./build/folder/your-binary&amp;quot;&lt;/span&gt;))
  (invoke bin &lt;span class=&quot;org-string&quot;&gt;&amp;quot;postprocess&amp;quot;&lt;/span&gt;))
&lt;/pre&gt;
&lt;/div&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 9: &lt;/span&gt;Or your output folder?&lt;/label&gt;&lt;pre class=&quot;src src-scheme&quot;&gt;&lt;span class=&quot;org-comment-delimiter&quot;&gt;;; &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;Also, do note that the whole previous debacle of which
&lt;/span&gt;&lt;span class=&quot;org-comment-delimiter&quot;&gt;;; &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;path joining method to use still applies!
&lt;/span&gt;(&lt;span class=&quot;org-keyword&quot;&gt;let&lt;/span&gt; ((bin (string-append #$output &lt;span class=&quot;org-string&quot;&gt;&amp;quot;/bin/your-binary&amp;quot;&lt;/span&gt;)))
  (invoke bin &lt;span class=&quot;org-string&quot;&gt;&amp;quot;postprocess&amp;quot;&lt;/span&gt;))
&lt;/pre&gt;
&lt;/div&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 10: &lt;/span&gt;Do you even call it straight or do invoke it indirectly to account for cross-compilation?&lt;/label&gt;&lt;pre class=&quot;src src-scheme&quot;&gt;&lt;span class=&quot;org-comment-delimiter&quot;&gt;;; &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;Taken from gnu/packages/rust-apps.scm, &amp;quot;just&amp;quot; package
&lt;/span&gt;(&lt;span class=&quot;org-keyword&quot;&gt;let&lt;/span&gt; ((just (&lt;span class=&quot;org-keyword&quot;&gt;if&lt;/span&gt; ,(%current-target-system)
                (search-input-file native-inputs &lt;span class=&quot;org-string&quot;&gt;&amp;quot;/bin/just&amp;quot;&lt;/span&gt;)
                (string-append out &lt;span class=&quot;org-string&quot;&gt;&amp;quot;/bin/just&amp;quot;&lt;/span&gt;))))
  (&lt;span class=&quot;org-keyword&quot;&gt;with-output-to-file&lt;/span&gt;
             (string-append bash-completions-dir &lt;span class=&quot;org-string&quot;&gt;&amp;quot;/just&amp;quot;&lt;/span&gt;)
             (&lt;span class=&quot;org-keyword&quot;&gt;lambda&lt;/span&gt; _ (invoke just &lt;span class=&quot;org-string&quot;&gt;&amp;quot;--completions&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;bash&amp;quot;&lt;/span&gt;))))
&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
I'm sure there's a lot more, but these are the stuff that I personally experienced. The issue is that for most of these you might feel like one or the other is the &lt;i&gt;obvious&lt;/i&gt; answer, but if you scroll through the package definitions, I'd bet money on it that you'd find multiple examples of all of them.
&lt;/p&gt;

&lt;p&gt;
Now, don't get me wrong, I'm not pointing fingers at anybody. Guix is a volunteer project to which hundreds, if not thousands of people contribute to. You cannot expect perfect coordination from so many people, especially volunteers who might just want to see X or Y package in the registry and don't necessarily care about digging super deep into how things work. Not to mention, having ten different similarly good ways of doing something is a known Lisp curse. When you have barely any syntax and very convenient tools to mess with ASTs, all hell breaks loose.
&lt;/p&gt;

&lt;p&gt;
But I think it'd still make sense to codify some basics at least. Have a given order where package fields should go. If someone strays from this, have them justify it with a solid reason. Give pointers on divisive styles like the examples above, providing reasons why Guix recommends one over the other.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.3&quot; href=&quot;#fn.3&quot; class=&quot;footref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
This would serve two important purposes:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Firstly, with less noise in the code, it'd be much easier for everyone to study the code, make changes, and review others' changes. Mundane code beats smart code when it comes to working together and with Lisp(likes) one already needs discipline to write &amp;quot;standardised&amp;quot; code.&lt;/li&gt;
&lt;li&gt;Secondly, with these things codified, newcomers would have one less thing to be stumped by. I was so surprised when during my first PR my usage of &lt;code&gt;string-append&lt;/code&gt; was replaced with &lt;code&gt;in-vicinity&lt;/code&gt;, not only because I had no idea that this function existed in the first place, but because I was working based on another package where &lt;code&gt;string-append&lt;/code&gt; was used and didn't even consider it's not the only option.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-changelog-is-confusing&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;changelog-is-confusing&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;2.4.&lt;/span&gt; ChangeLog is confusing&lt;/h3&gt;
&lt;div id=&quot;text-2-4&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
I briefly touched on ChangeLog in my WezTerm post, but I wanted to bring it up again, because despite my best efforts at trying to build &amp;quot;muscle memory&amp;quot; for it, I still feel nearly as lost as I did at the start.
&lt;/p&gt;

&lt;p&gt;
Okay, that's not entirely fair. I was able to get the provided Emacs's yasnippet templates loaded, which automate some of the tedium that comes with the ChangeLog commit message style. I've also been made aware that there is a file under &lt;code&gt;etc&lt;/code&gt; called &lt;code&gt;commiter.scm&lt;/code&gt;, which practically writes all the boilerplate for you.
&lt;/p&gt;

&lt;p&gt;
However, it's not really the tedium that bothers me. If ChangeLog were really just a very rigid and verbose system where each possibility is set in stone and obvious from a glance, I probably wouldn't mention it or, hell, maybe even would have put it in as a positive.
&lt;/p&gt;

&lt;p&gt;
But that isn't the case, because where these templates cannot help is the nuanced parts. It is not entirely obvious how involved you need to make your commit messages and, even from my &lt;b&gt;very&lt;/b&gt; limited experience, it differs greatly between maintainers who accepts what.
&lt;/p&gt;

&lt;p&gt;
For instance, with Rust applications you are &lt;a href=&quot;https://guix.gnu.org/cookbook/en/html_node/Common-Workflow-for-Rust-Packaging.html&quot;&gt;expected&lt;/a&gt; to import all dependencies into the registry. So far so good. But how does one reflect this in their commits?
&lt;/p&gt;

&lt;p&gt;
For one, you must include your note about the crate import in the same commit as the one that introduces your package. This goes against the usual convention where each change gets their own commit, but (while I don't remember it mentioned in the guide) it's obvious enough to figure out based on a quick &lt;code&gt;git log&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
What's far less obvious, however, is how to phrase this import. To keep everyone in the loop, an entry in a ChangeLog-formatted commit message looks like the following:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;An asterisk to denote a new entry,&lt;/li&gt;
&lt;li&gt;The name of the file that was edited,&lt;/li&gt;
&lt;li&gt;Then the name of the variable / function that was edited in parentheses,&lt;/li&gt;
&lt;li&gt;Then, only if what you were editing is a struct with multiple fields, the name of the field in square brackets,&lt;/li&gt;
&lt;li&gt;Then, if said field has its own sections, the name of the section in curly brackets,&lt;/li&gt;
&lt;li&gt;Finally, after a set of colons, you add a short textual description of what you've done.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
For example:
&lt;/p&gt;

&lt;p&gt;
&lt;code&gt;* foo/bar/baz.scm (example-struct)[section]{subsection}: Frobnicated some walruses.&lt;/code&gt;
&lt;/p&gt;

&lt;p&gt;
Now that we're on the same page, let's try to construct a line like this for our Rust imports. The start of the line is easy: &lt;code&gt;* gnu/packages/rust-crates.scm&lt;/code&gt;. But what comes right afterwards and especially in the description field is nontrivial and wildly varies between authors.
&lt;/p&gt;

&lt;p&gt;
From what I can see, there are two bigger schools of thought:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-change-log&quot;&gt;* gnu/packages/rust-apps.scm ($PACKAGE): Init at $VERSION.
* gnu/packages/rust-crates.scm (lookup-cargo-inputs): Added imports.
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
This variant only records the essentials. &amp;quot;We're handling $PACKAGE, which is a Rust application&amp;quot; and &amp;quot;We've imported a given number of crates&amp;quot;. The pros of this is that it's extremely short and simple, and it conveys intent without going into the details. The con is that it doesn't mention anything about which crates exactly were imported.
&lt;/p&gt;

&lt;p&gt;
And that is exactly what the second variant focuses on:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-change-log&quot;&gt;* gnu/packages/rust-apps.scm ($PACKAGE): Init at $VERSION.
* gnu/packages/rust-crates.scm ($CRATE1, $CRATE2, $CRATE3, ...): New variables.
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
This is the polar opposite of the first version. Very specific, clearly spells out what crates were imported into the registry and at what version, and fits more into the usual commit message schema.
&lt;/p&gt;

&lt;p&gt;
On the other hand, it occasionally produces monsters like this:
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-02-24-guix_one_month_later/cargo_imports.avif&quot; alt=&quot;cargo_imports.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 1: &lt;/span&gt;This goes on for four more pages, by the way.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
In my personal and, indeed, just as subjective opinion, Variant 1 is the clear winner. The point of a commit message is to convey intent or, as the ChangeLog manual puts it:
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;&lt;a href=&quot;https://www.gnu.org/prep/standards/html_node/Change-Log-Concepts.html&quot;&gt;ChangeLog Concepts&lt;/a&gt;:&lt;/b&gt; People can see the current version; they don’t need the change log to tell them what is in it. What they want from a change log is a clear explanation of how the earlier version differed.
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
Yes, spelling out the whole list is &amp;quot;clear&amp;quot; in a certain sense, but since these crates were imported by a script and (beyond a couple of cases) aren't really touched by human hands, I believe the only relevant thing worth recording is that the importing process happened.
&lt;/p&gt;

&lt;p&gt;
But, while I have laser-focused on a single pain area, it's not the only place where disagreements like this happen. I've been given recommendations to rephrase several of my commit messages and it's not that I'm lazy to do this (in fact I've followed this advice most of the time), I just feel like the system is both rigid enough not to be very pleasant to use, but also loose to be truly automated.
&lt;/p&gt;

&lt;p&gt;
Again, Guix is a very flexible system, so maybe this &amp;quot;looseness&amp;quot; is actually a sheep in wolf's clothing. Better this than constant overrides in case the rigid system doesn't quite cover all the needs of maintainers.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-system-ups-and-downs&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;system-ups-and-downs&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;3.&lt;/span&gt; System ups and downs&lt;/h2&gt;
&lt;div id=&quot;text-3&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
While the previous section was about my experience with packaging stuff, this will be about using others' packages.
&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-stability-of-my-config&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;stability-of-my-config&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.1.&lt;/span&gt; Stability of my config&lt;/h3&gt;
&lt;div id=&quot;text-3-1&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
One thing I found interesting is how stable my config files ended up being while using Guix. Beyond the occasional addition to my home configuration, I found that I didn't have to touch my system config file at all.
&lt;/p&gt;

&lt;p&gt;
While this is obviously partly because I already experimented a bunch on NixOS before finding the right subset of applications that I need to be productive, I also feel like Guix is generally the less fiddly system of the two (in this particular aspect).
&lt;/p&gt;

&lt;p&gt;
Even more notably, I haven't had to touch Shepherd, Guix System's service manager once yet. Everything Just Works™, including changing over to PipeWire (my preferred audio server), which is both &lt;a href=&quot;https://guix.gnu.org/manual/1.5.0/en/html_node/Sound-Home-Services.html&quot;&gt;very well documented&lt;/a&gt; and only required a couple of lines in my home config:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 11: &lt;/span&gt;All you need to switch over.&lt;/label&gt;&lt;pre class=&quot;src src-scheme&quot;&gt;(service home-dbus-service-type)
(service home-pipewire-service-type)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
If everything was this simple in the Guix world, I'd have nothing to complain about. This is a gold standard.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-interfacing-with-my-phone&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;interfacing-with-my-phone&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.2.&lt;/span&gt; Interfacing with my phone&lt;/h3&gt;
&lt;div id=&quot;text-3-2&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
I wanted to move some files from my phone to my PC. On every other distro I've tried before, all this took was plugging it in and clicking on &amp;quot;Mount filesystem&amp;quot;. Or, perhaps if it was a more frugal distro, I had to issue &lt;code&gt;lsblk&lt;/code&gt;, find the right disk drive, and manually &lt;code&gt;mount&lt;/code&gt; it at a folder of my liking.
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-02-24-guix_one_month_later/android.avif&quot; alt=&quot;android.avif&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
With Guix System, I was unable to do this. I plugged my phone in, KDE Plasma offered to mount it. I clicked on it and all I got was a really confusing error. Finally, after quite a bit of digging on Google, I found a topic on the &lt;a href=&quot;https://bbs.archlinux.org/viewtopic.php?id=235200&quot;&gt;Arch Linux forums&lt;/a&gt; where someone mentioned &lt;code&gt;kio-extras&lt;/code&gt; and &lt;code&gt;kio-fuse&lt;/code&gt;. By adding these to my profile, the phone was finally able to connect.
&lt;/p&gt;

&lt;p&gt;
This particular instance wasn't a huge ordeal, but it is an annoying paper-cut, that makes Guix System feel slightly less polished. I believe the existence of a &amp;quot;Guix Wiki&amp;quot; (in the spirit of the Gentoo and Arch wikis) would help a lot in situations like this.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.4&quot; href=&quot;#fn.4&quot; class=&quot;footref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-blunderbird&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;blunderbird&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.3.&lt;/span&gt; Blunderbird&lt;/h3&gt;
&lt;div id=&quot;text-3-3&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
I generally use Thunderbird (or, since Guix doesn't provide Mozilla-branded binaries, &lt;a href=&quot;https://directory.fsf.org/wiki/Icedove&quot;&gt;Icedove&lt;/a&gt;) to handle my mail. It's a far more pleasant process in my opinion than constantly having a tab open in your browser.
&lt;/p&gt;

&lt;p&gt;
Well, as it turns out an update recently broke Icedove, because it implicitly relies on the other Mozilla-packages being the same version. Because of this, none of the build servers were able to build the package. This ultimately resulted in me being unable to update my system (without removing Icedove), because the moment I tried, Guix would notice that there is no pre-built package and began building it on my PC.
&lt;/p&gt;

&lt;p&gt;
This would be fine in most other cases. I do have a 12 core Ryzen CPU and 32 GBs of RAM,&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.5&quot; href=&quot;#fn.5&quot; class=&quot;footref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt; so it's not like I don't have the hardware usually. However, in this particular instance, even such mighty specs failed to cope with all of Icedove's dependencies (I believe it's &lt;code&gt;webkit-gtk&lt;/code&gt; that fails specifically, but I'm not 100% sure), so after my PC crashed twice trying to update, I decided to try to pursue different avenues.
&lt;/p&gt;

&lt;p&gt;
My first attempt was using GNOME's Evolution mail client. No luck. No matter what I tried, it couldn't connect GMail using OAuth2 and all the errors I've got were very cryptic.
&lt;/p&gt;

&lt;p&gt;
Fine, whatever, I use KDE Plasma anyway, so why not give KMail a shot? I installed KMail and…
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-02-24-guix_one_month_later/kmail.avif&quot; alt=&quot;kmail.avif&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Well, that's annoying. Neither the link, nor the button worked. I tried looking up what's going on and was led to believe the issue is caused by another dependency named &lt;code&gt;akonadi&lt;/code&gt; missing. It's packaged in Guix, so I added it to my profile, reconfigured the environment, restarted KMail and… Nothing. Still the same error.
&lt;/p&gt;

&lt;p&gt;
A bit more investigation later, I found that running &lt;code&gt;akonadictl status&lt;/code&gt; should give me a bit more insight:
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-02-24-guix_one_month_later/akonadi.avif&quot; alt=&quot;akonadi.avif&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
So it's unable to connect to MariaDB (which I've also added to my profile off-screen). At this point I was kind of stuck and abandoned this experiment.
&lt;/p&gt;

&lt;p&gt;
I don't know, maybe there was an easy solution and I was merely a step away from making KMail work, but manually pulling in 5-6 dependencies for an application on a declarative distro is kind of above my tolerance. I get that not everything can work out of the box, but this style of debugging fits Slackware&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.6&quot; href=&quot;#fn.6&quot; class=&quot;footref&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; a lot more than it does a modern Linux distro.
&lt;/p&gt;

&lt;p&gt;
Well, with the obvious alternatives ruled out, I was left with figuring out what to do with the Icedove situation. At the time I started writing this article, the fix was &lt;a href=&quot;https://codeberg.org/guix/guix/pulls/6706&quot;&gt;yet unmerged&lt;/a&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.7&quot; href=&quot;#fn.7&quot; class=&quot;footref&quot;&gt;7&lt;/a&gt;&lt;/sup&gt; so there were only a couple of options someone who uses the package could take:
&lt;/p&gt;

&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;&lt;b&gt;Not update:&lt;/b&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Pros: Requires no actions.&lt;/li&gt;
&lt;li&gt;Cons: No upgrades until the situation is resolved.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Remove &lt;code&gt;icedove&lt;/code&gt; from your profile:&lt;/b&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Pros: Very simple to do. You get other upgrades for everything else.&lt;/li&gt;
&lt;li&gt;Cons: You have to use another method to manage your mail. As seen above, if you don't want to use the browser route, this isn't necessarily an easy process.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Use an &lt;a href=&quot;https://guix.gnu.org/manual/1.5.0/en/html_node/Inferiors.html&quot;&gt;Inferior&lt;/a&gt;:&lt;/b&gt; (a.k.a. instruct Guix to pull in an older channel's package)
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Pros: You still have Icedove (even if it's an older version) &lt;i&gt;and&lt;/i&gt; upgrades for everything else.&lt;/li&gt;
&lt;li&gt;Cons: Requires tracking down the last commit that still worked, setting up the inferior, and then eventually removing it once the upgrade is done.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
Ultimately I chose option #3 and made an inferior for Icedove. In this instance, it was thankfully super easy to find the last good commit, because someone else &lt;a href=&quot;https://codeberg.org/guix/guix/issues/6714&quot;&gt;already did&lt;/a&gt; the dirty work. Despite the warning in the manual, it didn't even take particularly long to compute the new Guix derivation.
&lt;/p&gt;

&lt;p&gt;
On one hand, I suppose it's another praise towards Guix's flexibility to be able to &amp;quot;patch&amp;quot; the package lookup with an arbitrary older (or even completely separate) channel. Doing so on a traditional, imperative package manager would be a giant pain.
&lt;/p&gt;

&lt;p&gt;
On the other hand, it's an unfortunate situation to see a mainstream application breaking people's systems and no urgent action being taken for nearly a week, when a PR is already ready and waiting.
&lt;/p&gt;

&lt;p&gt;
This is, of course, the blessing and curse of a volunteer project. Just as you can jump into any task that you fancy, nobody is obligated to check out your work, which can (in unfortunate cases, such as this) cause disruptions.
&lt;/p&gt;

&lt;p&gt;
For some more positive news, this seems to be going to be at least partly addressed soon as Guix is &lt;a href=&quot;https://codeberg.org/guix-foundation/website/raw/branch/main/downloads/guix-foundation-fundraising-future-2026.pdf&quot;&gt;considering&lt;/a&gt; a grant system, which would financially support certain developers working in key areas.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-nvidia-drivers-and-non-lts-kernels&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;nvidia-drivers-and-non-lts-kernels&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.4.&lt;/span&gt; NVIDIA drivers and non-LTS kernels&lt;/h3&gt;
&lt;div id=&quot;text-3-4&quot; class=&quot;outline-text-3&quot;&gt;
&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;Massive disclaimer here:&lt;/b&gt; This is a Nonguix &amp;quot;issue&amp;quot;. And in-fact it's not even an issue, it's more just a case of &amp;quot;things could be explained much better&amp;quot;. Still, it was something that caused me quite a bit of pain and if I can spare only one other person from experiencing it, it was already a win in my book.
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
After one of the updates, my system started being unable to properly start up my desktop environment. I was able to get to GDM, enter my login info, press login, and then it'd either hang there or I'd get my windows stuck in the top left corner, without any of the usual Plasma UX decorations.
&lt;/p&gt;

&lt;p&gt;
Thankfully, as Guix is a generation-based declarative distro, I was able to roll back to an older version and have a working PC, but I found it really annoying that no matter how much I upgraded my system, it never quite seemed to help.
&lt;/p&gt;

&lt;p&gt;
I was quite close to giving up and going back to Nix or some other distro, when I finally found a lead. By reading the &lt;code&gt;dmesg&lt;/code&gt; logs, I found out that the problem was the kernel module not finding certain functions:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 12: &lt;/span&gt;This is not my exact kernel logs (&lt;a href=&quot;https://reddit.com/r/Gentoo/comments/1edo01u/bootup_error_lsmod_could_not_insert_nvidia/&quot;&gt;source&lt;/a&gt;), but I saw the same error messages.&lt;/label&gt;&lt;pre class=&quot;src src-nil&quot;&gt;[   95.122493] nvidia: loading out-of-tree module taints kernel.
[   95.122503] nvidia: module license 'NVIDIA' taints kernel.
[   95.122507] nvidia: module verification failed: signature and/or required key missing - tainting kernel
[   95.122508] nvidia: module license taints kernel.
[   95.595636] nvidia-nvlink: Nvlink Core is being initialized, major device number 508
[   95.596880] nvidia 0000:01:00.0: enabling device (0406 -&amp;gt; 0407)
[  101.074782] nvidia_drm: Unknown symbol nvKmsKapiGetFunctionsTable (err -2)
[12407.716914] nvidia_drm: Unknown symbol nvKmsKapiGetFunctionsTable (err -2)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
While this didn't quite solve the issue yet, it gave me a vital pointer, leading back to the &lt;a href=&quot;https://gitlab.com/nonguix/nonguix#:~:text=%2E%2E%2E%29-,NVIDIA%20graphics%20card,-NVIDIA%20support%20in&quot;&gt;Nonguix README&lt;/a&gt;. I gave the NVIDIA section a closer look and lo, the culprit was found:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-nil&quot;&gt;nonguix-transformation-nvidia [#:driver nvda]
                              [#:open-source-kernel-module? #f]
                              [#:s0ix-power-management? #f]
                              [#:kernel-mode-setting? #t]
                              [#:configure-xorg? #f]

Return a procedure that transforms an operating system, setting up
DRIVER (default: nvda) for NVIDIA graphics card.

OPEN-SOURCE-KERNEL-MODULE? (default: #f) only supports Turing and later
architectures and is expected to work with 'linux-lts'.

S0IX-POWER-MANAGEMENT? (default: #f) improves suspend and hibernate on systems
with supported graphics cards.

KERNEL-MODE-SETTING? (default: #t) is required for Wayland and rootless Xorg
support.

CONFIGURE-XORG? (default: #f) is required for Xorg display managers.  Currently
this argument configures the one used by '%desktop-services', GDM or SDDM.

Use 'replace-mesa', for application setup out of the operating system
declaration.
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Can you see it? If not, here's the line of note: &lt;i&gt;&amp;quot;and is expected to work with 'linux-lts'&amp;quot;.&lt;/i&gt; Yup, that's all the warning you get that &amp;quot;if you use the usual &lt;code&gt;linux&lt;/code&gt; package as your kernel, it can crash on you at any time.&amp;quot;
&lt;/p&gt;

&lt;p&gt;
Obviously, this doesn't mean that the Nonguix team hasn't done their due-diligence. After all, the solution is there. But I definitely think it'd be worth a stronger emphasis on the fact that you can and will break your install if you don't use the LTS kernel with a newer card / open-source driver.
&lt;/p&gt;

&lt;p&gt;
I'm also certain I'm not the only one who fell for this as there is already an &lt;a href=&quot;https://gitlab.com/nonguix/nonguix/-/issues/441&quot;&gt;issue&lt;/a&gt; on Nonguix' Gitlab, where the ticket opener's phrasing strongly implies they had to figure out the &lt;code&gt;linux-lts&lt;/code&gt; solution themselves as well.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-no-easy-way-of-seeing-what-is-updated&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;no-easy-way-of-seeing-what-is-updated&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.5.&lt;/span&gt; No easy way of seeing what is updated&lt;/h3&gt;
&lt;div id=&quot;text-3-5&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
One thing I sorely feel is missing (though Nix is no better in this regard) is the fact that there is no easy built in way to see what upgrading will do to your system.
&lt;/p&gt;

&lt;p&gt;
There are approximations, sure. You can either pass &lt;code&gt;--dry-run&lt;/code&gt; to Guix and get something along the lines of work items. There is also a new project by LiterateLisp's author Wilko, which allows you to &lt;a href=&quot;https://me.literatelisp.eu/hacklog-diffing-and-comparing-guix-derivations-using-breadth-first-search--jaccard.html&quot;&gt;diff two derivations&lt;/a&gt;, which is super cool and I hope one day it'll be upstreamed into Guix proper, but (unless I massively misunderstood things) it's a tool for after the fact. That is to say this only tells you what changed once the update is done.
&lt;/p&gt;

&lt;p&gt;
Now, of course, there is an argument to be made that, due to the way declarative distros work, you don't really have to care what's what version and it's not a huge deal to just roll back if you're not happy with the changes. But every imperative distro manages to give you a list of &amp;quot;here's what's gonna happen&amp;quot; and give you a choice if you consent to the upgrade or not, and I really miss it from here.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-sleep-causes-crashes&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;sleep-causes-crashes&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.6.&lt;/span&gt; Sleep causes crashes&lt;/h3&gt;
&lt;div id=&quot;text-3-6&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
I don't know if it's me doing things wrong or the driver not being good enough or something completely different causing it, but I am unable to use the Sleep function of the PC. The moment I press it, the PC freezes and requires a hard restart.
&lt;/p&gt;

&lt;p&gt;
What this means is that, if I know I need to go away for some time, I either leave things on (which wastes a lot of electricity) or I turn my PC off (not a huge deal, but a bit more inconvenient than just continuing from where you've left off).
&lt;/p&gt;

&lt;p&gt;
It's not exactly a vital feature, but all previous distros I've used had this working out of the box, so Guix System struggling with it is really unfortunate.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-odd-freezes-and-crashes&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;odd-freezes-and-crashes&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.7.&lt;/span&gt; Odd freezes and crashes&lt;/h3&gt;
&lt;div id=&quot;text-3-7&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
I cannot effectively quantify this, but Guix System doesn't really feel stable in my experience. In this ~1 month of use, I've experienced at least 4-5 spontaneous freezes and choppy performance while not doing anything particularly strenuous on the PC, which does not inspire confidence when it comes to working uninterrupted.
&lt;/p&gt;

&lt;p&gt;
I had freezes during listening to Youtube, browsing files in Dolphin, writing text in Emacs. Usually these issues went away on their own after a while, but not only does it make me worry that something (physical or digital) will end up damaged, I also get completely thrown out of my flow state.
&lt;/p&gt;

&lt;p&gt;
I definitely can't rule it out that some (perhaps even all) of these were caused by the notoriously finicky NVIDIA drivers. But it is the unfortunate reality, that I cannot swap GPUs for the sake of a distro, no matter how much I like it and none of the other distros I've tried had produced instability this bad before.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-conclusion&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;4.&lt;/span&gt; Conclusion&lt;/h2&gt;
&lt;div id=&quot;text-4&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
On a conceptual level, I love Guix and Guix System. I much prefer writing Scheme over Nix's language and I really like that there is far lesser of a divide between the builders and the package definitions.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.8&quot; href=&quot;#fn.8&quot; class=&quot;footref&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
I also love the low barrier of entry when it comes to contributing to the ecosystem. I'd wager Guix might be one of the easiest distros to have packages accepted for, if you have a bit of Scheme experience and are willing to oblige the maintainers.
&lt;/p&gt;

&lt;p&gt;
But, and I'm sad to say this 'but', both still feel unpolished to me. Ever since I've grown accustomed to Linux in a way that no longer makes me want to distro-hop, my journey was always about finding a distro that maximises stability and package freshness, and settling for it.
&lt;/p&gt;

&lt;p&gt;
Guix System sadly neither fulfilled the stability (due to all the crashes), nor the freshness (due to many of the packages lagging far behind upstream) in the way I was hoping for and I am unlikely to stick with it much longer. I will likely still have Guix present on whichever distro I go back to, but for now I'm buying out of the GNU dream.
&lt;/p&gt;

&lt;p&gt;
Now, I don't want to sound entitled and talk down a project I'm otherwise very much aligned with. I recognise the gargantuan effort that went into getting things this far, especially considering that Guix isn't just aiming towards x86 and ARM, but also to be a host and accelerator for &lt;a href=&quot;https://guix.gnu.org/en/blog/2026/the-64-bit-hurd/&quot;&gt;HURD&lt;/a&gt;. I also recognise and respect that most of this effort was accomplished with no monetary gain expected and on what is considered (at least in corporate terms) a &lt;a href=&quot;https://guix.gnu.org/en/blog/2026/result-of-sustain-and-strengthen-fundraising/&quot;&gt;shoestring-budget&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
Even more importantly, things aren't happening in a vacuum and, while Guix's current state didn't prove to be quite what I was looking for, it doesn't mean it's not going to continue developing. Not only are they pulling in fresh funds &lt;a href=&quot;https://codeberg.org/guix-foundation/website/raw/branch/main/downloads/guix-foundation-fundraising-future-2026.pdf&quot;&gt;to fund infrastructure and projects&lt;/a&gt;, there's also a lot of ideas floating around how to make things better.
&lt;/p&gt;

&lt;p&gt;
I will only bring one example, but it will be comprehensive: The &lt;a href=&quot;https://codeberg.org/guix/maintenance/src/branch/master/doc/guix-days-2026/shared-cryptpad-guix-days-2026.md&quot;&gt;Shared Cryptpad of Guix Days 2026&lt;/a&gt;. This is the loose log of a couple of brainstorming sessions of Guix veterans and core developers, which touches on a massive amount of potential ways to make Guix more approachable including:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;How to manage secrets declaratively (this is &lt;a href=&quot;https://github.com/Mic92/sops-nix&quot;&gt;not simple&lt;/a&gt; even on Nix),&lt;/li&gt;
&lt;li&gt;How to communicate between the teams better,&lt;/li&gt;
&lt;li&gt;How to introduce breaking changes in a way that won't cause pain for everyone,&lt;/li&gt;
&lt;li&gt;Bringing a new GC to Guile, that should speed everything up (especially multi-threaded code),&lt;/li&gt;
&lt;li&gt;What they could learn from Nix,&lt;/li&gt;
&lt;li&gt;How to make the substitution situation better,&lt;/li&gt;
&lt;li&gt;How to automate PRs,&lt;/li&gt;
&lt;li&gt;How to make package breaks rarer and how to respond to them quicker,&lt;/li&gt;
&lt;li&gt;And (this was the one I found most interesting) thoughts about breaking with the GNU hardline: The possibility of enabling installing firmware in the installer, breaking the self-imposed wall between Guix and Nonguix, and even vaguely alluding towards the option of dropping the GNU label as a whole, while still retaining their best qualities and the commitment to free software.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
The list goes on. Obviously, all of these exist only as ideas and plans so far, but the important part is that the maintainers aren't sitting on their laurels and doing nothing. They're smart people trying their best to make Guix an even better experience than it already is.
&lt;/p&gt;

&lt;p&gt;
I wanted to end my post on a positive note, especially since much of my post has been anything but. Even though Guix System as it is today didn't quite live up to my expectations, I have no doubts that in a couple years time, if the project continues with this fervour and dedication, it will be a very serious contender. Perhaps still a niche one, because its commitments to Scheme and only free software severely limits its audience, but a well-polished and incredibly powerful system for those that can live within these bounds.
&lt;/p&gt;

&lt;p&gt;
In fact, I've read some comments of people who have been using Guix System for over a decade. You cannot get that amount of dedication, if you yourselves (i.e. the maintainers) aren't dedicated yourself, and Guix's clearly are. I wish them nothing but the best and it's not like I'll completely remove myself from the project's orbit, as I still have a couple packages I'd love to see merged and I'm still interested in the project's future developments.
&lt;/p&gt;

&lt;p&gt;
Thanks for reading!
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;h2 class=&quot;footnotes&quot;&gt;Footnotes: &lt;/h2&gt;
&lt;div id=&quot;text-footnotes&quot;&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.1&quot; href=&quot;#fnr.1&quot; class=&quot;footnum&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
At work I happen to use &lt;a href=&quot;https://www.djangoproject.com/&quot;&gt;Django&lt;/a&gt;, which has an extension called &amp;quot;&lt;a href=&quot;https://django-reversion.readthedocs.io/en/stable/&quot;&gt;reversion&lt;/a&gt;&amp;quot;. It allows reverting objects in your database to a previous revision. Pretty clever name, but it burnt into my mind so much, I accidentally mixed up the two.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.2&quot; href=&quot;#fnr.2&quot; class=&quot;footnum&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
I immediately went gung-ho and put both &lt;code&gt;harec&lt;/code&gt; and &lt;code&gt;qbe&lt;/code&gt; (which doesn't even cause a dependency issue) into &lt;code&gt;hare-build-system&lt;/code&gt;.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
In hindsight, while I can justify my actions (my reasoning was that &lt;code&gt;harec&lt;/code&gt; and &lt;code&gt;qbe&lt;/code&gt; are used by the compiler, therefore it makes sense to have them present in the build system), it wasn't the right course of action.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
Especially because this issue only really came up with a single package. Just like with any other project a big blast radius has to make sense. In this case, it didn't.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.3&quot; href=&quot;#fnr.3&quot; class=&quot;footnum&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Obviously the best would be if the &lt;code&gt;guix lint&lt;/code&gt; script was able to identify and fix these, but let's be real, making a script to rewrite an arbitrary input to something that conforms to a given output is a fool's errand.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.4&quot; href=&quot;#fnr.4&quot; class=&quot;footnum&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
In the strictest sense there is already one or rather two. There is &lt;a href=&quot;https://libreplanet.org/wiki/Group:Guix&quot;&gt;Libreplanet&lt;/a&gt;, which has a Guix page, but it's pretty much only used by direct maintainers for storing information that doesn't directly belong in the Guix Manual. For the sake of completeness, there is also the &lt;a href=&quot;https://wiki.systemcrafters.net/guix/&quot;&gt;System Crafters&lt;/a&gt; Wiki, which at the time of writing hasn't seen any contributions in two years.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.5&quot; href=&quot;#fnr.5&quot; class=&quot;footnum&quot;&gt;5&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Before you ask, my toilet isn't made from gold. I bought my sticks before the price increase for a third of what they cost now… I'm not rich, just lucky.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.6&quot; href=&quot;#fnr.6&quot; class=&quot;footnum&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
No disrespect towards Slackware or its users. It's a very cool distribution and the fact that it is still going strong is nothing short of impressive. But it's &amp;quot;do it yourself&amp;quot; approach is exactly the opposite of what I'm looking for in a declarative distribution. In an ideal world, your entire setup would be a couple files of Scheme code and you'd practically never have to &amp;quot;figure things out&amp;quot;.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.7&quot; href=&quot;#fnr.7&quot; class=&quot;footnum&quot;&gt;7&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
The fix was ultimately merged after six days and I have since removed the inferior from my install.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.8&quot; href=&quot;#fnr.8&quot; class=&quot;footnum&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
In Nix, you're expected to write Bash snippets in your build stages with some rudimentary substitution of variables provided by the system. This works, but is (in my opinion) far less ergonomic or elegant than Guix's all or nothing approach to Scheme.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;/div&gt;</content></entry><entry><title>LLMs Are Taking the Joy Out of Our Lives</title><id>https://nemin.hu/llm.html</id><author><name>Nemin</name></author><updated>2026-02-11T16:50:00Z</updated><link href="https://nemin.hu/llm.html" rel="alternate" /><content type="html">&lt;div role=&quot;doc-toc&quot; id=&quot;table-of-contents&quot;&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;div role=&quot;doc-toc&quot; id=&quot;text-table-of-contents&quot;&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#job&quot;&gt;1. Job&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#hype-driven-death-march&quot;&gt;2. Hype-driven death march&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#art-robbery&quot;&gt;3. Art-robbery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#post-truth-society&quot;&gt;4. Post-truth society&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#we-re-burning-our-planet-for-this&quot;&gt;5. We're burning our planet for… this?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#i-m-not-fully-innocent-but-at-least-i-care&quot;&gt;6. I'm not fully innocent, but at least I care&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
This website does not, will not, and has not willingly used AI for its creation. I write every word myself and rely on traditional methods to publish it to the internet.
&lt;/p&gt;

&lt;p&gt;
That means I correct my spelling using &lt;a href=&quot;https://github.com/hunspell/hunspell&quot;&gt;Hunspell&lt;/a&gt; in &lt;a href=&quot;https://www.gnu.org/software/emacs/&quot;&gt;Emacs&lt;/a&gt; with its convenient &lt;code&gt;flyspell-mode&lt;/code&gt; checker. I do not generate pictures for my articles, I either create them myself or take them from the Internet with proper credits.
&lt;/p&gt;

&lt;p&gt;
Obviously, I use tools like LSPs and linters gratuitously, but I write the code I present in my articles by hand. I don't even look at AI output.
&lt;/p&gt;

&lt;p&gt;
If I don't know something, I go on the internet and search. If that doesn't work, I go to a community and ask. You'd be surprised how kind and helpful people are, if you show them a modicum of politeness.
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;Note:&lt;/b&gt; This is a largely unedited rant. You've been warned.
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div id=&quot;outline-container-job&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;job&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;1.&lt;/span&gt; Job&lt;/h2&gt;
&lt;div id=&quot;text-1&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
&lt;b&gt;I don't like AI.&lt;/b&gt; Not because I feel like it's endangering my job—even though for many others, including people I hold dearly—it is a very real possibility. I am embedded nicely into my team and my manager is level-headed enough to understand you cannot pass off our job to a machine and expect things not to collapse eventually.
&lt;/p&gt;

&lt;p&gt;
I don't like it, because the code produced using LLMs are at best laughably bad tangles of over-complicated boilerplate and cookie-cutter repetition of code that could very easily be DRY-d. At worst they generate a simulacrum of what a person might write, causing your intuition (trained on how real people think and what real people write, and which would otherwise help you catch bugs and issues) to become unreliable.
&lt;/p&gt;

&lt;p&gt;
And with how much less effort it takes for an AI to spit out a thousand lines, than it takes a person to review said thousand lines, we have no chance of inspect code with nearly as much care and scrutiny as before.
&lt;/p&gt;

&lt;p&gt;
Projects, both professional and hobby-driven, are suffocating under vibe-code pushed onto them both by people meaning well and people intending to profit. Because of the aforementioned ease of generating more code and how convincingly AI can masquerade as a person, the ever-increasing load of dealing with the crap-deluge is added atop the already thankless workload of these contributors, wasting valuable hours and manpower. &lt;a href=&quot;https://github.com/curl/curl/pull/20312&quot;&gt;cURL&lt;/a&gt; may have been the first, but it sure as hell won't be the last.
&lt;/p&gt;

&lt;p&gt;
The solution? &lt;i&gt;Just use AI to filter out the AI!&lt;/i&gt; This is untenable. Models made specifically to suss out LLMs written text &lt;a href=&quot;https://www.scientificamerican.com/article/chatgpt-detector-catches-ai-generated-papers-with-unprecedented-accuracy/#:~:text=By%20contrast%2C%20the%20AI%20detector%20ZeroGPT%20identified%20AI%2Dwritten%20introductions%20with%20an%20accuracy%20of%20only%20about%2035%E2%80%9365%25%2C&quot;&gt;aren't at all reliable&lt;/a&gt;. With code you have even less to rely on, because professional code generally doesn't have &amp;quot;character&amp;quot;.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.1&quot; href=&quot;#fn.1&quot; class=&quot;footref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
So we're left with rudimentary tools, like:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&amp;quot;All PRs above X thousand lines are blanket banned&amp;quot;,&lt;/li&gt;
&lt;li&gt;&amp;quot;PRs committed by an LLM are blanket banned&amp;quot;,&lt;/li&gt;
&lt;li&gt;&amp;quot;PRs where the author, after a lengthy questioning, cannot answer questions in a satisfying way are banned&amp;quot;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
It's an awkward cat-and-mouse game of developers chasing down more and more convincing models and conniving people, while the reliability of code isn't improving at nearly the same rate.
&lt;/p&gt;

&lt;p&gt;
And this is just programming. In many companies CEOs and boards are using AI as an excuse to lay people off:
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
Oh, who needs a new generation to replace the old one? We'll just do it with the AI!
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
And when that old generation ages out and there's nobody to train new people, what will happen then, huh?
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
Oh, but AI won't make everyone jobless, you'll just be able to do 20 people's job alone!
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
First, that's bullshit. Second, you can be grateful it's bullshit, because the job market is already terrible. Imagine if suddenly there were 20x less jobs. It'd be catastrophic.
&lt;/p&gt;

&lt;p&gt;
A friend of mine studied visual arts. After many years in university, he entered a job market, that no longer had a need for him. He wasn't a &amp;quot;pro&amp;quot; (despite being skilled), so he had no foot in the door. And he couldn't become a &amp;quot;pro&amp;quot;, because all the jobs he tried to apply to declined, saying they'll just use GenAI to make stuff! How do you build a portfolio that way?
&lt;/p&gt;

&lt;p&gt;
He had spent a couple years living off odd jobs and now is re-educating himself as a programmer. While I'm glad he found something else that also interests him, I think it's still tragic he wasn't able to get work doing his dream job. Potential greatness dying at the feet of &amp;quot;eh, good enough.&amp;quot;
&lt;/p&gt;

&lt;p&gt;
Or there is that funny story where a business owner didn't bother to hire customer support. Their AI &lt;a href=&quot;https://reddit.com/r/LegalAdviceUK/comments/1qxc7x9/an_ai_chatassist_created_and_offered_a_customer/&quot;&gt;offered a customer an 80% discount&lt;/a&gt;. In this instance, the owner was extremely lucky, because they weren't obligated to fulfil the order, but imagine if they were. We might intuitively think a chatbot's responses aren't legally binding, but why not? If companies think they can offload customer complaints to these bots, surely they are created to offer only company-sanctioned information.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-hype-driven-death-march&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;hype-driven-death-march&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;2.&lt;/span&gt; Hype-driven death march&lt;/h2&gt;
&lt;div id=&quot;text-2&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
&lt;b&gt;I don't like AI.&lt;/b&gt; Not because I'm a Luddite&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.2&quot; href=&quot;#fn.2&quot; class=&quot;footref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, I'm not. For most of my childhood and early adult years, the march of technology was something I beheld in awe.
&lt;/p&gt;

&lt;p&gt;
I was born right before smartphones as we know them today were a thing. I saw mobiles go from bricks to hand-held supercomputers. While this too has its own detriments (unchecked social media exposure has made the childhood experience much more stressful than it needs to be), it also brought with itself undeniable benefits.
&lt;/p&gt;

&lt;p&gt;
A minimal smartphone nowadays is affordable to just about anyone, who isn't completely destitute. It won't be fast, won't do most of the stuff a top-of-the-line model can, but it can give you access to a lot of things. Banking, applying to work, handling your communication (both synchronous and asynchronous), entertainment, even security through 2FA/MFA.
&lt;/p&gt;

&lt;p&gt;
Smartphones are indeed so convenient, the idea of life without them is almost unthinkable to many of us. Whether or not that's a good thing is arguable (and you can probably argue towards &amp;quot;it's not&amp;quot; easier), but what nobody can deny is that smartphones are an utter financial and cultural success.
&lt;/p&gt;

&lt;p&gt;
With AI, meanwhile, companies are still figuring out how to even make it profitable. You know, when it's actually AI and not just &lt;a href=&quot;https://en.wikipedia.org/wiki/Builder.ai&quot;&gt;An Indian&lt;/a&gt;. It's not particularly good at coding, you cannot trust important choices on it, because someone has to be responsible for them, it cannot really innovate, just repeat variations. Last year OpenAI &lt;a href=&quot;https://www.theregister.com/2025/10/29/microsoft_earnings_q1_26_openai_loss/&quot;&gt;lost $11.5 billion&lt;/a&gt;. Things are fine, trust us. And all that keeps this bubble of kinda-sorta-works-trust-us cycle alive is &lt;a href=&quot;https://en.wikipedia.org/wiki/AI_bubble#:~:text=money%20by%20mid%2D2027%2E%5B26%5D-,Circular%20investment,-%5Bedit%5D&quot;&gt;five or so companies&lt;/a&gt; slipping money into each others' pockets.
&lt;/p&gt;

&lt;p&gt;
When this pops, and I frankly see no possibility that it won't, everyone will suffer. Maybe it will be another 2008 even. My tech ETFs are 25% up since I bought them a year ago. That kind of growth is unnatural and I dread the day the debt catches up or the investors wisen up to the ploy. I'm sure many of them already have, but it's more profitable to keep up the charade.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-art-robbery&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;art-robbery&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;3.&lt;/span&gt; Art-robbery&lt;/h2&gt;
&lt;div id=&quot;text-3&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
&lt;b&gt;I don't like AI.&lt;/b&gt; Not because I'm good at art. I can barely draw stick figures. For people like me, Generative AI is supposedly a boon and the great equaliser that allows even us to materialise our dreams.
&lt;/p&gt;

&lt;p&gt;
Except all of these models were trained on artists who did not consent to their works being chewed up and regurgitated in a thousand different, yet eerily similar ways, losing all the emotion and originality, that these people poured into each and every piece.
&lt;/p&gt;

&lt;p&gt;
And then there's all the authors, whose books were scraped and fed into LLMs. That resulted in &lt;a href=&quot;https://www.bbc.com/news/articles/c5y4jpg922qo&quot;&gt;a lawsuit or two&lt;/a&gt;, yes, but the damage is already done. You cannot exactly &amp;quot;untrain&amp;quot; a model. You can only put safeguards and rules on it, that clever people will always be able to dodge.
&lt;/p&gt;

&lt;p&gt;
And even if you could selectively remove all this knowledge from them, who in their right mind would? In this rat race, only the smartest, most knowledgeable AI wins, everyone else loses big. Sans comprehensive governmental crackdowns, no company would willingly cripple their own chances. And governments won't crack down on it, because then other, less copyright-concerned countries' AIs will take the lead. That does not make the line go up.
&lt;/p&gt;

&lt;p&gt;
Art is an inherently sentient thing. I originally considered saying it is a &amp;quot;human thing&amp;quot;, but there are instances of animals making art too. The loftiest art-piece down to the raunchiest fetish-art is all made because someone wanted someone else to feel something. The actual art-piece may not appeal, but the sentiment is universally beautiful.
&lt;/p&gt;

&lt;p&gt;
So when you delegate this to a machine, that has no understanding of anything more than &amp;quot;what pixel usually goes next to what other pixels&amp;quot;, everything you make is hollow. It might be very technically impressive (though, it wasn't you who impressed, but the machine), but there is no intent behind it.
&lt;/p&gt;

&lt;p&gt;
I feel nothing, but disgust when I look at AI &amp;quot;art&amp;quot;. It's always the same. Shrimp-Jesus, hyper-sexualised big tiddy anime waifus, diabetes-inducing sugary-cute animals with eyes as big as fists, comically obese people falling through glass bridges, buff gigachads with glowing red eyes telling you that you're not sigma. I will not bring examples, I'm sure you've seen all of these already. Just writing this list down makes me exasperated.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-post-truth-society&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;post-truth-society&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;4.&lt;/span&gt; Post-truth society&lt;/h2&gt;
&lt;div id=&quot;text-4&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
&lt;b&gt;I don't like AI.&lt;/b&gt; Not because I'm not tired of Google giving terrible results or because I enjoy digging into ancient threads &lt;i&gt;that&lt;/i&gt; much. I don't like it, because you can never be quite sure if what you're reading is true or not without putting in your own research and that the LLM will go out of its way to convince you to trust it.
&lt;/p&gt;

&lt;p&gt;
I can begrudgingly accept that chatbots can be a good springboard, if you have no idea how to start researching something. By demanding sources and justifications, you can get by, perhaps even achieve more than if you were left to use Google alone.
&lt;/p&gt;

&lt;p&gt;
However, many people think it's a miracle-machine and trust its output blindly, leading to at best embarrassment, at worst death. AI struggles with the 'r'-s in &amp;quot;strawberry&amp;quot;. LLMs will swear functions that never existed are parts of API-s. People, places, events, just about anything can be hallucinated. Chatbots melt into sobbing messes, if you &lt;a href=&quot;https://futurism.com/chatgpt-haywire-seahorse-emoji&quot;&gt;ask about seahorses&lt;/a&gt;. It &lt;a href=&quot;https://www.acpjournals.org/doi/10.7326/aimcc.2024.1260&quot;&gt;drove a man&lt;/a&gt; to drink Bromine and nearly end his own life.
&lt;/p&gt;

&lt;p&gt;
Hell, forget about &amp;quot;nearly&amp;quot;, there is now a &lt;a href=&quot;https://en.wikipedia.org/wiki/Deaths_linked_to_chatbots&quot;&gt;list on Wikipedia&lt;/a&gt; to count the amount of suicides and deaths attributed to LLMs. At the time of writing &lt;i&gt;fourteen&lt;/i&gt; people could be still alive. AI is not just a tool to kill people, but also to make the living more miserable.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.3&quot; href=&quot;#fn.3&quot; class=&quot;footref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; But trust us, just 50 billion more parameters and it'll be &lt;i&gt;perfect!&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
And even when things don't end so grimly, AI is still a tool for radicalisation. It has never been easier to make up legit sounding hogwash. Claude, give me a 200 page PDF about how my political opponent will bankrupt old people, raises taxes to unprecedented levels, and send off all young men to war. You think that's a ridiculous example, &lt;a href=&quot;https://telex.hu/english/2025/09/08/orban-announces-national-consultation-on-tiszas-supposed-leaked-tax-plans&quot;&gt;it happened&lt;/a&gt; in my country.
&lt;/p&gt;

&lt;p&gt;
Old and/or tech illiterate people not only have no idea if what they're looking at is legit or not, they might not even realize it's a question that needs to be asked. Ten-fifteen years ago something happening on video was either legit or very obviously fake. The few hoaxes that reached the mainstream were memorable exceptions. Today generating stuff that's not outrageously obvious and can easily fool many is an everyday occasion.
&lt;/p&gt;

&lt;p&gt;
Even I get occasionally fooled and that terrifies me, because I'm supposed to be tech savvy. Technology is my livelihood. I've been banging bits together since I was eight years old. And yet every once in a while my judgement lapses and I read a comment or watch a video, and I don't spot the obvious. The whiplash I feel when other comments inform me that I was engaging with slop without knowing is always stinging.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-we-re-burning-our-planet-for-this&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;we-re-burning-our-planet-for-this&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;5.&lt;/span&gt; We're burning our planet for… this?&lt;/h2&gt;
&lt;div id=&quot;text-5&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
&lt;b&gt;I don't like AI.&lt;/b&gt; Not because, it's the sole cause for climate change. It existed before AI. Arguably AI isn't even the biggest contributor, global shipping and the private flights of billionaires probably cause magnitudes more problems than Joe Schmoe asking the magic answering machine if he should invest into NFTs in 2026.
&lt;/p&gt;

&lt;p&gt;
But the fact that we're wasting perfectly good land and, far more importantly, perfectly good water to waste on dystopian giga-complexes for AI loads that haven't even materialised yet, while also shipping off nearly all future RAM and GPUs for these same hypothetical AI loads. Now that both baffles me and infuriates me.
&lt;/p&gt;

&lt;p&gt;
This is pure line-go-up mindset. Tomorrow be damned, today we have to shovel a few extra cents into $NVDA. Meanwhile, from June to August, we can barely go outside for a few hours, because temperatures are more often closer to 40°C than they are to 25°C. When I was a child (and I'm in my mid-twenties at the time of writing this, I'm not exactly old!) winters had snow for weeks. Summers were balmy, but never asphalt-melting hot.
&lt;/p&gt;

&lt;p&gt;
Is providing more means of generating zero-views slop really our first priority? Is that really what we need right now?
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-i-m-not-fully-innocent-but-at-least-i-care&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;i-m-not-fully-innocent-but-at-least-i-care&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;6.&lt;/span&gt; I'm not fully innocent, but at least I care&lt;/h2&gt;
&lt;div id=&quot;text-6&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
&lt;b&gt;I don't like AI.&lt;/b&gt; And yet, strictly at work, I do occasionally use AI-enabled code completion. Or sometimes make the AI generate me boilerplate for unit tests, that practically any templating system could also generate, just perhaps a bit more rigidly. And every couple of months I toy around with the freely available models online and run a few queries out of curiosity. And for that, it's &lt;i&gt;fine.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
I cannot hate people for wanting to use a tool, nor would I want to. It's a shiny toy, with some uses (albeit with caveats). My problem is with this dogged insistence on growth towards some vague faraway goal that seems to shift between AGI, replacing workers, enhancing workers, enhancing processes, replacing processes, etc. ad nauseam. It's gross, it does nothing but generate profits for a very tiny fraction of people out of thin air, while also causing pain on several scales. And it's just plain emotionally and mentally draining.
&lt;/p&gt;

&lt;p&gt;
And for that, as long as I'm able to, I'm keeping AI out of my hobby. Thanks for reading.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;h2 class=&quot;footnotes&quot;&gt;Footnotes: &lt;/h2&gt;
&lt;div id=&quot;text-footnotes&quot;&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.1&quot; href=&quot;#fnr.1&quot; class=&quot;footnum&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Of course, this isn't universally true, there's definitely eccentric and unusual coding styles out there, that AI has no hope of replicating. However, such style is generally frowned upon in professional contexts, because you're not writing for yourself, rather for a team, that might one day contain people, who you'll never meet.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.2&quot; href=&quot;#fnr.2&quot; class=&quot;footnum&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
The actual &lt;a href=&quot;https://en.wikipedia.org/wiki/Luddite#Legacy:~:text=These%20attacks%20on%20machines%20did%20not%20imply%20any%20necessary%20hostility%20to%20machinery%20as%20such%3B%20machinery%20was%20just%20a%20conveniently%20exposed%20target%20against%20which%20an%20attack%20could%20be%20made&quot;&gt;historical Luddites&lt;/a&gt; get a really bad rep. While their methods were destructive, it's hard to seriously blame them. It was never about &amp;quot;uhh, machines &lt;b&gt;bad!&lt;/b&gt;&amp;quot; More &amp;quot;this new technology is rapidly causing mass unemployment and that's endangering tons of livelihoods.&amp;quot;
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
Considering how similar this is to today's situation (though one may wonder if AI truly brings as much benefit to society as the mechanic looms did), maybe I am a bit of a Luddite. At least in spirit, since I cannot exactly bring a hammer against a program.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.3&quot; href=&quot;#fnr.3&quot; class=&quot;footnum&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Yes, this is intentionally mimicking the usual AI slop marker of &amp;quot;It's not just X, it's Y&amp;quot;. If you're gonna think this whole text is AI generated too just because of this, I'm going to send you a very mean stare.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;/div&gt;</content></entry><entry><title>Adventures in Guix Packaging</title><id>https://nemin.hu/guix-packaging.html</id><author><name>Nemin</name></author><updated>2026-02-01T15:58:00Z</updated><link href="https://nemin.hu/guix-packaging.html" rel="alternate" /><content type="html">&lt;div role=&quot;doc-toc&quot; id=&quot;table-of-contents&quot;&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;div role=&quot;doc-toc&quot; id=&quot;text-table-of-contents&quot;&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#introduction&quot;&gt;1. Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#baby-steps&quot;&gt;2. Baby steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#enter-wezterm&quot;&gt;3. Enter WezTerm&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#a-mediocre-first-attempt&quot;&gt;3.1. A mediocre first attempt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#a-brief-second-attempt-at-packaging-locally&quot;&gt;3.2. A brief second attempt at packaging locally&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#third-time-s-the-charm&quot;&gt;4. Third time's the charm&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#setting-things-up&quot;&gt;4.1. Setting things up&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#importing-cargo-crates&quot;&gt;4.2. Importing Cargo crates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#patching-cargo-toml&quot;&gt;4.3. Patching Cargo.toml&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#we-have-liftoff-almost&quot;&gt;4.4. We have liftoff… Almost&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#gpu-maladies&quot;&gt;4.5. GPU maladies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#finding-fonts&quot;&gt;4.6. Finding fonts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#it-s-vulkan-not-vulkan-t&quot;&gt;4.7. It's Vulkan, not Vulkan't&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#a-short-aside-committing-is-hard&quot;&gt;4.8. A short aside: Committing is hard!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#and-yet-some-stuff-is-still-better-left-to-the-professionals&quot;&gt;5. And yet, some stuff is still better left to the Professionals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;6. Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-introduction&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;introduction&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;1.&lt;/span&gt; Introduction&lt;/h2&gt;
&lt;div id=&quot;text-1&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
Having &lt;a href=&quot;./guix.html&quot;&gt;freshly jumped&lt;/a&gt; into Guix System, it didn't take long for me to want to attempt packaging something for the distro.
&lt;/p&gt;

&lt;p&gt;
Unlike the usual, &amp;quot;imperative&amp;quot; distributions, whose build scripts mostly interact with your system directly, Nix and Guix &amp;quot;recipes&amp;quot; provide a set of instructions for the build environment how it needs to compile your package's contents &amp;quot;from zero&amp;quot; and then where to place each of the resulting files.
&lt;/p&gt;

&lt;p&gt;
My first choice was &lt;a href=&quot;https://github.com/ValveSoftware/gamescope/&quot;&gt;Gamescope&lt;/a&gt;, Valve's home-brewed compositor that enabled HDR gaming under Linux.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.1&quot; href=&quot;#fn.1&quot; class=&quot;footref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; However, I quickly realized I bit bigger than I could chew. It took me less than an hour of trying to run into a wall that was (at least at that time) seemingly completely insurmountable.
&lt;/p&gt;

&lt;p&gt;
The errors I received were somewhat vague, the system couldn't find the necessary dependencies, and I really wasn't sure what I was doing. In hindsight, it really wasn't the right approach and had I went about this project with more planning, it may have gone much better. Luckily dropping this thread allowed me to pick up a similarly interesting, but much more fruitful project, which I'd like to showcase to you in this article.
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;TL;DR:&lt;/b&gt; I spent about a week packaging &lt;a href=&quot;https://github.com/wezterm/wezterm&quot;&gt;WezTerm&lt;/a&gt; and learning the ropes of being a Guix contributor along the way.
&lt;/p&gt;

&lt;p&gt;
During the packaging process I stumble many times, only to stand back up and figure out a solution. I also explain some of my complaints about the peculiarities of the process, but also provide plenty of praise about of how much the system tries to enable you to do your job. Finally, I also touch on how positive the experience of the code review was.
&lt;/p&gt;

&lt;p&gt;
If you just want to use WezTerm, do a &lt;code&gt;guix pull&lt;/code&gt; and &lt;code&gt;guix install wezterm&lt;/code&gt;.
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-baby-steps&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;baby-steps&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;2.&lt;/span&gt; Baby steps&lt;/h2&gt;
&lt;div id=&quot;text-2&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
Being somewhat disappointed in how quickly I failed with my initial attempt, I decided to instead start small and work my way up. To do this, I picked one of Gamescope's dependencies, &lt;a href=&quot;https://github.com/ValveSoftware/openvr&quot;&gt;OpenVR&lt;/a&gt;, and &lt;a href=&quot;https://codeberg.org/guix/guix/pulls/5962&quot;&gt;updated its package&lt;/a&gt; to its newest version.
&lt;/p&gt;

&lt;p&gt;
This might sound impressive without context, but in reality it was little more than me just replacing a hash and a version number in a package definition someone else already wrote:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 1: &lt;/span&gt;My massive changes.&lt;/label&gt;&lt;pre class=&quot;src src-diff&quot;&gt;&lt;span class=&quot;org-diff-context&quot;&gt;  diff --git a/gnu/packages/game-development.scm b/gnu/packages/game-development.scm
&lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;index 56f147f956..5cecbf6963 100644
--- &lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;&lt;span class=&quot;org-diff-file-header&quot;&gt;a/gnu/packages/game-development.scm&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;
+++ &lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;&lt;span class=&quot;org-diff-file-header&quot;&gt;b/gnu/packages/game-development.scm&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;org-diff-hunk-header&quot;&gt;@@ -3294,7 +3294,7 @@&lt;/span&gt;&lt;span class=&quot;org-diff-function&quot;&gt; (define-public instead&lt;/span&gt;
&lt;span class=&quot;org-diff-context&quot;&gt; (define-public openvr
   (package
     (name &amp;quot;openvr&amp;quot;)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-removed&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;    (version &amp;quot;&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;&lt;span class=&quot;org-diff-refine-removed&quot;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;&lt;span class=&quot;org-diff-refine-removed&quot;&gt;26&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;&lt;span class=&quot;org-diff-refine-removed&quot;&gt;7&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;&amp;quot;)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;    (version &amp;quot;&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;&lt;span class=&quot;org-diff-refine-added&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;&lt;span class=&quot;org-diff-refine-added&quot;&gt;12&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;&lt;span class=&quot;org-diff-refine-added&quot;&gt;14&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;&amp;quot;)
&lt;/span&gt;&lt;span class=&quot;org-diff-context&quot;&gt;     (home-page &amp;quot;https://github.com/ValveSoftware/openvr/&amp;quot;)
     (source
      (origin
&lt;/span&gt;&lt;span class=&quot;org-diff-hunk-header&quot;&gt;@@ -3304,7 +3304,7 @@&lt;/span&gt;&lt;span class=&quot;org-diff-function&quot;&gt; (define-public openvr&lt;/span&gt;
&lt;span class=&quot;org-diff-context&quot;&gt;              (commit (string-append &amp;quot;v&amp;quot; version))))
        (file-name (git-file-name name version))
        (sha256
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-removed&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;        (base32 &amp;quot;&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;&lt;span class=&quot;org-diff-refine-removed&quot;&gt;09rvrja3pz6ggs41ra71p4dwjl4n&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;&lt;span class=&quot;org-diff-refine-removed&quot;&gt;rpqrqw&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;&lt;span class=&quot;org-diff-refine-removed&quot;&gt;jiy92xl33hhxbsmx&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;&amp;quot;))))
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;        (base32 &amp;quot;0&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;&lt;span class=&quot;org-diff-refine-added&quot;&gt;i85awq7w669j0x091chma&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;&lt;span class=&quot;org-diff-refine-added&quot;&gt;rcx1zqwn1j4v0d42bcjcvhqa6iv0v&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;&amp;quot;))))
&lt;/span&gt;&lt;span class=&quot;org-diff-context&quot;&gt;     (build-system cmake-build-system)
     (arguments
&lt;/span&gt;      ;; No tests.
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Even though the changes I made are trivial, to get here, I had to set up the development environment, I had to set up a fork of the repository, learn about the project's committing rules, how opening PR-s for Guix works, and how I could test my changes locally. (I'm only making a tally here, all of these will be elaborated on in more detail in the rest of the article.)
&lt;/p&gt;

&lt;p&gt;
I'd really encourage anyone interested in how the packaging process works to seek out similar low hanging fruits to get down the basics in an environment where you have basically no chance of failing.
&lt;/p&gt;

&lt;p&gt;
You walk away with knowledge and a basis to build more involved stuff and in turn some members of the community might just get an update to a package they've been waiting for or someone might get inspired to add yet another package that they couldn't until now.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-enter-wezterm&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;enter-wezterm&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;3.&lt;/span&gt; Enter WezTerm&lt;/h2&gt;
&lt;div id=&quot;text-3&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
&lt;a href=&quot;https://wezterm.org/&quot;&gt;WezTerm&lt;/a&gt; is one of the many GPU-accelerated terminal emulators out there, distinguishing itself with a fairly broad Lua-based configuration API and having tabs as a built-in feature among other things.
&lt;/p&gt;

&lt;p&gt;
I admit, I haven't really used or interacted much with it previously. If I remember correctly, I tried it years ago before quickly moving on to other applications for my terminal needs, which begs the question: &lt;i&gt;Why even bother to spend quite a few days on packaging it?&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
Well, I've basically been nerd-sniped into it. While I was still figuring out my own woes with Guix System's installation, I was frequently reading r/Guix in hopes of getting a bit further. And it just so happened that one of the posts on the front page at the time was &lt;a href=&quot;https://reddit.com/r/GUIX/comments/1qni0hn/wezterm/&quot;&gt;a plea&lt;/a&gt; from a prospective Guix user for a native WezTerm package for Guix. What I quickly discovered is that there was none. I couldn't even really find discussions about it. This felt like a &lt;i&gt;call.&lt;/i&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;Note from the future:&lt;/b&gt; Along the post, you'll occasionally see little boxes like this one. While the main body of the article documents my thinking and justifications during development, in these I'll bring attention to parts where I've either did things in a not entirely idiomatic way or where there's a much better / easier solution.
&lt;/p&gt;

&lt;p&gt;
The point of this post is less &amp;quot;How to write a 100% correct package definition that will get immediately merged into Guix&amp;quot; and more &amp;quot;What sort of journey would someone face, who has &lt;i&gt;some&lt;/i&gt; declarative packaging and Scheme knowledge, but is still quite new to the whole topic&amp;quot;.
&lt;/p&gt;

&lt;p&gt;
While the final code is indubitably much better than what I started with, I believe there is more merit in showcasing my iterative process instead of simply going through the merged package, as it doesn't really give any insight into how one would go about packaging something from zero.
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-a-mediocre-first-attempt&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;a-mediocre-first-attempt&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.1.&lt;/span&gt; A mediocre first attempt&lt;/h3&gt;
&lt;div id=&quot;text-3-1&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
To start out, I wanted to try to compile WezTerm in an ad-hoc environment to get an idea what it'd need. I cloned it using &lt;code&gt;git clone https://github.com/wezterm/wezterm&lt;/code&gt; and created a new shell environment using &lt;code&gt;guix shell rust cargo&lt;/code&gt;… Except this didn't actually work, as Guix immediately spit the following error: &lt;code&gt;guix shell: error: cargo: unknown package&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
Oh, right. With a cursory read of the Rust package, it quickly turned out Cargo isn't a separate package, but rather an &lt;i&gt;output&lt;/i&gt; of Rust. Outputs in Guix are basically separate &amp;quot;sub-packages&amp;quot; of packages, that maintainers can use to group related artifacts together, without all of them having to be installed at the same time in case the user doesn't need them.
&lt;/p&gt;

&lt;p&gt;
For instance most packaged libraries include both dynamic variants under &lt;code&gt;out&lt;/code&gt; (which is the default output) and statically-linked ones under &lt;code&gt;static&lt;/code&gt;. With Rust, we have the following options (you can find these using &lt;code&gt;guix search&lt;/code&gt;):
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-nil&quot;&gt;name: rust
version: 1.85.1
outputs:
  + rust-src: [description missing]
  + tools: [description missing]
  + cargo: [description missing]
  + out: everything else
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
For our purposes, we need &lt;code&gt;out&lt;/code&gt;, which contains the Rust compiler &lt;code&gt;rustc&lt;/code&gt;, and we also need &lt;code&gt;cargo&lt;/code&gt;. To instruct the shell to load both, the command we actually need is &lt;code&gt;guix shell rust rust:cargo&lt;/code&gt;. We could have also explicitly used &lt;code&gt;rust:out&lt;/code&gt; for &lt;code&gt;rustc&lt;/code&gt;, but if we leave it out, the shell uses it by default anyway.
&lt;/p&gt;

&lt;p&gt;
Armed with the right toolchain, I issued a naive &lt;code&gt;cargo build&lt;/code&gt; and for a while things seemed to be in order, only to suddenly come to a crashing halt with the following error:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-nil&quot;&gt;error occurred in cc-rs: failed to find tool &amp;quot;cc&amp;quot;:
No such file or directory (os error 2)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
It might come as a surprise, but due to the expectation that most things will be handled using declarative configuration, Guix System doesn't set the &lt;code&gt;CC&lt;/code&gt; environment variable (nor &lt;code&gt;CXX&lt;/code&gt;, though we won't be using that in this project).
&lt;/p&gt;

&lt;p&gt;
In fact, there isn't even any C compiler toolchain installed by default. This causes any programs that expect Autoconf/Makefile-like conventions to not be able to compile stuff. We can really easily solve it by adding &lt;code&gt;gcc-toolchain&lt;/code&gt; to our shell and setting &lt;code&gt;CC&lt;/code&gt; to &lt;code&gt;gcc&lt;/code&gt;:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-bash&quot;&gt;guix shell rust rust:cargo gcc-toolchain
&lt;span class=&quot;org-builtin&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;CC&lt;/span&gt;=gcc
cargo build
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Moments later we experience a new error (which, just like before, I'll abridge by a lot, because it's several pages long):
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-nil&quot;&gt;Could not find openssl via pkg-config:
Could not run `PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 pkg-config --libs --cflags openssl`
The pkg-config command could not be found.
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The issue here is twofold, though both come from the same source. The compiler is looking for OpenSSL using &lt;code&gt;pkg-config&lt;/code&gt;, but our environment doesn't contain either. While we could do what we previously did and exit the shell, add &lt;code&gt;pkg-config&lt;/code&gt; and &lt;code&gt;openssl&lt;/code&gt;, go back, call Cargo, and fail again at the next missing dependency, there is a slightly better way.
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;Note from the future:&lt;/b&gt; Look, I'll be honest, the first time I did this process, I was actually doing this completely &amp;quot;manually&amp;quot; as described above. That is to say, I tried compiling and when it eventually failed with an error about a missing dependency, I just added it to the list, rinse and repeat.
&lt;/p&gt;

&lt;p&gt;
It's a horribly inefficient method and I didn't want to drag this post down by emulating it.
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
Though WezTerm has no Guix package (we're working on it right now!), it does come with a &lt;a href=&quot;https://nixos-and-flakes.thiscute.world/nixos-with-flakes/introduction-to-flakes&quot;&gt;Nix flake&lt;/a&gt; and, though Nix and Guix have diverged quite a bit, it's still very helpful to see the list of packages spelled out for us by someone else:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-nix&quot;&gt;&lt;span class=&quot;org-nix-attribute&quot;&gt;nativeBuildInputs&lt;/span&gt; =
  &lt;span class=&quot;org-nix-keyword&quot;&gt;with&lt;/span&gt; pkgs;
  [
    installShellFiles
    ncurses &lt;span class=&quot;org-comment&quot;&gt;# tic for terminfo
&lt;/span&gt;    pkg-config
    python3
  ]
  ++ lib.optional stdenv.isDarwin perl;

&lt;span class=&quot;org-nix-attribute&quot;&gt;buildInputs&lt;/span&gt; =
  &lt;span class=&quot;org-nix-keyword&quot;&gt;with&lt;/span&gt; pkgs;
  [
    fontconfig
    openssl
    zlib
  ]
  ++ lib.optionals stdenv.isLinux [
    libxkbcommon
    wayland

    xorg.libX11
    xorg.libxcb
    xorg.xcbutil
    xorg.xcbutilimage
    xorg.xcbutilkeysyms
    xorg.xcbutilwm &lt;span class=&quot;org-comment&quot;&gt;# contains xcb-ewmh among others
&lt;/span&gt;  ]
  ++ lib.optionals stdenv.isDarwin ([
    libiconv
  ]);
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Obviously we cannot use this 1:1 as the two systems have different packaging conventions, but we can at least extract a good first approximation of the dependency list:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-bash&quot;&gt;guix shell rust rust:cargo gcc-toolchain pkg-config openssl wayland libx11 &lt;span class=&quot;org-sh-escaped-newline&quot;&gt;\&lt;/span&gt;
     libxcb xcb-util libxkbcommon xcb-util-image freetype fontconfig
&lt;span class=&quot;org-builtin&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;CC&lt;/span&gt;=gcc
cargo build --bin wezterm
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
And, guess what, the application builds. Victory! Or so we think until we actually try to run it using &lt;code&gt;cargo run --bin wezterm&lt;/code&gt;:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-nil&quot;&gt;ERROR  wezterm_gui::frontend &amp;gt; Failed to create window: with_egl_lib failed:
libEGL.so.1: libEGL.so.1: cannot open shared object file: No such file or directory,
libEGL.so: libEGL.so: cannot open shared object file: No such file or directory,
libEGL.so.1: libEGL.so.1: cannot open shared object file: No such file or directory,
libEGL.so: libEGL.so: cannot open shared object file: No such file or directory
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;a id=&quot;egl-first&quot;&gt;&lt;/a&gt;Bummer. WezTerm is a GPU-accelerated terminal emulator, so it (unsurprisingly) needs to communicate with our GPU. Under NVIDIA , this happens with the help of a library called &lt;code&gt;libEGL&lt;/code&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.2&quot; href=&quot;#fn.2&quot; class=&quot;footref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, which our binary cannot locate without some additional help. The reason for this is that Guix isn't using the standard &lt;a href=&quot;https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard&quot;&gt;Linux filesystem hierarchy&lt;/a&gt;, so &lt;code&gt;libEGL&lt;/code&gt; is nowhere to be found on the list of places the library loader checks by default.
&lt;/p&gt;

&lt;p&gt;
The &amp;quot;fix&amp;quot; I used to circumvent this is really ugly, but for a quick attempt it sufficed. Firstly, I needed to figure out where &lt;code&gt;libEGL.so&lt;/code&gt; even is. Thankfully all this took was a simple &lt;code&gt;guix locate libEGL.so&lt;/code&gt; (this command is case-sensitive, I learned it the hard way), which spits out a list such as this:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-bash&quot;&gt;fhs-union-32@0.0     /gnu/store/wr6kq2ixxcnh4nidg64ivsns19k7ynnn-fhs-union-32-0.0/lib/libEGL.so
fhs-union-64@0.0     /gnu/store/9jn31ksjz3frkhwdk5f72s65lrn3gbs7-fhs-union-64-0.0/lib/libEGL.so
mesa@25.2.3          /gnu/store/4kp4rn5vnnaj57464k72wqpg45k45x56-nvda-580.12/lib/libEGL.so
nvda@580.12          /gnu/store/d7vln3cxysi04wf7p8nzwgwabjysznjd-nvda-580.12/lib/libEGL.so
libglvnd@1.7.0       /gnu/store/qhc0y0xzmdmh38bpr9yv2il8alrv5l4g-libglvnd-1.7.0/lib/libEGL.so
mesa@21.3.8          /gnu/store/7rn2s4ip6926sdn8h23qbdyhsr4lj4xk-mesa-21.3.8/lib/libEGL.so
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
As you can see, due to the way Guix works, multiple versions of the library can coexist without conflicting with each other. As I'm running an NVIDIA card, I picked the line for &lt;code&gt;nvda&lt;/code&gt;. Next, I had to set the special environment variable &lt;code&gt;LD_PRELOAD&lt;/code&gt; to this file's path and then call &lt;code&gt;wezterm&lt;/code&gt;. What this does is instruct the dynamic library loader to add the contents of the environment variable to its load path&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.3&quot; href=&quot;#fn.3&quot; class=&quot;footref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; and thus the library is finally visible to the application.
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-bash&quot;&gt;&lt;span class=&quot;org-variable-name&quot;&gt;LD_PRELOAD&lt;/span&gt;=/gnu/store/d7vln3cxysi04wf7p8nzwgwabjysznjd-nvda-580.12/lib/libEGL.so &lt;span class=&quot;org-sh-escaped-newline&quot;&gt;\&lt;/span&gt;
    ./target/debug/wezterm
&lt;/pre&gt;
&lt;/div&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-02-01-guix_packaging/wezterm_manual.avif&quot; alt=&quot;wezterm_manual.avif&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;i&gt;Et voila,&lt;/i&gt; we have WezTerm running. Reddit thread &lt;a href=&quot;https://old.reddit.com/r/GUIX/comments/1qni0hn/wezterm/o2a3ebf/&quot;&gt;solved&lt;/a&gt;. Well, kind of. Our victory is a bit of a hollow one. While we did compel Cargo to build the terminal and it technically works, we have not made a package. Guix isn't aware of WezTerm and anyone else who wants to build the application has to follow these haphazard steps, including overriding a &amp;quot;potentially dangerous&amp;quot; environment variable.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-a-brief-second-attempt-at-packaging-locally&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;a-brief-second-attempt-at-packaging-locally&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.2.&lt;/span&gt; A brief second attempt at packaging locally&lt;/h3&gt;
&lt;div id=&quot;text-3-2&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
To turn our fickle experiment into a proper package, we need to create a build recipe, which we can later integrate into the &lt;a href=&quot;https://codeberg.org/guix/guix&quot;&gt;guix/guix&lt;/a&gt; Codeberg repository. The customary filename for such recipes in Guix is, perhaps unsurprisingly, &lt;code&gt;guix.scm&lt;/code&gt;. Inside this file you're expected to return a &lt;code&gt;(package ...)&lt;/code&gt; form as your final value, which Guix can then process and turn into a derivation&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.4&quot; href=&quot;#fn.4&quot; class=&quot;footref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; for you.
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-scheme&quot;&gt;&lt;span class=&quot;linenr&quot;&gt; 1: &lt;/span&gt;(&lt;span class=&quot;org-keyword&quot;&gt;define-module&lt;/span&gt; (&lt;span class=&quot;org-type&quot;&gt;wezterm&lt;/span&gt;))
&lt;span class=&quot;linenr&quot;&gt; 2: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt; 3: &lt;/span&gt;(&lt;span class=&quot;org-keyword&quot;&gt;use-modules&lt;/span&gt; (guix git)
&lt;span class=&quot;linenr&quot;&gt; 4: &lt;/span&gt;             (guix git-download)
&lt;span class=&quot;linenr&quot;&gt; 5: &lt;/span&gt;             (guix packages)
&lt;span class=&quot;linenr&quot;&gt; 6: &lt;/span&gt;             ((guix licenses) &lt;span class=&quot;org-builtin&quot;&gt;#:prefix&lt;/span&gt; license:)
&lt;span class=&quot;linenr&quot;&gt; 7: &lt;/span&gt;             (guix build-system cargo)
&lt;span class=&quot;linenr&quot;&gt; 8: &lt;/span&gt;             (gnu packages)
&lt;span class=&quot;linenr&quot;&gt; 9: &lt;/span&gt;             (gnu packages commencement)
&lt;span class=&quot;linenr&quot;&gt;10: &lt;/span&gt;             (gnu packages tls)
&lt;span class=&quot;linenr&quot;&gt;11: &lt;/span&gt;             (gnu packages freedesktop)
&lt;span class=&quot;linenr&quot;&gt;12: &lt;/span&gt;             (gnu packages xorg)
&lt;span class=&quot;linenr&quot;&gt;13: &lt;/span&gt;             (gnu packages xdisorg)
&lt;span class=&quot;linenr&quot;&gt;14: &lt;/span&gt;             (gnu packages fontutils)
&lt;span class=&quot;linenr&quot;&gt;15: &lt;/span&gt;             (gnu packages autotools)
&lt;span class=&quot;linenr&quot;&gt;16: &lt;/span&gt;             (gnu packages guile)
&lt;span class=&quot;linenr&quot;&gt;17: &lt;/span&gt;             (gnu packages pkg-config)
&lt;span class=&quot;linenr&quot;&gt;18: &lt;/span&gt;             (gnu packages sdl)
&lt;span class=&quot;linenr&quot;&gt;19: &lt;/span&gt;             (gnu packages texinfo))
&lt;span class=&quot;linenr&quot;&gt;20: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;21: &lt;/span&gt;(package
&lt;span class=&quot;linenr&quot;&gt;22: &lt;/span&gt;  (name &lt;span class=&quot;org-string&quot;&gt;&amp;quot;wezterm&amp;quot;&lt;/span&gt;)
&lt;span class=&quot;linenr&quot;&gt;23: &lt;/span&gt;  (version &lt;span class=&quot;org-string&quot;&gt;&amp;quot;05343b3&amp;quot;&lt;/span&gt;)
&lt;span class=&quot;linenr&quot;&gt;24: &lt;/span&gt;  (home-page &lt;span class=&quot;org-string&quot;&gt;&amp;quot;https://wezterm.org/&amp;quot;&lt;/span&gt;)
&lt;span class=&quot;linenr&quot;&gt;25: &lt;/span&gt;  (synopsis &lt;span class=&quot;org-string&quot;&gt;&amp;quot;TODO&amp;quot;&lt;/span&gt;)
&lt;span class=&quot;linenr&quot;&gt;26: &lt;/span&gt;  (description &lt;span class=&quot;org-string&quot;&gt;&amp;quot;TODO&amp;quot;&lt;/span&gt;)
&lt;span class=&quot;linenr&quot;&gt;27: &lt;/span&gt;  (license license:expat)
&lt;span class=&quot;linenr&quot;&gt;28: &lt;/span&gt;  (source (git-checkout (url &lt;span class=&quot;org-string&quot;&gt;&amp;quot;https://github.com/wezterm/wezterm&amp;quot;&lt;/span&gt;)
&lt;span class=&quot;linenr&quot;&gt;29: &lt;/span&gt;                        (commit version)))
&lt;span class=&quot;linenr&quot;&gt;30: &lt;/span&gt;  (inputs (append (list gcc-toolchain pkg-config openssl wayland
&lt;span class=&quot;linenr&quot;&gt;31: &lt;/span&gt;                        libx11 libxcb xcb-util libxkbcommon xcb-util-image
&lt;span class=&quot;linenr&quot;&gt;32: &lt;/span&gt;                        freetype fontconfig)))
&lt;span class=&quot;linenr&quot;&gt;33: &lt;/span&gt;  (build-system cargo-build-system))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Let's go through our code, section by section. We begin by declaring a module for our package. This is not strictly necessary, but it's common practice with Guile code and (as you'll soon see) it won't really matter in the long run anyway. Next we import a bunch of utilities and packages. A common rule of thumb is that packages under &lt;code&gt;guix&lt;/code&gt; are utilities, while &lt;code&gt;gnu packages&lt;/code&gt; are packages.
&lt;/p&gt;

&lt;p&gt;
We import stuff like methods for importing from a Git repository, definitions of software licenses (more on that below), and Guix's Cargo-based builder preset. The observant might notice that we &lt;b&gt;don't&lt;/b&gt; pull in &lt;code&gt;rust&lt;/code&gt;, &lt;code&gt;rust:cargo&lt;/code&gt;, and &lt;code&gt;gcc-toolchain&lt;/code&gt;. All of these are handled by the preset.
&lt;/p&gt;

&lt;p&gt;
Next comes the main package definition. We declare some static metadata, such as the name of the package, its version number (I'm using the current commit of the repository, as WezTerm hasn't had a release since 2024), the project's website, and we leave the synopsis and description empty for now, as I'd rather first have the package working before wasting time on bookkeeping busywork.
&lt;/p&gt;

&lt;p&gt;
Finally we set the package's license. In Guix (and Nix too), licenses aren't simple text fields, so we can't simply say &lt;code&gt;&amp;quot;MIT&amp;quot;&lt;/code&gt; and move on with our lives. Rather they are objects of their own. This both allows recording any necessary to fully specify the license and also removes a lot of guesswork from the packagers' workflow such as:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&amp;quot;Do I specify 'MIT'? Or 'Expat'? Or 'MIT/Expat'?&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Is the field case sensitive?&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Is it 'GPL-3' or 'GPL3' or maybe 'GPLv3'?&amp;quot;&lt;/li&gt;
&lt;li&gt;And so on and so forth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
To access these license objects, we need to pull in the &lt;code&gt;(guix licenses)&lt;/code&gt; module and specify from it the exact variant we need. In our case this is &lt;code&gt;license:expat&lt;/code&gt;, the MIT license's other name. As you can see, in the imports section I specified &lt;code&gt;license:&lt;/code&gt; as the license module's prefix. This means all the identifiers exported from this module are automatically renamed to contain &lt;code&gt;license:&lt;/code&gt; in the front. This is done to prevent overshadowing unrelated names and prefixing licenses this way is an idiomatic practice in Guix.
&lt;/p&gt;

&lt;p&gt;
This is followed by the actually interesting stuff. First we declare the source of our package's code. This can be many things, an archive downloaded from the Internet, a Git repository like we see in this case, or even a local folder as seen in this &lt;a href=&quot;https://dthompson.us/posts/guix-for-development.html&quot;&gt;excellent tutorial&lt;/a&gt;. We declare our commit to be the same as what we set for our version, i.e. the Git commit with the (short) ID &lt;code&gt;05343b3&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
Next we declare our &amp;quot;inputs&amp;quot;, these are all the packages that we either need to use to build our package or we need at runtime. Note the vagueness of the word &amp;quot;use&amp;quot; here: Inputs may not just be source code dependencies. For instance, as with the manual method, &lt;code&gt;pkg-config&lt;/code&gt; is used to find other sources' locations and won't actually be part of the final executable. But we might also include tools that generate files. Or we might request assets, like fonts. It's best to think of inputs as not just the ingredients, but the entire kitchen supply.
&lt;/p&gt;

&lt;p&gt;
After that comes the build system. Just like our source, this too can be &lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Build-Systems.html&quot;&gt;many things&lt;/a&gt; and it is what primarily decides what our build process is going to look like. Since we're trying to make a Rust package, we use &lt;code&gt;cargo-build-system&lt;/code&gt;, which itself is based on the &lt;code&gt;gnu-build-system&lt;/code&gt; (which is a smarter codification of the good old &lt;code&gt;./configure&lt;/code&gt;, &lt;code&gt;make&lt;/code&gt;, &lt;code&gt;make install&lt;/code&gt; ritual), except with all the GNU-specific build tools replaced with calls to Cargo.
&lt;/p&gt;

&lt;p&gt;
Well, let's give it a shot. Surely, things cannot be much more complex than this.
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 2: &lt;/span&gt;Crash and burn…&lt;/label&gt;&lt;pre class=&quot;src src-nil&quot;&gt;error: failed to get `finl_unicode` as a dependency of package `termwiz v0.24.0 (/tmp/guix-build-wezterm-05343b3.drv-0/source/termwiz)`

Caused by:
  failed to load source for dependency `finl_unicode`

Caused by:
  Unable to update https://github.com/wez/finl_unicode.git?branch=no_std

Caused by:
  can't checkout from 'https://github.com/wez/finl_unicode.git': you are in the offline mode (--offline)
error: in phase 'build': uncaught exception:
%exception #&amp;lt;&amp;amp;invoke-error program: &amp;quot;cargo&amp;quot; arguments: (&amp;quot;build&amp;quot; &amp;quot;--offline&amp;quot; &amp;quot;-j&amp;quot; &amp;quot;24&amp;quot; &amp;quot;--release&amp;quot; &amp;quot;-p&amp;quot; &amp;quot;wezterm-gui&amp;quot; &amp;quot;--features&amp;quot; &amp;quot;distro-defaults&amp;quot;) exit-status: 101 term-signal: #f stop-signal: #f&amp;gt; 
phase `build' failed after 0.0 seconds
command &amp;quot;cargo&amp;quot; &amp;quot;build&amp;quot; &amp;quot;--offline&amp;quot; &amp;quot;-j&amp;quot; &amp;quot;24&amp;quot; &amp;quot;--release&amp;quot; &amp;quot;-p&amp;quot; &amp;quot;wezterm-gui&amp;quot; &amp;quot;--features&amp;quot; &amp;quot;distro-defaults&amp;quot; failed with status 101
build process 18 exited with status 256
builder for `/gnu/store/nllax38xh9v0hcsc3g9vviw5mqnrj5x0-wezterm-05343b3.drv' failed with exit code 1
build of /gnu/store/nllax38xh9v0hcsc3g9vviw5mqnrj5x0-wezterm-05343b3.drv failed
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Things can't be as simple as we'd like, huh?
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-third-time-s-the-charm&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;third-time-s-the-charm&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;4.&lt;/span&gt; Third time's the charm&lt;/h2&gt;
&lt;div id=&quot;text-4&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
The main reason we failed is because Guix is very particular about the way it allows source code to be fetched&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.5&quot; href=&quot;#fn.5&quot; class=&quot;footref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt; and, though Cargo has an ecosystem of its own, Guix wants to have the final say. If packages, such as the previously seen &lt;code&gt;finl_unicode&lt;/code&gt; aren't known by Guix, then even though Cargo could fetch it for us, the build system will refuse to proceed.
&lt;/p&gt;

&lt;p&gt;
&lt;a id=&quot;import-mention&quot;&gt;&lt;/a&gt;The good news is that Guix has built in features to allow mass-importing crates, so we don't have to manually go through every single of WezTerm's dozen crates. The bad news is that, for this to work, you need to be working inside Guix's primary repository or else you're unleashing an unreasonable amount of extra work on yourself. Since our end goal is to upstream the package definition anyway, it really is the right choice to just bite the bullet and do it properly.
&lt;/p&gt;

&lt;p&gt;
So, back to &lt;i&gt;tabula rasa&lt;/i&gt;, let's create a package inside the actual Guix package repository.
&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-setting-things-up&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;setting-things-up&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;4.1.&lt;/span&gt; Setting things up&lt;/h3&gt;
&lt;div id=&quot;text-4-1&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
I'm going to assume at least a bit of Git knowledge, as this post will be long enough as-is without starting from the very basics. Due to my previous tiny contribution with OpenVR, I already had a fork repository of &lt;a href=&quot;https://codeberg.org/guix/guix&quot;&gt;guix/guix&lt;/a&gt;, so that's one hurdle down. Next, we do the usual, update the &lt;code&gt;master&lt;/code&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.6&quot; href=&quot;#fn.6&quot; class=&quot;footref&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; branch, create a new branch, switch to it, then prepare our working environment:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-bash&quot;&gt;git pull
git switch --create add-wezterm
guix shell -D guix -CPWN
./bootstrap  &lt;span class=&quot;org-comment-delimiter&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;runs autoreconf, creates configure
&lt;/span&gt;./configure  &lt;span class=&quot;org-comment-delimiter&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;creates Makefile
&lt;/span&gt;make
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The first two &lt;code&gt;git&lt;/code&gt; commands are fairly trivial, but the third command is worth talking about a little. The interesting thing about Guix development is that it happens 90% inside Guix itself! Isn't that cool? By issuing &lt;code&gt;guix shell -D guix -CPWN&lt;/code&gt;, we are:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;code&gt;-C&lt;/code&gt;: are transported into a container, a fully isolated environment, in which only the source tree is available,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-D guix&lt;/code&gt;: with the development inputs of the &lt;code&gt;guix&lt;/code&gt; pacakge included,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-P&lt;/code&gt;: with the profile initialized to the source tree's environment,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-W&lt;/code&gt;: with the &lt;code&gt;guix&lt;/code&gt; executable available,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-N&lt;/code&gt;: and finally we request network access.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
But, before we can throw ourselves into writing our package with wild abandon, we have one more preliminary thing to take care. We need to set up our development environment by first running &lt;code&gt;./bootstrap&lt;/code&gt;, followed by &lt;code&gt;./configure&lt;/code&gt;. These two commands generate the necessary Makefiles and are a one time cost (unless you're nuking your entire repo checkout and recreating it from scratch, you don't really have to call these ever again). For more info about this part of the process, please see the &lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Building-from-Git.html&quot;&gt;appropriate manual chapter&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
Then, by issuing &lt;code&gt;make&lt;/code&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.7&quot; href=&quot;#fn.7&quot; class=&quot;footref&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;, the development environment does several things at once:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;It &lt;a href=&quot;https://www.gnu.org/software/guile/manual/html_node/Compilation.html&quot;&gt;compiles&lt;/a&gt; all existing Scheme files into &lt;code&gt;.go&lt;/code&gt; files, which aren't &lt;a href=&quot;https://go.dev/&quot;&gt;Golang&lt;/a&gt; source files, but rather Guile byte-code. This speeds up execution as instead of interpreting source code, Guile can work based on pre-chewed binary data,&lt;/li&gt;
&lt;li&gt;It compiles all documentation. To our purposes this isn't hugely relevant, but it's a one-time cost, so there's not much point in figuring out elaborate ways to avoid it,&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
Finally, and most importantly, it places a new script into our workspace named &lt;code&gt;pre-inst-env&lt;/code&gt;. The name stands for &amp;quot;[run with] pre-installation environment&amp;quot; and it is the primary driver of our Code-&amp;gt;Compile-&amp;gt;Test loop.
&lt;/p&gt;

&lt;p&gt;
By prepending all our &lt;code&gt;guix&lt;/code&gt; invocations with &lt;code&gt;./pre-inst-env&lt;/code&gt;, we can instruct Guix to use the workspace package registry as its source and thus we can build and test our package without the horribly slow process of manually doing &lt;code&gt;guix pull&lt;/code&gt; on our work directory and &lt;code&gt;guix build &amp;lt;package&amp;gt;&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
What's even better is that this works even outside the container, so we can use all our usual tools while testing or build in the restricted environment to make sure we're not introducing any external dependencies.
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
After a couple minutes of wait, our workspace is finally ready for action. Now we can finally handle the actual Guix stuff. First order of business is figuring out where even our package should go. The Guix repository's folder structure is logical, but it is still occasionally non-trivial to figure out where some packages might be located.
&lt;/p&gt;

&lt;p&gt;
A good rule of thumb is that usually things go into general categories (e.g. &lt;code&gt;emulators.scm&lt;/code&gt;), but if a set of packages only make sense together (an example being &lt;code&gt;django.scm&lt;/code&gt; for all the Python &lt;a href=&quot;https://www.djangoproject.com/&quot;&gt;Django&lt;/a&gt; stuff out there), it can go into a category, and thus, a new file of its own. For us, there are two possible candidates:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://codeberg.org/guix/guix/src/commit/69cc00def4f85724bb6698f2365f661e5a946691/gnu/packages/rust-apps.scm&quot;&gt;rust-apps&lt;/a&gt;, whose purpose I'm still not 100% sure about, since the only thing that connects the stuff inside it seems to be that it's all Rust-based applications,&lt;/li&gt;
&lt;li&gt;and &lt;a href=&quot;https://codeberg.org/guix/guix/src/commit/69cc00def4f85724bb6698f2365f661e5a946691/gnu/packages/terminals.scm&quot;&gt;terminals.scm&lt;/a&gt;, which is for terminal emulators. This is the one I ultimately went with, as it already had &lt;a href=&quot;https://github.com/alacritty/alacritty&quot;&gt;Alacritty&lt;/a&gt; inside, which is also a Rust-based terminal emulator, just like WezTerm.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
So, let's just plop our previous package definition (extended a little bit) into this file and give it a whirl.
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-scheme&quot;&gt;(&lt;span class=&quot;org-keyword&quot;&gt;define-public&lt;/span&gt; &lt;span class=&quot;org-function-name&quot;&gt;wezterm&lt;/span&gt;
  (package
    (name &lt;span class=&quot;org-string&quot;&gt;&amp;quot;wezterm&amp;quot;&lt;/span&gt;)
    (version &lt;span class=&quot;org-string&quot;&gt;&amp;quot;05343b387085842b434d267f91b6b0ec157e4331&amp;quot;&lt;/span&gt;)
    (source
     (origin
       (method git-fetch)
       (file-name &lt;span class=&quot;org-string&quot;&gt;&amp;quot;wezterm&amp;quot;&lt;/span&gt;)
       (uri (git-reference
              (url &lt;span class=&quot;org-string&quot;&gt;&amp;quot;https://github.com/wezterm/wezterm&amp;quot;&lt;/span&gt;)
              (recursive? #t)
              (commit version)))
       (sha256
        (base32 &lt;span class=&quot;org-string&quot;&gt;&amp;quot;0q3f1y3bx3g2k21yzp6wkws6kyxsmk4pscmvd8gqmjbbss8az9ap&amp;quot;&lt;/span&gt;))))
    (native-inputs (list pkg-config))
    (inputs (append (cargo-inputs 'wezterm)
                    (list openssl wayland libx11
                          libxcb xcb-util xcb-imdkit
                          libxkbcommon xcb-util-image
                          freetype fontconfig libssh2
                          libgit2 sqlite `(,zstd &lt;span class=&quot;org-string&quot;&gt;&amp;quot;lib&amp;quot;&lt;/span&gt;)
                          mesa)))
    (build-system cargo-build-system)
    (home-page &lt;span class=&quot;org-string&quot;&gt;&amp;quot;https://wezterm.org/&amp;quot;&lt;/span&gt;)
    (synopsis &lt;span class=&quot;org-string&quot;&gt;&amp;quot;Powerful cross-platform terminal emulator and multiplexer&amp;quot;&lt;/span&gt;)
    (description
     &lt;span class=&quot;org-string&quot;&gt;&amp;quot;A GPU-accelerated cross-platform terminal emulator and
multiplexer written by wez and implemented in Rust.  Features:

@itemize
@item Runs on Linux, macOS, Windows 10, FreeBSD and NetBSD,
@item Multiplex terminal panes, tabs and windows on local and
remote hosts, with native mouse and scrollback,
@item Ligatures, Color Emoji and font fallback, with true color
and dynamic color schemes,
@item Hyperlinks.
@end itemize
&amp;quot;&lt;/span&gt;)
    (license license:expat)))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
There's only really three things of note:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Instead of simply doing a &amp;quot;bare&amp;quot; &lt;code&gt;package&lt;/code&gt; form, we wrap our package definition into a &lt;code&gt;define-public&lt;/code&gt; form. This is how we ensure that multiple package definitions can coexist in a single file.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
Secondly, the package source form was rewritten to use &lt;code&gt;origin&lt;/code&gt; instead, which allows for a lot more flexibility in how we download stuff from the net.
&lt;/p&gt;

&lt;p&gt;
Among other things, it allows us to rewrite parts of the source, delete unnecessary files, create wholly new files, and (as can be seen here) it also allows us to cryptographically check whether the source files we get really are what we expect them to be. Packages meant for the main Guix repo all must have their hash checked, so adding it wasn't merely my frivolousness.
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;Note from the future:&lt;/b&gt; As it turns out, as helpful as this step was during development, it is entirely possible to make WezTerm work without recursive cloning and, in fact, this option is only allowed in extraordinary cases.
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
I also enabled recursive cloning of the repo as there are some submodules involved and once we're past the source-fetching stage, the network is disabled and we wouldn't be able to download them anymore.
&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
Thirdly you may wonder about the weird &lt;code&gt;`(,zstd &amp;quot;lib&amp;quot;)&lt;/code&gt; form in our inputs. As inputs are actual other packages, not strings-based specifications, we can no longer do the previous &amp;quot;package:output&amp;quot; way of referring to alternative outputs.
&lt;/p&gt;

&lt;p&gt;
For that, we need to create a two-element list, where the first is the dependency and the second is the textual name of the output. So, really, this is just the packaging way of saying &lt;code&gt;zstd:lib&lt;/code&gt;.
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-importing-cargo-crates&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;importing-cargo-crates&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;4.2.&lt;/span&gt; Importing Cargo crates&lt;/h3&gt;
&lt;div id=&quot;text-4-2&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
Still, with all this work, if we run this using &lt;code&gt;./pre-inst-env guix build wezterm&lt;/code&gt;, we will be met with the previous error about Cargo not being able to fetch &lt;code&gt;finl_unicode&lt;/code&gt;. Which shouldn't be surprising, since &lt;a href=&quot;#import-mention&quot;&gt;as mentioned&lt;/a&gt; earlier we haven't imported our Cargo dependencies into Guix yet.
&lt;/p&gt;

&lt;p&gt;
To do this, we will need to issue &lt;code&gt;guix import&lt;/code&gt; in our previously checked out WezTerm Git repository in the following way:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-bash&quot;&gt;guix import -i &amp;lt;path to Guix repo&amp;gt;/gnu/packages/rust-crates.scm &lt;span class=&quot;org-sh-escaped-newline&quot;&gt;\&lt;/span&gt;
     crate -f &amp;lt;path to Git repo&amp;gt;/Cargo.lock &lt;span class=&quot;org-sh-escaped-newline&quot;&gt;\&lt;/span&gt;
     wezterm
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
What this does is go through every individual Cargo dependency in the &lt;code&gt;Cargo.lock&lt;/code&gt; file and compares it to the list found in &lt;code&gt;rust-crates.scm&lt;/code&gt;. If a matching entry is found, nothing is done, if not, the importer adds a new entry to the file. If a crate has a matching crates.io page, an entry such as this is generated:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-scheme&quot;&gt;(&lt;span class=&quot;org-keyword&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;org-function-name&quot;&gt;rust-libssh-rs-0.3.6&lt;/span&gt;
  (crate-source &lt;span class=&quot;org-string&quot;&gt;&amp;quot;libssh-rs&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;0.3.6&amp;quot;&lt;/span&gt;
                &lt;span class=&quot;org-string&quot;&gt;&amp;quot;11f6fj59dqpy7n0g74s7vnnyrbpxbrcyxhnrvfnsb5dvsq8f2rih&amp;quot;&lt;/span&gt;))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
If not, however, then we get a slightly bigger variant:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-scheme&quot;&gt;(&lt;span class=&quot;org-keyword&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;org-function-name&quot;&gt;rust-finl-unicode-1.3.0.a1892f2&lt;/span&gt;
  &lt;span class=&quot;org-comment-delimiter&quot;&gt;;; &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;TODO REVIEW: Define standalone package if this is a workspace.
&lt;/span&gt;  (origin
    (method git-fetch)
    (uri (git-reference (url &lt;span class=&quot;org-string&quot;&gt;&amp;quot;https://github.com/wez/finl_unicode.git&amp;quot;&lt;/span&gt;)
                        (commit &lt;span class=&quot;org-string&quot;&gt;&amp;quot;a1892f26245529f2ef3877a9ebd610c96cec07a6&amp;quot;&lt;/span&gt;)))
    (file-name (git-file-name &lt;span class=&quot;org-string&quot;&gt;&amp;quot;rust-finl-unicode&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;1.3.0.a1892f2&amp;quot;&lt;/span&gt;))
    (sha256 (base32 &lt;span class=&quot;org-string&quot;&gt;&amp;quot;0g9lqwrzm7ca54vlq8sgix3wvbsxwp7glkx3dzjdd591grfbmi6z&amp;quot;&lt;/span&gt;))))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
It bears resemblance to our own package's &lt;code&gt;origin&lt;/code&gt; field  and it also has a foreboding comment about how we need to review this generated package to check if it's a &amp;quot;workspace&amp;quot;. Elaborating on &lt;a href=&quot;https://doc.rust-lang.org/cargo/reference/workspaces.html&quot;&gt;what a workspace is&lt;/a&gt; in Rust is beyond the scope of this article, but the long story short is that a single Cargo project may contain multiple, related subprojects and the importer is not able to untangle these by itself.
&lt;/p&gt;

&lt;p&gt;
If you're packaging something that depends on such a project, you must manually make sure every single subproject gets its own definition, which call back to the main workspace. &lt;code&gt;finl_unicode&lt;/code&gt; isn't such a project, however, so we can safely ignore the warning for now.
&lt;/p&gt;

&lt;p&gt;
Where we cannot ignore the comment, however, is with the bindings for OpenSSL, &lt;code&gt;rust-openssl-sys-0.9.111&lt;/code&gt;. If we were to leave things as-is, we'd soon find that our project does not build due to not finding &lt;code&gt;openssl-src&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
Thankfully, we don't have to figure it out how to fix this issue ourselves, as the package already has several other versions in the Guix repo. We simply have to copy someone else's solution and we're one step closer to making WezTerm build:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-diff&quot;&gt;&lt;span class=&quot;org-diff-header&quot;&gt;diff --git a/gnu/packages/rust-crates.scm b/gnu/packages/rust-crates.scm
index 74fba63cd8..213882b4a2 100644
--- &lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;&lt;span class=&quot;org-diff-file-header&quot;&gt;a/gnu/packages/rust-crates.scm&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;
+++ &lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;&lt;span class=&quot;org-diff-file-header&quot;&gt;b/gnu/packages/rust-crates.scm&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;org-diff-hunk-header&quot;&gt;@@ -13605,9 +13605,19 @@&lt;/span&gt;&lt;span class=&quot;org-diff-function&quot;&gt; (define rust-openssl-sys-0.9.110&lt;/span&gt;
&lt;span class=&quot;org-diff-context&quot;&gt;                     (copy-file &amp;quot;Cargo.toml.orig&amp;quot; &amp;quot;Cargo.toml&amp;quot;))))

 (define rust-openssl-sys-0.9.111
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-removed&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;  ;; TODO REVIEW: Check bundled sources.
&lt;/span&gt;&lt;span class=&quot;org-diff-context&quot;&gt;   (crate-source &amp;quot;openssl-sys&amp;quot; &amp;quot;0.9.111&amp;quot;
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-removed&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;                &amp;quot;08f3mpsabivfi3fd0qv9231qidqy68lr8a4qi32y6xda43av5jl2&amp;quot;))
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;               &amp;quot;08f3mpsabivfi3fd0qv9231qidqy68lr8a4qi32y6xda43av5jl2&amp;quot;
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;               #:snippet
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;               #~(begin
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                   ;; Remove dependency on boringssl and vendor openssl source.
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                   (substitute* &amp;quot;Cargo.toml.orig&amp;quot;
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                     ((&amp;quot;vendored = .*&amp;quot;) &amp;quot;vendored = []\n&amp;quot;)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                     ((&amp;quot;.*bssl.*&amp;quot;) &amp;quot;&amp;quot;)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                     ((&amp;quot;.*openssl-src.*&amp;quot;) &amp;quot;&amp;quot;)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                     ;; Allow any version of bindgen.
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                     ((&amp;quot;(bindgen = \\{ version =) \&amp;quot;.*\&amp;quot;,&amp;quot; _ bindgen)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                      (string-append bindgen &amp;quot;\&amp;quot;*\&amp;quot;,&amp;quot;)))
&lt;/span&gt;+                   (copy-file &amp;quot;Cargo.toml.orig&amp;quot; &amp;quot;Cargo.toml&amp;quot;))))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Once the importer has gone through every dependency, it also generates a lookup table entry in &lt;code&gt;rust-crates.scm&lt;/code&gt; in the form of &lt;code&gt;package-name =&amp;gt; (list &amp;lt;list-of-dependencies&amp;gt;)&lt;/code&gt;. This is ultimately the mechanism that pairs user defined Rust Guix packages to their Cargo defined dependencies. For instance, since we entered &lt;code&gt;wezterm&lt;/code&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.8&quot; href=&quot;#fn.8&quot; class=&quot;footref&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; in our &lt;code&gt;cargo import&lt;/code&gt; query, we get the following list:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-scheme&quot;&gt;(wezterm =&amp;gt;
       (list rust-addr2line-0.25.1
        rust-adler-1.0.2
        rust-adler2-2.0.1
        rust-adler32-1.2.0
        rust-ahash-0.8.12
        rust-aho-corasick-1.1.4
        rust-aligned-vec-0.6.4
        ... and like 600 more crates ...
        ))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
And somewhere in this list, &lt;code&gt;finl_unicode&lt;/code&gt; can also be found. Finally, we are able to provide all our dependencies to the build system:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 3: &lt;/span&gt;The build system has access to all the crates that we automagically imported from the Cargo lockfile.&lt;/label&gt;&lt;pre class=&quot;src src-scheme&quot;&gt;&lt;span class=&quot;org-comment-delimiter&quot;&gt;;; &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;cons* takes an arbitrary amount of elements and a list and prepends the former to the latter.
&lt;/span&gt;&lt;span class=&quot;org-comment-delimiter&quot;&gt;;; &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;E.g. (cons* 'a 'b 'c '(d e)) =&amp;gt; (a b c d e)
&lt;/span&gt;(inputs (cons*
         openssl
         wayland
         libx11
         ... our previous list of packages ...
         (cargo-inputs 'wezterm)))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
We issue &lt;code&gt;./pre-inst-env guix build wezterm&lt;/code&gt;… and run into the same error. What gives?
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-patching-cargo-toml&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;patching-cargo-toml&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;4.3.&lt;/span&gt; Patching Cargo.toml&lt;/h3&gt;
&lt;div id=&quot;text-4-3&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
Though the code itself is fairly trivial, understanding the solution to this issue was perhaps the most difficult part of the entire process to me. Largely because there is almost zero indication what and how you need to do, and the docs &lt;a href=&quot;https://guix.gnu.org/cookbook/en/html_node/Cargo-Workspaces-and-Development-Snapshots.html#:~:text=To%20use%20our%20packaged%20development%20snapshots%2C%20it%E2%80%99s%20also%20necessary%20to%20modify%20Cargo%2Etoml%20in%20a%20build%20phase%2C%20with%20a%20package%2Dspecific%20substitution%20pattern%2E&quot;&gt;barely touch upon&lt;/a&gt; the existence of this issue and its fix either. I had to resort to reading other packages' definitions, which worked, but definitely wasn't the smooth-sailing as things had been up to this point.
&lt;/p&gt;

&lt;p&gt;
To finally reveal this hidden menace that has been keeping us from compiling this poor application till now, let's take a look at two different dependencies from WezTerm's &lt;code&gt;Cargo.toml&lt;/code&gt;:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-toml&quot;&gt;&lt;span class=&quot;org-variable-name&quot;&gt;finl_unicode&lt;/span&gt; = { version = &lt;span class=&quot;org-string&quot;&gt;&amp;quot;1.3&amp;quot;&lt;/span&gt;,  git=&lt;span class=&quot;org-string&quot;&gt;&amp;quot;https://github.com/wez/finl_unicode.git&amp;quot;&lt;/span&gt;, branch=&lt;span class=&quot;org-string&quot;&gt;&amp;quot;no_std&amp;quot;&lt;/span&gt; , default-features=&lt;span class=&quot;org-keyword&quot;&gt;false&lt;/span&gt;, features=[&lt;span class=&quot;org-string&quot;&gt;&amp;quot;categories&amp;quot;&lt;/span&gt;, &lt;span class=&quot;org-string&quot;&gt;&amp;quot;grapheme_clusters&amp;quot;&lt;/span&gt;]}
&lt;span class=&quot;org-variable-name&quot;&gt;fixed&lt;/span&gt; = &lt;span class=&quot;org-string&quot;&gt;&amp;quot;1.23&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
For dependencies like &lt;code&gt;fixed&lt;/code&gt;, there is no issue, the build system can match them with their imported package no-problem. However, for some reason that I still don't quite understand, if there is a &lt;code&gt;git&lt;/code&gt; field in the dependency list, then that completely breaks the proces, as Cargo will always try to download it from the internet, which then fails due to the build environment not having network access.
&lt;/p&gt;

&lt;p&gt;
The solution?
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-toml&quot;&gt;&lt;span class=&quot;org-variable-name&quot;&gt;finl_unicode&lt;/span&gt; = { version = &lt;span class=&quot;org-string&quot;&gt;&amp;quot;1.3&amp;quot;&lt;/span&gt;, default-features=&lt;span class=&quot;org-keyword&quot;&gt;false&lt;/span&gt;, features=[&lt;span class=&quot;org-string&quot;&gt;&amp;quot;categories&amp;quot;&lt;/span&gt;, &lt;span class=&quot;org-string&quot;&gt;&amp;quot;grapheme_clusters&amp;quot;&lt;/span&gt;]}
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Yep. That's it. We just have to instruct Guix to rewrite this one line (well, actually two, because there's two such dependencies in the project, but I'll not bore you with the exact same process) and everything else will magically work.
&lt;/p&gt;

&lt;p&gt;
To do this, we introduce a new section to our package definition, &lt;code&gt;arguments&lt;/code&gt;. The role of this field is to override certain aspects of the build system preset, such as the build flags or what so-called &amp;quot;build phases&amp;quot; we wish to execute on the code.
&lt;/p&gt;

&lt;p&gt;
A build phase can be practically anything from setting environment variables, to compiling code, to editing files. We would like to do the last one, so let's introduce a new build phase:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-scheme&quot;&gt;(arguments
 (list
  &lt;span class=&quot;org-builtin&quot;&gt;#:phases&lt;/span&gt;
  #~(modify-phases %standard-phases
      (add-after 'unpack 'use-guix-vendored-dependencies
        (&lt;span class=&quot;org-keyword&quot;&gt;lambda&lt;/span&gt; _
          (substitute* &lt;span class=&quot;org-string&quot;&gt;&amp;quot;Cargo.toml&amp;quot;&lt;/span&gt;
            ((&lt;span class=&quot;org-string&quot;&gt;&amp;quot;,  git.*default-features&amp;quot;&lt;/span&gt;)
             &lt;span class=&quot;org-string&quot;&gt;&amp;quot;, default-features&amp;quot;&lt;/span&gt;)
            ((&lt;span class=&quot;org-string&quot;&gt;&amp;quot;, git.*, rev.*}&amp;quot;&lt;/span&gt;)
             &lt;span class=&quot;org-comment-delimiter&quot;&gt;;; &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;This is added to ensure a different dependency
&lt;/span&gt;             &lt;span class=&quot;org-comment-delimiter&quot;&gt;;; &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;uses libraries provided by the inputs instead
&lt;/span&gt;             &lt;span class=&quot;org-comment-delimiter&quot;&gt;;; &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;of relying on bundled files.
&lt;/span&gt;             &lt;span class=&quot;org-string&quot;&gt;&amp;quot;, features=[\&amp;quot;use-system-lib\&amp;quot;]}&amp;quot;&lt;/span&gt;)))))))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
This code adds a new phase after &lt;code&gt;unpack&lt;/code&gt; (which is the phase responsible for extracting the actually usable files and folders from our &lt;code&gt;origin&lt;/code&gt;) named &lt;code&gt;use-guix-vendored-dependencies&lt;/code&gt;. The name of the phase that we're adding is arbitrary, but you're expected to pick something informative and truthful, so please don't try &lt;code&gt;do-stuff&lt;/code&gt; or &lt;code&gt;thing&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
The magic happens inside &lt;code&gt;substitute*&lt;/code&gt;, which takes a filename and a list of &lt;code&gt;regexp =&amp;gt; replacement&lt;/code&gt; pairs, and then executes the replacements. It's kind of like having a lispy &lt;code&gt;sed&lt;/code&gt; in your arsenal.
&lt;/p&gt;

&lt;p&gt;
&lt;a id=&quot;gexp&quot;&gt;&lt;/a&gt;Careful readers may have noticed the weird &lt;code&gt;#~&lt;/code&gt; symbol in front of the &lt;code&gt;modify-phases&lt;/code&gt; function call. This is a Guix-exclusive extension to Scheme's syntax, called &lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/G_002dExpressions.html&quot;&gt;&lt;i&gt;G-expressions&lt;/i&gt;&lt;/a&gt; (abbreviated as &amp;quot;gexp&amp;quot;).
&lt;/p&gt;

&lt;p&gt;
Their job is to facilitate working with files as data, therefore whenever we need to directly affect our source code (be that creation, deletion, or modification) or call out into the operating system, we need to do so inside a gexp. This isn't the only place where we'll need to do so, so I'll call into attention when it happens.
&lt;/p&gt;

&lt;p&gt;
In more important news, if we were to issue &lt;code&gt;./pre-inst-env guix build wezterm&lt;/code&gt; now, we'd find that the package actually builds… before failing again:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-nil&quot;&gt;error: crates-io is replaced with non-remote-registry source dir /tmp/guix-build-wezterm-05343b387085842b434d267f91b6b0ec157e4331.drv-0/source/guix-vendor;
include `--registry crates-io` to use crates.io
error: in phase 'package': uncaught exception:
%exception #&amp;lt;&amp;amp;invoke-error program: &amp;quot;cargo&amp;quot; arguments: (&amp;quot;package&amp;quot; &amp;quot;--offline&amp;quot; &amp;quot;--no-metadata&amp;quot; &amp;quot;--no-verify&amp;quot;) exit-status: 101 term-signal: #f stop-signal: #f&amp;gt; 
phase `package' failed after 0.1 seconds
command &amp;quot;cargo&amp;quot; &amp;quot;package&amp;quot; &amp;quot;--offline&amp;quot; &amp;quot;--no-metadata&amp;quot; &amp;quot;--no-verify&amp;quot; failed with status 101
build process 18 exited with status 256
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The actual error is a little vague, but the gist is that by default the &lt;code&gt;cargo-build-system&lt;/code&gt; &lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Build-Systems.html#:~:text=Unless%20install%2Dsource%3F%20%23f%20is%20defined%20it%20will%20also%20install%20a%20source%20crate%20repository%20of%20itself%20and%20unpacked%20sources%2C%20to%20ease%20in%20future%20hacking%20on%20Rust%20packages%2E&quot;&gt;attempts&lt;/a&gt; to install the sources of all the dependencies we used. For us, this is neither desirable, nor does it actually allow the build to finish, so by adding the following to our arguments, we disable it (along with tests&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.9&quot; href=&quot;#fn.9&quot; class=&quot;footref&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;):
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-diff&quot;&gt;&lt;span class=&quot;org-diff-header&quot;&gt;diff --git a/gnu/packages/terminals.scm b/gnu/packages/terminals.scm
index 476e2b743e..84f52b24f8 100644
--- &lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;&lt;span class=&quot;org-diff-file-header&quot;&gt;a/gnu/packages/terminals.scm&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;
+++ &lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;&lt;span class=&quot;org-diff-file-header&quot;&gt;b/gnu/packages/terminals.scm&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;org-diff-hunk-header&quot;&gt;@@ -1702,6 +1702,8 @@&lt;/span&gt;&lt;span class=&quot;org-diff-function&quot;&gt; (define-public wezterm&lt;/span&gt;
&lt;span class=&quot;org-diff-context&quot;&gt;       (build-system cargo-build-system)
       (arguments
        (list
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;        #:install-source? #f
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;        #:tests? #f
&lt;/span&gt;&lt;span class=&quot;org-diff-context&quot;&gt;         #:phases
         #~(modify-phases %standard-phases
&lt;/span&gt;             (add-after 'unpack 'use-guix-vendored-dependencies
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Call build again, and…
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-we-have-liftoff-almost&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;we-have-liftoff-almost&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;4.4.&lt;/span&gt; We have liftoff… Almost&lt;/h3&gt;
&lt;div id=&quot;text-4-4&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
No errors. Our build was successful.
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-nil&quot;&gt;successfully built /gnu/store/h8d2igkg4vlg687ixgj6hizyd4gbzl64-wezterm-05343b387085842b434d267f91b6b0ec157e4331.drv
/gnu/store/h99jlsdism6jxn3x02fv7jig61r6ydv2-wezterm-05343b387085842b434d267f91b6b0ec157e4331
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Let's celebrate by starting a shell that contains our newly built terminal in it and start it up:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-bash&quot;&gt;guix shell wezterm -- wezterm
guix shell: error: wezterm: command not found
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Sadly we're still no quite there, but there is only one step separating us from the same spot as we were with the original &amp;quot;ad-hoc&amp;quot; implementation. If we list the files in our built derivation, we would find that it doesn't contain anything useful:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-bash&quot;&gt;/gnu/store/h99jlsdism6jxn3x02fv7jig61r6ydv2-wezterm-05343b387085842b434d267f91b6b0ec157e4331
└── share
    └── doc
        └── wezterm-05343b387085842b434d267f91b6b0ec157e4331
            ├── ANGLE.md
            ├── LICENSE.md
            └── README.md
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
What happened? Simply put, we only told Guix to build our executables, not that we actually &lt;i&gt;need&lt;/i&gt; them. The built package contains several different binaries, of which we're primarily interested in one: &lt;code&gt;wezterm-gui&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
To access it, we will a phase called &lt;code&gt;install&lt;/code&gt; which is supposed to put everything in its rightful place. Let's modify &lt;code&gt;arguments&lt;/code&gt; and override it to install our file:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-diff&quot;&gt;&lt;span class=&quot;org-diff-header&quot;&gt;diff --git a/gnu/packages/terminals.scm b/gnu/packages/terminals.scm
index 84f52b24f8..8f54a79dbe 100644
--- &lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;&lt;span class=&quot;org-diff-file-header&quot;&gt;a/gnu/packages/terminals.scm&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;
+++ &lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;&lt;span class=&quot;org-diff-file-header&quot;&gt;b/gnu/packages/terminals.scm&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;org-diff-hunk-header&quot;&gt;@@ -1712,7 +1712,16 @@&lt;/span&gt;&lt;span class=&quot;org-diff-function&quot;&gt; (define-public wezterm&lt;/span&gt;
&lt;span class=&quot;org-diff-context&quot;&gt;                   ((&amp;quot;,  git.*default-features&amp;quot;)
                    &amp;quot;, default-features&amp;quot;)
                   ((&amp;quot;, git.*, rev.*}&amp;quot;)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-removed&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;                   &amp;quot;, features=[\&amp;quot;use-system-lib\&amp;quot;]}&amp;quot;)))))))
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                   &amp;quot;, features=[\&amp;quot;use-system-lib\&amp;quot;]}&amp;quot;))))
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;          (replace 'install
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;           (lambda* (#:key inputs native-inputs #:allow-other-keys)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;             ;; Binaries
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;             (with-directory-excursion &amp;quot;target/release&amp;quot;
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;               (for-each
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                 (lambda (name) (install-file name (string-append #$output &amp;quot;/bin&amp;quot;)))
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                '(&amp;quot;wezterm&amp;quot; &amp;quot;wezterm-gui&amp;quot;
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                  &amp;quot;wezterm-mux-server&amp;quot;
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                  &amp;quot;strip-ansi-escapes&amp;quot;))))))))
&lt;/span&gt;       (home-page &amp;quot;https://wezterm.org/&amp;quot;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;Note from the future:&lt;/b&gt; Binaries aren't the only thing that need to be installed for a package like this. You also need to copy over stuff like icons, &lt;code&gt;.desktop&lt;/code&gt; files, and any other various assets a package might need.
&lt;/p&gt;

&lt;p&gt;
I abridged the package definition here quite a bit, because much of the process is just more of the same and I see very little educational value in repeating 95% the same code.
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
This snippet of code enters &lt;code&gt;./target/release&lt;/code&gt;, which is the folder where Cargo places our compiled binaries, walks through our supplied list of executable names and copies each to &lt;code&gt;#$output/bin&lt;/code&gt;. In Bash terms, you could think of this as &lt;code&gt;cp ./target/release/wezterm $OUTPUT/bin/&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
You may wonder what &lt;code&gt;#$output&lt;/code&gt; even is. It's actually two separate things combined:
&lt;/p&gt;

&lt;p&gt;
&lt;code&gt;#$&lt;/code&gt; is a so-called &lt;a href=&quot;https://www.gnu.org/software/guile/manual/html_node/Reader-Extensions.html&quot;&gt;reader extension&lt;/a&gt; specific to Guix. Reader extensions are a user-defined extension to the usual syntax extensions, which are in turn special characters that affect the behavior of Lispy languages and are mostly used because spelling out the entire form they shorten would be quite unwieldy.
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 4: &lt;/span&gt;Some examples of common syntax extensions to all Lispy languages.&lt;/label&gt;&lt;pre class=&quot;src src-scheme&quot;&gt;(+ 1 2)         &lt;span class=&quot;org-comment-delimiter&quot;&gt;;; &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;=&amp;gt; 3        (Form is evaluated as usual)
&lt;/span&gt;'(+ 1 2)        &lt;span class=&quot;org-comment-delimiter&quot;&gt;;; &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;=&amp;gt; (+ 1 2)  (Form is &amp;quot;quoted&amp;quot; and returned as-is)
&lt;/span&gt;`(+ 1 2)        &lt;span class=&quot;org-comment-delimiter&quot;&gt;;; &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;=&amp;gt; (+ 1 2)  (Form is &amp;quot;quasiquoted&amp;quot; and returned as-is)
&lt;/span&gt;`(+ 1 ,(+ 1 1)) &lt;span class=&quot;org-comment-delimiter&quot;&gt;;; &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;=&amp;gt; (+ 1 2)  (Form is &amp;quot;quasiquoted&amp;quot;, the third element is &amp;quot;unquoted&amp;quot;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
In our case &lt;code&gt;#$&lt;/code&gt; is a lot like &lt;code&gt;,&lt;/code&gt; (also known as &lt;code&gt;unquote&lt;/code&gt;), which is used in conjunction with &lt;code&gt;`&lt;/code&gt; (&lt;code&gt;quasiquote&lt;/code&gt;). It allows us to &amp;quot;unquote&amp;quot; (i.e. execute) code inside a &lt;a href=&quot;#gexp&quot;&gt;gexp&lt;/a&gt;, which itself acts as sort of a quasiquote (i.e. a form that isn't evaluated by default, only the parts that are explicitly unquoted). For more information about the concept, Guile has an &lt;a href=&quot;https://www.gnu.org/software/guile/manual/html_node/Expression-Syntax.html&quot;&gt;entire page&lt;/a&gt; dedicated to it.
&lt;/p&gt;

&lt;p&gt;
Meanwhile, &lt;code&gt;output&lt;/code&gt; is simply a variable provided implicitly in our install phase, that points to the directory where our derivation will be built into. This folder acts similarly to a filesystem root folder (what you may know as &lt;code&gt;/&lt;/code&gt;), but it only contains files that either we explicitly placed into it or are automatically copied by the build phases.
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-02-01-guix_packaging/symlinks.avif&quot; alt=&quot;symlinks.avif&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
When we install a package, the files from its output are made available in our general environment. You can see this by calling &lt;code&gt;ls /run/current-system/profile/bin&lt;/code&gt;, which will list out most&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.10&quot; href=&quot;#fn.10&quot; class=&quot;footref&quot;&gt;10&lt;/a&gt;&lt;/sup&gt; of the binaries available to you in the current environment. One thing of note is that none of these files here are the actual executables, but rather symlinks to binaries found in the Guix store, which is the organized collection of all built derivations.
&lt;/p&gt;

&lt;p&gt;
Circling back to our package, now that our executables are copied over to the output's bin folder, they will be available to use if we were to install the package. Let's do so:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-bash&quot;&gt;guix shell wezterm
wezterm
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
This time the terminal runs, but then immediately crashes…
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-gpu-maladies&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;gpu-maladies&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;4.5.&lt;/span&gt; GPU maladies&lt;/h3&gt;
&lt;div id=&quot;text-4-5&quot; class=&quot;outline-text-3&quot;&gt;
&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;Note from the future:&lt;/b&gt; The method shown below might be a viable path for some particularly stubborn libraries, however, it is &lt;b&gt;not&lt;/b&gt; a good first approach. As it turns out in this case we can get rid of this entire build phase by simply instructing Cargo to link &lt;code&gt;libEGL&lt;/code&gt; using the &lt;code&gt;RUSTFLAGS&lt;/code&gt; environment variable.
&lt;/p&gt;

&lt;p&gt;
If you're thinking of making your own package, please be sure to check whether you can simply instruct your linker before you attempt to hack apart the code you're working on.
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-bash&quot;&gt;21:59:33.533  ERROR  wezterm_gui::frontend &amp;gt; Failed to create window: with_egl_lib failed:
libEGL.so.1: libEGL.so.1: cannot open shared object file: No such file or directory,
libEGL.so: libEGL.so: cannot open shared object file: No such file or directory,
libEGL.so.1: libEGL.so.1: cannot open shared object file: No such file or directory,
libEGL.so: libEGL.so: cannot open shared object file: No such file or directory
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
You might remember, that we've &lt;a href=&quot;#egl-first&quot;&gt;seen&lt;/a&gt; this error already. This time, however, we'll do it right and instead of relying on a random build string extracted from a command, we'll rely on our build inputs.
&lt;/p&gt;

&lt;p&gt;
To figure out what we're supposed to do, let's &lt;code&gt;grep&lt;/code&gt; for &lt;code&gt;libEGL.so&lt;/code&gt; to find all the places the library is used. There are some irrelevant results, but there is some promising code under &lt;code&gt;./window/src/egl.rs&lt;/code&gt;. The definition of the function named &lt;code&gt;with_egl_lib&lt;/code&gt; reveals how exactly we're trying to load the library:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-rust&quot;&gt;&lt;span class=&quot;org-keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;org-function-name&quot;&gt;with_egl_lib&lt;/span&gt;&amp;lt;&lt;span class=&quot;org-variable-name&quot;&gt;F&lt;/span&gt;: &lt;span class=&quot;org-type&quot;&gt;FnMut&lt;/span&gt;(&lt;span class=&quot;org-type&quot;&gt;EglWrapper&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;org-constant&quot;&gt;anyhow&lt;/span&gt;::&lt;span class=&quot;org-type&quot;&gt;Result&lt;/span&gt;&amp;lt;&lt;span class=&quot;org-type&quot;&gt;Self&lt;/span&gt;&amp;gt;&amp;gt;(
    &lt;span class=&quot;org-keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;func&lt;/span&gt;: &lt;span class=&quot;org-type&quot;&gt;F&lt;/span&gt;,
) -&amp;gt; &lt;span class=&quot;org-constant&quot;&gt;anyhow&lt;/span&gt;::&lt;span class=&quot;org-type&quot;&gt;Result&lt;/span&gt;&amp;lt;&lt;span class=&quot;org-type&quot;&gt;Self&lt;/span&gt;&amp;gt; {
    &lt;span class=&quot;org-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;org-keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;paths&lt;/span&gt;: &lt;span class=&quot;org-type&quot;&gt;Vec&lt;/span&gt;&amp;lt;&lt;span class=&quot;org-constant&quot;&gt;std&lt;/span&gt;::&lt;span class=&quot;org-constant&quot;&gt;path&lt;/span&gt;::&lt;span class=&quot;org-type&quot;&gt;PathBuf&lt;/span&gt;&amp;gt; = &lt;span class=&quot;org-preprocessor&quot;&gt;vec!&lt;/span&gt;[
        &lt;span class=&quot;org-preprocessor&quot;&gt;#[cfg(target_os = &lt;/span&gt;&lt;span class=&quot;org-string&quot;&gt;&amp;quot;windows&amp;quot;&lt;/span&gt;&lt;span class=&quot;org-preprocessor&quot;&gt;)]&lt;/span&gt;
        &lt;span class=&quot;org-string&quot;&gt;&amp;quot;libEGL.dll&amp;quot;&lt;/span&gt;.into(),
        &lt;span class=&quot;org-preprocessor&quot;&gt;#[cfg(target_os = &lt;/span&gt;&lt;span class=&quot;org-string&quot;&gt;&amp;quot;windows&amp;quot;&lt;/span&gt;&lt;span class=&quot;org-preprocessor&quot;&gt;)]&lt;/span&gt;
        &lt;span class=&quot;org-string&quot;&gt;&amp;quot;atioglxx.dll&amp;quot;&lt;/span&gt;.into(),
        &lt;span class=&quot;org-preprocessor&quot;&gt;#[cfg(all(not(target_os = &lt;/span&gt;&lt;span class=&quot;org-string&quot;&gt;&amp;quot;macos&amp;quot;&lt;/span&gt;&lt;span class=&quot;org-preprocessor&quot;&gt;), not(target_os = &lt;/span&gt;&lt;span class=&quot;org-string&quot;&gt;&amp;quot;windows&amp;quot;&lt;/span&gt;&lt;span class=&quot;org-preprocessor&quot;&gt;)))]&lt;/span&gt;
        &lt;span class=&quot;org-string&quot;&gt;&amp;quot;libEGL.so.1&amp;quot;&lt;/span&gt;.into(),
        &lt;span class=&quot;org-preprocessor&quot;&gt;#[cfg(all(not(target_os = &lt;/span&gt;&lt;span class=&quot;org-string&quot;&gt;&amp;quot;macos&amp;quot;&lt;/span&gt;&lt;span class=&quot;org-preprocessor&quot;&gt;), not(target_os = &lt;/span&gt;&lt;span class=&quot;org-string&quot;&gt;&amp;quot;windows&amp;quot;&lt;/span&gt;&lt;span class=&quot;org-preprocessor&quot;&gt;)))]&lt;/span&gt;
        &lt;span class=&quot;org-string&quot;&gt;&amp;quot;libEGL.so&amp;quot;&lt;/span&gt;.into(),
    ];
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
This piece of code tries to simply look for &lt;code&gt;libEGL.so&lt;/code&gt; without any path associated with it. Therefore the dynamic library loader will try all known search places. However, since Guix doesn't put libraries in the normal search paths, this will fail.
&lt;/p&gt;

&lt;p&gt;
So, what if we were to replace this string by an exact path to the library provided by one of our inputs? Turns out that's exactly what we need to do.
&lt;/p&gt;

&lt;p&gt;
As you might have guessed, we need yet another phase:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-diff&quot;&gt;&lt;span class=&quot;org-diff-header&quot;&gt;diff --git a/gnu/packages/terminals.scm b/gnu/packages/terminals.scm
index f7eb633624..34758f238e 100644
--- &lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;&lt;span class=&quot;org-diff-file-header&quot;&gt;a/gnu/packages/terminals.scm&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;
+++ &lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;&lt;span class=&quot;org-diff-file-header&quot;&gt;b/gnu/packages/terminals.scm&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;org-diff-hunk-header&quot;&gt;@@ -1713,6 +1713,10 @@&lt;/span&gt;&lt;span class=&quot;org-diff-function&quot;&gt; (define-public wezterm&lt;/span&gt;
&lt;span class=&quot;org-diff-context&quot;&gt;                    &amp;quot;, default-features&amp;quot;)
                   ((&amp;quot;, git.*, rev.*}&amp;quot;)
                    &amp;quot;, features=[\&amp;quot;use-system-lib\&amp;quot;]}&amp;quot;))))
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;            (add-after 'unpack 'fix-libegl-so
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;              (lambda _
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                (substitute* &amp;quot;./window/src/egl.rs&amp;quot;
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                  ((&amp;quot;libEGL.so&amp;quot;) (string-append #$mesa &amp;quot;/lib/libEGL.so&amp;quot;)))))
&lt;/span&gt;&lt;span class=&quot;org-diff-context&quot;&gt;             (replace 'install
              (lambda* (#:key inputs native-inputs #:allow-other-keys)
&lt;/span&gt;                ;; Binaries
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Let's rebuild and run our package again.
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-02-01-guix_packaging/working.avif&quot; alt=&quot;working.avif&quot; /&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Success! Well, mostly. If we really wanted to, we could stop here. Our package technically &amp;quot;works&amp;quot; and can be installed. However, in its current state, it'd never be accepted into the registry for two main reasons (and a myriad of small ones, see the &lt;a href=&quot;#reject-reasons&quot;&gt;final part&lt;/a&gt; of this story).
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-finding-fonts&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;finding-fonts&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;4.6.&lt;/span&gt; Finding fonts&lt;/h3&gt;
&lt;div id=&quot;text-4-6&quot; class=&quot;outline-text-3&quot;&gt;
&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;Note from the future:&lt;/b&gt; WezTerm actually bundles &lt;a href=&quot;https://github.com/wezterm/wezterm/blob/main/README-DISTRO-MAINTAINER.md#un-bundling-vendored-fonts&quot;&gt;four different fonts&lt;/a&gt;. During the development of the package I only packaged JetBrains Mono and Roboto, because those two are considered vital for the terminal to function.
&lt;/p&gt;

&lt;p&gt;
However, by the time the package was merged, one of the contributors added the other two fonts too and replaced this entire phase with much more elegant code.
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
Of these two big issues, one is that we are still relying on fonts bundled by the source repository. This is a no-go, especially since most of the fonts we wish to use are already packaged in Guix.
&lt;/p&gt;

&lt;p&gt;
By looking into the source files, we find that WezTerm's font-loading code is found in &lt;code&gt;wezterm-font/src/parser.rs&lt;/code&gt;:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-rust&quot;&gt;&lt;span class=&quot;org-doc&quot;&gt;/// In case the user has a broken configuration, or no configuration,
/// we bundle JetBrains Mono and Noto Color Emoji to act as reasonably
/// sane fallback fonts.
/// This function loads those.
&lt;/span&gt;&lt;span class=&quot;org-keyword&quot;&gt;pub&lt;/span&gt;(&lt;span class=&quot;org-keyword&quot;&gt;crate&lt;/span&gt;) &lt;span class=&quot;org-keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;org-function-name&quot;&gt;load_built_in_fonts&lt;/span&gt;(&lt;span class=&quot;org-variable-name&quot;&gt;font_info&lt;/span&gt;: &lt;span class=&quot;org-rust-ampersand&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;org-keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;org-type&quot;&gt;Vec&lt;/span&gt;&amp;lt;&lt;span class=&quot;org-type&quot;&gt;ParsedFont&lt;/span&gt;&amp;gt;) -&amp;gt; &lt;span class=&quot;org-constant&quot;&gt;anyhow&lt;/span&gt;::&lt;span class=&quot;org-type&quot;&gt;Result&lt;/span&gt;&amp;lt;()&amp;gt; {
    &lt;span class=&quot;org-preprocessor&quot;&gt;#[allow(unused_macros)]&lt;/span&gt;
    &lt;span class=&quot;org-preprocessor&quot;&gt;macro_rules!&lt;/span&gt; font {
       (&lt;span class=&quot;org-variable-name&quot;&gt;$font&lt;/span&gt;:literal) =&amp;gt; {
           (&lt;span class=&quot;org-preprocessor&quot;&gt;include_bytes!&lt;/span&gt;($font) &lt;span class=&quot;org-keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;org-rust-ampersand&quot;&gt;&amp;amp;&lt;/span&gt;'&lt;span class=&quot;org-keyword&quot;&gt;static&lt;/span&gt; [&lt;span class=&quot;org-type&quot;&gt;u8&lt;/span&gt;], $font)
       };
   }
   &lt;span class=&quot;org-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;lib&lt;/span&gt; = &lt;span class=&quot;org-keyword&quot;&gt;crate&lt;/span&gt;::&lt;span class=&quot;org-constant&quot;&gt;ftwrap&lt;/span&gt;::&lt;span class=&quot;org-type&quot;&gt;Library&lt;/span&gt;::new()&lt;span class=&quot;org-rust-question-mark&quot;&gt;?&lt;/span&gt;;

   &lt;span class=&quot;org-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;built_ins&lt;/span&gt;: &lt;span class=&quot;org-rust-ampersand&quot;&gt;&amp;amp;&lt;/span&gt;[&lt;span class=&quot;org-rust-ampersand&quot;&gt;&amp;amp;&lt;/span&gt;[(&lt;span class=&quot;org-rust-ampersand&quot;&gt;&amp;amp;&lt;/span&gt;[&lt;span class=&quot;org-type&quot;&gt;u8&lt;/span&gt;], &lt;span class=&quot;org-rust-ampersand&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;org-type&quot;&gt;str&lt;/span&gt;)]] = &lt;span class=&quot;org-rust-ampersand&quot;&gt;&amp;amp;&lt;/span&gt;[
       &lt;span class=&quot;org-preprocessor&quot;&gt;#[cfg(any(test, feature = &lt;/span&gt;&lt;span class=&quot;org-string&quot;&gt;&amp;quot;vendor-jetbrains&amp;quot;&lt;/span&gt;&lt;span class=&quot;org-preprocessor&quot;&gt;))]&lt;/span&gt;
       &lt;span class=&quot;org-rust-ampersand&quot;&gt;&amp;amp;&lt;/span&gt;[
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-BoldItalic.ttf&amp;quot;&lt;/span&gt;),
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-Bold.ttf&amp;quot;&lt;/span&gt;),
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-ExtraBoldItalic.ttf&amp;quot;&lt;/span&gt;),
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-ExtraBold.ttf&amp;quot;&lt;/span&gt;),
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-ExtraLightItalic.ttf&amp;quot;&lt;/span&gt;),
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-ExtraLight.ttf&amp;quot;&lt;/span&gt;),
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-Italic.ttf&amp;quot;&lt;/span&gt;),
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-LightItalic.ttf&amp;quot;&lt;/span&gt;),
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-Light.ttf&amp;quot;&lt;/span&gt;),
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-MediumItalic.ttf&amp;quot;&lt;/span&gt;),
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-Medium.ttf&amp;quot;&lt;/span&gt;),
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-Regular.ttf&amp;quot;&lt;/span&gt;),
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-SemiBoldItalic.ttf&amp;quot;&lt;/span&gt;),
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-SemiBold.ttf&amp;quot;&lt;/span&gt;),
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-ThinItalic.ttf&amp;quot;&lt;/span&gt;),
           &lt;span class=&quot;org-preprocessor&quot;&gt;font!&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;../../assets/fonts/JetBrainsMono-Thin.ttf&amp;quot;&lt;/span&gt;),
       ],
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
As you guessed, we need yet another phase, this time to replace all instances of &lt;code&gt;../../assets/fonts&lt;/code&gt; to the font package's own path:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-diff&quot;&gt;&lt;span class=&quot;org-diff-header&quot;&gt;diff --git a/gnu/packages/terminals.scm b/gnu/packages/terminals.scm
index 13e690e11f..a2a5a71a07 100644
--- &lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;&lt;span class=&quot;org-diff-file-header&quot;&gt;a/gnu/packages/terminals.scm&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;
+++ &lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;&lt;span class=&quot;org-diff-file-header&quot;&gt;b/gnu/packages/terminals.scm&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;org-diff-hunk-header&quot;&gt;@@ -67,6 +67,7 @@&lt;/span&gt;&lt;span class=&quot;org-diff-function&quot;&gt; (define-module (gnu packages terminals)&lt;/span&gt;
&lt;span class=&quot;org-diff-context&quot;&gt;   #:use-module (gnu packages sqlite)
   #:use-module (gnu packages elf)
   #:use-module (gnu packages vulkan)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;  #:use-module (gnu packages fonts)
&lt;/span&gt;&lt;span class=&quot;org-diff-context&quot;&gt;   #:use-module ((guix licenses) #:prefix license:)
   #:use-module (guix build-system cargo)
   #:use-module (guix build-system cmake)
&lt;/span&gt;&lt;span class=&quot;org-diff-hunk-header&quot;&gt;@@ -1702,7 +1703,9 @@&lt;/span&gt;&lt;span class=&quot;org-diff-function&quot;&gt; (define-public wezterm&lt;/span&gt;
&lt;span class=&quot;org-diff-context&quot;&gt;                             patchelf
                             vulkan-loader
                             `(,zstd &amp;quot;lib&amp;quot;)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-removed&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;org-diff-removed&quot;&gt;                            mesa)))
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                            mesa
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;&lt;span class=&quot;org-diff-refine-added&quot;&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;&lt;span class=&quot;org-diff-refine-added&quot;&gt;                            font-jetbrains-mono
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;&lt;span class=&quot;org-diff-refine-added&quot;&gt;+&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;&lt;span class=&quot;org-diff-refine-added&quot;&gt;                            font-google-roboto&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;)))
&lt;/span&gt;&lt;span class=&quot;org-diff-context&quot;&gt;       (build-system cargo-build-system)
       (arguments
        (list
&lt;/span&gt;&lt;span class=&quot;org-diff-hunk-header&quot;&gt;@@ -1721,6 +1724,17 @@&lt;/span&gt;&lt;span class=&quot;org-diff-function&quot;&gt; (define-public wezterm&lt;/span&gt;
&lt;span class=&quot;org-diff-context&quot;&gt;               (lambda _
                 (substitute* &amp;quot;./window/src/egl.rs&amp;quot;
                   ((&amp;quot;libEGL.so&amp;quot;) (string-append #$mesa &amp;quot;/lib/libEGL.so&amp;quot;)))))
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;            (add-after 'unpack 'fix-font-load-path
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;              (lambda* (#:key inputs #:allow-other-keys)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                (substitute* &amp;quot;wezterm-font/src/parser.rs&amp;quot;
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                  ((&amp;quot;../../assets/fonts/JetBrains&amp;quot;)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                   (string-append
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                    #$font-jetbrains-mono
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                    &amp;quot;/share/fonts/truetype/JetBrains&amp;quot;))
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                  ((&amp;quot;../../assets/fonts/Roboto&amp;quot;)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                   (string-append
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                    #$font-google-roboto
&lt;/span&gt;+                    &amp;quot;/share/fonts/truetype/Roboto&amp;quot;)))))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
A rebuild confirms that the terminal still launches as before, only this time it's using fonts from Guix, not the ones bundled in the repository.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-it-s-vulkan-not-vulkan-t&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;it-s-vulkan-not-vulkan-t&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;4.7.&lt;/span&gt; It's Vulkan, not Vulkan't&lt;/h3&gt;
&lt;div id=&quot;text-4-7&quot; class=&quot;outline-text-3&quot;&gt;
&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;Note from the future:&lt;/b&gt; Just like with &lt;code&gt;libEGL&lt;/code&gt;, this isn't the best way to ensure WezTerm (or any other Rust-based package) can see Vulkan and is only shown because this is what I stumbled upon while reading others' code and what any other complete beginner might reasonably reach out for without knowing better.
&lt;/p&gt;

&lt;p&gt;
In this case too, you can simply just instruct the linker, which both helps us get rid of &lt;code&gt;patchelf&lt;/code&gt; and any manual mucking with the load paths and is a lot more idiomatic.
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
While the terminal is functional at this point, if we were to inspect the parent terminal where we started &lt;code&gt;wezterm&lt;/code&gt;, we might notice a suspicious error message:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-bash&quot;&gt;libEGL warning: pci id for fd 20: 10de:2f04, driver (null)

pci id for fd 21: 10de:2f04, driver (null)
kmsro: driver missing
libEGL warning: egl: failed to create dri2 screen
pci id for fd 21: 10de:2f04, driver (null)
kmsro: driver missing
libEGL warning: egl: failed to create dri2 screen
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
This is in fact the other big issue foreshadowed two sections ago. While we have fixed the situation with &lt;code&gt;libEGL&lt;/code&gt;, there is another library we need to really get WezTerm working. This library is &lt;code&gt;libvulkan&lt;/code&gt;, also known as &lt;a href=&quot;https://en.wikipedia.org/wiki/Vulkan&quot;&gt;Vulkan&lt;/a&gt;, OpenGL's successor. WezTerm uses it to enable GPU hardware acceleration and, without it, we're left with an embarrassingly slow CPU-accelerated rendering pipeline, that even stuff as old as the original &lt;code&gt;xterm&lt;/code&gt; beat by magnitudes of speed.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.11&quot; href=&quot;#fn.11&quot; class=&quot;footref&quot;&gt;11&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
However, this time around, unlike with EGL, there is no hard-coded filename to patch, no matter how hard we're looking. So the usual trick of rewriting won't work here. Instead, we are going to add &lt;code&gt;libvulkan.so&lt;/code&gt; to the library loader's search path in another way. Enter &lt;code&gt;patchelf&lt;/code&gt;, a handy tool that can manipulate the &lt;a href=&quot;https://en.wikipedia.org/wiki/Executable_and_Linkable_Format&quot;&gt;ELF header&lt;/a&gt; of a binary.
&lt;/p&gt;

&lt;p&gt;
ELF or &lt;i&gt;&amp;quot;Executable and Loadable Format&amp;quot;&lt;/i&gt; is the Unix solution to storing the metadata of executables. It is a small header that defines (among many other things, this is just a taste) what sort of machine this code is expected to run on (32-bit or 64-bit, x86 or ARM, etc.), what memory location our program code starts at, and most importantly for our purposes, the libraries linked to the executable.
&lt;/p&gt;

&lt;p&gt;
By changing this header table, we are able to slip in a reference to &lt;code&gt;libvulkan.so&lt;/code&gt;, without it ever being mentioned directly in the code:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-diff&quot;&gt;&lt;span class=&quot;org-diff-header&quot;&gt;diff --git a/gnu/packages/terminals.scm b/gnu/packages/terminals.scm
index 34758f238e..13e690e11f 100644
--- &lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;&lt;span class=&quot;org-diff-file-header&quot;&gt;a/gnu/packages/terminals.scm&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;
+++ &lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;&lt;span class=&quot;org-diff-file-header&quot;&gt;b/gnu/packages/terminals.scm&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;org-diff-header&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;org-diff-hunk-header&quot;&gt;@@ -65,6 +65,8 @@&lt;/span&gt;&lt;span class=&quot;org-diff-function&quot;&gt; (define-module (gnu packages terminals)&lt;/span&gt;
&lt;span class=&quot;org-diff-context&quot;&gt;   #:use-module (gnu packages fcitx5)
   #:use-module (gnu packages version-control)
   #:use-module (gnu packages sqlite)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;  #:use-module (gnu packages elf)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;  #:use-module (gnu packages vulkan)
&lt;/span&gt;&lt;span class=&quot;org-diff-context&quot;&gt;   #:use-module ((guix licenses) #:prefix license:)
   #:use-module (guix build-system cargo)
   #:use-module (guix build-system cmake)
&lt;/span&gt;&lt;span class=&quot;org-diff-hunk-header&quot;&gt;@@ -1697,6 +1699,8 @@&lt;/span&gt;&lt;span class=&quot;org-diff-function&quot;&gt; (define-public wezterm&lt;/span&gt;
&lt;span class=&quot;org-diff-context&quot;&gt;                             libssh2
                             libgit2
                             sqlite
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                            patchelf
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                            vulkan-loader
&lt;/span&gt;&lt;span class=&quot;org-diff-context&quot;&gt;                             `(,zstd &amp;quot;lib&amp;quot;)
                             mesa)))
       (build-system cargo-build-system)
&lt;/span&gt;&lt;span class=&quot;org-diff-hunk-header&quot;&gt;@@ -1717,6 +1721,12 @@&lt;/span&gt;&lt;span class=&quot;org-diff-function&quot;&gt; (define-public wezterm&lt;/span&gt;
&lt;span class=&quot;org-diff-context&quot;&gt;               (lambda _
                 (substitute* &amp;quot;./window/src/egl.rs&amp;quot;
                   ((&amp;quot;libEGL.so&amp;quot;) (string-append #$mesa &amp;quot;/lib/libEGL.so&amp;quot;)))))
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;            (add-before 'install 'patch-libvulkan-so
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;              (lambda* (#:key inputs #:allow-other-keys)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                (invoke &amp;quot;patchelf&amp;quot;
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                        &amp;quot;--add-needed&amp;quot;
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                        (string-append #$vulkan-loader &amp;quot;/lib/libvulkan.so&amp;quot;)
&lt;/span&gt;&lt;span class=&quot;org-diff-indicator-added&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;org-diff-added&quot;&gt;                        &amp;quot;./target/release/wezterm-gui&amp;quot;)))
&lt;/span&gt;&lt;span class=&quot;org-diff-context&quot;&gt;             (replace 'install
              (lambda* (#:key inputs native-inputs #:allow-other-keys)
&lt;/span&gt;                ;; Binaries
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;code&gt;invoke&lt;/code&gt;  as the name suggests is Guix's mechanism to call out into a different application during build time. Because we're inside a gexp, our code is ensured to only be actually executed when we're done with the building and just before the installation is attempted. Since we added &lt;code&gt;patchelf&lt;/code&gt; to our inputs, we don't have to worry about finding it, &lt;code&gt;invoke&lt;/code&gt; will sort it out for us.
&lt;/p&gt;

&lt;p&gt;
Another minor, yet important thing to note is that this time we used &lt;code&gt;add-before&lt;/code&gt; instead of &lt;code&gt;add-after&lt;/code&gt;. We don't really care when exactly &lt;code&gt;patchelf&lt;/code&gt; runs, only that it happens before the installation process is finished, as we'd first like to change the ELF header.
&lt;/p&gt;

&lt;p&gt;
With this step done, &lt;code&gt;libvulkan.so&lt;/code&gt; is finally visible to the library loader and the system can start using Vulkan to drive its graphics pipeline:
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-02-01-guix_packaging/vulkan.avif&quot; alt=&quot;vulkan.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 1: &lt;/span&gt;The different GPUs (real or emulated) WezTerm sees, now that Vulkan is enabled.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-a-short-aside-committing-is-hard&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;a-short-aside-committing-is-hard&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;4.8.&lt;/span&gt; A short aside: Committing is hard!&lt;/h3&gt;
&lt;div id=&quot;text-4-8&quot; class=&quot;outline-text-3&quot;&gt;
&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 5: &lt;/span&gt;An example of the &amp;quot;ChangeLog&amp;quot; format.&lt;/label&gt;&lt;pre class=&quot;src src-change-log&quot;&gt;gnu: Add wezterm.

* gnu/packages/terminal.scm: (wezterm): New variable.
* gnu/packages/rust-crates.scm (lookup-cargo-inputs) [&lt;span class=&quot;org-change-log-conditionals&quot;&gt;wezterm&lt;/span&gt;]: New entry.
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
This was a topic I wanted to mention, but couldn't really find a better place for, so I'll just stick it here before the epilogue.
&lt;/p&gt;

&lt;p&gt;
Guix, like most other GNU projects, follows the so-called &amp;quot;&lt;a href=&quot;https://www.gnu.org/prep/standards/html_node/Style-of-Change-Logs.html&quot;&gt;ChangeLog&lt;/a&gt;&amp;quot; style of commit messages. The idea is to have all contributors provide mostly consistent descriptions to their commits, based on a set of rules that makes understanding what exactly changed easier and in a sense &amp;quot;algorithmic&amp;quot;.
&lt;/p&gt;

&lt;p&gt;
You can basically go through a list of possible commit message formats and figure out what the current change fits most. Was it an addition, a modification, a deletion, a fix? After you nailed down the action, you can extract the exact file, section, and sometimes even subsection that was modified. And then there's still the &amp;quot;free form&amp;quot; part of the commit message, that elaborates on exactly what happened.
&lt;/p&gt;

&lt;p&gt;
Thing is, while this sounds excellent on paper, I found it really difficult to properly follow this style. With every commit I was double guessing myself whether what I'm writing is succinct enough and fits the style, or if I'm making subtle errors and just think it's correct because it looks &lt;i&gt;mostly&lt;/i&gt; fine.
&lt;/p&gt;

&lt;p&gt;
Unlike most other things in this article, I have no &amp;quot;and then it all clicked&amp;quot; moment to share here. I basically limped to the finish line, stealing turns of phrases from other contributors and then most of my commit messages disappeared when my submission was overhauled (see below). The rest that remained are such simple messages, that they don't really contain any of the actually iffy stuff, that confused me.
&lt;/p&gt;

&lt;p&gt;
Don't get me wrong, this is still better than total anarchy and I imagine with time you get a &amp;quot;feel&amp;quot; for how to do it right, but it sure as hell didn't inspire much confidence in me while I was working on this project.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-and-yet-some-stuff-is-still-better-left-to-the-professionals&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;and-yet-some-stuff-is-still-better-left-to-the-professionals&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;5.&lt;/span&gt; And yet, some stuff is still better left to the Professionals&lt;/h2&gt;
&lt;div id=&quot;text-5&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
So, at this point we have a pretty well-functioning package. In fact, what you're seeing above is 90% the same as what I've &lt;a href=&quot;https://codeberg.org/guix/guix/pulls/6020&quot;&gt;submitted to guix/guix&lt;/a&gt;, with the high hopes, that the contributors there would accept it and I'd have a proper foot in the ecosystem.
&lt;/p&gt;

&lt;p&gt;
&lt;a id=&quot;reject-reasons&quot;&gt;&lt;/a&gt;The reality is both a little disappointing and, in a sense, very reassuring. As it turned out, my package definition was lacking in quite a few things:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Instead of handling all dependencies correctly, I relied on &lt;code&gt;(recurisve? #t)&lt;/code&gt;, which is considered an &lt;a href=&quot;https://codeberg.org/guix/guix/pulls/6020#issuecomment-10315016&quot;&gt;anti-pattern&lt;/a&gt;. Instead, I should have unbundled all dependencies properly.&lt;/li&gt;
&lt;li&gt;I missed some bundled files that could've been deleted from the source folder, as they're completely unused during the compilation process.&lt;/li&gt;
&lt;li&gt;My &lt;code&gt;install&lt;/code&gt; phase didn't quite install all the files necessary. I accidentally left out stuff like shell completions and integrations.&lt;/li&gt;
&lt;li&gt;I didn't install the &lt;a href=&quot;https://en.wikipedia.org/wiki/Terminfo&quot;&gt;Terminfo&lt;/a&gt; files for WezTerm. This caused a lot of subtle errors with command line applications, that rely on these files to know how to manipulate the state of the terminal.&lt;/li&gt;
&lt;li&gt;The version string I came up with was incorrect both per the Guix rules and &lt;a href=&quot;https://github.com/wezterm/wezterm/blob/main/README-DISTRO-MAINTAINER.md&quot;&gt;WezTerm's own&lt;/a&gt;. The final version conforms to both.&lt;/li&gt;
&lt;li&gt;Turns out WezTerm reads a file called &lt;code&gt;.tag&lt;/code&gt; to figure its own version out. I didn't include this in my initial submission.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
The way I patched &lt;code&gt;libEGL&lt;/code&gt; and &lt;code&gt;libvulkan&lt;/code&gt; are considered unidiomatic and can be replaced with a much simpler snippet, which just instructs Cargo to link these libraries as you'd usually do:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-scheme&quot;&gt;(setenv &lt;span class=&quot;org-string&quot;&gt;&amp;quot;RUSTFLAGS&amp;quot;&lt;/span&gt;
      (string-join
       '(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;-C&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;link-arg=-lEGL&amp;quot;&lt;/span&gt;
         &lt;span class=&quot;org-string&quot;&gt;&amp;quot;-C&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;link-arg=-lvulkan&amp;quot;&lt;/span&gt;)
       &lt;span class=&quot;org-string&quot;&gt;&amp;quot; &amp;quot;&lt;/span&gt;))
&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;The description I came up with was not quite up to snuff either, due to some sensationalized words that I didn't manage to cut.&lt;/li&gt;
&lt;li&gt;And I had quite a few stylistic gaffes that weren't strictly wrong, but could be expressed much better. For a couple of examples:
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;I used &lt;code&gt;string-append&lt;/code&gt; to concatenate folders, even though Guile has a dedicated function for it called &lt;a href=&quot;https://www.gnu.org/software/guile/manual/html_node/File-System.html#:~:text=Scheme%20Procedure:-,in%2Dvicinity,-directory%20file&quot;&gt;in-vicinity&lt;/a&gt;.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.12&quot; href=&quot;#fn.12&quot; class=&quot;footref&quot;&gt;12&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;I kept referencing packages directly in places where &lt;a href=&quot;https://codeberg.org/guix/guix/src/branch/master/guix/build/utils.scm#L686&quot;&gt;search-input-file&lt;/a&gt; would have sufficed.&lt;/li&gt;
&lt;li&gt;I destructured lists using &lt;code&gt;car&lt;/code&gt; and &lt;code&gt;cdr&lt;/code&gt;, when &lt;code&gt;match-lambda&lt;/code&gt; works much nicer for these purposes.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Yet, the story doesn't end on a sour note. I had three different contributors jump in and offer help and feedback, turning my package into one that truly deserves to be in the repository.
&lt;/p&gt;

&lt;p&gt;
Of these three people, I'd like to specifically call out and thank &lt;a href=&quot;https://codeberg.org/hako&quot;&gt;hako&lt;/a&gt;, who also happens to be the person running and developing &amp;quot;&lt;a href=&quot;https://guix.moe&quot;&gt;Guix Moe&lt;/a&gt;&amp;quot;, a project which includes a powerful build farm / mirror, and a hand-crafted, Nonguix-enabled &lt;a href=&quot;https://codeberg.org/hako/Testament&quot;&gt;LiveCD&lt;/a&gt;, which allows for much easier installation of Guix System.
&lt;/p&gt;

&lt;p&gt;
Hako jumped in fairly early after I opened the PR and practically revamped my code from the ground up. Obviously much of the code remains mine, but the remaining extensions and refactors made the whole thing a lot more readable and correct and, without their help, I don't think my package would have made it in or at least not nearly as easily. They also went the extra mile to include all of the recommended fonts for WezTerm, which further fixed some failing test-cases and issues.
&lt;/p&gt;

&lt;p&gt;
Being a software developer, I was already familiar with the experience of facing a code review, so having my PR thrown back for several days didn't feel too daunting. On the contrary, it was actually quite great to see the amount of care put into and attention given to my work. That's the beauty of free software and people acting out of genuine enthusiasm and willingness to help.
&lt;/p&gt;

&lt;p&gt;
I hope my experience might assuage some worries of potential packagers: &lt;b&gt;Your code doesn't have to be perfect!&lt;/b&gt; As long as you're willing to communicate and address change requests, you'll be fine and if things really are above your level, there will be others who will bear that load for you.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-conclusion&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;6.&lt;/span&gt; Conclusion&lt;/h2&gt;
&lt;div id=&quot;text-6&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
So ends about a week of coding around and finding out. As I happen to have &lt;i&gt;some&lt;/i&gt; Nix experience, the process wasn't nearly as harrowing as it might seem from a first glance, but Guix definitely has a couple of rough edges.
&lt;/p&gt;

&lt;p&gt;
One thing that I found a bit frustrating for example was how the manual can occasionally be outdated, causing people (like myself) who aren't &amp;quot;in the know&amp;quot; to commit mistakes that could've been easily avoided otherwise. For instance, &lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Submitting-Patches.html#:~:text=Run%20guix%20style%20package%20to%20format%20the%20new%20package&quot;&gt;it stipulates&lt;/a&gt; that you should use &lt;code&gt;guix style&lt;/code&gt; to provide a consistent styling to your code. I've been following this advice and religiously formatting my code with each commit, only to be &lt;a href=&quot;https://codeberg.org/guix/guix/pulls/6020#issuecomment-10344076&quot;&gt;told by hako&lt;/a&gt;, that I actually shouldn't use the formatter at all, because it cannot cope with complex code.
&lt;/p&gt;

&lt;p&gt;
Still, despite all this and even if Guix isn't quite as powerful from an infrastructure-perspective as, say, Nix, I believe the enthusiasm of its community still carries the experience hard and because of that, I came away from the experience with quite a positive impression while Guix came away with a working WezTerm package.
&lt;/p&gt;

&lt;p&gt;
If you're reading this, you're only a &lt;code&gt;guix pull&lt;/code&gt; away from being able to add &lt;code&gt;wezterm&lt;/code&gt; to your manifest or &lt;code&gt;guix install&lt;/code&gt; it and have it available on your system. If you happen to be a WezTerm user, I hope my package will serve you well.
&lt;/p&gt;

&lt;p&gt;
Thank you for sticking with me in this quite long post! Till next time!
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;h2 class=&quot;footnotes&quot;&gt;Footnotes: &lt;/h2&gt;
&lt;div id=&quot;text-footnotes&quot;&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.1&quot; href=&quot;#fnr.1&quot; class=&quot;footnum&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
I was trying to play &lt;a href=&quot;https://store.steampowered.com/app/2012840/Portal_with_RTX/&quot;&gt;Portal RTX&lt;/a&gt; and I was a bit annoyed that even though both that and KDE support HDR, I still cannot make the two work due to Gamescope not being present on Guix System.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.2&quot; href=&quot;#fnr.2&quot; class=&quot;footnum&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
I'm being intentionally vague here. The actual trip graphical data takes to your monitor is convoluted and varies by several factors including driver, manufacturer, whether you're using Xorg or Wayland, etc. Because of that I'm not very comfortable talking about it in any authoritative way and so I won't. It doesn't really matter in the context of this blogpost anyway.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.3&quot; href=&quot;#fnr.3&quot; class=&quot;footnum&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Specifically &lt;code&gt;LD_PRELOAD&lt;/code&gt; adds libraries to the &lt;i&gt;front&lt;/i&gt; of the search path. This can be used to override libraries which can be used for things like injecting debug messages, using alternative memory allocation, etc.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
It's an extremely powerful tool and a great attack vector if you happen to load in a tainted library. Since we're working with Guix-packaged stuff, we're fine, but it's not something to use without care and good reason.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.4&quot; href=&quot;#fnr.4&quot; class=&quot;footnum&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
In case you're unused to declarative lingo, you can think of a &amp;quot;derivation&amp;quot; as a deterministic result from a build script. Usually this is a software package (like WezTerm's in this particular instance), but the system is flexible enough to be utilized for basically anything that results in files.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
For instance, the Guix home manager allows you to manage your dotfiles in a declarative way. The contents of the derivations built by Guix home are the files that are placed into your home folder including configuration files, scripts, and whatever else you may need.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.5&quot; href=&quot;#fnr.5&quot; class=&quot;footnum&quot;&gt;5&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Specifically what this means is that by the time the build system is engaged, you're only allowed to access data you've specified either in your inputs or the &lt;code&gt;source&lt;/code&gt; of your package. The actual build itself happens inside a sealed off container without even network access. This is both for security reasons (a malicious package has a far smaller attack surface if it cannot talk to the cloud, nor even see your files) and to ensure reproducibility.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
While Guix is primarily known for its FSF/GNU affiliation, one of their major goals is to provide a system that can be bootstrapped from the ground up using nothing more than a tiny binary seed and a long-long chain of programs of increasing complexity. It is an utterly fascinating topic and if you find it as fascinating as I do, I really suggest giving &lt;a href=&quot;https://bootstrappable.org/&quot;&gt;this website&lt;/a&gt; a read.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.6&quot; href=&quot;#fnr.6&quot; class=&quot;footnum&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
It's completely unrelated to the packaging journey in the post, but I found it really interesting, that Guix had a &lt;a href=&quot;https://codeberg.org/guix/guix-consensus-documents/src/branch/main/003-rename-default-branch.md&quot;&gt;consensus vote&lt;/a&gt; about doing away with the &lt;a href=&quot;https://x.com/xpasky/status/1272280760280637441&quot;&gt;recently controversial&lt;/a&gt; &amp;quot;master&amp;quot; name for the base branch.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
It was nice to see the kind of pragmatism and forward thinking involved in this process: The maintainers weighed how much effort it'd take to update everyone from one branch to another, how difficult it'd be to undo, what other possible actions they could take, etc. Ultimately, the vote fell through in its infancy, due to not achieving majority support and master was kept.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
Whether or not you agree with the outcome, I hope you're similarly pleased to see that this project takes its democratic values seriously. If I happen to stick with Guix for a longer while, I hope to one day join the decision making myself, even if only as a minor voice in the choir.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.7&quot; href=&quot;#fnr.7&quot; class=&quot;footnum&quot;&gt;7&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
If you happen to be following along or thinking of writing your own package, please don't forget about the &lt;code&gt;-j&lt;/code&gt; flag. It allows multiple parallel jobs to run and Guix's source is very well decoupled from each other. I have a 24-core Ryzen processor and it's a joy to see how fast &lt;code&gt;make -j24&lt;/code&gt; chews through the build process compared to the utter sluggishness of a single-core run.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.8&quot; href=&quot;#fnr.8&quot; class=&quot;footnum&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
It is worth noting that you have to get the package name right. When I first tried importing my crates, I didn't realize I had to use the final package's name and just entered &lt;code&gt;finl_unicode&lt;/code&gt;, since I assumed I have to provide the name of the crate I wanted to import.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
This ultimately sent me down a painful side-track that led nowhere until I finally read the docs better and realized I actually need to use &lt;code&gt;wezterm&lt;/code&gt;. Learn by my mistake!
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.9&quot; href=&quot;#fnr.9&quot; class=&quot;footnum&quot;&gt;9&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
I'm actually not quite sure what's Guix's policy regarding tests. Some packages have them disabled, some enabled, some selectively disable some tests. In my variant of the package, I disabled them following other packages' example, but in the final variant, that other contributors helped out with, the tests are once again enabled.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.10&quot; href=&quot;#fnr.10&quot; class=&quot;footnum&quot;&gt;10&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
There's also your personal profile's &lt;code&gt;bin&lt;/code&gt; and &lt;code&gt;sbin&lt;/code&gt; folders, &lt;code&gt;current-system&lt;/code&gt;'s &lt;code&gt;sbin&lt;/code&gt; folder, stuff managed by &lt;code&gt;guix home&lt;/code&gt;, etc.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.11&quot; href=&quot;#fnr.11&quot; class=&quot;footnum&quot;&gt;11&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
I'm not 100% sure if Vulkan is needed on AMD hardware. I have a sneaking suspicion that the error is at least partly caused by the fact that on Guix System NVIDIA hardware requires a small &lt;a href=&quot;https://gitlab.com/nonguix/nonguix#:~:text=%25my%2Dos%29-,Application%20setup,-Application%20setup%20involves&quot;&gt;magic incantation&lt;/a&gt; to work with most packages. Still, whether the error is caused by the lack of Vulkan or not doesn't really matter, as the library is still a needed dependency of WezTerm, so adding it wasn't just for kicks.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.12&quot; href=&quot;#fnr.12&quot; class=&quot;footnum&quot;&gt;12&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
In my defense &lt;code&gt;in-vicinity&lt;/code&gt; is a fairly horrible name for a function that joins paths. I mean, yeah, sure, it technically means what it does, but surely &lt;code&gt;path-append&lt;/code&gt; or &lt;code&gt;construct-path&lt;/code&gt; would've worked a lot better.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;/div&gt;</content></entry><entry><title>Guix System First Impressions as a Nix User</title><id>https://nemin.hu/guix.html</id><author><name>Nemin</name></author><updated>2026-01-26T18:09:00Z</updated><link href="https://nemin.hu/guix.html" rel="alternate" /><content type="html">&lt;div role=&quot;doc-toc&quot; id=&quot;table-of-contents&quot;&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;div role=&quot;doc-toc&quot; id=&quot;text-table-of-contents&quot;&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#my-journey-to-guix-system&quot;&gt;1. My Journey to Guix System&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#installer-impressions&quot;&gt;2. Installer Impressions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#i-can-t-find-my-way-land&quot;&gt;3. I Can't Find my Way-land&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#sympathy-for-the-devil&quot;&gt;4. Sympathy for the Devil&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#goals&quot;&gt;5. Goals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#results&quot;&gt;6. Results&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#the-good&quot;&gt;6.1. The Good&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#the-ambiguous&quot;&gt;6.2. The Ambiguous&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#the-bad&quot;&gt;6.3. The Bad&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#overall&quot;&gt;7. Overall&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#notes&quot;&gt;8. Notes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-my-journey-to-guix-system&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;my-journey-to-guix-system&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;1.&lt;/span&gt; My Journey to Guix System&lt;/h2&gt;
&lt;div id=&quot;text-1&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
Feel free to skip this section if you don't really care about backstories. I just figured it makes sense to recap how and why one might start having an interest in declarative distros before tackling the main topic.
&lt;/p&gt;

&lt;p&gt;
I've been a Linux-only&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.1&quot; href=&quot;#fn.1&quot; class=&quot;footref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; user for about ten years now and, like many others, I too embarked on the arduous journey of distro-hopping. I started with &lt;a href=&quot;https://www.linuxmint.com/&quot;&gt;Mint&lt;/a&gt; and when that felt too slow, I switched to &lt;a href=&quot;https://ubuntu.com/&quot;&gt;Ubuntu&lt;/a&gt;. When Ubuntu felt too handholdy&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.2&quot; href=&quot;#fn.2&quot; class=&quot;footref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, I switched to &lt;a href=&quot;https://archlinux.org&quot;&gt;Arch&lt;/a&gt;, which proved to be my main driver for well over five or so years. And when I couldn't resist the Siren's call, I moved on to &lt;a href=&quot;https://gentoo.org&quot;&gt;Gentoo&lt;/a&gt;, thinking surely &amp;quot;harder is better&amp;quot;. Which resulted in severe burnout in a few months, so I capitulated and switched to &lt;a href=&quot;https://fedoraproject.org/&quot;&gt;Fedora&lt;/a&gt;, which was very stable and honestly an all around excellent system. But once more, my interest was piqued, and (before today's adventure) I finally switched to &lt;a href=&quot;https://nixos.org/&quot;&gt;NixOS&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
I've always had a passing interest towards Nix ever since I've first heard about it, but until fairly recently, I always dismissed it as a tool for DevOps guys. The syntax was &lt;i&gt;weird&lt;/i&gt;, the need for reproducible environments seemingly irrelevant, and stuff like the oft-recommended &lt;a href=&quot;https://nixos.org/guides/nix-pills/&quot;&gt;Nix Pills&lt;/a&gt; seemed anything but newbie-friendly.
&lt;/p&gt;

&lt;p&gt;
So then why would someone like me, who's so adamant about not needing Nix eventually choose to go all-in? I guess it was at first less about Nix being better and just the rest being worse.
&lt;/p&gt;

&lt;p&gt;
Of the two big reasons for the switch, one was that I realized that having per-directory environments for your projects is actually a very handy thing to do when you like to toy around with many technologies. I used to generate my &lt;a href=&quot;https://oddwords.hu&quot;&gt;other blog&lt;/a&gt; using &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; and, no matter which distro I used, it was always a pain in the neck to have a good Ruby environment set up. &lt;code&gt;bundler install&lt;/code&gt; didn't really want to work without privileges and I wasn't really a fan of unleashing &lt;code&gt;sudo&lt;/code&gt; on it, but usually that was the only way I could get things to work.
&lt;/p&gt;

&lt;p&gt;
With Nix, however, it was a matter of just describing a few packages in a shell and boom, Ruby in one folder, no Ruby (and thus no mess) everywhere else. &lt;b&gt;I was hooked!&lt;/b&gt; I started adding &lt;code&gt;shell.nix&lt;/code&gt; files to all my little projects, hell, I started planning projects by first adding a &lt;code&gt;shell.nix&lt;/code&gt; with all the dependencies I would reasonably need.
&lt;/p&gt;

&lt;p&gt;
The other reason, which ultimately cemented that I need to commit, was that I was getting tired of my installed packages slowly drifting out of control. Sure, every package manager has some method of listing what's installed, but these are usually cumbersome and completely ephemeral (in the sense that any listing becomes invalid the moment you change anything).
&lt;/p&gt;

&lt;p&gt;
With NixOS, the equation is flipped on its head: No longer did I query the system to tell me what's installed and what's not, it was now the system that worked based on files that I edit. The difference sounds small on paper, but for me it was an extremely liberating feeling to know that I could edit my system configuration in a versionable, explicit, and centralized way.
&lt;/p&gt;

&lt;p&gt;
But NixOS isn't the only declarative distro out there. In fact GNU forked Nix fairly early and made their own spin called &lt;a href=&quot;https://guix.gnu.org/&quot;&gt;Guix&lt;/a&gt;, whose big innovation is that, instead of using the unwieldy Nix-language, it uses Scheme. Specifically &lt;a href=&quot;https://www.gnu.org/software/guile/&quot;&gt;Guile Scheme&lt;/a&gt;, GNU's sanctioned configuration language. I've been following Guix for a bit, but it never felt quite ready to me with stuff like KDE being only barely supported and a lot of hardware not working out of the box.
&lt;/p&gt;

&lt;p&gt;
However, now that (after three years) Guix announced its &lt;a href=&quot;https://guix.gnu.org/en/blog/2026/gnu-guix-1.5.0-released/&quot;&gt;1.5.0 release&lt;/a&gt; with a lot of stuff stabilized and KDE finally a first-party citizen, I figured now is the best time to give it a fresh shot. This post captures my experiences from installation to the first 3-4 days.
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
&lt;b&gt;Note:&lt;/b&gt; Ultimately, after a tumultuous month of usage, I decided not to stick with Guix System. While my enthusiasm and excitement towards Guix remains the same, it sadly still doesn't deliver quite the amount of stability and availability as I hoped. For more details, please see my &lt;a href=&quot;./guix-one-month-later.html&quot;&gt;followup article&lt;/a&gt;.
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-installer-impressions&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;installer-impressions&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;2.&lt;/span&gt; Installer Impressions&lt;/h2&gt;
&lt;div id=&quot;text-2&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
Plug your USB in, &lt;code&gt;dd&lt;/code&gt; the file onto the drive, reboot, nothing unusual. If you've ever installed a Linux system, it's more of the same.
&lt;/p&gt;

&lt;p&gt;
After selecting the pendrive in my BIOS settings, the monitor began to glow in a deep, radiant blue as the Guix System logo appeared on my screen… only to suddenly switch to a menacing red: My CPU's integrated GPU is not supported by free firmware. A helpful popup gave me a gentle nudge about picking free hardware next time (buddy, have you seen the PC part prices these days?) and off I went into the installer proper.
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-01-26-guix/installer_partitions.avif&quot; alt=&quot;installer_partitions.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 1: &lt;/span&gt;Picture of the installer graciously borrowed from the Guix installer manual.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
The installer itself is refreshingly barebones and I mean this in a positive way. It asks all the necessary questions and provides a nice basic configuration file, all done in a retro &lt;a href=&quot;https://guix.gnu.org/manual/1.5.0/en/html_node/Guided-Graphical-Installation.html&quot;&gt;Ncurses-based TUI&lt;/a&gt;. I was really happy to see that, unlike my last attempt at using Guix System in the early 2020-s, KDE Plasma is now a first-party choice during installation. I never really vibed too much with GNOME and the other options didn't appeal either, so the choice was obvious.
&lt;/p&gt;

&lt;p&gt;
Now, I'm not sure if I just picked the worst possible time or if the Guix servers were facing unusual load or whatever may have happened, but after such a breeze of a setup, the moment I pressed install, my PC became unusable for the next 2.5 &lt;b&gt;hours.&lt;/b&gt; Which is unacceptable for an installation process these days in my opinion. I am lucky enough to live in a household with fiber-optic internet, that merely shrugs at bandwidth of up to a gigabit per second and yet nearly all packages downloaded with a whopping 50 &lt;i&gt;kilobit&lt;/i&gt; per second, meaning even small-ish 5-10 megabyte packages took long minutes to download.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.3&quot; href=&quot;#fn.3&quot; class=&quot;footref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
A reboot later my issues only got worse.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-i-can-t-find-my-way-land&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;i-can-t-find-my-way-land&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;3.&lt;/span&gt; I Can't Find my Way-land&lt;/h2&gt;
&lt;div id=&quot;text-3&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
I was assuming I'd get SDDM after having chosen KDE Plasma, but (what a later, &lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/X-Window.html#:~:text=by%20default%20the%20GNOME%20Display%20Manager%20(GDM).&quot;&gt;closer read&lt;/a&gt; of the manual made me realize is the expected outcome for a default config) it was GDM that loaded in. I entered my name and password, and I was greeted with the familiar Plasma 6 spinner. The first hint that something might be off was that it loaded a bit longer than usual, but I was not going to get mad at waiting 10 seconds instead of 3. After all, I did just wait magnitudes longer to get here.
&lt;/p&gt;

&lt;p&gt;
With practically nothing installed beyond the very basics, I clicked on Konsole, hoping to start prodding around my config and add some of my day to day apps. To my horror, it opened in the top left corner, without a titlebar and without any borders. What's more, no matter what I did, I couldn't move it. It also didn't show up on the menu bar, despite the application launcher still being completely usable. At this point I was fairly exhausted by these antics, but I figured,
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
Well, it's a brand new release, perhaps this just snuck in. Let's give updating a shot and see if that helps.
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
So I issued &lt;code&gt;guix pull&lt;/code&gt;… The download whizzed by with speed quite unexpected after what I experienced with the installer… Only to crash into the brick wall that's indexing. Okay, whatever, another 10-12 minutes down the drain, at least now I have newest version.
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-01-26-guix/downloads.avif&quot; alt=&quot;downloads.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 2: &lt;/span&gt;Better than before download speeds&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Except I didn't. Because, unlike Nix, the &lt;code&gt;guix&lt;/code&gt; executable is not an omnipresent, unique thing that anyone and everyone uses on your PC. Not only does every user have their own instance, if you don't issue a certain set of commands, you won't start using the new version, despite updating it.
&lt;/p&gt;

&lt;p&gt;
To Guix's credit, the CLI does scream at you to update your environment or else you'll keep using the old version, but I still find this system very disorientating compared to Nix. I'm certain experienced Guixheads are long past being tripped up by this sort of stuff and might even struggle to remember that there was a time they had to do these special steps too, but as a new user it felt a bit rough, especially consdering this is Guix System, i.e. the system whose whole purpose is to be integrate Guix as much as it can.
&lt;/p&gt;

&lt;p&gt;
Back to our issue at hand. I issued &lt;code&gt;sudo -s&lt;/code&gt; and &lt;code&gt;guix pull&lt;/code&gt;-ed again. Once more 10-12 minutes passed indexing. But at least I could finally call &lt;code&gt;guix system reconfigure /etc/config.scm&lt;/code&gt;. Interestingly things are much faster this time around, I saw speeds up to 30-50 Mbps. Before long the system was updated to the newest commit and I rebooted with high hopes.
&lt;/p&gt;

&lt;p&gt;
High hopes, that were immediately dashed when Plasma loaded in the same messed up way. At this point I started to suspect this might be an issue with the GPU driver, so I enabled the LXQT desktop environment and rebooted once more. Thankfully that one worked like a charm and I was able to boot up both Emacs (editing Scheme with GNU Nano is a pain I do not wish on anyone) and &lt;a href=&quot;https://librewolf.net/&quot;&gt;LibreWolf&lt;/a&gt; (Firefox's de-Mozilla-d variant).
&lt;/p&gt;

&lt;p&gt;
Not having found anything too useful in the docs, I decided to make my problem someone else's so I fired up ERC&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.4&quot; href=&quot;#fn.4&quot; class=&quot;footref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; and connected to Libera.chat's &lt;code&gt;#guix&lt;/code&gt; channel. After around half an hour of wait, a user by the name of Rutherther stepped up and offered me some help. We were able to figure it out that Nouveau wasn't able to drive my GPU (an RTX 5070), so his recommendation was that I should try booting with &lt;code&gt;nomodeset&lt;/code&gt;. I did, but it sadly didn't help much either.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-sympathy-for-the-devil&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;sympathy-for-the-devil&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;4.&lt;/span&gt; Sympathy for the Devil&lt;/h2&gt;
&lt;div id=&quot;text-4&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
At this point I was out of ideas. Ideas of solving this using pure-Guix System, that is. There was still one option I wanted to avoid as long as I could, but alas, it seemed like the only option, that still had a realistic chance of working.
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-01-26-guix/logo-screen-light.svg&quot; class=&quot;org-svg&quot; alt=&quot;logo-screen-light.svg&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 3: &lt;/span&gt;Nonguix's official logo, self-described to be &lt;a href=&quot;https://gitlab.com/nonguix/nonguix/-/issues/317&quot;&gt;&amp;quot;dark and evil&amp;quot;&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Enter &lt;a href=&quot;https://gitlab.com/nonguix/nonguix&quot;&gt;Nonguix&lt;/a&gt;, the Mr. Hyde to Guix's Dr. Jekyll, the shady guy who offers you a hit and first time's for free, the… Erm, in a nutshell, it's the repository for non-free applications and drivers packages for Guix System, basically. Interestingly enough, by Guix's own findings &lt;a href=&quot;https://guix.gnu.org/en/blog/2025/guix-user-and-contributor-survey-2024-the-results-part-2/&quot;&gt;about 64% of users&lt;/a&gt; utilize the Nonguix channel, which is perhaps not &amp;quot;literally everyone&amp;quot;, but it does paint a picture that there is still stuff out there that you simply cannot replace with FOSS software yet.
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 1: &lt;/span&gt;At the time of writing, this is all one has to do to enable Nonguix.&lt;/label&gt;&lt;pre class=&quot;src src-scheme&quot;&gt;&lt;span class=&quot;linenr&quot;&gt; 1: &lt;/span&gt;(cons* (channel
&lt;span class=&quot;linenr&quot;&gt; 2: &lt;/span&gt;      (name 'nonguix)
&lt;span class=&quot;linenr&quot;&gt; 3: &lt;/span&gt;      (url &lt;span class=&quot;org-string&quot;&gt;&amp;quot;https://gitlab.com/nonguix/nonguix&amp;quot;&lt;/span&gt;)
&lt;span class=&quot;linenr&quot;&gt; 4: &lt;/span&gt;      &lt;span class=&quot;org-comment-delimiter&quot;&gt;;; &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;Enable signature verification:
&lt;span class=&quot;linenr&quot;&gt; 5: &lt;/span&gt;&lt;/span&gt;      (introduction
&lt;span class=&quot;linenr&quot;&gt; 6: &lt;/span&gt;       (make-channel-introduction
&lt;span class=&quot;linenr&quot;&gt; 7: &lt;/span&gt;        &lt;span class=&quot;org-string&quot;&gt;&amp;quot;897c1a470da759236cc11798f4e0a5f7d4d59fbc&amp;quot;&lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt; 8: &lt;/span&gt;        (openpgp-fingerprint
&lt;span class=&quot;linenr&quot;&gt; 9: &lt;/span&gt;         &lt;span class=&quot;org-string&quot;&gt;&amp;quot;2A39 3FFF 68F4 EF7A 3D29  12AF 6F51 20A0 22FB B2D5&amp;quot;&lt;/span&gt;))))
&lt;span class=&quot;linenr&quot;&gt;10: &lt;/span&gt;     %default-channels)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Enabling the repo wasn't exactly difficult. You just paste the short excerpt from above (also found in the README) into your &lt;code&gt;~/.config/guix/channels.scm&lt;/code&gt;, &lt;code&gt;guix pull&lt;/code&gt;, let it index to its heart's content again, and then you have access to all that is nasty (yet occasionally useful) in the world.
&lt;/p&gt;

&lt;p&gt;
I figured perhaps if &lt;a href=&quot;https://www.fsfla.org/ikiwiki/selibre/linux-libre/&quot;&gt;Linux-libre&lt;/a&gt; and its free firmware couldn't deal with my GPU, then surely Linux proper with its binary blobs could. Hell, for good measure I threw in the NVIDIA transform, which is supposed to automagically translate all dependencies to use the proprietary drivers.
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-01-26-guix/nvidia_first_shot_crash.avif&quot; alt=&quot;nvidia_first_shot_crash.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 4: &lt;/span&gt;What haste and half-reading manuals gets you…&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Turns out my eagerness was a mistake. Not only did the process take yet another half an hour (if not more, I stopped counting), upon reboot all I was met with was a kernel panic about the driver not being able to cope with the GPU it found and a massive spew of FSCK logs.
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-01-26-guix/fsck.avif&quot; alt=&quot;fsck.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 5: &lt;/span&gt;'FSCK' was indeed very close to the first words that came to my mind at this moment.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
With no better ideas in mind, I took out my pendrive again and burned Nonguix's own pre-built ISO on it using my partner's PC. While it ultimately did get me a working system, this version has three unfortunate hindrances:
&lt;/p&gt;

&lt;ol class=&quot;org-ol&quot;&gt;
&lt;li&gt;It was built in 2022, far before Guix's &lt;a href=&quot;https://guix.gnu.org/blog/2025/migrating-to-codeberg/&quot;&gt;migration to Codeberg&lt;/a&gt;, meaning it still attempts to pull content from the unfathomably slow &lt;a href=&quot;https://savannah.gnu.org/&quot;&gt;GNU Savannah&lt;/a&gt; mirror. I had to manually override my &lt;code&gt;channels.scm&lt;/code&gt; to point at the Codeberg repo instead, but with no easy means of finding its &amp;quot;&lt;a href=&quot;https://guix.gnu.org/manual/1.5.0/en/html_node/Channel-Authentication.html&quot;&gt;channel introduction&lt;/a&gt;&amp;quot;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.5&quot; href=&quot;#fn.5&quot; class=&quot;footref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;, I had to pass in &lt;code&gt;--disable-authentication&lt;/code&gt; to Guix when updating my system. A bit scary, but I trust the Codeberg repo.&lt;/li&gt;
&lt;li&gt;Because of its age, I got a lot of somewhat intimidating errors about hardware not being recognized and other stuff I couldn't even decipher, but ultimately the system booted to the installer without issue.&lt;/li&gt;
&lt;li&gt;For some reason while the installer itself does include Nonguix stuff, it actually does not include the repo in the resulting channels files, nor the substitution server for the project. The README has a warning about this, but if you happen to miss it, you could accidentally install a non-Nonguix Guix System (say that three times fast).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
None of these were particularly hard to fix, however, and soon enough I was back where I started. That is to say, in a &lt;code&gt;nomodeset&lt;/code&gt; X11 session, except this time running &lt;a href=&quot;https://i3wm.org/&quot;&gt;i3&lt;/a&gt;, as LXQT wasn't an available option on an installer this old. There was certainly a bit of a hacker-ish vibe to messing with code files in an environment like that, but I was honestly much more looking forward to finally having a usable desktop.
&lt;/p&gt;

&lt;p&gt;
Having learned from my hastiness, this time I was smarter. I only enabled the full kernel and firmware blobs, without going anywhere near the NVIDIA transform. I issued another &lt;code&gt;guix system reconfigure&lt;/code&gt; and, after having time for another tea session, my update was finally finished.
&lt;/p&gt;

&lt;p&gt;
I rebooted with tentative nervousness and… Success? Huh.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-goals&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;goals&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;5.&lt;/span&gt; Goals&lt;/h2&gt;
&lt;div id=&quot;text-5&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
Obviously there is little point in throwing Guix System on my PC and declaring success. I wanted to be able to at least reproduce the kind of workflow I'm used to using NixOS. For that, I need the following:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;b&gt;A browser:&lt;/b&gt; preferably Firefox, as I'm not a huge fan of Chrome / Chromium,&lt;/li&gt;
&lt;li&gt;&lt;b&gt;An E-mail client:&lt;/b&gt; preferably Thunderbird,&lt;/li&gt;
&lt;li&gt;&lt;b&gt;A basic office suite:&lt;/b&gt; preferably LibreOffice,&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Dev environments:&lt;/b&gt; for Rust, Zig, Scheme, and TypeScript (with the option for more, if possible),&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Emacs:&lt;/b&gt; I do almost all my text editing in it these days, falling back to Neovim for quick tasks,&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Discord:&lt;/b&gt; for chatting with friends,&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Telegram:&lt;/b&gt; for chatting with family,&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Steam:&lt;/b&gt; for the very rare occasions I want to game,&lt;/li&gt;
&lt;li&gt;&lt;b&gt;NVIDIA drivers:&lt;/b&gt; I prefer to offload day-to-day usage to my CPU's integrated GPU, as it cuts my energy usage in half.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Of these it was obvious that two would be relatively hard and one &amp;quot;outright impossible&amp;quot;. The two being Steam and the drivers (as both are non-free and thus not in Guix's default repos) and the &amp;quot;impossible&amp;quot; one being Discord (which not even the non-free repo has packaged). But I was ready to compromise a little bit since I am requesting stuff that's explicitly against Guix's goals.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-results&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;results&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;6.&lt;/span&gt; Results&lt;/h2&gt;
&lt;div id=&quot;text-6&quot; class=&quot;outline-text-2&quot;&gt;

&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-01-26-guix/desktop.avif&quot; alt=&quot;desktop.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 6: &lt;/span&gt;My desktop running Wezterm packaged by me and Emacs.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
While there has been occasional bumps and hitches along the ride, I must say I'm very impressed with Guix System so far. Let's go through this list in order:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;b&gt;Browser:&lt;/b&gt; So far I'm really enjoying LibreWolf. It feels a lot snappier than Firefox and I'm really baffled how much speed I was apparently missing out on.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;E-mails:&lt;/b&gt; I installed Icedove, which is basically just Thunderbird without Mozilla branding. It works as expected.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Office suite:&lt;/b&gt; LibreOffice is available as expected. Not much to say about it. I guess it's interesting that Guix isn't following the usual &lt;code&gt;-stale&lt;/code&gt; / &lt;code&gt;-fresh&lt;/code&gt; packaging schema, but I don't really mind not having cutting edge versions of an office suite :)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Dev environments:&lt;/b&gt; I've only briefly toyed with development environments so far, but to me it seems like for simple use-cases it might be even easier to use than &lt;code&gt;shell.nix&lt;/code&gt; (you don't need any sort of ceremony, just a &lt;code&gt;manifest.scm&lt;/code&gt; file with a &lt;code&gt;(specifications-&amp;gt;manifest &amp;lt;list of packages&amp;gt;)&lt;/code&gt; form inside and you have a dev env ready to go.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Emacs:&lt;/b&gt; Installed just fine. I had to install &lt;code&gt;emacs-vterm&lt;/code&gt; to make &lt;a href=&quot;https://github.com/akermu/emacs-libvterm&quot;&gt;Vterm&lt;/a&gt; work, but all that took was the very simple process of adding the library to my home configuration and then referencing it in my Emacs config as per this &lt;a href=&quot;https://reddit.com/r/GUIX/comments/11gzhyu/how_to_compile_the_vterm_module_from_emacs_and/&quot;&gt;Reddit post&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Discord:&lt;/b&gt; I decided to just use Discord's browser version, which works just as fine (if not better). It's trading a tiny bit of convenience in return for not having to figure out how to manually add a package for it from some random third-party source. From what I've read elsewhere Flatpak is also an option, but I prefer having just one package manager at a time.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Steam:&lt;/b&gt; Installed shockingly easily. I have to really give props to the Nonguix team. I tested Portal 2 with the Nouveau driver, it is a little disheartening to see a 15 years old game&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.6&quot; href=&quot;#fn.6&quot; class=&quot;footref&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; lag, but I understand the people's hands are tied when it comes to the free drivers. After I managed to install the proprietary drivers, I was able to play even Portal RTX, which is something I never managed to get to work using NixOS.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;NVIDIA drivers:&lt;/b&gt; This time I actually read the docs properly and it didn't take long for me to realize the initial problem that caused my previous install to be unbootable was of course found between the chair and keyboard. This time, after making sure I enabled the open drivers and kernel mode-setting, I crossed my fingers, issued a reconfigure and it works beautifully!&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-the-good&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;the-good&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;6.1.&lt;/span&gt; The Good&lt;/h3&gt;
&lt;div id=&quot;text-6-1&quot; class=&quot;outline-text-3&quot;&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;p&gt;
&lt;b&gt;Helpful community:&lt;/b&gt; While I do feel like Guix's community could be much larger (see below), the one that exists is very helpful and nice from my limited experience. In all places I've looked so far (Libera's &lt;code&gt;#guix&lt;/code&gt;, /r/Guix, and the guix/guix Codeberg repository) I was met with genuinely kind and helpful people.
&lt;/p&gt;

&lt;p&gt;
That is not to say I haven't seen some bad eggs, especially in posts from years ago, but I don't think there is any community without those, so I'm not going to cite this as a negative.
&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Home configuration:&lt;/b&gt; Having &lt;code&gt;guix home&lt;/code&gt; be a built-in, first class citizen, instead of a community made &amp;quot;extension&amp;quot; is excellent. Instead of needing to consult a third-party resource like Home Manager's &lt;a href=&quot;https://nix-community.github.io/home-manager/&quot;&gt;documentation&lt;/a&gt; you can simply use what you already know about Guix and, if you happen to hit a wall, you can just read the &lt;a href=&quot;https://guix.gnu.org/manual/1.5.0/en/html_node/Home-Configuration.html&quot;&gt;official handbook&lt;/a&gt; which is guaranteed to always stay up to date with the rest of the system.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Package availability:&lt;/b&gt; As long as you largely use FOSS stuff (which is much easier than one might think), the amount of choice is awesome. I could basically just copy over the list of packages from my Nix config and practically everything had an equivalent.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
&lt;b&gt;Scheme:&lt;/b&gt; I'm not really a seasoned Schemer, but I have dabbled in the language previously and it feels so much better to me than Nix (the language) ever did. One great benefit of this is that it's a lot easier to start digging into package definitions to figure things out for yourself.
&lt;/p&gt;

&lt;p&gt;
This is &amp;quot;&lt;a href=&quot;https://www.gnu.org/philosophy/free-sw.html#make-changes&quot;&gt;Freedom 1&lt;/a&gt;&amp;quot; of GNU's Four Essential Freedoms in effect. Since the code is pretty much just Scheme and the different mechanisms available are fairly well documented (see caveat below), the barrier to entry is much lower than with Nix in my opinion.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.7&quot; href=&quot;#fn.7&quot; class=&quot;footref&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
Another nice benefit of this is that you can use Emacs' extensive Scheme support to help your configuration. Tools like &lt;a href=&quot;https://github.com/emacsmirror/geiser&quot;&gt;Geiser&lt;/a&gt; can plug right into Guix and help you find package and function names and, once you're experienced enough, debug your config/packages on the fly. I personally haven't yet achieved mastery of such level yet, but having the REPL confirm if I've entered names in correctly before running the code is already a boon.
&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
&lt;b&gt;Ease of hacking:&lt;/b&gt; In the &amp;quot;to tinker on&amp;quot; sense, rather than &amp;quot;being insecure&amp;quot;. With Nix, merely pulling in Nixpkgs is an effort, due to the repository being massive. My otherwise beefy machine struggled to switch between branches and make commits, which doesn't exactly inspire confidence in contributing, even though it was otherwise something I was excited to do. Meanwhile, with Guix I was able to get a fully functioning development environment in 15 minutes tops, which includes cloning the repo, authenticating all commits, generating bytecode for the entire repository, and getting Emacs set up to work nice with the codebase.
&lt;/p&gt;

&lt;p&gt;
Not to mention, at the time of writing &lt;a href=&quot;https://github.com/NixOS/nixpkgs/pull/453219&quot;&gt;my Nixpkgs PR of guile-colorized&lt;/a&gt; is still not accepted, despite being open since October, 2025. Which is kind of disheartening, when the package is really trivial and has a very low blast-radius. With Guix I &lt;a href=&quot;https://codeberg.org/guix/guix/pulls/5962&quot;&gt;got an answer&lt;/a&gt; to an extremely noobish question on my first PR in mere hours.
&lt;/p&gt;

&lt;p&gt;
On a separate, but related note, I also found it a lot easier to test my package in a &amp;quot;live&amp;quot; environment as &lt;code&gt;guix pull&lt;/code&gt; supports a parameter called &lt;code&gt;--url&lt;/code&gt; which you can easily point to a folder on your own PC. So once I was confident my code should work, I could just &amp;quot;check out&amp;quot; my local repository clone and build it like I was an end user. This let me make sure it really does work.
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-the-ambiguous&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;the-ambiguous&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;6.2.&lt;/span&gt; The Ambiguous&lt;/h3&gt;
&lt;div id=&quot;text-6-2&quot; class=&quot;outline-text-3&quot;&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;p&gt;
&lt;b&gt;Search:&lt;/b&gt; &lt;code&gt;guix search&lt;/code&gt; not taking an extra parameter like &lt;code&gt;nix search&lt;/code&gt; is both very convenient and a bit of a bummer.
&lt;/p&gt;

&lt;p&gt;
Its absence is not a deal breaker, but I really loved how with Nix, you could search in &lt;i&gt;anything&lt;/i&gt;, that has a flake. Be that Nixpkgs, a repo you downloaded, a repo that's on a git forge, etc. I remember being awestruck that I could just do &lt;code&gt;nix search github:mozilla/nixpkgs-mozilla&lt;/code&gt; and search for their builds of Firefox without having to manually check out anything.
&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
&lt;b&gt;The documentation:&lt;/b&gt; Oof, this one is a bit hard to pass definite judgment on.
&lt;/p&gt;

&lt;p&gt;
On one hand I love the thoroughness of it all. You can get a fairly decent idea of what Guix, what it can do for your, how to use it, and how to extend it, just by reading the manual. It is evident that the Guix team and GNU in general takes its mission to educate using free software very seriously. Stuff like the &lt;a href=&quot;https://guix.gnu.org/cookbook/en/html_node/Packaging-Tutorial.html&quot;&gt;Packaging tutorial&lt;/a&gt; make it very easy for complete beginners to hack together package definitions without needing to consult any other resource.
&lt;/p&gt;

&lt;p&gt;
On the other hand, it really is just a manual, not a tutorial. What I mean by this is that concepts that could belong together aren't placed near each other. A simple example would be &lt;a href=&quot;https://guix.gnu.org/manual/1.5.0/en/html_node/Services.html&quot;&gt;services&lt;/a&gt; and &lt;a href=&quot;https://guix.gnu.org/manual/1.5.0/en/html_node/Service-Reference.html#:~:text=modify-services%20services&quot;&gt;customizing them&lt;/a&gt;. Assuming, you're in one of the sub-pages of Services and you suddenly realize you want to replace/modify one of the services, you are left completely clueless how that works. You have to go to a completely different chapter and find one particular function's description and then apply what you learn there. The &lt;a href=&quot;https://guix.gnu.org/cookbook/en/guix-cookbook.html&quot;&gt;Guix Cookbook&lt;/a&gt; has some examples, but you have to know about the cookbook in the first place.
&lt;/p&gt;

&lt;p&gt;
And before anyone misunderstands me, I'm fine with RTFM, but in my opinion one of the preconditions of mass-appeal is having &amp;quot;pre-chewed&amp;quot; solutions for common problems, that don't require perusing multiple chapters.
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-the-bad&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;the-bad&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;6.3.&lt;/span&gt; The Bad&lt;/h3&gt;
&lt;div id=&quot;text-6-3&quot; class=&quot;outline-text-3&quot;&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;b&gt;Substitute server stability:&lt;/b&gt; I imagine this is an issue that only a massive bag of money could fix, but the CI/CD servers could definitely use some more processing power. It's really annoying when you're trying to test something and you're suddenly forced to wait 10-15 minutes because the server can only spare 50-100 kbps for you.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
&lt;b&gt;Content out there:&lt;/b&gt; Clearly this isn't the Guix team's fault (and it's something I'm trying to lessen with this post, even if just a tiny bit), but it's really hard to find good quality material when it comes to Guix.
&lt;/p&gt;

&lt;p&gt;
I mean, sure, there is the excellent &lt;a href=&quot;https://systemcrafters.net/craft-your-system-with-guix/&quot;&gt;System Crafters&lt;/a&gt; tutorial series, and the odd gems like &lt;a href=&quot;https://dthompson.us/posts/guix-for-development.html&quot;&gt;DThompson's dev env tutorial&lt;/a&gt;, but as a whole you're largely left to your own to trawl through the manual, IRC logs, Reddit threads, Codeberg and the previous issue tracker, etc. It's not an impossible task, especially if you're used to doing Linux things &amp;quot;the hard way&amp;quot;, but it's certainly a far cry from such one-stop shops as &lt;a href=&quot;https://nixos-and-flakes.thiscute.world/nixos-with-flakes/introduction-to-flakes&quot;&gt;the Nix Flakes book&lt;/a&gt; or &lt;a href=&quot;https://mhwombat.codeberg.page/nix-book/&quot;&gt;Wombat's Book of Nix&lt;/a&gt;.
&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Guix's own build speed:&lt;/b&gt; Nix excels in speed, so I was hoping Guix would be the same. Yet stuff like &lt;code&gt;guix pull&lt;/code&gt; really bog things down. Doubly so, if you want to update not just your own &lt;code&gt;guix&lt;/code&gt; instance, but also root's.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Clarity of commands:&lt;/b&gt; The fact that all concerns are lumped together (unlike Nix's many utilities) means that to the new user the many commands such as &lt;code&gt;guix pull&lt;/code&gt;, &lt;code&gt;guix {system, home} reconfigure&lt;/code&gt;, &lt;code&gt;guix update&lt;/code&gt; can easily feel overwhelming and unclear what's updating/changing what. With time I'm sure you obtain a sort of mental muscle memory and you never think about it again, but starting out it's definitely a confusing part.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-overall&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;overall&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;7.&lt;/span&gt; Overall&lt;/h2&gt;
&lt;div id=&quot;text-7&quot; class=&quot;outline-text-2&quot;&gt;
&lt;div class=&quot;org-src-container&quot;&gt;
&lt;label class=&quot;org-src-name&quot;&gt;&lt;span class=&quot;listing-number&quot;&gt;Listing 2: &lt;/span&gt;My current, fairly barebones Guix home config.&lt;/label&gt;&lt;pre class=&quot;src src-scheme&quot;&gt;&lt;span class=&quot;linenr&quot;&gt; 1: &lt;/span&gt;(&lt;span class=&quot;org-keyword&quot;&gt;define-module&lt;/span&gt; (&lt;span class=&quot;org-type&quot;&gt;guix-home-config&lt;/span&gt;)
&lt;span class=&quot;linenr&quot;&gt; 2: &lt;/span&gt;  &lt;span class=&quot;org-builtin&quot;&gt;#:use-module&lt;/span&gt; (nongnu packages)
&lt;span class=&quot;linenr&quot;&gt; 3: &lt;/span&gt;  &lt;span class=&quot;org-builtin&quot;&gt;#:use-module&lt;/span&gt; (gnu packages)
&lt;span class=&quot;linenr&quot;&gt; 4: &lt;/span&gt;  &lt;span class=&quot;org-builtin&quot;&gt;#:use-module&lt;/span&gt; (gnu home)
&lt;span class=&quot;linenr&quot;&gt; 5: &lt;/span&gt;  &lt;span class=&quot;org-builtin&quot;&gt;#:use-module&lt;/span&gt; (gnu home services)
&lt;span class=&quot;linenr&quot;&gt; 6: &lt;/span&gt;  &lt;span class=&quot;org-builtin&quot;&gt;#:use-module&lt;/span&gt; (gnu home services shells)
&lt;span class=&quot;linenr&quot;&gt; 7: &lt;/span&gt;  &lt;span class=&quot;org-builtin&quot;&gt;#:use-module&lt;/span&gt; (gnu services)
&lt;span class=&quot;linenr&quot;&gt; 8: &lt;/span&gt;  &lt;span class=&quot;org-builtin&quot;&gt;#:use-module&lt;/span&gt; (gnu system shadow)
&lt;span class=&quot;linenr&quot;&gt; 9: &lt;/span&gt;  &lt;span class=&quot;org-builtin&quot;&gt;#:use-module&lt;/span&gt; (guix gexp))
&lt;span class=&quot;linenr&quot;&gt;10: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;11: &lt;/span&gt;(&lt;span class=&quot;org-keyword&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;org-function-name&quot;&gt;%packages&lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;12: &lt;/span&gt;  (list &lt;span class=&quot;org-string&quot;&gt;&amp;quot;git&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;openssh&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;librewolf&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;ripgrep&amp;quot;&lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;13: &lt;/span&gt;        &lt;span class=&quot;org-string&quot;&gt;&amp;quot;bat&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;eza&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;fd&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;zoxide&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;bc&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;gimp&amp;quot;&lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;14: &lt;/span&gt;        &lt;span class=&quot;org-string&quot;&gt;&amp;quot;libreoffice&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;jujutsu&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;starship&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;direnv&amp;quot;&lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;15: &lt;/span&gt;        &lt;span class=&quot;org-string&quot;&gt;&amp;quot;okular&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;gwenview&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;bitwarden-desktop&amp;quot;&lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;16: &lt;/span&gt;        &lt;span class=&quot;org-string&quot;&gt;&amp;quot;icedove-wayland&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;telegram-desktop&amp;quot;&lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;17: &lt;/span&gt;        &lt;span class=&quot;org-string&quot;&gt;&amp;quot;emacs-vterm&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;ispell&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;hunspell&amp;quot;&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;&amp;quot;wezterm&amp;quot;&lt;/span&gt;))
&lt;span class=&quot;linenr&quot;&gt;18: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;19: &lt;/span&gt;(&lt;span class=&quot;org-keyword&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;org-function-name&quot;&gt;%nonfree-packages&lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;20: &lt;/span&gt;  (list &lt;span class=&quot;org-string&quot;&gt;&amp;quot;steam-nvidia&amp;quot;&lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;21: &lt;/span&gt;        &lt;span class=&quot;org-string&quot;&gt;&amp;quot;mpv-nvidia&amp;quot;&lt;/span&gt;))
&lt;span class=&quot;linenr&quot;&gt;22: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;23: &lt;/span&gt;(&lt;span class=&quot;org-keyword&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;org-function-name&quot;&gt;home-config&lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;24: &lt;/span&gt;  (home-environment
&lt;span class=&quot;linenr&quot;&gt;25: &lt;/span&gt;   (packages (specifications-&amp;gt;packages (append %nonfree-packages %packages)))
&lt;span class=&quot;linenr&quot;&gt;26: &lt;/span&gt;   (services
&lt;span class=&quot;linenr&quot;&gt;27: &lt;/span&gt;    (append
&lt;span class=&quot;linenr&quot;&gt;28: &lt;/span&gt;     (list
&lt;span class=&quot;linenr&quot;&gt;29: &lt;/span&gt;      (service home-bash-service-type
&lt;span class=&quot;linenr&quot;&gt;30: &lt;/span&gt;               (home-bash-configuration
&lt;span class=&quot;linenr&quot;&gt;31: &lt;/span&gt;                 (aliases '((&lt;span class=&quot;org-string&quot;&gt;&amp;quot;ls&amp;quot;&lt;/span&gt; . &lt;span class=&quot;org-string&quot;&gt;&amp;quot;eza&amp;quot;&lt;/span&gt;)))
&lt;span class=&quot;linenr&quot;&gt;32: &lt;/span&gt;                 (bashrc (list (local-file &lt;span class=&quot;org-string&quot;&gt;&amp;quot;./bashrc.sh&amp;quot;&lt;/span&gt;)))))
&lt;span class=&quot;linenr&quot;&gt;33: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;34: &lt;/span&gt;      (service home-files-service-type
&lt;span class=&quot;linenr&quot;&gt;35: &lt;/span&gt;               `((&lt;span class=&quot;org-string&quot;&gt;&amp;quot;.guile&amp;quot;&lt;/span&gt; ,%default-dotguile)
&lt;span class=&quot;linenr&quot;&gt;36: &lt;/span&gt;                 (&lt;span class=&quot;org-string&quot;&gt;&amp;quot;.Xdefaults&amp;quot;&lt;/span&gt; ,%default-xdefaults)))
&lt;span class=&quot;linenr&quot;&gt;37: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;38: &lt;/span&gt;      (service home-xdg-configuration-files-service-type
&lt;span class=&quot;linenr&quot;&gt;39: &lt;/span&gt;               `((&lt;span class=&quot;org-string&quot;&gt;&amp;quot;gdb/gdbinit&amp;quot;&lt;/span&gt; ,%default-gdbinit)
&lt;span class=&quot;linenr&quot;&gt;40: &lt;/span&gt;                 (&lt;span class=&quot;org-string&quot;&gt;&amp;quot;nano/nanorc&amp;quot;&lt;/span&gt; ,%default-nanorc))))
&lt;span class=&quot;linenr&quot;&gt;41: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;42: &lt;/span&gt;     %base-home-services))))
&lt;span class=&quot;linenr&quot;&gt;43: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;44: &lt;/span&gt;home-config
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
In a nutshell I'm very positively surprised by Guix System. After struggling so much with it years ago, this time everything just clicked after a much shorter battle. So much so that I'm happy to make it my daily driver for the foreseeable future. Beyond the slightly slower execution speed, I'm getting a comparable experience to NixOS, with all the usual pros a declarative environment brings and without having to put up with Nixlang.
&lt;/p&gt;

&lt;p&gt;
My only recurring issues so far are the occasional slow download speeds and that I have to start my kernel in &lt;code&gt;nomodeset&lt;/code&gt; because otherwise the graphical environment crashes without me being able to switch to a TTY. It's a bummer, but honestly, I'm not too bothered by it so far. I'm trusting a driver update will fix it soon enough and, if not, it's not exactly difficult to &lt;a href=&quot;https://guix.gnu.org/manual/1.5.0/en/html_node/operating_002dsystem-Reference.html#:~:text=kernel-arguments&quot;&gt;throw in a kernel parameter&lt;/a&gt; into your config.
&lt;/p&gt;

&lt;p&gt;
I'm hoping to do a followup post about packaging in Guix, because I've been dipping my toes into it by &lt;a href=&quot;https://codeberg.org/guix/guix/pulls/6020&quot;&gt;trying to package&lt;/a&gt; Wezterm and the journey there was similarly arduous as installing the system itself.
&lt;/p&gt;

&lt;p&gt;
Till then, thank you for reading and see you next time!
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-notes&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;notes&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;8.&lt;/span&gt; Notes&lt;/h2&gt;
&lt;div id=&quot;text-8&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
The stuff you see below are all I managed to write down mid-process. Some of these I threw it into the file from Nano, some from half-broken X11 sessions. Because of this, it's not exactly well-edited, but I hope it might provide a glimpse into my mind at the time.
&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;The installer is decently simple
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;I appreciate the warning about incompatible hardware&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;2.5 hours at least to install (mirrors throttle connection to 50kbps)&lt;/li&gt;
&lt;li&gt;KDE is simply not working out of the box (titlebars are missing)
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;It seems to also default to X11, when I'm looking for Wayland&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;The first &lt;code&gt;guix pull&lt;/code&gt; is horrendously slow&lt;/li&gt;
&lt;li&gt;Wayland continues to elude me, seems to be an Nvidia issue
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;IRC recommends &lt;code&gt;nomodeset&lt;/code&gt;, doesn't help&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Try enabling Nonguix, system no longer boots&lt;/li&gt;
&lt;li&gt;Try installing using the Nonguix ISO
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;Lots of errors, terribly old release&lt;/li&gt;
&lt;li&gt;Having to &lt;code&gt;guix pull&lt;/code&gt; myself to the present day again&lt;/li&gt;
&lt;li&gt;Also I'm missing the introduction, so I have to run it using &lt;code&gt;--disable-authentication&lt;/code&gt;, not great, but I trust the Codeberg repo&lt;/li&gt;
&lt;li&gt;At least the download speed seems to have normalized&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;It isn't entirely clear when you have to use &lt;code&gt;sudo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;i3&lt;/code&gt; on a shitty low-res has a certain vibe to it, but I'd prefer a system working out of the box&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;h2 class=&quot;footnotes&quot;&gt;Footnotes: &lt;/h2&gt;
&lt;div id=&quot;text-footnotes&quot;&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.1&quot; href=&quot;#fnr.1&quot; class=&quot;footnum&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Well, if only life was so easy. What I mean here is that on my personal computer, I've not had Windows since about 2015. For work purposes my hands are currently chained to MacOS (though even there I use a Debian-based container).
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.2&quot; href=&quot;#fnr.2&quot; class=&quot;footnum&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
No disrespect to Ubuntu-users, past and present! My opinion at the time was quite ignorant and nowadays I far more appreciate an easy to maintain system as you'll see from the rest of this post.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.3&quot; href=&quot;#fnr.3&quot; class=&quot;footnum&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
It's merely a hunch, but it feels to me that the servers are far slower during the (Central-European) night. During midday, I get really good download speeds, but after around 8 PM, it slows to a crawl.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.4&quot; href=&quot;#fnr.4&quot; class=&quot;footnum&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Which, for the uninitiated, is an IRC client &lt;a href=&quot;https://www.gnu.org/software/emacs/manual/html_mono/erc.html&quot;&gt;built into Emacs&lt;/a&gt;. This editor continues to wow me every day.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.5&quot; href=&quot;#fnr.5&quot; class=&quot;footnum&quot;&gt;5&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
I probably could have figured it out in time. But at this point I was a bit exasperated and I really didn't want to type in an 10x4 character hexadecimal code by hand.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.6&quot; href=&quot;#fnr.6&quot; class=&quot;footnum&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Goodness gracious, Portal 2 is almost 15 years old…
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.7&quot; href=&quot;#fnr.7&quot; class=&quot;footnum&quot;&gt;7&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
That being said, my Nix experience was still very much helpful here. Understanding stuff such as build phases, why packages need to be patched and how this usually works, and what the different build flags mean is pretty much a must if you want to attain an understanding deeper than just &amp;quot;kinda getting it.&amp;quot;
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;/div&gt;</content></entry><entry><title>Implementing Perlin Noise in TypeScript</title><id>https://nemin.hu/perlin.html</id><author><name>Nemin</name></author><updated>2026-01-20T19:25:00Z</updated><link href="https://nemin.hu/perlin.html" rel="alternate" /><content type="html">&lt;div role=&quot;doc-toc&quot; id=&quot;table-of-contents&quot;&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;div role=&quot;doc-toc&quot; id=&quot;text-table-of-contents&quot;&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#introduction&quot;&gt;1. Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#basic-outline&quot;&gt;2. Basic outline&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#implementation&quot;&gt;3. Implementation&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#vector-class&quot;&gt;3.1. Vector class&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#interpolation-function&quot;&gt;3.2. Interpolation function&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#noise-function&quot;&gt;3.3. Noise function&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#main-loop&quot;&gt;3.4. Main loop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-introduction&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;introduction&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;1.&lt;/span&gt; Introduction&lt;/h2&gt;
&lt;div id=&quot;text-1&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
Ever since I first started programming, I was always fascinated by noise functions. The idea that you can take a relatively simple algorithm and generate realistic-looking textures of clouds, fire, cloth, etc. is really cool.
&lt;/p&gt;

&lt;p&gt;
However, as simple as the concept is, I still found myself struggling with putting it into code, until the idea one day clicked and I was able to finally implement it. To help others, who might wonder how this algorithm works, I'd like to share a small tutorial here how one might go about implementing a simple variant of &lt;a href=&quot;https://en.wikipedia.org/wiki/Perlin_noise&quot;&gt;Perlin noise&lt;/a&gt; in TypeScript.
&lt;/p&gt;

&lt;p&gt;
One caveat of the code below is that it focuses more on readability, without paying much attention towards performance and optimization, and thus isn't exactly fast, despite the algorithm being &lt;a href=&quot;https://en.wikipedia.org/wiki/Embarrassingly_parallel#:~:text=The%20Mandelbrot%20set%2C%20Perlin%20noise%20and%20similar%20images%2C%20where%20each%20point%20is%20calculated%20independently%2E&quot;&gt;Embarrasingly Parallel&lt;/a&gt;. For more serious implementations one should consider following the original algorithm's permutation lookup table-based approach, use &lt;a href=&quot;https://en.wikipedia.org/wiki/WebGL&quot;&gt;WebGL&lt;/a&gt; with a fragment shader that can easily compute every point's noise value at the same time, &lt;del&gt;or just use a better language than JS…&lt;/del&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-basic-outline&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;basic-outline&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;2.&lt;/span&gt; Basic outline&lt;/h2&gt;
&lt;div id=&quot;text-2&quot; class=&quot;outline-text-2&quot;&gt;
&lt;p&gt;
Given an arbitrarily sized rectangle, we need to do the following steps to fill it with noise:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;First we divide the rectangle into a grid of squares. The size of these squares is freely adjustable by us and it determines the general &amp;quot;roughness&amp;quot; of the result.&lt;/li&gt;
&lt;li&gt;For each of these grid points, we generate a so-called &lt;i&gt;gradient vector&lt;/i&gt;. In the context of Perlin noise, these are little more than vectors whose magnitude is 1 and which point in random directions.&lt;/li&gt;
&lt;li&gt;Then, for every pixel inside the original rectangle, we gather the four closest grid-points and their accompanying gradients.&lt;/li&gt;
&lt;li&gt;We calculate the &lt;a href=&quot;https://en.wikipedia.org/wiki/Dot_product&quot;&gt;dot product&lt;/a&gt; of the vector formed by the pixel's coordinates and each of the gradients.&lt;/li&gt;
&lt;li&gt;To smush these values into one, we take a proportional part of both the upper and lower corners' values based on the X coordinate of our pixel and then calculate a final value from that based on the Y coordinate.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-implementation&quot; class=&quot;outline-2&quot;&gt;
&lt;h2 id=&quot;implementation&quot;&gt;&lt;span class=&quot;section-number-2&quot;&gt;3.&lt;/span&gt; Implementation&lt;/h2&gt;
&lt;div id=&quot;text-3&quot; class=&quot;outline-text-2&quot;&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-vector-class&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;vector-class&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.1.&lt;/span&gt; Vector class&lt;/h3&gt;
&lt;div id=&quot;text-3-1&quot; class=&quot;outline-text-3&quot;&gt;
&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-typescript&quot;&gt;&lt;span class=&quot;linenr&quot;&gt; 1: &lt;/span&gt;&lt;span class=&quot;org-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt; {
&lt;span class=&quot;linenr&quot;&gt; 2: &lt;/span&gt;  x: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;;
&lt;span class=&quot;linenr&quot;&gt; 3: &lt;/span&gt;  y: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;;
&lt;span class=&quot;linenr&quot;&gt; 4: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt; 5: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;constructor&lt;/span&gt;(x: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;, y: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;) {
&lt;span class=&quot;linenr&quot;&gt; 6: &lt;/span&gt;    &lt;span class=&quot;org-typescript-this&quot;&gt;this&lt;/span&gt;.x = x;
&lt;span class=&quot;linenr&quot;&gt; 7: &lt;/span&gt;    &lt;span class=&quot;org-typescript-this&quot;&gt;this&lt;/span&gt;.y = y;
&lt;span class=&quot;linenr&quot;&gt; 8: &lt;/span&gt;  }
&lt;span class=&quot;linenr&quot;&gt; 9: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;10: &lt;/span&gt;  &lt;span class=&quot;org-typescript-access-modifier&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;org-function-name&quot;&gt;random_gradient&lt;/span&gt;(): &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt; {
&lt;span class=&quot;linenr&quot;&gt;11: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;angle&lt;/span&gt; = Math.&lt;span class=&quot;org-function-call&quot;&gt;random&lt;/span&gt;() * 2 * Math.PI;
&lt;span class=&quot;linenr&quot;&gt;12: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;org-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt;(Math.&lt;span class=&quot;org-function-call&quot;&gt;cos&lt;/span&gt;(angle), Math.&lt;span class=&quot;org-function-call&quot;&gt;sin&lt;/span&gt;(angle));
&lt;span class=&quot;linenr&quot;&gt;13: &lt;/span&gt;  }
&lt;span class=&quot;linenr&quot;&gt;14: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;15: &lt;/span&gt;  &lt;span class=&quot;org-function-name&quot;&gt;sub&lt;/span&gt;(&lt;span class=&quot;org-variable-name&quot;&gt;other&lt;/span&gt;: &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt;): &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt; {
&lt;span class=&quot;linenr&quot;&gt;16: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;org-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt;(&lt;span class=&quot;org-typescript-this&quot;&gt;this&lt;/span&gt;.x - other.x, &lt;span class=&quot;org-typescript-this&quot;&gt;this&lt;/span&gt;.y - other.y);
&lt;span class=&quot;linenr&quot;&gt;17: &lt;/span&gt;  }
&lt;span class=&quot;linenr&quot;&gt;18: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;19: &lt;/span&gt;  &lt;span class=&quot;org-function-name&quot;&gt;add&lt;/span&gt;(&lt;span class=&quot;org-variable-name&quot;&gt;other&lt;/span&gt;: &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt;): &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt; {
&lt;span class=&quot;linenr&quot;&gt;20: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;org-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt;(&lt;span class=&quot;org-typescript-this&quot;&gt;this&lt;/span&gt;.x + other.x, &lt;span class=&quot;org-typescript-this&quot;&gt;this&lt;/span&gt;.y + other.y);
&lt;span class=&quot;linenr&quot;&gt;21: &lt;/span&gt;  }
&lt;span class=&quot;linenr&quot;&gt;22: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;23: &lt;/span&gt;  &lt;span class=&quot;org-function-name&quot;&gt;dot_product&lt;/span&gt;(&lt;span class=&quot;org-variable-name&quot;&gt;other&lt;/span&gt;: &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt;): &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt; {
&lt;span class=&quot;linenr&quot;&gt;24: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;org-typescript-this&quot;&gt;this&lt;/span&gt;.x * other.x + &lt;span class=&quot;org-typescript-this&quot;&gt;this&lt;/span&gt;.y * other.y;
&lt;span class=&quot;linenr&quot;&gt;25: &lt;/span&gt;  }
&lt;span class=&quot;linenr&quot;&gt;26: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;27: &lt;/span&gt;  &lt;span class=&quot;org-function-name&quot;&gt;toString&lt;/span&gt;(): &lt;span class=&quot;org-typescript-primitive&quot;&gt;string&lt;/span&gt; {
&lt;span class=&quot;linenr&quot;&gt;28: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;org-string&quot;&gt;`(&lt;/span&gt;&lt;span class=&quot;org-keyword&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;org-default&quot;&gt;this.x&lt;/span&gt;&lt;span class=&quot;org-keyword&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;org-string&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;org-keyword&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;org-default&quot;&gt;this.y&lt;/span&gt;&lt;span class=&quot;org-keyword&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;org-string&quot;&gt;)`&lt;/span&gt;;
&lt;span class=&quot;linenr&quot;&gt;29: &lt;/span&gt;  }
&lt;span class=&quot;linenr&quot;&gt;30: &lt;/span&gt;}
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
We begin by defining a very rudimentary Vector class. Usually we'd define more operations, like multiplication, length, etc., but for our purposes this suffices.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.1&quot; href=&quot;#fn.1&quot; class=&quot;footref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
You may wonder why we bother to define &lt;code&gt;toString&lt;/code&gt;, when our end result will be graphical anyway. The reason is simple: We are going to associate data with our coordinates, using JavaScript's built-in &lt;code&gt;Map&lt;/code&gt;. However, because these don't support indexing by objects, we need to first convert our &lt;code&gt;Vector&lt;/code&gt; into a string, which does work as a key.&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.2&quot; href=&quot;#fn.2&quot; class=&quot;footref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-interpolation-function&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;interpolation-function&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.2.&lt;/span&gt; Interpolation function&lt;/h3&gt;
&lt;div id=&quot;text-3-2&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
In our algorithm, every pixel's value comes from four different values, but the end result needs to be only a single value. Therefore we need a function that, when given two numbers, allows us to pick a third, that is a mix of a given percentage of one of our numbers and a complementary percentage of the other. For this we utilize &lt;a href=&quot;https://en.wikipedia.org/wiki/Linear_interpolation#Programming_language_support&quot;&gt;Linear interpolation&lt;/a&gt;:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-typescript&quot;&gt;&lt;span class=&quot;linenr&quot;&gt;31: &lt;/span&gt;&lt;span class=&quot;org-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;org-function-name&quot;&gt;lerp&lt;/span&gt;(&lt;span class=&quot;org-variable-name&quot;&gt;percent&lt;/span&gt;: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;, &lt;span class=&quot;org-variable-name&quot;&gt;start&lt;/span&gt;: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;, &lt;span class=&quot;org-variable-name&quot;&gt;end&lt;/span&gt;: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;): &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt; {
&lt;span class=&quot;linenr&quot;&gt;32: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;return&lt;/span&gt; start + percent * (end - start);
&lt;span class=&quot;linenr&quot;&gt;33: &lt;/span&gt;}
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
This function takes a &lt;code&gt;[start, end]&lt;/code&gt; range and a percentage, and has the following properties:
&lt;/p&gt;

&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;&lt;code&gt;lerp(0, start, end) = start + 0 * (end - start) = start&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lerp(1, start, end) = start + 1 * (end - start) = start + end - start = end&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;Otherwise, &lt;code&gt;lerp(x, start, end) = ((1 - x) * start) + (x * end)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
&lt;b&gt;Example:&lt;/b&gt; Let's say our inputs are &lt;code&gt;0.25&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, and &lt;code&gt;5&lt;/code&gt;. We add to &lt;code&gt;1&lt;/code&gt; a fourth of the difference between &lt;code&gt;5&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt;, thus get &lt;code&gt;1 + 0.25 * (5 - 1) = 1 + 0.25 * 4 = 1 + 1 = 2&lt;/code&gt;. If we were to pick &lt;code&gt;0.5&lt;/code&gt; as our &lt;code&gt;percent&lt;/code&gt; instead, our result would be &lt;code&gt;3&lt;/code&gt; which is the exact middle between &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;5&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
This already works, but it's less than ideal because the edges of the interpolation function are very sharp leading to very obvious artifacts on the edges of grid cells:
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-01-20-perlin/lerp.avif&quot; alt=&quot;lerp.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 1: &lt;/span&gt;Noise full of visual artifacts.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
It's easy to see why this happens if we take a look at how interpolating between some random values looks like:
&lt;/p&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-01-20-perlin/lerp_graph.avif&quot; alt=&quot;lerp_graph.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 2: &lt;/span&gt;Sharp peaks and valleys that show up as visible lines in our noise.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Thankfully fixing this is fairly simple. If the issue is that the endpoints' connections are too sharp, we simply have to smoothen them out. Enter the &amp;quot;&lt;a href=&quot;https://en.wikipedia.org/wiki/Smoothstep#Variations&quot;&gt;Smoothstep&lt;/a&gt;&amp;quot; function:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-typescript&quot;&gt;&lt;span class=&quot;linenr&quot;&gt;34: &lt;/span&gt;&lt;span class=&quot;org-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;org-function-name&quot;&gt;smoothstep&lt;/span&gt;(&lt;span class=&quot;org-variable-name&quot;&gt;x&lt;/span&gt;: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;): &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt; {
&lt;span class=&quot;linenr&quot;&gt;35: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;return&lt;/span&gt; 6*x**5 - 15*x**4 + 10*x**3;
&lt;span class=&quot;linenr&quot;&gt;36: &lt;/span&gt;}
&lt;/pre&gt;
&lt;/div&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-01-20-perlin/smoothstep_func.avif&quot; alt=&quot;smoothstep_func.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 3: &lt;/span&gt;The Smoothstep function visualized in the range &lt;code&gt;[0, 1]&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Given a number between &lt;code&gt;[0, 1]&lt;/code&gt;, this function returns another value between &lt;code&gt;[0, 1]&lt;/code&gt;, but &amp;quot;smoothened out&amp;quot;. To provide a better visual explanation of what this means, let's see the previous interpolation, after we ran our percentages through Smoothstep&lt;a id=&quot;smoothstep&quot;&gt;&lt;/a&gt;:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-typescript&quot;&gt;&lt;span class=&quot;linenr&quot;&gt;37: &lt;/span&gt;&lt;span class=&quot;org-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;org-function-name&quot;&gt;interpolate&lt;/span&gt;(&lt;span class=&quot;org-variable-name&quot;&gt;percent&lt;/span&gt;: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;, &lt;span class=&quot;org-variable-name&quot;&gt;start&lt;/span&gt;: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;, &lt;span class=&quot;org-variable-name&quot;&gt;end&lt;/span&gt;: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;): &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt; {
&lt;span class=&quot;linenr&quot;&gt;38: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;smooth_percent&lt;/span&gt; = &lt;span class=&quot;org-function-call&quot;&gt;smoothstep&lt;/span&gt;(percent);
&lt;span class=&quot;linenr&quot;&gt;39: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;org-function-call&quot;&gt;lerp&lt;/span&gt;(smooth_percent, start, end);
&lt;span class=&quot;linenr&quot;&gt;40: &lt;/span&gt;}
&lt;/pre&gt;
&lt;/div&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-01-20-perlin/smootherstep.avif&quot; alt=&quot;smootherstep.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 4: &lt;/span&gt;The peaks and valleys are gone.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
As you can see, the end result looks a lot more &amp;quot;natural&amp;quot; as one curve flows nicely into the next, without any obvious edges.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-noise-function&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;noise-function&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.3.&lt;/span&gt; Noise function&lt;/h3&gt;
&lt;div id=&quot;text-3-3&quot; class=&quot;outline-text-3&quot;&gt;
&lt;p&gt;
With all the groundwork finally done, we reach the main algorithm. Let's take it in parts:
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-typescript&quot;&gt;&lt;span class=&quot;linenr&quot;&gt;41: &lt;/span&gt;&lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;gradients&lt;/span&gt; = &lt;span class=&quot;org-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;org-type&quot;&gt;Map&lt;/span&gt;&amp;lt;&lt;span class=&quot;org-typescript-primitive&quot;&gt;string&lt;/span&gt;, &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt;&amp;gt;();
&lt;span class=&quot;linenr&quot;&gt;42: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;43: &lt;/span&gt;&lt;span class=&quot;org-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;org-function-name&quot;&gt;get_gradient&lt;/span&gt;(&lt;span class=&quot;org-variable-name&quot;&gt;vec&lt;/span&gt;: &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt;): &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt; {
&lt;span class=&quot;linenr&quot;&gt;44: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;key&lt;/span&gt; = vec.&lt;span class=&quot;org-function-call&quot;&gt;toString&lt;/span&gt;();
&lt;span class=&quot;linenr&quot;&gt;45: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;gradient&lt;/span&gt;: &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt; | &lt;span class=&quot;org-constant&quot;&gt;undefined&lt;/span&gt; = gradients.&lt;span class=&quot;org-function-call&quot;&gt;get&lt;/span&gt;(key);
&lt;span class=&quot;linenr&quot;&gt;46: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;47: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;if&lt;/span&gt; (!gradient) {
&lt;span class=&quot;linenr&quot;&gt;48: &lt;/span&gt;    gradient = Vector.&lt;span class=&quot;org-function-call&quot;&gt;random_gradient&lt;/span&gt;();
&lt;span class=&quot;linenr&quot;&gt;49: &lt;/span&gt;    gradients.&lt;span class=&quot;org-function-call&quot;&gt;set&lt;/span&gt;(key, gradient);
&lt;span class=&quot;linenr&quot;&gt;50: &lt;/span&gt;  }
&lt;span class=&quot;linenr&quot;&gt;51: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;52: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;return&lt;/span&gt; gradient
&lt;span class=&quot;linenr&quot;&gt;53: &lt;/span&gt;}
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
First we initialize a &lt;code&gt;Map&lt;/code&gt; to hold our calculated gradients. This both serves as an optimization (we only need to calculate a gradient for every grid cell corner once) and also ensures that every corner always gets the same gradient (our &lt;code&gt;random_gradient&lt;/code&gt; function has no knowledge about the outside state of our function, so it'll happily generate as many random vectors as we ask for).&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fnr.3&quot; href=&quot;#fn.3&quot; class=&quot;footref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-typescript&quot;&gt;&lt;span class=&quot;linenr&quot;&gt;55: &lt;/span&gt;&lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;offset_vectors&lt;/span&gt;: &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt;[] = [
&lt;span class=&quot;linenr&quot;&gt;56: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt;(0, 0),
&lt;span class=&quot;linenr&quot;&gt;57: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt;(1, 0),
&lt;span class=&quot;linenr&quot;&gt;58: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt;(0, 1),
&lt;span class=&quot;linenr&quot;&gt;59: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt;(1, 1),
&lt;span class=&quot;linenr&quot;&gt;60: &lt;/span&gt;];
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
We then create four offset vectors, which will help us address the grid cell's corners.
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-typescript&quot;&gt;&lt;span class=&quot;linenr&quot;&gt;62: &lt;/span&gt;&lt;span class=&quot;org-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;org-function-name&quot;&gt;noise&lt;/span&gt;(&lt;span class=&quot;org-variable-name&quot;&gt;x&lt;/span&gt;: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;, &lt;span class=&quot;org-variable-name&quot;&gt;y&lt;/span&gt;: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;): &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt; {
&lt;span class=&quot;linenr&quot;&gt;63: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;coordinate_vector&lt;/span&gt; = &lt;span class=&quot;org-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt;(x, y);
&lt;span class=&quot;linenr&quot;&gt;64: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;floored_vector&lt;/span&gt; = &lt;span class=&quot;org-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;org-type&quot;&gt;Vector&lt;/span&gt;(Math.&lt;span class=&quot;org-function-call&quot;&gt;floor&lt;/span&gt;(x), Math.&lt;span class=&quot;org-function-call&quot;&gt;floor&lt;/span&gt;(y));
&lt;span class=&quot;linenr&quot;&gt;65: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;fraction_vector&lt;/span&gt; = coordinate_vector.&lt;span class=&quot;org-function-call&quot;&gt;sub&lt;/span&gt;(floored_vector);
&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;
Our function takes in an X,Y pair representing the coordinates of a pixel. This pair is turned into three separate vectors:
&lt;/p&gt;
&lt;ul class=&quot;org-ul&quot;&gt;
&lt;li&gt;one for the raw coordinates (&lt;code&gt;coordinate_vector&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;one for the grid cell (&lt;code&gt;floored_vector&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;and finally the relative position of the coordinates inside the grid cell (&lt;code&gt;fraction_vector&lt;/code&gt;),&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-typescript&quot;&gt;&lt;span class=&quot;linenr&quot;&gt;66: &lt;/span&gt;  &lt;span class=&quot;org-comment-delimiter&quot;&gt;// &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;top left, top right, bottom left, bottom right
&lt;span class=&quot;linenr&quot;&gt;67: &lt;/span&gt;&lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; [&lt;span class=&quot;org-variable-name&quot;&gt;tl&lt;/span&gt;, &lt;span class=&quot;org-variable-name&quot;&gt;tr&lt;/span&gt;, &lt;span class=&quot;org-variable-name&quot;&gt;bl&lt;/span&gt;, &lt;span class=&quot;org-variable-name&quot;&gt;br&lt;/span&gt;] = offset_vectors.&lt;span class=&quot;org-function-call&quot;&gt;map&lt;/span&gt;((&lt;span class=&quot;org-variable-name&quot;&gt;offset_vector&lt;/span&gt;) &lt;span class=&quot;org-keyword&quot;&gt;=&amp;gt;&lt;/span&gt; {
&lt;span class=&quot;linenr&quot;&gt;68: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;corner_vector&lt;/span&gt; = floored_vector.&lt;span class=&quot;org-function-call&quot;&gt;add&lt;/span&gt;(offset_vector);
&lt;span class=&quot;linenr&quot;&gt;69: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;gradient&lt;/span&gt; = &lt;span class=&quot;org-function-call&quot;&gt;get_gradient&lt;/span&gt;(corner_vector);
&lt;span class=&quot;linenr&quot;&gt;70: &lt;/span&gt;    
&lt;span class=&quot;linenr&quot;&gt;71: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;return&lt;/span&gt; coordinate_vector.&lt;span class=&quot;org-function-call&quot;&gt;sub&lt;/span&gt;(corner_vector).&lt;span class=&quot;org-function-call&quot;&gt;dot_product&lt;/span&gt;(gradient);
&lt;span class=&quot;linenr&quot;&gt;72: &lt;/span&gt;  });
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Next we calculate the dot products between the vector of our pixel's coordinates and each corner's gradient. &lt;code&gt;get_gradient&lt;/code&gt; ensures we always get the same gradient for a given corner.
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-typescript&quot;&gt;&lt;span class=&quot;linenr&quot;&gt;73: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;upper_interpolation&lt;/span&gt; = &lt;span class=&quot;org-function-call&quot;&gt;interpolate&lt;/span&gt;(fraction_vector.x, tl, tr);
&lt;span class=&quot;linenr&quot;&gt;74: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;lower_interpolation&lt;/span&gt; = &lt;span class=&quot;org-function-call&quot;&gt;interpolate&lt;/span&gt;(fraction_vector.x, bl, br);
&lt;span class=&quot;linenr&quot;&gt;75: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;76: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;noise_value&lt;/span&gt; = &lt;span class=&quot;org-function-call&quot;&gt;interpolate&lt;/span&gt;(
&lt;span class=&quot;linenr&quot;&gt;77: &lt;/span&gt;    fraction_vector.y,
&lt;span class=&quot;linenr&quot;&gt;78: &lt;/span&gt;    upper_interpolation,
&lt;span class=&quot;linenr&quot;&gt;79: &lt;/span&gt;    lower_interpolation,
&lt;span class=&quot;linenr&quot;&gt;80: &lt;/span&gt;  );
&lt;span class=&quot;linenr&quot;&gt;81: &lt;/span&gt;    
&lt;span class=&quot;linenr&quot;&gt;82: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;return&lt;/span&gt; noise_value;
&lt;span class=&quot;linenr&quot;&gt;83: &lt;/span&gt;}
&lt;span class=&quot;linenr&quot;&gt;84: &lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Finally we do a 2D interpolation using the function we defined &lt;a href=&quot;#smoothstep&quot;&gt;earlier&lt;/a&gt;. We first interpolate between the ranges of &lt;code&gt;[top left, top right]&lt;/code&gt; and &lt;code&gt;[bottom left, bottom right]&lt;/code&gt; using the pixel coordinate's relative X position, and then, with one final interpolation using the coordinate's relative Y position, we get our noise value.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;outline-container-main-loop&quot; class=&quot;outline-3&quot;&gt;
&lt;h3 id=&quot;main-loop&quot;&gt;&lt;span class=&quot;section-number-3&quot;&gt;3.4.&lt;/span&gt; Main loop&lt;/h3&gt;
&lt;div id=&quot;text-3-4&quot; class=&quot;outline-text-3&quot;&gt;
&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-typescript&quot;&gt;&lt;span class=&quot;linenr&quot;&gt; 85: &lt;/span&gt;&lt;span class=&quot;org-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;org-function-name&quot;&gt;generate_noise&lt;/span&gt;(
&lt;span class=&quot;linenr&quot;&gt; 86: &lt;/span&gt;  &lt;span class=&quot;org-variable-name&quot;&gt;width&lt;/span&gt;: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;,
&lt;span class=&quot;linenr&quot;&gt; 87: &lt;/span&gt;  &lt;span class=&quot;org-variable-name&quot;&gt;height&lt;/span&gt;: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;,
&lt;span class=&quot;linenr&quot;&gt; 88: &lt;/span&gt;  &lt;span class=&quot;org-variable-name&quot;&gt;resolution&lt;/span&gt;: &lt;span class=&quot;org-typescript-primitive&quot;&gt;number&lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt; 89: &lt;/span&gt;): &lt;span class=&quot;org-type&quot;&gt;Float32Array&lt;/span&gt; {
&lt;span class=&quot;linenr&quot;&gt; 90: &lt;/span&gt;  gradients.&lt;span class=&quot;org-function-call&quot;&gt;clear&lt;/span&gt;();
&lt;span class=&quot;linenr&quot;&gt; 91: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt; 92: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;values&lt;/span&gt; = &lt;span class=&quot;org-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;org-type&quot;&gt;Float32Array&lt;/span&gt;(width * height);
&lt;span class=&quot;linenr&quot;&gt; 93: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt; 94: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class=&quot;org-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;y&lt;/span&gt; = 0; y &amp;lt; &lt;span class=&quot;org-type&quot;&gt;height&lt;/span&gt;; &lt;span class=&quot;org-type&quot;&gt;y&lt;/span&gt;++) {
&lt;span class=&quot;linenr&quot;&gt; 95: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class=&quot;org-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;x&lt;/span&gt; = 0; x &amp;lt; &lt;span class=&quot;org-type&quot;&gt;width&lt;/span&gt;; &lt;span class=&quot;org-type&quot;&gt;x&lt;/span&gt;++) {
&lt;span class=&quot;linenr&quot;&gt; 96: &lt;/span&gt;      &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;x_percent&lt;/span&gt; = x / width;
&lt;span class=&quot;linenr&quot;&gt; 97: &lt;/span&gt;      &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;y_percent&lt;/span&gt; = y / height;
&lt;span class=&quot;linenr&quot;&gt; 98: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt; 99: &lt;/span&gt;      &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;value&lt;/span&gt; = &lt;span class=&quot;org-function-call&quot;&gt;noise&lt;/span&gt;(
&lt;span class=&quot;linenr&quot;&gt;100: &lt;/span&gt;        x_percent * resolution,
&lt;span class=&quot;linenr&quot;&gt;101: &lt;/span&gt;        y_percent * resolution,
&lt;span class=&quot;linenr&quot;&gt;102: &lt;/span&gt;      );
&lt;span class=&quot;linenr&quot;&gt;103: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;104: &lt;/span&gt;      &lt;span class=&quot;org-comment-delimiter&quot;&gt;// &lt;/span&gt;&lt;span class=&quot;org-comment&quot;&gt;Map [-1, 1] =&amp;gt; [0, 1]
&lt;span class=&quot;linenr&quot;&gt;105: &lt;/span&gt;&lt;/span&gt;      values[y * width + x] = (value + 1) / 2;
&lt;span class=&quot;linenr&quot;&gt;106: &lt;/span&gt;    }
&lt;span class=&quot;linenr&quot;&gt;107: &lt;/span&gt;  }
&lt;span class=&quot;linenr&quot;&gt;108: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;109: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;return&lt;/span&gt; values;
&lt;span class=&quot;linenr&quot;&gt;110: &lt;/span&gt;}
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
First we clear the gradients map, as we don't want any previous data to interfere in case this isn't the first time we run the function.
&lt;/p&gt;

&lt;p&gt;
Then we iterate through every pixel in our rectangle and calculate its coordinates, mapping &lt;code&gt;[0, width]&lt;/code&gt; and &lt;code&gt;[0, height]&lt;/code&gt; to &lt;code&gt;[0, 1]&lt;/code&gt;. We also take a parameter called &lt;code&gt;resolution&lt;/code&gt;, this determines how &amp;quot;zoomed in&amp;quot; our noise looks like:
&lt;/p&gt;

&lt;div class=&quot;sameline&quot;&gt;

&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-01-20-perlin/big_resolution.avif&quot; alt=&quot;big_resolution.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 5: &lt;/span&gt;Noise with high &lt;code&gt;resolution&lt;/code&gt; value.&lt;/p&gt;
&lt;/div&gt;


&lt;div class=&quot;figure&quot;&gt;
&lt;p&gt;&lt;img src=&quot;./assets/imgs/2026-01-20-perlin/small_resolution.avif&quot; alt=&quot;small_resolution.avif&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;figure-number&quot;&gt;Figure 6: &lt;/span&gt;Noise with low &lt;code&gt;resolution&lt;/code&gt; value.&lt;/p&gt;
&lt;/div&gt;

&lt;/div&gt;

&lt;p&gt;
Finally, our noise function returns values in the range of &lt;code&gt;[-1, 1]&lt;/code&gt;, but we need &lt;code&gt;[0, 1]&lt;/code&gt;, so we add one to our result, mapping it to &lt;code&gt;[0, 2]&lt;/code&gt; and then divide it by 2, thus end up with &lt;code&gt;[0, 1]&lt;/code&gt;.
&lt;/p&gt;

&lt;div class=&quot;org-src-container&quot;&gt;
&lt;pre class=&quot;src src-typescript&quot;&gt;&lt;span class=&quot;linenr&quot;&gt;111: &lt;/span&gt;document.&lt;span class=&quot;org-function-call&quot;&gt;getElementById&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;button&amp;quot;&lt;/span&gt;).&lt;span class=&quot;org-function-call&quot;&gt;addEventListener&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;click&amp;quot;&lt;/span&gt;, () &lt;span class=&quot;org-keyword&quot;&gt;=&amp;gt;&lt;/span&gt; {
&lt;span class=&quot;linenr&quot;&gt;112: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;canvas&lt;/span&gt; = document.&lt;span class=&quot;org-function-call&quot;&gt;getElementById&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;canvas&amp;quot;&lt;/span&gt;) &lt;span class=&quot;org-keyword&quot;&gt;as&lt;/span&gt; HTMLCanvasElement;
&lt;span class=&quot;linenr&quot;&gt;113: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;ctx&lt;/span&gt; = canvas.&lt;span class=&quot;org-function-call&quot;&gt;getContext&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;2d&amp;quot;&lt;/span&gt;);
&lt;span class=&quot;linenr&quot;&gt;114: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;115: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;maxFactor&lt;/span&gt; = &lt;span class=&quot;org-function-call&quot;&gt;Number&lt;/span&gt;(
&lt;span class=&quot;linenr&quot;&gt;116: &lt;/span&gt;    (document.&lt;span class=&quot;org-function-call&quot;&gt;getElementById&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;factor&amp;quot;&lt;/span&gt;) &lt;span class=&quot;org-keyword&quot;&gt;as&lt;/span&gt; HTMLInputElement).value
&lt;span class=&quot;linenr&quot;&gt;117: &lt;/span&gt;  );
&lt;span class=&quot;linenr&quot;&gt;118: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;119: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;baseFrequency&lt;/span&gt; = &lt;span class=&quot;org-function-call&quot;&gt;Number&lt;/span&gt;(
&lt;span class=&quot;linenr&quot;&gt;120: &lt;/span&gt;    (document.&lt;span class=&quot;org-function-call&quot;&gt;getElementById&lt;/span&gt;(&lt;span class=&quot;org-string&quot;&gt;&amp;quot;frequency&amp;quot;&lt;/span&gt;) &lt;span class=&quot;org-keyword&quot;&gt;as&lt;/span&gt; HTMLInputElement).value
&lt;span class=&quot;linenr&quot;&gt;121: &lt;/span&gt;  );
&lt;span class=&quot;linenr&quot;&gt;122: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;123: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;width&lt;/span&gt; = canvas.width;
&lt;span class=&quot;linenr&quot;&gt;124: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;height&lt;/span&gt; = canvas.height;
&lt;span class=&quot;linenr&quot;&gt;125: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;imageData&lt;/span&gt; = ctx.&lt;span class=&quot;org-function-call&quot;&gt;getImageData&lt;/span&gt;(0, 0, width, height);
&lt;span class=&quot;linenr&quot;&gt;126: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;127: &lt;/span&gt;  imageData.data.&lt;span class=&quot;org-function-call&quot;&gt;fill&lt;/span&gt;(0);
&lt;span class=&quot;linenr&quot;&gt;128: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;129: &lt;/span&gt;  &lt;span class=&quot;org-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class=&quot;org-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;factor&lt;/span&gt; = 0; factor &amp;lt;= maxFactor; factor++) {
&lt;span class=&quot;linenr&quot;&gt;130: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;values&lt;/span&gt; = &lt;span class=&quot;org-function-call&quot;&gt;generate_noise&lt;/span&gt;(width, height, baseFrequency * (2 ** factor));
&lt;span class=&quot;linenr&quot;&gt;131: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;132: &lt;/span&gt;    &lt;span class=&quot;org-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class=&quot;org-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;y&lt;/span&gt; = 0; y &amp;lt; &lt;span class=&quot;org-type&quot;&gt;height&lt;/span&gt;; &lt;span class=&quot;org-type&quot;&gt;y&lt;/span&gt;++) {
&lt;span class=&quot;linenr&quot;&gt;133: &lt;/span&gt;      &lt;span class=&quot;org-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class=&quot;org-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;x&lt;/span&gt; = 0; x &amp;lt; &lt;span class=&quot;org-type&quot;&gt;width&lt;/span&gt;; &lt;span class=&quot;org-type&quot;&gt;x&lt;/span&gt;++) {
&lt;span class=&quot;linenr&quot;&gt;134: &lt;/span&gt;        &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;idx&lt;/span&gt; = y * width + x;
&lt;span class=&quot;linenr&quot;&gt;135: &lt;/span&gt;        &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;value&lt;/span&gt; = values[idx];
&lt;span class=&quot;linenr&quot;&gt;136: &lt;/span&gt;        &lt;span class=&quot;org-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;org-variable-name&quot;&gt;color&lt;/span&gt; = Math.&lt;span class=&quot;org-function-call&quot;&gt;floor&lt;/span&gt;(Math.&lt;span class=&quot;org-function-call&quot;&gt;max&lt;/span&gt;(0, value) * 255 / (2 ** (factor + 1)));
&lt;span class=&quot;linenr&quot;&gt;137: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;138: &lt;/span&gt;        imageData.data[idx * 4 + 0] += color / 1.5;
&lt;span class=&quot;linenr&quot;&gt;139: &lt;/span&gt;        imageData.data[idx * 4 + 1] += color / 1.3;
&lt;span class=&quot;linenr&quot;&gt;140: &lt;/span&gt;        imageData.data[idx * 4 + 2] += color;
&lt;span class=&quot;linenr&quot;&gt;141: &lt;/span&gt;        imageData.data[idx * 4 + 3] = 255;
&lt;span class=&quot;linenr&quot;&gt;142: &lt;/span&gt;      }
&lt;span class=&quot;linenr&quot;&gt;143: &lt;/span&gt;    }
&lt;span class=&quot;linenr&quot;&gt;144: &lt;/span&gt;  }
&lt;span class=&quot;linenr&quot;&gt;145: &lt;/span&gt;
&lt;span class=&quot;linenr&quot;&gt;146: &lt;/span&gt;  ctx.&lt;span class=&quot;org-function-call&quot;&gt;putImageData&lt;/span&gt;(imageData, 0, 0)
&lt;span class=&quot;linenr&quot;&gt;147: &lt;/span&gt;});
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Since I don't want to cause lag immediately on page load, I bound the noise generation to a button that you can find below. We request the underlying image data from our canvas element and zero it out. This is done as a precaution in case the user presses the button multiple times. Without this reset, the &lt;code&gt;+=&lt;/code&gt; operations below would quickly oversaturate our output image.
&lt;/p&gt;

&lt;p&gt;
For this demo, I added two user-controlled variables, these being &lt;code&gt;factor&lt;/code&gt;, which determines how many layers of noise we generate and &lt;code&gt;baseFrequency&lt;/code&gt;, which determines the initial resolution (i.e. roughness) of the noise. For some examples, try a factor of 8 and frequency of 1 for a gentle fog and 4 and 16 for a fuzzy carpet.
&lt;/p&gt;

&lt;p&gt;
We generate a noise array with the given iteration's frequency and fill in the current pixel based on the value of the noise. I skewed the numbers a little to get a pleasant blue color, without any modification, we'd be left with a grayscale image.
&lt;/p&gt;

&lt;style&gt;

  #demo {
    margin: 1rem auto;
    width: fit-content;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
  }

  #controls {
    background-color: #aaa;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    padding: 0.25rem;
    border: 1px solid #888;
  }
&lt;/style&gt;

&lt;div id=&quot;demo&quot;&gt;
  &lt;canvas width=&quot;300px&quot; style=&quot;width: 300px; height: 300px; border: 1px solid #888; background-color: #aaa;&quot; id=&quot;canvas&quot; height=&quot;300px&quot;&gt;
  &lt;/canvas&gt;
  
  &lt;div id=&quot;controls&quot;&gt;
    Factor: &lt;input value=&quot;5&quot; type=&quot;range&quot; min=&quot;1&quot; max=&quot;8&quot; id=&quot;factor&quot; /&gt;
    Base frequency: &lt;input value=&quot;2&quot; type=&quot;range&quot; min=&quot;1&quot; max=&quot;64&quot; id=&quot;frequency&quot; /&gt;
    &lt;button id=&quot;button&quot;&gt;Start&lt;/button&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;script src=&quot;./assets/js/perlin.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;
Thank you for reading this post and I hope you may have learned something fun! If you'd like to read the code as a whole, you can access it &lt;a href=&quot;https://codeberg.org/nemin/blog/src/branch/main/assets/js/perlin.ts&quot;&gt;here&lt;/a&gt;.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;h2 class=&quot;footnotes&quot;&gt;Footnotes: &lt;/h2&gt;
&lt;div id=&quot;text-footnotes&quot;&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.1&quot; href=&quot;#fnr.1&quot; class=&quot;footnum&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
In fact, we could easily get away without a class and just use inlined operations with either &lt;code&gt;{x: number, y: number}&lt;/code&gt; objects or &lt;code&gt;[x, y]&lt;/code&gt; arrays, but I believe the class-based version provides most pedagogical clarity, so that's the one I went with.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.2&quot; href=&quot;#fnr.2&quot; class=&quot;footnum&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Technically the &lt;code&gt;Map&lt;/code&gt; really doesn't care what we pass in as key. But since all keys are coerced into primitives, all of our &lt;code&gt;Vector&lt;/code&gt;-s would be turned into strings in the form of the dreaded &lt;code&gt;&amp;quot;[object Object]&amp;quot;&lt;/code&gt; and lose their identity, which would pretty much defeat the point of a hash-map. To avoid this, we override &lt;code&gt;toString&lt;/code&gt; and instead return a sensible and, more importantly, unique string representation, that our map can take with no issue.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;footdef&quot;&gt;&lt;sup&gt;&lt;a role=&quot;doc-backlink&quot; id=&quot;fn.3&quot; href=&quot;#fnr.3&quot; class=&quot;footnum&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; &lt;div role=&quot;doc-footnote&quot; class=&quot;footpara&quot;&gt;&lt;p class=&quot;footpara&quot;&gt;
Those who are already familiar with the algorithm (or just read its Wikipedia page before continuing this article) might have realized that this is a vastly different way of generating gradients compared to the original version.
&lt;/p&gt;

&lt;p class=&quot;footpara&quot;&gt;
In the original we only have a handful (8 or 16) pre-generated gradient vectors and we pick one of them based on a somewhat involved process of hashing the current coordinate and then chopping off the lower 4 bits of the resulting number. While this most likely results in faster code, I find that it somewhat obscures of what we're really doing.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;/div&gt;</content></entry></feed>