<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Opensource Odyssey</title><description>Follow my journey through the vast world of opensource tech: Focus on NixOS, Doom Emacs and TypeScript web development.</description><link>https://opensource-odyssey.net/</link><language>en-us</language><item><title>Building a timeless tech stack</title><link>https://opensource-odyssey.net/posts/building-a-timeless-tech-stack/</link><guid isPermaLink="true">https://opensource-odyssey.net/posts/building-a-timeless-tech-stack/</guid><description>If I&apos;m going to spend time on it, might as well pick something that will pay off in the long run.</description><pubDate>Tue, 16 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When learning technology, I&apos;ve tried to seek out projects that will be around for a &lt;em&gt;long&lt;/em&gt; time.&lt;/p&gt;
&lt;h2&gt;The origin story {#the-origin-story}&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;I used to be a Windows/MacOS user like you, but then I took an arrow to the knee.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Windows was the default. What I - and probably you - grew up with. Not a concious choice, just a matter of fact.&lt;/p&gt;
&lt;p&gt;Then I &quot;emanzipated&quot; myself and &quot;graduated&quot; to MacOS. In the early 2010s, you were some kind of hipster if you used Apple. But the main selling point was the &quot;it just works&quot;. However, after some years of being an Apple apostel, I was burnt. My MacBook Pro broke and my frustration with Apple grew.&lt;/p&gt;
&lt;p&gt;So I looked elsewhere and my gaze fell onto GNU/Linux. I installed &lt;a href=&quot;https://www.linuxmint.com/&quot;&gt;Linux Mint&lt;/a&gt; as my first distro on an older machine. Tough daunting, it wasn&apos;t so bad. And paired really well with my efforts of learning how to code and how computers work.&lt;/p&gt;
&lt;h2&gt;Proprietary software vs. open source software {#proprietary-software-vs-dot-open-source-software}&lt;/h2&gt;
&lt;p&gt;What really appealed to me with Linux was the open source nature.&lt;/p&gt;
&lt;p&gt;This difference - that the codebase of the Linux Kernel is open source, while Windows and MacOS are not - became a litmus test for me. Why even bother with software, that I can&apos;t control? Am I an end-consumer that doesn&apos;t understand what is happening, or am I a sovereign individual?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you don&apos;t control your tech-stack, your tech-stack controls you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At first I was petrified: Which software do I use? But quickly found out that there is a whole ice-berg outside of the shiny, official app stores by the big companies. The landscape of open source is almost limitless. &lt;em&gt;Because&lt;/em&gt; it is open, and there are no barriers to entry.&lt;/p&gt;
&lt;p&gt;Moreover, open source software tends to be more robust and secure than proprietary software. Read more arguments in the amazing booklet &quot;&lt;a href=&quot;https://en.wikipedia.org/wiki/The_Cathedral_and_the_Bazaar&quot;&gt;The cathedral and the bazaar&lt;/a&gt;&quot;.&lt;/p&gt;
&lt;p&gt;Open source allows you to tinker and customize everything to your preferences. It is an immense increase in personal sovereignty.&lt;/p&gt;
&lt;p&gt;Open source is hard to kill, because it only takes one lunatic to host the code.&lt;/p&gt;
&lt;p&gt;In times where advertisements are everywhere and your data is the digital oil for 3rd parties, open source grants transparency. An open source codebase can be audited as to what is acutally going on behind the scenes.&lt;/p&gt;
&lt;h2&gt;Hard mode: Open source is not enough {#hard-mode-open-source-is-not-enough}&lt;/h2&gt;
&lt;p&gt;While open source has become a buzz word, it&apos;s important to know that not all open source is the same. Famously, &lt;a href=&quot;https://stallman.org/&quot;&gt;Richard Stallman&lt;/a&gt; made the distinction between plain open source and &lt;a href=&quot;https://www.gnu.org/philosophy/floss-and-foss.en.html&quot;&gt;&lt;em&gt;free libre&lt;/em&gt; open source software&lt;/a&gt; (free as in freedom), aka FLOSS.&lt;/p&gt;
&lt;p&gt;The emphazise with FLOSS software is the sovereignty of the user. This is the antithesis of the contemporary dominance of Silicon Valley and the creeping influence of Chinese tech companies.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Why yes, of course I FLOSS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Running completly on FLOSS is significantly harder than just relying on FOSS. Some hardware drivers are proprietary, and if you want to avoid those, you will be significantly limited in the hardware you can use. Personally, I&apos;m not religious about using &lt;em&gt;only FLOSS&lt;/em&gt;. But if I can help it, FLOSS it is.&lt;/p&gt;
&lt;h2&gt;The tech stacks I chose {#the-tech-stacks-i-chose}&lt;/h2&gt;
&lt;p&gt;But enough with the abstract words. To me the tech that fulfills these requirements is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The linux kernel&lt;/strong&gt;: Thousands of contributors, has been around since the 90s, on countless devices (including servers and mobile)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The GNU utils&lt;/strong&gt;: Learning these empowers you to use &lt;em&gt;any UNIX system&lt;/em&gt; - super powerful and truly timeless&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Emacs editor&lt;/strong&gt;: Invented by the Stallman himself, has been around since 70s, with a hardened community of power users&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vim motions&lt;/strong&gt;: Vi has been around since the 70s and has a mode-based text-editing experience. With vim motions a text document becomes a different playing field. If this is new to you, then &lt;a href=&quot;https://www.youtube.com/watch?v=qZO9A5F6BZs&quot;&gt;go watch this gem&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nix&lt;/strong&gt;: &lt;code&gt;nix&lt;/code&gt; is many things, amongst others it is a functional programming language that allows you to specify packages and their configurations for your systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are some examples of technologies that I invest my time in. Technologies that I learn with joy, because I know that learning these things will serve me for the rest of my life.&lt;/p&gt;
&lt;p&gt;Yes, the road, especially initially, is hard. But it gets easier. Things actually make sense after a while.&lt;/p&gt;
&lt;h2&gt;Conclusion {#conclusion}&lt;/h2&gt;
&lt;p&gt;When you are at the cross-road to learn a new technology, ask yourself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is it open source?&lt;/li&gt;
&lt;li&gt;Will it be around for a while?&lt;/li&gt;
&lt;li&gt;Is it worth the time invest?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And have the courage to look elsewhere if you answered these questions with &quot;no&quot;.&lt;/p&gt;
&lt;p&gt;This simple principle will pay dividends as your knowledge grows.&lt;/p&gt;
</content:encoded></item><item><title>hello world</title><link>https://opensource-odyssey.net/posts/hello-world/</link><guid isPermaLink="true">https://opensource-odyssey.net/posts/hello-world/</guid><description>Welcome to the world of Opensource Odyssey.</description><pubDate>Mon, 15 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My odyssey into the seas of open source software started a few years ago. Now, I want to use this site as an outlet to document this journey.&lt;/p&gt;
&lt;h2&gt;What to expect in this blog {#what-to-expect-in-this-blog}&lt;/h2&gt;
&lt;p&gt;This blog is for computer adventurers that like to take ownership of their tech stack.&lt;/p&gt;
&lt;p&gt;Some things I try are pretty &quot;niche&quot;. Like, using Emacs, using Linux (NixOS, to be precise) and having a keyboard-oriented productivity flow. It&apos;s not always easy to find resources on these topics. For myself, some of the best pointers I have found, have been on sites like this one. In other words, enthusiasts documenting their trials and errors.&lt;/p&gt;
&lt;h2&gt;An overview of what I&apos;m enthusiastic about {#an-overview-of-what-i-m-enthusiastic-about}&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Linux (namely &lt;a href=&quot;https://nixos.org/&quot;&gt;NixOS&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/doomemacs/doomemacs&quot;&gt;Doom Emacs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Tiling window managers (currently using &lt;a href=&quot;https://hyprland.org/&quot;&gt;hyprland&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Shortcuts and keyboard-focused workflows&lt;/li&gt;
&lt;li&gt;Coding (mostly typescript) for web-apps and CLI tools&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My posts will cover these topics (and maybe more).&lt;/p&gt;
&lt;h2&gt;Join the odyssey {#join-the-odyssey}&lt;/h2&gt;
&lt;p&gt;This site offers RSS feeds - for all posts &amp;amp; for individual tags. You can subscribe to the RSS feed with the URL &lt;code&gt;https://opensource-odyssey.net/index.xml&lt;/code&gt; or navigate to a &lt;a href=&quot;/tags/&quot;&gt;tag&lt;/a&gt; and click the RSS icon next to it. This way you can be notified about new content and select what you are interested in.&lt;/p&gt;
&lt;p&gt;Expect a true epic. An epic to gain mastery over technology and use it to &lt;em&gt;your benefit&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for checking out this site! Enjoy the content and feel free to share!&lt;/p&gt;
</content:encoded></item><item><title>My Linux Odyssey</title><link>https://opensource-odyssey.net/posts/my-linux-odyssey/</link><guid isPermaLink="true">https://opensource-odyssey.net/posts/my-linux-odyssey/</guid><description>From 0 to Linux hero - read about my journey with the best OS there is.</description><pubDate>Sat, 27 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div class=&quot;ox-hugo-toc toc&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;heading&quot;&amp;gt;Table of Contents&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#phase-i-discovering-linux&quot;&gt;Phase I: Discovering Linux&lt;/a&gt;
- &lt;a href=&quot;#abandoning-macos&quot;&gt;Abandoning MacOS&lt;/a&gt;
- &lt;a href=&quot;#the-first-linux-desktop&quot;&gt;The first Linux desktop&lt;/a&gt;
- &lt;a href=&quot;#distro-hopping&quot;&gt;Distro hopping&lt;/a&gt;
- &lt;a href=&quot;#from-debian-base-to-manjaro&quot;&gt;From Debian-base to Manjaro&lt;/a&gt;
- &lt;a href=&quot;#gaming-on-linux&quot;&gt;Gaming on Linux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#phase-ii-tiling-windows-and-dotfiles&quot;&gt;Phase II: Tiling windows and dotfiles&lt;/a&gt;
- &lt;a href=&quot;#tiling-window-managers&quot;&gt;Tiling window managers&lt;/a&gt;
- &lt;a href=&quot;#qtile&quot;&gt;QTile&lt;/a&gt;
- &lt;a href=&quot;#off-the-beaten-path-arco-linux&quot;&gt;Off the beaten path: Arco Linux&lt;/a&gt;
- &lt;a href=&quot;#establishing-dotfiles&quot;&gt;Establishing dotfiles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#phase-iii-mastering-arch&quot;&gt;Phase III: Mastering Arch&lt;/a&gt;
- &lt;a href=&quot;#daring-arch&quot;&gt;Daring Arch&lt;/a&gt;
- &lt;a href=&quot;#learning-how-to-solve-problems&quot;&gt;Learning how to solve problems&lt;/a&gt;
- &lt;a href=&quot;#versioning-packages&quot;&gt;Versioning packages&lt;/a&gt;
- &lt;a href=&quot;#managing-two-systems&quot;&gt;Managing two systems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#phase-iv-declaring-reproducibility&quot;&gt;Phase IV: Declaring reproducibility&lt;/a&gt;
- &lt;a href=&quot;#the-final-distro-hop-nixos&quot;&gt;The final distro hop: NixOS&lt;/a&gt;
- &lt;a href=&quot;#settling-on-nixos&quot;&gt;Settling on NixOS&lt;/a&gt;
- &lt;a href=&quot;#switching-to-flakes&quot;&gt;Switching to Flakes&lt;/a&gt;
- &lt;a href=&quot;#killer-feature-generations&quot;&gt;Killer feature: Generations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#phase-v-nix-verstehen&quot;&gt;Phase V: Nix verstehen&lt;/a&gt;
- &lt;a href=&quot;#flexing-the-nix-muscles&quot;&gt;Flexing the nix muscles&lt;/a&gt;
- &lt;a href=&quot;#mvp-vimjoyer&quot;&gt;MVP: Vimjoyer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#epilog&quot;&gt;Epilog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;
&amp;lt;!--endtoc--&amp;gt;&lt;/p&gt;
&lt;p&gt;OpenSource, for me, really began with Linux.&lt;/p&gt;
&lt;p&gt;This is a long post. But then again, I am compressing 5 years of adventures.&lt;/p&gt;
&lt;p&gt;Just sit back, get your tea and enjoy.&lt;/p&gt;
&lt;h2&gt;Phase I: Discovering Linux {#phase-i-discovering-linux}&lt;/h2&gt;
&lt;h4&gt;Abandoning MacOS {#abandoning-macos}&lt;/h4&gt;
&lt;p&gt;Before Linux I used MacOS (and loved Apple). But my beloved MacBook broke - wouldn&apos;t charge anymore, not even on power supply. My attempts to repair left me a few hundred dollars lighter and with a broken MacBook. Frustration grew, a new solution was needed.&lt;/p&gt;
&lt;p&gt;At the same time I started to code. One of the early lessons for any becoming coder is the &lt;a href=&quot;https://en.wikipedia.org/wiki/Unix_philosophy&quot;&gt;UNIX philosophy&lt;/a&gt;. In short, building a program that does one thing, but really well, and that is modular to be used in combination with other such programs. The approach appealed to me and with nothing to lose, I installed &lt;a href=&quot;https://www.linuxmint.com/&quot;&gt;Linux Mint&lt;/a&gt; on an older laptop.&lt;/p&gt;
&lt;h4&gt;The first Linux desktop {#the-first-linux-desktop}&lt;/h4&gt;
&lt;p&gt;&quot;Flashing a boot-able usb-drive&quot; sounds fancy, but it&apos;s not hard to do. With a simple Internet search anybody can do it. Downloading an ISO file also requires no skill. The installation process was plain simple and the default desktop charmed me. I made the decision then and there to keep walking this path.&lt;/p&gt;
&lt;p&gt;Using Linux Mint as a daily driver took any angst and reservation. It really wasn&apos;t &lt;em&gt;that&lt;/em&gt; complicated. Navigating the desktop felt intuitive, the user interface was beautiful and the performance was great. Doing all basic things like browsing and editing code files with &lt;a href=&quot;https://vscodium.com/&quot;&gt;VSCodium&lt;/a&gt; were a breeze.&lt;/p&gt;
&lt;p&gt;Soon after familiarizing myself with the basics and getting my groove, I looked to the horizon: Distro hopping.&lt;/p&gt;
&lt;h4&gt;Distro hopping {#distro-hopping}&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Distro hopping&lt;/strong&gt; means installing another linux distro in hopes for a better computing experience.&lt;/p&gt;
&lt;p&gt;Because... &lt;em&gt;there are &lt;a href=&quot;https://distrowatch.com/&quot;&gt;a lot of distros&lt;/a&gt; out there&lt;/em&gt;. How do you know you already found &lt;em&gt;the best&lt;/em&gt; distro for you? &lt;a href=&quot;https://ubuntu.com/desktop/flavours&quot;&gt;Ubuntu with all it&apos;s flavors alone&lt;/a&gt; can be smothering.&lt;/p&gt;
&lt;p&gt;But I tried them all anyway. Was I more of a KDE Plasma or Gnome type of person? Or maybe Budgie? Cinnamon? &lt;strong&gt;I learned that the desktop environment is something different than the distro&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A desktop is basically the look and feel of your computer. How the windows are decorated, how the cursor looks, the opening and closing animations, as well as some programs like a file finder, password key-chain etc. There is &lt;em&gt;a ton&lt;/em&gt; of variants out there. All open source and free to use.&lt;/p&gt;
&lt;p&gt;Keep the distinction in mind: &lt;strong&gt;A distro or distribution is a complete package of software packages and desktop environment&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I realized what I like is actually &quot;making the computer mine&quot;. This allows me to have shortcuts to quickly open certain programs and be more productive.&lt;/p&gt;
&lt;p&gt;Distro hopping is kind of a phase as you grow as a Linux user. But it&apos;s not all futile. It broadens your horizon and shows various approaches to solve the same issue.&lt;/p&gt;
&lt;h4&gt;From Debian-base to Manjaro {#from-debian-base-to-manjaro}&lt;/h4&gt;
&lt;p&gt;As I was distro hopping, &quot;package managers&quot; started to peek my interest.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;package manager&lt;/strong&gt; is a program that manages which software is installed on your machine and with what version. The package manger handles installation of new software and system updates. On Ubuntu and Debian the package manager is &lt;code&gt;apt&lt;/code&gt;, on Arch it is &lt;code&gt;pacman&lt;/code&gt; etc.&lt;/p&gt;
&lt;p&gt;Apparently with the arch package manager you could get access to something called &quot;the &lt;a href=&quot;https://aur.archlinux.org/&quot;&gt;A.U.R.&lt;/a&gt;&quot;. Apparently this would make life easier. And &lt;a href=&quot;https://manjaro.org/&quot;&gt;Manjaro&lt;/a&gt; delivered on that promise. It came with everything built in, supported Gnome and Plasma as desktop environments, &lt;em&gt;and&lt;/em&gt; could enable the AUR.&lt;/p&gt;
&lt;p&gt;During those times I gradually abandoned the graphic user interfaces (GUIs) and adopted the command line (CLI) more and more. First only to update the system, but then also to navigate the directory tree and tweaking with copy+paste snippets from the Internet.&lt;/p&gt;
&lt;h4&gt;Gaming on Linux {#gaming-on-linux}&lt;/h4&gt;
&lt;p&gt;Manjaro also had a great reputation for gaming support. Gaming was a thing I was still missing a bit. Granted, on MacOS the gaming department is also fairly limited. The &lt;a href=&quot;https://wiki.manjaro.org/index.php/Manjaro_Hardware_Detection_Overview&quot;&gt;Manjaro hardware detection&lt;/a&gt; tool (&lt;code&gt;mhwd&lt;/code&gt;) would automatically install any NVIDIA driver or other for you. This worked great! Linux computing was getting easier.&lt;/p&gt;
&lt;p&gt;With &lt;a href=&quot;https://lutris.net/&quot;&gt;Lutris&lt;/a&gt;, I was even able to play League of Legends on the older computer!&lt;/p&gt;
&lt;p&gt;So gaming was slowly coming around. Nowadays this is a non-issue. If &quot;but I can game on Linux&quot; is what&apos;s holding you back to switch to Linux, be assured: &lt;a href=&quot;https://www.protondb.com/&quot;&gt;Almost any steam game now runs out of the box&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Valve, the company behind Steam, has since come out with the &lt;a href=&quot;https://store.steampowered.com/steamdeck&quot;&gt;Steam Deck&lt;/a&gt; which runs on the Arch package manager.&lt;/p&gt;
&lt;h2&gt;Phase II: Tiling windows and dotfiles {#phase-ii-tiling-windows-and-dotfiles}&lt;/h2&gt;
&lt;h4&gt;Tiling window managers {#tiling-window-managers}&lt;/h4&gt;
&lt;p&gt;If you don&apos;t know what that term means, good for you! Jokes aside, you know how a window opens when you double-click an application? Have you ever asked yourself how the size and position of that window is determined? Well, tiling window managers manage &lt;em&gt;that&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Why would you want to worry about that? If you are a true keyboard warrior, your entire worth is measured in how little you have to touch a mouse to use a computer. Allegedly, if you keep the hands over your keys at all time, you can actually work faster.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;window manager&lt;/strong&gt; (short WM) arranges the window on your screens. A &lt;strong&gt;tiling window manager&lt;/strong&gt; automatically arranges the windows in tiles on your desktop. This ensures maximum real-estate usage of the screen and alleviates the hustle of manually resizing windows.&lt;/p&gt;
&lt;h4&gt;QTile {#qtile}&lt;/h4&gt;
&lt;p&gt;I took that leap, dipped my toes into &lt;a href=&quot;https://i3wm.org/&quot;&gt;i3&lt;/a&gt; and &lt;a href=&quot;https://awesomewm.org/&quot;&gt;awesomewm&lt;/a&gt;, but landed on &lt;a href=&quot;https://qtile.org/&quot;&gt;qtile&lt;/a&gt;. The reason was that Python was the only programming language I knew &amp;amp; QTile is written in Python. This was a great match, because I could practice programming on my own configuration! Also, it was again a declarative approach, which meant &quot;re-installations&quot; were easy.&lt;/p&gt;
&lt;p&gt;QTile challenged me to solve small issues like the status bar myself. But it also empowered me to configure things exactly how I wanted.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What motivated me was the idea that my work will not be lost.&lt;/p&gt;
&lt;p&gt;I am always building and improving.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But QTile on top of a Manjaro installation is... Well not ideal. Either you have an entire unused desktop environment installed, or you look elsewhere.&lt;/p&gt;
&lt;h4&gt;Off the beaten path: Arco Linux {#off-the-beaten-path-arco-linux}&lt;/h4&gt;
&lt;p&gt;The time with Manjaro and &lt;code&gt;pacman&lt;/code&gt; came in handy. I could leverage the same packages on a more &quot;headless&quot; distro. After some research I found &lt;a href=&quot;https://arcolinux.com/&quot;&gt;Arco linux&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Arco is cool, because it gives you an easy and customizable arch-base install. It allows for easy auto-install scripts and fit my need really well.&lt;/p&gt;
&lt;p&gt;But Arco is not pure Arch. For some months this didn&apos;t bother me too much. But then, the nagging that I could not honestly say &quot;&lt;a href=&quot;https://knowyourmeme.com/memes/btw-i-use-arch&quot;&gt;I use arch, BTW&lt;/a&gt;&quot; was too much. I had to dare the dreaded install.&lt;/p&gt;
&lt;h4&gt;Establishing dotfiles {#establishing-dotfiles}&lt;/h4&gt;
&lt;p&gt;With my existing use of doom emacs, I was no stranger to storing configuration in text files that are checked into a repo. So I consolidated my emacs configuration with my qtile configuration and some useful scripts and had my first dotfiles repo!&lt;/p&gt;
&lt;p&gt;The gist is that you can keep all configuration files in a single place - and repo. With the &lt;a href=&quot;https://www.gnu.org/software/stow/&quot;&gt;gnu&lt;/a&gt; &lt;code&gt;stow&lt;/code&gt; command the files can then be symlinked to their proper place. A very powerful workflow that I still use to this day for some stuff.&lt;/p&gt;
&lt;h2&gt;Phase III: Mastering Arch {#phase-iii-mastering-arch}&lt;/h2&gt;
&lt;h4&gt;Daring Arch {#daring-arch}&lt;/h4&gt;
&lt;p&gt;Some consider Arch linux the pinnacle of GNU/Linux: Rolling release, AUR, most packages, you name it.&lt;/p&gt;
&lt;p&gt;The Arch install was not &lt;em&gt;that&lt;/em&gt; hard. But I do consider it a &quot;rite of passage&quot;. Now I was finally part of the cool kids! And I would never distro hop again. Right!?&lt;/p&gt;
&lt;h4&gt;Learning how to solve problems {#learning-how-to-solve-problems}&lt;/h4&gt;
&lt;p&gt;In all honesty, the install and everything to do with Linux taught me how to approach problems and solve them. To this day I can&apos;t remember a single problem that I have not managed to solve. My process boils down to this: &lt;strong&gt;Try =&amp;gt; fail =&amp;gt; formulate hypothesis why it fails =&amp;gt; research =&amp;gt; tweak steps. Repeat.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now, on that &quot;research&quot; part, the best resource I found was the &lt;a href=&quot;https://wiki.archlinux.org/title/Installation_guide&quot;&gt;arch wiki&lt;/a&gt;. It became the first site I would go to, to learn more about a particular aspect of the computer. Having such a &quot;bible&quot;, a single place of knowledge, is invaluable.&lt;/p&gt;
&lt;p&gt;Even if you are not using arch, the wiki may be able to help you out.&lt;/p&gt;
&lt;h4&gt;Versioning packages {#versioning-packages}&lt;/h4&gt;
&lt;p&gt;By now, the command line had become my bread and butter. Especially for administering the packages installed on my machine.&lt;/p&gt;
&lt;p&gt;To discover new software I used &lt;a href=&quot;https://github.com/Morganamilo/paru&quot;&gt;paru&lt;/a&gt;. Not a complete stand-alone package manager, &lt;code&gt;paru&lt;/code&gt; wraps &lt;code&gt;pacman&lt;/code&gt; to ease the interaction with the AUR. It also comes with a great CLI search.&lt;/p&gt;
&lt;p&gt;I also played around with &lt;a href=&quot;https://github.com/fosskers/aura&quot;&gt;aura&lt;/a&gt;, which actually is a stand-alone package manager that creates snapshots (like lock-files) of all packages installed. Aura can be used for creating more &quot;reproducible&quot; systems. For example if you want to keep to machines in sync in terms of the software they are running.&lt;/p&gt;
&lt;h4&gt;Managing two systems {#managing-two-systems}&lt;/h4&gt;
&lt;p&gt;I started having a work laptop. Of course this machine had to be configured the same as my personal machine. Thanks to the installation scripts and dotfiles I was quickly able to set it up. This was great! Just imagine on your first day of work to come in and for the first few hours wait as your computer is automatically installing everything you are used to.&lt;/p&gt;
&lt;p&gt;As time progressed, I tried to keep my work and home laptop configured in sync. Things I configured on one system should also be available on the other.&lt;/p&gt;
&lt;p&gt;But this was cumbersome. Merging the inevitably diverging git branches became an hour-long task. And even then, some configuration commands I did on one system I had forgotten on the other - a doomed endeavor.&lt;/p&gt;
&lt;p&gt;But what could solve this problem? Could there be a better distro out there for me?&lt;/p&gt;
&lt;h2&gt;Phase IV: Declaring reproducibility {#phase-iv-declaring-reproducibility}&lt;/h2&gt;
&lt;p&gt;Yes. Yes, there could: NixOS.&lt;/p&gt;
&lt;h4&gt;The final distro hop: NixOS {#the-final-distro-hop-nixos}&lt;/h4&gt;
&lt;p&gt;A coding colleague brought it to my attention, as he was interested in it and knew about my excessive &lt;a href=&quot;https://github.com/doomemacs/doomemacs&quot;&gt;doom-emacs&lt;/a&gt; usage. &lt;a href=&quot;https://github.com/hlissner&quot;&gt;HLissner&lt;/a&gt;, the creator and maintainer of doom is also a NixOS user. Henrik has this funny answer on his &lt;a href=&quot;https://github.com/hlissner/dotfiles?tab=readme-ov-file#frequently-asked-questions&quot;&gt;dotfiles repo&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why NixOS?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because managing a fleet of servers, a hundred strong, is the tenth circle of hell without a declarative, generational, and immutable single-source-of-truth configuration framework like NixOS.&lt;/p&gt;
&lt;p&gt;Sure beats the nightmare of brittle capistrano/chef/puppet/ansible/shell scripts I left behind.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Should I use NixOS?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Short answer&lt;/strong&gt;: no.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Long answer&lt;/strong&gt;: no really. Don&apos;t.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Long long answer&lt;/strong&gt;: I&apos;m not kidding. Don&apos;t.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unsigned long long answer&lt;/strong&gt;: Alright alright. Here&apos;s why not:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Its learning curve is steep.&lt;/li&gt;
&lt;li&gt;You will trial and error your way to enlightenment, if you survive the frustration long enough.&lt;/li&gt;
&lt;li&gt;NixOS is unlike other Linux distros. Your issues will be unique and difficult to google. A decent grasp of Linux and your chosen services is a must, if only to distinguish Nix(OS) issues from Linux (or upstream) issues -- as well as to debug them or report them to the correct authority (and coherently).&lt;/li&gt;
&lt;li&gt;If words like &quot;declarative&quot;, &quot;generational&quot;, and &quot;immutable&quot; don&apos;t put your sexuality in jeopardy, you&apos;re considering NixOS for the wrong reasons.&lt;/li&gt;
&lt;li&gt;The overhead of managing a NixOS config will rarely pay for itself with 3 systems or fewer (perhaps another distro with nix on top would suit you better?).&lt;/li&gt;
&lt;li&gt;Official documentation for Nix(OS) is vast, but shallow. Unofficial resources and example configs are sparse and tend toward too simple or too complex (and most are outdated). Case in point: this repo.&lt;/li&gt;
&lt;li&gt;The Nix language is obtuse and its toolchain is not intuitive. Your experience will be infinitely worse if functional languages are alien to you, however, learning Nix is a must to do even a fraction of what makes NixOS worth the trouble.&lt;/li&gt;
&lt;li&gt;If you need somebody else to tell you whether or not you need NixOS, you don&apos;t need NixOS.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&apos;re not discouraged by this, then you didn&apos;t need my advice in the first place. Stop procrastinating and try NixOS!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This intrigued me.&lt;/p&gt;
&lt;p&gt;On a spare laptop I gave it a try. The installation of NixOS was super easy. But I had acquired some particular tastes: QTile. Configuring python dependencies for NixOS is still a daunting task for me, but after enough bashing my head against the wall it worked (with a nixpkgs overlay I believe).&lt;/p&gt;
&lt;h4&gt;Settling on NixOS {#settling-on-nixos}&lt;/h4&gt;
&lt;p&gt;The beauty of nix is that if you configure something on one system, you can reuse that configuration on another. So the work on the spare laptop could easily be translated to my home machine.&lt;/p&gt;
&lt;p&gt;The entire configuration of my machine could now be checked into my dotfiles repository! Adding the work machine was also not that hard, granted I used duct-tape-based methods to make it all work.&lt;/p&gt;
&lt;p&gt;But nix allowed me to &lt;em&gt;rely&lt;/em&gt; on the software that is installed and how its configured. It took away the issue of forgetting to &lt;em&gt;instruct my computer about a configuration&lt;/em&gt;, because I would &lt;em&gt;declare&lt;/em&gt; the configuration in a text file.&lt;/p&gt;
&lt;h4&gt;Switching to Flakes {#switching-to-flakes}&lt;/h4&gt;
&lt;p&gt;Are they still experimental? Anyhow, flakes in nix are the future. When you are using them for a nix configuration, the flake creates a &lt;code&gt;flake.lock&lt;/code&gt; alongside your &lt;code&gt;flake.nix&lt;/code&gt;. This lock file pins a commit hash of the &lt;a href=&quot;https://github.com/NixOS/nixpkgs&quot;&gt;nixpkgs repository&lt;/a&gt;. With this commit hash you can actually reproduce the &lt;em&gt;exact&lt;/em&gt; version of software across systems. Let that sink in.&lt;/p&gt;
&lt;p&gt;Moreover, with a flakes setup you can have multiple machines share the &lt;em&gt;same&lt;/em&gt; configuration in your repo. The system configuration can be modularized and so you can for example write a &lt;code&gt;tool-belt.nix&lt;/code&gt; file that contains all the CLI tools you want to share across all computers.&lt;/p&gt;
&lt;p&gt;Finally, I achieved the dream: my personal and work machine could share the same configuration. There was no more drift between the systems! I was able to also setup a home-server running with the same configuration and just omit all the desktop related packages.&lt;/p&gt;
&lt;h4&gt;Killer feature: Generations {#killer-feature-generations}&lt;/h4&gt;
&lt;p&gt;The real killer feature of NixOS is the generations. Every time you change something about the config, you create a new &quot;atomic&quot; snapshot of the software and its version at that point in time. In the background all packages are installed in the &lt;code&gt;/nix/store&lt;/code&gt; and just symlinked into a profile. Yes, this bloats the disc some, but you can always garbage collect.&lt;/p&gt;
&lt;p&gt;Simply: When you boot your machine, you are presented with the choice to roll back to any previous generation.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;[dramatic pause]&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Do you have any idea how many times I busted my setup and started from &lt;em&gt;scratch&lt;/em&gt; - as in, usb-drive, wipe the machine, install fresh, run my scripts, configure everything... &lt;em&gt;Do you have any idea?!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With Nix, this is a &lt;strong&gt;non-issue&lt;/strong&gt;. Power off, roll back, you&apos;re good.&lt;/p&gt;
&lt;p&gt;Damn.&lt;/p&gt;
&lt;h2&gt;Phase V: Nix verstehen {#phase-v-nix-verstehen}&lt;/h2&gt;
&lt;p&gt;Or in English: &quot;understand noting&quot;. It&apos;s a double-entendre, because &quot;nix&quot; in German means &quot;nothing&quot;, too.&lt;/p&gt;
&lt;p&gt;The &quot;Nix&quot;-way of doing Linux is so different to what is already out there. But I believe that it&apos;s the &quot;right&quot; way to configuration. When it finally &quot;clicks&quot;, for example when you build an existing configuration on a brand new machine and suddenly you are in your familiar environment, with &lt;em&gt;everything&lt;/em&gt; down to the color scheme, font, shortcuts, bash aliases, wallpaper. In that moment you glimpse the greatness and potential of the Nix-way.&lt;/p&gt;
&lt;h4&gt;Flexing the nix muscles {#flexing-the-nix-muscles}&lt;/h4&gt;
&lt;p&gt;To show off a little what conveniences I now get to enjoy:&lt;/p&gt;
&lt;p&gt;&amp;lt;!--list-separator--&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;home-manager&lt;/code&gt; - declare the user-space&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://nix-community.github.io/home-manager/&quot;&gt;Home Manager&lt;/a&gt; can be what manages the user-space on your machine. So not on a system level, but user-level. You can use it even in non-NixOS setups - looking at you MacOS people.&lt;/p&gt;
&lt;p&gt;All user installed software, services, configuration and more can be configured with home-manager. When using NixOS you can simply plug it in to your system configuration. It allows a much finer grained controlled of the user environment.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;!--list-separator--&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;hyprland&lt;/code&gt; &amp;amp; &lt;code&gt;waybar&lt;/code&gt; - a modern environment&lt;/p&gt;
&lt;p&gt;QTile bit the dust when I tried to switch to wayland. Apparently it supports it, but I couldn&apos;t get it to work. Also, whenever QTile did break while tinkering, it left the system in a near-unusable state (learned about the tty this way).&lt;/p&gt;
&lt;p&gt;Now my desktop environment - i.e. hyprland and some more packages, are declared in my flake. This is &lt;em&gt;so&lt;/em&gt; cool!&lt;/p&gt;
&lt;p&gt;When I find a cool waybar example from some other nix user, I can just copy+paste it!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;!--list-separator--&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nh&lt;/code&gt; - the nix helper&lt;/p&gt;
&lt;p&gt;To have some visual sugar while updating the system, &lt;a href=&quot;https://github.com/viperML/nh&quot;&gt;nh has you covered&lt;/a&gt;. Now, all you need is the &lt;code&gt;nh os switch&lt;/code&gt; command, because &lt;code&gt;nh&lt;/code&gt; takes the &lt;code&gt;hostname&lt;/code&gt; of your machine and applies the same named flake part!&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.youtube.com/watch?v=DnA4xNTrrqY&quot;&gt;YouTuber Vimjoyer has a video how to set it up&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;!--list-separator--&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;stylix&lt;/code&gt; - theme &lt;em&gt;everything&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Ricing is usually only for folks that really have too much time on their hands (not excluding myself here). Because going through every single program and customizing the theme and get it right is nigh impossible.&lt;/p&gt;
&lt;p&gt;What if there were a program that could do it all. Automatically.&lt;/p&gt;
&lt;p&gt;Meet &lt;a href=&quot;https://github.com/danth/stylix&quot;&gt;stylix&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This post is too long already, so &lt;a href=&quot;https://www.youtube.com/watch?v=ljHkWgBaQWU&quot;&gt;just watch this video by Vimjoyer&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;MVP: Vimjoyer {#mvp-vimjoyer}&lt;/h4&gt;
&lt;p&gt;Speaking of whom... &lt;a href=&quot;https://www.youtube.com/@vimjoyer&quot;&gt;His YouTube channel is so unbelievably helpful&lt;/a&gt;. If you are considering giving Nix a try, you &lt;em&gt;must&lt;/em&gt; watch the videos. You will not get a more up-to-date, concise nix on-boarding.&lt;/p&gt;
&lt;h2&gt;Epilog {#epilog}&lt;/h2&gt;
&lt;p&gt;Told you I had some adventures...&lt;/p&gt;
&lt;p&gt;What can we now take away from this? Personally, I learned that pursuing my ambitions pays off. As I build my timeless tech-stack there is only one way: up. No matter how difficult the problem, with the tried-and-true approach &lt;strong&gt;Try =&amp;gt; fail =&amp;gt; formulate hypothesis why it fails =&amp;gt; research =&amp;gt; tweak steps. Repeat.&lt;/strong&gt; nothing is unachievable.&lt;/p&gt;
&lt;p&gt;This process teaches you about the technology you use. You become the master of your system. Able to determine every little detail of the setup. Navigating the computer becomes muscle memory and you are free to focus on what really matters.&lt;/p&gt;
&lt;p&gt;All of this was a long-winded way of saying:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check out my rice&lt;/strong&gt; 😎&lt;/p&gt;
</content:encoded></item><item><title>Touching base, or: reverting to the default</title><link>https://opensource-odyssey.net/posts/reverting-to-the-default/</link><guid isPermaLink="true">https://opensource-odyssey.net/posts/reverting-to-the-default/</guid><description>Customization is cool, but defaults matter.</description><pubDate>Wed, 17 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve been using &lt;a href=&quot;https://github.com/doomemacs/doomemacs&quot;&gt;Doom Emacs&lt;/a&gt; for a few years now. After picking it up, the configuration and customization via a text file (the &lt;code&gt;config.el&lt;/code&gt;) caught my interest. It allows the config to grow organically, be checked into &lt;code&gt;git&lt;/code&gt;, be versioned, breaking changes can easily be reverted and transfering the exact config to a new system becomes a non-issue.&lt;/p&gt;
&lt;p&gt;As the yeas passed by, my config grew and grew. A lot of trial and error was still inside that I didn&apos;t bother to clean up. Then, the other week, I had an issue with my Emacs configuration and I re-installed Doom and just used the default configuration.&lt;/p&gt;
&lt;h2&gt;Dropping everything for the default config {#dropping-everything-for-the-default-config}&lt;/h2&gt;
&lt;p&gt;Quickly the realization dawned on me, that the default Doom config was &lt;em&gt;much faster&lt;/em&gt; than my own aged configuration. And I decided to start over.&lt;/p&gt;
&lt;p&gt;In other words, go through my existing file, only take the parts that are truly needed and leave out all of the rest.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This took my config from 588 lines to 196&lt;/strong&gt; (granted, not everything I used in my old config is in the new config). Importantly, I&apos;m not really missing anything and noticed significant improvements in the default doom configuration. Like new packages being used as defaults etc.&lt;/p&gt;
&lt;p&gt;Not only is the config file now much more readable, but the speed improvement is still there.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The default doom emacs configuration is really &lt;em&gt;great&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Hitchhiking on the backs of masters {#hitchhiking-on-the-backs-of-masters}&lt;/h2&gt;
&lt;p&gt;Truth is that I&apos;m not the brightest bulb in the shed.&lt;/p&gt;
&lt;p&gt;Other people have thought much deeper about certain issues and have much more know-how how to solve them. Instead of re-inventing the wheel, have a look at what the state-of-the-art is.&lt;/p&gt;
&lt;p&gt;Then just copy the state-of-the-art. Or at least take inspiration from it. &lt;em&gt;Especially, if your original starting point was the &quot;state-of-the-art&quot;.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Swimming with the swarm {#swimming-with-the-swarm}&lt;/h2&gt;
&lt;p&gt;I&apos;m not one to go for what is the most popular: Not using Windows or MacOS, not using iOS or Google Android, not using Microsoft or Google products, etc.&lt;/p&gt;
&lt;p&gt;This &quot;swimming against the swarm&quot; has its costs. Convenience mainly. Make no mistake, it also has it&apos;s upside, but it&apos;s a tough climb.&lt;/p&gt;
&lt;p&gt;It&apos;s not about doing the opposite of what everybody else is doing. If it were, I would be using &lt;a href=&quot;https://templeos.org/&quot;&gt;TempleOS&lt;/a&gt;. It&apos;s about having principles and within these confines picking the most viable solution. And swimming with the swarm of other individuals that have the same principles.&lt;/p&gt;
&lt;h2&gt;Conclusion {#conclusion}&lt;/h2&gt;
&lt;p&gt;In conclusion, be encouraged to touch base with the projects that you&apos;ve started using.&lt;/p&gt;
&lt;p&gt;You&apos;ll be surprised how good most of the defaults are!&lt;/p&gt;
</content:encoded></item><item><title>Configuring Doom Emacs to be your Typescript IDE</title><link>https://opensource-odyssey.net/posts/configuring-doom-emacs-to-be-your-typescript-ide/</link><guid isPermaLink="true">https://opensource-odyssey.net/posts/configuring-doom-emacs-to-be-your-typescript-ide/</guid><description>Did I doom myself? I sure did.</description><pubDate>Sun, 04 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For a while I struggled with using Emacs as a IDE for modern web-development. In other words, Typescript, prettier, .tsx files, etc.&lt;/p&gt;
&lt;p&gt;In the end it&apos;s not hard, here is how I have my Doom configured:&lt;/p&gt;
&lt;h2&gt;settings in the &lt;code&gt;init.el&lt;/code&gt; {#settings-in-the-init-dot-el}&lt;/h2&gt;
&lt;p&gt;First up, we have to make sure that we are using the power of a language server protocol (short &lt;code&gt;lsp&lt;/code&gt;). The lsp is a client-server protocol where your IDE is getting all the type-hinting, Intellisense, auto-complete etc from. As far as I understand this, this does &lt;em&gt;not mean you are connected to the Internet, you are hosting the lsp automatically locally&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;~/.config/doom/init.el&lt;/code&gt;, set these (along your other config, of course):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(doom!
  :tools
  (lsp +peek) ; +peek is optional, read more about it in the lsp README (g d)
  (debugger +lsp) ; we&apos;ll get to this later

  :lang
  (javascript +lsp)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This takes care of a solid setup already! However, there are some further tweaks needed for a fully  Integrated Developer Environment.&lt;/p&gt;
&lt;p&gt;💡 Remember, you can always rebuild your Doom with &lt;code&gt;SPC h r r&lt;/code&gt;, this will automatically install the packages required.&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;typescript-mode&lt;/code&gt; {#typescript-mode}&lt;/h2&gt;
&lt;p&gt;With this mode we already have an automatic connection to the &lt;a href=&quot;https://emacs-lsp.github.io/lsp-mode/&quot;&gt;lsp-mode&lt;/a&gt;. When you open a &lt;code&gt;.ts&lt;/code&gt; file for the first time, you will get an interactive dialog to download a language-server. I use &lt;code&gt;ts-ls&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(use-package! typescript-mode
  :mode &quot;\\.ts\\&apos;&quot; ; set the mode as default for .ts files
  :config
  (require &apos;dap-node) ; enable the debugger
  (dap-node-setup) ; setup the debugger
  (setq typescript-indent-level 2) ; set typescript indent level
  (add-hook &apos;typescript-mode-hook #&apos;add-node-modules-path) ; for mono-repo support
  (add-hook &apos;typescript-mode-hook #&apos;prettier-js-mode) ; use the prettier config of the project
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;code&gt;typescript-tsx-mode&lt;/code&gt; {#typescript-tsx-mode}&lt;/h2&gt;
&lt;p&gt;For React, a similar setup can be used for &lt;code&gt;.tsx&lt;/code&gt; files.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(use-package! typescript-tsx-mode ; automatically installed with (javascript +lsp)
  :mode  &quot;\\.tsx\\&apos;&quot; ; default for .tsx files
  :config ; see above
  (add-hook &apos;typescript-tsx-mode-hook #&apos;add-node-modules-path)
  (add-hook &apos;typescript-tsx-mode-hook #&apos;prettier-js-mode)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don&apos;t forget to rebuild your config.&lt;/p&gt;
&lt;h2&gt;Trying it out in a fresh &lt;code&gt;bun&lt;/code&gt; project {#trying-it-out-in-a-fresh-bun-project}&lt;/h2&gt;
&lt;p&gt;Give it a try, for example with a new &lt;a href=&quot;https://bun.sh&quot;&gt;bun&lt;/a&gt; repo (make sure to have it installed):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir typescript-test
cd typescript-test
bun init -y
bun run index.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates the basic project that is typescript ready. Go into the &lt;code&gt;index.ts&lt;/code&gt; and try it out!&lt;/p&gt;
&lt;h2&gt;Next steps: Debugger {#next-steps-debugger}&lt;/h2&gt;
&lt;p&gt;Now, to have a &lt;em&gt;real&lt;/em&gt; IDE, we need a debugger. Unfortunately, I am no wizard and have not found a good solution yet. The best I have so far is a basic &lt;code&gt;dap-mode&lt;/code&gt; setup.&lt;/p&gt;
&lt;p&gt;If you have solved this for a typescript setup - especially in mono-repos, do let me know! The debugging experience in Doom Emacs is not that great and often times I result to &lt;code&gt;console.log()&lt;/code&gt;, which is obviously less than ideal.&lt;/p&gt;
</content:encoded></item><item><title>How I published this blog post</title><link>https://opensource-odyssey.net/posts/how-to-write-this-post/</link><guid isPermaLink="true">https://opensource-odyssey.net/posts/how-to-write-this-post/</guid><description>Diving deeper into Doom Emacs and how I write my blog</description><pubDate>Thu, 18 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Follow me on my journey on how this blog post was written and published. You may discover an amazing workflow to quickly bring &lt;em&gt;your&lt;/em&gt; thoughts to the web as well.&lt;/p&gt;
&lt;h2&gt;The basic setup with org-mode {#the-basic-setup-with-org-mode}&lt;/h2&gt;
&lt;p&gt;In the beginning there was darkness. Then, a fresh new &lt;code&gt;.org&lt;/code&gt; file appears. The &lt;code&gt;opensourceodyssey.org&lt;/code&gt; file, to be precise.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Org&lt;/code&gt; or &lt;a href=&quot;https://orgmode.org/&quot;&gt;org-mode&lt;/a&gt; is a markdown syntax for Emacs. You can do &lt;em&gt;a lot&lt;/em&gt; with it; such as: taking notes, tracking tasks, writing literal code files (like &lt;a href=&quot;https://jupyter.org/&quot;&gt;Jupyter notebooks for Python&lt;/a&gt;), and exporting to all kinds of other formats (like &lt;code&gt;.md&lt;/code&gt;, &lt;code&gt;.html&lt;/code&gt; or &lt;code&gt;reveal-js&lt;/code&gt; presentations).&lt;/p&gt;
&lt;p&gt;Every post you read is a level 2 heading in the &lt;code&gt;opensourceodyssey.org&lt;/code&gt; file that is marked as &quot;DONE&quot;.&lt;/p&gt;
&lt;p&gt;Notice in the top, the &lt;code&gt;#+hugo_base_dir&lt;/code&gt; variable, that has a value of a server ssh alias and a file path on that server. We&apos;ll get back to this.&lt;/p&gt;
&lt;p&gt;In summary, org and Emacs is the place to write the content.&lt;/p&gt;
&lt;h2&gt;Go Hugo! {#go-hugo}&lt;/h2&gt;
&lt;p&gt;Next step is to setup a simple &lt;a href=&quot;https://gohugo.io/&quot;&gt;hugo&lt;/a&gt; project. Hugo calls itself &quot;The world’s fastest framework for building websites&quot; and you can confirm this by navigating this very site. It specializes in static site generation, in other words sites where the information does not change based on the user. This is perfect for a blog.&lt;/p&gt;
&lt;p&gt;After installing &lt;code&gt;hugo&lt;/code&gt;, just create a new hugo project with &lt;code&gt;hugo new site [SITENAME] --format yaml&lt;/code&gt;. Replace &lt;code&gt;[SITENAME]&lt;/code&gt; with the name of your project. The entry folder will have this name. I prefer &lt;a href=&quot;https://yaml.org/&quot;&gt;YAML&lt;/a&gt; over &lt;a href=&quot;https://toml.io/en/&quot;&gt;TOML&lt;/a&gt;, so I opt for YAML.&lt;/p&gt;
&lt;p&gt;Hugo takes care of the &quot;business logic&quot;. To make things look pretty, we need a &lt;em&gt;theme&lt;/em&gt;. Hugo offers a wide variety of &lt;a href=&quot;https://themes.gohugo.io/&quot;&gt;free-to-use themes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I picked a beautiful theme: &lt;a href=&quot;https://github.com/adityatelange/hugo-PaperMod&quot;&gt;PaperMod&lt;/a&gt;. The features include a light/dark mode, posts with navigation to the previous/next post, automatic word count and reading time calculator and much much more.&lt;/p&gt;
&lt;p&gt;Each theme comes with its own installation instructions. Follow them and make the changes in your newly crated &lt;code&gt;hugo&lt;/code&gt; project.&lt;/p&gt;
&lt;p&gt;Great! In short, with &lt;code&gt;org&lt;/code&gt; we write the posts and with &lt;code&gt;hugo&lt;/code&gt; we render the HTML for a browser.&lt;/p&gt;
&lt;h2&gt;Publishing the post with &lt;code&gt;ox-hugo&lt;/code&gt; {#publishing-the-post-with-ox-hugo}&lt;/h2&gt;
&lt;p&gt;Now we need some kind of glue to tie it all together. Fortunately, Emacs has us covered with &lt;a href=&quot;https://ox-hugo.scripter.co/&quot;&gt;ox-hugo&lt;/a&gt;. Moreover, Doom has maybe the easiest installation ever for this integration:&lt;/p&gt;
&lt;p&gt;Go into the &lt;code&gt;init.el&lt;/code&gt; of your &lt;code&gt;~/.config/doom&lt;/code&gt; and change the line of org to be like&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(org +hugo)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then rebuild the configuration (with &lt;code&gt;SPC h r r&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Now, in any org document you can execute the &lt;code&gt;org-export-dispatch&lt;/code&gt; (with &lt;code&gt;SPC m e&lt;/code&gt;) to pop the export menu.&lt;/p&gt;
&lt;p&gt;Then I select &quot;Export Hugo-compatible Markdown&quot; and choose to export all subtrees. The destination will be the &lt;code&gt;#+hugo_base_dir:&lt;/code&gt; we set at the top of the file!&lt;/p&gt;
&lt;p&gt;Yes, it&apos;s &lt;em&gt;that&lt;/em&gt; easy. Navigate to the hugo directory and check the &lt;code&gt;content/&lt;/code&gt; directory for your posts.&lt;/p&gt;
&lt;h2&gt;Adding metadata to a post {#adding-metadata-to-a-post}&lt;/h2&gt;
&lt;p&gt;You should also add metadata to the individual subheadings to support the &quot;export all subtrees&quot; command.&lt;/p&gt;
&lt;p&gt;The &quot;Posts&quot; h1 has the following properties right underneath it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:PROPERTIES:
:EXPORT_HUGO_SECTION: posts
:END:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the individual h2 (that each represent a post) have the following properties underneath it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:PROPERTIES:
:EXPORT_FILE_NAME: reverting-to-the-default
:EXPORT_HUGO_SECTION: posts
:EXPORT_DATE: 2024-07-17
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :cover &apos;((image . &quot;@/assets/post-images/touching-base.png&quot;) (alt . &quot;A ship docked at land&quot;))
:END:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the &lt;code&gt;:EXPORT_HUGO_CUSTOM_FRONT_MATTER:&lt;/code&gt; setting. &lt;code&gt;ox-hugo&lt;/code&gt; supports &lt;a href=&quot;https://ox-hugo.scripter.co/doc/custom-front-matter/#maps-of-single-and-list-values&quot;&gt;putting &lt;em&gt;any&lt;/em&gt; custom frontmatter&lt;/a&gt; in the generated &lt;code&gt;.md&lt;/code&gt; document! According to the PaperMod docs and &lt;a href=&quot;https://github.com/adityatelange/hugo-PaperMod/wiki/Installation#sample-pagemd&quot;&gt;sample &lt;code&gt;Page.md&lt;/code&gt;,&lt;/a&gt; the frontmatter for a cover image looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cover:
    image: &quot;&amp;lt;image path/url&amp;gt;&quot; # image path/url
    alt: &quot;&amp;lt;alt text&amp;gt;&quot; # alt text
    caption: &quot;&amp;lt;text&amp;gt;&quot; # display caption under cover
    relative: false # when using page bundles set this to true
    hidden: true # only hide on current single page
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;cover&lt;/code&gt; is the first-level key, containing an object. Ergo: (&lt;code&gt;:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :cover &apos;((image . &quot;@/assets/post-images/touching-base.png&quot;) (alt . &quot;A ship docked at land&quot;))&lt;/code&gt;). Now this just works when triggering &lt;code&gt;ox-hugo&lt;/code&gt; export.&lt;/p&gt;
&lt;p&gt;The tags of each post is set directly behind the heading with &lt;code&gt;:tagname:&lt;/code&gt; syntax. Our hugo theme takes care of creating pages for our tags.&lt;/p&gt;
&lt;p&gt;We can then test our setup on localhost with executing &lt;code&gt;hugo server&lt;/code&gt; in the hugo base directory. &lt;code&gt;http://localhost:1313&lt;/code&gt; then shows our site.&lt;/p&gt;
&lt;h2&gt;Hosting the site {#hosting-the-site}&lt;/h2&gt;
&lt;p&gt;Finally, we have our site and our content and we just need to bring it online.&lt;/p&gt;
&lt;p&gt;For this I rented a VPS and a domain. Point the domain to the static IP of the VPS with DNS records. Then all that is needed is a webserver on the VPS to handle requests. I chose &lt;code&gt;caddy&lt;/code&gt;. &lt;a href=&quot;https://caddyserver.com/&quot;&gt;Caddy&lt;/a&gt; is a very easy to use webserver that automatically handles certificates for you.&lt;/p&gt;
&lt;p&gt;Since Hugo is a static site generation tool, caddy is simply serving static files. Here is the &lt;code&gt;Caddyfile&lt;/code&gt; config for a hugo blog.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;opensource-odyssey.net {
        root * /server/public
        file_server

}

www.opensource-odyssey.net {
        redir https://opensource-odyssey.net{uri}

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;💡 &lt;code&gt;/server/&lt;/code&gt; is the directory where the hugo site lives. It&apos;s owned by the &lt;code&gt;caddy&lt;/code&gt; user and group.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;hugo&lt;/code&gt; command builds our site and places everything in the &lt;code&gt;public/&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;Now restart &lt;code&gt;caddy&lt;/code&gt; with &lt;code&gt;sudo systemctl restart caddy&lt;/code&gt; and voila, the site is online!&lt;/p&gt;
</content:encoded></item><item><title>Running an astro blog through org mode</title><link>https://opensource-odyssey.net/posts/running-astro-through-org-mode/</link><guid isPermaLink="true">https://opensource-odyssey.net/posts/running-astro-through-org-mode/</guid><description>I love Doom Emacs, but I don&apos;t know Go. Soooo, I&apos;m turning away from GoHugo and towards Astro.</description><pubDate>Sun, 08 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I love Doom Emacs. By now, you should know that. Also quite fond of sharing my passion and findings with the world, hence blogging. When I started this project back in 2024, &lt;a href=&quot;/posts/how-to-write-this-post&quot;&gt;I used Doom Emacs to write articles&lt;/a&gt; and would publish them on the web with &lt;a href=&quot;https://gohugo.io/&quot;&gt;GoHugo&lt;/a&gt;. That was a lovely work-flow, since with a key-chord I could not only transform my &lt;code&gt;.org&lt;/code&gt; file into markdown, but also sync it to the VPS, where it would be picked up by Hugo through a cron job.&lt;/p&gt;
&lt;p&gt;There was, however, one thing that bugged me: I don&apos;t understand Go and Go templates. This limited me in customizing the appearance of my site. Don&apos;t get me wrong, the &lt;a href=&quot;https://themes.gohugo.io/&quot;&gt;Hugo Themes are georgeous&lt;/a&gt;, but they felt too cookie cutter to me. The desire to stand out ate away at me.&lt;/p&gt;
&lt;h2&gt;A little hiatus {#a-little-hiatus}&lt;/h2&gt;
&lt;p&gt;My profession is web development, and around last year I went all in on &lt;a href=&quot;http://payloadcms.com/&quot;&gt;PayloadCMS&lt;/a&gt;. For the unaware, Payload is &amp;lt;span class=&quot;underline&quot;&amp;gt;the&amp;lt;/span&amp;gt; content management system for &lt;a href=&quot;https://nextjs.org/&quot;&gt;NextJS&lt;/a&gt;. And since everything looked like a nail, the decision to rewrite the blog in PayloadCMS and just use the admin interface for content felt close. Even went so far to start &lt;a href=&quot;https://www.youtube.com/@OpenSource-Odyssey&quot;&gt;a YouTube channel&lt;/a&gt; to document the creation of this new site.&lt;/p&gt;
&lt;p&gt;However, after (&lt;em&gt;checks notes&lt;/em&gt;) 17 episodes my motivation fades. Creating videos, producing them, uploading them and maintaining all this content was too much. After all, I&apos;m a developer and therefore lazy af. Or put in more formal way: &quot;The friction of producing the content was too high&quot;.&lt;/p&gt;
&lt;p&gt;I fell off.&lt;/p&gt;
&lt;h2&gt;The kindling of a new flame {#the-kindling-of-a-new-flame}&lt;/h2&gt;
&lt;p&gt;But the spark never went away. I still want to share my passion with the world, just with less &quot;friction&quot;.&lt;/p&gt;
&lt;p&gt;Reminiscing about the good ol times, when publishing content could be as easy as pressing some buttons, a new spark arose.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;There must be some solution to my &quot;cannot customize the template&quot; situation.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With AI being so good nowadays, I asked the almighty what path forward I should choose. After all, I &lt;em&gt;could&lt;/em&gt; dip my toes into Go...&lt;/p&gt;
&lt;p&gt;But nay. There was an even &lt;em&gt;better way&lt;/em&gt; to leverage all my web development skills and still keep using my established workflow with Doom Emacs, org-mode and &lt;a href=&quot;https://ox-hugo.scripter.co/&quot;&gt;ox-hugo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Enter Astro.build {#enter-astro-dot-build}&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; wasn&apos;t completely new to me. Here and there I heard tales of the blazingly fast performance of astro sites - with great developer experience, too. The question was just if there was a org-mode exporter for Astro.&lt;/p&gt;
&lt;p&gt;But the AI schooled me and informed me that I could just keep using &lt;code&gt;ox-hugo&lt;/code&gt; for the content management and use Astro as the visual representation on the web.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Vince-McMahon.jpg&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Hol up. You&apos;re telling me that I can do that? Yes. Yes you can.&lt;/p&gt;
&lt;p&gt;It&apos;s what you are looking at right now: a site build with Astro, TypeScript, React, TailwindCSS - in short tech I&apos;m familiar with. But the article was still written in an org-mode file.&lt;/p&gt;
&lt;p&gt;Let&apos;s dive deeper into how it works.&lt;/p&gt;
&lt;h2&gt;Scaffolding Astro {#scaffolding-astro}&lt;/h2&gt;
&lt;p&gt;The creation of the project was straightforward.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Create the project (select &quot;Empty&quot; template)
pnpm create astro@latest my-blog

# Add integrations
cd opensource-odyssey-blog
pnpm astro add react tailwind
pnpm add -D @tailwindcss/typography
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I initialized the Astro project and added the React/Tailwind integrations.&lt;/p&gt;
&lt;h2&gt;Using ox-hugo to export markdown to Astro {#using-ox-hugo-to-export-markdown-to-astro}&lt;/h2&gt;
&lt;p&gt;The key realization was that at the core, &lt;code&gt;ox-hugo&lt;/code&gt; just transforms &lt;code&gt;.org&lt;/code&gt; files into &lt;code&gt;.md&lt;/code&gt; files and places them in some specified &lt;code&gt;content/&lt;/code&gt; directory. Fortunately, Astro &lt;em&gt;also&lt;/em&gt; expects markdown files to live in such a directory. Granted, there is a little gotcha, in that Hugo expects the &lt;code&gt;content/&lt;/code&gt; directory to be at the root of the project, whereas Astro has it nested in &lt;code&gt;src/content/&lt;/code&gt;, but by setting the &lt;code&gt;HUGO_BASE_DIR&lt;/code&gt; to &lt;code&gt;./src/&lt;/code&gt; that can be mitigated.&lt;/p&gt;
&lt;p&gt;This is how the header of my &lt;code&gt;.org&lt;/code&gt; file now looks.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#+TITLE: Opensource Odyssey Blog
#+AUTHOR: Alex
#+HUGO_BASE_DIR: ./src/
#+HUGO_FRONT_MATTER_FORMAT: yaml
#+STARTUP: indent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this configuration it is as easy as pressing &lt;code&gt;SPC m e H A&lt;/code&gt; and the entire &lt;code&gt;.org&lt;/code&gt; file with the headlines as sub-trees are converted to &lt;code&gt;.md&lt;/code&gt; and placed in the directory.&lt;/p&gt;
&lt;p&gt;The rest is just Astro stuff.&lt;/p&gt;
&lt;p&gt;Creating a &lt;code&gt;src/pages/posts/[...slug].astro&lt;/code&gt; file that will render the articles&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { getCollection, render } from &quot;astro:content&quot;
import Layout from &quot;@/layouts/Layout.astro&quot;

export const prerender = true

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

const { entry } = Astro.props
const { Content: PostContent } = await render(entry)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adding more frontmatter to each headline&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:PROPERTIES:
:EXPORT_FILE_NAME: running-astro-through-org-mode
:EXPORT_DATE: 2026-02-08
:EXPORT_DESCRIPTION: I love Doom Emacs, but I don&apos;t know Go. Soooo, I&apos;m turning away from GoHugo and towards Astro.
:END:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And creating a &lt;code&gt;src/content.config.ts&lt;/code&gt; to validate the frontmatter with &lt;code&gt;zod&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { defineCollection, z } from &quot;astro:content&quot;

const Post = defineCollection({
  schema: z.object({
    title: z.string(),
    description: z.string().optional(),
    date: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
    heroImage: z.string().optional(),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
    cover: z
      .object({
        image: z.string(),
        alt: z.string().default(&quot;Post cover image&quot;), // Default alt text if missing
      })
      .optional(),
  }),
})

export const collections = { Post }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is a little more to it, of course, but these are the essential parts. I am sure you can figure them out by yourself.&lt;/p&gt;
&lt;p&gt;That&apos;s it. It was that easy!&lt;/p&gt;
&lt;h2&gt;Summary about this journey {#summary-about-this-journey}&lt;/h2&gt;
&lt;p&gt;So I&apos;m back - and in style! At the time of writing I sank about 5 hours into the entire process, from setting up the project to migrating my old articles to publishing everything on the web.&lt;/p&gt;
&lt;p&gt;Finally I have full control over the look and feel of the site. There are more features that I&apos;m envisioning, but these will come with time.&lt;/p&gt;
&lt;p&gt;Astro is really, really amazing. You should check it out - maybe it fits a need that you have.&lt;/p&gt;
&lt;p&gt;I&apos;m very glad to have picked up another tool and not done everything in NextJS/PayloadCMS. This broadens my horizon, sharpens my skills and makes me a more versed developer.&lt;/p&gt;
</content:encoded></item><item><title>Writing an awesome CLI with typescript</title><link>https://opensource-odyssey.net/posts/writing-an-awesome-cli-with-typescript/</link><guid isPermaLink="true">https://opensource-odyssey.net/posts/writing-an-awesome-cli-with-typescript/</guid><description>Command Line Applications are dope - and so is typescript. So how to write a CLI tool with typescript?</description><pubDate>Thu, 22 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div class=&quot;ox-hugo-toc toc&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;heading&quot;&amp;gt;Table of Contents&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#an-overview-of-the-tech-stack&quot;&gt;An overview of the tech stack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#setting-up-the-project-and-installing-dependencies&quot;&gt;Setting up the project &amp;amp; installing dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#quality-of-life-typescript-path-alias-imports-with-bun&quot;&gt;Quality of life: Typescript path alias imports with &lt;code&gt;bun&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#quality-of-life-prettier-and-package-dot-json-scripts&quot;&gt;Quality of life: Prettier &amp;amp; package.json scripts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#file-structure-and-separation-of-concerns&quot;&gt;File structure and separation of concerns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#the-cli-menu-menu-dot-ts&quot;&gt;The CLI menu - &lt;code&gt;menu.ts&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#the-last-run-configuration-from-db-db-dot-ts&quot;&gt;The last run configuration from DB - &lt;code&gt;db.ts&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#bringing-it-all-together-main-dot-ts&quot;&gt;Bringing it all together - &lt;code&gt;main.ts&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;
&amp;lt;!--endtoc--&amp;gt;&lt;/p&gt;
&lt;p&gt;If you have ever wanted to build a Command Line Interface (short: &lt;code&gt;CLI&lt;/code&gt;) in typescript, this post is for you!&lt;/p&gt;
&lt;p&gt;Granted, &lt;a href=&quot;https://www.typescriptlang.org/&quot;&gt;Typescript&lt;/a&gt; is not the most conventional language to do this. Maybe you&apos;re better served with &lt;a href=&quot;https://go.dev/&quot;&gt;Go&lt;/a&gt; and &lt;a href=&quot;https://github.com/spf13/cobra&quot;&gt;Cobra-cli&lt;/a&gt;. But in case TS is your go-to language - like it is for me right now - continue reading.&lt;/p&gt;
&lt;h2&gt;An overview of the tech stack {#an-overview-of-the-tech-stack}&lt;/h2&gt;
&lt;p&gt;First, let&apos;s take a brief overview of the tools we&apos;ll be using:&lt;/p&gt;
&lt;p&gt;For the basic typescript project, package manager and javascript-runtime, &lt;a href=&quot;https://bun.sh&quot;&gt;bun&lt;/a&gt; has become my favorite. &lt;strong&gt;Bun&lt;/strong&gt; is blazingly-fast, easy to set up, and just makes coding in TS a joy. If you&apos;ve never tried it, this is the perfect opportunity to do so. Make sure you have &lt;code&gt;bun&lt;/code&gt; installed, then continue reading.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Typescript&lt;/strong&gt; is the language of choice. It provides the developer experience that Javascript never had. In the beginning the type-syntax may seem daunting, but usually the best Typescript is just sprinkled in and the rest is inferred.&lt;/p&gt;
&lt;p&gt;Now to the real MVP of this post is &lt;a href=&quot;https://www.npmjs.com/package/@clack/prompts&quot;&gt;@clack/prompts&lt;/a&gt;. &lt;strong&gt;Clack&lt;/strong&gt; enables you to &lt;em&gt;effortlessly build beautiful command-line apps&lt;/em&gt;. With Typescript. Check out their npm page. I&apos;ve taken a liking to their API and general flow.&lt;/p&gt;
&lt;p&gt;Lastly, in case our CLI requires some kind of data storage, we&apos;ll use &lt;a href=&quot;https://www.npmjs.com/package/lowdb&quot;&gt;lowdb&lt;/a&gt;. &lt;strong&gt;Lowdb&lt;/strong&gt; is a very simple database that runs on &lt;code&gt;.json&lt;/code&gt;. The benefits are to have the basic database operations (create, read, update, delete), persistent storage, and the database is easily readable for troubleshooting and general checks. This is optional, but often comes to be used in some way for my CLIs.&lt;/p&gt;
&lt;h2&gt;Setting up the project &amp;amp; installing dependencies {#setting-up-the-project-and-installing-dependencies}&lt;/h2&gt;
&lt;p&gt;Let&apos;s get coding!&lt;/p&gt;
&lt;p&gt;Open your favorite code editor and terminal. To start with, we need our basic project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir simple-ts-cli
cd simple-ts-cli
bun init # run through the bun CLI - for &apos;entry point&apos; set src/main.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&apos;ll use a &lt;code&gt;src/&lt;/code&gt; directory for all our code. This way we keep the project root clean.&lt;/p&gt;
&lt;p&gt;Bun already created a &lt;code&gt;tsconfig.json&lt;/code&gt; for us with awesome defaults!&lt;/p&gt;
&lt;p&gt;Next, we install the dependencies. Since this is a CLI, we don&apos;t really care about them being &lt;code&gt;devDependencies&lt;/code&gt;. Just run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bun add @clack/prompts lowdb
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Quality of life: Typescript path alias imports with &lt;code&gt;bun&lt;/code&gt; {#quality-of-life-typescript-path-alias-imports-with-bun}&lt;/h2&gt;
&lt;p&gt;One thing that&apos;s &lt;a href=&quot;https://bun.sh/guides/runtime/tsconfig-paths&quot;&gt;easy to setup&lt;/a&gt; and makes development even nicer is having import path aliases for every file. This allows us to &lt;strong&gt;always use absolute imports&lt;/strong&gt;. Whenever you move code from one file to the other, the imports can be copied as well. No more trying to figure out how many directories to &lt;code&gt;..&lt;/code&gt; up!&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;tsconfig.json&lt;/code&gt; we just add:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    // ...
    &quot;paths&quot;: {
      &quot;~/*&quot;: [&quot;./src/*&quot;]
}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can import from the base of our &lt;code&gt;src/&lt;/code&gt; directory with &quot;~/&quot;!&lt;/p&gt;
&lt;h2&gt;Quality of life: Prettier &amp;amp; package.json scripts {#quality-of-life-prettier-and-package-dot-json-scripts}&lt;/h2&gt;
&lt;p&gt;I don&apos;t like the semicolons at the end of the line, so I use &lt;a href=&quot;https://prettier.io/&quot;&gt;prettier&lt;/a&gt; to remove them. This also makes moving lines, cutting and pasting easier. &lt;a href=&quot;/posts/configuring-doom-emacs-to-be-your-typescript-ide/&quot;&gt;My IDE is already configured&lt;/a&gt; to read the prettier file in the root of the project.&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;.prettierrc&lt;/code&gt; in the project root and add this line&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;semi: false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Last quality of life thing we cannot go without is a simple &lt;code&gt;start&lt;/code&gt; script in our &lt;code&gt;package.json&lt;/code&gt; to run our CLI.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;package.json&lt;/code&gt; add:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  // ...
  &quot;scripts&quot;: {
   &quot;start&quot;: &quot;bun run src/main.ts&quot;
  },
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can run our CLI with &lt;code&gt;bun start&lt;/code&gt; from the root of the project!&lt;/p&gt;
&lt;h2&gt;File structure and separation of concerns {#file-structure-and-separation-of-concerns}&lt;/h2&gt;
&lt;p&gt;We&apos;ll write a simple typescript CLI: ask the user some questions and execut business logic depending on the answers.&lt;/p&gt;
&lt;p&gt;Our CLI will &quot;remember&quot; the last run and default to these choices. For this we&apos;ll use lowdb as our database.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;main.ts&lt;/code&gt; contains the entry point to our CLI and the main flow. Keep it clean to easily follow the overall flow of the app.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;db.ts&lt;/code&gt; contains our database code. The type definition for &lt;code&gt;lowdb&lt;/code&gt; and some basic exported functions, like reading the database, updating it or cleaning all the data.&lt;/p&gt;
&lt;p&gt;Our user interaction is covered in &lt;code&gt;menu.ts&lt;/code&gt;. Here we define the different questions and export a function for each menu - if our CLI ends up being more interactive.&lt;/p&gt;
&lt;p&gt;Finally, &lt;code&gt;lib.ts&lt;/code&gt; is more of a placeholder for more business logic. Everything else will end up here.&lt;/p&gt;
&lt;p&gt;This is not a strict requirement, just some pattern that I&apos;ve ended up with. Keep it simple, don&apos;t abstract, unless you have to.&lt;/p&gt;
&lt;h2&gt;The CLI menu - &lt;code&gt;menu.ts&lt;/code&gt; {#the-cli-menu-menu-dot-ts}&lt;/h2&gt;
&lt;p&gt;Create the file &lt;code&gt;src/menu.ts&lt;/code&gt; and add this content:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { group, outro, select, text } from &quot;@clack/prompts&quot;
import type { LastRunConfig } from &quot;~/db&quot;

// We&apos;ll get into the lastRunConfig in the next section
export async function mainMenu(lastRunConfig: LastRunConfig) {
  // group() chains multiple prompts in a row
  const choices = await group(
    {
      username: () =&amp;gt;
        // text() is a basic user input prompt
        text({
          message: &quot;Enter your name: &quot;,
          // if there is a last run, use the value from it
          initialValue: lastRunConfig.username,
          // validate user input before continuing
          validate(value) {
            if (!/[a-z]/.test(value))
              // if we return from validate() the user cannot continue
              return &quot;Username should only contain lowercase characters&quot;
          },
        }),
      // results is an object that contains previous answers!
      environment: ({ result }) =&amp;gt;
        // select() with some types for inferred return types
        select&amp;lt;EnvironmentOption[], EnvironmentValue&amp;gt;({
          message: `Select your environment, ${result.username}`,
          initialValue: lastRunConfig.environment,
          options: [
            // we get auto-complete here from our types
            { value: &quot;dev&quot;, label: &quot;Dev&quot; },
            { value: &quot;qa&quot;, label: &quot;Staging&quot; },
            {
              value: &quot;prod&quot;,
              label: &quot;Prod&quot;,
              // hints are only shown when option is hovered
              hint: &quot;⚠ You better know what you are doing&quot;,
            },
          ],
        }),
    },
    {
      // in case the user interrupts the menu with CTRL + C
      onCancel: () =&amp;gt; {
        outro(&quot;K, thx, bye&quot;)
        // abort the program, no error
        process.exit(0)
      },
    },
  )

  // we could do more validation or reshaping with the choices here
  return choices
}

// --- Types
// I always use this GenericOption for typing selects
export type GenericOption = {
  label?: string | undefined
  hint?: string | undefined
}

// the actual options of our select
export type EnvironmentValue = &quot;dev&quot; | &quot;qa&quot; | &quot;prod&quot;
// some typescript inferrence magic
export type EnvironmentOption = GenericOption &amp;amp;
  Record&amp;lt;&quot;value&quot;, EnvironmentValue&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Study this code and the comments. The &lt;code&gt;group()&lt;/code&gt; is a sequence of prompts. In the end &lt;code&gt;choices&lt;/code&gt; will be an object containing &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;environment&lt;/code&gt;. This is just a sample, depending on your needs, create new properties in the &lt;code&gt;group()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The Types are something I just copy &amp;amp; paste from project to project. Notice how we pass &lt;code&gt;EnvironmentOption[]&lt;/code&gt; (array!) in the &lt;code&gt;select&amp;lt;EnvironmentOption[], EnvironmentValue&amp;gt;({...})&lt;/code&gt;. We&apos;re saying: &quot;These are the possible options and in the end I expect one of these Values&quot;.&lt;/p&gt;
&lt;p&gt;With this setup our &lt;code&gt;choices&lt;/code&gt; variable knows which values it contains. Insanely useful for later &lt;code&gt;switch case&lt;/code&gt; statements or other conditional logic.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;results&lt;/code&gt; in the callback of a prompt, we can access previous answers. Super handy, we could even add optional prompts:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const choices = await group({
  answer1: () =&amp;gt; text({ message: &quot;Whats the secret word?&quot; }),
  answer2: ({ results }) =&amp;gt; {
    if (results.answer1 === &quot;valar morgulis&quot;) {
      // optional answer2
      return text({ message: &quot;A man needs a name: &quot; })
    }
    // otherwise answer2 will be undefined
    return
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more detail about using the &lt;code&gt;@clack/prompts&lt;/code&gt;, &lt;a href=&quot;https://github.com/bombshell-dev/clack/tree/main/examples&quot;&gt;read their examples&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The last run configuration from DB - &lt;code&gt;db.ts&lt;/code&gt; {#the-last-run-configuration-from-db-db-dot-ts}&lt;/h2&gt;
&lt;p&gt;Our &lt;code&gt;mainMenu()&lt;/code&gt; accepts a &lt;code&gt;lastRunConfig&lt;/code&gt; object. It&apos;s a nice touch to remember the last time the user ran an app. We&apos;ll use a simple JSON structure to store this data.&lt;/p&gt;
&lt;p&gt;Create the &lt;code&gt;src/db.ts&lt;/code&gt; and input this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { JSONFilePreset } from &quot;lowdb/node&quot;

export type LastRunConfig = {
  username: string
  environment: &quot;qa&quot; | &quot;dev&quot; | &quot;prod&quot;
}
type Data = {
  lastRunConfig: LastRunConfig
}

const defaultData: Data = {
  lastRunConfig: {
    username: &quot;&quot;,
    environment: &quot;dev&quot;,
  },
}
const db = await JSONFilePreset&amp;lt;Data&amp;gt;(&quot;db.json&quot;, defaultData)

export async function updateLastRunConfig(config: LastRunConfig) {
  db.data.lastRunConfig = config
  await db.write()
}

export async function getLastRunConfig() {
  await db.read()
  return db.data.lastRunConfig
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code uses the &lt;code&gt;db.json&lt;/code&gt; file as our database. We export a function to add a new &lt;code&gt;lastRunConfig&lt;/code&gt; and another to get the &lt;code&gt;lastRunConfig&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Bringing it all together - &lt;code&gt;main.ts&lt;/code&gt; {#bringing-it-all-together-main-dot-ts}&lt;/h2&gt;
&lt;p&gt;Let&apos;s put these pieces together. In the &lt;code&gt;main.ts&lt;/code&gt; we write:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { mainMenu } from &quot;~/menu&quot;
import { getLastRunConfig } from &quot;~/db&quot;
import { outro } from &quot;@clack/prompts&quot;

const lastRunConfig = await getLastRunConfig()
const choices = await mainMenu(lastRunConfig)
await updateLastRunConfig(choices)

outro(`Great choices:

Username - ${choices.username}
Environment - ${choices.environment}`)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ahh, how simple and beautiful. This code explains itself!&lt;/p&gt;
&lt;p&gt;Run the script twice to see if the &lt;code&gt;lastRunConfig&lt;/code&gt; actually works.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;outro()&lt;/code&gt; is another function from &lt;code&gt;@clack/prompts&lt;/code&gt; to say a final word of &quot;Goodbye!&quot; to the user.&lt;/p&gt;
&lt;p&gt;Of course in a real world app this would only be the beginning. After asking the user some questions, we would &lt;em&gt;actually do something with it&lt;/em&gt;. However, this is not the focus of this post. You got this from here!&lt;/p&gt;
&lt;h2&gt;Conclusion {#conclusion}&lt;/h2&gt;
&lt;p&gt;We created a simple typescript CLI with &lt;code&gt;bun&lt;/code&gt;, &lt;code&gt;@clack/prompts&lt;/code&gt; and &lt;code&gt;lowdb&lt;/code&gt;. Our CLI asks the user some question and remembers the answers for the next run.&lt;/p&gt;
&lt;p&gt;Hopefully this post gave you an overview of how to approach the topic of CLI with typescript.&lt;/p&gt;
</content:encoded></item><item><title>Lucia-auth in T3-Stack</title><link>https://opensource-odyssey.net/posts/lucia-auth-in-t3-stack/</link><guid isPermaLink="true">https://opensource-odyssey.net/posts/lucia-auth-in-t3-stack/</guid><description>I love T3-Stack and sometimes I want to have authentication there too!</description><pubDate>Fri, 09 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div class=&quot;ox-hugo-toc toc&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;heading&quot;&amp;gt;Table of Contents&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#what-is-t3-stack&quot;&gt;What is T3 Stack?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#why-lucia-auth&quot;&gt;Why Lucia-auth?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#how-to-add-lucia-auth-to-nextjs-13--app-router&quot;&gt;How to add lucia-auth to NextJS &amp;gt;= 13 (App Router)&lt;/a&gt;
- &lt;a href=&quot;#optional-how-to-setup-prisma-on-nixos&quot;&gt;Optional: How to setup Prisma on NixOS&lt;/a&gt;
- &lt;a href=&quot;#installing-lucia-auth-dependencies&quot;&gt;Installing lucia-auth dependencies&lt;/a&gt;
- &lt;a href=&quot;#editing-the-schema&quot;&gt;Editing the schema&lt;/a&gt;
- &lt;a href=&quot;#adding-the-basic-lucia-auth-functionality&quot;&gt;Adding the basic lucia-auth functionality&lt;/a&gt;
- &lt;a href=&quot;#adding-lucia-to-trpc-trpcreactprovider&quot;&gt;Adding lucia to TRPC - TRPCReactProvider&lt;/a&gt;
- &lt;a href=&quot;#adding-lucia-auth-to-trpc-createtrpccontext&quot;&gt;Adding lucia-auth to TRPC - createTRPCContext&lt;/a&gt;
- &lt;a href=&quot;#adding-lucia-auth-to-trpc-protectedprocedure&quot;&gt;Adding lucia-auth to TRPC - protectedProcedure&lt;/a&gt;
- &lt;a href=&quot;#adding-lucia-auth-to-trpc-the-auth-router&quot;&gt;Adding lucia-auth to TRPC - the auth router&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#conclusion-how-to-use-lucia-auth-with-trpc&quot;&gt;Conclusion - how to use lucia-auth with TRPC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;
&amp;lt;!--endtoc--&amp;gt;&lt;/p&gt;
&lt;p&gt;Developing web applications is my daily bread and butter. Over the past year or so, I really came to love &lt;a href=&quot;https://create.t3.gg/&quot;&gt;T3-Stack&lt;/a&gt;. This post covers the setup of lucia-auth with a &lt;code&gt;create-t3-app&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;What is T3 Stack? {#what-is-t3-stack}&lt;/h2&gt;
&lt;p&gt;For the uninitiated, T3 stack is the name of a project starter for &lt;a href=&quot;https://nextjs.org/&quot;&gt;NextJS&lt;/a&gt;. Originally started by &lt;a href=&quot;https://www.youtube.com/@t3dotgg?cbrd=1&quot;&gt;Theo GG&lt;/a&gt;, T3 focuses on easing the setup of a new project with some very nice packages pre-configured.&lt;/p&gt;
&lt;p&gt;When you start your T3 project, you walk through an installation wizard. I usually take all the goodness: &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind&lt;/a&gt;, &lt;a href=&quot;https://trpc.io/&quot;&gt;TRPC&lt;/a&gt;, app router, yes please!&lt;/p&gt;
&lt;p&gt;Depending on your needs, you can start with a database with &lt;a href=&quot;https://www.prisma.io/&quot;&gt;Prisma&lt;/a&gt; or &lt;a href=&quot;https://orm.drizzle.team/&quot;&gt;Drizzle ORM&lt;/a&gt; and even have authentication with &lt;a href=&quot;https://next-auth.js.org/&quot;&gt;NextAuth&lt;/a&gt; pre-configured. All super great, but I&apos;m not such a big fan of NextAuth.&lt;/p&gt;
&lt;p&gt;The reason is that most of my projects do not use the OAuth Providers from Discord, Google or GitHub. After all, we follow the &lt;a href=&quot;https://landchad.net/&quot;&gt;LandChad&lt;/a&gt; ethos here and outsourcing the user authentication and creating this important dependency is just not something I am willing to accept. NextAuth offers a username/password credentials authentication flow, but still...&lt;/p&gt;
&lt;h2&gt;Why Lucia-auth? {#why-lucia-auth}&lt;/h2&gt;
&lt;p&gt;What else? Well &lt;a href=&quot;https://www.youtube.com/@joshtriedcoding&quot;&gt;JoshTriedCoding&lt;/a&gt; did a video on an authentication library that according to him has the same, very good level of abstraction as shadcn. &lt;strong&gt;I fucking love &lt;a href=&quot;https://ui.shadcn.com/&quot;&gt;ShadCN&lt;/a&gt;&lt;/strong&gt;. Building cool user interfaces has never been so easy for me. Naturally, Josh had me immediately hooked and I needed to try out this gem he talked about: &lt;a href=&quot;https://lucia-auth.com/&quot;&gt;Lucia-auth&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;div align=&quot;center&quot;&amp;gt;
&amp;lt;iframe width=&quot;504&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/S37uRvBr65k&quot;&amp;gt;
frameborder=&quot;1px&quot;
&amp;lt;/iframe&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;The documentation of lucia-auth is also not too bad. They have examples for &lt;a href=&quot;https://lucia-auth.com/tutorials/username-and-password/nextjs-app&quot;&gt;many different variations of authentication&lt;/a&gt;. However, they don&apos;t have an example for using it with TRPC and having procedures for login, signup and logout.&lt;/p&gt;
&lt;p&gt;This is exactly what I&apos;ll cover in the rest of the post. We&apos;ll setup a basic T3 stack app and then add lucia-auth to it. Ever step will be shown in detail, and you should be able to reproduce.&lt;/p&gt;
&lt;h2&gt;How to add lucia-auth to NextJS &amp;gt;= 13 (App Router) {#how-to-add-lucia-auth-to-nextjs-13--app-router}&lt;/h2&gt;
&lt;p&gt;Let&apos;s get started. I will use &lt;code&gt;bun&lt;/code&gt; as my package manager and runtime. You don&apos;t have to; &lt;code&gt;npm&lt;/code&gt;, &lt;code&gt;yarn&lt;/code&gt;, &lt;code&gt;pnpm&lt;/code&gt; will also do just fine. First up, we open the terminal, navigate to a directory that suits as a parent for our project and then execute:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bun create t3-app@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is how I answered the questions in the wizard:&lt;/p&gt;
&lt;p&gt;Then, T3-Stack informs us about the next steps:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Next steps:
  cd lucia-auth-trpc-t3
  bun install
  bun run db:push
  bun run dev
  git commit -m &quot;initial commit&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Optional: How to setup Prisma on NixOS {#optional-how-to-setup-prisma-on-nixos}&lt;/h4&gt;
&lt;p&gt;Now, since I&apos;m on NixOS (as you may have gathered from &lt;a href=&quot;/posts/my-linux-odyssey/&quot;&gt;My Linux Odyssey&lt;/a&gt;), I have to add a dev shell so I can use Prisma. I use the following setup in most of my typescript projects.&lt;/p&gt;
&lt;p&gt;As a prerequisite to this step, you need to have &lt;a href=&quot;https://devenv.sh/&quot;&gt;devenv&lt;/a&gt; setup. I won&apos;t go into this here, maybe at a later post in time.&lt;/p&gt;
&lt;p&gt;First, we create our &lt;code&gt;shell.nix&lt;/code&gt; that houses the project specific packages and configs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  pkgs ? import &amp;lt;nixpkgs&amp;gt; { },
}:

pkgs.mkShell {
  buildInputs = with pkgs; [
    nodejs_20
    nodePackages_latest.prisma
    prisma-engines
  ];
  shellHook = &apos;&apos;
    export PRISMA_QUERY_ENGINE_LIBRARY=${pkgs.prisma-engines}/lib/libquery_engine.node
    export PRISMA_QUERY_ENGINE_BINARY=${pkgs.prisma-engines}/bin/query-engine
    export PRISMA_SCHEMA_ENGINE_BINARY=${pkgs.prisma-engines}/bin/schema-engine
  &apos;&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The environment variables are used for our &lt;code&gt;prisma&lt;/code&gt; commands to detect the right binary.&lt;/p&gt;
&lt;p&gt;Next, we create a &lt;code&gt;flake.nix&lt;/code&gt; alongside the &lt;code&gt;shell.nix&lt;/code&gt; in our project root.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  description = &quot;Run &apos;nix develop&apos; to have a dev shell that has everything this project needs&quot;;

  inputs = {
    nixpkgs.url = &quot;github:NixOS/nixpkgs&quot;;
    flake-utils.url = &quot;github:numtide/flake-utils&quot;;
  };

  outputs =
    {
      self,
      nixpkgs,
      flake-utils,
    }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
      in
      {
        devShells.default = import ./shell.nix { inherit pkgs; };
      }
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a generic flake that imports our &lt;code&gt;shell.nix&lt;/code&gt; and installs it for the right system.&lt;/p&gt;
&lt;p&gt;The final cherry on top is a &lt;code&gt;.envrc&lt;/code&gt; file in the project root with this content:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if nix flake show &amp;amp;&amp;gt; /dev/null; then
  use flake
else
  use nix
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, if the developer uses flakes, we use the flake setup, otherwise we just use the vanilla &lt;code&gt;shell.nix&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Make sure these new files are checked into &lt;code&gt;git&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally, in the project root run &lt;code&gt;direnv allow&lt;/code&gt; to install everything. You should then be able to migrate the Prisma schema and database with &lt;code&gt;prisma migrate dev&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;Installing lucia-auth dependencies {#installing-lucia-auth-dependencies}&lt;/h4&gt;
&lt;p&gt;With the basic project setup out of the way, we can install lucia-auth. So we follow the basic &quot;&lt;a href=&quot;https://lucia-auth.com/getting-started/&quot;&gt;Getting started&lt;/a&gt;&quot;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install lucia &lt;code&gt;bun add lucia&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add the Prisma adapter for lucia &lt;code&gt;bun add @lucia-auth/adapter-prisma&lt;/code&gt;. (If you went with Drizzle in the setup, of course this needs to be the Drizzle ORM adapter)&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Editing the schema {#editing-the-schema}&lt;/h4&gt;
&lt;p&gt;Since we are going for username/password credentials authentication, we have to store users and their sessions in our database. We can achieve this like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;model User {
  id       String    @id
  username String    @unique
  password String
  sessions Session[]
}

model Session {
  id        String   @id
  userId    String
  expiresAt DateTime

  user      User     @relation(references: [id], fields: [userId], onDelete: Cascade)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Migrate the schema with &lt;code&gt;prisma migrate dev&lt;/code&gt; (or &lt;code&gt;bun prisma migrate dev&lt;/code&gt; if you skipped the NixOS step). Give it a meaningful name like &quot;add user and session&quot;.&lt;/p&gt;
&lt;h4&gt;Adding the basic lucia-auth functionality {#adding-the-basic-lucia-auth-functionality}&lt;/h4&gt;
&lt;p&gt;Create the file &lt;code&gt;src/server/auth.ts&lt;/code&gt; and add the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { PrismaAdapter } from &quot;@lucia-auth/adapter-prisma&quot;;
import { PrismaClient } from &quot;@prisma/client&quot;;
import { Lucia } from &quot;lucia&quot;;

const client = new PrismaClient();

const adapter = new PrismaAdapter(client.session, client.user);

export const lucia = new Lucia(adapter, {
  sessionCookie: {
    attributes: {
      // set to `true` when using HTTPS
      secure: process.env.NODE_ENV === &quot;production&quot;,
    },
  },
  getUserAttributes: (attributes) =&amp;gt; {
    return {
      // attributes has the type of DatabaseUserAttributes
      username: attributes.username,
    };
  },
});

declare module &quot;lucia&quot; {
  interface Register {
    Lucia: typeof lucia;
    DatabaseUserAttributes: DatabaseUserAttributes;
  }
}

// simply pick some properties from our Prisma User schema
type DatabaseUserAttributes = Pick&amp;lt;User, &quot;username&quot;&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is our basic setup for lucia. We initialize it with the Prisma adapter, and provide it with some attributes of our user.&lt;/p&gt;
&lt;p&gt;However, there is one more function I add to this file: the &lt;code&gt;validateRequest()&lt;/code&gt;. It can be called on the server side to check the cookies of the user. We could add it at the top of a page function, or - as we&apos;ll see in the next section - add it to the TRPC context.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const validateRequest = cache(
  async (): Promise&amp;lt;
    { user: User; session: Session } | { user: null; session: null }
  &amp;gt; =&amp;gt; {
    const sessionId = cookies().get(lucia.sessionCookieName)?.value ?? null;

    if (!sessionId) {
      return { user: null, session: null };
    }

    const result = await lucia.validateSession(sessionId);

    // next.js throws when you attempt to set cookies while rendering the page
    try {
      if (result.session &amp;amp;&amp;amp; result.session.fresh) {
        const sessionCookie = lucia.createSessionCookie(result.session.id);
        cookies().set(
          sessionCookie.name,
          sessionCookie.value,
          sessionCookie.attributes,
        );
      }
      if (!result.session) {
        const sessionCookie = lucia.createBlankSessionCookie();
        cookies().set(
          sessionCookie.name,
          sessionCookie.value,
          sessionCookie.attributes,
        );
      }
    } catch {}
    return result;
  },
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Adding lucia to TRPC - TRPCReactProvider {#adding-lucia-to-trpc-trpcreactprovider}&lt;/h4&gt;
&lt;p&gt;In the lucia docs they now go for server actions. With this tutorial though, we will now add the authentication to TRPC. This will allow us to have &lt;code&gt;protectedProcedures&lt;/code&gt; that automatically check if a user is logged in. Furthermore, we can add the user and session to the TRPC context. With this, we can always use the logged in userID to get for example only a specific profile from the database.&lt;/p&gt;
&lt;p&gt;First up, we have to modify the default TRPC setup to stop using the &lt;code&gt;unstable_httpBatchStreamLink&lt;/code&gt;. This is the most critical step that had me struggling for a long time. The reason is that with &lt;code&gt;unstable_httpBatchStreamLink&lt;/code&gt; the server responds immediately after request and stream the response. Great and all, but then we can no longer set cookies - which we need for auth!&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;src/trpc/react.tsx&lt;/code&gt; the TRPCReactProvider should look as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;use client&quot;;

import { loggerLink, httpBatchLink } from &quot;@trpc/client&quot;;

export function TRPCReactProvider(props: { children: React.ReactNode }) {
  const queryClient = getQueryClient();

  const [trpcClient] = useState(() =&amp;gt;
    api.createClient({
      links: [
        loggerLink({
          enabled: (op) =&amp;gt;
            process.env.NODE_ENV === &quot;development&quot; ||
            (op.direction === &quot;down&quot; &amp;amp;&amp;amp; op.result instanceof Error),
        }),
        // 💡 use this!
        httpBatchLink({
        // instead of this
        // unstable_httpBatchStreamLink({
          transformer: SuperJSON,
          url: getBaseUrl() + &quot;/api/trpc&quot;,
          headers: () =&amp;gt; {
            const headers = new Headers();
            headers.set(&quot;x-trpc-source&quot;, &quot;nextjs-react&quot;);
            return headers;
          },
        }),
      ],
    })
  );

  return (
    &amp;lt;QueryClientProvider client={queryClient}&amp;gt;
      &amp;lt;api.Provider client={trpcClient} queryClient={queryClient}&amp;gt;
        {props.children}
      &amp;lt;/api.Provider&amp;gt;
    &amp;lt;/QueryClientProvider&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Adding lucia-auth to TRPC - createTRPCContext {#adding-lucia-auth-to-trpc-createtrpccontext}&lt;/h4&gt;
&lt;p&gt;In the &lt;code&gt;src/server/api/trpc.ts&lt;/code&gt; the TRPCContext is created. The Prisma adapter is added to the context for example. Let&apos;s add the user and session to the context!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const createTRPCContext = async (opts: { headers: Headers }) =&amp;gt; {
  const { user, session } = await validateRequest();
  return {
    db,
    user,
    session,
    ...opts,
  };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That was easy! Now we can access the user and session in every procedure with &lt;code&gt;ctx.user&lt;/code&gt; and &lt;code&gt;ctx.session&lt;/code&gt;. Nice.&lt;/p&gt;
&lt;h4&gt;Adding lucia-auth to TRPC - protectedProcedure {#adding-lucia-auth-to-trpc-protectedprocedure}&lt;/h4&gt;
&lt;p&gt;Since our app features authentication, there are probably some things we only want to allow authenticated users to perform. Luckily TRPC provides a convenient way to re-use this logic.&lt;/p&gt;
&lt;p&gt;We simply create a &lt;code&gt;protectedProcedure&lt;/code&gt; in &lt;code&gt;src/server/api/trpc.ts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const protectedProcedure = t.procedure.use(({ ctx, next }) =&amp;gt; {
  if (!ctx.user || !ctx.session) {
    throw new TRPCError({ code: &quot;UNAUTHORIZED&quot; });
  }
  // ensure that session and user are non-nullable for typescript
  return next({
    ctx: { session: { ...ctx.session }, user: { ...ctx.user } },
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fantastic. Now we can use this in our routers to automatically protect certain routes.&lt;/p&gt;
&lt;h4&gt;Adding lucia-auth to TRPC - the auth router {#adding-lucia-auth-to-trpc-the-auth-router}&lt;/h4&gt;
&lt;p&gt;There is one last thing we actually need to write: a TRPCRouter for our authentication logic.&lt;/p&gt;
&lt;p&gt;We&apos;ll keep this simple and create these routers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;me&lt;/code&gt; - returns the authenticated User from the database&lt;/li&gt;
&lt;li&gt;&lt;code&gt;login&lt;/code&gt; - authenticate with an existing User&lt;/li&gt;
&lt;li&gt;&lt;code&gt;signup&lt;/code&gt; - create a User in the database&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logout&lt;/code&gt; - kill the session for the authenticated User&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For the login, signup and logout I simply adopt the example &quot;server action&quot; code from the lucia-auth docs. There is a lot of code below, but if you are reading this far, it may be very instructional.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import {
  createTRPCRouter,
  protectedProcedure,
  publicProcedure,
} from &quot;~/server/api/trpc&quot;;
import { z } from &quot;zod&quot;;
import { verify, hash } from &quot;@node-rs/argon2&quot;;
import { TRPCError } from &quot;@trpc/server&quot;;
import { lucia } from &quot;~/server/auth&quot;;
import { cookies } from &quot;next/headers&quot;;

const LoginSchema = z.object({
  username: z.string(),
  password: z.string(),
});

export const authRouter = createTRPCRouter({
  me: protectedProcedure.query(({ ctx }) =&amp;gt; {
    return ctx.db.user.findFirstOrThrow({ where: { id: ctx.user.id } });
  }),
  login: publicProcedure.input(LoginSchema).mutation(async ({ ctx, input }) =&amp;gt; {
    const existingUser = await ctx.db.user.findFirst({
      where: { username: input.username },
    });
    if (!existingUser) throw new TRPCError({ code: &quot;UNAUTHORIZED&quot; });

    const validPassword = await verify(existingUser.password, input.password, {
      memoryCost: 19456,
      timeCost: 2,
      outputLen: 32,
      parallelism: 1,
    });

    if (!validPassword) throw new TRPCError({ code: &quot;UNAUTHORIZED&quot; });

    const session = await lucia.createSession(existingUser.id, {});
    const sessionCookie = lucia.createSessionCookie(session.id);
    cookies().set(
      sessionCookie.name,
      sessionCookie.value,
      sessionCookie.attributes,
    );
    return true;
  }),

  signup: publicProcedure
    .input(LoginSchema)
    .mutation(async ({ ctx, input }) =&amp;gt; {
      const existingUser = await ctx.db.user.findFirst({
        where: { username: input.username },
      });
      if (existingUser)
        throw new TRPCError({
          code: &quot;FORBIDDEN&quot;,
          message: &quot;Username already taken&quot;,
        });

      const hashedPassword = await hash(input.password, {
        memoryCost: 19456,
        timeCost: 2,
        outputLen: 32,
        parallelism: 1,
      });

      const { id } = await ctx.db.user.create({
        data: {
          username: input.username,
          password: hashedPassword,
        },
      });

      const session = await lucia.createSession(id, {});
      const sessionCookie = lucia.createSessionCookie(session.id);
      cookies().set(
        sessionCookie.name,
        sessionCookie.value,
        sessionCookie.attributes,
      );
      return true;
    }),

  logout: protectedProcedure.mutation(async ({ ctx }) =&amp;gt; {
    await lucia.invalidateSession(ctx.session.id);
    const sessionCookie = lucia.createBlankSessionCookie();
    cookies().set(
      sessionCookie.name,
      sessionCookie.value,
      sessionCookie.attributes,
    );
  }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion - how to use lucia-auth with TRPC {#conclusion-how-to-use-lucia-auth-with-trpc}&lt;/h2&gt;
&lt;p&gt;Finally we are done! Well, at least for the basics. Lucia informs us to implement a CSRF token if there are no server actions used. So that&apos;s a TODO. Then we also need our front-end components, like the forms and pages.&lt;/p&gt;
&lt;p&gt;This post is already way too long, so I won&apos;t go into it here. But you will find great resources elsewhere.&lt;/p&gt;
&lt;p&gt;Just keep in mind that now we can use our auth router on the server and client by simply using the right import:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Import this on server pages and in server logic
import { api } from &quot;~/trpc/server&quot;;
const me = await api.auth.me()

// Import this on client components (where &quot;use client&quot; is used)
import { api } from &quot;~/trpc/react&quot;
const { data: me } = await api.auth.me.useQuery()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks for following this lengthy guide. I hope you learned something. Make sure to follow my RSS feed to always stay up to date with new posts.&lt;/p&gt;
</content:encoded></item><item><title>Chiseling dotfiles: The Digital Mason</title><link>https://opensource-odyssey.net/posts/the-digital-mason/</link><guid isPermaLink="true">https://opensource-odyssey.net/posts/the-digital-mason/</guid><description>A Digital Mason crafts configuration instead of stone. Become a Digital Mason yourself.</description><pubDate>Sat, 14 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I declare myself a &lt;strong&gt;Digital Mason&lt;/strong&gt;. Instead of stone, I work with zeros and ones. Find how you can become one, too.&lt;/p&gt;
&lt;h2&gt;The Rough Ashlar {#the-rough-ashlar}&lt;/h2&gt;
&lt;p&gt;A concept, heard in passing, deeply resonated with me: &lt;a href=&quot;https://onthesquare.net/rough-and-perfect-ashlars/&quot;&gt;The Rough Ashlar from Freemasonry&lt;/a&gt;. This has nothing to do with wearing robes, learning secret handshakes or participating in bizarre rituals. Instead that brick is the symbol of continuous improvement.&lt;/p&gt;
&lt;p&gt;The Rough Ashlar is a jagged stone straight from the quarry. It is flawed, imperfect, unusable in masonry. It has to be chiseled to become smooth, useful, perfect.&lt;/p&gt;
&lt;p&gt;Freemasonry draws the parallel of the stone to human life. A newcomer joining a lodge is the Rough Ashlar. Over their lifetime, they shape themselves into the person they want to become: The Smooth or Perfect Ashlar.&lt;/p&gt;
&lt;p&gt;Not only is this metaphor is profound for personal growth, but can be applied to system crafting as well. That&apos;s probably why it resonated so strongly with me.&lt;/p&gt;
&lt;h2&gt;Enter The Quarry {#enter-the-quarry}&lt;/h2&gt;
&lt;p&gt;Switching from proprietary garbage, like MacOS or Windows, to Linux is like stepping into the quarry: There are countless stones. Some are pre-shaped - like Ubuntu, Manjaro or even &lt;a href=&quot;https://omarchy.org/&quot;&gt;Omarchy&lt;/a&gt;; some are minimal - like Arch, where you have to install every package yourself.&lt;/p&gt;
&lt;p&gt;Picking your first distro feels like picking one of these stones. The endless choice is overwhelming. There is no &quot;just do this&quot;-solution. The first obstacle is getting a grasp of the landscape. Which stone/distro is best really depends on &lt;em&gt;your preferences and goals&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For myself, reconnaissance meant trying out different distros. Starting with Ubuntu, then Kubuntu, then Linux Mint, back to Ubuntu, then Manjaro (&lt;a href=&quot;/posts/my-linux-odyssey&quot;&gt;read more about my Linux Odyssey here&lt;/a&gt;). Those were dark days, since tinkering often lead to a broken system, which meant re-installing everything from scratch and starting over. However, through trial and error, I learned about my own preferences and gained experience in the various flavors.&lt;/p&gt;
&lt;p&gt;With understanding, it is possible to determine which packages are bloat - unwanted/unnecessary. &lt;strong&gt;Bloat is the enemy of the Perfect Ashlar.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Let The Chiseling Begin {#let-the-chiseling-begin}&lt;/h2&gt;
&lt;p&gt;I landed on Arch, btw. The beauty was that out of the box, Arch didn&apos;t have &lt;em&gt;any bloat&lt;/em&gt;!&lt;/p&gt;
&lt;p&gt;The meme of Gru from &quot;Despicable Me&quot; comes to mind with the line &quot;no packages installed&quot;.&lt;/p&gt;
&lt;p&gt;Every package had to be explicitly installed. This meant much more sweat to get things to work, let alone just right for me.&lt;/p&gt;
&lt;p&gt;The revelation of that time were &amp;lt;span class=&quot;underline&quot;&amp;gt;config files&amp;lt;/span&amp;gt;. &lt;a href=&quot;https://github.com/doomemacs/doomemacs&quot;&gt;Doom Emacs&lt;/a&gt; had already gently introduced me to the concept. All features are adjusted in the &lt;code&gt;config.el&lt;/code&gt; (or rather &lt;code&gt;config.org&lt;/code&gt; - literal programming FTW). The birth of my &lt;code&gt;.dotfiles&lt;/code&gt; repo.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Declaring&lt;/em&gt; the configuration for Doom Emacs and having the configuration in a version control system (&lt;code&gt;git&lt;/code&gt;) basically translates to &quot;savegames&quot;. Like a checkpoint in a video game that you can come back to if you die down the line.&lt;/p&gt;
&lt;p&gt;Trying out new stuff became fun! After all, if things didn&apos;t pan out &lt;code&gt;git reset --hard origin/main&lt;/code&gt; was just a few keystrokes away.&lt;/p&gt;
&lt;p&gt;So the appetite for more customization grew. Turns out that having configuration stored in file is actually pretty common. Out with the Gnome and KDE, in with the i3 and &lt;a href=&quot;https://qtile.org/&quot;&gt;qtile&lt;/a&gt;. PUT ALL THE THINGS IN CONFIG FILES!&lt;/p&gt;
&lt;p&gt;With &lt;a href=&quot;https://www.gnu.org/software/stow/&quot;&gt;gnu stow&lt;/a&gt; you can even automatically sym-link all your &lt;code&gt;.dotfiles&lt;/code&gt; from the repo to their correct location (be it in &lt;code&gt;~/&lt;/code&gt; or &lt;code&gt;~/.config/&lt;/code&gt; ). It&apos;s an amazing tool and you should give it a try.&lt;/p&gt;
&lt;p&gt;It felt like ecstasy installing &lt;em&gt;my setup&lt;/em&gt; onto a new computer. Instead of clicking through endless GUIs, it was just a &lt;code&gt;git clone&lt;/code&gt;, a &lt;code&gt;stow .&lt;/code&gt; and running a bash script to install the right programs. The power.&lt;/p&gt;
&lt;p&gt;The lesson: Configure your system &lt;em&gt;once&lt;/em&gt; and never lose the progress! Every tweak to the system was a chisel stroke on the stone. Slow and deliberate. My configuration grew with me.&lt;/p&gt;
&lt;h2&gt;The Stone That Fits {#the-stone-that-fits}&lt;/h2&gt;
&lt;p&gt;But it all deteriorated.&lt;/p&gt;
&lt;p&gt;For work I got another laptop. Installation? Easy peasy lemon squeasy. After all, it was using the same &lt;code&gt;.dotfiles&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But... The work laptop also was different: no dedicated GPU, some different programs (didn&apos;t need Steam) and most importantly &amp;lt;span class=&quot;underline&quot;&amp;gt;different versions&amp;lt;/span&amp;gt; of programs, since I ran the installation/update commands at different times.&lt;/p&gt;
&lt;p&gt;Two machines, that tried to use the same &lt;code&gt;.dotfiles&lt;/code&gt; repo and yet kept drifting apart.&lt;/p&gt;
&lt;p&gt;The longing for a new, different stone - a different material to chisel away at - grew. By nature, Arch Linux is imperative. Configuration happens through terminal commands (like the install script) or manually changing &lt;code&gt;/etc/&lt;/code&gt; files. Documenting, let alone remembering, which commands had been done on one system and not the other was futile.&lt;/p&gt;
&lt;p&gt;And then came NixOS.&lt;/p&gt;
&lt;p&gt;A colleague suggested it to me for my avid use of Doom Emacs. The creator of Doom, &lt;a href=&quot;https://github.com/hlissner&quot;&gt;Henrik Lissner&lt;/a&gt;, turned out to use nix. And the philosophy fits. Instead of interactively &lt;em&gt;instructing&lt;/em&gt; a system how to behave, everything is &lt;em&gt;declared&lt;/em&gt;. NixOS just does it for the whole system; all system packages &amp;amp; services, all users and their packages &amp;amp; services.&lt;/p&gt;
&lt;p&gt;Declaration hits two birds with one stone: The declaration &lt;em&gt;is&lt;/em&gt; the documentation &lt;em&gt;and&lt;/em&gt; handles configuration.&lt;/p&gt;
&lt;p&gt;Great! Nix solves my issue of drifting systems. The perfect stone was discovered. The diamond in the rough. The only problem was learning &lt;em&gt;how to use it&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Shaping The Perfect Ashlar {#shaping-the-perfect-ashlar}&lt;/h2&gt;
&lt;p&gt;Learning &lt;code&gt;nix&lt;/code&gt; is hard! Especially coming from the familiar imperative Linux world. However, my aspiration carried me through the initial skill-issues. If you are at this point right now: Ask for help in the discord channel, read the &lt;a href=&quot;https://wiki.nixos.org/wiki/NixOS_Wiki&quot;&gt;NixOS wiki&lt;/a&gt; &amp;amp; watch every single video of &lt;a href=&quot;https://www.youtube.com/@vimjoyer&quot;&gt;Vimjoyer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;NixOS holds your hand. It won&apos;t allow you to rebuild your declared system, if there is a syntax error. And if you still manage to fuck everything up, simply boot into a previous generation! This superpower still shapes my Opensource Odyssey.&lt;/p&gt;
&lt;p&gt;Moreover, through a single &lt;code&gt;.dotfiles&lt;/code&gt; repo you can declare multiple systems. Let machines share exactly the same packages (as in byte for byte the same package) and the same configuration. The stuff that differs is still declared, but not shared.&lt;/p&gt;
&lt;p&gt;There will be less to remember. Configuration is done once, it is done right &amp;amp; it is done forever.&lt;/p&gt;
&lt;p&gt;For instance, tools like &lt;code&gt;vim&lt;/code&gt;, &lt;code&gt;tealdeer&lt;/code&gt; and &lt;code&gt;dust&lt;/code&gt; are part of my &quot;terminal toolbelt&quot;. This toolbelt is installed &amp;lt;span class=&quot;underline&quot;&amp;gt;all machines&amp;lt;/span&amp;gt; (be it VPS, home machine or work machine).&lt;/p&gt;
&lt;p&gt;Moving into new hardware has never been easier. Last year, when a new baremetal miniserver entered my roster, the biggest hurdle was getting &lt;code&gt;ssh&lt;/code&gt; and &lt;code&gt;git&lt;/code&gt; installed. From there, the rest was a simple &lt;code&gt;nh os switch&lt;/code&gt;. Setting up the entire server according to my bespoke preferences took no more than an hour (and most of that time was just looking at the &lt;a href=&quot;https://github.com/nix-community/nh&quot;&gt;nh&lt;/a&gt; output). From what I understand even that can be further optimized so that you can install &lt;em&gt;your distro&lt;/em&gt; from a USB stick.&lt;/p&gt;
&lt;p&gt;In other words, &lt;a href=&quot;https://nixos.org/&quot;&gt;try Nix&lt;/a&gt;. Don&apos;t ever look back.&lt;/p&gt;
&lt;h2&gt;The Digital Mason’s Creed {#the-digital-mason-s-creed}&lt;/h2&gt;
&lt;p&gt;This brings us back to Digital Masonry. The Digital Mason chisels not stone, but configuration files. This is a lifelong process. With time the system becomes more and more perfect.&lt;/p&gt;
&lt;p&gt;While contemplating the concept of Digital Masonry, the Rifleman&apos;s Creed from the movie Full Metal Jacket came to mind.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://youtube.com/watch?v=Hgd2F2QNfEE&quot;&gt;https://youtube.com/watch?v=Hgd2F2QNfEE&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here is the adapted version for the Digital Mason (props to Grok for the adaptation). Repeat after me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is my config.
There are many like it, but this one is mine.&lt;/p&gt;
&lt;p&gt;My config is my best friend. It is my life.
I must master it as I must master my machine.&lt;/p&gt;
&lt;p&gt;Without me, my config is useless.
Without my config, I am useless.&lt;/p&gt;
&lt;p&gt;I must declare it true.
I must refine it sharper than the defaults that try to own me.&lt;/p&gt;
&lt;p&gt;I must chisel it before it chisels me. I will.&lt;/p&gt;
&lt;p&gt;Before the terminal, I swear this creed:
my config and myself are defenders of my sanity,
we are the masters of our bloat,
we are the saviors of my workflow.&lt;/p&gt;
&lt;p&gt;So be it, until there is no friction, but flow.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Go Forth &amp;amp; Chisel Your Config {#go-forth-and-chisel-your-config}&lt;/h2&gt;
&lt;p&gt;There you have it. No need for robes or secret handshakes. Only need the mindset of a craftsman. Look at your system like a stone that you adapt to &amp;lt;span class=&quot;underline&quot;&amp;gt;your preferences&amp;lt;/span&amp;gt;. It grows with you. It supports you. It empowers you.&lt;/p&gt;
&lt;p&gt;Go build your own temple with one keystroke at a time.&lt;/p&gt;
</content:encoded></item></channel></rss>