<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Arne Bahlo</title><link>https://arne.me</link><description>Arne Bahlo&apos;s personal website</description><language>en-us</language><copyright>2021 – 2026 Arne Bahlo</copyright><managingEditor>hey@arne.me</managingEditor><webMaster>hey@arne.me</webMaster><lastBuildDate>Tue, 04 Aug 2026 10:25:36 +0000</lastBuildDate><item><title>I built a NAS</title><link>https://arne.me/blog/buildlog-nas</link><description><![CDATA[In this blog post I describe why and how I set up my personal cloud on a NAS.
]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/buildlog-nas</guid><pubDate>Sat, 07 Feb 2026 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Without going too deep into it, in light of our late-stage surveillance
capitalism and political escalations, I do not want to depend on tech companies
for my personal data, especially those from the United States.</p>
<p>In lieu of that, I’ve decided to take matters into my own hands and build a NAS.</p>
<h2><a inert href="#table-of-contents" aria-hidden="true" class="anchor" id="table-of-contents"></a>Table of contents</h2>
<ul>
<li><a href="#history">History</a> (you can skip this one)</li>
<li><a href="#hardware">Hardware</a></li>
<li><a href="#system">System</a></li>
<li><a href="#security">Security</a>
<ul>
<li><a href="#zfs-encryption">ZFS encryption</a></li>
<li><a href="#headscale">Headscale</a></li>
<li><a href="#zfs-encryption">SSL</a></li>
</ul>
</li>
<li><a href="#backups">Backups</a></li>
<li><a href="#apps">Apps</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
<h2><a inert href="#history" aria-hidden="true" class="anchor" id="history"></a>History</h2>
<p>I’ve been <a href="https://arne.me/blog/diy-music-streaming-with-nixos-and-jellyfin">hosting my own music</a>
since 2024, and I moved my personal data to a hosted NextCloud on Hetzner
shortly after.
<a href="https://cryptomator.org/">Cryptomator</a> helped me end-to-end encrypt my files
on the Hetzner servers, and while I would recommend this, the workflow is a bit
convoluted—for example, on macOS it mounts a volume that is harder to access.</p>
<p>The biggest problem for me was photos—I was using iCloud photos, and even though
something like <a href="https://immich.app">Immich</a> get’s really close to the
functionality I’m interested in, it’s not automatically end-to-end encrypted.</p>
<h2><a inert href="#hardware" aria-hidden="true" class="anchor" id="hardware"></a>Hardware</h2>
<p>Let’s talk about constraints:</p>
<ul>
<li>No pre-built, closed-source solution (Synology, QNAP, etc.): If I do this, I’m
going to do it the right way.</li>
<li>Ideally SSD-only: Using HDDs is cheap (see next point), but they’re more prone
to fail and wayyy slower.</li>
<li>Enough power: Because I wanted to run apps like Immich or
<a href="https://nextcloud.com">NextCloud</a>, I needed a fast-enough processor and
enough memory, at <em>least</em> 8 GB.</li>
<li>Reasonably priced: My budget was around 700 €.</li>
</ul>
<p>Things I considered:</p>
<ul>
<li>Old QNAP stations: Some of these come with SATA slots that also support SSD,
but ultimately the processors of the ones I looked at were too weak.</li>
<li>Framework/Intel NUC/whatever with an external NVMe/M.2 case for my storage
SSDs: There aren’t a lot of external NVMe RAID cases out there that look
trustworthy, plus it felt wrong to have them connected over USB; it’s
probably a lot slower as well.</li>
</ul>
<p>At this point, I felt like there was no good solution, until I found the <a href="https://www.intel.com/content/www/us/en/products/sku/89187/intel-nuc-kit-nuc6i7kyk/specifications.html">Intel NUC
NUC6i7KYK, a.k.a. Skull Canyon</a>.
It has:</p>
<ul>
<li>Intel Core i7 (6MB cache, up to 3.5 GHz)</li>
<li>16 GB memory (at least the one I got on eBay)</li>
<li>2 internal M.2 SSD slots</li>
</ul>
<p>This sounded perfect, and I got it used for around 200 €, with an internal
500 GB SSD already built in, which I used to test the setup.</p>
<p>My plan is to then get 2× 4 TB SSDs and mirror them for maximum security.
This is more than enough storage for our family for the next few years.
Also have you looked at SSD prices lately? 100 €/TB is a good deal.</p>
<h2><a inert href="#system" aria-hidden="true" class="anchor" id="system"></a>System</h2>
<p>From my research, these were the three top options:</p>
<ul>
<li><a href="https://www.truenas.com/truenas-scale">TrueNAS Community Edition (Scale)</a>:
Open-source NAS system based on ZFS, with support for many apps using Docker.</li>
<li><a href="https://unraid.net">Unraid</a>: Closed source; supports ZFS; BTRFS and XFS;
needs a license ($249 for Lifetime)</li>
<li>Handrolled: Build everthing myself using Arch/NixOS w/ Docker</li>
</ul>
<p>While Unraid looked promising, I did not want any closed-source software.
I thought about hand-rolling, but since I wanted this setup to be as stable as
possible and I have very little experience with ZFS, I eventually decided on
TrueNAS Scale.</p>
<p>One annoyance with TrueNAS<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup> was that the installation could not be on a drive
used for data.
After some research, I decided to install on an SSD in a SATA case connected via
USB.
Booting from an external drive is not great and should be avoided if possible,
but this is a common setup for people building NAS systems using mini-computers.</p>
<p>One problem I encountered was that initramfs failed to find the boot pool on
boot because the (external) SSD was not discovered when it tried.
I solved this problem by running this on the TrueNAS shell:</p>
<pre class="syntax-highlighting"><code class="language-sh"><span class="source shell bash"><span class="meta function-call shell"><span class="variable function shell">midclt</span></span><span class="meta function-call arguments shell"> call system.advanced.update <span class="string quoted single shell"><span class="punctuation definition string begin shell">&#39;</span>{&quot;kernel_extra_options&quot;: &quot;rootdelay=20&quot;}<span class="punctuation definition string end shell">&#39;</span></span></span>
</span></code></pre>
<p>This pauses the boot process for 20s, giving the USB controller time to set up
the SSD.</p>
<h2><a inert href="#security" aria-hidden="true" class="anchor" id="security"></a>Security</h2>
<p>Security is all about attack vectors.
My setup is meant to protect against theft and automated attacks.
There is room to tighten this up even more, but this is more than good enough
for me.</p>
<h3><a inert href="#zfs-encryption" aria-hidden="true" class="anchor" id="zfs-encryption"></a>ZFS encryption</h3>
<p>TrueNAS supports ZFS-native encryption.
You can choose to encrypt the root pool, which I did; the encryption key is
stored on the system itself for auto-unlock.
This is great, but it offers no protection against the whole system being
stolen.</p>
<p>Because of this, I have a top-level dataset under <code>tank</code> called <code>encrypted</code>,
which is encrypted with a password.
This means the system boots, but no data (including app data) is accessible
until I log in and decrypt the dataset.</p>
<p>For this reason, I’m also not using the built-in backup solution; I don’t want
the encryption key and password to be accessible on boot.
More about backups further down.</p>
<p>I disabled the text console without a password prompt to prevent someone with
physical access to the running, decrypted system from extracting data.</p>
<h3><a inert href="#headscale" aria-hidden="true" class="anchor" id="headscale"></a>Headscale</h3>
<p>There’s no way I’m exposing this system to the public internet.
This is far too dangerous, even if you know what you’re doing.
I still want to be able to access all my data from on the go, so I set up
<a href="https://headscale.net/stable/">Headscale</a>, an open-source, self-hosted version
of <a href="https://tailscale.com">Tailscale</a> and compatible with the Tailscale apps.
With this, I can access my NAS from anywhere via a self-hosted VPN.</p>
<h3><a inert href="#ssl" aria-hidden="true" class="anchor" id="ssl"></a>SSL</h3>
<p>But what if a machine in the VPN or on my local Wi-Fi gets compromised?
The attacker would be able to sniff all HTTP traffic, extract credentials, and
access all my data.
This is why we encrypt our HTTP traffic.</p>
<p>For the TrueNAS system, I forward HTTP to HTTPS and use the self-signed
certificate that comes pre installed.</p>
<p>But most TrueNAS apps expose ports without SSL, which means we need a reverse
proxy.
While TrueNAS supports the option to expose ports for inter-container
communication, each app gets its own Docker network, which means a reverse proxy
can’t access other apps by default<sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup>.
To fix this, I use <a href="https://github.com/tjhorner/dragonify">Dragonify</a> (which I
forked for security reasons).
It connects all apps to a single network, so I can use <a href="https://nginxproxymanager.com/">Nginx Proxy Manager</a><sup class="footnote-ref"><a href="#fn-3" id="fnref-3" data-footnote-ref>3</a></sup>
to serve apps with SSL on a subdomain.</p>
<p>I bought a domain solely for this setup and use <a href="https://desec.io">deSEC</a> with
an account created specifically for this domain.
This means when a token gets compromised, only that single domain is affected.
But that token lives on the encrypted dataset, soo it’s unlikely.</p>
<h2><a inert href="#backups" aria-hidden="true" class="anchor" id="backups"></a>Backups</h2>
<p>Because I don’t want any encryption keys or access credentials on the system
partition, I’m not using the native TrueNAS backup systems.
After some research, I’ve landed on <a href="https://github.com/garethgeorge/backrest">Backrest</a>,
a configuration interface for <a href="https://restic.readthedocs.io">Restic</a>.</p>
<p>Here’s what Filippo Valsorda has to say about Restic encryption:</p>
<blockquote>
<p>“The design might not be perfect, but it’s good. Encryption is a first-class
feature, the implementation looks sane and I guess the deduplication
trade-off is worth it. So… I’m going to use restic for my personal backups.”</p>
</blockquote>
<p>For storage, I’m using <a href="https://www.hetzner.com/storage/object-storage/">Hetzner Object Storage</a>,
mostly because it’s environmentally friendly and hosted in the EU.</p>
<h2><a inert href="#apps" aria-hidden="true" class="anchor" id="apps"></a>Apps</h2>
<p>We already talked about Tailscale, Dragonify, Nginx Proxy Manager, and Backrest.</p>
<p>Here are the apps I’m using</p>
<ul>
<li>Immich for photos</li>
<li>NextCloud for documents</li>
<li><a href="https://syncthing.net">Syncthing</a> for more advanced sync needs<sup class="footnote-ref"><a href="#fn-4" id="fnref-4" data-footnote-ref>4</a></sup></li>
<li><a href="https://jellyfin.org">Jellyfin</a> for music</li>
<li><a href="https://miniflux.app">Miniflux</a> for RSS feeds</li>
</ul>
<h2><a inert href="#conclusion" aria-hidden="true" class="anchor" id="conclusion"></a>Conclusion</h2>
<p>So that’s my setup.
It works really well, and I’m happy.
I’ll try to edit this post in six months to give an update.</p>
<p>Am I missing something?
Let me know: <a href="mailto:hey@arne.me">hey@arne.me</a></p>
<p><em>Thanks to Eric &amp; Jan for proof-reading this post and listening to my ramblings!</em></p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>Not sure if this restriction also applies to Unraid/other systems. <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p>Apparently this was possible with 24.04 (Dragonfish) <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
<li id="fn-3">
<p>I might switch to a boring Caddy configuration soon. <a href="#fnref-3" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3">↩</a></p>
</li>
<li id="fn-4">
<p>E.g. syncing my Obsidian vault or Steamdeck screenshots <a href="#fnref-4" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="4" aria-label="Back to reference 4">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>Brené Brown on productivity and self-worth</title><link>https://arne.me/blog/brene-brown-on-productivity-and-self-worth</link><description><![CDATA[This post quotes from Dare to Lead (Brown, 2018) about the problem of making
worthiness a function of productivity.
]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/brene-brown-on-productivity-and-self-worth</guid><pubDate>Mon, 21 Jul 2025 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>I’m currently reading Dare to Lead (Brown, 2018) and this made me think:</p>
<blockquote>
<p>When worthiness is a function of productivity, we lose the ability to pump
the brakes. The idea of doing something that doesn’t add to the bottom line
provokes stress and anxiety. It feels completely contrary to what we believe
we want to achieve in life—we convince ourselves that downtime, like playing
with our kids, hanging out with our partners, napping, tooling around in the
garage, or going for a run is a waste of precious time. Why sleep when you can
work?</p>
</blockquote>
<p>Although I’m not a workaholic, I do see myself in some of these points.<br>
I need to work on that.</p>
]]></content:encoded></item><item><title>Reclaiming my attention</title><link>https://arne.me/blog/reclaiming-my-attention</link><description><![CDATA[This post documents my journey to detach from my smartphone.
]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/reclaiming-my-attention</guid><pubDate>Sat, 05 Jul 2025 00:00:00 +0000</pubDate><content:encoded><![CDATA[<blockquote>
<p>“Like fingers pointing to the moon, other diverse disciplines from
anthropology to education, behavioral economics to family counseling,
similarly suggest that the skillful management of attention is the sine qua
non of the good life and the key to improving virtually every aspect of your
experience.” — Winifred Gallagher</p>
</blockquote>
<p>Our attention is being stolen.<br>
We’re slowly losing the ability to concentrate, not only because of TikTok, but
also because we constantly have access to <em>easy</em> consumption.
It’s digital fast food.</p>
<p>When I started to notice this on myself, I started with the naïve approach and
deleted all time sink<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup> apps like YouTube or Instagram.
This worked for a day or two until I either got bored or found another reason to
re-install.</p>
<p>Then I moved to Apple Screen Time.
This works for a bit, but that “15 minutes more” button starts to become muscle
memory very quickly.
Even apps like <a href="https://one-sec.app">one sec</a> got deleted because they’re
annoying (that’s the idea, I know).</p>
<p>In January 2024 I went <a href="/blog/i-lived-7-days-without-a-smartphone">7 days with only my Apple Watch</a>,
leaving my iPhone at my desk at all times.
It was doable, but very impractical.</p>
<h2><a inert href="#none-of-this-worked" aria-hidden="true" class="anchor" id="none-of-this-worked"></a>None of this worked</h2>
<p>In early 2025 I sold my Apple Watch and bought a Casio DW-5600BB-1.
This had a large impact as I would no longer get buzzed on my wrist for every
notification<sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup>.</p>
<p>Then I deleted my Instagram account and the YouTube, Mastodon and Bluesky apps.
Yes, a lot of my friends are on Instagram—but I mostly watched Reels anyway!
I do miss Mastodon.
I now use YouTube in Safari with <a href="https://apps.apple.com/de/app/blockyt-for-safari/id1575379461?l=en-GB">shorts blocked</a>.
This creates enough friction for me to watch videos intentionally (most of the
time).
It also lets me use cool extensions like <a href="https://sponsor.ajay.app/">SponsorBlock</a>
and <a href="https://dearrow.ajay.app/">DeArrow</a>.</p>
<p>And when Apple Intelligence (if you can call it that) got to Europe, I turned
on the new <em>Reduce Interruptions</em> focus and never turned it off.
This works great because it randomly lets things through, but blocks most of it.</p>
<p>I tried a dumb phone<sup class="footnote-ref"><a href="#fn-3" id="fnref-3" data-footnote-ref>3</a></sup>, but this was <em>way</em> too much friction—I need a usable
phone for renting a bike, getting my parcels, etc.</p>
<p>In March I bought a <a href="https://bulletjournal.com">Bullet Journal</a> and started
tracking my tasks there instead of my phone.
I also started journalling and tracking my habits and I love the analog
lifestyle—even my running plan is analog!</p>
<p>The combination of all of this is working out pretty well so far:
My phone is mostly boring.
It doesn’t have any exciting apps.
It doesn’t <em>spark joy</em>.
Because of this, I’m spending my time a lot more intentional.</p>
<p>That being said, sometimes I spend a little too much time on YouTube (at least
that’s longform content) and I should probably delete the Slack app to not check
the work chat when I’m not at my desk.</p>
<h2><a inert href="#we-deserve-more-humane-tech" aria-hidden="true" class="anchor" id="we-deserve-more-humane-tech"></a>We deserve more humane tech</h2>
<p>I’m happy to see the first smartphone vendors start to add physical switches
to their phones to disable connectivity or limit apps<sup class="footnote-ref"><a href="#fn-4" id="fnref-4" data-footnote-ref>4</a></sup> and maybe I’ll switch
to one of them one day.</p>
<p>This is not a political post, but if it was, it would talk about the obscene
power of big tech and the necessity to regulate and break up.
It would appeal on you to rethink your investment into these services, services
that are actively spying on you, services that exploit your mental health for
the sake of raising shareholder value.</p>
<p>Now go, touch some grass<sup class="footnote-ref"><a href="#fn-5" id="fnref-5" data-footnote-ref>5</a></sup>. 🌱</p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>Knapp, J. &amp; Zeratsky, J. (2018). <em>Make Time: How to focus on what matters every day.</em> <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p>Yes, you can turn all notifications off. I didn’t. <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
<li id="fn-3">
<p><a href="https://www.punkt.ch/en/products/mp02-4g-mobile-phone/">Punkt MP02</a> <a href="#fnref-3" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3">↩</a></p>
</li>
<li id="fn-4">
<p><a href="https://www.fairphone.com/">Fairphone 6</a>, <a href="https://mudita.com/products/phones/mudita-kompakt/">Mudita Kompakt</a> <a href="#fnref-4" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="4" aria-label="Back to reference 4">↩</a></p>
</li>
<li id="fn-5">
<p>Apparently <a href="https://touchgrass.now/">there’s an app for that</a> <a href="#fnref-5" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="5" aria-label="Back to reference 5">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>My favorite things of 2024</title><link>https://arne.me/blog/favorites-2024</link><description><![CDATA[This post is a record of my favorite things in 2024.
]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/favorites-2024</guid><pubDate>Sun, 22 Dec 2024 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Here are my favorite things, digital and physical, of 2024!</p>
<h2><a inert href="#music" aria-hidden="true" class="anchor" id="music"></a>Music</h2>
<ul>
<li><a href="https://www.youtube.com/playlist?list=PL8tkzXKlhGxltN2yyQwjlkd6jZVknhj9l">I Love You So F***ing Much by Glass Animals</a>:
One of my favorite bands, every single album is amazing. and this is no
exception.</li>
<li><a href="https://www.youtube.com/playlist?list=PL3roRV3JHZzbatp5PvE-88ApmVishqGHN">Clancy by Twenty One Pilots</a>:
There’s artists with unique story telling and then there’s Twenty One Pilots.
I really like this album.</li>
<li><a href="https://www.youtube.com/playlist?list=PLlqZM4covn1HF92FlF7W5nUI_Ixh2m3Rk">From Zero by Linkin Park</a>:
This sounds like Hybrid Theory reloaded—which is a good thing—and I love that they’re back.</li>
<li><a href="https://www.youtube.com/playlist?list=PLgpybwCcUnBV9ydEM2ReIB1ycE3BxAX_4">Older (and Wiser)</a>:
This is the music you want to hear on long train rides through the country
side and on long winter evenings.</li>
</ul>
<h2><a inert href="#books" aria-hidden="true" class="anchor" id="books"></a>Books</h2>
<p>These links go to my reviews (no spoilers):</p>
<ul>
<li><a href="/library/piranesi">Piranesi by Susanna Clarke</a>: One of the best books I’ve
ever read.</li>
<li><a href="/library/project-hail-mary">Project Hail Mary by Andy Weir</a>: Loved the book
and told everyone about it after reading it.</li>
<li><a href="/library/leviathan-wakes">The Expanse series by James S. A. Corey</a>: 7 books
plus a few novellas. Couldn’t stop reading!</li>
<li><a href="/library/useful-not-true">Useful Not True by Derek Sivers</a>: One of the books
that change how you think about the world, incredible read.</li>
</ul>
<h2><a inert href="#apps" aria-hidden="true" class="anchor" id="apps"></a>Apps</h2>
<ul>
<li><a href="https://tilo.dev/manet/">Manet</a>: I’ve started buying my music and hosting it
myself this year<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup>, this app makes it a joy to listen to music.</li>
<li><a href="https://couchtim.es">CouchTimes</a>: The best TV show tracker in the App
Store, period. It doesn’t have feature bloat and it respects your privacy.</li>
<li><a href="https://mitchellh.com/ghostty">Ghostty</a>: A super fast, new Terminal emulator
for the Mac.</li>
<li><a href="https://halide.cam">Halide</a>: This camera app has been around for a while, but
this year they released <em>Process Zero</em>, a mode that applies minimal
corrections and makes photos look more like digital camera photos.</li>
</ul>
<h2><a inert href="#goods" aria-hidden="true" class="anchor" id="goods"></a>Goods</h2>
<ul>
<li><a href="https://aranet.com/en/home/products/aranet4-home">aranet4</a>: There are cheaper
options, but a CO<sub>2</sub> monitor for my home office has helped me
keep the air at good levels.</li>
<li><a href="https://heat-it.com">heat-it</a>: This tiny tool draws power from your phone
to relieve itch &amp; pain from insect bites.</li>
<li><a href="https://www.leatherman.com/de_DE/2h-wave-638.html">Leatherman 2H Wave+</a>:
The only big Leatherman that’s legal to carry in Germany, super versatile and
handy to have around.</li>
</ul>
<p>What were your favorites?</p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>I even wrote <a href="/blog/diy-music-streaming-with-nixos-and-jellyfin">a blogpost</a>
about it. <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>Jujutsu in practice</title><link>https://arne.me/blog/jj-in-practice</link><description><![CDATA[Jujutsu is a version control system, an alternative to Git. This blog post
is not a tutorial, but practical examples of how I use it.
]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/jj-in-practice</guid><pubDate>Mon, 21 Oct 2024 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>This post is not about the Japanese martial arts <em>Jiu-jitsu</em>, it’s about a new
VCS, or version control system.
There are some great tutorials and introductions for
<a href="https://github.com/martinvonz/jj">Jujutsu</a>, short <code>jj</code>, so I want to give some
insight in how I use it day to day for this website.</p>
<h2><a inert href="#initialize-a-repository" aria-hidden="true" class="anchor" id="initialize-a-repository"></a>Initialize a repository</h2>
<p>You can initialize <code>jj</code> in an existing <code>git</code> repository like this:</p>
<pre class="syntax-highlighting"><code class="language-sh"><span class="source shell bash"><span class="meta function-call shell"><span class="variable function shell">$</span></span><span class="meta function-call arguments shell"> jj git init<span class="variable parameter option shell"><span class="punctuation definition parameter shell"> --</span>git-repo</span> .</span>
</span></code></pre>
<p>This will create a <code>.jj</code> directory next to <code>.git</code>.
You can now use both <code>git</code> and <code>jj</code>, although I wouldn’t recommend it.
There’s a work-in-progress native storage backend, but since all my projects
are <code>git</code> repositories anyway, this works great for me.</p>
<p>Note that this is non-destructive; if you want to go back to <code>git</code>, all it takes
is a <code>rm -r .jj</code>.</p>
<h2><a inert href="#get-an-overview" aria-hidden="true" class="anchor" id="get-an-overview"></a>Get an overview</h2>
<p>Running <code>jj log</code> in the repository for this very website gives this output:</p>
<pre class="syntax-highlighting"><code class="language-sh"><span class="source shell bash"><span class="meta function-call shell"><span class="variable function shell">$</span></span><span class="meta function-call arguments shell"> jj log</span>
<span class="meta function-call shell"><span class="variable function shell">@</span></span><span class="meta function-call arguments shell">  qzsvtxpv hey@arne.me 2024-10-21 09:58:06 e18f7532</span>
<span class="meta function-call shell"><span class="variable function shell">│</span></span><span class="meta function-call arguments shell">  Add blog/jj-in-practice</span>
<span class="meta function-call shell"><span class="variable function shell">│</span></span><span class="meta function-call arguments shell"> ○  yoxxsupn hey@arne.me 2024-10-20 22:55:03 ae5d9109</span>
<span class="meta function-call shell"><span class="variable function shell">├─╯</span></span><span class="meta function-call arguments shell">  Add library/calibans-war</span>
<span class="meta function-call shell"><span class="variable function shell">│</span></span><span class="meta function-call arguments shell"> ○  tvkvwslw hey@arne.me 2024-10-20 22:49:54 5e4dee1f</span>
<span class="meta function-call shell"><span class="variable function shell">├─╯</span></span><span class="meta function-call arguments shell">  Add library/the-posthumous-memoirs-of-bras-cubas</span>
<span class="meta function-call shell"><span class="variable function shell">│</span></span><span class="meta function-call arguments shell"> ○  pywmtrys hey@arne.me 2024-10-20 21:20:11 7bda14b7</span>
<span class="meta function-call shell"><span class="variable function shell">│</span></span><span class="meta function-call arguments shell"> │  Add atoms/1</span>
<span class="meta function-call shell"><span class="variable function shell">│</span></span><span class="meta function-call arguments shell"> ○  xnlzypwn hey@arne.me 2024-10-20 21:20:10 8a004404</span>
<span class="meta function-call shell"><span class="variable function shell">├─╯</span></span><span class="meta function-call arguments shell">  Add atoms functionality</span>
<span class="meta function-call shell"><span class="variable function shell">◆</span></span><span class="meta function-call arguments shell">  wxxtrmqk hey@arne.me 2024-10-20 16:18:15 main HEAD@git 1eb46c81</span>
<span class="meta function-call shell"><span class="variable function shell">│</span></span><span class="meta function-call arguments shell">  Add weekly/166</span>
<span class="meta function-call shell"><span class="variable function shell"><span class="meta group expansion tilde"><span class="variable language tilde shell">~</span></span></span></span>
</span></code></pre>
<p>This already shows one of the biggest differences, compared to <code>git</code>:
There’s no branches, other than <code>main</code>.
You <em>can</em> create branches, which are called <em>bookmarks</em> in <code>jj</code>, but you don’t
<em>need</em> to.
Instead, you work mostly with changes<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup>.</p>
<p>The terminal above shows the change <code>w</code> (you can use the first letter to
reference changes; on your terminal it’ll be highlighted as well) as a parent
to <code>x</code>, <code>t</code>, <code>y</code> and <code>q</code>.
All these child-revisions don’t have a branch/bookmark, but they don’t need one.
You can see what’s in-flight at this repository better than with any <code>git</code> repo,
especially if branches haven’t been cleaned up in a while.</p>
<h2><a inert href="#create-a-revision" aria-hidden="true" class="anchor" id="create-a-revision"></a>Create a revision</h2>
<p>My usual flow with <code>git</code> is to leave changes in the staging area until I’m
ready to commit.
Sometimes, if I have to switch branches or want to save my work, I’ll stash
or create a WIP commit.</p>
<p>In <code>jj</code>, there is no staging area—everything is a revision.
Let’s create a new revision on top of my revisions where I add atoms
functionality:</p>
<pre class="syntax-highlighting"><code class="language-sh"><span class="source shell bash"><span class="meta function-call shell"><span class="variable function shell">$</span></span><span class="meta function-call arguments shell"> jj new<span class="variable parameter option shell"><span class="punctuation definition parameter shell"> -</span>r</span> p</span>
</span></code></pre>
<p>Running <code>git log</code> again:</p>
<pre class="syntax-highlighting"><code class="language-sh"><span class="source shell bash"><span class="meta function-call shell"><span class="variable function shell">$</span></span><span class="meta function-call arguments shell"> jj log</span>
<span class="meta function-call shell"><span class="variable function shell">@</span></span><span class="meta function-call arguments shell">  kxqvnxnw hey@arne.me 2024-10-21 10:03:20 22c020cf</span>
<span class="meta function-call shell"><span class="variable function shell">│</span></span><span class="meta function-call arguments shell">  (empty</span><span class="meta function-call shell"></span><span class="meta post-cmd shell">)</span> <span class="punctuation definition compound begin shell">(</span><span class="meta function-call shell"><span class="variable function shell">no</span></span><span class="meta function-call arguments shell"> description set</span><span class="punctuation definition compound end shell">)</span>
<span class="meta function-call shell"><span class="variable function shell">○</span></span><span class="meta function-call arguments shell">  pywmtrys hey@arne.me 2024-10-20 21:20:11 HEAD@git 7bda14b7</span>
<span class="meta function-call shell"><span class="variable function shell">│</span></span><span class="meta function-call arguments shell">  Add atoms/1</span>
<span class="meta function-call shell"><span class="variable function shell">○</span></span><span class="meta function-call arguments shell">  xnlzypwn hey@arne.me 2024-10-20 21:20:10 8a004404</span>
<span class="meta function-call shell"><span class="variable function shell">│</span></span><span class="meta function-call arguments shell">  Add atoms functionality</span>
<span class="meta function-call shell"><span class="variable function shell">│</span></span><span class="meta function-call arguments shell"> ○  qzsvtxpv hey@arne.me 2024-10-21 10:03:18 27229dca</span>
<span class="meta function-call shell"><span class="variable function shell">├─╯</span></span><span class="meta function-call arguments shell">  Add blog/jj-in-practice</span>
<span class="meta function-call shell"><span class="variable function shell">│</span></span><span class="meta function-call arguments shell"> ○  yoxxsupn hey@arne.me 2024-10-20 22:55:03 ae5d9109</span>
<span class="meta function-call shell"><span class="variable function shell">├─╯</span></span><span class="meta function-call arguments shell">  Add library/calibans-war</span>
<span class="meta function-call shell"><span class="variable function shell">│</span></span><span class="meta function-call arguments shell"> ○  tvkvwslw hey@arne.me 2024-10-20 22:49:54 5e4dee1f</span>
<span class="meta function-call shell"><span class="variable function shell">├─╯</span></span><span class="meta function-call arguments shell">  Add library/the-posthumous-memoirs-of-bras-cubas</span>
<span class="meta function-call shell"><span class="variable function shell">◆</span></span><span class="meta function-call arguments shell">  wxxtrmqk hey@arne.me 2024-10-20 16:18:15 main 1eb46c81</span>
<span class="meta function-call shell"><span class="variable function shell">│</span></span><span class="meta function-call arguments shell">  Add weekly/166</span>
<span class="meta function-call shell"><span class="variable function shell"><span class="meta group expansion tilde"><span class="variable language tilde shell">~</span></span></span></span>
</span></code></pre>
<p>You’ll notice that our active revisions are now left-aligned, and the one to
add this very blog post has moved to the right.
There’s no hierarchy, they’re all descendants of <code>Add weekly/166</code>.</p>
<p>After doing some work, e.g. adding a new atom, I can <em>describe</em> that revision
with <code>jj describe</code>.
This is comparable to <code>git commit</code>, but it doesn’t actually create a commit or
a revision, it only <em>describes</em> the current one.</p>
<p>Sometimes I want to update a previous revision, in this case <code>Add atoms/1</code>.
I can run <code>jj squash</code> to merge the current one with its parent.</p>
<h2><a inert href="#push-and-pull" aria-hidden="true" class="anchor" id="push-and-pull"></a>Push and pull</h2>
<p>To fetch new revisions, I run <code>jj git fetch</code>; to push branches/bookmarks, I run
<code>jj git push</code>.
This uses the same <code>git</code> server it was using before.</p>
<p>Before pushing, I need to move my bookmark to the revision I want to push.
I push the <code>main</code> branch to deploy my website, so if I wanted to publish my
atoms functionality (should I?), I would run <code>jj bookmark set main -r p</code> before
pushing.</p>
<h2><a inert href="#rebase-and-split" aria-hidden="true" class="anchor" id="rebase-and-split"></a>Rebase and split</h2>
<p>Sometimes I need to rebase. Fortunately that’s a lot simpler than it is in
<code>git</code>:
I can run <code>jj rebase -s &lt;source&gt; -d &lt;destination&gt;</code> to move revisions around.
If I wanted support for atoms for this blog post, I would run
<code>jj rebase -s q -d p</code> and it would move the revision for this blog post on top
of “Add atoms/1”.</p>
<p><code>jj</code> also does automatic rebasing, e.g. if you squash changes into a revision
that has descendants.</p>
<p>And if I have a revision that I’d like to be two, I run <code>jj split</code> and
interactively select what belongs to which revision.</p>
<h2><a inert href="#undo" aria-hidden="true" class="anchor" id="undo"></a>Undo</h2>
<p>Undoing an (interactive) rebase in <code>git</code> is not fun.
<code>jj undo</code> undoes the last <code>jj</code> operation, doesn’t matter if it’s abandoning
(deleting) a revision or doing a rebase.
This is a life saver!
You can also run <code>jj op log</code> to display your last <code>jj</code> operations.</p>
<h2><a inert href="#things-i-stumble-upon" aria-hidden="true" class="anchor" id="things-i-stumble-upon"></a>Things I stumble upon</h2>
<p><del>I’ve been using <code>git</code> for a long, long time.
My brain assumes that after a <code>commit</code>, I’m working on the next one.
It also assumes that <code>jj describe</code> does the same as <code>git commit</code> (it does not).
I often describe a revision and continue editing files, which then erroneously
get added to the current revision.
I’m not saying this is wrong, it makes sense in the <code>jj</code> world, but I keep
tripping over that and have to run <code>jj split</code> to pull changes out again.</del>
<br>
alterae on Lobste.rs <a href="https://lobste.rs/s/fbjowx/jujutsu_practice#c_xyhzxa">pointed out</a>
that you can describe and immediately create a new revision on top of it with
<code>jj commit</code>. Thanks!</p>
<p><del>One other thing is that you cannot check out a revision directly (or maybe I
just don’t know how to), so when I’ve moved to a different revision and want
to move back, I run <code>jj new &lt;revision&gt;</code>, which creates an empty revision on top
of it.
This means that if I’m not done with the revision, I have to keep running
<code>jj squash</code> to move changes into it.</del>
<br>
gecko on Lobste.rs <a href="https://lobste.rs/s/fbjowx/jujutsu_practice#c_ytil6w">pointed out</a>
that you can check out a revision directly with <code>jj edit &lt;revision&gt;</code>. Thanks!</p>
<h2><a inert href="#why-it-works-for-me" aria-hidden="true" class="anchor" id="why-it-works-for-me"></a>Why it works for me</h2>
<p>A week ago, I removed <code>jj</code> from my website’s repository, to see if I’d miss it.
I added it back the same day.
Jujutsu feels <em>lighter</em> than <code>git</code>, while at the same time giving you a full
overview of what’s in-flight right now<sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup>.
Having no staging area means I only need to worry about revisions (see caveat
above).</p>
<p>If trying new things sounds fun to you, give
<a href="https://github.com/martinvonz/jj">Jujutsu</a> a spin!</p>
<h2><a inert href="#further-reading" aria-hidden="true" class="anchor" id="further-reading"></a>Further reading</h2>
<ul>
<li><a href="https://martinvonz.github.io/jj/v0.13.0/tutorial/">Official Jujutsu Tutorial</a></li>
<li><a href="https://martinvonz.github.io/jj/latest/git-comparison/">Comparison with Git</a></li>
<li><a href="https://steveklabnik.github.io/jujutsu-tutorial/">Steve’s Jujutsu Tutorial</a></li>
<li><a href="https://v5.chriskrycho.com/essays/jj-init/">Chris Krycho’s jj init essay</a></li>
<li><a href="https://www.youtube.com/watch?v=2otjrTzRfVk">Chris Krycho’s video on jj</a></li>
</ul>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>What’s cool about a <code>jj</code> change, is that updating it doesn’t change its ID. <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p>If you work in large projects with many contributors, you can
<a href="https://martinvonz.github.io/jj/latest/tutorial/#the-log-command-and-revsets">tune your <code>jj log</code> to only your revisions</a>. <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>We need more zero config tools</title><link>https://arne.me/blog/we-need-more-zero-config-tools</link><description><![CDATA[Recently, I've become fond of tools that just work, out of the box.
This blogpost is an ode to them.
]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/we-need-more-zero-config-tools</guid><pubDate>Tue, 01 Oct 2024 00:00:00 +0000</pubDate><content:encoded><![CDATA[<blockquote>
<p>It just works. — Steve Jobs</p>
</blockquote>
<p>If you follow this blog, you’ll know that I’m doing a series called “Emacs
Config From Scratch”<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup>.
Emacs is an <del>editor</del> operating system, where you can configure and customize
literally everything.
I like that you can truly make it yours, but it’s a lot of work to get there.</p>
<p>Recently, I’ve become fond of (command line) tools that <em>just work</em>, out of the
box<sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup>.
This blogpost is an ode to them.</p>
<h2><a inert href="#fish" aria-hidden="true" class="anchor" id="fish"></a>Fish</h2>
<p>Julia Evans recently posted <a href="https://jvns.ca/blog/2024/09/12/reasons-i--still--love-fish/">Reasons I still love the fish shell</a>,
and the first point she makes is “no configuration”.</p>
<p>Things that require plugins and lots of code in shells like ZSH, like
autosuggestions, are included and configured by default in fish.
At the time of writing this, <a href="https://github.com/bahlo/dotfiles/blob/87fbba772f95188f55201e6717cc8fb70ee6ac38/fish/config.fish">my fish config</a>
has less than 31 loc, most of which are abbreviations.</p>
<p>I have two fish plugins configured: <a href="https://github.com/jethrokuan/z">z</a> for
jumping to a directory, and <a href="https://github.com/jorgebucaran/hydro">hydro</a> as
my shell prompt. Neither need configuration.</p>
<h2><a inert href="#helix" aria-hidden="true" class="anchor" id="helix"></a>Helix</h2>
<p><a href="https://github.com/bahlo/dotfiles/tree/8df1cdd47c1907a471fefaf4c798f423c6b6edf3/nvim">My Neovim config</a>
had 21 external plugins.
Making LSP, tree-sitter and formatting work took a while (LSP alone needs 3
plugins) and in the end there were still things that didn’t work.</p>
<p>I’ve switched to <a href="https://helix-editor.com">Helix</a>, which can do so much out of
the box, here’s a non-exhaustive list:</p>
<ul>
<li>LSP (including autocompletion, show signature, go to definition, show references, etc.) just works</li>
<li>Tree-sitter is built in, you can even do selections on tree-sitter objects</li>
<li>A file picker and global search</li>
<li>Pressing a key in normal mode shows subsequent keys you can press, and what
they do</li>
<li>You can jump to any visible word, add/remove/replace quotes or other characters</li>
<li>… and so much more</li>
</ul>
<p>The config for the code editor I use all day is 5 loc. Here it is:</p>
<pre class="syntax-highlighting"><code class="language-toml"><span class="text plain">theme = &quot;kanagawa&quot;

[editor]
line-number = &quot;relative&quot;
cursorline = true
rulers = [80]
</span></code></pre>
<p>I will say that it takes some getting used to as it folows the <code>selection -&gt; action</code> model, i.e. you need to run <code>wd</code> instead of <code>dw</code> to delete the next
word.</p>
<h2><a inert href="#lazygit" aria-hidden="true" class="anchor" id="lazygit"></a>Lazygit</h2>
<p>After raving about <a href="https://magit.vc">Magit</a> in London, my team showed me
<a href="https://github.com/jesseduffield/lazygit">Lazygit</a> and I’ve been using it ever
since—it’s really good and it does exactly what you want it to do, without
configuration<sup class="footnote-ref"><a href="#fn-3" id="fnref-3" data-footnote-ref>3</a></sup>.</p>
<p>You can toggle different screen modes, panes adjust in size when active and
pretty much everything you want to do is only a few keystrokes away.</p>
<h2><a inert href="#zellij" aria-hidden="true" class="anchor" id="zellij"></a>Zellij</h2>
<p>A batteries-included Tmux alternative, <a href="https://zellij.dev">Zellij</a> doesn’t
need any configuration to work well. You can set up Layouts without additional
plugins (although there is a plugin system) and I’m generally not missing
anything from my Tmux configuration.</p>
<p>My favorite feature is the floating panes. Press <code>Ctrl + p, w</code> to toggle a
pane floating on top of everything else—I often use this for Lazygit.</p>
<h2><a inert href="#what-else" aria-hidden="true" class="anchor" id="what-else"></a>What else?</h2>
<p>Here are some tools you have sent me that are zero/minimal config:</p>
<ul>
<li>Matthew shared <a href="https://github.com/Canop/broot">Broot</a>, a new way to see and
navigate directory trees</li>
<li>Ilija shared <a href="https://k9scli.io">k9s</a>, a CLI to maange Kubernetes clusters</li>
<li>Alexander shared <a href="https://github.com/xyproto/orbiton">Orbiton</a>, a text editor
they built</li>
</ul>
<p>Do you have a tool that requires no (or minimal) configuration?
<a href="mailto:hey@arne.me">Send me an email</a> and I’ll add it to the list!</p>
<p>And if you’re building something, please strive to make the default experience
work really well for most people.</p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p><a href="/blog/emacs-from-scratch-part-one-foundations">Check</a>
<a href="/blog/emacs-from-scratch-part-two">it</a>
<a href="/blog/emacs-config-from-scratch-part-three">out</a> <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p>I’m starting to feel the same about programming languages and external
dependencies, but that’s a different post. <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
<li id="fn-3">
<p>You <em>can</em> configure <a href="https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md">almost everything</a>—but
you don’t need to. <a href="#fnref-3" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>Emacs Config From Scratch, Part 3: LSP &amp; Tree-sitter</title><link>https://arne.me/blog/emacs-config-from-scratch-part-three</link><description><![CDATA[In this third part of my Emacs Config From Scratch series, I configure LSP and Tree-sitter.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/emacs-config-from-scratch-part-three</guid><pubDate>Sat, 11 May 2024 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>This is Part 3 of my series, <em>Emacs Config From Scratch</em><sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup>, where I create
my perfect editor using Emacs.
In this post, I’ll do some housekeeping, set up LSP<sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup>, language modes for Rust,
Go, TypeScript and Zig, and add search.</p>
<!-- more -->
<h2><a inert href="#table-of-contents" aria-hidden="true" class="anchor" id="table-of-contents"></a>Table Of Contents</h2>
<ul>
<li><a href="#housekeeping">Housekeeping</a></li>
<li><a href="#lsp">LSP</a></li>
<li><a href="#tree-sitter">Tree-sitter</a></li>
<li><a href="#language-support">Language support</a></li>
<li><a href="#search">Search</a></li>
<li><a href="#wrapping-up">Wrapping up</a></li>
</ul>
<h2><a inert href="#housekeeping" aria-hidden="true" class="anchor" id="housekeeping"></a>Housekeeping</h2>
<p>The first thing I want to do is make the UI titlebar match the Emacs theme and
hide the file icon:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> emacs
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>add<span class="keyword operator arithmetic lisp">-</span><span class="keyword control lisp">to</span><span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">list</span> &#39;default<span class="keyword operator arithmetic lisp">-</span>frame<span class="keyword operator arithmetic lisp">-</span>alist &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>ns<span class="keyword operator arithmetic lisp">-</span>transparent<span class="keyword operator arithmetic lisp">-</span>titlebar . <span class="constant language lisp">t</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>add<span class="keyword operator arithmetic lisp">-</span><span class="keyword control lisp">to</span><span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">list</span> &#39;default<span class="keyword operator arithmetic lisp">-</span>frame<span class="keyword operator arithmetic lisp">-</span>alist &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>ns<span class="keyword operator arithmetic lisp">-</span>appearance . light<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> ns<span class="keyword operator arithmetic lisp">-</span>use<span class="keyword operator arithmetic lisp">-</span>proxy<span class="keyword operator arithmetic lisp">-</span>icon  <span class="constant language lisp">nil</span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> frame<span class="keyword operator arithmetic lisp">-</span>title<span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">format</span> <span class="constant language lisp">nil</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>The next thing is automatically loading <code>$PATH</code> from my shell using
<a href="https://github.com/purcell/exec-path-from-shell">exec-path-from-shell</a>—this
is important so Emacs can find our installed binaries, e.g. for language servers:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> exec<span class="keyword operator arithmetic lisp">-</span>path<span class="keyword operator arithmetic lisp">-</span><span class="keyword control lisp">from</span><span class="keyword operator arithmetic lisp">-</span>shell
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>exec<span class="keyword operator arithmetic lisp">-</span>path<span class="keyword operator arithmetic lisp">-</span><span class="keyword control lisp">from</span><span class="keyword operator arithmetic lisp">-</span>shell<span class="keyword operator arithmetic lisp">-</span>initialize<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>Another problem I ran into was writing square brackets when on the MacBook, as it
would interpret the keys as <code>Meta-5</code>/<code>Meta-6</code>.
I fixed that by updating the keybindings from
<a href="/blog/emacs-from-scratch-part-one-foundations">Part 1</a>:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> emacs
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="keyword control lisp">when</span> <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="keyword operator comparison lisp">eq</span> system<span class="keyword operator arithmetic lisp">-</span>type &#39;darwin<span class="punctuation definition group end lisp">)</span></span>
    <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> mac<span class="keyword operator arithmetic lisp">-</span>command<span class="keyword operator arithmetic lisp">-</span>modifier &#39;super<span class="punctuation definition group end lisp">)</span></span>
    <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> mac<span class="keyword operator arithmetic lisp">-</span>option<span class="keyword operator arithmetic lisp">-</span>modifier <span class="constant language lisp">nil</span><span class="punctuation definition group end lisp">)</span></span>
    <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> mac<span class="keyword operator arithmetic lisp">-</span>control<span class="keyword operator arithmetic lisp">-</span>modifier <span class="constant language lisp">nil</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>I like to keep most of my code at 80 characters, so let’s add a ruler:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> emacs
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span><span class="keyword operator arithmetic lisp">-</span>default <span class="support function lisp">fill</span><span class="keyword operator arithmetic lisp">-</span>column <span class="constant numeric lisp">80</span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">set</span><span class="keyword operator arithmetic lisp">-</span>face<span class="keyword operator arithmetic lisp">-</span>attribute &#39;<span class="support function lisp">fill</span><span class="keyword operator arithmetic lisp">-</span>column<span class="keyword operator arithmetic lisp">-</span>indicator <span class="constant language lisp">nil</span>
                      :foreground <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>#717C7C<span class="punctuation definition string end lisp">&quot;</span></span> <span class="comment line semicolon lisp"><span class="punctuation definition comment lisp">;</span> katana-gray
</span>                      :background <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>transparent<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>global<span class="keyword operator arithmetic lisp">-</span>display<span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">fill</span><span class="keyword operator arithmetic lisp">-</span>column<span class="keyword operator arithmetic lisp">-</span>indicator<span class="keyword operator arithmetic lisp">-</span>mode <span class="constant numeric lisp">1</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>Finally, we want to store backup files in <code>~/.saves</code> instead of next to the file
we’re saving:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> emacs
  :config
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> backup<span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">directory</span><span class="keyword operator arithmetic lisp">-</span>alist `<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>.<span class="punctuation definition string end lisp">&quot;</span></span> . <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>~/.saves<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<h2><a inert href="#lsp" aria-hidden="true" class="anchor" id="lsp"></a>LSP</h2>
<p>Let’s install <a href="https://company-mode.github.io">company-mode</a> first, for
auto-completion:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> company<span class="keyword operator arithmetic lisp">-</span>mode
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>global<span class="keyword operator arithmetic lisp">-</span>company<span class="keyword operator arithmetic lisp">-</span>mode<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>Now we configure the built-in LSP package
<a href="https://github.com/joaotavora/eglot">eglot</a>:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> emacs
  :hook <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>zig<span class="keyword operator arithmetic lisp">-</span>mode . eglot<span class="keyword operator arithmetic lisp">-</span>ensure<span class="punctuation definition group end lisp">)</span></span>
  :hook <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>rust<span class="keyword operator arithmetic lisp">-</span>mode . eglot<span class="keyword operator arithmetic lisp">-</span>ensure<span class="punctuation definition group end lisp">)</span></span>
  :hook <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>go<span class="keyword operator arithmetic lisp">-</span>mode . eglot<span class="keyword operator arithmetic lisp">-</span>ensure<span class="punctuation definition group end lisp">)</span></span>
  :hook <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>typescript<span class="keyword operator arithmetic lisp">-</span>mode . eglot<span class="keyword operator arithmetic lisp">-</span>ensure<span class="punctuation definition group end lisp">)</span></span>
  :general
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>leader<span class="keyword operator arithmetic lisp">-</span>keys
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>l<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>:ignore <span class="constant language lisp">t</span> :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>lsp<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>l &lt;escape&gt;<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>keyboard<span class="keyword operator arithmetic lisp">-</span>escape<span class="keyword operator arithmetic lisp">-</span>quit :which<span class="keyword operator arithmetic lisp">-</span>key <span class="constant language lisp">t</span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>l r<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>eglot<span class="keyword operator arithmetic lisp">-</span>rename :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>rename<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>l a<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>eglot<span class="keyword operator arithmetic lisp">-</span>code<span class="keyword operator arithmetic lisp">-</span>actions :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>code actions<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>This runs <code>eglot-ensure</code> in languages we have language servers installed for.
It also sets up <code>SPC l r</code> to rename a symbol and <code>SPC l a</code> to prompt for code
actions.</p>
<h2><a inert href="#tree-sitter" aria-hidden="true" class="anchor" id="tree-sitter"></a>Tree-sitter</h2>
<p>We’ll use <a href="https://github.com/renzmann/treesit-auto">treesit-auto</a> to
automatically install and use tree-sitter major modes:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> treesit<span class="keyword operator arithmetic lisp">-</span>auto
  :custom
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>treesit<span class="keyword operator arithmetic lisp">-</span>auto<span class="keyword operator arithmetic lisp">-</span>install &#39;prompt<span class="punctuation definition group end lisp">)</span></span>
  :config
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>treesit<span class="keyword operator arithmetic lisp">-</span>auto<span class="keyword operator arithmetic lisp">-</span>add<span class="keyword operator arithmetic lisp">-</span><span class="keyword control lisp">to</span><span class="keyword operator arithmetic lisp">-</span>auto<span class="keyword operator arithmetic lisp">-</span>mode<span class="keyword operator arithmetic lisp">-</span>alist &#39;all<span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>global<span class="keyword operator arithmetic lisp">-</span>treesit<span class="keyword operator arithmetic lisp">-</span>auto<span class="keyword operator arithmetic lisp">-</span>mode<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>This is handy because it doesn’t require us to think about using e.g.
<code>zig-ts-mode</code> instead of <code>zig-mode</code>, it handles everything for us.</p>
<h2><a inert href="#language-support" aria-hidden="true" class="anchor" id="language-support"></a>Language support</h2>
<p>Next, we install all language modes we need:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> markdown<span class="keyword operator arithmetic lisp">-</span>mode
  :config
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> markdown<span class="keyword operator arithmetic lisp">-</span>fontify<span class="keyword operator arithmetic lisp">-</span>code<span class="keyword operator arithmetic lisp">-</span>blocks<span class="keyword operator arithmetic lisp">-</span>natively <span class="constant language lisp">t</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> zig<span class="keyword operator arithmetic lisp">-</span>mode
  :general
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>leader<span class="keyword operator arithmetic lisp">-</span>keys
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>:ignore <span class="constant language lisp">t</span> :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>mode<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m &lt;escape&gt;<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>keyboard<span class="keyword operator arithmetic lisp">-</span>escape<span class="keyword operator arithmetic lisp">-</span>quit :which<span class="keyword operator arithmetic lisp">-</span>key <span class="constant language lisp">t</span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m b<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>zig<span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">compile</span> :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>build<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m r<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>zig<span class="keyword operator arithmetic lisp">-</span>run :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>run<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m t<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>zig<span class="keyword operator arithmetic lisp">-</span>test :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>test<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> rust<span class="keyword operator arithmetic lisp">-</span>mode
  :general
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>leader<span class="keyword operator arithmetic lisp">-</span>keys
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>:ignore <span class="constant language lisp">t</span> :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>mode<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m &lt;escape&gt;<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>keyboard<span class="keyword operator arithmetic lisp">-</span>escape<span class="keyword operator arithmetic lisp">-</span>quit :which<span class="keyword operator arithmetic lisp">-</span>key <span class="constant language lisp">t</span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m b<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>rust<span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">compile</span> :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>build<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m r<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>rust<span class="keyword operator arithmetic lisp">-</span>run :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>run<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m t<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>rust<span class="keyword operator arithmetic lisp">-</span>test :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>test<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m k<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>rust<span class="keyword operator arithmetic lisp">-</span>check :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>check<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m c<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>rust<span class="keyword operator arithmetic lisp">-</span>run<span class="keyword operator arithmetic lisp">-</span>clippy :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>clippy<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> go<span class="keyword operator arithmetic lisp">-</span>mode<span class="punctuation definition group end lisp">)</span></span>
<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> gotest
  :general
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>leader<span class="keyword operator arithmetic lisp">-</span>keys
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>:ignore <span class="constant language lisp">t</span> :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>mode<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m &lt;escape&gt;<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>keyboard<span class="keyword operator arithmetic lisp">-</span>escape<span class="keyword operator arithmetic lisp">-</span>quit :which<span class="keyword operator arithmetic lisp">-</span>key <span class="constant language lisp">t</span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m t<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>go<span class="keyword operator arithmetic lisp">-</span>test<span class="keyword operator arithmetic lisp">-</span>current<span class="keyword operator arithmetic lisp">-</span>project :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>test<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>m r<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>go<span class="keyword operator arithmetic lisp">-</span>run :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>run<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> typescript<span class="keyword operator arithmetic lisp">-</span>mode<span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>I’m using <code>SPC m</code> to change based on major-mode, e.g. means <code>SPC m t</code> means test
in most programming modes, but won’t exist in <code>markdown-mode</code>.</p>
<h2><a inert href="#search" aria-hidden="true" class="anchor" id="search"></a>Search</h2>
<p>Sometimes we don’t know what file we’re looking for, so let’s add
<a href="https://github.com/dajva/rg.el">rg.el</a> to help us find it:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> rg
  :general
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>leader<span class="keyword operator arithmetic lisp">-</span>keys
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>f<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>rg<span class="keyword operator arithmetic lisp">-</span>menu :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>find<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>This opens a Magit-like menu and allows you to search in various modes (dwim,
regex, literal, etc.).</p>
<h2><a inert href="#wrapping-up" aria-hidden="true" class="anchor" id="wrapping-up"></a>Wrapping up</h2>
<p>Opening a Zig project now looks like this<sup class="footnote-ref"><a href="#fn-3" id="fnref-3" data-footnote-ref>3</a></sup>; see also the final
<a href="/blog/emacs-config-from-scratch-part-three/init.el"><code>init.el</code></a>:</p>
<p><img src="/blog/emacs-config-from-scratch-part-three/zig-lsp.png" alt="A screenshot of Emacs with a dark theme showing Zig code and a context menu for code actions" /></p>
<p>I’m going to switch to Emacs as my primary editor and tune it further in the coming
weeks.
In the next part, I want to add support for Org-mode, show a dashboard on startup,
enable font ligatures and fix all the small things that I’ll find.</p>
<p>Subscribe to the <a href="/blog/atom.xml">RSS feed</a> so you don’t miss Part 4,
and <a href="mailto:hey@arne.me">let me know</a> what you think!</p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>Check out parts <a href="/blog/emacs-from-scratch-part-one-foundations">one</a> and
<a href="/blog/emacs-from-scratch-part-two">two</a> if you haven’t already! <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p><a href="https://en.wikipedia.org/wiki/Language_Server_Protocol">Language Server Protocol</a> <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
<li id="fn-3">
<p>By the way, I switched my theme to
<a href="https://github.com/meritamen/emacs-kanagawa-theme">Kanagawa</a>. <a href="#fnref-3" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>Writing an SDK in Zig</title><link>https://arne.me/blog/writing-an-sdk-in-zig-part-1</link><description><![CDATA[Part 1 of writing an SDK for Axiom using the Zig programming language.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/writing-an-sdk-in-zig-part-1</guid><pubDate>Thu, 09 May 2024 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>The first project I used Zig for was a rewrite of a custom static site generator
for the <a href="https://firechicken.club">Fire Chicken Webring</a>, you can read that post
here: <a href="/blog/thoughts-on-zig">Thoughts on Zig</a>.</p>
<p>Writing a small application is a lot easier than writing a library, especially
if you’re hacking it together like I was.
Let’s do something harder.</p>
<p>And because I work at <a href="https://axiom.co">Axiom</a>, we’re going to write an SDK for
the <a href="https://axiom.co/docs/restapi/endpoints">public API</a>.
In this first part I’ll set up the library and add a single <code>getDatasets</code> fn
to fetch all datasets the token has access to.</p>
<!-- more -->
<em class="note">
<p>We’re using Zig 0.12. It might not work with a different version.</p>
</em>
<h2><a inert href="#bootstrap-the-sdk" aria-hidden="true" class="anchor" id="bootstrap-the-sdk"></a>Bootstrap the SDK</h2>
<p>First, we create a directory called <code>axiom-zig</code> and run <code>zig init</code>:</p>
<pre class="syntax-highlighting"><code class="language-zsh"><span class="source shell bash"><span class="meta function-call shell"><span class="variable function shell">info:</span></span><span class="meta function-call arguments shell"> created build.zig</span>
<span class="meta function-call shell"><span class="variable function shell">info:</span></span><span class="meta function-call arguments shell"> created build.zig.zon</span>
<span class="meta function-call shell"><span class="variable function shell">info:</span></span><span class="meta function-call arguments shell"> created src/main.zig</span>
<span class="meta function-call shell"><span class="variable function shell">info:</span></span><span class="meta function-call arguments shell"> created src/root.zig</span>
<span class="meta function-call shell"><span class="variable function shell">info:</span></span><span class="meta function-call arguments shell"> see <span class="meta group expansion command backticks shell"><span class="punctuation section group begin shell">`</span><span class="meta function-call shell"><span class="variable function shell">zig</span></span><span class="meta function-call arguments shell"> build<span class="variable parameter option shell"><span class="punctuation definition parameter shell"> --</span>help</span></span><span class="punctuation section group end shell">`</span></span> for a menu of options</span>
</span></code></pre>
<p>We also want to create a <code>.gitignore</code> to ignore the following folders:</p>
<pre class="syntax-highlighting"><code><span class="text plain">/zig-cache
/zig-out
</span></code></pre>
<p>Perfect. Next step: Create a client struct in <code>root.zig</code>.
We’ll need an Axiom API token to authenticate requests, a <code>std.http.Client</code> to
make requests and an <code>std.mem.Allocator</code> to allocate and free resources:</p>
<pre class="syntax-highlighting"><code class="language-zig"><span class="text plain">const std = @import(&quot;std&quot;);
const Allocator = std.mem.Allocator;
const http = std.http;
// We&#39;ll need these later:
const fmt = std.fmt;
const json = std.json;

/// SDK provides methods to interact with the Axiom API.
pub const SDK = struct {
    allocator: Allocator,
    api_token: []const u8,
    http_client: http.Client,

    /// Create a new SDK.
    fn new(allocator: Allocator, api_token: []const u8) SDK {
        return SDK{ .allocator = allocator, .api_token = api_token, .http_client = http.Client{ .allocator = allocator } };
    }

    /// Deinitialize the SDK.
    fn deinit(self: *SDK) void {
        self.http_client.deinit();
    }
}

test &quot;SDK.init/deinit&quot; {
    var sdk = SDK.new(std.testing.allocator, &quot;token&quot;);
    defer sdk.deinit();
    try std.testing.expectEqual(sdk.api_token, &quot;token&quot;);
}
</span></code></pre>
<p>Initially I had <code>deinit(self: SDK)</code> (without the pointer). Zig didn’t like this
at all and led me down a rabbit hole of storing the <code>http.Client</code> as a pointer
too—once I found my way out and remembered I need a pointer everything worked.</p>
<p>I like that Zig encourages writing tests not only next to the source code (like
Go), not only in the same file (like Rust), but <em>next to the code it’s testing</em>.</p>
<h2><a inert href="#add-getdatasets" aria-hidden="true" class="anchor" id="add-getdatasets"></a>Add getDatasets</h2>
<p>Our first method will be <code>getDatasets</code>, which returns a list of Axiom datasets
(<a href="https://axiom.co/docs/restapi/endpoints/getDatasets">see the api documentation</a>).</p>
<h3><a inert href="#create-a-model" aria-hidden="true" class="anchor" id="create-a-model"></a>Create a model</h3>
<p>First we need a model:</p>
<pre class="syntax-highlighting"><code class="language-zig"><span class="text plain">pub const Dataset = struct {
    id: []const u8,
    name: []const u8,
    description: []const u8,
    who: []const u8,
    created: []const u8,
};
</span></code></pre>
<p>Don’t worry about <code>created</code> being a datetime, we’ll deal with that later.</p>
<h3><a inert href="#add-the-getdatasets-fn" aria-hidden="true" class="anchor" id="add-the-getdatasets-fn"></a>Add the <code>getDatasets</code> fn</h3>
<p>Let’s add a function to get the datasets to our <code>SDK</code> struct:</p>
<pre class="syntax-highlighting"><code class="language-zig"><span class="text plain">/// Get all datasets the token has access to.
/// Caller owns the memory.
fn getDatasets(self: *SDK) ![]Dataset {
    // TODO: Store base URL in global const or struct
    const url = comptime std.Uri.parse(&quot;https://api.axiom.co/v2/datasets&quot;) catch unreachable;

    // TODO: Draw the rest of the owl
}
</span></code></pre>
<p>We’re taking a pointer to <code>SDK</code> called <code>self</code> again, this means that this is a
method you call on a created <code>SDK</code>. The <code>!</code> means it can return an error.
In a later post I want to go deeper into error handling, for now it can return
<em>any</em> error.</p>
<p>Because there is no dynamic part of the URL, we can parse it at compile time
using <code>comptime</code>.
I like this explicit keyword, in Rust you need to rely on macros to achieve
something similar, or use
<a href="https://github.com/rust-lang-nursery/lazy-static.rs">lazy_static</a>.</p>
<h3><a inert href="#make-the-http-request" aria-hidden="true" class="anchor" id="make-the-http-request"></a>Make the HTTP request</h3>
<p>Let’s open a connection to the server:</p>
<pre class="syntax-highlighting"><code class="language-zig"><span class="text plain">var server_header_buffer: [4096]u8 = undefined; // Is 4kb enough?
var request = try self.http_client.open(.GET, url, .{
    .server_header_buffer = &amp;server_header_buffer,
});
defer request.deinit();
</span></code></pre>
<p>I wonder if 4kb is always enough for server headers. Especially in a library I
don’t want it to fail because the server is suddenly sending more headers.</p>
<p>Axiom uses Bearer auth, so let’s set the <code>Authorization</code> header:</p>
<pre class="syntax-highlighting"><code class="language-zig"><span class="text plain">var authorization_header_buf: [64]u8 = undefined;
// TODO: Store this on the SDK for better re-use.
const authorization_header = try fmt.bufPrint(&amp;authorization_header_buf, &quot;Bearer {s}&quot;, .{self.api_token});
request.headers.authorization = .{ .override = authorization_header };
</span></code></pre>
<p>An Axiom API is 41 characters, plus <code>Bearer </code>‘s 7 characters equals 48 characters.
We’re allocating 64 to be safe if it ever changes (it really shouldn’t).</p>
<p>Also note that I’m calling <code>try fmt.BufPrint</code>; this will return the error
to the caller of our function (that’s what the <code>!</code> indicating).</p>
<p>Finally, we can send the headers to the server and wait for a response:</p>
<pre class="syntax-highlighting"><code class="language-zig"><span class="text plain">try request.send();
try request.wait();
</span></code></pre>
<h3><a inert href="#parse-the-json-response" aria-hidden="true" class="anchor" id="parse-the-json-response"></a>Parse the JSON-response</h3>
<p>First, we need to read the body into a buffer:</p>
<pre class="syntax-highlighting"><code class="language-zig"><span class="text plain">var body: [1024 * 1024]u8 = undefined; // 1mb should be enough?
const content_length = try request.reader().readAll(&amp;body);
</span></code></pre>
<p>Same issue as with the server headers: What is a good size for a fixed buffer
here?</p>
<p>I’ve tried doing this dynamically with
<code>request.reader().readAllAlloc(...)</code>, but parsing the JSON with this allocated
memory relied on the allocated <code>[]const u8</code> for string values.
This means as soon as I deallocated the body, all string values in the returned
JSON were invalid (use-after-free). Yikes.</p>
<p>Next, we call <code>json.parseFromSlice</code> with our body:</p>
<pre class="syntax-highlighting"><code class="language-zig"><span class="text plain">const parsed_datasets = try json.parseFromSlice([]Dataset, self.allocator, body[0..content_length], .{});
defer parsed_datasets.deinit();
</span></code></pre>
<p>Now we need to copy the memory out of the <code>parsed_datasets.value</code> to prevent it
from being freed on the <code>parsed_datasets.deinit()</code> above and return it:</p>
<pre class="syntax-highlighting"><code class="language-zig"><span class="text plain">const datasets = try self.allocator.dupe(Dataset, parsed_datasets.value);
return datasets;
</span></code></pre>
<p><strong>Edit:</strong> Matthew <a href="https://fosstodon.org/@mlugg/112412986075452820">let me know on Mastodon</a>
that this implementation is still illegal, and it’s only working because the
stack memory is not getting clobbered.
You can set <code>.{ .allocate = .alloc_always }</code> in <code>json.parseFromSlice</code>,
which will dupe the strings, but not actually solve the problem (where do the
strings live?).
What I ended up doing is creating a <code>Value(T)</code> struct, which embeds both the
value and an arena allocator which I pass to <code>json.parseFromSliceLeaky</code>.
This means the value you get back from <code>getDatasets</code> will have a <code>deinit()</code>
method and you need to do <code>.value</code> to get the actual value.
You can read the updated source code <a href="https://codeberg.org/enra/axiom-zig/src/commit/e550ce96c1e0f5c429432c195dc275b59807dd9d/src/root.zig">on Codeberg</a>.</p>
<h3><a inert href="#write-a-test" aria-hidden="true" class="anchor" id="write-a-test"></a>Write a test</h3>
<p>And finally we’ll write a test where we initialize the SDK, get the datasets
and ensure <code>_traces</code> is the first one returned.
Once I set up CI, I’ll create an Axiom org just for testing so we can be sure
which datasets are returned.</p>
<pre class="syntax-highlighting"><code class="language-zig"><span class="text plain">test &quot;getDatasets&quot; {
    const allocator = std.testing.allocator;

    const api_token = try std.process.getEnvVarOwned(allocator, &quot;AXIOM_TOKEN&quot;);
    defer allocator.free(api_token);

    var sdk = SDK.new(allocator, api_token);
    defer sdk.deinit();

    const datasets = try sdk.getDatasets();
    defer allocator.free(datasets);

    try std.testing.expect(datasets.len &gt; 0);
    try std.testing.expectEqualStrings(&quot;_traces&quot;, datasets[0].name);
}
</span></code></pre>
<p>If you want to see everything together, check it out on <a href="https://codeberg.org/enra/axiom-zig/src/commit/8195a18dde78c4a4c6c8b1024b035dce9fd69744/src/root.zig">Codeberg</a>.</p>
<h2><a inert href="#next-steps" aria-hidden="true" class="anchor" id="next-steps"></a>Next steps</h2>
<p>In the next part I’ll add <code>createDataset</code>, <code>updateDataset</code> and <code>deleteDataset</code>,
initial error handling and show how you can import the library in a Zig project.</p>
<p>Any thoughts? <a href="mailto:hey@arne.me">Let me know!</a></p>
]]></content:encoded></item><item><title>Thoughts on Zig</title><link>https://arne.me/blog/thoughts-on-zig</link><description><![CDATA[I've started writing Zig. These are my thoughts.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/thoughts-on-zig</guid><pubDate>Wed, 01 May 2024 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Zig is a programming language designed by Andrew Kelley.
The <a href="https://ziglang.org">official website</a> lists three principles of the
language:</p>
<ul>
<li>No hidden control flow.</li>
<li>No hidden memory allocations.</li>
<li>No preprocessor, no macros.</li>
</ul>
<p>For someone like me coming mostly from Rust, Go and TypeScript, this is different—and different is interesting, so I wanted to know what it feels like
to write code in it.</p>
<p>Here are my thoughts after 3 nights of using Zig to rewrite the static site generator<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup> I use for the <a href="https://firechicken.club">Fire Chicken Webring</a>.
Note that this is a limited use case and only scratches the surface of the
language.</p>
<!-- more -->
<h2><a inert href="#explicit-allocation" aria-hidden="true" class="anchor" id="explicit-allocation"></a>Explicit allocation</h2>
<p>This is one of the biggest differentiators of Zig.
Go doesn’t force you to think at all, Rust forces you to think about ownership
and Zig forces you to think about allocations.
If you want to allocate memory, you need to pass an allocator to it and
remember to free it afterwards.</p>
<p>One thing that I stumbled upon a lot:
Sometimes there is a <code>.deinit()</code> function on the returned struct, sometimes
that method takes an <code>allocator</code>, sometimes you need to <code>allocator.free(value)</code>
and sometimes it returns an enum or a struct, and you need to figure out which
values you have to free.</p>
<h2><a inert href="#documentation" aria-hidden="true" class="anchor" id="documentation"></a>Documentation</h2>
<p>If you write Zig, you’ll find yourself reading Zig a lot to understand how to
use a function, which resources you must free, and possibly, why that function
panics.
There is no generated documentation like <a href="https://docs.rs">docs.rs</a> or <a href="https://pkg.go.dev">pkg.go.dev</a>; if you want to know which methods a library has, look at the source.</p>
<p>Here are some resources other than the
<a href="https://github.com/ziglang/zig/tree/master">source code</a>
that I found useful to get started:</p>
<ul>
<li><a href="https://ziglang.org/learn/overview/">In-depth Overview</a></li>
<li><a href="https://ziglang.org/documentation/">Zig Language Reference</a></li>
<li><a href="https://zig-by-example.com">Zig by Example</a></li>
<li><a href="https://cookbook.ziglang.cc">Zig Cookbook</a></li>
<li><a href="https://zig.guide">Zig Guide</a></li>
<li><a href="https://ziglang.org/documentation/master/std/#std">Zig std documentation</a></li>
</ul>
<p>Another reason you might need to look at the source code of function you’re
calling is confusing error messages.</p>
<h2><a inert href="#errors" aria-hidden="true" class="anchor" id="errors"></a>Errors</h2>
<p>The error messages of the Zig compiler can be very hard to figure out.
Here’s an example:</p>
<pre class="syntax-highlighting"><code class="language-sh"><span class="source shell bash"><span class="meta function-call shell"><span class="variable function shell">$</span></span><span class="meta function-call arguments shell"> zig build run</span>
<span class="meta function-call shell"><span class="variable function shell">Segmentation</span></span><span class="meta function-call arguments shell"> fault at address 0x102ee6000</span>
<span class="meta function-call shell"><span class="variable function shell">Panicked</span></span><span class="meta function-call arguments shell"> during a panic. Aborting.</span>
</span></code></pre>
<p>Generally, if you’re used to Rust’s <em>exceptional</em> error handling, this is rough.</p>
<p>Once I got an error from the standard library and only noticed after
reading the source code that <code>ArrayList</code> is not a supported type to pass to the
given function.
Another time, the templating library I’ve temporarily used randomly panicked
with an out-of-bounds after doing a nested loop.</p>
<h2><a inert href="#libraries" aria-hidden="true" class="anchor" id="libraries"></a>Libraries</h2>
<p>There are a bunch of libraries for Zig (see <a href="https://github.com/zigcc/awesome-zig">awesome-zig</a>) and I can only talk about
the one’s I’ve tried, but most of the libraries I’ve looked at are either
archived, a thin wrapper around a C library, heavy WIP and barely usable, or have weird error scenarios.</p>
<p>This lead to me implementing my own
<a href="https://github.com/bahlo/firechicken.club/blob/78491b3c2b04d04c4f0bfdce3b360c8081837683/src/main.zig#L241-L320">shitty datetime function</a><sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup>
and using <code>std.fmt</code> instead of templating.</p>
<p>I believe these are due to the immaturity of the language and ecosystem, but I
wouldn’t be surprised if people started building their own libraries, which they
take everywhere.</p>
<h2><a inert href="#strings" aria-hidden="true" class="anchor" id="strings"></a>Strings</h2>
<p>We have to talk about strings. Zig has none; if you want a string, use
<code>[]const u8</code>. You also can’t compare that type with <code>==</code>, you need to use a
specific function<sup class="footnote-ref"><a href="#fn-3" id="fnref-3" data-footnote-ref>3</a></sup>.</p>
<p>Initially I found this irritating—why not introduce a string type that is
<code>[]const u8</code> under the hood and overload the <code>==</code> operator?
I think it would improve developer experience, but does it fit into Zig?</p>
<h2><a inert href="#no-magic" aria-hidden="true" class="anchor" id="no-magic"></a>No magic</h2>
<p>Remember the three idioms from the beginning of the article?
Zig is huge on being <em>transparent</em>, i.e., no magic; the code you read is what
happens.</p>
<p>And I appreciate that.
In Rust, it’s common to build abstractions to hide boilerplate logic
(e.g. using macros to generate the deserialization logic of a struct), in Go
it’s common to generate code to do this.
Zig doesn’t have any of that (though I guess you could generate Zig code).
I’m not sure how well that scales in big codebases, but I think it’s
interesting.</p>
<h2><a inert href="#conclusion" aria-hidden="true" class="anchor" id="conclusion"></a>Conclusion</h2>
<p>I like Zig. For a bigger project or something that needs async<sup class="footnote-ref"><a href="#fn-4" id="fnref-4" data-footnote-ref>4</a></sup>, I’ll still
reach for Rust for its safety features and vibrant ecosystem, but for small
projects, it’s fun to reach for an interesting language.</p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>See <a href="https://arne.me/blog/write-your-own-ssg">Why You Should Write Your Own Static Site Generator</a> <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p>Maybe don’t look too closely. <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
<li id="fn-3">
<p><code>std.mem.eql</code> <a href="#fnref-3" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3">↩</a></p>
</li>
<li id="fn-4">
<p>Async functions have been removed from Zig at this time. <a href="#fnref-4" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="4" aria-label="Back to reference 4">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>DIY Music Streaming with NixOS, Jellyfin and Manet</title><link>https://arne.me/blog/diy-music-streaming-with-nixos-and-jellyfin</link><description><![CDATA[In this post I describe how I'm hosting my own music streaming service with
NixOS and Jellyfin on Hetzner for €6 / month.
]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/diy-music-streaming-with-nixos-and-jellyfin</guid><pubDate>Mon, 01 Apr 2024 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>In this post, I describe how I’m hosting my own music streaming service with
NixOS, Jellyfin and Manet on Hetzner for €6 / month.</p>
<p>If you know your way around servers, this is neither a novel nor a complicated
setup—quite the opposite; the beauty of this configuration lies in its
simplicity.</p>
<!-- more -->
<h2><a inert href="#table-of-contents" aria-hidden="true" class="anchor" id="table-of-contents"></a>Table of Contents</h2>
<ul>
<li><a href="#set-up-a-vm-with-nixos">Set up a VM with NixOS</a></li>
<li><a href="#install-and-configure-packages">Install and configure packages</a></li>
<li><a href="#configure-jellyfin">Configure Jellyfin</a></li>
<li><a href="#apps">Apps</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
<h2><a inert href="#set-up-a-vm-with-nixos" aria-hidden="true" class="anchor" id="set-up-a-vm-with-nixos"></a>Set up a VM with NixOS</h2>
<p>The smallest (and cheapest) VM on Hetzner Cloud, CX11 with 1 VCPU and 2 GB
RAM, is more than enough<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup>.</p>
<p>Because my music collection is still pretty small, I’ve decided to go with
Hetzner volumes.
If you have more than 100 GB of music, I’d recommend looking into a different
storage solution in combination with <code>rclone</code>, see
<a href="https://arne.me/blog/plex-on-nixos/#setting-up-storage">the storage section of an earlier post</a>.</p>
<p>Hetzner doesn’t have a one-click NixOS image, so I chose any image and followed
the “traditional” ISO installation as described in this repo:
<a href="https://github.com/nix-community/nixos-install-scripts/tree/master/hosters/hetzner-cloud">nixos-install-scripts</a>.
The <a href="https://nixos.wiki/wiki/Install_NixOS_on_Hetzner_Cloud">NixOS docs</a> also
recommend <a href="https://github.com/elitak/nixos-infect">nixos-infect</a>—if you take
this path, I’d be genuinely interested to <a href="/contact">hear how it worked</a>.</p>
<p>Then, just to be safe, I ran <code>nix-channel --update</code> and
<code>nixos-rebuild switch --upgrade</code> on the VM to upgrade everything to the latest
version.</p>
<p>After finding my attached volume with <code>ls /dev/sd*</code> and mounting it with
<code>mount /dev/sdb /mnt/media</code> (replace <code>/dev/sdb</code> with the path of your volume),
I ran <code>nixos-generate-config</code> to regenerate the hardware configuration and
automatically mount it on the next boot.</p>
<p>Because this VM is only for music, I didn’t do anything to harden the vm.
If you want more security, take a look at the excellent
<a href="https://xeiaso.net/blog/paranoid-nixos-2021-07-18/">Paranoid NixOS Setup</a>
article by Xe.</p>
<h2><a inert href="#install-and-configure-packages" aria-hidden="true" class="anchor" id="install-and-configure-packages"></a>Install and configure packages</h2>
<p>On the VM, I opened <code>/etc/nixos/configuration.nix</code> in a text editor.
Nano comes installed out of the box, but you can get vim in an ephemeral shell
with <code>nix-shell -p vim</code>.</p>
<p>In that file, I started by changing <code>networking.hostName</code> and <code>time.timeZone</code>.
Subsequently, I configured the installed packages:</p>
<pre class="syntax-highlighting"><code class="language-nix"><span class="text plain">environment.systemPackages = with pkgs; [
  jellyfin
  jellyfin-web
  jellyfin-ffmpeg
  caddy
  logrotate
];
</span></code></pre>
<h3><a inert href="#jellyfin" aria-hidden="true" class="anchor" id="jellyfin"></a>Jellyfin</h3>
<p>Jellyfin is an open-source media server, comparable to Plex.
I installed Jellyfin itself (<code>jellyfin</code>), the official Jellyfin web client
(<code>jellyfin-web</code>), and a modified version of FFmpeg for
<a href="https://jellyfin.org/docs/general/administration/hardware-acceleration/#supported-acceleration-methods">hardware acceleration</a> (<code>jellyfin-ffmpeg</code>).
Let’s enable Jellyfin:</p>
<pre class="syntax-highlighting"><code class="language-nix"><span class="text plain">services.jellyfin.enable = true;
</span></code></pre>
<h3><a inert href="#caddy" aria-hidden="true" class="anchor" id="caddy"></a>Caddy</h3>
<p>We need a reverse proxy, mostly for automatic HTTPS.<br />
I chose Caddy because the configuration is the shortest<sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup>:</p>
<pre class="syntax-highlighting"><code class="language-nix"><span class="text plain">services.caddy = {
  enable = true;
  virtualHosts.&quot;jellyfin.example.com&quot;.extraConfig = &#39;&#39;
    reverse_proxy 127.0.0.1:8096
  &#39;&#39;;
};
networking.firewall.allowedTCPPorts = [ 80 443 ];
</span></code></pre>
<p>You should replace <code>jellyfin.example.com</code> with the domain you want to use for
your Jellyfin setup. Make sure to point <code>A</code> and <code>AAAA</code> records in your domain
provider to the public IP addresses of the VM.</p>
<h3><a inert href="#logrotate" aria-hidden="true" class="anchor" id="logrotate"></a>Logrotate</h3>
<p>After a few days, I was uploading more music and noticed, that my SFTP uploads
were stalled.
After hours of debugging, I found out, that this apparently was due to a very
large <code>/var/log/auth.log</code> beeing too large<sup class="footnote-ref"><a href="#fn-3" id="fnref-3" data-footnote-ref>3</a></sup>.</p>
<p><code>logrotate</code> fixes this by automatically rotating, compressing and removing this
(and other) log files.
It’s enabled like this:</p>
<pre class="syntax-highlighting"><code class="language-nix"><span class="text plain">  services.logrotate.enable = true;
</span></code></pre>
<h3><a inert href="#auto-upgrade" aria-hidden="true" class="anchor" id="auto-upgrade"></a>Auto-upgrade</h3>
<p>To keep maintenance low, I enable automatic system upgrades using the following
configuration:</p>
<pre class="syntax-highlighting"><code class="language-nix"><span class="text plain">system.autoUpgrade = {
  enable = true;
  allowReboot = true;
}
</span></code></pre>
<p>This checks every day at 4:40 am by default, so the chances of the VM rebooting
while I want to listen to music are very slim.</p>
<h3><a inert href="#apply-changes" aria-hidden="true" class="anchor" id="apply-changes"></a>Apply changes</h3>
<p>Run <code>nixos-rebuild switch</code> to apply the configuration.</p>
<h2><a inert href="#configure-jellyfin" aria-hidden="true" class="anchor" id="configure-jellyfin"></a>Configure Jellyfin</h2>
<p>Go to the address of your server and follow the installation wizard to set up
Jellyfin<sup class="footnote-ref"><a href="#fn-4" id="fnref-4" data-footnote-ref>4</a></sup>.
Create a library for your music and upload some to <code>/mnt/media</code>.</p>
<h2><a inert href="#apps" aria-hidden="true" class="anchor" id="apps"></a>Apps</h2>
<p>Having a good mobile app is crucial for me.
The official Jellyfin apps are excellent for configuring Jellyfin, but not
great for listening to music.</p>
<p>When I discovered <a href="https://tilo.dev/manet/">Manet</a>, an iOS app, it changed
everything.
While it looks and feels very similar to Apple Music, it works significantly
better.
André, the developer who is developing this on the side, is super responsive and
quick to fix the occasional bug.
Seriously, if you use Jellyfin and iOS, and not use this app, you’re missing out.
Did I mention that it’s beautiful and free?</p>
<p>On the web, I’m using the web client.
It’s not great, but it’s fine.
Hoping for Manet on macOS one day<sup class="footnote-ref"><a href="#fn-5" id="fnref-5" data-footnote-ref>5</a></sup>.</p>
<h2><a inert href="#conclusion" aria-hidden="true" class="anchor" id="conclusion"></a>Conclusion</h2>
<p>And there you have it: A self-hosted music streaming service for around €6 /
month, configured with a few lines of code and requiring little maintenance.</p>
<p>I’ve been using this for over a month now, and I couldn’t be happier.</p>
<p>What does your setup look like? Any feedback? <a href="/contact">Let me know!</a></p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>I have a load average of 0.05 while listening to music. It’s fine. <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p>Caddy adds a <code>Server</code> header by default. To remove it, add <code>header /* { -Server }</code> to your config. <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
<li id="fn-3">
<p>Very deep in Stack Overflow threads was one comment, suggesting that the
log file was too large.
I can’t find it anymore.
Do you have additional information?
<a href="/contact">I’d love to hear from you!</a> <a href="#fnref-3" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3">↩</a></p>
</li>
<li id="fn-4">
<p>I wish this could be configured with Nix. <a href="#fnref-4" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="4" aria-label="Back to reference 4">↩</a></p>
</li>
<li id="fn-5">
<p>No pressure, André! <a href="#fnref-5" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="5" aria-label="Back to reference 5">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>I Lived 7 Days without a Smartphone</title><link>https://arne.me/blog/i-lived-7-days-without-a-smartphone</link><description><![CDATA[I left my smart phone at my desk for 7 days and instead used my Apple Watch
Ultra 2. This is how it went.
]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/i-lived-7-days-without-a-smartphone</guid><pubDate>Mon, 29 Jan 2024 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>I’m addicted to my phone. You are probably too.</p>
<p>And I’m trying to fix this: I don’t have TikTok<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup> or Instagram, I only use YouTube in Safari with <a href="https://apps.apple.com/us/app/blockyt-for-safari/id1575379461">Shorts blocked</a>, and I use <a href="https://one-sec.app">one-sec</a> for apps that I don’t want to waste my time on. But still, time-sinks keep creeping in after a week or two.</p>
<p>So, I did what I wanted to do for years: I put my iPhone at my desk and left it there for 7 days<sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup>. Here’s how it went.</p>
<!-- more -->
<h2><a inert href="#the-setup" aria-hidden="true" class="anchor" id="the-setup"></a>The setup</h2>
<p>Here’s all the things I can do reasonably effective with my Apple Watch Ultra 2:</p>
<ul>
<li>Inspect and create calendar events</li>
<li>Manage my tasks with the <a href="https://www.omnigroup.com/omnifocus">OmniFocus 4</a> watch app</li>
<li>Send and receive texts with a swipe-capable on-screen keyboard or voice input</li>
<li>Make and receive phone calls</li>
<li>Track my hydration using <a href="https://waterminder.com">Waterminder</a></li>
<li>Track medication</li>
<li>Check the weather with <a href="https://www.meetcarrot.com/weather/">Carrot Weather</a></li>
<li>Record workouts with <a href="https://gentler.app">Gentler Streak</a></li>
<li>Record voice notes with <a href="https://www.openplanetsoftware.com/just-press-record/">Just Press Record</a></li>
<li>Navigate to and in places with Maps</li>
<li>Listen to music and podcasts with Apple’s apps</li>
</ul>
<p>I also utilized the Action Button to run a Shortcut with the following menu:</p>
<ul>
<li>New OmniFocus inbox item</li>
<li>Start audio recording</li>
<li>Toggle my office light (for that sweet sweet Rembrandt lighting)</li>
<li>Defer OmniFocus items to tomorrow (you can’t do that using the watchOS app)</li>
</ul>
<h2><a inert href="#how-it-changed-my-everyday-life" aria-hidden="true" class="anchor" id="how-it-changed-my-everyday-life"></a>How it changed my everyday life</h2>
<p>Instead of being constantly reachable—not only via text or calls, but also via email and socials, I sat down in the evenings for some admin time.
In that time, I would check and reply to emails and non-iMessage text messages, as well as going through OmniFocus and Just Press Record data to clean it up.</p>
<p>But besides that time, I was just—present. When I was out and about and waiting for something, I was forced to just <em>wait</em>. This gave me time to reflect and think things through, a time I didn’t know I was missing. Instead of having to make an effort to not look at my phone when I’m with friends, I was relaxed because I had no other choice. And instead of doom-scrolling while watching a TV show on the couch, I just watched the TV show (a nice side effect is that this raises the bar of content you consume).</p>
<p>But there were some downsides: I bought a new pair of glasses in that week and had no phone to make a quick photo (I hope I still like them when they arrive this week, lol). My watch had an issue with phone calls one morning, so I couldn’t make or take phone calls (a restart eventually fixed it). My 4-year-old complained that I can’t play an audiobook or music for him while in the car. Because the swipe keyboard was not as reliable as I wished it’d been, I used the voice input in places where it might be awkward. I had to take a piece of paper to write things down that I needed to remember (like the model of the lenses for my new glasses).</p>
<p>Here’s a guest contribution from my wife:</p>
<blockquote>
<p>This husband of mine has many ideas. The “I dump my phone”-experiment was not my favorite one. I’m all for reducing your screen time and limiting doomscrolling. But being unreachable because your Apple Watch decides to throw a tantrum, not being able to answer quickly to a text or to capture an important family moment because you don’t have a camera—that’s just not practical, especially when you forget to recharge your watch. So this little experiment drove me crazy more than once tbh—and I am a little relieved it’s over now. I really do hope he still likes his new glasses when they arrive. It never gets boring with this one.</p>
</blockquote>
<h2><a inert href="#should-you-do-this" aria-hidden="true" class="anchor" id="should-you-do-this"></a>Should you do this?</h2>
<p>This heavily depends on how much you are willing to give up. You can carry a notebook and pen and a camera and fix some of the issues noted above. Maybe you don’t need to text as much.</p>
<p>I definitely recommend trying it; I got my phone back today, and I appreciate both the flexibility—I can do almost anything with it—and being more present in some situations.</p>
<p>That being said, I was looking forward to getting my phone back after four days. Worth a try.</p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>I tried TikTok for a few days and had ~4h screen time per day just for that app. <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p>Full disclosure: I made one exception when I had to drive somewhere with navigation and my Watch was at 10%. <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>My Favorite Articles of 2023</title><link>https://arne.me/blog/my-favorite-articles-of-2023</link><description><![CDATA[In this post I collect my favourite articles of the whole year.
]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/my-favorite-articles-of-2023</guid><pubDate>Sun, 31 Dec 2023 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>This year I’ve read over 500 articles for my newsletter,
<a href="/weekly">Arne’s Weekly</a>.
For this post, I’ve distilled them down to the ones I liked the most and/or
that had the biggest impact on my life.</p>
<!-- more -->
<p>Here they are, in no particular order:</p>
<ul>
<li><a href="https://mikefisher.substack.com/p/im-on-it">“I’m On It”</a>: Mike Fisher writes about their favorite response when given a task.</li>
<li><a href="https://www.nationalgeographic.co.uk/history-and-civilisation/2022/08/the-science-of-why-you-have-great-ideas-in-the-shower">The science of why you have great ideas in the shower</a>: Stacey Colino has the neurological explanation for the reasons you get great ideas in the shower or find bug fixes when on a walk: The default mode network in your brain.</li>
<li><a href="https://world.hey.com/joaoqalves/bricks-of-love-create-purpose-and-engagement-with-weekly-updates-4a91aa61">Bricks of Love: create purpose and engagement with weekly updates</a>: João Alves about purpose, motivation, and the magic of weekly updates.</li>
<li><a href="https://anthonyhobday.com/sideprojects/saferules/">Visual design rules you can safely follow every time</a>: Anthony Hobday has a list of design rules that you should remember.</li>
<li><a href="https://arendjr.nl/2023/04/mvp-the-most-valuable-programmer">The Most Valuable Programmer</a>: Arend van Beelen jr. writes about the feature that make someone an MVP (Most Valuable Programmer).</li>
<li><a href="https://eukaryotewritesblog.com/2021/05/02/theres-no-such-thing-as-a-tree/">There’s no such thing as a tree (phylogenetically)</a>: Georgia Ray explains why most trees developed individually and other weird botanic quirks.</li>
<li><a href="https://hbr.org/2017/02/8-ways-to-read-a-lot-more-books-this-year">8 Ways to Read (a Lot) More Books This Year</a>: Neil Pasricha writes a very motivating article with tips to read more books.</li>
<li><a href="https://annehelen.substack.com/p/youd-be-happier-living-closer-to">You’d Be Happier Living Closer to Friends. Why Don’t You?</a>: Anne Helen Petersen asks why people don’t live closer to friends (i.e., in the same street).</li>
<li><a href="https://mashable.com/article/best-self-help-book-advice">Every self-help book ever, boiled down to 11 simple rules</a>: Chris Taylor tries to distill the essence of hundreds of self-help books into eleven rules.</li>
<li><a href="https://julian.digital/2023/07/06/multi-layered-calendars/">Multi-layered calendars</a>: Julian noticed that our calendars are stuck in the past and explores a more modern approach using different layers for different activities.</li>
<li><a href="https://blog.johnqian.com/startup-spark">How a startup loses its spark</a>: John Qian explains what makes a startup intoxicating and how these qualities are eroded with more employees.</li>
</ul>
<p>What were your favorites? <a href="mailto:hey@arne.me">Let me know!</a></p>
]]></content:encoded></item><item><title>Emacs Config From Scratch, Part Two: Projects and Keybindings</title><link>https://arne.me/blog/emacs-from-scratch-part-two</link><description><![CDATA[In this second post in my Emacs from Scratch series, we’ll
set up a way to manage projects, quickly find files, set up
custom keybindings, interact with Git and open a terminal inside Emacs.
]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/emacs-from-scratch-part-two</guid><pubDate>Thu, 28 Dec 2023 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>This is the second post in my Emacs From Scratch series.</p>
<p>In <a href="/blog/emacs-from-scratch-part-one-foundations">Part 1</a>, we’ve set up
the UI and evil mode.
Today we’ll set up a way to manage projects, quickly find files, set up custom keybindings, interact with Git and open a terminal inside Emacs.</p>
<!-- more -->
<h2><a inert href="#table-of-contents" aria-hidden="true" class="anchor" id="table-of-contents"></a>Table of Contents</h2>
<ul>
<li><a href="#project-manager">Project manager</a></li>
<li><a href="#custom-keybindings">Custom keybindings</a></li>
<li><a href="#fuzzy-finding">Fuzzy finding</a></li>
<li><a href="#git-integration">Git integration</a></li>
<li><a href="#terminal">Terminal</a></li>
<li><a href="#small-tweaks">Small tweaks</a>
<ul>
<li><a href="#make-ctrl-u-work-like-in-vim">Make <code>Ctrl-u</code> work like in Vim</a></li>
<li><a href="#comment-lines">Comment lines</a></li>
<li><a href="#optimizing-the-garbage-collector">Optimizing the garbage collector</a></li>
<li><a href="#dont-use-esc-as-a-modifier">Don’t use ESC as a modifier</a></li>
</ul>
</li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
<h2><a inert href="#project-manager" aria-hidden="true" class="anchor" id="project-manager"></a>Project manager</h2>
<p>Vim is usually terminal-first; you navigate to a directory and open Vim.
Emacs is the other way around; you start Emacs, open a project and maybe a terminal buffer (see <a href="#terminal">Terminal</a> further down).</p>
<p>Let’s set up <a href="https://github.com/bbatsov/projectile">Projectile</a> to manage our
projects and quickly find files.</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> projectile
  :demand
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>projectile<span class="keyword operator arithmetic lisp">-</span>mode <span class="keyword operator arithmetic lisp">+</span><span class="constant numeric lisp">1</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>You can now run <code>M-x</code> (a.k.a. <code>Opt-x</code> on macOS) and type
<code>projectile-add-known-project</code> to add a project as well as
<code>projectile-switch-project</code> to open a project.</p>
<p>This is neither fast, nor discoverable. Let’s set up some custom keybindings.</p>
<h2><a inert href="#custom-keybindings" aria-hidden="true" class="anchor" id="custom-keybindings"></a>Custom keybindings</h2>
<p>Before we define our own keybindings, we need to do something to improve
discoverability. <a href="https://github.com/justbur/emacs-which-key">which-key</a> will
show available commands as you start a keybinding sequence:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> which<span class="keyword operator arithmetic lisp">-</span>key
  :demand
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> which<span class="keyword operator arithmetic lisp">-</span>key<span class="keyword operator arithmetic lisp">-</span>idle<span class="keyword operator arithmetic lisp">-</span>delay <span class="constant numeric lisp">0.5</span><span class="punctuation definition group end lisp">)</span></span> <span class="comment line semicolon lisp"><span class="punctuation definition comment lisp">;</span> Open after .5s instead of 1s
</span>  :config
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>which<span class="keyword operator arithmetic lisp">-</span>key<span class="keyword operator arithmetic lisp">-</span>mode<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>We’ll use <a href="https://github.com/noctuid/general.el">general.el</a> because it makes
it super easy to define keybindings and allows us to define them in a
<code>use-package</code> function.</p>
<p>We’ll have <code>SPC</code> as our leader key, which allows us to press <code>SPC</code> and have all our custom keybindings show up.</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> general
  :demand
  :config
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>general<span class="keyword operator arithmetic lisp">-</span>evil<span class="keyword operator arithmetic lisp">-</span>setup<span class="punctuation definition group end lisp">)</span></span>

  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>general<span class="keyword operator arithmetic lisp">-</span>create<span class="keyword operator arithmetic lisp">-</span>definer leader<span class="keyword operator arithmetic lisp">-</span>keys
    :states &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>normal insert visual emacs<span class="punctuation definition group end lisp">)</span></span>
    :keymaps &#39;override
    :prefix <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>SPC<span class="punctuation definition string end lisp">&quot;</span></span>
    :global<span class="keyword operator arithmetic lisp">-</span>prefix <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>C-SPC<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>

  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>leader<span class="keyword operator arithmetic lisp">-</span>keys
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>x<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>execute<span class="keyword operator arithmetic lisp">-</span>extended<span class="keyword operator arithmetic lisp">-</span>command :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>execute command<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>r<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>restart<span class="keyword operator arithmetic lisp">-</span>emacs :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>restart emacs<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>i<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">lambda</span> <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="punctuation definition group end lisp">)</span></span> <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>interactive<span class="punctuation definition group end lisp">)</span></span> <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">find</span><span class="keyword operator arithmetic lisp">-</span>file user<span class="keyword operator arithmetic lisp">-</span>init<span class="keyword operator arithmetic lisp">-</span>file<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span> :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>open init file<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>

    <span class="comment line semicolon lisp"><span class="punctuation definition comment lisp">;</span>; Buffer
</span>    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>b<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>:ignore <span class="constant language lisp">t</span> :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>buffer<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="comment line semicolon lisp"><span class="punctuation definition comment lisp">;</span>; Don&#39;t show an error because SPC b ESC is undefined, just abort
</span>    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>b &lt;escape&gt;<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>keyboard<span class="keyword operator arithmetic lisp">-</span>escape<span class="keyword operator arithmetic lisp">-</span>quit :which<span class="keyword operator arithmetic lisp">-</span>key <span class="constant language lisp">t</span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>bd<span class="punctuation definition string end lisp">&quot;</span></span>  &#39;kill<span class="keyword operator arithmetic lisp">-</span>current<span class="keyword operator arithmetic lisp">-</span>buffer
  <span class="punctuation definition group end lisp">)</span></span>
</span></span></code></pre>
<p>We’re using <code>:which-key</code> to add a description that will show up next to the
command.
To add custom keybindings to Projectile, we need to edit the <code>use-package</code>
definition and move it after the <code>use-package general</code> function:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> projectile
  :demand
  :general
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>leader<span class="keyword operator arithmetic lisp">-</span>keys
    :states &#39;normal
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>SPC<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>projectile<span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">find</span><span class="keyword operator arithmetic lisp">-</span>file :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>find file<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>

    <span class="comment line semicolon lisp"><span class="punctuation definition comment lisp">;</span>; Buffers
</span>    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>b b<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>projectile<span class="keyword operator arithmetic lisp">-</span>switch<span class="keyword operator arithmetic lisp">-</span><span class="keyword control lisp">to</span><span class="keyword operator arithmetic lisp">-</span>buffer :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>switch buffer<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>

    <span class="comment line semicolon lisp"><span class="punctuation definition comment lisp">;</span>; Projects
</span>    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>p<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>:ignore <span class="constant language lisp">t</span> :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>projects<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>p &lt;escape&gt;<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>keyboard<span class="keyword operator arithmetic lisp">-</span>escape<span class="keyword operator arithmetic lisp">-</span>quit :which<span class="keyword operator arithmetic lisp">-</span>key <span class="constant language lisp">t</span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>p p<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>projectile<span class="keyword operator arithmetic lisp">-</span>switch<span class="keyword operator arithmetic lisp">-</span>project :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>switch project<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>p a<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>projectile<span class="keyword operator arithmetic lisp">-</span>add<span class="keyword operator arithmetic lisp">-</span>known<span class="keyword operator arithmetic lisp">-</span>project :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>add project<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>p r<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>projectile<span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">remove</span><span class="keyword operator arithmetic lisp">-</span>known<span class="keyword operator arithmetic lisp">-</span>project :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>remove project<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>projectile<span class="keyword operator arithmetic lisp">-</span>mode <span class="keyword operator arithmetic lisp">+</span><span class="constant numeric lisp">1</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>Now we can press <code>SPC</code> and get suggestions that we can navigate along.
<code>SPC SPC</code> lets you open a file in the current project (or a project if none is open),
<code>SPC b</code> opens buffer options, <code>SPC p</code> project options, etc.</p>
<p>This is what it looks like:</p>
<picture>
  <source srcset="/blog/emacs-from-scratch-part-two/which-key.avif" type="image/avif" />
  <img src="/blog/emacs-from-scratch-part-two/which-key.avif" alt="And Emacs window with a drawer open showing different shortcuts, e.g. SPC → find file, r → +buffer et al" />
</picture>
<p>But when you try to find a file, or add a project, you’ll notice that this is
clunky as you need to type the exact path for it to work.
Let’s fix that.</p>
<h2><a inert href="#fuzzy-finding" aria-hidden="true" class="anchor" id="fuzzy-finding"></a>Fuzzy finding</h2>
<p>We’ll be using <a href="https://github.com/abo-abo/swiper">ivy</a> as our generic completion frontend.
This will make choosing files and projects a lot more ergonomic:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> ivy
  :config
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>ivy<span class="keyword operator arithmetic lisp">-</span>mode<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>Now we get a list of possible entries and live-search.</p>
<h2><a inert href="#git-integration" aria-hidden="true" class="anchor" id="git-integration"></a>Git integration</h2>
<p>To manage source control, we’ll use <a href="https://github.com/magit/magit">Magit</a>, which is–rightfully–widely considered to be the best Git client ever:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> magit
  :general
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>leader<span class="keyword operator arithmetic lisp">-</span>keys
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>g<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>:ignore <span class="constant language lisp">t</span> :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>git<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>g &lt;escape&gt;<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>keyboard<span class="keyword operator arithmetic lisp">-</span>escape<span class="keyword operator arithmetic lisp">-</span>quit :which<span class="keyword operator arithmetic lisp">-</span>key <span class="constant language lisp">t</span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>g g<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>magit<span class="keyword operator arithmetic lisp">-</span>status :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>status<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>g l<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>magit<span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">log</span> :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>log<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>general<span class="keyword operator arithmetic lisp">-</span>nmap
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>&lt;escape&gt;<span class="punctuation definition string end lisp">&quot;</span></span> <span class="constant character lisp"><span class="punctuation definition constant lisp">#</span>&#39;transient-quit-one</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>To make Magit work nicely with Evil, let’s add <a href="https://github.com/emacs-evil/evil-collection">evil-collection</a>:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> evil<span class="keyword operator arithmetic lisp">-</span>collection
  :after evil
  :demand
  :config
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>evil<span class="keyword operator arithmetic lisp">-</span>collection<span class="keyword operator arithmetic lisp">-</span>init<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>In addition we need to add this to the <code>:init</code> block of <code>use-package evil</code> to
prevent evil and evil-collection interfering:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> evil<span class="keyword operator arithmetic lisp">-</span>want<span class="keyword operator arithmetic lisp">-</span>keybinding <span class="constant language lisp">nil</span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>Now <code>SPC g g</code> will open up the current Git status.
You can stage single files with <code>s</code>, all files with <code>S</code> and commit by pressing <code>cc</code>.</p>
<p>Finally, we want to highlight uncommited changes in the gutter:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> diff<span class="keyword operator arithmetic lisp">-</span>hl
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>add<span class="keyword operator arithmetic lisp">-</span>hook &#39;magit<span class="keyword operator arithmetic lisp">-</span>pre<span class="keyword operator arithmetic lisp">-</span>refresh<span class="keyword operator arithmetic lisp">-</span>hook &#39;diff<span class="keyword operator arithmetic lisp">-</span>hl<span class="keyword operator arithmetic lisp">-</span>magit<span class="keyword operator arithmetic lisp">-</span>pre<span class="keyword operator arithmetic lisp">-</span>refresh<span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>add<span class="keyword operator arithmetic lisp">-</span>hook &#39;magit<span class="keyword operator arithmetic lisp">-</span>post<span class="keyword operator arithmetic lisp">-</span>refresh<span class="keyword operator arithmetic lisp">-</span>hook &#39;diff<span class="keyword operator arithmetic lisp">-</span>hl<span class="keyword operator arithmetic lisp">-</span>magit<span class="keyword operator arithmetic lisp">-</span>post<span class="keyword operator arithmetic lisp">-</span>refresh<span class="punctuation definition group end lisp">)</span></span>
  :config
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>global<span class="keyword operator arithmetic lisp">-</span>diff<span class="keyword operator arithmetic lisp">-</span>hl<span class="keyword operator arithmetic lisp">-</span>mode<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<h2><a inert href="#terminal" aria-hidden="true" class="anchor" id="terminal"></a>Terminal</h2>
<p>Even though we’ll try to make everything work from inside Emacs, sometimes it’s just quicker and easier to have a shell.</p>
<p>We’ll use <a href="https://github.com/akermu/emacs-libvterm">vterm</a> as a terminal emulator we can use inside emacs.</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> vterm<span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>We’ll also install <a href="https://github.com/jixiuf/vterm-toggle">vterm-toggle</a>, a package that allows us to toggle between the active buffer and a vterm buffer:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> vterm<span class="keyword operator arithmetic lisp">-</span>toggle
  :general
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>leader<span class="keyword operator arithmetic lisp">-</span>keys
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>&#39;<span class="punctuation definition string end lisp">&quot;</span></span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>vterm<span class="keyword operator arithmetic lisp">-</span>toggle :which<span class="keyword operator arithmetic lisp">-</span>key <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>terminal<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>Pressing <code>SPC '</code> will open a terminal. Pressing it again will hide it, but keep any processes running.</p>
<h2><a inert href="#small-tweaks" aria-hidden="true" class="anchor" id="small-tweaks"></a>Small tweaks</h2>
<p>As every time, we’ll do a few small tweaks:</p>
<h3><a inert href="#make-ctrl-u-work-like-in-vim" aria-hidden="true" class="anchor" id="make-ctrl-u-work-like-in-vim"></a>Make <code>Ctrl-u</code> work like in Vim</h3>
<p>Add the following to the <code>:init</code> block of <code>use-package evil</code>:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> evil<span class="keyword operator arithmetic lisp">-</span>want<span class="keyword operator arithmetic lisp">-</span>C<span class="keyword operator arithmetic lisp">-</span>u<span class="keyword operator arithmetic lisp">-</span>scroll <span class="constant language lisp">t</span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<h3><a inert href="#comment-lines" aria-hidden="true" class="anchor" id="comment-lines"></a>Comment lines</h3>
<p>We want similar behaviour to <a href="https://github.com/tpope/vim-commentary">commentary.vim</a> and comment objects in/out with <code>gc</code>:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> evil<span class="keyword operator arithmetic lisp">-</span>nerd<span class="keyword operator arithmetic lisp">-</span>commenter
  :general
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>general<span class="keyword operator arithmetic lisp">-</span>nvmap
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>gc<span class="punctuation definition string end lisp">&quot;</span></span> &#39;evilnc<span class="keyword operator arithmetic lisp">-</span>comment<span class="keyword operator arithmetic lisp">-</span>operator<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<h3><a inert href="#optimizing-the-garbage-collector" aria-hidden="true" class="anchor" id="optimizing-the-garbage-collector"></a>Optimizing the garbage collector</h3>
<p>We’ll use <a href="https://github.com/emacsmirror/gcmh">GCMH</a>, “the Garbage Collector Magic Hack”, to minimize GC interference with user activity:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> gcmh
  :demand
  :config
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>gcmh<span class="keyword operator arithmetic lisp">-</span>mode <span class="constant numeric lisp">1</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>Move this up to be the first package loaded after configuring <code>use-package</code>, to improve start-up time.</p>
<h3><a inert href="#dont-use-esc-as-a-modifier" aria-hidden="true" class="anchor" id="dont-use-esc-as-a-modifier"></a>Don’t use ESC as a modifier</h3>
<p>If you want to exit a menu, <code>&lt;escape&gt;</code> is the key of choice, esp. coming from Vim. Let’s match that behaviour:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> emacs
  :init
	<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>global<span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">set</span><span class="keyword operator arithmetic lisp">-</span>key <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>kbd <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>&lt;escape&gt;<span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span> &#39;keyboard<span class="keyword operator arithmetic lisp">-</span>escape<span class="keyword operator arithmetic lisp">-</span>quit<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<h2><a inert href="#conclusion" aria-hidden="true" class="anchor" id="conclusion"></a>Conclusion</h2>
<p>We now have everything we need to manage projects, navigate to files, run terminal commands and manage Git.
Starting with this post, I’m using this very setup to edit this series.</p>
<p>In part 3, we’ll set up Tree-sitter and, if available, LSP for Rust, Go, TypeScript and Markdown.</p>
<p>Subscribe to the <a href="/blog/atom.xml">RSS Feed</a> so you don’t miss the following
parts, and <a href="/contact">let me know</a> what you think!</p>
]]></content:encoded></item><item><title>Yearly Themes 2024</title><link>https://arne.me/blog/yearly-themese-2024</link><description><![CDATA[I review my yearly themes for 2023 and set new ones for the coming year.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/yearly-themese-2024</guid><pubDate>Wed, 27 Dec 2023 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Instead of setting a fixed goal as my New Year’s resolution, only to then not
even make it through January, I’m setting Yearly Themes.
CGP Grey has a <a href="https://www.youtube.com/watch?v=NVGuFdX5guE">great video on them</a>,
here’s a quote:</p>
<blockquote>
<p>For some things, precision matters, for others, it doesn’t and when trying
to build yourself into a better version of yourself, exact data points don’t
matter.
All that matters, is the trend line.</p>
</blockquote>
<p>Yearly Themes act as a signpost to give your year a direction—the idea is to
keep them in mind when making decisions, no matter how small.</p>
<!-- more -->
<h2><a inert href="#looking-back-at-2023" aria-hidden="true" class="anchor" id="looking-back-at-2023"></a>Looking back at 2023</h2>
<p>My 2023 started with two themes:</p>
<dl>
<dt>Year of Business</dt>
<dd>
<p>In the past, most of my side projects were open-source and/or non-profit.
I wanted to dive into figuring out if side projects with actual revenue was
something I wanted to pursue.</p>
</dd>
<dt>Year of Adventure</dt>
<dd>
<p>I have a desk job and work from home and while I enjoy watching outdoor
media, I have very little actual experience.</p>
</dd>
</dl>
<p>In the second half of 2023 I switched themes<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup> to:</p>
<dl>
<dt>Year of Balance</dt>
<dd>
<p>My time is mostly divided into family, work, side projects and leisure.
I want to be conscious of keeping a balance in my time invested.</p>
</dd>
</dl>
<p>I’m okay with how Year of Business and Year of Adventure went; I have a better
idea of what it takes to turn a hobby project into something that generates
revenue, and we’re leasing an orchard<sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup>.</p>
<p>Year of Balance went really well, I’m now doing a better job allocating the time
to what’s important to me.</p>
<h2><a inert href="#yearly-themes-2024" aria-hidden="true" class="anchor" id="yearly-themes-2024"></a>Yearly Themes 2024</h2>
<p>These are my Yearly Themes for next year:</p>
<dl>
<dt>Year of Fillilng The Gaps</dt>
<dd>
<p>I’ve made progress in many areas in my life over the last few years. I feel
like that created “gaps” in some less interesting areas. This year, I want to
invest in these areas to raise the overall base-line of myself.</p>
</dd>
<dt>Year of Fitness</dt>
<dd>
<p>After doing <em>Year of Health</em> at twice, I aim to focus on improving, instead
of maintaining, my fitness. One metric for this could be my
<a href="https://en.wikipedia.org/wiki/VO2_max">VO₂ max</a>, which my Apple Watch
conveniently automatically records for me<sup class="footnote-ref"><a href="#fn-3" id="fnref-3" data-footnote-ref>3</a></sup>.</p>
</dd>
</dl>
<h2><a inert href="#whats-yours" aria-hidden="true" class="anchor" id="whats-yours"></a>What’s yours?</h2>
<p>Do you do New Year’s resolutions or Yearly Themes, or something entirely
different? What do they look like? I’d love to <a href="/contact">hear from you</a>.</p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>Yes, you’re allowed to do that. <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p>Not really adventurous, but hey, I’m outside more! <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
<li id="fn-3">
<p>Thankfully, <a href="https://9to5mac.com/2023/12/26/apple-watch-ban-news/">it still has an oxygen sensor</a> <a href="#fnref-3" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>Emacs Config From Scratch, Part 1: Foundations</title><link>https://arne.me/blog/emacs-from-scratch-part-one-foundations</link><description><![CDATA[The first post of my Emacs from Scratch series is all about the initial setup and defaults.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/emacs-from-scratch-part-one-foundations</guid><pubDate>Fri, 22 Dec 2023 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Welcome to my new series <em>Emacs From Scratch</em>.
I’m far from an Emacs expert, so join me in my quest to figure out how to create
a useful Emacs setup from nothing<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup>.</p>
<p>In this part, we’ll install Emacs, set up sane defaults, packaging and do some
basic UI tweaks to build a solid foundation.</p>
<!-- more -->
<h2><a inert href="#table-of-contents" aria-hidden="true" class="anchor" id="table-of-contents"></a>Table of Contents</h2>
<ul>
<li><a href="#install-emacs">Install Emacs</a></li>
<li><a href="#remove-ui-elements">Remove UI elements</a></li>
<li><a href="#configure-the-package-manager">Configure the package manager</a></li>
<li><a href="#set-sane-defaults">Set sane defaults</a></li>
<li><a href="#become-evil">Become evil</a></li>
<li><a href="#set-font-and-theme">Set font and theme</a></li>
<li><a href="#add-a-nicer-modeline">Add a nicer modeline</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
<h2><a inert href="#install-emacs" aria-hidden="true" class="anchor" id="install-emacs"></a>Install Emacs</h2>
<p>On macOS, everyone recommends
<a href="https://github.com/d12frosted/homebrew-emacs-plus">Emacs Plus</a>.
For other systems, check out <a href="https://github.com/doomemacs/doomemacs/blob/master/docs/getting_started.org#emacs--dependencies">Doom’s Emacs &amp; dependencies</a> documentation.</p>
<p>We’re running this command:</p>
<pre class="syntax-highlighting"><code class="language-sh"><span class="source shell bash"><span class="meta function-call shell"><span class="variable function shell">brew</span></span><span class="meta function-call arguments shell"> reinstall emacs-plus <span class="punctuation separator continuation line shell">\
</span><span class="variable parameter option shell"><span class="punctuation definition parameter shell">  --</span>with-savchenkovaleriy-big-sur-icon</span> <span class="punctuation separator continuation line shell">\
</span><span class="variable parameter option shell"><span class="punctuation definition parameter shell">  --</span>with-native-comp</span></span>
</span></code></pre>
<p>And this is what it looks like when we start Emacs for the first time:</p>
<picture>
  <source srcset="/blog/emacs-from-scratch-part-one-foundations/vanilla-emacs.avif" type="image/avif" />
  <img src="/blog/emacs-from-scratch-part-one-foundations/vanilla-emacs.avif" alt="A default Emacs window showing outdated (euphemismus) butons and generally looking like it screams to be customized." />
</picture>
<h2><a inert href="#remove-ui-elements" aria-hidden="true" class="anchor" id="remove-ui-elements"></a>Remove UI elements</h2>
<p>We want to remove everything but the text. To do so, we first create a file in
<code>$HOME/.emacs.d/init.el</code>.</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>tool<span class="keyword operator arithmetic lisp">-</span>bar<span class="keyword operator arithmetic lisp">-</span>mode <span class="keyword operator arithmetic lisp">-</span><span class="constant numeric lisp">1</span><span class="punctuation definition group end lisp">)</span></span>             <span class="comment line semicolon lisp"><span class="punctuation definition comment lisp">;</span> Hide the outdated icons
</span><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>scroll<span class="keyword operator arithmetic lisp">-</span>bar<span class="keyword operator arithmetic lisp">-</span>mode <span class="keyword operator arithmetic lisp">-</span><span class="constant numeric lisp">1</span><span class="punctuation definition group end lisp">)</span></span>           <span class="comment line semicolon lisp"><span class="punctuation definition comment lisp">;</span> Hide the always-visible scrollbar
</span><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> inhibit<span class="keyword operator arithmetic lisp">-</span>splash<span class="keyword operator arithmetic lisp">-</span>screen <span class="constant language lisp">t</span><span class="punctuation definition group end lisp">)</span></span> <span class="comment line semicolon lisp"><span class="punctuation definition comment lisp">;</span> Remove the &quot;Welcome to GNU Emacs&quot; splash screen
</span><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> use<span class="keyword operator arithmetic lisp">-</span>file<span class="keyword operator arithmetic lisp">-</span>dialog <span class="constant language lisp">nil</span><span class="punctuation definition group end lisp">)</span></span>      <span class="comment line semicolon lisp"><span class="punctuation definition comment lisp">;</span> Ask for textual confirmation instead of GUI
</span></span></code></pre>
<p>If you start Emacs now, you’ll see the GUI elements for a few milliseconds.
Let’s fix that by adding these lines to <code>$HOME/.emacs.d/early-init.el</code><sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup>:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">push</span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>menu<span class="keyword operator arithmetic lisp">-</span>bar<span class="keyword operator arithmetic lisp">-</span>lines . <span class="constant numeric lisp">0</span><span class="punctuation definition group end lisp">)</span></span> default<span class="keyword operator arithmetic lisp">-</span>frame<span class="keyword operator arithmetic lisp">-</span>alist<span class="punctuation definition group end lisp">)</span></span>
<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">push</span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>tool<span class="keyword operator arithmetic lisp">-</span>bar<span class="keyword operator arithmetic lisp">-</span>lines . <span class="constant numeric lisp">0</span><span class="punctuation definition group end lisp">)</span></span> default<span class="keyword operator arithmetic lisp">-</span>frame<span class="keyword operator arithmetic lisp">-</span>alist<span class="punctuation definition group end lisp">)</span></span>
<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">push</span> &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>vertical<span class="keyword operator arithmetic lisp">-</span>scroll<span class="keyword operator arithmetic lisp">-</span>bars<span class="punctuation definition group end lisp">)</span></span> default<span class="keyword operator arithmetic lisp">-</span>frame<span class="keyword operator arithmetic lisp">-</span>alist<span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>This is better:</p>
<picture>
  <source srcset="/blog/emacs-from-scratch-part-one-foundations/no-gui-emacs.avif" type="image/avif" />
  <img src="/blog/emacs-from-scratch-part-one-foundations/no-gui-emacs.avif" alt="Emacs without GUI elements and the scratch buffer open." />
</picture>
<p>We’ll take care of the default scratch text and the <code>C-h C-a</code> hint down below.</p>
<h2><a inert href="#configure-the-package-manager" aria-hidden="true" class="anchor" id="configure-the-package-manager"></a>Configure the package manager</h2>
<p>We’ll be using <a href="https://github.com/radian-software/straight.el"><code>straight.el</code></a>
for package management.</p>
<p>This is the installation code from the <code>straight.el</code> README:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">defvar</span> bootstrap<span class="keyword operator arithmetic lisp">-</span>version<span class="punctuation definition group end lisp">)</span></span>
<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="keyword control lisp">let</span> <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>bootstrap<span class="keyword operator arithmetic lisp">-</span>file
    <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">expand</span><span class="keyword operator arithmetic lisp">-</span>file<span class="keyword operator arithmetic lisp">-</span>name
      <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>straight/repos/straight.el/bootstrap.el<span class="punctuation definition string end lisp">&quot;</span></span>
      <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="keyword operator logical lisp">or</span> <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>bound<span class="keyword operator arithmetic lisp">-</span><span class="keyword operator logical lisp">and</span><span class="keyword operator arithmetic lisp">-</span>true<span class="keyword operator arithmetic lisp">-</span>p straight<span class="keyword operator arithmetic lisp">-</span>base<span class="keyword operator arithmetic lisp">-</span>dir<span class="punctuation definition group end lisp">)</span></span>
        user<span class="keyword operator arithmetic lisp">-</span>emacs<span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">directory</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>bootstrap<span class="keyword operator arithmetic lisp">-</span>version <span class="constant numeric lisp">7</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="keyword control lisp">unless</span> <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>file<span class="keyword operator arithmetic lisp">-</span>exists<span class="keyword operator arithmetic lisp">-</span>p bootstrap<span class="keyword operator arithmetic lisp">-</span>file<span class="punctuation definition group end lisp">)</span></span>
    <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="keyword control lisp">with</span><span class="keyword operator arithmetic lisp">-</span>current<span class="keyword operator arithmetic lisp">-</span>buffer
      <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>url<span class="keyword operator arithmetic lisp">-</span>retrieve<span class="keyword operator arithmetic lisp">-</span>synchronously
       <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el<span class="punctuation definition string end lisp">&quot;</span></span>
       &#39;silent &#39;inhibit<span class="keyword operator arithmetic lisp">-</span>cookies<span class="punctuation definition group end lisp">)</span></span>
    <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>goto<span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">char</span> <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>point<span class="keyword operator arithmetic lisp">-</span><span class="keyword operator comparison lisp">max</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
    <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">eval</span><span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">print</span><span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">last</span><span class="keyword operator arithmetic lisp">-</span>sexp<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">load</span> bootstrap<span class="keyword operator arithmetic lisp">-</span>file <span class="constant language lisp">nil</span> &#39;nomessage<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>The docs also recommend adding this to our <code>early-init.el</code> to prevent
<code>package.el</code> from loading:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> package<span class="keyword operator arithmetic lisp">-</span>enable<span class="keyword operator arithmetic lisp">-</span>at<span class="keyword operator arithmetic lisp">-</span>startup <span class="constant language lisp">nil</span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>Next we’ll install <a href="https://github.com/jwiegley/use-package">use-package</a> for
tidier specification and better performance:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>straight<span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">use-package</span> &#39;<span class="support function lisp">use-package</span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>Then we’ll make <code>use-package</code> use <code>straight.el</code> by default and always <code>:defer t</code>
for lazy loading:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> straight<span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">use-package</span><span class="keyword operator arithmetic lisp">-</span>by<span class="keyword operator arithmetic lisp">-</span>default <span class="constant language lisp">t</span><span class="punctuation definition group end lisp">)</span></span>
<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> <span class="support function lisp">use-package</span><span class="keyword operator arithmetic lisp">-</span><span class="keyword control lisp">always</span><span class="keyword operator arithmetic lisp">-</span>defer <span class="constant language lisp">t</span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<h2><a inert href="#set-sane-defaults" aria-hidden="true" class="anchor" id="set-sane-defaults"></a>Set sane defaults</h2>
<p>It’s good practice to specify Emacs-specific settings in a <code>use-package</code> block,
even though this doesn’t change anything functionally.
In the following, I’ll repeat the <code>use-package emacs</code> function, but you can,
and probably should, move these all into a single <code>use-package</code> block.</p>
<p>Let’s start without the default scratch message and the text at the
bottom saying “For information about GNU Emacs and the GNU system, type
<code>C-h C-a</code>”:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> emacs
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> initial<span class="keyword operator arithmetic lisp">-</span>scratch<span class="keyword operator arithmetic lisp">-</span>message <span class="constant language lisp">nil</span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="meta function lisp"><span class="storage type function-type lisp">defun</span> <span class="entity name function lisp">display-startup-echo-area-message</span></span> <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="punctuation definition group end lisp">)</span></span>
    <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>message <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span><span class="punctuation definition string end lisp">&quot;</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>In confirmation dialogs, we want to be able to type <code>y</code> and <code>n</code> instead of
having to spell the whole words:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> emacs
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>defalias &#39;<span class="support function lisp">yes-or-no-p</span> &#39;<span class="support function lisp">y-or-n-p</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>Make everything use UTF-8:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> emacs
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">set</span><span class="keyword operator arithmetic lisp">-</span>charset<span class="keyword operator arithmetic lisp">-</span>priority &#39;unicode<span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> locale<span class="keyword operator arithmetic lisp">-</span>coding<span class="keyword operator arithmetic lisp">-</span>system &#39;utf<span class="keyword operator arithmetic lisp">-</span><span class="constant numeric lisp">8</span>
        coding<span class="keyword operator arithmetic lisp">-</span>system<span class="keyword operator arithmetic lisp">-</span><span class="keyword control lisp">for</span><span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">read</span> &#39;utf<span class="keyword operator arithmetic lisp">-</span><span class="constant numeric lisp">8</span>
        coding<span class="keyword operator arithmetic lisp">-</span>system<span class="keyword operator arithmetic lisp">-</span><span class="keyword control lisp">for</span><span class="keyword operator arithmetic lisp">-</span><span class="support function lisp">write</span> &#39;utf<span class="keyword operator arithmetic lisp">-</span><span class="constant numeric lisp">8</span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">set</span><span class="keyword operator arithmetic lisp">-</span>terminal<span class="keyword operator arithmetic lisp">-</span>coding<span class="keyword operator arithmetic lisp">-</span>system &#39;utf<span class="keyword operator arithmetic lisp">-</span><span class="constant numeric lisp">8</span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">set</span><span class="keyword operator arithmetic lisp">-</span>keyboard<span class="keyword operator arithmetic lisp">-</span>coding<span class="keyword operator arithmetic lisp">-</span>system &#39;utf<span class="keyword operator arithmetic lisp">-</span><span class="constant numeric lisp">8</span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">set</span><span class="keyword operator arithmetic lisp">-</span>selection<span class="keyword operator arithmetic lisp">-</span>coding<span class="keyword operator arithmetic lisp">-</span>system &#39;utf<span class="keyword operator arithmetic lisp">-</span><span class="constant numeric lisp">8</span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>prefer<span class="keyword operator arithmetic lisp">-</span>coding<span class="keyword operator arithmetic lisp">-</span>system &#39;utf<span class="keyword operator arithmetic lisp">-</span><span class="constant numeric lisp">8</span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> default<span class="keyword operator arithmetic lisp">-</span>process<span class="keyword operator arithmetic lisp">-</span>coding<span class="keyword operator arithmetic lisp">-</span>system &#39;<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>utf<span class="keyword operator arithmetic lisp">-</span><span class="constant numeric lisp">8</span><span class="keyword operator arithmetic lisp">-</span>unix . utf<span class="keyword operator arithmetic lisp">-</span><span class="constant numeric lisp">8</span><span class="keyword operator arithmetic lisp">-</span>unix<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>Use spaces, but configure tab-width for modes that use tabs (looking at you,
Go):</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> emacs
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span><span class="keyword operator arithmetic lisp">-</span>default indent<span class="keyword operator arithmetic lisp">-</span>tabs<span class="keyword operator arithmetic lisp">-</span>mode <span class="constant language lisp">nil</span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span><span class="keyword operator arithmetic lisp">-</span>default tab<span class="keyword operator arithmetic lisp">-</span>width <span class="constant numeric lisp">2</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>Map the correct keybindings for macOS:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> emacs
  :init
	<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="keyword control lisp">when</span> <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="keyword operator comparison lisp">eq</span> system<span class="keyword operator arithmetic lisp">-</span>type &#39;darwin<span class="punctuation definition group end lisp">)</span></span>
		<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> mac<span class="keyword operator arithmetic lisp">-</span>command<span class="keyword operator arithmetic lisp">-</span>modifier &#39;super<span class="punctuation definition group end lisp">)</span></span>
		<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> mac<span class="keyword operator arithmetic lisp">-</span>option<span class="keyword operator arithmetic lisp">-</span>modifier &#39;meta<span class="punctuation definition group end lisp">)</span></span>
		<span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> mac<span class="keyword operator arithmetic lisp">-</span>control<span class="keyword operator arithmetic lisp">-</span>modifier &#39;control<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<h2><a inert href="#become-evil" aria-hidden="true" class="anchor" id="become-evil"></a>Become evil</h2>
<p>I’m used to Vim keybindings and want to keep them, so we’ll use
<a href="https://github.com/emacs-evil/evil">evil</a>:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> evil
  :demand <span class="comment line semicolon lisp"><span class="punctuation definition comment lisp">;</span> No lazy loading
</span>  :config
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>evil<span class="keyword operator arithmetic lisp">-</span>mode <span class="constant numeric lisp">1</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<h2><a inert href="#set-font-and-theme" aria-hidden="true" class="anchor" id="set-font-and-theme"></a>Set font and theme</h2>
<p>We’ll be using the <a href="https://fsd.it/shop/fonts/pragmatapro/">PragmataPro</a>
typeface:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> emacs
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">set</span><span class="keyword operator arithmetic lisp">-</span>face<span class="keyword operator arithmetic lisp">-</span>attribute &#39;default <span class="constant language lisp">nil</span>
    :font <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>PragmataPro Mono Liga<span class="punctuation definition string end lisp">&quot;</span></span>
    :height <span class="constant numeric lisp">160</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>For themes, I can recommend the
<a href="https://github.com/doomemacs/themes">Doom Themes</a>, we’ll be using
<code>doom-challenger-deep</code><sup class="footnote-ref"><a href="#fn-3" id="fnref-3" data-footnote-ref>3</a></sup>:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> doom<span class="keyword operator arithmetic lisp">-</span>themes
  :demand
  :config
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">load</span><span class="keyword operator arithmetic lisp">-</span>theme &#39;doom<span class="keyword operator arithmetic lisp">-</span>challenger<span class="keyword operator arithmetic lisp">-</span>deep <span class="constant language lisp">t</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>Finally, we want relative line numbers in prog mode:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> emacs
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="meta function lisp"><span class="storage type function-type lisp">defun</span> <span class="entity name function lisp">ab</span></span><span class="keyword operator arithmetic lisp">/</span>enable<span class="keyword operator arithmetic lisp">-</span>line<span class="keyword operator arithmetic lisp">-</span>numbers <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="punctuation definition group end lisp">)</span></span>
    <span class="string quoted double lisp"><span class="punctuation definition string begin lisp">&quot;</span>Enable relative line numbers<span class="punctuation definition string end lisp">&quot;</span></span>
    <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>interactive<span class="punctuation definition group end lisp">)</span></span>
    <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>display<span class="keyword operator arithmetic lisp">-</span>line<span class="keyword operator arithmetic lisp">-</span>numbers<span class="keyword operator arithmetic lisp">-</span>mode<span class="punctuation definition group end lisp">)</span></span>
    <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">setq</span> display<span class="keyword operator arithmetic lisp">-</span>line<span class="keyword operator arithmetic lisp">-</span>numbers &#39;relative<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>add<span class="keyword operator arithmetic lisp">-</span>hook &#39;<span class="keyword control lisp">prog</span><span class="keyword operator arithmetic lisp">-</span>mode<span class="keyword operator arithmetic lisp">-</span>hook <span class="constant character lisp"><span class="punctuation definition constant lisp">#</span>&#39;ab/enable-line-numbers</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<h2><a inert href="#add-a-nicer-modeline" aria-hidden="true" class="anchor" id="add-a-nicer-modeline"></a>Add a nicer modeline</h2>
<p>We’ll install <a href="https://github.com/seagle0128/doom-modeline">doom-modeline</a>:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> doom<span class="keyword operator arithmetic lisp">-</span>modeline
  :ensure <span class="constant language lisp">t</span>
  :init <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>doom<span class="keyword operator arithmetic lisp">-</span>modeline<span class="keyword operator arithmetic lisp">-</span>mode <span class="constant numeric lisp">1</span><span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>For pretty icons, we need to install
<a href="https://github.com/rainstormstudio/nerd-icons.el">nerd-icons</a> as well:</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> nerd<span class="keyword operator arithmetic lisp">-</span>icons<span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<p>After restarting Emacs, run <code>M-x nerd-icons-install-fonts</code> (<code>Option-x</code> on
macOS) to install the icon font.</p>
<p>And we’ll install <a href="https://github.com/TeMPOraL/nyan-mode">Nyan Mode</a>, a minor
mode which shows a Nyan Cat (which is 12 years old at the point of writing this)
in your modeline to indicate position in the open buffer.</p>
<pre class="syntax-highlighting"><code class="language-lisp"><span class="source lisp"><span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span><span class="support function lisp">use-package</span> nyan<span class="keyword operator arithmetic lisp">-</span>mode
  :init
  <span class="meta group lisp"><span class="punctuation definition group begin lisp">(</span>nyan<span class="keyword operator arithmetic lisp">-</span>mode<span class="punctuation definition group end lisp">)</span></span><span class="punctuation definition group end lisp">)</span></span>
</span></code></pre>
<h2><a inert href="#conclusion" aria-hidden="true" class="anchor" id="conclusion"></a>Conclusion</h2>
<p>This is what looks like:</p>
<picture>
  <source srcset="/blog/emacs-from-scratch-part-one-foundations/final.avif" type="image/avif" />
  <img src="/blog/emacs-from-scratch-part-one-foundations/final.avif" alt="Emacs with relative line numbers, a nice fond and color scheme." />
</picture>
<p>We have sane defaults, we can open a file with <code>:e</code>, navigate around and we
have a nice color scheme<sup class="footnote-ref"><a href="#fn-4" id="fnref-4" data-footnote-ref>4</a></sup> and modeline.
Here’s the the final <a href="/blog/emacs-from-scratch-part-one-foundations/init.el"><code>init.el</code></a>
and <a href="/blog/emacs-from-scratch-part-one-foundations/early-init.el"><code>early-init.el</code></a></p>
<p>In part 2, we’ll add a project manager, our own keybindings, the best Git TUI,
a handy shortcut to restart Emacs and a ton of small tweaks.</p>
<p>Subscribe to the <a href="/blog/atom.xml">RSS Feed</a> so you don’t miss the following
parts, and <a href="mailto:hey@arne.me">let me know</a> if I missed anything foundational!</p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>If you don’t want to have to configure everything and just want an editor that,
works, <del>use VS Code</del> check out <a href="https://github.com/doomemacs/doomemacs">Doom Emacs</a>. <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p><code>early-init.el</code> is loaded during startup, see <a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Early-Init-File.html">The Early Init File</a>.
Unless explicitly noted, configuration should go in <code>init.el</code>. <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
<li id="fn-3">
<p>Derived from the <a href="https://challenger-deep-theme.github.io">incredible original</a> <a href="#fnref-3" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3">↩</a></p>
</li>
<li id="fn-4">
<p>Yes, I know that’s what they’re called in Vim. <a href="#fnref-4" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="4" aria-label="Back to reference 4">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>30 Bits of Advice for 30 Years</title><link>https://arne.me/blog/30-bits-of-advice-for-30-years</link><description><![CDATA[This article contains some advice I've collected in my life so far.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/30-bits-of-advice-for-30-years</guid><pubDate>Fri, 17 Nov 2023 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>I’m turning 30 this year.</p>
<p>Inspired by Kevin Kelly’s incredible
<a href="https://kk.org/thetechnium/68-bits-of-unsolicited-advice/">68 Bits of Unsolicited Advice</a><sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup>,
I collected bits of advice I’ve picked up over the years—mostly by people wiser
than me.</p>
<!-- more -->
<p>Here they are, in no particular order:</p>
<ul>
<li>Communication is your most important skill, get good at it.</li>
<li>Be kind first, be right later.<sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup></li>
<li>Don’t make your problems other people’s problems.</li>
<li>Read more books.</li>
<li>Help others succeed, and they’ll help you succeed in the long term.</li>
<li>It’s only awkward if you make it awkward.</li>
<li>When giving presents, the perceived value counts, not the actual value (this
goes in both directions).</li>
<li>Your vacation starts when you leave your house and ends when you’re back in your house.</li>
<li>People care more about stories than facts.</li>
<li>Find someone outside your bubble that you can talk to and make sure you’re
both able to understand where the other comes from.</li>
<li>Think of yourself as the person you want to be, not the person you are.<sup class="footnote-ref"><a href="#fn-3" id="fnref-3" data-footnote-ref>3</a></sup></li>
<li>Trust is the most important ingredient when building a team.</li>
<li>Always choose the long-term option to leverage the compound effect.<sup class="footnote-ref"><a href="#fn-3" id="fnref-3-2" data-footnote-ref>3</a></sup></li>
<li>Create systems instead of chasing goals.<sup class="footnote-ref"><a href="#fn-3" id="fnref-3-3" data-footnote-ref>3</a></sup></li>
<li>Don’t let your expenses rise as fast as your income.</li>
<li>Make time for your brain to work without distractions to allow the mind to wander.<sup class="footnote-ref"><a href="#fn-4" id="fnref-4" data-footnote-ref>4</a></sup></li>
<li>Less is more.<sup class="footnote-ref"><a href="#fn-5" id="fnref-5" data-footnote-ref>5</a></sup></li>
<li>Always expand your knowledge, skills and network to be ready when opportunities arise.<sup class="footnote-ref"><a href="#fn-6" id="fnref-6" data-footnote-ref>6</a></sup></li>
<li>Curious people are smart. Be curious.<sup class="footnote-ref"><a href="#fn-7" id="fnref-7" data-footnote-ref>7</a></sup></li>
<li>Don’t be afraid to look stupid.<sup class="footnote-ref"><a href="#fn-8" id="fnref-8" data-footnote-ref>8</a></sup></li>
<li>Keep a can-do mindset.<sup class="footnote-ref"><a href="#fn-9" id="fnref-9" data-footnote-ref>9</a></sup></li>
<li>Don’t get discouraged because of existing work. Instead, try to find ways to
put your own spin on it.</li>
<li>It’s never as easy as it looks.<sup class="footnote-ref"><a href="#fn-10" id="fnref-10" data-footnote-ref>10</a></sup></li>
<li>Perfection is the enemy of good.<sup class="footnote-ref"><a href="#fn-11" id="fnref-11" data-footnote-ref>11</a></sup></li>
<li>Good design requires as little cognitive overhead as possible to use.<sup class="footnote-ref"><a href="#fn-12" id="fnref-12" data-footnote-ref>12</a></sup></li>
<li>When creating, take all the shortcuts you can to get a prototype which you
can iterate on.</li>
<li>Under-promise and over-deliver.</li>
<li>Always do extra.<sup class="footnote-ref"><a href="#fn-13" id="fnref-13" data-footnote-ref>13</a></sup></li>
<li>Keep a brag document.<sup class="footnote-ref"><a href="#fn-14" id="fnref-14" data-footnote-ref>14</a></sup></li>
<li>Surround yourself with people who are passionate about their work.</li>
</ul>
<p>Thoughts? <a href="/contact">I’d love to hear from you!</a></p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>The Internet Archive has the <a href="https://web.archive.org/web/20201101101332/https://kk.org/thetechnium/68-bits-of-unsolicited-advice/">full version</a>. <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p>See <a href="https://jamesclear.com/why-facts-dont-change-minds">Why Facts Don’t Change Our Minds</a>. <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
<li id="fn-3">
<p>Atomic Habits (Clear, 2018) <a href="#fnref-3" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3">↩</a> <a href="#fnref-3-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="3-2" aria-label="Back to reference 3-2">↩<sup class="footnote-ref">2</sup></a> <a href="#fnref-3-3" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="3-3" aria-label="Back to reference 3-3">↩<sup class="footnote-ref">3</sup></a></p>
</li>
<li id="fn-4">
<p>See <a href="https://www.nationalgeographic.co.uk/history-and-civilisation/2022/08/the-science-of-why-you-have-great-ideas-in-the-shower">The science of why you have great ideas in the shower</a>. <a href="#fnref-4" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="4" aria-label="Back to reference 4">↩</a></p>
</li>
<li id="fn-5">
<p>See <a href="https://www.nature.com/articles/d41586-021-00592-0">Adding is favoured over subtracting in problem solving</a> <a href="#fnref-5" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="5" aria-label="Back to reference 5">↩</a></p>
</li>
<li id="fn-6">
<p>See <a href="https://www.swyx.io/create-luck">How to Create Luck</a> <a href="#fnref-6" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="6" aria-label="Back to reference 6">↩</a></p>
</li>
<li id="fn-7">
<p>See <a href="https://durmonski.com/life-advice/curiosity-is-better-than-being-smart/">Why Curiosity Is Better Than Being Smart?</a> <a href="#fnref-7" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="7" aria-label="Back to reference 7">↩</a></p>
</li>
<li id="fn-8">
<p>See <a href="http://danluu.com/look-stupid/">Willingness to look stupid</a> <a href="#fnref-8" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="8" aria-label="Back to reference 8">↩</a></p>
</li>
<li id="fn-9">
<p>See <a href="https://anthonyvicino.com/power-word/">The most powerful word in the world</a> <a href="#fnref-9" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="9" aria-label="Back to reference 9">↩</a></p>
</li>
<li id="fn-10">
<p>See <a href="https://web.archive.org/web/20221209050432/https://collabfund.com/blog/hard/">Harder Than It Looks, Not As Fun as It Seems</a> <a href="#fnref-10" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="10" aria-label="Back to reference 10">↩</a></p>
</li>
<li id="fn-11">
<p>Philosophical Dictionary (Voltaire, 1764) <a href="#fnref-11" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="11" aria-label="Back to reference 11">↩</a></p>
</li>
<li id="fn-12">
<p>The Design of Everyday Things (Norman, 2013) <a href="#fnref-12" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="12" aria-label="Back to reference 12">↩</a></p>
</li>
<li id="fn-13">
<p>See <a href="https://www.bennorthrop.com/Essays/2021/always-do-extra.php">Always do Extra</a> <a href="#fnref-13" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="13" aria-label="Back to reference 13">↩</a></p>
</li>
<li id="fn-14">
<p>See <a href="https://jvns.ca/blog/brag-documents/">Get your work recognized: write a brag document</a> <a href="#fnref-14" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="14" aria-label="Back to reference 14">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>We Need to Bring Back Webrings</title><link>https://arne.me/blog/we-need-to-bring-back-webrings</link><description><![CDATA[I built a webring for personal websites.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/we-need-to-bring-back-webrings</guid><pubDate>Tue, 14 Nov 2023 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>The year is 1996.
You feed your Tamagotchi, get a Squeezit and turn on the home computer.
You’ve told your family they can’t do phone calls for the next hour.
The dial-up modem makes beeping sounds<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup>.
You’re online.</p>
<p>Yesterday you found this fly website about amateur radio, and you want to
explore more—but how can you find related websites?
Yahoo is slow and not really showing you what you’re looking for.
Then you notice that this website is part of the “Amateur Radio Webring”.
You click the arrow to the right and dive into another website about amateur
radio.</p>
<!-- more -->
<p>You wake up.</p>
<p>The year is 2023.
You can no longer get a Squeezit anywhere, you live under surveillance
capitalism and the
<a href="https://en.wikipedia.org/wiki/Enshittification">enshittification</a> is ruining
the web.
You found this goofy personal website, and you’d like to browse similar ones.
What do you do?
Find the person on socials, look at who they’re following and check if they have
a website?</p>
<p>It’s time to bring webrings back.</p>
<p>Introducing the <a href="https://firechicken.club">Fire Chicken Webring</a>; an invite-only
webring for personal websites.
You can explore this webring by going to the footer of this website and
clicking on either arrow to navigate to another personal website.</p>
<p>If we’re friends, or I follow you anywhere, <a href="/contact">@ me</a> and I’ll add you.
If not, here’s some other modern webrings<sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup>:
<a href="https://hotlinewebring.club">Hotline Webring</a>,
<a href="https://sidebar.io/webring">Sidebar.io Webring</a>,
<a href="https://a11y-webring.club">a11y-webring.club</a>,
<a href="https://cs.sjoy.lol/#webring">CS.Sjoy.lol</a>,
<a href="https://fediring.net">Fediring.net</a>,
and <a href="https://applianceri.ng">Appliance Ring</a>.</p>
<p>Own a niche website? Why not start your own webring?</p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>For full immersion, see <a href="https://youtu.be/gsNaR6FRuO0">this video</a>. <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p>Know of more? Please <a href="/contact">reach out to me</a>. <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>Archive Your Old Projects</title><link>https://arne.me/blog/archive-your-old-projects</link><description><![CDATA[In this post I describe how I wish I had archived all my old projects and my approach going forward.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/archive-your-old-projects</guid><pubDate>Sun, 12 Nov 2023 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Yesterday, while looking through a folder called <em>old things lol glhf</em>, I fell
into a rabbit hole of old abandoned projects—mostly websites and graphic
design, but I also found one or two Flash<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup> projects and compiled <code>.exe</code>
files<sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup>.</p>
<p>And, while it was really fun remembering projects I’ve long forgotten, there
was no structure, and it was often difficult to figure out what a project did
and what it looked like—some even missed crucial data.
This made me think about how I want to archive my projects going forward.</p>
<!-- more -->
<p>Here is my new strategy:</p>
<h2><a inert href="#leave-it-online" aria-hidden="true" class="anchor" id="leave-it-online"></a>Leave it online</h2>
<p>If the project is on the web, doesn’t require maintenance and doesn’t cost you
money, leave it online.</p>
<p>Occasionally, you’ll need to move to a different domain, for example when re-doing
a website.
When talking to <a href="https://flbn.sh">Ollie</a> about this, he told me that some people
leave their old websites online at <code>&lt;year&gt;.&lt;domain&gt;</code> and I love that idea<sup class="footnote-ref"><a href="#fn-3" id="fnref-3" data-footnote-ref>3</a></sup>.</p>
<p>You can look up old content and redirect links, so your
<a href="https://www.w3.org/Provider/Style/URI">URIs stay cool</a>.
And in ten years it’ll probably still be online.</p>
<h2><a inert href="#archive-it" aria-hidden="true" class="anchor" id="archive-it"></a>Archive it</h2>
<p>If you can’t leave it online, save it to your file system.
You don’t have to go all <a href="https://johnnydecimal.com">Johnny.Decimal</a>, but at
least create a dedicated folder and subfolders for every year.</p>
<p>But instead of just copying your project to the archive folder and be done with
it, consider these points to make life easier for your future self:</p>
<dl>
<dt>Make screenshots</dt>
<dd>
<p>Having a screenshot allows you to relive your memories more easily without
going through the hassle of setting up a project.
If you want to go the extra mile, do a screen recording showcasing your
project—this has the bonus effect of hearing your voice from years ago, and it
has more context.</p>
</dd>
<dt>Add a README</dt>
<dd>
<p>Explain what the project did, when it was created and abandoned, who
contributed and how to get it running again.</p>
</dd>
<dt>Back up the database</dt>
<dd>
<p>Some of my projects are missing a database dump, so all the actual content is
gone. Run <code>sqldump</code> or whatever export functionality your database supports
and add it next to your files.</p>
</dd>
<dt>Keep generated assets</dt>
<dd>
<p>When using static site generators, add the folder containing the built HTML,
CSS &amp; JS to the archive.
That way, all you have to do is run a static file server to be able to browse
the complete website.</p>
</dd>
</dl>
<h2><a inert href="#save-it-to-the-internet-archive" aria-hidden="true" class="anchor" id="save-it-to-the-internet-archive"></a>Save it to the Internet Archive</h2>
<p>If you don’t own your platform (maybe you’re publishing to Substack or Notion),
you can at least save your website to the
<a href="https://web.archive.org">Wayback Machine</a>.
I would also advise saving your content somewhere you control.</p>
<h2><a inert href="#show-me-yours" aria-hidden="true" class="anchor" id="show-me-yours"></a>Show me yours</h2>
<p>So that’s it, that’s my new project archival strategy.</p>
<p>How are you archiving your projects?
Am I missing anything?
<a href="/contact">Let me know.</a></p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>If you don’t know what this is, <a href="https://en.wikipedia.org/wiki/Adobe_Flash">Wikipedia’s got your back</a> <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p>Not sure what to do with these besides deleting. <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
<li id="fn-3">
<p>You can find the previous version of this very website at <a href="https://2023.arne.me">2023.arne.me</a>. <a href="#fnref-3" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>My App Defaults</title><link>https://arne.me/blog/app-defaults</link><description><![CDATA[The apps I use]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/app-defaults</guid><pubDate>Sun, 05 Nov 2023 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>In episode 097 of the <a href="https://hemisphericviews.com/">Hemispheric Views</a> podcast, the hosts discuss their app defaults, which has inspired some people to share their own<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup>.</p>
<!-- more -->
<p>These are mine:</p>
<dl>
<dt>Mail Client</dt>
<dd>
<p><a href="https://hey.com">HEY</a></p>
</dd>
<dt>Mail Server</dt>
<dd>
<p>HEY &amp; <a href="https://fastmail.fm">Fastmail</a><sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup></p>
</dd>
<dt>Notes</dt>
<dd>
<p><a href="https://tot.rocks">Tot</a> &amp; <a href="https://obsidian.md">Obsidian</a></p>
</dd>
<dt>To-Do</dt>
<dd>
<p><a href="https://culturedcode.com/things">Things</a></p>
</dd>
<dt>iPhone Photo Shooting</dt>
<dd>
<p>Camera.app</p>
</dd>
<dt>Photo Management</dt>
<dd>
<p>Photos.app</p>
</dd>
<dt>Calendar</dt>
<dd>
<p>Calendar.app synced with iCloud</p>
</dd>
<dt>Cloud File Storage</dt>
<dd>
<p>iCloud Drive</p>
</dd>
<dt>RSS</dt>
<dd>
<p><a href="http://reederapp.com">Reeder</a> synced with <a href="https://feedbin.com">Feedbin</a></p>
</dd>
<dt>Contacts</dt>
<dd>
<p>Contacts.app synced with iCloud</p>
</dd>
<dt>Browser</dt>
<dd>
<p>Safari</p>
</dd>
<dt>Chat</dt>
<dd>
<p>iMessage, WhatsApp, Signal</p>
</dd>
<dt>Bookmarks</dt>
<dd>
<p>Safari</p>
</dd>
<dt>Read It Later</dt>
<dd>
<p>n/a</p>
</dd>
<dt>Word Processing</dt>
<dd>
<p>Pages.app</p>
</dd>
<dt>Spreadsheets</dt>
<dd>
<p>Numbers.app</p>
</dd>
<dt>Presentations</dt>
<dd>
<p>Keynote.app</p>
</dd>
<dt>Shopping Lists</dt>
<dd>
<p><a href="https://www.smartgrocery.app">Grocery</a></p>
</dd>
<dt>Meal Planning</dt>
<dd>
<p>n/a</p>
</dd>
<dt>Budgeting and Personal Finance</dt>
<dd>
<p><a href="https://www.ynab.com">YNAB</a></p>
</dd>
<dt>News</dt>
<dd>
<p><a href="https://lagedernation.org">Lage der Nation</a><sup class="footnote-ref"><a href="#fn-3" id="fnref-3" data-footnote-ref>3</a></sup>, New York Times</p>
</dd>
<dt>Music</dt>
<dd>
<p>Apple Music</p>
</dd>
<dt>Podcasts</dt>
<dd>
<p><a href="https://overcast.fm">Overcast</a></p>
</dd>
<dt>Password Management</dt>
<dd>
<p><a href="https://1password.com">1Password</a></p>
</dd>
</dl>
<p>Now there are a few categories I want to add myself:</p>
<dl>
<dt>Weather</dt>
<dd>
<p><a href="https://www.meetcarrot.com/weather/">CARROT</a></p>
</dd>
<dt>TV Show Tracker</dt>
<dd>
<p><a href="https://couchtim.es">CouchTimes</a></p>
</dd>
<dt>Terminal Emulator</dt>
<dd>
<p><a href="https://iterm.app">iTerm</a></p>
</dd>
<dt>Code Editor</dt>
<dd>
<p><a href="https://neovim.io">NeoVim</a></p>
</dd>
<dt>Photo Editor</dt>
<dd>
<p><a href="https://www.pixelmator.com/photomator/">Photomator</a></p>
</dd>
</dl>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p><a href="https://rknight.me/">Robb</a> made <a href="https://defaults.rknight.me">a list</a>. <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p>FastMail is handling the domains and forwarding to HEY. Don’t ask. <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
<li id="fn-3">
<p>An excellent German news podcast. <a href="#fnref-3" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>Why You Should Write Your Own Static Site Generator</title><link>https://arne.me/blog/write-your-own-ssg</link><description><![CDATA[I rewrote my personal website using basic libraries and the flexibility is incredible.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/write-your-own-ssg</guid><pubDate>Fri, 03 Nov 2023 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>I’ve used <a href="https://jekyllrb.com">a</a> <a href="https://gohugo.io">lot</a>
<a href="https://www.11ty.dev">of</a> <a href="https://www.getzola.org">static</a>
<a href="https://nextjs.org">site</a> <a href="https://astro.build">generators</a> in the past, and
they all have their own features and quirks, but most importantly, you have to
architect your website to match what the framework expects.</p>
<p>Since yesterday, this website has been powered by my own SSG<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup>.
It’s not meant to be reusable, it’s just normal code—parsing and generating
files for this specific website.</p>
<p>And oh boy do I love it.</p>
<!-- more -->
<h2><a inert href="#why" aria-hidden="true" class="anchor" id="why"></a>Why??</h2>
<p>When Vercel released <a href="https://nextjs.org/blog/next-14">Next.js 14</a> recently,
some friends I’ve talked to were still on Next.js 12 and really felt the
pressure to upgrade to not fall behind even more.
This made me think about the longevity and robustness of my website, and so I
decided I don’t want to depend on other people’s decisions and run after version
upgrades I don’t care about.</p>
<p>And even if your content is Markdown and media, almost everything around it
needs to be updated when switching frameworks—sometimes even when upgrading.
When I used Astro before, I wanted to statically generate OG images and, after
some research, managed to build it and even wrote
<a href="/blog/static-og-images-in-astro">an article</a> explaining how.
You lose all of this custom logic.</p>
<p>Plus, you get to choose your own stack.
Want to write your content in AsciiDoc? No-one can stop you!</p>
<p>I know what you’re thinking, and you’re right, it’s <em>way</em> more work than using
something that already exists.
But it’s also so much more fun.
You can do anything with this, and you don’t need to read documentation or try
to understand other people’s architectural decisions—just start writing code!</p>
<h2><a inert href="#okay-tell-me-how-you-did-it" aria-hidden="true" class="anchor" id="okay-tell-me-how-you-did-it"></a>Okay, tell me how you did it</h2>
<p>After contemplating building something dynamic<sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup> for search without
JavaScript, I decided to stay with a static site.
It’s faster, and you don’t have to worry about security or stability.
And of course, I chose the best programming language on the planet, Rust (my
beloved).
Wait, come back, this is not a Rust post!</p>
<p>A static site generator mostly needs to do five things:</p>
<ol>
<li>Convert markdown to HTML</li>
<li>Render HTML templates</li>
<li>Compile CSS</li>
<li>Generate RSS feeds</li>
<li>Generate a sitemap</li>
</ol>
<p>And of these, you might not even need the last three.
Surely, your favorite programming language has a Markdown parser and a
templating engine.</p>
<p>For Rust, I chose these crates<sup class="footnote-ref"><a href="#fn-3" id="fnref-3" data-footnote-ref>3</a></sup>:</p>
<ul>
<li><a href="https://crates.io/crates/comrak">comrak</a> and <a href="https://crates.io/crates/gray_matter">gray_matter</a> for parsing Markdown and the frontmatter</li>
<li><a href="https://maud.lambda.xyz">maud</a> for compile-time templates</li>
<li><a href="https://crates.io/crates/grass">grass</a> for SCSS compilation</li>
<li><a href="https://crates.io/crates/rss">rss</a> for (you guessed it) generating RSS feeds</li>
<li><a href="https://crates.io/crates/quick-xml">quick-xml</a> for generating the sitemap</li>
</ul>
<p>Once I got the tools I needed, I just started writing software.
I built something to parse my content, something to render all the different
templates I needed, something to generate the RSS, something to generate a
sitemap and…that’s it, really!</p>
<p>And when it’s time to add OG images to this website, I can choose the best
libraries and just build it.</p>
<h2><a inert href="#what-now" aria-hidden="true" class="anchor" id="what-now"></a>What now?</h2>
<p>I hope this article left you either validated that rolling your own is not worth
it, and you want to keep using a framework, or interested to see what’s on the
other side.</p>
<p>If you’re in the latter camp, you can check out the
<a href="https://github.com/bahlo/arne.me">source of this website</a> to get some
inspiration.
But most importantly: Choose a tech stack that excites you and have fun!</p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>Static Site Generators, in case you didn’t make that connection yet. <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p>Heresy! <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
<li id="fn-3">
<p>If I had chosen Go, I probably would’ve looked at <a href="https://github.com/yuin/goldmark">goldmark</a>, <a href="https://github.com/abhinav/goldmark-frontmatter">goldmark-frontmatter</a>, <a href="https://pkg.go.dev/html/template">html/template</a> and <a href="https://pkg.go.dev/encoding/xml">encoding/xml</a>. <a href="#fnref-3" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3">↩</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title>Static OG (Open Graph) Images in Astro</title><link>https://arne.me/blog/static-og-images-in-astro</link><description><![CDATA[A guide to set up build-time Open Graph images in Astro using Satori, sharp and Astro endpoints.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/static-og-images-in-astro</guid><pubDate>Fri, 07 Apr 2023 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Have you every shared a link to a friend and wondered where the image preview
came from?
That’s the <a href="https://ogp.me/">Open Graph Protocol</a>, a set of HTML <code>&lt;meta&gt;</code>
extensions that can enrich a link, originally invented at Facebook.
This blog post describes how you can generate these images on build time in
the <a href="https://astro.build">Astro web framework</a>.</p>
<!-- more -->
<p>Most of the guides out there (including Vercel’s official <a href="https://vercel.com/docs/concepts/functions/edge-functions/og-image-generation">OG Image Generation</a>)
use a function to generate the OG image dynamically.
While there’s nothing wrong with that, I really wanted mine to be statically
generated on build-time; it’s faster, cheaper and cooler.</p>
<h2><a inert href="#build-your-image" aria-hidden="true" class="anchor" id="build-your-image"></a>Build your image</h2>
<p>The first thing to do is figure out what your OG image should look like.
We’re going to use Vercel’s <a href="https://github.com/vercel/satori">Satori</a>, a
JavaScript library which can render a HTML tree as SVG.
Vercel also has a great <a href="https://og-playground.vercel.app/">OG Image Playground</a>,
where you can play around and quickly get results.</p>
<picture>
  <source srcset="/blog/static-og-images-in-astro/og-image-playground.avif" type="image/avif" />
  <img src="/blog/static-og-images-in-astro/og-image-playground.png" alt="A screenshot of the OG Image Playground showing a code editor on the left and a preview and some settings on the right." />
</picture>
<p>Set the size to 1200×630px as that’s what Facebook
recommends in <a href="https://developers.facebook.com/docs/sharing/webmasters/images/">their guidelines</a>.
Use Flexbox liberally and enable debug mode when figuring out the layout.
<a href="https://css-tricks.com/snippets/css/a-guide-to-flexbox/">A Complete Guide to Flexbox</a>
is a great resource to have at hand.</p>
<h2><a inert href="#create-an-astro-endpoint" aria-hidden="true" class="anchor" id="create-an-astro-endpoint"></a>Create an Astro endpoint</h2>
<p>Got a nice image built in the playground? Then let’s get started.
Install Satori to generate the SVG and <a href="https://github.com/lovell/sharp">sharp</a>
to then convert it to PNG:</p>
<pre class="syntax-highlighting"><code class="language-sh"><span class="source shell bash"><span class="meta function-call shell"><span class="variable function shell">$</span></span><span class="meta function-call arguments shell"> npm install satori sharp</span>
</span></code></pre>
<p>Then create an endpoint, e.g. <code>pages/og-image.png.ts</code> with the following code:</p>
<pre class="syntax-highlighting"><code class="language-typescript"><span class="text plain">import fs from &quot;fs/promises&quot;;
import satori from &quot;satori&quot;;
import sharp from &quot;sharp&quot;;
import type { APIRoute } from &quot;astro&quot;;

export const get: APIRoute = async function get({ params, request }) {
  const robotoData = await fs.readFile(
    &quot;./public/fonts/roboto/Roboto-Regular.ttf&quot;
  );

  const svg = await satori(
    {
      type: &quot;h1&quot;,
      props: {
        children: &quot;Hello world&quot;,
        style: {
          fontWeight: &quot;bold&quot;,
        },
      },
    },
    {
      width: 1200,
      height: 630,
      fonts: [
        {
          name: &quot;Roboto&quot;,
          data: robotoData,
          weight: &quot;normal&quot;,
          style: &quot;normal&quot;,
        },
      ],
    }
  );

  const png = await sharp(Buffer.from(svg)).png().toBuffer();

  return new Response(png, {
    headers: {
      &quot;Content-Type&quot;: &quot;image/png&quot;,
    },
  });
};
</span></code></pre>
<p>One drawback that you can already see in the code above is that Astro does not support TSX endpoints, so we’ll need to use React-elements-like objects.</p>
<p>You’ll also always need to provide a font because it’ll be embedded.
I used <a href="https://fonts.google.com/specimen/Roboto">Roboto</a> in the example above,
<a href="https://rsms.me/inter/">Inter</a> or <a href="https://fonts.google.com/specimen/Open+Sans">Open Sans</a>
are other solid free sans fonts (choose WOFF or TTF/OTF, WOFF2 is not supported).</p>
<p>Run <code>astro dev</code> and navigate to the endpoint (in our example <a href="http://localhost:3000/og-image.png">:3000/og-image.png</a>)
to see the generated image.</p>
<h2><a inert href="#generate-for-each-item-in-a-collection" aria-hidden="true" class="anchor" id="generate-for-each-item-in-a-collection"></a>Generate for each item in a collection</h2>
<p>Once you have an image that you like in a function, you can create them in
batch, for whole collections.
Let’s assume you have a <code>blog</code> collection and your blog posts live at <code>pages/blog/:slug/index.astro</code>.
Create a <code>pages/blog/:slug/og-image.png.ts</code> with your API route and the <code>getStaticPaths</code> function exported:</p>
<pre class="syntax-highlighting"><code class="language-typescript"><span class="text plain">import { getCollection, getEntryBySlug } from &quot;astro:content&quot;;

export async function getStaticPaths() {
  const posts = await getCollection(&quot;blog&quot;);
  return posts.map((post) =&gt; ({
    params: { slug: post.slug },
    props: post,
  }));
}

export const get: APIRoute = async function get({ params, request }) {
  // ...
};
</span></code></pre>
<p>If you now run <code>astro build</code>, you’ll see that it statically generates an OG image for every blog post you have on your site.</p>
<h2><a inert href="#handling-images" aria-hidden="true" class="anchor" id="handling-images"></a>Handling images</h2>
<p>Images (like fonts) need to be embedded into the SVG.
The easiest way I found is using <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs">data urls</a>
by first reading the file to a Base64 string like this:</p>
<pre class="syntax-highlighting"><code class="language-typescript"><span class="text plain">const myImageBase64 = (await fs.readFile(&quot;./public/my-image.png&quot;)).toString(
  &quot;base64&quot;
);
</span></code></pre>
<p>And then setting it as the <code>backgroundImage</code> or <code>src</code> property in Satori:</p>
<pre class="syntax-highlighting"><code class="language-typescript"><span class="text plain">{
  type: &quot;div&quot;,
  props: {
    style: {
      backgroundImage: `url(&#39;data:image/png;base64,${myImageBase64}&#39;)`,
    }
  }
}
</span></code></pre>
<p>Be aware that Satori does not support <code>backgroundSize: cover</code>, so if you have
that use case, you’ll need to build it yourself with <a href="https://github.com/image-size/image-size">image-size</a> and some math.</p>
<h2><a inert href="#set-og-tags-in-html" aria-hidden="true" class="anchor" id="set-og-tags-in-html"></a>Set OG tags in HTML</h2>
<p>Now the only thing left to do is link to your OG images in your <code>&lt;head&gt;</code>.
There are two properties you’ll want to use for images: <code>og:image</code> and
<code>twitter:image</code>.</p>
<pre class="syntax-highlighting"><code class="language-html"><span class="text html basic"><span class="meta tag inline any html"><span class="punctuation definition tag begin html">&lt;</span><span class="entity name tag inline any html">meta</span> <span class="meta attribute-with-value html"><span class="entity other attribute-name html">property</span><span class="punctuation separator key-value html">=</span></span><span class="meta attribute-with-value html"><span class="string quoted double html"><span class="punctuation definition string begin html">&quot;</span>og:image<span class="punctuation definition string end html">&quot;</span></span></span> <span class="meta attribute-with-value html"><span class="entity other attribute-name html">content</span><span class="punctuation separator key-value html">=</span></span><span class="meta attribute-with-value html"><span class="string quoted double html"><span class="punctuation definition string begin html">&quot;</span>/blog/og-image.png<span class="punctuation definition string end html">&quot;</span></span></span><span class="punctuation definition tag end html"> /&gt;</span></span>
<span class="meta tag inline any html"><span class="punctuation definition tag begin html">&lt;</span><span class="entity name tag inline any html">meta</span> <span class="meta attribute-with-value html"><span class="entity other attribute-name html">property</span><span class="punctuation separator key-value html">=</span></span><span class="meta attribute-with-value html"><span class="string quoted double html"><span class="punctuation definition string begin html">&quot;</span>twitter:image<span class="punctuation definition string end html">&quot;</span></span></span> <span class="meta attribute-with-value html"><span class="entity other attribute-name html">content</span><span class="punctuation separator key-value html">=</span></span><span class="meta attribute-with-value html"><span class="string quoted double html"><span class="punctuation definition string begin html">&quot;</span>/blog/og-image.png<span class="punctuation definition string end html">&quot;</span></span></span><span class="punctuation definition tag end html"> /&gt;</span></span>
</span></code></pre>
<p>Use dynamic paths on collections to automatically use the correct image.
Check out <a href="https://ogp.me/">the Open Graph protocol</a> for more Open Graph meta
extensions.</p>
<h2><a inert href="#further-links--conclusion" aria-hidden="true" class="anchor" id="further-links--conclusion"></a>Further links &amp; conclusion</h2>
<p>If you want to see real, working code (I know I often do), check out the endpoint
that powers the OG images of my book reviews:
<a href="https://github.com/bahlo/arne.me/blob/main/src/pages/books/%5B...slug%5D/og-image.png.ts"><code>pages/books/[...slug]/og-image.png.ts</code></a>.
This is what it looks like: <a href="https://2023.arne.me/books/the-design-of-everyday-things/og-image.png">OG image of a book review</a>.</p>
<p>Did I miss anything? Can this workflow be further improved?
<a href="mailto:hey@arne.me">Drop me an email</a> or <a href="https://spezi.social/@arne">@ me in the Fediverse</a>,
I’d love to hear from you.</p>
]]></content:encoded></item><item><title>You’re Using Email Wrong</title><link>https://arne.me/blog/youre-using-email-wrong</link><description><![CDATA[If you don’t like email, try a different strategy.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/youre-using-email-wrong</guid><pubDate>Sun, 06 Mar 2022 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>You probably don’t like email, not a lot of people do.
That’s because you’re using it wrong.</p>
<p>Chances are that if you look at your inbox, it’s full of unsolicited marketing
emails, log-in notifications or spam.
Or you’re doing inbox zero and all that trash lives in your archive.</p>
<p>As everything else, email is subject (hah) to entropy.
If you’re not careful, chaos will take over.</p>
<!-- more -->
<h2><a inert href="#a-different-concept" aria-hidden="true" class="anchor" id="a-different-concept"></a>A different concept</h2>
<p>As controversial as the company behind <a href="https://www.hey.com">HEY</a> is, the
concept that they introduced with their mail app has fundamentally changed how I
think about email.
I adapted the part that resonated with me to my <a href="https://fastmail.com">Fastmail</a>
account like this:</p>
<h3><a inert href="#inbox" aria-hidden="true" class="anchor" id="inbox"></a>Inbox</h3>
<p>This is where all emails sent by humans end up.
That’s it.</p>
<h3><a inert href="#papertrail" aria-hidden="true" class="anchor" id="papertrail"></a>Papertrail</h3>
<p>Notifications, invoices, everything that you don’t want to delete but are not
really interested in.
This folder has about 1.6k unread emails right now.
They’re not meant to be read, but if I need to look something up, I know where
to look.</p>
<h3><a inert href="#newsfeed" aria-hidden="true" class="anchor" id="newsfeed"></a>Newsfeed</h3>
<p>I’m subscribed to over 20 newsletters and all of them end up in here.
If I have time, I’ll read the newsletters and add the articles I want to read to
<a href="https://instapaper.com">Instapaper</a>.</p>
<p>By the way, if you’re looking for something to add to your newsfeed, check out
<a href="/weekly">Arne’s Weekly</a>.</p>
<h2><a inert href="#the-setup" aria-hidden="true" class="anchor" id="the-setup"></a>The Setup</h2>
<p>I’m using Fastmail with a custom domain and have aliases with rules for the
different destinations, for example <code>papertrail@example.org</code> or
<code>newsfeed@example.org</code>.</p>
<p>This way, if I sign up for a new service or subscribe to a newsletter, instead
of having to adapt existing rules, I can use the proper email address and
everything will go where it should.</p>
<p>This is not Fastmail specific, you can do the same with most providers.
For example, if you have <code>hikingfan@gmail.com</code>, you could set a rule for
<code>hikingfan+papertrail@gmail.com</code> and one for <code>hikingfan+newsfeed@gmail.com</code> and
use those email addresses when signing up.</p>
<h2><a inert href="#the-human-factor" aria-hidden="true" class="anchor" id="the-human-factor"></a>The human factor</h2>
<p>If I look at my inbox, it’s a joy.
Feedback to blog posts, articles and personal messages from people I care about.
Sticking with this strategy made email about humans, not about machines.</p>
<p>I encourage you to try it!</p>
<p>If you have a different concept, feedback or ideas, please
<a href="mailto:hey@arne.me">let me know</a>.</p>
<p><strong>Update (Mar 08, 2022):</strong> Multiple people have responded with their systems and
questions (which I love, please keep doing that), some missed a way to track if
a service “lost” their email address.
If you use an alias, e.g. <code>papertrail@example.org</code>, you can use the <code>+</code> operator
like this: <code>papertrail+twitter@example.org</code>.
The sorting rule will be a bit more complicated, but you won’t have to adapt
it every time you sign up for something.</p>
]]></content:encoded></item><item><title>Plex on NixOS</title><link>https://arne.me/blog/plex-on-nixos</link><description><![CDATA[In this post I describe how I set up Plex on NixOS, including a virtual file system for Backblaze B2 and Nginx for HTTPS.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/plex-on-nixos</guid><pubDate>Tue, 22 Feb 2022 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>A few weeks ago, the hard drive (yes, I know) in my home lab died.
It was a sad moment, especially because I ran Plex on it and rely on that for my
music and audiobook needs.</p>
<p>The upside is that it gave me the opportunity to rethink my Plex setup.
Hosting it at home is great for storage costs and control, but it’s hard to
share with friends or access on the go, especially with a NATed IPv4, so I
decided to move to the cloud.</p>
<!-- more -->
<h2><a inert href="#table-of-contents" aria-hidden="true" class="anchor" id="table-of-contents"></a>Table of Contents</h2>
<ul>
<li><a href="#choosing-a-server-and-storage-method">Choosing a server and storage method</a></li>
<li><a href="#setting-up-nixos">Setting up NixOS</a></li>
<li><a href="#setting-up-storage">Setting up storage</a></li>
<li><a href="#configuring-plex">Configuring Plex</a>
<ul>
<li><a href="#adding-an-audiobooks-plugin">Adding an Audiobooks Plugin</a></li>
</ul>
</li>
<li><a href="#configuring-nginx">Configuring Nginx</a></li>
<li><a href="#how-much-does-it-cost">How much does it cost?</a></li>
<li><a href="#wrapping-up">Wrapping up</a></li>
</ul>
<h2><a inert href="#choosing-a-server-and-storage-method" aria-hidden="true" class="anchor" id="choosing-a-server-and-storage-method"></a>Choosing a server and storage method</h2>
<p>I chose <a href="https://cloud.hetzner.com">Hetzner Cloud</a> because I like their service,
and they use green energy.</p>
<p>The biggest challenge was storage. Hetzner charges around €50/month for a 1 TB
volume (others have comparable pricing).</p>
<p>But then my friend Eric told me about <a href="https://rclone.org">rclone</a> and its
ability to mount blob storage (which is cheap) as a virtual disk.
That means Plex sees all files as if they were actually there and if it tries
to read a file, it’s downloaded on demand if it’s not cached already.</p>
<p>Armed with this knowledge, I started setting up the server.</p>
<h2><a inert href="#setting-up-nixos" aria-hidden="true" class="anchor" id="setting-up-nixos"></a>Setting up NixOS</h2>
<p>NixOS is a declarative and reproducible operating system.
You have a configuration file in <code>/etc/nixos/configuration.nix</code> that defines
your installed applications, configuration and system setup.
And if you mess up, you can always roll back.</p>
<p>The first I did was creating a server on Hetzner with any distribution (I went
width a <a href="https://www.hetzner.com/cloud#pricing">CPX11</a> and Ubuntu) and then following the instructions on the
<a href="https://github.com/nix-community/nixos-install-scripts/tree/master/hosters/hetzner-cloud">install scripts for Hetzner Cloud</a>.</p>
<p>If you follow along, make sure to choose a server with at least 40 GB of disk
space.</p>
<p>After booting into NixOS, I changed the root password by running <code>passwd</code> and
upgraded NixOS (see <a href="https://nixos.org/manual/nixos/stable/index.html#sec-upgrading">Upgrading NixOS</a>).
If you want to further secure your NixOS installation, Christine Dodrill has a
great guide called <a href="https://christine.website/blog/paranoid-nixos-2021-07-18">Paranoid NixOS Setup</a>.</p>
<h2><a inert href="#setting-up-storage" aria-hidden="true" class="anchor" id="setting-up-storage"></a>Setting up storage</h2>
<p>I decided to go with <a href="https://www.backblaze.com/b2/cloud-storage.html">Backblaze B2</a>
as I have used it before, it’s cheaper than S3, and I don’t support Amazon.
If you want to use something else, rclone supports
<a href="https://rclone.org/#providers">a lot of providers</a>.</p>
<p>After creating a bucket for the media, I created an Application Key and made
note of the <code>keyID</code> and <code>applicationKey</code>.</p>
<p>Then I added the following lines to my Nix configuration at
<code>/etc/nixos/configuration.nix</code> to install rclone and create a
<code>/etc/rclone/rclone.conf</code> for the bucket:</p>
<pre class="syntax-highlighting"><code class="language-nix"><span class="text plain">environment.systemPackages = [ pkgs.rclone ];

environment.etc = {
  &quot;rclone/rclone.conf&quot; = {
    text = &#39;&#39;
      [b2]
      type = b2
      account = &lt;keyID&gt;
      key = &lt;applicationKey&gt;
      hard_delete = true
      versions = false
    &#39;&#39;;
    mode = &quot;0644&quot;;
  };
};
</span></code></pre>
<p>If you follow along, make sure to replace <code>&lt;keyID&gt;</code> and <code>&lt;applicationKey&gt;</code>.</p>
<p>By the way, NixOS comes with nano preinstalled, so if you want a real editor,
you can get it with the following command:</p>
<pre class="syntax-highlighting"><code class="language-sh"><span class="source shell bash"><span class="meta function-call shell"><span class="variable function shell">$</span></span><span class="meta function-call arguments shell"> nix-shell<span class="variable parameter option shell"><span class="punctuation definition parameter shell"> -</span>p</span> vim</span>
</span></code></pre>
<p>For the disk mount, I created a Systemd service that mounts the bucket on start
and automatically starts on boot.</p>
<pre class="syntax-highlighting"><code class="language-nix"><span class="text plain">systemd.services.plex_media = {
  enable = true;
  description = &quot;Mount media dir&quot;;
  wantedBy = [&quot;multi-user.target&quot;];
  serviceConfig = {
    ExecStartPre = &quot;/run/current-system/sw/bin/mkdir -p /mnt/media&quot;;
    ExecStart = &#39;&#39;
      ${pkgs.rclone}/bin/rclone mount &#39;b2:&lt;bucket name&gt;/&#39; /mnt/media \
        --config=/etc/rclone/rclone.conf \
        --allow-other \
        --allow-non-empty \
        --log-level=INFO \
        --buffer-size=50M \
        --drive-acknowledge-abuse=true \
        --no-modtime \
        --vfs-cache-mode full \
        --vfs-cache-max-size 20G \
        --vfs-read-chunk-size=32M \
        --vfs-read-chunk-size-limit=256M
    &#39;&#39;;
    ExecStop = &quot;/run/wrappers/bin/fusermount -u /mnt/media&quot;;
    Type = &quot;notify&quot;;
    Restart = &quot;always&quot;;
    RestartSec = &quot;10s&quot;;
    Environment = [&quot;PATH=${pkgs.fuse}/bin:$PATH&quot;];
  };
};
</span></code></pre>
<p>If you follow along, make sure to replace <code>&lt;bucket name&gt;</code>.</p>
<p>The <code>--vfs-*</code> arguments configure the virtual file system.
I only have 40 GB local disk space, so I set the cache size to 20 GB (using
<code>--vfs-cache-max-size</code>).</p>
<p>I then ran <code>nixos-rebuild switch</code> to apply the configuration, uploaded some
data to the bucket and listed <code>/mnt/media</code> to make sure everything works.</p>
<h2><a inert href="#configuring-plex" aria-hidden="true" class="anchor" id="configuring-plex"></a>Configuring Plex</h2>
<p>NixOS has a predefined service for Plex, which I used like this:</p>
<pre class="syntax-highlighting"><code class="language-nix"><span class="text plain">nixpkgs.config.allowUnfree = true; # Plex is unfree

services.plex = {
  enable = true;
  dataDir = &quot;/var/lib/plex&quot;;
  openFirewall = true;
  user = &quot;plex&quot;;
  group = &quot;plex&quot;;
};
</span></code></pre>
<p>With this configuration, Nix will open the correct ports in the firewall,
create a user called <code>plex</code> with a group also called <code>plex</code> and install the Plex
Media Server with the configuration in <code>/var/lib/plex</code>.</p>
<h3><a inert href="#adding-an-audiobooks-plugin" aria-hidden="true" class="anchor" id="adding-an-audiobooks-plugin"></a>Adding an Audiobooks Plugin</h3>
<p>I wanted to use the <a href="https://github.com/macr0dev/Audiobooks.bundle">Audiobooks.bundle</a>
metadata agent for better matching, so I added this to the <code>let</code>-section at the
top of <code>plex.nix</code>:</p>
<pre class="syntax-highlighting"><code class="language-nix"><span class="text plain">let
  audiobooksPlugin = pkgs.stdenv.mkDerivation {
    name = &quot;Audiobooks.bundle&quot;;
    src = pkgs.fetchurl {
      url = https://github.com/macr0dev/Audiobooks.bundle/archive/9b1de6b66cd8fe11c7d27623d8579f43df9f8b86.zip;
      sha256 = &quot;539492e3b06fca2ceb5f0cb6c5e47462d38019317b242f6f74d55c3b2d5f6e1d&quot;;
    };
    buildInputs = [ pkgs.unzip ];
    installPhase = &quot;mkdir -p $out; cp -R * $out/&quot;;
  };
in
  # ...
</span></code></pre>
<p>That fetches the commit <code>9b1de6b</code> of the audiobooks plugin and makes sure that
the SHA256 is correct.</p>
<p>Then I told Plex to use this plugin like this:</p>
<pre class="syntax-highlighting"><code class="language-nix"><span class="text plain">services.plex.managePlugins = true;
services.plex.extraPlugins = [audiobooksPlugin];
</span></code></pre>
<p>If you’re following along and get an error which says
<code>services.plex.managePlugins</code> no longer has an effect, remove that line.</p>
<p>At this point, after running <code>nixos-rebuild switch</code> again, I was able to access
the Plex interface at <code>https://&lt;domain or ip&gt;:32400</code>.</p>
<p>Plex needs an initial configuration, but only allows it if it’s coming from a
local connection.
One way to do this is an SSH tunnel, which I opened like this:</p>
<pre class="syntax-highlighting"><code class="language-sh"><span class="source shell bash"><span class="meta function-call shell"><span class="variable function shell">$</span></span><span class="meta function-call arguments shell"> ssh<span class="variable parameter option shell"><span class="punctuation definition parameter shell"> -</span>L</span> 32400:localhost:32400 user@domain-or-ip</span>
</span></code></pre>
<p>Then I opened <a href="http://localhost:32400/web">http://localhost:32400/web</a> in my local browser and set up Plex.</p>
<h2><a inert href="#configuring-nginx" aria-hidden="true" class="anchor" id="configuring-nginx"></a>Configuring Nginx</h2>
<p>I wanted a nice domain with HTTPS on 443 (instead of HTTP on port 32400), so I
set up <a href="https://nginx.com">Nginx</a> with <a href="https://letsencrypt.org">Let’s Encrypt</a>
next.</p>
<p>The first thing I did was setting <code>openFirewall</code> to false in the Plex
configuration.
Then I allowed port 80 and 443 for HTTP and HTTPS and all the
<a href="https://github.com/NixOS/nixpkgs/blob/nixos-21.11/nixos/modules/services/misc/plex.nix#L157-L160">Plex ports</a>
except for 32400 as we want to proxy the web interface through Nginx.</p>
<pre class="syntax-highlighting"><code class="language-nix"><span class="text plain">services.plex = {
  openFirewall = false;
  # ...
};

networking.firewall = {
  allowedTCPPorts = [ 3005 8324 32469 80 443 ];
  allowedUDPPorts = [ 1900 5353 32410 32412 32413 32414 ];
};
</span></code></pre>
<p>Then I configured ACME:</p>
<pre class="syntax-highlighting"><code class="language-nix"><span class="text plain">security.acme.acceptTerms = true;
security.acme.defaults.email = &quot;&lt;your email&gt;&quot;;
</span></code></pre>
<p>The default provider is Let’s Encrypt, you can find their terms of service here:
<a href="https://letsencrypt.org/repository/">Policy and Legal Repository</a>.</p>
<p>Now it was time to add the Nginx service.
I used recommended settings and only PFS-enabled ciphers with AES256.
As this proxies Plex requests, I forwarded some headers as well.
Here’s the code:</p>
<pre class="syntax-highlighting"><code class="language-nix"><span class="text plain">services.nginx = {
  enable = true;

  # Recommended settings
  recommendedGzipSettings = true;
  recommendedOptimisation = true;
  recommendedProxySettings = true;
  recommendedTlsSettings = true;

  # Only allow PFS-enabled ciphers with AES256
  sslCiphers = &quot;AES256+EECDH:AES256+EDH:!aNULL&quot;;

  virtualHosts = {
    &quot;&lt;your domain&gt;&quot; = {
      forceSSL = true;
      enableACME = true;
      extraConfig = &#39;&#39;
        # Some players don&#39;t reopen a socket and playback stops totally instead of resuming after an extended pause
        send_timeout 100m;
        # Plex headers
        proxy_set_header X-Plex-Client-Identifier $http_x_plex_client_identifier;
        proxy_set_header X-Plex-Device $http_x_plex_device;
        proxy_set_header X-Plex-Device-Name $http_x_plex_device_name;
        proxy_set_header X-Plex-Platform $http_x_plex_platform;
        proxy_set_header X-Plex-Platform-Version $http_x_plex_platform_version;
        proxy_set_header X-Plex-Product $http_x_plex_product;
        proxy_set_header X-Plex-Token $http_x_plex_token;
        proxy_set_header X-Plex-Version $http_x_plex_version;
        proxy_set_header X-Plex-Nocache $http_x_plex_nocache;
        proxy_set_header X-Plex-Provides $http_x_plex_provides;
        proxy_set_header X-Plex-Device-Vendor $http_x_plex_device_vendor;
        proxy_set_header X-Plex-Model $http_x_plex_model;
        # Buffering off send to the client as soon as the data is received from Plex.
        proxy_redirect off;
        proxy_buffering off;
      &#39;&#39;;
      locations.&quot;/&quot; = {
        proxyPass = &quot;http://localhost:32400&quot;;
        proxyWebsockets = true;
      };
    };
  };
};
</span></code></pre>
<p>If you’re following along, make sure to replace <code>&lt;your domain&gt;</code>.</p>
<p>To secure things even further, I set some headers for every request:</p>
<pre class="syntax-highlighting"><code class="language-nix"><span class="text plain">services.nginx.commonHttpConfig = &#39;&#39;
  # Add HSTS header with preloading to HTTPS requests.
  # Adding this header to HTTP requests is discouraged
  map $scheme $hsts_header {
      https   &quot;max-age=31536000; includeSubdomains; preload&quot;;
  }
  add_header Strict-Transport-Security $hsts_header;
  # Enable CSP for your services.
  #add_header Content-Security-Policy &quot;script-src &#39;self&#39;; object-src &#39;none&#39;; base-uri &#39;none&#39;;&quot; always;
  # Minimize information leaked to other domains
  add_header &#39;Referrer-Policy&#39; &#39;origin-when-cross-origin&#39;;
  # Disable embedding as a frame
  add_header X-Frame-Options DENY;
  # Prevent injection of code in other mime types (XSS Attacks)
  add_header X-Content-Type-Options nosniff;
  # Enable XSS protection of the browser.
  # May be unnecessary when CSP is configured properly (see above)
  add_header X-XSS-Protection &quot;1; mode=block&quot;;
&#39;&#39;;
</span></code></pre>
<p>Finally, I ran <code>nixos-rebuild switch</code> one last time to apply the configuration.
Then I opened <code>https://my-domain</code> in a browser and started creating Plex
libraries.</p>
<h2><a inert href="#how-much-does-it-cost" aria-hidden="true" class="anchor" id="how-much-does-it-cost"></a>How much does it cost?</h2>
<p>The CPX11 costs €4,75/month with backups enabled, B2 costs $0.005/GB/month
storage + $0.01/GB downloaded.
Storage pricing depends heavily on the amount of media stored and the amount
of media downloaded.
I pay around €10/month for my setup.</p>
<h2><a inert href="#wrapping-up" aria-hidden="true" class="anchor" id="wrapping-up"></a>Wrapping up</h2>
<p>All that’s left to do now is further configure NixOS to set a hostname,
timezone, installed packages like <code>htop</code> and enabling
<a href="https://nixos.org/manual/nixos/stable/index.html#sec-upgrading-automatic">Automatic Upgrades</a>.</p>
<p>In case that’s useful for you, here is the
<a href="/blog/plex-on-nixos/configuration.nix">configuration.nix</a>
from when I tested this blog post.</p>
<p>If you discover an issue or have a question, please don’t hesitate to
<a href="mailto:hey@arne.me">let me know</a>, I’m more than happy to help!</p>
]]></content:encoded></item><item><title>Hello World</title><link>https://arne.me/blog/hello-world</link><description><![CDATA[My first blogpost (not really).]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/hello-world</guid><pubDate>Sun, 13 Feb 2022 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>I’ve had many blogs, but this is the first post on this site, so stay tuned. ❤️</p>
]]></content:encoded></item><item><title>I&apos;m Writing an App</title><link>https://arne.me/blog/im-writing-an-app</link><description><![CDATA[A human, interested in software development, automation and sustainability]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/im-writing-an-app</guid><pubDate>Fri, 12 Jun 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>The last two weeks I’ve spent quite some time on evenings and weekends to work on an iOS app.
I won’t tell you what it is though, it’s way too early for that.</p>
<p>This is the first post in a series and this one is about technologies and tooling.</p>
<!-- more -->
<h2><a inert href="#technologies" aria-hidden="true" class="anchor" id="technologies"></a>Technologies</h2>
<p>I’m writing a native app in Swift and UIKit.</p>
<p>The decision against using React Native or any other third-party mobile technology was mostly because native feels better (to me), is often faster and gets new features first. Using React Native would have sped up the development process a lot (mostly because I know that technology and my Swift/UIKit knowledge is a little rusty), but I wanted it to be native and I’m in no rush.</p>
<p>The project was initially set up to use SwiftUI as it seems to be the future of iOS development, but I quickly hit some limits. More experienced iOS-Developers would have probably found a way around it, but I feel more at home with UIKit and it’s a stable, battle-tested technology.</p>
<h2><a inert href="#tooling" aria-hidden="true" class="anchor" id="tooling"></a>Tooling</h2>
<p>Spending most of my working time writing Go I’ve come to value automatic formatting and good linting. For that I’m using <a href="https://github.com/nicklockwood/SwiftFormat">SwiftFormat</a> and <a href="https://github.com/realm/SwiftLint">SwiftLint</a>, both with the default configuration. You can configure Xcode to automatically run those when building, which I highly recommend.</p>
<p>For dependency management I’ve started using <a href="https://github.com/Carthage/Carthage">Carthage</a>, mostly because that was the defacto-standard when I last wrote an app. But I’ve switched over to <a href="https://swift.org/package-manager">Swift Package Manager</a> because it has great Xcode-integration and is generally stable.</p>
<p>Most iOS developers have experienced their share of .xcodeproj-related merge conflicts or weird diffs. Generally I prefer all files in my project to be human-readable. For that I’ve used <a href="https://github.com/yonaskolb/XcodeGen">XcodeGen</a>, which allows you to define your project in a simple <code>project.yml</code> and ignore your project with these lines in you <code>.gitignore</code>:</p>
<pre class="syntax-highlighting"><code><span class="text plain">*.xcodeproj
!*.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
</span></code></pre>
<p>That means that on CI, new clones or changes in project.yml you need to install XcodeGen and run <code>xcodegen generate</code> to set it up, but for me that’s worth it.</p>
<p>Talking about CI, I’ve set up <a href="https://fastlane.tools/">Fastlane</a> for linting and testing. It allows easy definition of rules and automatic TestFlight or even AppStore submissions (including taking screenshots), which is awesome. It also has a <a href="https://docs.fastlane.tools/plugins/available-plugins">ton of plugins</a>.</p>
<p>Since Travis CI costs a <a href="https://travis-ci.com/plans">small fortune</a> for private repositories I’ve migrated away and now use <a href="https://github.com/features/actions">GitHub Actions</a>.
It took some time to configure it right, so here’s my <code>.github/workflows/ci.yml</code>, hoping it’s useful for some people:</p>
<pre class="syntax-highlighting"><code class="language-yaml"><span class="source yaml"><span class="string unquoted plain out yaml"><span class="entity name tag yaml">name</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">CI</span>

<span class="constant language boolean yaml">on</span><span class="punctuation separator key-value mapping yaml">:</span> <span class="meta flow-sequence yaml"><span class="punctuation definition sequence begin yaml">[</span><span class="string unquoted plain in yaml">push</span><span class="punctuation definition sequence end yaml">]</span></span>

<span class="string unquoted plain out yaml"><span class="entity name tag yaml">jobs</span></span><span class="punctuation separator key-value mapping yaml">:</span>
  <span class="string unquoted plain out yaml"><span class="entity name tag yaml">lint-and-test</span></span><span class="punctuation separator key-value mapping yaml">:</span>
    <span class="string unquoted plain out yaml"><span class="entity name tag yaml">runs-on</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">macos-latest</span>
    <span class="string unquoted plain out yaml"><span class="entity name tag yaml">steps</span></span><span class="punctuation separator key-value mapping yaml">:</span>
      <span class="punctuation definition block sequence item yaml">-</span> <span class="string unquoted plain out yaml"><span class="entity name tag yaml">run</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">sudo xcode-select -s /Applications/Xcode_11.5.app/Contents/Developer</span>
      <span class="punctuation definition block sequence item yaml">-</span> <span class="string unquoted plain out yaml"><span class="entity name tag yaml">name</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">Checkout code</span>
        <span class="string unquoted plain out yaml"><span class="entity name tag yaml">uses</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">actions/checkout@v2</span>
      <span class="punctuation definition block sequence item yaml">-</span> <span class="string unquoted plain out yaml"><span class="entity name tag yaml">name</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">Cache Bundler</span>
        <span class="string unquoted plain out yaml"><span class="entity name tag yaml">uses</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">actions/cache@v2</span>
        <span class="string unquoted plain out yaml"><span class="entity name tag yaml">with</span></span><span class="punctuation separator key-value mapping yaml">:</span>
          <span class="string unquoted plain out yaml"><span class="entity name tag yaml">path</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">vendor/bundle</span>
          <span class="string unquoted plain out yaml"><span class="entity name tag yaml">key</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">$-gems-$</span>
          <span class="string unquoted plain out yaml"><span class="entity name tag yaml">restore-keys</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="keyword control flow block-scalar literal yaml">|</span>
<span class="string unquoted block yaml">            $-gems-
</span>      <span class="punctuation definition block sequence item yaml">-</span> <span class="string unquoted plain out yaml"><span class="entity name tag yaml">name</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">Cache Swift Package Manager</span>
        <span class="string unquoted plain out yaml"><span class="entity name tag yaml">uses</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">actions/cache@v2</span>
        <span class="string unquoted plain out yaml"><span class="entity name tag yaml">with</span></span><span class="punctuation separator key-value mapping yaml">:</span>
          <span class="string unquoted plain out yaml"><span class="entity name tag yaml">path</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">.build</span>
          <span class="string unquoted plain out yaml"><span class="entity name tag yaml">key</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">$-spm-$</span>
          <span class="string unquoted plain out yaml"><span class="entity name tag yaml">restore-keys</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="keyword control flow block-scalar literal yaml">|</span>
<span class="string unquoted block yaml">            $-spm-
</span>      <span class="punctuation definition block sequence item yaml">-</span> <span class="string unquoted plain out yaml"><span class="entity name tag yaml">name</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">Install dependencies</span>
        <span class="string unquoted plain out yaml"><span class="entity name tag yaml">run</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="keyword control flow block-scalar literal yaml">|</span>
<span class="string unquoted block yaml">          bundle update --bundler
          bundle install
          brew install xcodegen swiftlint swiftformat
</span>      <span class="punctuation definition block sequence item yaml">-</span> <span class="string unquoted plain out yaml"><span class="entity name tag yaml">name</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">Generate Xcode project</span>
        <span class="string unquoted plain out yaml"><span class="entity name tag yaml">run</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">xcodegen generate</span>
      <span class="punctuation definition block sequence item yaml">-</span> <span class="string unquoted plain out yaml"><span class="entity name tag yaml">name</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">Lint</span>
        <span class="string unquoted plain out yaml"><span class="entity name tag yaml">run</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">bundle exec fastlane lint</span>
      <span class="punctuation definition block sequence item yaml">-</span> <span class="string unquoted plain out yaml"><span class="entity name tag yaml">name</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">Test</span>
        <span class="string unquoted plain out yaml"><span class="entity name tag yaml">run</span></span><span class="punctuation separator key-value mapping yaml">:</span> <span class="string unquoted plain out yaml">bundle exec fastlane test</span>
</span></code></pre>
<p>That’s my setup so far. If you have any questions or suggestions,
<a href="/contact">hit me up</a>.</p>
]]></content:encoded></item><item><title>Automate #2: Checklists with Things</title><link>https://arne.me/blog/automate-2-checklists-with-things</link><description><![CDATA[How to use checklists in TaskPaper-Format with Things.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/automate-2-checklists-with-things</guid><pubDate>Sat, 09 Feb 2019 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>This is the second post of my series <em>Automate</em>.</p>
<p>On the <a href="https://www.relay.fm/cortex">Cortex podcast</a> (which inspired the whole series), CGP Grey and Myke Hurley sometimes talk about their checklists; whole projects that can be invoked by a tap if needed.
These lists are for things that are important to get right, but you do them not often enough to remember every step, examples are an Airport or a YouTube checklist.
They mostly use <a href="https://www.omnigroup.com/omnifocus">OmniFocus</a> for this, which can export and import projects as <a href="https://taskpaper.com/">TaskPaper</a>.</p>
<!-- more -->
<p>When thinking about this, I found more and more usecases for checklists and really wanted to have this set up, but I personally can’t deal with the way some things work in OmniFocus.
I use <a href="https://culturedcode.com/">Things</a> for my todos, which doesn’t support TaskPaper, but supports its own <a href="https://support.culturedcode.com/customer/en/portal/articles/2803573">JSON format</a>.
I didn’t want to store my checklists in a proprietary format like this Things-specific JSON, I wanted TaskPaper.</p>
<p>Then I remembered playing around with <a href="https://scriptable.app/">Scriptable</a>, which could execute JavaScript on iOS with native bindings to UI, Clipboard, etc. – this sounded perfect for my usecase, so I started writing a script to convert TaskPaper to Things JSON, which will be the core of my checklist flow.</p>
<h2><a inert href="#the-script" aria-hidden="true" class="anchor" id="the-script"></a>The Script</h2>
<p>I ended up writing a 300-line script (<a href="https://github.com/bahlo/scriptable-scripts/blob/master/TaskPaperToThings.js">it’s on GitHub</a>) with only enough functionality to fit my needs (e.g. it only supports tabbed indentation and may break on the slightest deviation).</p>
<p>Since I wanted to run this script from Apples Shortcuts app, I needed to use a hack for input/output data.
Many apps face these problems and use the clipboard (e.g. <a href="https://autosleep.tantsissa.com/shortcutsapp#TOC-How-do-I-use-AutoSleep-dictionaries-in-the-shortcuts-app-">AutoSleep</a>), so that’s what I did as well.
If you run the script in the Scriptable app, it will get the clipboard contents expecting text in TaskPaper format, convert it to the Things JSON and copy it.</p>
<p>Here’s how the <code>@tag</code> and <code>@attr(value)</code> work:</p>
<ul>
<li><code>@due(2019-02-09)</code> would be setting the due date</li>
<li><code>@today</code> will set the task or project to start today</li>
<li><code>@start(2019-02-09)</code> will set the task or project to start on the given date (in <code>YYYY-MM-DD</code> format)</li>
<li><code>@due(2019-02-09)</code> will set the due-date of a task or project to the given date</li>
<li>Anything else without a value (like <code>@foo</code>) will be a Things tag (this tag has to exist beforehand)</li>
<li>Anything else with a value will be a Things attribute, see the <a href="https://support.culturedcode.com/customer/en/portal/articles/2803573">reference</a> for attributes</li>
</ul>
<h2><a inert href="#the-shortcut" aria-hidden="true" class="anchor" id="the-shortcut"></a>The Shortcut</h2>
<p>I wanted one shortcut to start all checklists from. Sadly you cannot get a directory listing from an iCloud folder, so for now if I add a new checklist, I have to add the filename to the <code>List</code> at the top.</p>
<p>This is what the Shortcut looks like:</p>
<picture>
  <source srcset="/blog/automate-2-checklists-with-things/shortcut.avif" type="image/avif" />
  <img src="/blog/automate-2-checklists-with-things/shortcut.png" alt="An iOS shortcut: Select from a list of items, get the file from iCloud Drive, copy it to the clipboard, run the Scriptable script, get the clipboard, url encode it and open Things using the URL scheme" />
</picture>
<p>After a checklist was chosen, the shortcut gets the file from iCloud Drive (it has to be in the Shortcuts application folder to be accessible).
Then it copies the file contents to the Clipboard and runs the script (the Scriptable action should appear automatically in your Shortcuts app once you created it). After that, it retrieves the Things JSON from the clipboard, url-encodes it and opens Things with its URL scheme.</p>
<h2><a inert href="#conclusion" aria-hidden="true" class="anchor" id="conclusion"></a>Conclusion</h2>
<p>This shortcut-scriptable-combination helps to use checklists in Things from anywhere while storing them in a highly-compatible format. I really love to find more use cases for checklists. Do you use checklists? What usecases do you have? I’d love to <a href="/contact">hear from you</a>.</p>
]]></content:encoded></item><item><title>Automate #1: Spotify Podcast Mirror</title><link>https://arne.me/blog/automate-1-spotify-podcast-mirror</link><description><![CDATA[How I created an Automator application, which records the latest episode of a Spotify podcast, fills out metadata and generates a file for metadata for a podcast client to subscribe to.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/automate-1-spotify-podcast-mirror</guid><pubDate>Sat, 05 Jan 2019 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>This is the first post of my series <em>Automate</em>.</p>
<p>In this post I describe how I created an Automator application, which will record the latest episode of a Spotify podcast, fill out metadata like title and description and generate a file for metadata for a podcast client to subscribe to.</p>
<!-- more -->
<h2><a inert href="#preparation" aria-hidden="true" class="anchor" id="preparation"></a>Preparation</h2>
<p>If you want to follow along, you need a machine with macOS and the following installed:</p>
<ul>
<li>Spotify</li>
<li><a href="https://www.rogueamoeba.com/audiohijack/">Audio Hijack</a> ($77, sorry)</li>
<li><a href="https://stedolan.github.io/jq">jq</a></li>
<li><a href="https://golang.org/">Golang</a></li>
</ul>
<p>You also need a bit of knowledge in Apple Script, Bash scripting and JSON and be comfortable in a terminal.</p>
<h3><a inert href="#create-audio-hijack-session" aria-hidden="true" class="anchor" id="create-audio-hijack-session"></a>Create Audio Hijack Session</h3>
<p>Audio Hijack is an application that can do complex audio pipelines, often used for recording podcasts or live streaming. We use this to capture the output of Spotify to a file. Audio Hijack has a concept of <em>sessions</em>, meaning various saved pipelines.</p>
<p>To prepare things, start Audio Hijack and create a new session based on the <code>Application Audio</code> template.
This post assumes you only have on session. If you have multiple, make sure it’s at the first position. In the session, select Spotify as Application and change the Recorder to 128 kbps (or whatever you prefer). I recommend disabling the Output Device, but it’s helpful if you want to debug something. After saving, it should look similar to this:</p>
<picture>
  <source srcset="/blog/automate-1-spotify-podcast-mirror/audio_hijack_session.avif" type="image/avif" />
  <img src="/blog/automate-1-spotify-podcast-mirror/audio_hijack_session.png" alt="An AudioHijack session with Spotify and a Recorder" />
</picture>
<h3><a inert href="#spotify-credentials" aria-hidden="true" class="anchor" id="spotify-credentials"></a>Spotify Credentials</h3>
<p>We need various metadata about the latest show, like title, Spotify uri or duration.
To get those, we use the <a href="https://developer.spotify.com/documentation/web-api/">Spotify Web API</a>. You need to create a Spotify App by going to the <a href="https://developer.spotify.com/dashboard">developer dashboard</a> and create a client id.
Fill out the fields and you’ll get a <code>Client ID</code> and a <code>Client Secret</code>.</p>
<h3><a inert href="#spotify-id" aria-hidden="true" class="anchor" id="spotify-id"></a>Spotify ID</h3>
<p>We also need the Spotify ID of the podcast you want to mirror. To get that, click the three dots on the podcast page in Spotify, select <code>Share</code> and <code>Copy Spotify URI</code>.
The uri looks something like <code>spotify:show:abcdef</code>, where <code>abcdef</code> is your Spotify ID.</p>
<h3><a inert href="#folder-setup" aria-hidden="true" class="anchor" id="folder-setup"></a>Folder Setup</h3>
<p>Lastly, there needs to be a folder where the podcast lives. We use a tool in our last step to generate the RSS feed, so we can subscribe to the podcast from our favourite podcast app. Fot the tool to work, we need to have a <code>podcast.toml</code> similar to this <a href="https://github.com/bahlo/toml-podcast/blob/dffe266169567348cb5049f0bdc8c09a56e10879/examples/podcast.toml">example</a>, but without the <code>[[episodes]]</code> parts in our folder.
There also needs to be an empty subfolder called <code>dist/</code>.</p>
<h2><a inert href="#lets-do-this" aria-hidden="true" class="anchor" id="lets-do-this"></a>Let’s do this</h2>
<h3><a inert href="#1-create-an-automator-project" aria-hidden="true" class="anchor" id="1-create-an-automator-project"></a>1. Create an Automator Project</h3>
<p>Open the Automator app and choose <code>New Document</code>.
Then click on <code>Application</code> to build a <code>.app</code> file you can launch later.</p>
<h3><a inert href="#2-get-latest-episode-metadata" aria-hidden="true" class="anchor" id="2-get-latest-episode-metadata"></a>2. Get Latest Episode Metadata</h3>
<p>We need the following attributes of the latest episode:</p>
<ul>
<li>Spotify URI</li>
<li>Duration</li>
<li>Name</li>
<li>Description</li>
<li>Release date</li>
</ul>
<p>We use Bash to get the data, so drag a new Run Shell Script onto your workflow with the following contents:</p>
<pre class="syntax-highlighting"><code class="language-sh"><span class="source shell bash"><span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell">!/bin/bash</span><span class="comment line number-sign shell">
</span>
<span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> Exit the script if any command returns an error code.</span><span class="comment line number-sign shell">
</span><span class="meta function-call shell"><span class="support function set shell">set</span></span><span class="meta function-call arguments shell"> <span class="variable parameter option shell"><span class="punctuation definition parameter shell">-</span>e</span></span>

<span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> Define variables</span><span class="comment line number-sign shell">
</span><span class="variable other readwrite assignment shell">PODCAST_PATH</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="string quoted single shell"><span class="punctuation definition string begin shell">&#39;</span>/path/to/podcast/folder<span class="punctuation definition string end shell">&#39;</span></span></span>
<span class="variable other readwrite assignment shell">SPOTIFY_ID</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="string quoted single shell"><span class="punctuation definition string begin shell">&#39;</span>abcdef<span class="punctuation definition string end shell">&#39;</span></span></span>
<span class="variable other readwrite assignment shell">AUTH_HEADER</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="string quoted single shell"><span class="punctuation definition string begin shell">&#39;</span>abcdef123456<span class="punctuation definition string end shell">&#39;</span></span></span> <span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> base64 encoded `&lt;client id&gt;:&lt;client secret&gt;`</span><span class="comment line number-sign shell">
</span>
<span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> Get Spotify access token</span><span class="comment line number-sign shell">
</span><span class="variable other readwrite assignment shell">TOKEN_RES</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="meta group expansion command parens shell"><span class="punctuation definition variable shell">$</span><span class="punctuation section parens begin shell">(</span><span class="meta function-call shell"><span class="variable function shell">curl</span></span><span class="meta function-call arguments shell"><span class="variable parameter option shell"><span class="punctuation definition parameter shell"> -</span>s</span><span class="variable parameter option shell"><span class="punctuation definition parameter shell"> -</span>X</span> POST <span class="string quoted single shell"><span class="punctuation definition string begin shell">&#39;</span>https://accounts.spotify.com/api/token<span class="punctuation definition string end shell">&#39;</span></span> <span class="punctuation separator continuation line shell">\
</span><span class="variable parameter option shell"><span class="punctuation definition parameter shell">	-</span>H</span> Authorization:<span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span>Basic <span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">AUTH_HEADER</span></span><span class="punctuation definition string end shell">&quot;</span></span><span class="variable parameter option shell"><span class="punctuation definition parameter shell"> --</span>data</span> <span class="string quoted single shell"><span class="punctuation definition string begin shell">&#39;</span>grant_type=client_credentials<span class="punctuation definition string end shell">&#39;</span></span></span><span class="punctuation section parens end shell">)</span></span></span>
<span class="variable other readwrite assignment shell">ACCESS_TOKEN</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="meta group expansion command parens shell"><span class="punctuation definition variable shell">$</span><span class="punctuation section parens begin shell">(</span><span class="meta function-call shell"><span class="support function echo shell">echo</span></span><span class="meta function-call arguments shell"> <span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">TOKEN_RES</span></span></span> <span class="keyword operator logical pipe shell">|</span> <span class="meta function-call shell"><span class="variable function shell">/usr/local/bin/jq</span></span><span class="meta function-call arguments shell"><span class="variable parameter option shell"><span class="punctuation definition parameter shell"> -</span>r</span> .access_token</span><span class="punctuation section parens end shell">)</span></span></span>

<span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> Get show</span><span class="comment line number-sign shell">
</span><span class="variable other readwrite assignment shell">SHOW_RES</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="meta group expansion command parens shell"><span class="punctuation definition variable shell">$</span><span class="punctuation section parens begin shell">(</span><span class="meta function-call shell"><span class="variable function shell">curl</span></span><span class="meta function-call arguments shell"><span class="variable parameter option shell"><span class="punctuation definition parameter shell"> -</span>s</span> <span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span>https://api.spotify.com/v1/shows/<span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">SPOTIFY_ID</span></span><span class="punctuation definition string end shell">&quot;</span></span> <span class="punctuation separator continuation line shell">\
</span><span class="variable parameter option shell"><span class="punctuation definition parameter shell">	-</span>H</span> Authorization:<span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span>Bearer <span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">ACCESS_TOKEN</span></span><span class="punctuation definition string end shell">&quot;</span></span></span><span class="punctuation section parens end shell">)</span></span></span>
<span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> Get first (latest) item</span><span class="comment line number-sign shell">
</span><span class="variable other readwrite assignment shell">ITEM</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="meta group expansion command parens shell"><span class="punctuation definition variable shell">$</span><span class="punctuation section parens begin shell">(</span><span class="meta function-call shell"><span class="support function echo shell">echo</span></span><span class="meta function-call arguments shell"> <span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">SHOW_RES</span></span><span class="punctuation definition string end shell">&quot;</span></span></span> <span class="keyword operator logical pipe shell">|</span> <span class="meta function-call shell"><span class="variable function shell">/usr/local/bin/jq</span></span><span class="meta function-call arguments shell"> <span class="string quoted single shell"><span class="punctuation definition string begin shell">&#39;</span>.episodes.items[0]<span class="punctuation definition string end shell">&#39;</span></span></span><span class="punctuation section parens end shell">)</span></span></span>
<span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> Get release date</span><span class="comment line number-sign shell">
</span><span class="variable other readwrite assignment shell">RELEASE_DATE</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="meta group expansion command parens shell"><span class="punctuation definition variable shell">$</span><span class="punctuation section parens begin shell">(</span><span class="meta function-call shell"><span class="support function echo shell">echo</span></span><span class="meta function-call arguments shell"> <span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">ITEM</span></span><span class="punctuation definition string end shell">&quot;</span></span></span> <span class="keyword operator logical pipe shell">|</span> <span class="meta function-call shell"><span class="variable function shell">/usr/local/bin/jq</span></span><span class="meta function-call arguments shell"><span class="variable parameter option shell"><span class="punctuation definition parameter shell"> -</span>r</span> .release_date</span><span class="punctuation section parens end shell">)</span></span></span>

<span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> If the episode already exists, exit early</span><span class="comment line number-sign shell">
</span><span class="keyword control if begin shell">if</span> <span class="support function test begin shell">[</span><span class="meta function-call arguments shell"> <span class="variable parameter option shell"><span class="punctuation definition parameter shell">-</span>f</span> <span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">PODCAST_PATH</span></span>/dist/episodes/<span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">RELEASE_DATE</span></span>.mp3<span class="punctuation definition string end shell">&quot;</span></span> <span class="support function test end shell">]</span></span><span class="keyword operator logical continue shell">;</span> <span class="keyword control if then shell">then</span>
	<span class="punctuation definition compound begin shell">(</span><span class="meta function-call shell"></span><span class="meta function-call arguments shell"><span class="keyword operator assignment redirection shell">&gt;&amp;</span><span class="constant numeric integer decimal file-descriptor shell">2</span> echo <span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span>Episode already exists<span class="punctuation definition string end shell">&quot;</span></span></span><span class="punctuation definition compound end shell">)</span>
	<span class="meta function-call shell"><span class="support function exit shell">exit</span></span><span class="meta function-call arguments shell"> 1</span>
<span class="keyword control if end shell">fi</span>

<span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> Get and echo metadata, each line will be a parameter to the next action</span><span class="comment line number-sign shell">
</span><span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> in the Automator workflow.</span><span class="comment line number-sign shell">
</span><span class="meta function-call shell"><span class="support function echo shell">echo</span></span><span class="meta function-call arguments shell"> <span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">ITEM</span></span><span class="punctuation definition string end shell">&quot;</span></span></span> <span class="keyword operator logical pipe shell">|</span> <span class="meta function-call shell"><span class="variable function shell">/usr/local/bin/jq</span></span><span class="meta function-call arguments shell"><span class="variable parameter option shell"><span class="punctuation definition parameter shell"> -</span>r</span> .uri</span>
<span class="meta function-call shell"><span class="support function echo shell">echo</span></span><span class="meta function-call arguments shell"> <span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">ITEM</span></span><span class="punctuation definition string end shell">&quot;</span></span></span> <span class="keyword operator logical pipe shell">|</span> <span class="meta function-call shell"><span class="variable function shell">/usr/local/bin/jq</span></span><span class="meta function-call arguments shell"><span class="variable parameter option shell"><span class="punctuation definition parameter shell"> -</span>r</span> .duration_ms</span>
<span class="meta function-call shell"><span class="support function echo shell">echo</span></span><span class="meta function-call arguments shell"> <span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">ITEM</span></span><span class="punctuation definition string end shell">&quot;</span></span></span> <span class="keyword operator logical pipe shell">|</span> <span class="meta function-call shell"><span class="variable function shell">/usr/local/bin/jq</span></span><span class="meta function-call arguments shell"><span class="variable parameter option shell"><span class="punctuation definition parameter shell"> -</span>r</span> .name</span>
<span class="meta function-call shell"><span class="support function echo shell">echo</span></span><span class="meta function-call arguments shell"> <span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">ITEM</span></span><span class="punctuation definition string end shell">&quot;</span></span></span> <span class="keyword operator logical pipe shell">|</span> <span class="meta function-call shell"><span class="variable function shell">/usr/local/bin/jq</span></span><span class="meta function-call arguments shell"><span class="variable parameter option shell"><span class="punctuation definition parameter shell"> -</span>r</span> .description</span>
<span class="meta function-call shell"><span class="support function echo shell">echo</span></span><span class="meta function-call arguments shell"> <span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">RELEASE_DATE</span></span><span class="punctuation definition string end shell">&quot;</span></span></span>
</span></code></pre>
<p>We need the metadata again later, so save it in a variable by dragging the <code>Set value of variable</code> below the Bash action and giving it a name (e.g. <code>Metadata</code>).</p>
<h3><a inert href="#3-start-the-recording-process" aria-hidden="true" class="anchor" id="3-start-the-recording-process"></a>3. Start the Recording Process</h3>
<p>Now we get to the messy part. Drag a <code>Run AppleScript</code> action to the end of the workflow and paste the following code.</p>
<p>Read the comments (lines starting with <code>--</code>) to know what it’s doing. Make sure the checkbox next to Automator in System Settings → Security &amp; Privacy → Accessibility) is checked, otherwise you will get an error and the keyboard shortcuts won’t work.</p>
<pre class="syntax-highlighting"><code class="language-applescript"><span class="source applescript"><span class="keyword other applescript">on</span> <span class="support function built-in command applescript">run</span> <span class="meta array applescript"><span class="punctuation section array applescript">{</span>input<span class="punctuation separator array applescript">,</span> parameters<span class="punctuation section array applescript">}</span></span>
	<span class="meta statement set applescript"><span class="keyword control def set applescript">set</span> <span class="variable other readwrite set applescript">uri</span> <span class="keyword control def set applescript">to</span> <span class="support function built-in property applescript">item</span> <span class="constant numeric applescript">1</span> <span class="keyword other applescript">of</span> input</span>
	<span class="meta statement set applescript"><span class="keyword control def set applescript">set</span> <span class="variable other readwrite set applescript">duration_ms</span> <span class="keyword control def set applescript">to</span> <span class="support function built-in property applescript">item</span> <span class="constant numeric applescript">2</span> <span class="keyword other applescript">of</span> input</span>
	<span class="meta statement set applescript"><span class="keyword control def set applescript">set</span> <span class="variable other readwrite set applescript">title</span> <span class="keyword control def set applescript">to</span> <span class="support function built-in property applescript">item</span> <span class="constant numeric applescript">3</span> <span class="keyword other applescript">of</span> input</span>
	<span class="meta statement set applescript"><span class="keyword control def set applescript">set</span> <span class="variable other readwrite set applescript">description</span> <span class="keyword control def set applescript">to</span> <span class="support function built-in property applescript">item</span> <span class="constant numeric applescript">4</span> <span class="keyword other applescript">of</span> input</span>
	<span class="meta statement set applescript"><span class="keyword control def set applescript">set</span> <span class="variable other readwrite set applescript">release_date</span> <span class="keyword control def set applescript">to</span> <span class="support function built-in property applescript">item</span> <span class="constant numeric applescript">5</span> <span class="keyword other applescript">of</span> input</span>

	<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Prepare Spotify
</span><span class="meta block tell application generic applescript">	<span class="keyword control tell applescript">tell</span> <span class="support class built-in applescript">application</span> <span class="string quoted double application-name applescript"><span class="punctuation definition string applescript">&quot;</span>Spotify<span class="punctuation definition string applescript">&quot;</span></span>
		<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Start spotify
</span>		<span class="support function standard-suite applescript">activate</span>

		<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Start playing our episode
</span>		play track uri

		<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Make sure we start at 0s
</span>		<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> If we are at 0s, this will skip to the previous track
</span>		<span class="support function built-in command applescript">delay</span> <span class="constant numeric applescript">1</span>
		<span class="meta statement set applescript"><span class="keyword control def set applescript">set</span> <span class="variable other readwrite set applescript">episode_id</span> <span class="keyword control def set applescript">to</span> <span class="keyword operator reference applescript">id</span> <span class="keyword other applescript">of</span> current track</span>
		previous track
		pause
		<span class="support function built-in command applescript">delay</span> <span class="constant numeric applescript">1</span>
<span class="meta block if applescript">		<span class="keyword control if applescript">if</span> episode_id <span class="keyword operator word applescript">is not</span> <span class="keyword operator reference applescript">id</span> <span class="keyword other applescript">of</span> current track <span class="keyword control then applescript">then</span>
			play track uri
			pause
		<span class="keyword control end applescript">end if</span></span>
	<span class="keyword control tell applescript">end tell</span></span>

	<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Start recording
</span><span class="meta block tell application generic applescript">	<span class="keyword control tell applescript">tell</span> <span class="support class built-in applescript">application</span> <span class="string quoted double application-name applescript"><span class="punctuation definition string applescript">&quot;</span>Audio Hijack<span class="punctuation definition string applescript">&quot;</span></span>
		<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Start Audio Hijack
</span>		<span class="support function standard-suite applescript">activate</span>

<span class="meta block tell application system-events applescript">		<span class="keyword control tell applescript">tell</span> <span class="support class built-in applescript">application</span> <span class="string quoted double application-name applescript"><span class="punctuation definition string applescript">&quot;</span>System Events<span class="punctuation definition string applescript">&quot;</span></span>
			<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> CMD + 1 to open Sessions
</span>			<span class="support function system-events processes applescript">keystroke</span> <span class="string quoted double applescript"><span class="punctuation definition string applescript">&quot;</span>1<span class="punctuation definition string applescript">&quot;</span></span> using command down
			<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Arrow down to select the first one
</span>			<span class="support function system-events processes applescript">key code</span> <span class="constant numeric applescript">124</span>
			<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Open session
</span>			<span class="support function system-events processes applescript">keystroke</span> <span class="string quoted double applescript"><span class="punctuation definition string applescript">&quot;</span>o<span class="punctuation definition string applescript">&quot;</span></span> using command down
			<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Start recording
</span>			<span class="support function system-events processes applescript">keystroke</span> <span class="string quoted double applescript"><span class="punctuation definition string applescript">&quot;</span>r<span class="punctuation definition string applescript">&quot;</span></span> using command down
		<span class="keyword control tell applescript">end tell</span></span>
	<span class="keyword control tell applescript">end tell</span></span>

	<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Play track
</span><span class="meta block tell application generic applescript">	<span class="keyword control tell applescript">tell</span> <span class="support class built-in applescript">application</span> <span class="string quoted double application-name applescript"><span class="punctuation definition string applescript">&quot;</span>Spotify<span class="punctuation definition string applescript">&quot;</span></span>
		<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Bring to foreground
</span>		<span class="support function standard-suite applescript">activate</span>

		<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Start playing our track again
</span>		play track uri

		<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Wait the length of our episode
</span>		<span class="support function built-in command applescript">delay</span> duration_ms <span class="keyword operator arithmetic applescript">/</span> <span class="constant numeric applescript">1000</span>

		<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Press pause
</span>		pause

		<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Quit spotify
</span>		<span class="support function standard-suite applescript">quit</span>
	<span class="keyword control tell applescript">end tell</span></span>

<span class="meta block tell application generic applescript">	<span class="keyword control tell applescript">tell</span> <span class="support class built-in applescript">application</span> <span class="string quoted double application-name applescript"><span class="punctuation definition string applescript">&quot;</span>Audio Hijack<span class="punctuation definition string applescript">&quot;</span></span>
		<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Bring Audio Hijack to foreground
</span>		<span class="support function standard-suite applescript">activate</span>

<span class="meta block tell application system-events applescript">		<span class="keyword control tell applescript">tell</span> <span class="support class built-in applescript">application</span> <span class="string quoted double application-name applescript"><span class="punctuation definition string applescript">&quot;</span>System Events<span class="punctuation definition string applescript">&quot;</span></span>
			<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Stop recording
</span>			<span class="support function built-in command applescript">delay</span> <span class="constant numeric applescript">1</span>
			<span class="support function system-events processes applescript">keystroke</span> <span class="string quoted double applescript"><span class="punctuation definition string applescript">&quot;</span>r<span class="punctuation definition string applescript">&quot;</span></span> using command down
		<span class="keyword control tell applescript">end tell</span></span>

		<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Quit Audio Hijack
</span>		<span class="support function standard-suite applescript">quit</span>
	<span class="keyword control tell applescript">end tell</span></span>
<span class="keyword operator reference applescript">end</span> <span class="support function built-in command applescript">run</span>
</span></code></pre>
<h3><a inert href="#4-save-release-date-to-its-own-variable" aria-hidden="true" class="anchor" id="4-save-release-date-to-its-own-variable"></a>4. Save Release Date to it’s Own Variable</h3>
<p>We use the publish date of the episode as filename, so we need to get this first. Drag the action <em>Get Value of Variable</em> into your workflow and select the <code>Metadata</code> variable (or whatever you called it). After that, drag a <em>Run AppleScript</em> action at the end with the following contents.</p>
<pre class="syntax-highlighting"><code class="language-applescript"><span class="source applescript"><span class="keyword other applescript">on</span> <span class="support function built-in command applescript">run</span> <span class="meta array applescript"><span class="punctuation section array applescript">{</span>input<span class="punctuation separator array applescript">,</span> parameters<span class="punctuation section array applescript">}</span></span>
	<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> Return the fifth parameter of our Metadata variable,
</span>	<span class="comment line double-dash applescript"><span class="punctuation definition comment applescript">--</span> which is the release date
</span>	<span class="constant other text applescript">return</span> <span class="support function built-in property applescript">item</span> <span class="constant numeric applescript">5</span> <span class="keyword other applescript">of</span> input
<span class="keyword operator reference applescript">end</span> <span class="support function built-in command applescript">run</span>
</span></code></pre>
<p>Set the variable <code>ReleaseDate</code> using a <em>Set Value of Variable</em> action.</p>
<h3><a inert href="#5-move-file-to-destination" aria-hidden="true" class="anchor" id="5-move-file-to-destination"></a>5. Move File to Destination</h3>
<p>Use a <em>Find Finder</em> items action to search the destination folder of your Audio Hijack session for files with all of the following attributes:</p>
<ul>
<li>Kind is music</li>
<li>Date created is today</li>
</ul>
<p>Then drag a <em>Run AppleScript</em> with the following contents into your workflow to get the first item (which will be the latest).</p>
<pre class="syntax-highlighting"><code class="language-applescript"><span class="source applescript"><span class="keyword other applescript">on</span> <span class="support function built-in command applescript">run</span> <span class="meta array applescript"><span class="punctuation section array applescript">{</span>input<span class="punctuation separator array applescript">,</span> parameters<span class="punctuation section array applescript">}</span></span>
	# <span class="keyword other applescript">Get</span> latest <span class="support function built-in property applescript">item</span>
	<span class="constant other text applescript">return</span> <span class="support function built-in property applescript">item</span> <span class="constant numeric applescript">1</span> <span class="keyword other applescript">of</span> input
<span class="keyword operator reference applescript">end</span> <span class="support function built-in command applescript">run</span>
</span></code></pre>
<p>After that, rename the file using <em>Rename Finder Items</em>. Choose Name <code>Single Item</code> and set <code>Basename only</code> to <code>ReleaseDate</code>.</p>
<p>Then move the renamed file to <code>podcast-folder/dist/episodes/</code> with a <em>Move Finder Items</em> action.</p>
<h3><a inert href="#6-metadata-and-deployment" aria-hidden="true" class="anchor" id="6-metadata-and-deployment"></a>6. Metadata and Deployment</h3>
<p>Now that we have our recording, we have to generate the RSS feed with the metadata so podcast clients can display the episodes in a nice list. After that we upload both the feed and the episode.</p>
<p>Use <em>Get Value of Variable</em> to get the <code>Metadata</code> variable again.</p>
<p>To generate the <code>feed.xml</code>, we append the metadata of an episode to a simple TOML file. I wrote a small tool for this, which you can find here: <a href="https://web.archive.org/web/20200923091609/https://github.com/bahlo/toml-podcast">toml-podcast</a>.
After generating the <code>feed.xml</code>, we use the <a href="https://aws.amazon.com/cli">AWS CLI</a> deploy it and the newly recorded episode to an AWS S3 bucket.</p>
<p>Add a <em>Run Shell Script</em> action with the following contents:</p>
<pre class="syntax-highlighting"><code class="language-sh"><span class="source shell bash"><span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell">!/bin/bash</span><span class="comment line number-sign shell">
</span>
<span class="meta function-call shell"><span class="support function set shell">set</span></span><span class="meta function-call arguments shell"> <span class="variable parameter option shell"><span class="punctuation definition parameter shell">-</span>e</span></span>

<span class="variable other readwrite assignment shell">PODCAST_PATH</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="string quoted single shell"><span class="punctuation definition string begin shell">&#39;</span>/path/to/podcast/folder<span class="punctuation definition string end shell">&#39;</span></span></span>
<span class="variable other readwrite assignment shell">URI</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">2</span></span><span class="punctuation definition string end shell">&quot;</span></span></span>
<span class="variable other readwrite assignment shell">DURATION_MS</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">3</span></span><span class="punctuation definition string end shell">&quot;</span></span></span>
<span class="variable other readwrite assignment shell">TITLE</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">4</span></span><span class="punctuation definition string end shell">&quot;</span></span></span>
<span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> For description, we have to escape double quotes (&quot;), because the TOML strings</span><span class="comment line number-sign shell">
</span><span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> use double quotes as well.</span><span class="comment line number-sign shell">
</span><span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> If the TOML is invalid, toml-podcast will crash.</span><span class="comment line number-sign shell">
</span><span class="variable other readwrite assignment shell">DESCRIPTION</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="meta group expansion command parens shell"><span class="punctuation definition variable shell">$</span><span class="punctuation section parens begin shell">(</span><span class="meta function-call shell"><span class="support function echo shell">echo</span></span><span class="meta function-call arguments shell"> <span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">5</span></span><span class="punctuation definition string end shell">&quot;</span></span></span> <span class="keyword operator logical pipe shell">|</span> <span class="meta function-call shell"><span class="variable function shell">sed</span></span><span class="meta function-call arguments shell"> <span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span>s/<span class="constant character escape shell">\&quot;</span>/<span class="constant character escape shell">\\</span><span class="constant character escape shell">\\</span><span class="constant character escape shell">\&quot;</span>/g<span class="punctuation definition string end shell">&quot;</span></span></span><span class="punctuation section parens end shell">)</span></span></span>
<span class="variable other readwrite assignment shell">RELEASE_DATE</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">6</span></span><span class="punctuation definition string end shell">&quot;</span></span></span>

<span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> Add episode metadat to the end to podcast.toml</span><span class="comment line number-sign shell">
</span><span class="meta function-call shell"><span class="variable function shell">cat</span></span><span class="meta function-call arguments shell"> <span class="string unquoted heredoc shell"><span class="keyword operator assignment redirection shell">&lt;&lt;</span> <span class="keyword control heredoc-token shell">EOT</span></span> <span class="meta function-call arguments shell"><span class="keyword operator assignment redirection shell">&gt;&gt;</span> <span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">PODCAST_PATH</span></span>/podcast.toml<span class="punctuation definition string end shell">&quot;</span></span></span><span class="string unquoted heredoc shell">
[[episodes]]
title=&quot;<span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">TITLE</span></span>&quot;
file=&quot;<span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">RELEASE_DATE</span></span>.mp3&quot;
date=&quot;<span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">RELEASE_DATE</span></span>&quot;
description=&quot;<span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">DESCRIPTION</span></span>&quot;

<span class="keyword control heredoc-token shell">EOT</span></span></span>

<span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> Build feed.xml using toml-pdocast</span><span class="comment line number-sign shell">
</span><span class="meta function-call shell"><span class="support function cd shell">cd</span></span><span class="meta function-call arguments shell"> <span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">PODCAST_PATH</span></span></span>
<span class="meta function-call shell"><span class="variable function shell">/Users/arnebahlo/Developer/go/bin/toml-podcast</span></span>
<span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> Your $GOBIN will look different, find out your path by typing `which toml-podcast`</span><span class="comment line number-sign shell">
</span><span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> in your terminal</span><span class="comment line number-sign shell">
</span>
<span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> Set AWS credentials (replace with your real credentials)</span><span class="comment line number-sign shell">
</span><span class="meta function-call shell"><span class="storage modifier shell">export</span> <span class="variable other readwrite assignment shell">AWS_ACCESS_KEY_ID</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span>ABCDEF<span class="punctuation definition string end shell">&quot;</span></span></span></span>
<span class="meta function-call shell"><span class="storage modifier shell">export</span> <span class="variable other readwrite assignment shell">AWS_SECRET_ACCESS_KEY</span><span class="keyword operator assignment shell">=</span><span class="string unquoted shell"><span class="string quoted double shell"><span class="punctuation definition string begin shell">&quot;</span>GHIJKL<span class="punctuation definition string end shell">&quot;</span></span></span></span>

<span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> Upload episodes + feed.xml</span><span class="comment line number-sign shell">
</span><span class="meta function-call shell"><span class="support function cd shell">cd</span></span><span class="meta function-call arguments shell"> dist/</span>
<span class="meta function-call shell"><span class="variable function shell">/usr/local/bin/aws</span></span><span class="meta function-call arguments shell"> s3 sync episodes s3://fuf-mirror/episodes</span>
<span class="meta function-call shell"><span class="variable function shell">/usr/local/bin/aws</span></span><span class="meta function-call arguments shell"> s3 cp<span class="variable parameter option shell"><span class="punctuation definition parameter shell"> --</span>cache-control</span> max-age=600 feed.xml s3://fuf-mirror/feed.xml</span>
</span></code></pre>
<p>I use a <code>Cache-Control</code> header because I serve the podcast via a CloudFront, the AWS CDN and I want the <code>feed.xml</code> to be at most 10 minutes old.</p>
<h2><a inert href="#conclusion" aria-hidden="true" class="anchor" id="conclusion"></a>Conclusion</h2>
<p>Now recording the latest episode of your favourite Spotify podcast takes one click. If you have a spare Mac you could even start it automatically matching the release cycle of the podcast.</p>
]]></content:encoded></item><item><title>Announcing the Automate Series</title><link>https://arne.me/blog/announcing-the-automate-series</link><description><![CDATA[The start of a series of blog posts, all related to one of my yearly themes: automation.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/announcing-the-automate-series</guid><pubDate>Fri, 04 Jan 2019 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>I listen to a lot of podcasts every day, e.g. when doing chores or commuting. One of the shows I particulary enjoy recently is <a href="https://www.relay.fm/cortex">Cortex</a>, where Myke Hurley and CGP Grey talk about their ways to be productive.
Every year, they define yearly themes, which are a bit like new year’s resolutions, but instead of hard targets, they are more like directions in which you want to go.
I highly recommend listening to the <a href="https://www.relay.fm/cortex/79">Yearly Themes Episode of 2019</a>.</p>
<!-- more -->
<p>Since this is such a good idea, I naturally defined my own yearly themes for 2019. One of those is called <em>Year of Automation</em>, which to me means automating everything I can in my personal as well as professional life.</p>
<p>I will try and documente all bigger efforts (you don’t want to see all my email rules) in this blog series.</p>
]]></content:encoded></item><item><title>I18n in Node.js</title><link>https://arne.me/blog/i18n-in-node-js</link><description><![CDATA[Node.js uses ICU (International Components for Unicode) for i18n and only includes English data by default.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/i18n-in-node-js</guid><pubDate>Thu, 15 Nov 2018 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Yesterday we added unit tests for a component that uses the <a href="https://web.archive.org/web/20200921164547/https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl">Intl</a> API to a frontend project.
Everything worked flawlessly on our local machines, but it failed on CI.
The failing tests showed a number formatted in English instead of the expected German format.</p>
<!-- more -->
<p>We use Jest for running the tests, which is running on Node.js. As we found out in the process, Node.js uses ICU (International Components for Unicode) for its i18n support and, if not otherwise specified, only contains English ICU data.</p>
<p>The reason it worked on our local machines was, that they had <code>node</code> installed with full ICU data (Homebrew, for example, installs all by default), but the Docker image we used on CI didn’t.</p>
<h2><a inert href="#solution" aria-hidden="true" class="anchor" id="solution"></a>Solution</h2>
<p>To have full ICU data available, you can either</p>
<ul>
<li>compile the node binary with the flag <code>--with-intl=full-icu</code> (see <a href="https://nodejs.org/api/intl.html#intl_options_for_building_node_js">Options for building Node.js</a>), or</li>
<li>install the <a href="https://www.npmjs.com/package/full-icu">full-icu</a> package and use an environment variable (<code>NODE_ICU_DATA=node_modules/full-icu</code>) or a command-line flag (<code>--icu-data-dir=node_modules/full-icu</code>) to tell node where to find the data (see <a href="https://nodejs.org/api/intl.html#intl_providing_icu_data_at_runtime">Providing ICU data at runtime</a>).</li>
</ul>
<p>Since it was only failing on CI, we updated the build script to install the <code>full-icu</code> package and export the environment variable.
Also, in case you wondered, all major browsers support the Intl-API, according to <a href="https://caniuse.com/#feat=internationalization">Can I use</a>.</p>
<h2><a inert href="#conclusion" aria-hidden="true" class="anchor" id="conclusion"></a>Conclusion</h2>
<p>If you use any i18n features in your JavaScript application, make sure to include a basic test to be sure all locales you need are supported.</p>
]]></content:encoded></item><item><title>On Hexagonal Architecture</title><link>https://arne.me/blog/on-hexagonal-architecture</link><description><![CDATA[Why a hexagonal architecture can be better than traditional MVC-style architecture, with a real-world example.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/on-hexagonal-architecture</guid><pubDate>Sun, 02 Sep 2018 00:00:00 +0000</pubDate><content:encoded><![CDATA[<blockquote>
<p>A good architect maximises the number of decisions not made</p>
<p>— Robert C. Martin in Clean Architecture</p>
</blockquote>
<p>Most web services I worked with use a MVC-style architecture, with a <code>handlers</code> package and, if at all, a <code>repository</code> package. While this may be great for small services, the <code>handlers</code> package introduces a big problem: It mixes transport logic with business logic. This makes refactoring hard (imagine switching your HTTP framework) and therefore forces you to make decisions about these kind of things before even starting the project. So when I started a new project recently, I decided to use the hexagonal architecture (aka <em>Ports and Adapters</em>) and so far I’m really happy.</p>
<!-- more -->
<h2><a inert href="#what-i-like-about-it" aria-hidden="true" class="anchor" id="what-i-like-about-it"></a>What I like about it</h2>
<p>Here’s what I like about it:</p>
<h3><a inert href="#all-dependencies-point-inward" aria-hidden="true" class="anchor" id="all-dependencies-point-inward"></a>All dependencies point inward</h3>
<p>All outer layers like transport, storage or logging depend on the business logic, but never the other way around. The business logic is agnostic of any other layer, it doesn’t care, how it’s served or how data is stored, it’s pure code. This makes changing it super simple.</p>
<h3><a inert href="#you-can-defer-decisions" aria-hidden="true" class="anchor" id="you-can-defer-decisions"></a>You can defer decisions</h3>
<p>You can defer many decisions about technologies used until you really need them. You could, for example, start out with an <code>inmem</code> package for storage and only decide which database to use when you really need persistence.</p>
<h2><a inert href="#refactorings-are-simple" aria-hidden="true" class="anchor" id="refactorings-are-simple"></a>Refactorings are simple</h2>
<p>Since everything is contained in it’s domain, refactoring the transport package, for example, is refactoring only transport code. There is pure separation of concerns and everything has a clear place.</p>
<h3><a inert href="#testing-business-logic-is-simple" aria-hidden="true" class="anchor" id="testing-business-logic-is-simple"></a>Testing business logic is simple</h3>
<p>Since you only have pure code without layer dependencies, you can easily inject an <code>inmem</code> package as storage for example. No need for mocking complex database structs, which cost time.</p>
<h3><a inert href="#why-a-hexagon" aria-hidden="true" class="anchor" id="why-a-hexagon"></a>Why a hexagon</h3>
<p>It actually doesn’t matter how many sides the shape has. The point is, that there are many. The center represents the business logic and every side represents a port into or out of our application (that’s why it’s also called <em>Ports and Adapters</em>).</p>
<h2><a inert href="#a-real-world-example" aria-hidden="true" class="anchor" id="a-real-world-example"></a>A real-world example</h2>
<p>For this post let’s assume that we’re building an API to manage an inventory of some sort – it’s still similar to the application I’m building. You should be able to list all items in the inventory and logged-in users should have basic CRUD access.</p>
<p>To set things up, I created these packages:</p>
<ul>
<li><code>user</code>: Domain logic for user management (login, logout, validate)</li>
<li><code>inventory</code>: Domain logic for inventory (list, create, read, update, delete)</li>
<li><code>inmem</code>: In-memory storage for user and inventory</li>
<li><code>http</code>: HTTP transport logic, does de-/encoding and request handling</li>
</ul>
<p>The <code>user</code> and <code>inventory</code> packages define an interface for the storage struct. This way we can inject any struct that implements that interface and keep the separation of concerns.</p>
<p>Here’s how our <code>main.go</code> would look like, simplified:</p>
<pre class="syntax-highlighting"><code class="language-go"><span class="source go"><span class="keyword control go">package</span> main

<span class="meta import go"><span class="keyword control import go">import</span> <span class="meta group go"><span class="punctuation definition group begin go">(</span></span></span><span class="meta import go"><span class="meta group go">
	<span class="string quoted double go"><span class="punctuation definition string begin go">&quot;</span>net/http<span class="punctuation definition string end go">&quot;</span></span>

	transport <span class="string quoted double go"><span class="punctuation definition string begin go">&quot;</span>github.com/bahlo/inventory/http<span class="punctuation definition string end go">&quot;</span></span>
	<span class="string quoted double go"><span class="punctuation definition string begin go">&quot;</span>github.com/bahlo/inventory/inmem<span class="punctuation definition string end go">&quot;</span></span>
	<span class="string quoted double go"><span class="punctuation definition string begin go">&quot;</span>github.com/bahlo/inventory/inventory<span class="punctuation definition string end go">&quot;</span></span>
	<span class="string quoted double go"><span class="punctuation definition string begin go">&quot;</span>github.com/bahlo/inventory/user<span class="punctuation definition string end go">&quot;</span></span>
</span><span class="meta group go"><span class="punctuation definition group end go">)</span></span></span>

<span class="meta function declaration go"><span class="storage type go">func</span> <span class="entity name function go">main</span></span><span class="meta function parameters go"><span class="meta group go"><span class="punctuation definition group begin go">(</span></span></span><span class="meta function parameters go"></span><span class="meta function parameters go"></span><span class="meta function parameters go"><span class="meta group go"><span class="punctuation definition group end go">)</span></span></span><span class="meta function go"> </span><span class="meta function go"><span class="meta block go"><span class="punctuation definition block begin go">{</span></span></span><span class="meta function go"><span class="meta block go">
	<span class="meta initialization short go"><span class="variable other go">userStorage</span> <span class="keyword operator initialize go">:=</span></span> inmem<span class="meta function-call method go"><span class="punctuation accessor go">.</span><span class="variable function go">NewUserStorage</span><span class="meta group go"><span class="punctuation definition group begin go">(</span></span></span><span class="meta function-call method go"><span class="meta group go"><span class="punctuation definition group end go">)</span></span></span>
	<span class="meta initialization short go"><span class="variable other go">userService</span> <span class="keyword operator initialize go">:=</span></span> user<span class="meta function-call method go"><span class="punctuation accessor go">.</span><span class="variable function go">NewService</span><span class="meta group go"><span class="punctuation definition group begin go">(</span></span></span><span class="meta function-call method go"><span class="meta group go">userStorage</span><span class="meta group go"><span class="punctuation definition group end go">)</span></span></span>
	http<span class="meta function-call method go"><span class="punctuation accessor go">.</span><span class="variable function go">Handle</span><span class="meta group go"><span class="punctuation definition group begin go">(</span></span></span><span class="meta function-call method go"><span class="meta group go"><span class="string quoted double go"><span class="punctuation definition string begin go">&quot;</span>/v1/user<span class="punctuation definition string end go">&quot;</span></span><span class="punctuation separator go">,</span> transport<span class="meta function-call method go"><span class="punctuation accessor go">.</span><span class="variable function go">UserHandler</span><span class="meta group go"><span class="punctuation definition group begin go">(</span></span></span><span class="meta function-call method go"><span class="meta group go">userService</span><span class="meta group go"><span class="punctuation definition group end go">)</span></span></span></span><span class="meta group go"><span class="punctuation definition group end go">)</span></span></span>

	<span class="meta initialization short go"><span class="variable other go">inventoryStorage</span> <span class="keyword operator initialize go">:=</span></span> inmem<span class="meta function-call method go"><span class="punctuation accessor go">.</span><span class="variable function go">NewInventoryStorage</span><span class="meta group go"><span class="punctuation definition group begin go">(</span></span></span><span class="meta function-call method go"><span class="meta group go"><span class="punctuation definition group end go">)</span></span></span>
	<span class="meta initialization short go"><span class="variable other go">inventoryService</span> <span class="keyword operator initialize go">:=</span></span> inventory<span class="meta function-call method go"><span class="punctuation accessor go">.</span><span class="variable function go">NewService</span><span class="meta group go"><span class="punctuation definition group begin go">(</span></span></span><span class="meta function-call method go"><span class="meta group go">inventoryStorage</span><span class="meta group go"><span class="punctuation definition group end go">)</span></span></span>
	http<span class="meta function-call method go"><span class="punctuation accessor go">.</span><span class="variable function go">Handle</span><span class="meta group go"><span class="punctuation definition group begin go">(</span></span></span><span class="meta function-call method go"><span class="meta group go"><span class="string quoted double go"><span class="punctuation definition string begin go">&quot;</span>/v1/inventory<span class="punctuation definition string end go">&quot;</span></span><span class="punctuation separator go">,</span> transport<span class="meta function-call method go"><span class="punctuation accessor go">.</span><span class="variable function go">InventoryHandler</span><span class="meta group go"><span class="punctuation definition group begin go">(</span></span></span><span class="meta function-call method go"><span class="meta group go">inventoryService</span><span class="meta group go"><span class="punctuation definition group end go">)</span></span></span></span><span class="meta group go"><span class="punctuation definition group end go">)</span></span></span>

	http<span class="meta function-call method go"><span class="punctuation accessor go">.</span><span class="variable function go">ListenAndServe</span><span class="meta group go"><span class="punctuation definition group begin go">(</span></span></span><span class="meta function-call method go"><span class="meta group go"><span class="string quoted double go"><span class="punctuation definition string begin go">&quot;</span>:8080<span class="punctuation definition string end go">&quot;</span></span><span class="punctuation separator go">,</span> <span class="constant language go">nil</span></span><span class="meta group go"><span class="punctuation definition group end go">)</span></span></span>
</span></span><span class="meta function go"><span class="meta block go"><span class="punctuation definition block end go">}</span></span></span>
</span></code></pre>
<h2><a inert href="#problems" aria-hidden="true" class="anchor" id="problems"></a>Problems</h2>
<p>In real-world applications, we always have to make compromises and fight for clear separation of concerns. This is a list of problems I ran into while building the application.</p>
<h3><a inert href="#authentication" aria-hidden="true" class="anchor" id="authentication"></a>Authentication</h3>
<p>Let’s say we want to implement a token-based authentication and we need to protect some routes of the <code>inventory</code> service. Where should we get the token-validation function from? Getting the user service via our <code>NewService</code> constructor would result in unnecessary dependencies.
What I ended up doing was a <code>http.Guard(userService)</code> function, which returned a http middleware (<code>func (next http.Handler) http.Handler</code>) which parses the token and validates with <code>userService.UserForToken</code>. The middleware is then passed into the <code>transport.InventoryHandler</code> and wrapped around methods that needed protection. This way, there are no new dependencies.</p>
<h3><a inert href="#model-decorators" aria-hidden="true" class="anchor" id="model-decorators"></a>Model decorators</h3>
<p>Another problem are Go tags (<a href="https://golang.org/ref/spec#Tag">read up</a>). Let’s say we need some for transport (e.g. <code>json:&quot;id&quot;</code>) and database (e.g. <code>db:&quot;first_name&quot;</code>), but they’re attached to the model, which lies in the domain logic. A fix would be having the model structs duplicated in the other packages with the needed tags, but this introduces a lot of duplicate code and unnecessary complexity, so I decided to leave it as-is right now until I found a better solution.</p>
<h2><a inert href="#conclusion" aria-hidden="true" class="anchor" id="conclusion"></a>Conclusion</h2>
<p>I’m really happy with my application and I don’t think I’ll go back to MVC-style applications any time soon.
I encourage you to try and build a service using a hexagonal architecture and share your experience – doesn’t have to be Go. If you want to read more on hexagonal architecture, I recommend checking out <a href="https://gokit.io/">go-kit</a>. There is also a GopherCon 2018 talk by Matt King, here is <a href="https://about.sourcegraph.com/go/gophercon-2018-how-do-you-structure-your-go-apps">the script</a> until the videos are up. Also if you have comments or suggestions, <a href="/contact">hit me up</a> 🤙.</p>
]]></content:encoded></item><item><title>.vim-project</title><link>https://arne.me/blog/vim-projects</link><description><![CDATA[This post explains how to get project-specific Vim settings.]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/vim-projects</guid><pubDate>Thu, 14 Aug 2014 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>If you’re using Vim, you know that feel (if you aren’t, you can skip this
article): Everytime you open a project, you toggle
<a href="https://github.com/scrooloose/nerdtree">NERDTREE</a> and
<a href="https://github.com/majutsushi/tagbar">Tagbar</a> (or similar). But you don’t
want to put that in your <code>.vimrc</code>, because then they’d open every time, even
when you just want to quickly edit a file.</p>
<!-- more -->
<h2><a inert href="#but-wait" aria-hidden="true" class="anchor" id="but-wait"></a>But wait</h2>
<p>Sublime Text does have a <code>&lt;name&gt;.sublime-project</code>, which holds configuration
for the curent project. Why doesn’t Vim has something like that?</p>
<h2><a inert href="#just-do-it-yourself" aria-hidden="true" class="anchor" id="just-do-it-yourself"></a>Just do it yourself</h2>
<p>It’s not hard, paste the following lines anywhere in your <code>.vimrc</code>:</p>
<pre class="syntax-highlighting"><code class="language-vim"><span class="text plain">if filereadable(expand(&quot;.vim-project&quot;))
  source .vim-project
endif
</span></code></pre>
<p>You can now create a <code>.vim-project</code>-file anywhere you want and just write
stuff in it like in your <code>.vimrc</code>, this is one of mine:</p>
<pre class="syntax-highlighting"><code class="language-vim"><span class="text plain">autocmd VimEnter * NERDTree
autocmd VimEnter * Tagbar
</span></code></pre>
<p><em>Note: You may want to close Vim automatically after all windows but NERDTree
are closed, see the <a href="https://github.com/scrooloose/nerdtree#faq">FAQs</a> for
that.</em></p>
]]></content:encoded></item><item><title>Deploy Octopress, the right way</title><link>https://arne.me/blog/deploy-octopress-via-git</link><description><![CDATA[This post explains how to deploy an Octopress page via Git]]></description><author>Arne Bahlo</author><guid>https://arne.me/blog/deploy-octopress-via-git</guid><pubDate>Sat, 28 Sep 2013 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>You’re deploying your Octopress blog via Git to GitHub Pages (or Heroku), but
you don’t like Heroku and GitHub Pages are refreshing too slow and you really
don’t want to use Rsync, do you?</p>
<p>Just deploy to your own server via Git.</p>
<!-- more -->
<h2><a inert href="#step-one" aria-hidden="true" class="anchor" id="step-one"></a>Step one</h2>
<p>If you haven’t already done this, run <code>rake setup_github_pages</code> in your local Octopress repository and give it any valid GitHub url.</p>
<h2><a inert href="#step-two" aria-hidden="true" class="anchor" id="step-two"></a>Step two</h2>
<p>Set up a bare Git repository on your server to push to</p>
<pre class="syntax-highlighting"><code class="language-sh"><span class="source shell bash"><span class="meta function-call shell"><span class="variable function shell">mkdir</span></span><span class="meta function-call arguments shell"><span class="variable parameter option shell"><span class="punctuation definition parameter shell"> -</span>p</span> <span class="meta group expansion tilde"><span class="variable language tilde shell">~</span></span>/projects/octopress.git</span>
<span class="meta function-call shell"><span class="support function cd shell">cd</span></span><span class="meta function-call arguments shell"> <span class="meta group expansion tilde"><span class="variable language tilde shell">~</span></span>/projects/octopress.git</span>
<span class="meta function-call shell"><span class="variable function shell">git</span></span><span class="meta function-call arguments shell"> init<span class="variable parameter option shell"><span class="punctuation definition parameter shell"> --</span>bare <span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> Related: http://git-scm.com/book/ch4-2.html</span><span class="comment line number-sign shell">
</span></span></span></span></code></pre>
<h3><a inert href="#step-two-and-a-half" aria-hidden="true" class="anchor" id="step-two-and-a-half"></a>Step two and a half</h3>
<p>Create a <code>post-receive</code> on your server in <code>~/projects/octopress.git/hooks/</code> and give yourself execute permissions with <code>chmod u+x post-receive</code>.</p>
<pre class="syntax-highlighting"><code class="language-sh"><span class="source shell bash"><span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell">!/bin/bash</span><span class="comment line number-sign shell">
</span><span class="comment line number-sign shell"><span class="punctuation definition comment begin shell">#</span></span><span class="comment line number-sign shell"> You already know that you&#39;ll have to change the paths</span><span class="comment line number-sign shell">
</span><span class="meta function-call shell"><span class="variable function shell">git</span></span><span class="meta function-call arguments shell"><span class="variable parameter option shell"><span class="punctuation definition parameter shell"> --</span>work-tree</span><span class="keyword operator assignment option shell">=</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">HOME</span></span>/html/octopress<span class="variable parameter option shell"><span class="punctuation definition parameter shell"> --</span>git-dir</span><span class="keyword operator assignment option shell">=</span><span class="meta group expansion parameter shell"><span class="punctuation definition variable shell">$</span><span class="variable other readwrite shell">HOME</span></span>/projects/octopress.git checkout<span class="variable parameter option shell"><span class="punctuation definition parameter shell"> -</span>f</span></span>
</span></code></pre>
<h2><a inert href="#step-three" aria-hidden="true" class="anchor" id="step-three"></a>Step three</h2>
<p>Back in your local Octopress installation.</p>
<pre class="syntax-highlighting"><code><span class="text plain">cd _deploy
git remote set-url origin user@domain.com:projects/octopress.git
git pull -u origin master # Where master is the branch you set in your Rakefile as deploy_branch, defaults to master
</span></code></pre>
<p>And you’re set. If you run into any trouble, let me know.</p>
]]></content:encoded></item></channel></rss>