<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Woulve's Blog – Blog</title><link>http://blog.woulve.dev/blog/</link><description>Recent content in Blog on Woulve's Blog</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Sun, 26 Oct 2025 16:34:44 +0100</lastBuildDate><atom:link href="http://blog.woulve.dev/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>Turning a Raspberry Pi into an FM Radio Transmitter</title><link>http://blog.woulve.dev/blog/pi-webradio/</link><pubDate>Wed, 04 Mar 2026 15:24:00 +0100</pubDate><guid>http://blog.woulve.dev/blog/pi-webradio/</guid><description>
&lt;h2&gt;The Problem&lt;span class="hx:absolute hx:-mt-20" id="the-problem"&gt;&lt;/span&gt;
&lt;a href="#the-problem" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I bought a radio alarm clock for my bedroom. The goal was simple: get my phone out of there. Less doom-scrolling before bed, no &amp;ldquo;just one more video&amp;rdquo; at 1 AM, and an actual separation between rest and that attention-sucking rectangle we all carry around.&lt;/p&gt;
&lt;p&gt;The plan worked, mostly. Except I live in a bit of a ditch, and FM reception here is garbage.&lt;/p&gt;
&lt;h2&gt;The Solution&lt;span class="hx:absolute hx:-mt-20" id="the-solution"&gt;&lt;/span&gt;
&lt;a href="#the-solution" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I had a Raspberry Pi Zero 2W collecting dust. While looking for something to do with it, I stumbled upon &lt;a href="https://github.com/ChristopheJacquet/PiFmRds"target="_blank" rel="noopener"&gt;PiFmRds&lt;/a&gt;, a project that turns the Pi into an FM transmitter. No extra hardware required, just a ~20cm wire connected to GPIO 4 as an antenna.&lt;/p&gt;
&lt;p&gt;I was skeptical. Surely you can&amp;rsquo;t just&amp;hellip; broadcast FM from a bare Pi? Turns out you can. The Pi generates the FM signal by abusing its clock generator to output a frequency in the FM band. It&amp;rsquo;s a clever hack that shouldn&amp;rsquo;t work as well as it does.&lt;/p&gt;
&lt;p&gt;&lt;img src="IMG_5890.webp" alt="Raspberry Pi Zero 2W with antenna wire" loading="lazy" /&gt;&lt;/p&gt;
&lt;h2&gt;The Project&lt;span class="hx:absolute hx:-mt-20" id="the-project"&gt;&lt;/span&gt;
&lt;a href="#the-project" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I wrapped PiFmRds into a more complete solution: a systemd service that streams internet radio and broadcasts it over FM, with automatic reconnection when streams drop.&lt;/p&gt;
&lt;p&gt;The pipeline is simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;curl&lt;/code&gt; fetches an internet radio stream&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sox&lt;/code&gt; converts it to mono WAV at 44.1kHz&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pi_fm_rds&lt;/code&gt; broadcasts the audio on your chosen FM frequency&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Configuration lives in &lt;code&gt;/etc/fm-radio/config&lt;/code&gt; where you set the stream URL, broadcast frequency, and RDS metadata (station name and radio text that shows up on compatible receivers).&lt;/p&gt;
&lt;h3&gt;Web UI&lt;span class="hx:absolute hx:-mt-20" id="web-ui"&gt;&lt;/span&gt;
&lt;a href="#web-ui" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;I also built a web interface so I can manage everything from my phone. It lets you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Change the stream URL and FM frequency&lt;/li&gt;
&lt;li&gt;Set RDS station name and radio text&lt;/li&gt;
&lt;li&gt;Save station presets for quick switching&lt;/li&gt;
&lt;li&gt;Start, stop, and restart the service&lt;/li&gt;
&lt;li&gt;View live logs&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Range&lt;span class="hx:absolute hx:-mt-20" id="range"&gt;&lt;/span&gt;
&lt;a href="#range" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;This surprised me the most. I expected maybe a few meters, enough to reach the bedroom.&lt;/p&gt;
&lt;p&gt;Driving away from my house one day, I tuned into the frequency out of curiosity. I could still hear it clearly 200 meters down the road. The 20cm antenna wire apparently does more than I gave it credit for.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/&gt;&lt;/svg&gt;Note&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;p&gt;FM transmission is regulated in most countries. Keep the power low and don&amp;rsquo;t interfere with legitimate broadcasts. This is meant for personal, short-range use.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;The Setup&lt;span class="hx:absolute hx:-mt-20" id="the-setup"&gt;&lt;/span&gt;
&lt;a href="#the-setup" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git clone https://github.com/woulve/pifmwebradio.git
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd pifm-webradio
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo ./install.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The web UI at &lt;code&gt;http://&amp;lt;pi-ip&amp;gt;:8080&lt;/code&gt; handles all configuration, or edit &lt;code&gt;/etc/fm-radio/config&lt;/code&gt; directly if you prefer.&lt;/p&gt;
&lt;h2&gt;Final Thoughts&lt;span class="hx:absolute hx:-mt-20" id="final-thoughts"&gt;&lt;/span&gt;
&lt;a href="#final-thoughts" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;There&amp;rsquo;s something satisfying about repurposing old hardware. The Pi Zero 2W went from a drawer to being genuinely useful.&lt;/p&gt;
&lt;p&gt;I tried various stations, but ended up settling on a 1920s-style webradio stream. The vinyl crackling, the bandwidth limitations of the era, and the tinny speakers of the cheap alarm clock just pair well together. I sometimes let it play as I fall asleep.&lt;/p&gt;
&lt;p&gt;Sometimes the best projects are the ones that solve a small, specific problem you didn&amp;rsquo;t think had a solution.&lt;/p&gt;</description></item><item><title>Free your Kindle!</title><link>http://blog.woulve.dev/blog/kindle-jailbreak/</link><pubDate>Sun, 16 Nov 2025 17:01:00 +0100</pubDate><guid>http://blog.woulve.dev/blog/kindle-jailbreak/</guid><description>
&lt;h2&gt;Why Bother?&lt;span class="hx:absolute hx:-mt-20" id="why-bother"&gt;&lt;/span&gt;
&lt;a href="#why-bother" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Amazon recently decided to prevent users from directly downloading the ebooks they’ve paid for.&lt;br&gt;
This isn’t just a small inconvenience. It’s a reminder of how fragile “digital ownership” is when it’s wrapped in restrictive cloud ecosystems and licensing terms.&lt;/p&gt;
&lt;p&gt;If you care about actual ownership, offline access, long-term preservation, or simply being able to load your own books onto your own device, this shift is painful. Yes, there are still ways to strip DRM, but these methods are against Amazon’s TOS and noticeably more tedious than before. And even if you manage to free your files, sending them to the Kindle via &lt;em&gt;Send-to-Kindle&lt;/em&gt; works only when it feels like it.&lt;/p&gt;
&lt;p&gt;I often get books from sources other than Amazon (don&amp;rsquo;t let Jeff bezos read this), so unlocking the full functionality of my Kindle has been on my to-do list for a long while.&lt;/p&gt;
&lt;p&gt;Unfortunately, the newer Kindle OS versions weren’t vulnerable to the older WinterBreak exploit, which only supported up to version &lt;code&gt;5.18.0.2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That changed recently with the release of &lt;strong&gt;AdBreak&lt;/strong&gt;, a jailbreak that works all the way up to OS version &lt;code&gt;5.18.5.0.1&lt;/code&gt;.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/&gt;&lt;/svg&gt;Note&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;p&gt;You need a Kindle with ads enabled. If you previously disabled them, you can re-enable them by switching the account region. This is explained &lt;a href="https://kindlemodding.org/jailbreaking/AdBreak/#enabling-ads"target="_blank" rel="noopener"&gt;here&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;How the Jailbreak Works&lt;span class="hx:absolute hx:-mt-20" id="how-the-jailbreak-works"&gt;&lt;/span&gt;
&lt;a href="#how-the-jailbreak-works" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;The AdBreak process is straightforward and takes around fifteen minutes. In short, you let the Kindle download ads, copy those ads to a computer, and then run a script that exploits a use-after-free vulnerability in the Kindle’s browser sandbox. This enables code execution, which then triggers the jailbreak via a specially crafted image.&lt;/p&gt;
&lt;h3&gt;The Steps in Brief&lt;span class="hx:absolute hx:-mt-20" id="the-steps-in-brief"&gt;&lt;/span&gt;
&lt;a href="#the-steps-in-brief" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;Fill your Kindle’s storage with dummy files to prevent automatic updates.&lt;/li&gt;
&lt;li&gt;Wait for the device to download its ad bundle.&lt;/li&gt;
&lt;li&gt;Copy the ads folder to your computer.&lt;/li&gt;
&lt;li&gt;Unzip AdBreak and run the exploit script, which leverages a use-after-free vulnerability described &lt;a href="https://scarybeastsecurity.blogspot.com/2017/05/ode-to-use-after-free-one-vulnerable.html"target="_blank" rel="noopener"&gt;here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Install the required hotfix to ensure the jailbreak persists after reboot.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After that, you’re jailbroken.&lt;/p&gt;
&lt;h3&gt;Disabling Updates and Installing Apps&lt;span class="hx:absolute hx:-mt-20" id="disabling-updates-and-installing-apps"&gt;&lt;/span&gt;
&lt;a href="#disabling-updates-and-installing-apps" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Once the jailbreak is in place, you’ll want to disable OTA updates and install some essential tools.&lt;/p&gt;
&lt;h3&gt;Installing MRPI and KUAL&lt;span class="hx:absolute hx:-mt-20" id="installing-mrpi-and-kual"&gt;&lt;/span&gt;
&lt;a href="#installing-mrpi-and-kual" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Extract &lt;strong&gt;MRPI&lt;/strong&gt; (MobileRead Package Installer) and &lt;strong&gt;KUAL&lt;/strong&gt; (Kindle Unified Application Launcher) to your Kindle, then run the command &lt;code&gt;;log mrpi&lt;/code&gt; in the Kindle search bar.&lt;/p&gt;
&lt;p&gt;Inside KUAL, use the &lt;em&gt;Rename OTA binaries&lt;/em&gt; option to disable over-the-air updates permanently.&lt;/p&gt;
&lt;h3&gt;Adding Apps with KindleForge&lt;span class="hx:absolute hx:-mt-20" id="adding-apps-with-kindleforge"&gt;&lt;/span&gt;
&lt;a href="#adding-apps-with-kindleforge" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;One of the easiest ways to install additional software is by using &lt;strong&gt;KindleForge&lt;/strong&gt;, a GUI-based app store for Kindle homebrew packages.&lt;/p&gt;
&lt;p&gt;Some of my personal favorites:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ad Remover:&lt;/strong&gt; Removes ad banners&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kTerm:&lt;/strong&gt; A terminal emulator&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gambatte-K2:&lt;/strong&gt; A Game Boy emulator (yes, you can genuinely play Game Boy games on a Kindle)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Real Star: KOReader&lt;span class="hx:absolute hx:-mt-20" id="the-real-star-koreader"&gt;&lt;/span&gt;
&lt;a href="#the-real-star-koreader" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;KOReader is the main reason I wanted to jailbreak my Kindle in the first place. It supports a wide variety of formats, provides much more control over typography, and integrates beautifully with Calibre, OPDS catalogs, SSH syncing, and more.&lt;/p&gt;
&lt;p&gt;I keep all my ebooks in Calibre on my &lt;a href="http://blog.woulve.dev/blog/homelab/"&gt;home server&lt;/a&gt;, and with KOReader I can browse the entire library from anywhere and download books directly to my Kindle, essentially recreating the Kindle Store experience but for my own collection.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-green-200 hx:bg-green-100 hx:text-green-900 hx:dark:border-green-200/30 hx:dark:bg-green-900/30 hx:dark:text-green-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/&gt;&lt;/svg&gt;Tip&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;p&gt;The KOReader user guide is extensive and worth a look if you’re diving in: &lt;a href="https://koreader.rocks/user_guide/"target="_blank" rel="noopener"&gt;https://koreader.rocks/user_guide/&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Final Thoughts&lt;span class="hx:absolute hx:-mt-20" id="final-thoughts"&gt;&lt;/span&gt;
&lt;a href="#final-thoughts" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;This whole process made me dust off my Kindle and use it far more than before. It also brought back the nostalgia of jailbreaking iPods and rooting early Android phones. It was a reminder of the joy that comes with reclaiming control over your devices.&lt;/p&gt;
&lt;p&gt;&lt;img src="Kindle.webp" alt="My Kindle running a legally™ obtained version of donkey kong" loading="lazy" /&gt;&lt;/p&gt;</description></item><item><title>My Self-Hosted Home Lab</title><link>http://blog.woulve.dev/blog/homelab/</link><pubDate>Sun, 26 Oct 2025 12:00:00 +0100</pubDate><guid>http://blog.woulve.dev/blog/homelab/</guid><description>
&lt;h2&gt;Why Local-First?&lt;span class="hx:absolute hx:-mt-20" id="why-local-first"&gt;&lt;/span&gt;
&lt;a href="#why-local-first" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;This is the current trend in smart home and cloud services: everything requires connectivity and a terrible app that exists primarily to monetize you. Your light bulb doesn&amp;rsquo;t need to know your sleep schedule. Your photos don&amp;rsquo;t need to train someone else&amp;rsquo;s AI. Your documents don&amp;rsquo;t belong on a server you don&amp;rsquo;t control.&lt;/p&gt;
&lt;p&gt;So I built my own infrastructure. Everything runs on &lt;a href="https://unraid.net/"target="_blank" rel="noopener"&gt;Unraid&lt;/a&gt;, stays on my network, and works without internet. No accounts, no subscriptions, no data leaving my home. The trade-off is maintenance overhead, but the control is worth it.&lt;/p&gt;
&lt;h2&gt;The Hardware&lt;span class="hx:absolute hx:-mt-20" id="the-hardware"&gt;&lt;/span&gt;
&lt;a href="#the-hardware" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;The server started as an old gaming PC:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;Component&lt;/th&gt;
&lt;th style="text-align: left"&gt;Spec&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;CPU&lt;/td&gt;
&lt;td style="text-align: left"&gt;Intel i7-6700&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;GPU&lt;/td&gt;
&lt;td style="text-align: left"&gt;Nvidia GTX 1070 (for Plex transcoding and local AI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;RAM&lt;/td&gt;
&lt;td style="text-align: left"&gt;32GB DDR4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Storage&lt;/td&gt;
&lt;td style="text-align: left"&gt;3x 4TB HDD + 2x SSD cache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Not cutting edge, but more than enough for a home server. The GPU handles hardware transcoding in Plex and runs LLM models in Ollama.&lt;/p&gt;
&lt;h3&gt;Backups&lt;span class="hx:absolute hx:-mt-20" id="backups"&gt;&lt;/span&gt;
&lt;a href="#backups" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Weekly backups of all app data run via &lt;a href="https://github.com/mlapaglia/Borgitory"target="_blank" rel="noopener"&gt;Borgitory&lt;/a&gt;, a container that wraps Borg backup with a clean UI. Backups sync to a Hetzner storage bucket for offsite redundancy. If the server dies, I can restore everything.&lt;/p&gt;
&lt;h2&gt;Services Overview&lt;span class="hx:absolute hx:-mt-20" id="services-overview"&gt;&lt;/span&gt;
&lt;a href="#services-overview" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div class="hextra-cards hx:mt-4 hx:gap-4 hx:grid not-prose" style="--hextra-cards-grid-cols: 2;"&gt;
&lt;a
class="hextra-card hx:group hx:flex hx:flex-col hx:justify-start hx:overflow-hidden hx:rounded-lg hx:border hx:border-gray-200 hx:text-current hx:no-underline hx:dark:shadow-none hx:hover:shadow-gray-100 hx:dark:hover:shadow-none hx:shadow-gray-100 hx:active:shadow-sm hx:active:shadow-gray-200 hx:transition-all hx:duration-200 hx:hover:border-gray-300 hx:bg-transparent hx:shadow-xs hx:dark:border-neutral-800 hx:hover:bg-slate-50 hx:hover:shadow-md hx:dark:hover:border-neutral-700 hx:dark:hover:bg-neutral-900"&gt;&lt;div class="hx:mt-auto"&gt;
&lt;span class="hextra-card-icon hx:flex hx:font-semibold hx:items-start hx:gap-2 hx:pt-4 hx:px-4 hx:text-gray-700 hx:hover:text-gray-900 hx:dark:text-neutral-200 hx:dark:hover:text-neutral-50"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/&gt;&lt;/svg&gt;Home Automation&lt;/span&gt;&lt;div class="hextra-card-subtitle hx:line-clamp-3 hx:text-sm hx:font-normal hx:text-gray-500 hx:dark:text-gray-400 hx:px-4 hx:mb-4 hx:mt-2"&gt;Home Assistant, Zigbee2MQTT&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;
&lt;a
class="hextra-card hx:group hx:flex hx:flex-col hx:justify-start hx:overflow-hidden hx:rounded-lg hx:border hx:border-gray-200 hx:text-current hx:no-underline hx:dark:shadow-none hx:hover:shadow-gray-100 hx:dark:hover:shadow-none hx:shadow-gray-100 hx:active:shadow-sm hx:active:shadow-gray-200 hx:transition-all hx:duration-200 hx:hover:border-gray-300 hx:bg-transparent hx:shadow-xs hx:dark:border-neutral-800 hx:hover:bg-slate-50 hx:hover:shadow-md hx:dark:hover:border-neutral-700 hx:dark:hover:bg-neutral-900"&gt;&lt;div class="hx:mt-auto"&gt;
&lt;span class="hextra-card-icon hx:flex hx:font-semibold hx:items-start hx:gap-2 hx:pt-4 hx:px-4 hx:text-gray-700 hx:hover:text-gray-900 hx:dark:text-neutral-200 hx:dark:hover:text-neutral-50"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3"/&gt;&lt;/svg&gt;Media&lt;/span&gt;&lt;div class="hextra-card-subtitle hx:line-clamp-3 hx:text-sm hx:font-normal hx:text-gray-500 hx:dark:text-gray-400 hx:px-4 hx:mb-4 hx:mt-2"&gt;Plex, Navidrome, Calibre, Audiobookshelf&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;
&lt;a
class="hextra-card hx:group hx:flex hx:flex-col hx:justify-start hx:overflow-hidden hx:rounded-lg hx:border hx:border-gray-200 hx:text-current hx:no-underline hx:dark:shadow-none hx:hover:shadow-gray-100 hx:dark:hover:shadow-none hx:shadow-gray-100 hx:active:shadow-sm hx:active:shadow-gray-200 hx:transition-all hx:duration-200 hx:hover:border-gray-300 hx:bg-transparent hx:shadow-xs hx:dark:border-neutral-800 hx:hover:bg-slate-50 hx:hover:shadow-md hx:dark:hover:border-neutral-700 hx:dark:hover:bg-neutral-900"&gt;&lt;div class="hx:mt-auto"&gt;
&lt;span class="hextra-card-icon hx:flex hx:font-semibold hx:items-start hx:gap-2 hx:pt-4 hx:px-4 hx:text-gray-700 hx:hover:text-gray-900 hx:dark:text-neutral-200 hx:dark:hover:text-neutral-50"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/&gt;&lt;/svg&gt;Files &amp;amp; Sync&lt;/span&gt;&lt;div class="hextra-card-subtitle hx:line-clamp-3 hx:text-sm hx:font-normal hx:text-gray-500 hx:dark:text-gray-400 hx:px-4 hx:mb-4 hx:mt-2"&gt;Syncthing, Nextcloud, Paperless&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;
&lt;a
class="hextra-card hx:group hx:flex hx:flex-col hx:justify-start hx:overflow-hidden hx:rounded-lg hx:border hx:border-gray-200 hx:text-current hx:no-underline hx:dark:shadow-none hx:hover:shadow-gray-100 hx:dark:hover:shadow-none hx:shadow-gray-100 hx:active:shadow-sm hx:active:shadow-gray-200 hx:transition-all hx:duration-200 hx:hover:border-gray-300 hx:bg-transparent hx:shadow-xs hx:dark:border-neutral-800 hx:hover:bg-slate-50 hx:hover:shadow-md hx:dark:hover:border-neutral-700 hx:dark:hover:bg-neutral-900"&gt;&lt;div class="hx:mt-auto"&gt;
&lt;span class="hextra-card-icon hx:flex hx:font-semibold hx:items-start hx:gap-2 hx:pt-4 hx:px-4 hx:text-gray-700 hx:hover:text-gray-900 hx:dark:text-neutral-200 hx:dark:hover:text-neutral-50"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/&gt;&lt;/svg&gt;Local AI&lt;/span&gt;&lt;div class="hextra-card-subtitle hx:line-clamp-3 hx:text-sm hx:font-normal hx:text-gray-500 hx:dark:text-gray-400 hx:px-4 hx:mb-4 hx:mt-2"&gt;Ollama, Open WebUI&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;h2&gt;Home Automation&lt;span class="hx:absolute hx:-mt-20" id="home-automation"&gt;&lt;/span&gt;
&lt;a href="#home-automation" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;Home Assistant&lt;span class="hx:absolute hx:-mt-20" id="home-assistant"&gt;&lt;/span&gt;
&lt;a href="#home-assistant" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;img src="http://blog.woulve.dev/images/Screenshot_homeassistant.webp" alt="Home Assistant Dashboard" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.home-assistant.io/"target="_blank" rel="noopener"&gt;Home Assistant&lt;/a&gt; integrates all smart devices and handles automation logic. From turning on lights at sunset to adjusting thermostats based on occupancy, it automates a lot of useful tasks. The dashboard is fully customizable, and the community has tons of integrations for different devices.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-green-200 hx:bg-green-100 hx:text-green-900 hx:dark:border-green-200/30 hx:dark:bg-green-900/30 hx:dark:text-green-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/&gt;&lt;/svg&gt;Tip&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;p&gt;To get nice looking isometric background images, take a picture of a room and use ChatGPT to generate them with a prompt like: &amp;ldquo;Generate an isometric image of this room&amp;rdquo;.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3&gt;Zigbee2MQTT&lt;span class="hx:absolute hx:-mt-20" id="zigbee2mqtt"&gt;&lt;/span&gt;
&lt;a href="#zigbee2mqtt" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href="https://www.zigbee2mqtt.io/"target="_blank" rel="noopener"&gt;Zigbee2MQTT&lt;/a&gt; bridges Zigbee devices to MQTT, enabling local control of smart lights, sensors, and switches without any cloud dependency. I use the &lt;a href="https://sonoff.tech/en-de/products/sonoff-zigbee-3-0-usb-dongle-plus-zbdongle-e"target="_blank" rel="noopener"&gt;SONOFF Zigbee 3.0 USB Dongle Plus&lt;/a&gt; which works great with currently 40+ devices.&lt;/p&gt;
&lt;h2&gt;Media&lt;span class="hx:absolute hx:-mt-20" id="media"&gt;&lt;/span&gt;
&lt;a href="#media" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;Service&lt;/th&gt;
&lt;th style="text-align: left"&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://www.plex.tv/"target="_blank" rel="noopener"&gt;Plex&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Movies and TV shows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://www.navidrome.org/"target="_blank" rel="noopener"&gt;Navidrome&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Music streaming (Subsonic-compatible)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://www.audiobookshelf.org/"target="_blank" rel="noopener"&gt;Audiobookshelf&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Audiobook and podcast management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://calibre-ebook.com/"target="_blank" rel="noopener"&gt;Calibre&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Ebook library management&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Plex handles video, Navidrome streams my music collection to any Subsonic client, Audiobookshelf manages audiobooks and podcasts with progress tracking, and Calibre organizes my ebook library.&lt;/p&gt;
&lt;h2&gt;Photos &amp;amp; Documents&lt;span class="hx:absolute hx:-mt-20" id="photos--documents"&gt;&lt;/span&gt;
&lt;a href="#photos--documents" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;Service&lt;/th&gt;
&lt;th style="text-align: left"&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://immich.app/"target="_blank" rel="noopener"&gt;Immich&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Photo backup and browsing (Google Photos replacement)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://docs.paperless-ngx.com/"target="_blank" rel="noopener"&gt;Paperless-ngx&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Document management with OCR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://nextcloud.com/"target="_blank" rel="noopener"&gt;Nextcloud&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Calendar and contacts sync&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Immich backs up photos from my phone and provides a polished experience. Paperless-ngx scans, OCRs, and organizes documents. Nextcloud syncs calendars and contacts via CalDAV/CardDAV.&lt;/p&gt;
&lt;h2&gt;File Sync&lt;span class="hx:absolute hx:-mt-20" id="file-sync"&gt;&lt;/span&gt;
&lt;a href="#file-sync" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://syncthing.net/"target="_blank" rel="noopener"&gt;Syncthing&lt;/a&gt; keeps files synchronized across all my devices without any central server. It&amp;rsquo;s peer-to-peer, encrypted, and works very well to sync my documents and media between my laptop, desktop, and server.&lt;/p&gt;
&lt;h2&gt;Local AI&lt;span class="hx:absolute hx:-mt-20" id="local-ai"&gt;&lt;/span&gt;
&lt;a href="#local-ai" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;Service&lt;/th&gt;
&lt;th style="text-align: left"&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://ollama.ai/"target="_blank" rel="noopener"&gt;Ollama&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Local LLM inference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://github.com/open-webui/open-webui"target="_blank" rel="noopener"&gt;Open WebUI&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;ChatGPT-like interface for Ollama&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Running LLMs locally means conversations stay private. Ollama makes it easy to run models like Llama, Mistral, or Phi.&lt;/p&gt;
&lt;h2&gt;Knowledge&lt;span class="hx:absolute hx:-mt-20" id="knowledge"&gt;&lt;/span&gt;
&lt;a href="#knowledge" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://www.kiwix.org/"target="_blank" rel="noopener"&gt;Kiwix&lt;/a&gt; serves a local mirror of Wikipedia. Works offline, loads instantly, and provides access to human knowledge even when the internet is down.&lt;/p&gt;
&lt;h2&gt;Gaming&lt;span class="hx:absolute hx:-mt-20" id="gaming"&gt;&lt;/span&gt;
&lt;a href="#gaming" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;A &lt;a href="https://www.minecraft.net/"target="_blank" rel="noopener"&gt;Minecraft&lt;/a&gt; server for friends and family, routed through &lt;a href="https://playit.gg/"target="_blank" rel="noopener"&gt;playit.gg&lt;/a&gt; to make it accessible without exposing my home IP or opening ports.&lt;/p&gt;
&lt;h2&gt;External Access&lt;span class="hx:absolute hx:-mt-20" id="external-access"&gt;&lt;/span&gt;
&lt;a href="#external-access" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;For services that need to be publicly accessible, I use &lt;a href="https://www.cloudflare.com/products/tunnel/"target="_blank" rel="noopener"&gt;Cloudflare Tunnels&lt;/a&gt;. No port forwarding, no exposed IP, and Cloudflare handles SSL. The tunnel container connects outbound to Cloudflare, which proxies traffic back to my services.&lt;/p&gt;</description></item></channel></rss>