• Kiwix on Android (June 2026)

    Kiwix is a fantastic offline Wikipedia reader. Last time I tried installing a new library it was a usability nightmare. It’s still not great but it’s better now.

    1. Pick a Kiwix variant to install. The Play Store version is mostly fine but you can also sideload an APK that has file path restrictions listed. (See step 4).
    2. Choose a Kiwix library. The library picker is very poorly designed but it does work. I picked “Wikipedia The Free encyclopedia” at 115GB (from Feb 2026). That’s all 7+M articles. That collection also comes in a 48GB version (no pictures) and a 12GB version (short summary text only).
      Consider if “Best of Wikipedia” (50,000 articles) or “Wikipedia’s 1m Top Articles” would do for you instead, they are significantly smaller.
    3. Download. For this giant file I’m using BitTorrent and enjoying a full gigabit download, mostly from one direct web peer.
    4. Transfer the .ZIM file to your phone. If you have the Play store version it’s restricted to a couple of directories, I’m using /Android/media/org.kiwix.kiwixmobile/. The APK sideload can read ZIM files anywhere you put them.

    Their library picker doesn’t show you versions or dates. Consider looking at the direct download directory instead. The files named “maxi” include full text and pictures.

    A note on Android file transfer

    The MTP file transfer Android uses for USB is slow: limited to about 37 MiB/s for me, or about 30% of my Internet speed. The big ZIM will take an hour! MTP is also apparently kinda buggy with big files.

    The copy alternative I’m using is USB debugging mode and adb push. An AI claimed it would be about 2-3x as fast as MTP but it’s not: it copied in 33 MB/s. Good news is it did successfully copy.

    Another option is to use SolidExplorer or some other file explorer with a Wifi transfer utility. Not clear that’ll be faster than USB, WiFi speeds cap out at about 80MiB/s in practice in my experience.

    You know, for what’s at its heart a Linux system, Android is not very good at working with big files. Not a big deal in general but it must be a real nuisance for anyone maintaining a video library.

  • WebP revisited

    I’m tinkering with my postcards project. It’s a thread unroller: it takes Mastodon threads and reformats them in a way that makes sense for my “travel postcard” format I post in when I’m on a trip. This whole thing was vibe coded from the start, but I like the result enough I’m trying to turn it into a real tool and maybe a hosted product at some point.

    Anyway, today’s task was to convert the images from JPG to WebP to save space. It’s not a general image tool: this is taking tourist snaps from my pixel camera and displaying them in web box nominally 800 pixels wide. The images themselves are 1600 px and I’m mostly testing visually on a HighDPI display.

    The end result was the old 800px JPGs took 10M, simple 1600px JPGs took 36M, and 1600px WebP at quality=50 took 16M. The WebP looks as good as the JPG to me, even at the low quality setting. (The JPGs were at quality 85, I think, whatever the default is.)

    Encoding parameters

    The end result I landed on is quality=50, method=6. This is a remarkably low quality! Last time I looked at this I ended up with quality 90. But for this application with the 2x images I’d rather have a smaller file.

    FWIW I tested with an input file that’s 388k as a JPEG quality 85. Output sizes in webp from quality 10 to 90:

    60 76 96 116 132 148 168 216 364

    I also briefly tested method 4 vs method 6. Method 6 just makes slightly smaller files but using more CPU. It’s not a big difference either way.

    Code details

    WebP is good for smaller files that every browser can display, but WebP is staunchly an 8 bit format. I’m bummed, my Pixel is now outputting weirdo HDR JPGs that look nice. But it’s still a challenge to work with those, so I’m giving up on HDR for now. (I think the right answer is to resize to AVIF files but the open source tools for that are still difficult in HDR.

    I’m using PIL to create the webp files. Saving it is as easy as img.save(fn, 'WEBP', quality=50, method=6). The trick was getting a version of PIL with webp support. uv is compiling my own wheel, I think, which may be part of my problem. Anyway adding config-setting = { pillow = ["webp=enable"] } to pyproject.toml fixed it.

    AI programming notes

    I feel increasingly weird writing these work notes to myself. Everything done up above was done with coding agents, I’m not sure I learned much myself. But I spent a bunch of time tinkering to get there. Jules/Gemini had the wrong idea how to fix PIL the first time. I didn’t trust its own judgement about what quality level to use, I had to determine for myself.

  • Some notes picking up linkblog dev

    I did a lot of work 4 years ago writing my linkblog generator and then haven’t had to touch the code. I did a good job! Unfortunately it is now time to migrate off of Pinboard to Linkding, so I needed to update the code that fetches my bookmarks to use the new site.

    Dev Environment

    My code uses shot-scraper and Playwright to generate screenshots and that explicitly does not support Fedora, has various Debian dependencies on it. (I didn’t like this hack-around). So I set up distrobox as a dev environment. Now when I want to develop I have to do this

    distrobox enter linkblog-dev
    cd ~/src/linkblog/pinboard-to-static/
    source venv/bin/activate
    ./update.sh

    It also helps to run a web server in the site-generated/ directory to see the output.

    I don’t have VS.Code set up on the Fedora machine. Right now I’m using Kate, which is capable enough as a text editor. Note it has to run in the host OS, not inside the distrobox. (Files are bind mounted so there’s no container boundary problem.)

    AI programming

    Four years ago I wrote all my code by hand, of course. Now I want to use agent tools like Jules. This turned out to be pretty easy and I could just tell the agent to rewrite the Pinboard code for a new API. It read the Linkding docs and figured out most of it itself.

    The problem is I didn’t structure this code for anyone else, even an agent, to run it. It depends on some secrets (not checked in) and access to a Tailscale server. Worse, it has no tests. Yes, I hang my head in shame, this project was done very sloppily. And without tests or the ability to even run it a key part of the agent development loop doesn’t work.

    I yoloed this one change anyway and reviewed it carefully and it was fine. But I probably should adapt this code to be agent-friendly for further development. Test suites are a good idea for people too! But they are essential for an agent.

    Going forward

    I’m a little nervous about these code changes, particularly without testing. I’ll run it by hand for a few days and see how I feel.

  • Proxmox secure boot: June apocalypse

    The failed Secure Boot technology has a booby trap in it that might keep Linux boxes, including VMs, from successfully booting because some certificates start expiring June 24. The suggested fix is to enroll new security keys, which require a lot of BIOS fiddling. I was impressed Proxmox had a warning about this for your VMs and a tool for fixing it. Seems to work. It adds ms-cert=2023k,pre-enrolled-keys=1 to the disk options for the EFI partition.

    Update: secure boot keys on PC hardware

    I have some hardware I don’t have access to and I’m wondering if it will refuse to boot come June 27. Here’s some stuff you can do from a Linux CLI. RedHat also has decent docs on this.

    mokutil --sb-state will tell you if secure boot is active at all. It looks like it’s disabled on both of my Linux servers. A little surprised at that, btw.

    mokutil --db will show you the enrolled keys for secure boot. I don’t really know how to read all this, but you can find the problematic keys.

            Issuer: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation Third Party Marketplace Root
            Validity
                Not Before: Jun 27 21:22:45 2011 GMT
                Not After : Jun 27 21:32:45 2026 GMT

    Apparently the good new key will say 2023, but I don’t have one to be sure.

    Adding the new key is something you have to do in the BIOS. Installing a firmware update from the console is the surefire way. LVFS should also be able to do it, maybe as simple as fwupdmgr update. But that doesn’t seem to add a 2023 cert for me on my ASUS motherboard.

    Looking in dmesg for things about X.509 certs may be instructive for what the Linux system is doing. That won’t tell you what the BIOS is doing though.

    A fine rant

    Secure Boot is a complete disaster. In theory it was supposed to protect against pernicious malware. In practice it’s been a complete sham since the beginning because all the implementations are deeply buggy. See here for some history on that. If BIOS malware were a real thing in practice we would have seen a lot more attacks against these buggy implementations using such things as CN=DO NOT TRUST that were trusted by every major PC manufacturer.

    What Secure Boot did do was protect Microsoft’s monopoly. It continues to make installing other operating systems very difficult on PCs. About once a year I have to spend an hour+ dealing with some sort of secure boot fiasco in Linux. The most recent example: Nobara just doesn’t support it at all because they have no practical way to sign kernel packages in their build system.

    So I’m disabling Secure Boot everywhere. Including in VM BIOS, what a farce. I had one old PC that caused problems if you disabled it though. If you turned off secure boot there was no way to use some other platform features, maybe the TPM? I haven’t run into that in awhile though.

  • ISO dates and Unix locale

    I want my dates to be displayed in ISO year-month-day like this: 2026-06-09. And I want my times to be in 24 hour clocks with leading zeroes, like this: 08:37.

    There is no Unix locale to reliably do this.

    There is a hack, which is to set LC_TIME=en_DK.UTF-8. en_DK is a weird confection meaning “Danish formatting but with English words”. glibc defines it to be like I want. Problem solved?

    Unfortunately not entirely. Because actual Danish people sometimes write times with dots instead of colons. 08.37. I’m not entirely sure where all this happens but KDE Plasma is showing my system clock that way.

    What we could really use is a synthetic locale for international use. A locale that prints ISO dates and times.

    We could also use a synthetic UTC timezone. Right now I use Reykjavik time on servers, since that’s GMT+00:00 and doesn’t do timezone shifts. But it’d be nice to have an explicit “UTC” option. Some systems do have that, actually, but a lot of software expects a geographic time zone. The ones that make you pick off a map are the worst.

    (I feel like this blog post is self-parody, the most detailed of minutiae. But we use these dates and times everywhere! Why don’t they display the way I want, according to the primary international standard?)

  • Changing your hostname in Fedora

    I wanted to change the name of my Nobara Linux system. Nobara is a variant of Fedora. I’m running KDE Plasma on it. The process was remarkably manual. If you’re running GNOME there’s a GUI tool to do this. KDE doesn’t have one, but there is a 15 year old feature request.

    Basic process

    1. Add the new hostname as an alias for the old in /etc/hosts, on my system it’s pointing to 127.0.1.1.
    2. Run sudo hostnamectl set-hostname mynewname.
    3. (Optional) Reboot.

    hostnamectl(1) is a systemd program. It updates /etc/hostname and also updates some live system state. In theory the reboot isn’t necessary but it seems safer.

    Cleanup

    While the process above resets the system name, there’s still a lot of applications and services that need custom updating. I only found most of these by running rg and looking for the old name.

    • CUPS / printing: /etc/printcap has the old hostname in it. Apparently this file is generated from somewhere so you can’t just edit it. I ended up removing and reinstalling the printer.
    • akmods: /etc/pki/akmods/cacert.config has the old system name in it. As far as I can tell this is entirely cosmetic, and doesn’t need updating it. Changing this is scary because it has to do with kernel loading and secure boot signing, so I didn’t touch it. System seems fine wihtout.
    • syncthing: the hostname was baked into the syncthing node name. This seems cosmetic but is easy to change in the Web GUI.
    • KDEconnect: the hostname is baked into ~/.config/kdeconnect/config. Again, cosmetic. It’s easy to edit the file and the phone-side of KDEconnect will pick up the new name. Gemini advised I kill kdeconnectd first, apparently it will rewrite your config file out from under you?
    • /etc/hosts your old hostname is still here as an alias. It’s harmless to leave it there, and may even be papering over problems. But remove it if you want.

  • Getting a no-DRM epub from Kobo

    I bought a book from Kobo. It’s from 130 years ago, long out of copyright, none-the-less Kobo added some DRM to it. Here’s how I fixed it.

    • Buy the book on Kobo
    • Go to My Books. Click the three dots next to the book, choose “Download”.
    • Joke’s on you! Instead of downloading the book you get a 1.3KiB file named “URLLink.acsm”. This is really just a pointer to a copy of the book hosted by Adobe Digital Editions.
    • There are various ways to download the actual book. I used the Calibre ACSM Input Plugin to download it. Note this requires you log in to your Adobe ID or generate a temporary credential. Also note you have to install the plugin and restart Calibre before importing the ACSM file.
    • Joke’s on you! There’s now a ~2MB epub file but Calibre and nothing else can read it. It’s copy protected. Go ahead and delete the entry from Calibre.
    • Install the DeDRM tools plugin for Calibre. This one you have to download as a file. Restart Calibre.
    • Drag the ACSM file into Calibre again. It will download the epub, fix it, and store an unprotected version of the epub in your library. You can also export it and get a readable file.

    Note for most books you can save yourself a lot of trouble by just downloading the book from an unlicensed source. They won’t play any jokes on you, either.

  • Streaming Linux audio to a Sonos S1

    My new Linux desktop has a neat trick: I can route the sound I’m hearing to my Sonos S1 speakers. It’s always annoyed me I couldn’t do that easily before.

    The magic plumbing is PulseAudio, the Linux subsystem that handles media streams. (Or actually PipeWire, the replacement.) Basically it lets you pipe music (and video) around from program to program.

    There’s a third party Sonos controller app for Linux called Noson. It has the ability to tell the Sonos unit to play audio from a network stream. It will then create a network stream for you that is listening to PulseAudio on your Linux box.

    Once this is going the Sonos shows it is playing a track named “pulse.flac?acr=hostname:1400″. I don’t think hostname has to actually resolve for it to work.

    The other trick is to get the audio going out to PulseAudio. For me the source I want to play is Plexamp. By default Plexamp is playing out my speakers. I used pavucontrol to re-route it, choosing “noson” instead of my usual sound card / speakers for the output. I think I could also have just set this in Plexamp: right now it’s being told to play to “System default” but I can choose where it goes inside the app.

    The one downside to this setup is latency. Sonos wants to buffer a stream before playing it so anything I play is delayed about 10 seconds. Not awful but a little annoying.

    I’m doing all this to get Plexamp to drive the speakers in my house. Plexamp is really cool, working off my music library and doing all sorts of advanced DJ and mood / genre matching tricks.

    (As with anything these days, I figured all this out thanks to AI. Here’s a link to the conversation.)

    Pipewire

    Need to learn more about this! Here’s what qpwgraph shows me, you can also edit this like patch bays. coppwr is another tool that shows a similar view with more low level detail

  • USB ports on my new computer setup

    USB ports are so complicated now! So many different speeds and capabilities. I got a new computer for a Linux desktop. A Beelink SER8 and a fancy ProArt PC32QC monitor. Then I tested all the USB ports to see what they did.

    The test I did was plugging a Crucial SSD into each port with a good cable. The SSD maxes out at 10 Gbps, and I observed speeds of 10 Gbps, 5 Gbps, and 480 Mbps on various ports.

    Note: the monitor is plugged in with a USB-C cable between the monitor’s Thunderbolt upstream port to the Beelink’s USB4 40Gbps port in the back.

    Beelink SER8 ports

    There’s some good photos and info on the Beelink SER8 ports here.

    Observed drive speeds:

    • Front A: 10 Gbps
    • Front C: 10 Gbps
    • Back A, top left: 10 Gbps
    • Back A, bottom left: 480 Mbps
    • Back A, bottom right: 480 Mbps
    • Back C: ???
      This is a USB-4 40 Gbps port, I’m using it to carry video and data to my monitor.

    Bottom line here: the front ports are good. That back USB-C port is special. The back bottom ports are for low speed crap.

    ProArt PA32QC ports

    Observed drive speeds:

    • 6 Back USB-C (left): 10 Gbps
      This is actually a Thunderbolt daisy chaining port
    • 6 Back USB-C (right): ???
      This is a Thunderbolt port that goes to your computer. It’s carrying both video and data for me.
    • 7 Back USB-C (right). nothing
      This is an “upstream port”, hard drive does not work. I believe this is the port you connect to your computer if you’re not using the Thunderbolt at port 6.
    • 9 Back A: 5 Gbps
    • 9 Bottom A: 5 Gbps
    • 10 Bottom C: 5 Gbps

    This is confusing! The two ports on the bottom of the monitor are utility ordinary USB ports. Shame they’re only 5 Gbps. The back ports are complicated because this monitor also functions as a KVM so needs to separate upstream and downstream.

    I have another computer with a second ProArt monitor. That computer is plugged in from a USB 3.1 to to that back port 7 on the monitor the upstream port. That setup doesn’t work as well but I should test it again.

    GV desktop (X570-Pro)

    My ATX Linux desktop with an ASUS X570-Pro motherboard. The ports are all color coded and all seem to work as they should.

    • 2x USB-A black ports on front: 480M
    • 2x USB-A blue ports on front: 5G
    • 4x USB-A blue ports on back: 5G
    • 3x USB-A teal ports on back: 10G
    • 1x USB-C port on back: 10G

    GV desktop monitor (ProArt)

    I’ve got a ProART PA32QCV monitor plugged in to one of those 10G ports on the back of the computer. The hub in the monitor is only registering at 5G, not sure if that’s a cable problem or if it’s just a limitation of the monitor’s USB hub.

    • 6 Back USB-C (left): nothing
    • 6 Back USB-C (right): nothing
    • 7 Back USB-C (right). this is the port that has a USB cable going back to my desktop computer.
    • 9 Back A: 5 Gbps
    • 9 Bottom A: 5 Gbps
    • 10 Bottom C: 5 Gbps

    The Apple keyboard I have plugged into 9 Back A has its own USB port on it, presumably for a mouse. A drive plugged into that works at 480M.

    ProArt USB hub

    Having done all this testing I now understand what the ProArt PA32QCV is doing with USB. And it’s all very sensible.

    You have two choices for plugging the monitor in to your computer. Either a video cable (HDMI or DisplayPort) along with a USB 3 cable to port 7 on the back. Or else a USB 4 / Thunderbolt cable into port 6 (right) on the back carrying both video and data.

    Either way, the ProArt gives you three ports for regular use: keyboard, mouse, speakers, drives. 9 Back A, 9 Bottom A, and 10 Bottom C. These all work at 5 Gbit.

    The ProArt also has two unusual ports on the back in block 6. That’s really for the Thunderbolt subsystem: if you plug in the right side to your computer, then it’s daisy-chained to the left side and you can use it for a second monitor, or a peripheral, etc. It works at least up to 10G, wouldn’t surprise me if it does 40G. If you connect your monitor to the computer via video+USB both of these ports do nothing.

  • RII K22S keyboard bluetooth pairing

    The trick is to hold down Fn+Shift for a few seconds until the light binks rapidly.

    The Rii K22S keyboard is a nice little wireless keyboard. It’s well built and usable but I wouldn’t want to use it as my primary keyboard. Just perfect if you need an occasional couch keyboard or a backup for computer tinkering. I particularly like that it supports both Bluetooth and a proprietary 2.4GHz wireless dongle. (I don’t think it works via USB cable though, too bad.)

    Anyway, the manual lies to you about Bluetooth pairing. It says

    Press Fn+Shift, enter the Bluetooth pairing mode, the BT indicator flickers slowly. To pair with your device, open Bluetooth on the device, select RI721 BLE Keyboard and click on the connection.

    The misinformation here is that flickering slowly is pairing mode. It’s not. You have to hold down Fn+Shift a few seconds and then the light will start flashing fast (twice a second?). Only then is it in pairing mode.

    Rii makes a bunch of neat small wireless keyboards. I like the ultra-mini K06, about the size of a game controller. Not at all suitable for real typing but just perfect for a home theater PC where you need to press Win-Tab occasionally so the stupid game window comes into focus.