<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" version="2.0">
  <channel>
    <title>Nikolay Bryskin</title>
    <link>https://zxczxc.dev/</link>
    <atom:link href="https://zxczxc.dev/feed.xml" rel="self" type="application/rss+xml"/>
    <description>Notes on systems, Linux, and engineering</description>
    <lastBuildDate>Fri, 24 Jul 2026 16:18:41 GMT</lastBuildDate>
    <language>en</language>
    <generator>Lume 3.2.6</generator>
    <item>
      <title>Mouse over WiFi</title>
      <link>https://zxczxc.dev/posts/mouse-over-wifi/</link>
      <guid isPermaLink="false">https://zxczxc.dev/posts/mouse-over-wifi/</guid>
      <description>
        Both USB-C ports were taken — so the mouse got plugged into the ARM board nearby and forwarded to the laptop as input events: netevent, systemd socket activation, and a Tailscale-only port.
      </description>
      <content:encoded>
        <![CDATA[<p>Today I found that I needed to connect a USB mouse to my laptop — and both
USB-C ports were occupied, one by the monitor, one by power. Turns out it's
not a huge deal on Linux if you have another PC nearby.</p>
<p>The reasonable fix is a €3 USB-C adapter. But the adapter is three days of
shipping away, and the other PC — a little Rockchip ARM board that lives on
my desk — was right there, with free USB-A ports and a network connection to
my laptop. On Linux, &quot;the mouse is plugged into the wrong computer&quot; is not
really a hardware problem. It's a routing problem.</p>
<!--more-->
<h2 id="pick-your-layer" tabindex="-1"><a href="https://zxczxc.dev/posts/mouse-over-wifi/#pick-your-layer" class="header-anchor">Pick your layer</a></h2>
<p>There are two ways to ship a USB device across a network:</p>
<p><strong>Forward the USB device itself</strong> — that's <code>usbip</code>, which is in the mainline
kernel. The remote host sees a real USB device, URBs get shuttled over TCP,
and everything that could talk to the physical device can talk to the remote
one. That's the right tool when something needs the <em>device</em> — firmware
flashers, smartcard readers, license dongles.</p>
<p><strong>Forward the input events</strong> — a mouse is not an interesting USB device. By
the time applications see it, it's just a stream of tiny <code>input_event</code>
structs coming out of <code>/dev/input/eventN</code>: relative X, relative Y, button,
sync. The kernel will happily accept the same structs back through
<code>/dev/uinput</code> and synthesize a new input device out of them. So instead of
tunneling USB, you can read events on one machine and replay them on the
other. Less protocol, less latency sensitivity, and it works regardless of
what's on the other end — Wayland, X11, a bare console.</p>
<p>For a mouse, events win. The tool for this is
<a href="https://github.com/Blub/netevent">netevent</a> — a ~130 KB binary with two
relevant subcommands: <code>cat</code> (open an event device, dump its events to
stdout) and <code>create</code> (read events from stdin, create a uinput clone). It's
<code>netevent-git</code> in the AUR.</p>
<h2 id="the-five-minute-version" tabindex="-1"><a href="https://zxczxc.dev/posts/mouse-over-wifi/#the-five-minute-version" class="header-anchor">The five-minute version</a></h2>
<p>Find the mouse on the machine it's plugged into. <code>/dev/input/by-id/</code> gives
you stable names, unlike <code>eventN</code> which reshuffles on replug:</p>
<pre><code class="language-console">$ ls -l /dev/input/by-id/
lrwxrwxrwx 1 root root  9 platform-noserial-if01-event-mouse -&gt; ../event8
lrwxrwxrwx 1 root root 13 usb-Logitech_USB_Receiver-hidraw -&gt; ../../hidraw0
</code></pre>
<p>Then, from the laptop, one pipe:</p>
<pre><code class="language-console">$ ssh zxc-pc 'sudo netevent cat /dev/input/by-id/platform-noserial-if01-event-mouse' \
    | netevent create
</code></pre>
<p>That's genuinely all of it. <code>netevent cat</code> grabs the device exclusively
(<code>EVIOCGRAB</code> — the server machine stops seeing the mouse), streams events
over the SSH connection, and <code>netevent create</code> conjures a virtual &quot;Logitech
USB Receiver Mouse&quot; on the laptop that the desktop picks up like any
hotplugged device. The cursor moves. SSH gives you encryption and auth for
free.</p>
<p>I used exactly this for about an hour. Then the sysadmin brain kicked in,
because the pipe has problems:</p>
<ul>
<li>it needs <code>sudo</code> on the server (<code>/dev/input/*</code> is root:input),</li>
<li>it dies silently on suspend, roaming, or a Wi-Fi blip, and takes the
mouse with it,</li>
<li>it drags my interactive SSH config — agent forwarding, a GPG socket
RemoteForward — into what should be a dumb transport,</li>
<li>and it plants a landmine that I would step on two hours later. We'll get
there.</li>
</ul>
<h2 id="the-grown-up-version%3A-socket-activation" tabindex="-1"><a href="https://zxczxc.dev/posts/mouse-over-wifi/#the-grown-up-version%3A-socket-activation" class="header-anchor">The grown-up version: socket activation</a></h2>
<p>What I actually want is an appliance: the server <em>exports</em> the mouse,
permanently and cheaply; the laptop <em>attaches</em> when it wants to; the grab —
the mouse going dead on the server — should last exactly as long as the
attachment. That maps one-to-one onto systemd socket activation:</p>
<p><img src="https://zxczxc.dev/uploads/mouse-over-wifi/architecture.svg" alt="Events flow down through zxc-pc — receiver, /dev/input/event8, netevent cat — across a Tailscale-only TCP link on port 8763, then up through the laptop — socat, netevent create, /dev/uinput — into a virtual mouse"></p>
<p>Two unit files on the server. The socket:</p>
<pre><code class="language-ini"># /etc/systemd/system/zxc-mouse.socket
[Unit]
Description=Export Logitech mouse over Tailscale (netevent)

[Socket]
ListenStream=8763
BindToDevice=tailscale0
Accept=yes
MaxConnections=1

[Install]
WantedBy=sockets.target
</code></pre>
<p>And the per-connection service it spawns:</p>
<pre><code class="language-ini"># /etc/systemd/system/zxc-mouse@.service
[Unit]
Description=netevent mouse stream to %i

[Service]
ExecStart=/usr/bin/netevent cat /dev/input/by-id/platform-noserial-if01-event-mouse
StandardInput=socket
StandardOutput=socket
StandardError=journal
DynamicUser=yes
SupplementaryGroups=input
</code></pre>
<p>Almost every line here is doing security or lifecycle work:</p>
<ul>
<li><strong><code>Accept=yes</code></strong> is inetd mode: each TCP connection spawns a fresh
instance with the connection as stdin/stdout. <code>netevent cat</code> doesn't know
the network exists — it writes to &quot;stdout&quot; and systemd has already wired
that to the socket.</li>
<li><strong><code>BindToDevice=tailscale0</code></strong> scopes the listener with <code>SO_BINDTODEVICE</code>:
connections arriving via any other interface never reach it. The mouse is
reachable over the tailnet and nothing else — no LAN exposure, and
Tailscale brings the encryption and authentication that raw TCP doesn't.</li>
<li><strong><code>MaxConnections=1</code></strong> — a second client gets refused instead of fighting
over the grab.</li>
<li><strong><code>DynamicUser=yes</code> + <code>SupplementaryGroups=input</code></strong> is my favorite part.
The prototype needed <code>sudo</code> for exactly one reason: reading
<code>/dev/input/event8</code>, which is <code>root:input 0660</code>. But that's not a <em>root</em>
problem, it's a <em>group</em> problem — so let systemd mint a throwaway user
that exists only for the lifetime of the connection and holds exactly one
privilege: the <code>input</code> group. No sudo rules, no long-lived root process,
no real user to keep in the group forever (permanent <code>input</code> membership
is a standing keylogger license — every process you run can read every
keystroke).</li>
</ul>
<p>The lifecycle falls out of the socket activation for free:</p>
<p><img src="https://zxczxc.dev/uploads/mouse-over-wifi/sequence.svg" alt="Sequence diagram: laptop connects over the tailnet to port 8763, zxc-pc spawns an instance which grabs the mouse; events stream into the uinput clone; on disconnect the instance exits and the grab is released"></p>
<p>Nobody is grabbing anything while no one is attached. The mouse is a shared
resource with connection-scoped ownership, which is exactly the semantics
you'd want and exactly what an always-running daemon wouldn't give you.</p>
<p>On the laptop, the attaching end is one more unit:</p>
<pre><code class="language-ini"># /etc/systemd/system/zxc-mouse.service
[Unit]
Description=Attach Logitech mouse exported by zxc-pc

[Service]
ExecStart=/bin/sh -c 'socat -u TCP:zxc-pc:8763,keepalive,keepidle=5,keepintvl=5,keepcnt=2 STDOUT | netevent create --duplicates=resume'
Restart=always
RestartSec=2
RestartSteps=8
RestartMaxDelaySec=60
DynamicUser=yes
SupplementaryGroups=input
</code></pre>
<p>Same trick on this side — <code>netevent create</code> needs <code>/dev/uinput</code>, which is
also <code>root:input 0660</code>, so the same throwaway-user-plus-one-group pattern
covers it. <code>socat</code> instead of <code>nc</code> because of the keepalive knobs: a mouse
stream is silent when the mouse is idle, so without keepalives a dead peer
looks identical to a boring evening. With <code>keepidle=5,keepintvl=5,keepcnt=2</code>
a vanished server is detected in ~15 seconds, the pipe exits, and
<code>Restart=always</code> walks the backoff (2 s → 60 s) until the server is back.</p>
<p>One deliberate omission: this unit has <strong>no <code>[Install]</code> section</strong>. It cannot
be enabled at boot, even by accident. <code>systemctl start zxc-mouse</code> is the new
&quot;plug in the mouse&quot;; <code>systemctl stop zxc-mouse</code> is unplugging it — and
handing it back to the machine it's physically attached to.</p>
<p>Did the interface scoping actually work? Two checks:</p>
<pre><code class="language-console">$ ssh zxc-pc ss -tln | grep 8763
LISTEN 0  4096  *%tailscale0:8763  *:*        # the % is SO_BINDTODEVICE

$ nc -z -w3 192.168.88.247 8763               # via LAN: refused
$ systemctl start zxc-mouse &amp;&amp; systemctl is-active zxc-mouse
active                                         # via tailnet: streaming
</code></pre>
<h2 id="the-landmine" tabindex="-1"><a href="https://zxczxc.dev/posts/mouse-over-wifi/#the-landmine" class="header-anchor">The landmine</a></h2>
<p>I promised a landmine. First start of the polished new service:</p>
<pre><code>sh[1510469]: error: error while expecting hello packet: Success
</code></pre>
<p>The client connects, expects netevent's protocol hello, and gets EOF —
reported with errno 0, the error message equivalent of a shrug. The server
journal has the real story:</p>
<pre><code>netevent[61925]: error: failed to grab input device: Device or resource busy
</code></pre>
<p>Something already held the exclusive grab. It was the prototype — the SSH
pipe from two hours earlier. I had killed my end of it, but the remote
<code>netevent cat</code> had no idea: it only touches its dead socket when the mouse
<em>moves</em>, and the mouse, grabbed and therefore inert, had not moved. A
process whose only job is to report motion, kept alive by the absence of
motion, holding the grab forever. The new service died against it on every
retry.</p>
<p>One <code>pkill</code> on the server later, the service came up and stayed up.</p>
<h2 id="was-it-worth-it%3F" tabindex="-1"><a href="https://zxczxc.dev/posts/mouse-over-wifi/#was-it-worth-it%3F" class="header-anchor">Was it worth it?</a></h2>
<p>Latency over the LAN (Tailscale negotiates a direct path between machines
on the same network) is imperceptible — mouse events are a few dozen bytes
each, and the event clock is carried in the protocol. The whole thing cost
three unit files and one 130 KB tool, runs with no root process and no sudo
rule on either machine, exposes one port on one virtual interface, and
gives the mouse back the moment I stop the service.</p>
<p>The dongle is still not here. The mouse never found out it's being
forwarded.</p>
]]>
      </content:encoded>
      <category>linux</category>
      <category>systemd</category>
      <category>networking</category>
      <category>tailscale</category>
      <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
      <meta property="og:image" content="https://zxczxc.dev/uploads/mouse-over-wifi/cover.jpg"/>
    </item>
    <item>
      <title>AI agents are bad at code refactoring</title>
      <link>https://zxczxc.dev/posts/why-ai-agents-are-bad-at-code-refactoring/</link>
      <guid isPermaLink="false">https://zxczxc.dev/posts/why-ai-agents-are-bad-at-code-refactoring/</guid>
      <description>
        Agents ace greenfield work because training data runs requirements→design→code. Refactoring runs that pipeline backwards — and maybe rewrites win.
      </description>
      <content:encoded>
        <![CDATA[<p>Give an AI agent an empty directory and a page of requirements, and it will impress you. Give the same agent an existing codebase and a one-line feature request, and there's a good chance it will leave the project slightly worse while fully satisfying the request.</p>
<!--more-->
<p>And &quot;existing&quot; no longer means old. In the agentic era a codebase can reach that state — drifted design, blurred patterns, obsolete docs — in a matter of weeks, sometimes days, often with an agent as the original author. The archaeology now has to happen on sites that are still under construction.</p>
<p>I want to walk through why I think this happens, and why I suspect it's not a temporary weakness.</p>
<h2 id="two-forward-passes" tabindex="-1"><a href="https://zxczxc.dev/posts/why-ai-agents-are-bad-at-code-refactoring/#two-forward-passes" class="header-anchor">Two forward passes</a></h2>
<p>When an agent builds a project from scratch, the pipeline is:</p>
<p><strong>requirements → design → code</strong></p>
<p>The user states what is needed. The agent converts requirements into a software design, picking patterns that fit — it has seen thousands. Then it converts the design into code, step by step.</p>
<p>Both arrows point the same way, and both are <em>forward passes</em> — and humanity has documented them exhaustively. Every architecture book, every pattern catalog, every tutorial, every open-source project is a record of someone moving in this direction. A model trained on public text has effectively watched this pipeline run millions of times. It's not surprising that agents are good at it. It would be surprising if they weren't.</p>
<h2 id="refactoring-runs-the-pipeline-backwards" tabindex="-1"><a href="https://zxczxc.dev/posts/why-ai-agents-are-bad-at-code-refactoring/#refactoring-runs-the-pipeline-backwards" class="header-anchor">Refactoring runs the pipeline backwards</a></h2>
<p><img src="https://zxczxc.dev/uploads/why-ai-agents-are-bad-at-code-refactoring/a6wju0m2pyp8fc2yixfx.jpg" alt="Mental Gymnastics meme: &quot;Writing new code&quot; over gymnasts calmly standing on a mat; &quot;Refactoring an existing codebase&quot; over athletes doing extreme acrobatics off a burning van"></p>
<p>Now the opposite situation. The agent is dropped into an existing project. What it holds is the pipeline's <em>output</em> — the code. The inputs are exactly what's missing:</p>
<ul>
<li>The design has drifted; the code no longer matches any design that was ever drawn.</li>
<li>The patterns are blurred by local, not-so-clean fixes and workarounds.</li>
<li>The documentation and comments describe a system that no longer exists.</li>
</ul>
<p>The engineer's task — human or AI — becomes:</p>
<ol>
<li>Recover the <em>actual</em> current requirements (they've drifted from whatever the docs claim).</li>
<li>Discern the real patterns inside the noisy code.</li>
<li>For each anomaly, figure out <em>why</em> it exists: legacy? An unfinished plan? A past mistake? A requirement that moved?</li>
<li>Correct the requirements picture based on what the code investigation revealed.</li>
<li>Decide what design would be ideal for the corrected requirements.</li>
<li>Plan a path from the current code state to the target state.</li>
<li>Write the code.</li>
</ol>
<p>Look at the direction of these steps. Steps 1–4 run backwards: from code to design, from design to requirements. Steps 5 and 7 are the familiar forward passes — the easy, well-trained part. Step 6 is neither: it's plotting a route through a live system that has to keep working at every intermediate state — changing the tires while the car is moving.</p>
<p>And the backward direction has a problem the forward one doesn't: it is <em>underdetermined</em>. Many different pairs of (requirements, design) could have produced the code in front of you. The forward pass loses information; running it backwards means guessing what was lost. The only thing that disambiguates the guesses is history. And while git preserves the <em>what</em> perfectly — it's all there in the repo — the <em>why</em> mostly isn't: it lived in review comments, chat threads, and the heads of people who have moved on.</p>
<h2 id="step-zero%3A-the-smell" tabindex="-1"><a href="https://zxczxc.dev/posts/why-ai-agents-are-bad-at-code-refactoring/#step-zero%3A-the-smell" class="header-anchor">Step zero: the smell</a></h2>
<p>Worse, you rarely know in advance that refactoring is needed. It is almost never a ticket. You sit down to add a feature or fix a bug, and every implementation you sketch <em>smells</em>: each one fights the current design, each needs two workarounds to fit. The decision to refactor is the recognition of that smell — noticing a mismatch between the design you have and the requirement that just arrived.</p>
<p>This is a background process, not a step. A human engineer runs it constantly, motivated by their own future suffering: the person who will maintain this mess is them. An agent's episode ends when the diff lands. Nothing in its loop rewards stopping to question the design; if anything, the training signal runs the other way — &quot;sure, here's your feature&quot; reads as helpful, &quot;we should restructure first&quot; reads as evasive. The agent never has to live in the house it builds.</p>
<h2 id="refactoring-is-a-compression-task" tabindex="-1"><a href="https://zxczxc.dev/posts/why-ai-agents-are-bad-at-code-refactoring/#refactoring-is-a-compression-task" class="header-anchor">Refactoring is a compression task</a></h2>
<p>Strip it down and refactoring is, first of all, <em>compression</em>. Given tens of thousands of lines of code, extract the internal structure. And not only the current structure — also the historical one, because step 3 (<em>why is this here?</em>) is a question about time, not about code.</p>
<p>Two compressions, then. The first — what the design currently is — is hard, but at least the input fits in a context window. The second is harder in principle: the rationale was never written down, or was written down and has rotted along with the docs. If the code was written by an agent, the problem inverts rather than disappears: the <em>why</em> is on record in exhausting detail — but uncompressed, too voluminous to load, and impossible to tell which of it is still true. The agent is asked to decompress a lossy archive with the dictionary thrown away.</p>
<p>What a good human engineer brings here is a <em>prior</em> — and note what the prior is over. Not over patterns, but over <em>decay</em>. Having watched hundreds of codebases evolve, they can look at a warped shape and reconstruct the erosion: this was once a clean separation, then a deadline happened, then a hotfix happened. Their mental library includes the degraded versions of every pattern, because they have personally watched the degradation happen.</p>
<p>Models are trained mostly on snapshots — final states, clean tutorials, curated open source. Their library holds the pristine versions. So faced with drifted code, a model does what it was trained to do: it matches the mess to the nearest clean shape and confidently describes the codebase as the textbook version of whatever it most resembles. But <em>which</em> clean shape, if any, this mess descended from is precisely the question that was supposed to be investigated, not assumed away. Pattern-matching the noise to a pristine archetype is guessing at the story instead of recovering it.</p>
<h2 id="what-agents-do-instead" tabindex="-1"><a href="https://zxczxc.dev/posts/why-ai-agents-are-bad-at-code-refactoring/#what-agents-do-instead" class="header-anchor">What agents do instead</a></h2>
<p><img src="https://zxczxc.dev/uploads/why-ai-agents-are-bad-at-code-refactoring/y2wrftm3gcxbqhqh6nxe.jpg" alt="Drake meme: disapproving at &quot;Refactor the workaround&quot;, approving at &quot;Wrap it in an abstraction layer&quot;"></p>
<p>Deprived of the story, agents default to greenfield style: take the existing code as ground truth, assume everything in it is intentional, and build the new thing on top. A new abstraction here, a new entity there. Nothing is ever deleted; each session adds a stratum. The result is geological: layers on layers, each written as if everything below it were load-bearing.</p>
<p>If you've watched an agent work on a mature project, you've seen this: it doesn't refactor the workaround — it <em>respects</em> it. Wraps it, generalizes it, builds on it. The workaround becomes architecture.</p>
<p>One clarification, because it causes a lot of confusion: agents are quite good at the <em>mechanics</em> of refactoring. Tell one &quot;extract this logic into a module, keep behavior identical, update all call sites&quot; and it will do a decent job. This fools people. Fowler's catalog — extract method, move field, rename — is the last mile of refactoring. The hard part was never executing the moves; it's deciding that moves are needed, which ones, and toward what design. Judgment, not mechanics. Agents pass the mechanics test, and it tells us nothing about the judgment.</p>
<h2 id="the-economics-flip" tabindex="-1"><a href="https://zxczxc.dev/posts/why-ai-agents-are-bad-at-code-refactoring/#the-economics-flip" class="header-anchor">The economics flip</a></h2>
<p><img src="https://zxczxc.dev/uploads/why-ai-agents-are-bad-at-code-refactoring/xzr5i8inse89tciscjl1.jpg" alt="Vince McMahon reaction meme, escalating excitement: ship a hotfix / fix it properly / refactor the codebase / rewrite from scratch"></p>
<p>So here is my suspicion: maybe with AI it is simply cheaper to throw the code away and regenerate it. Look at what a rewrite actually involves:</p>
<ol>
<li>Extract the requirements — explicit and implicit.</li>
<li>Design against them.</li>
<li>Write the code.</li>
</ol>
<p>That's the greenfield pipeline with one pre-step attached. No smell detection, no archaeology, no step-6 migration plan — most of the seven-step backward pipeline simply disappears. Mass production killed the repair shop for exactly this reason: when manufacturing a new unit costs less than diagnosing the old one, nobody fixes anything. Code generation is heading toward free. Code <em>diagnosis</em> is not.</p>
]]>
      </content:encoded>
      <category>ai</category>
      <category>refactoring</category>
      <category>programming</category>
      <category>softwareengineering</category>
      <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
      <meta property="og:image" content="https://zxczxc.dev/uploads/why-ai-agents-are-bad-at-code-refactoring/cover-lis3cjixcg02u0l36n8n.jpg"/>
    </item>
    <item>
      <title>The Kilobyte UTXO Set: Utreexo, SwiftSync, and Bitcoin's state problem</title>
      <link>https://zxczxc.dev/posts/kilobyte-utxo-set/</link>
      <guid isPermaLink="false">https://zxczxc.dev/posts/kilobyte-utxo-set/</guid>
      <description>
        Why Bitcoin wants UTXO accumulators, how Utreexo's Merkle forest works, what it costs, and who should run what — with an interactive playground.
      </description>
      <content:encoded>
        <![CDATA[<p>Every Bitcoin node drags a 12 GB database through its whole life just to answer one question: does this coin exist? This article — published as an interactive edition — shows how Utreexo compresses that answer into about thirty hashes. Add coins, watch equal-height trees carry-merge, and spend them with Merkle proofs, right in the page.</p>
<p><img src="https://zxczxc.dev/uploads/kilobyte-utxo-set/playground.gif" alt="Playground recording: coins added, trees merging, a Merkle proof spend"></p>
<p><a href="https://zxczxc.dev/posts/kilobyte-utxo-set/">Open the interactive edition</a></p>
]]>
      </content:encoded>
      <category>bitcoin</category>
      <category>cryptography</category>
      <category>datastructures</category>
      <pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate>
      <meta property="og:image" content="https://zxczxc.dev/uploads/kilobyte-utxo-set/cover-bfjad0ppv1wiq78l2c2p.png"/>
    </item>
    <item>
      <title>How to stop gnome-keyring-daemon prompts</title>
      <link>https://zxczxc.dev/posts/no-gnome-keyring-daemon/</link>
      <guid isPermaLink="false">https://zxczxc.dev/posts/no-gnome-keyring-daemon/</guid>
      <content:encoded>
        <![CDATA[<p>In Gnome there is gnome-keyring-daemon that should be unlocked to provide access to keyrings. By default it unlocks using login password,
but if you use fingerprint or face unlock then it will prompt you to enter password every time any process try to access keyring.
For my setup it's 6 times after login and 2 times when I start the browser.</p>
<!--more-->
<p>Package removal does not work because it's a dependency of many other packages.
What actually works is to remove all ways of starting gnome-keyring-daemon like so</p>
<pre><code>mkdir -p ~/.local/share/dbus-1/services
cp /usr/share/dbus-1/services/{org.freedesktop.impl.portal.Secret.service,org.freedesktop.secrets,org.gnome.keyring.service}.service ~/.local/share/dbus-1/services
# replace last line with &quot;Exec=/usr/bin/true&quot;

cp /etc/xdg/autostart/gnome-keyring-{pkcs11,secrets,ssh}.desktop ~/.config/autostart
for f in ~/.config/autostart/gnome-keyring-*.desktop; do echo &quot;Hidden=true&quot; &gt;&gt; $f; done
</code></pre>
<p>BTW, may be it's simpler to <code>sudo chmod -x /usr/bin/gnome-keyring-daemon</code></p>
]]>
      </content:encoded>
      <category>linux</category>
      <category>gnome</category>
      <pubDate>Tue, 24 May 2022 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>How to make a personal blog on GitHub Pages (with Google Analytics)</title>
      <link>https://zxczxc.dev/posts/github-pages/</link>
      <guid isPermaLink="false">https://zxczxc.dev/posts/github-pages/</guid>
      <content:encoded>
        <![CDATA[<p>Create regular Github Pages repo, then edit <code>_config.yaml</code>:</p>
<pre><code class="language-yaml">google_analytics: G-XXX # https://support.google.com/analytics/answer/9306384
remote_theme: jekyll/minima
plugins:
- jekyll-remote-theme
minima:
  skin: dark
</code></pre>
<!--more-->
<p>Thanks to https://github.com/jekyll/minima/issues/561#issuecomment-748793956</p>
]]>
      </content:encoded>
      <category>jekyll</category>
      <category>github-pages</category>
      <pubDate>Mon, 09 May 2022 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>How to install Magisk on Xiaomi Mi Note 10 (tucana)</title>
      <link>https://zxczxc.dev/posts/tucana/</link>
      <guid isPermaLink="false">https://zxczxc.dev/posts/tucana/</guid>
      <content:encoded>
        <![CDATA[<ol>
<li>unlock bootloader</li>
<li>install <a href="https://github.com/topjohnwu/Magisk/releases/latest">MagiskManager</a></li>
<li>download <a href="https://eu.dl.twrp.me/tucana/">TWRP</a></li>
<li>enable USB debugging and connect adb</li>
</ol>
<!--more-->
<ol>
<li>reboot to fastboot <code>adb reboot bootloader</code></li>
<li>boot to TWRP <code>fastboot boot twrp.img</code></li>
<li>after booting twrp decrypt /sdcard using your pin/pattern</li>
<li>connect to device using adb <code>adb shell</code></li>
<li>copy boot partition to sdcard <code>dd if=/dev/block/bootdevice/by-name/boot of=/sdcard/boot.img</code></li>
<li>reboot to system <code>adb reboot</code></li>
<li>launch MagiskManager</li>
<li>press &quot;Install&quot; in &quot;Magisk&quot; card</li>
<li>uncheck &quot;Recovery Mode&quot;</li>
<li>press next</li>
<li>select boot.img file on the sdcard root</li>
<li>after successeful patch pull patched boot image to pc <code>adb pull /sdcard/Download/magisk_patched-*.img ./</code></li>
<li>reboot to bootloader again <code>adb reboot bootloader</code></li>
<li>flash patched boot image <code>fastboot flash boot magisk_patched-*.img</code></li>
<li>reboot to system <code>fastboot reboot</code></li>
<li>profit!</li>
</ol>
]]>
      </content:encoded>
      <category>android</category>
      <category>magisk</category>
      <pubDate>Fri, 06 May 2022 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>How to create an lnd node ready for accepting payments</title>
      <link>https://zxczxc.dev/posts/lnd-node/</link>
      <guid isPermaLink="false">https://zxczxc.dev/posts/lnd-node/</guid>
      <content:encoded>
        <![CDATA[<h2 id="install-lnd" tabindex="-1"><a href="https://zxczxc.dev/posts/lnd-node/#install-lnd" class="header-anchor">Install lnd</a></h2>
<pre><code>yay -S lnd
</code></pre>
<!--more-->
<h2 id="configure" tabindex="-1"><a href="https://zxczxc.dev/posts/lnd-node/#configure" class="header-anchor">Configure</a></h2>
<pre><code>mkdir ~/.lnd
cat &lt;&lt;EOF
bitcoin.active=true
bitcoin.mainnet=true
bitcoin.node=neutrino
feeurl=https://nodes.lightning.computer/fees/v1/btc-fee-estimates.json
no-rest-tls=true
EOF &gt; ~/.lnd/lnd.conf
</code></pre>
<h2 id="create-wallet" tabindex="-1"><a href="https://zxczxc.dev/posts/lnd-node/#create-wallet" class="header-anchor">Create wallet</a></h2>
<pre><code>lnd &amp;
lncli create
# pass password here and save seed phrase afterwards
kill %1
</code></pre>
<h2 id="setup-autounlock-and-start-lnd" tabindex="-1"><a href="https://zxczxc.dev/posts/lnd-node/#setup-autounlock-and-start-lnd" class="header-anchor">Setup autounlock and start lnd</a></h2>
<pre><code>echo &quot;&lt;password-for-the-wallet&gt;&quot; &gt; ~/.lnd/wallet.password
echo &quot;wallet-unlock-password-file=~/.lnd/wallet.password&quot; &gt;&gt; ~/.lnd/lnd.conf
lnd &amp;
</code></pre>
<h2 id="obtain-inbound-liquidity" tabindex="-1"><a href="https://zxczxc.dev/posts/lnd-node/#obtain-inbound-liquidity" class="header-anchor">Obtain inbound liquidity</a></h2>
<ul>
<li>go to https://lnbig.com</li>
<li>initiate the payment</li>
<li>provide node id <code>lncli getinfo | jq -r .identity_pubkey</code></li>
<li>use NodeID@Host:Port to connect to the node <code>lncli connect &lt;addr&gt;</code></li>
<li>wait for on-chain confirmation</li>
</ul>
]]>
      </content:encoded>
      <category>bitcoin</category>
      <category>lightning</category>
      <category>linux</category>
      <pubDate>Tue, 03 May 2022 00:00:00 GMT</pubDate>
    </item>
  </channel>
</rss>