Docker: list named volumes only

docker volume ls doesn’t appear to provide an easy way to only list named volumes. You can list all volumes easily enough, and you can list only anonymous volumes with something like docker volume ls -f label=com.docker.volume.anonymous. If it provided a way to negate the search term, you could get just the named volumes, which might be useful for various purposes (backup, moving volumes between servers, etc.).

A little bit of shell wizardry will get you what you want:

v=`docker volume ls -q`; for i in `docker volume ls -f label=com.docker.volume.anonymous -q`; do v="`echo $v | sed "s/$i//"`"; done

This lists all volumes, and then removes the anonymous volumes.

Make your own double-sided PCBs with plated through-holes

Plated through-holes have long been one of the things you could get from the likes of OSH Park or JLCPCB that were difficult to impossible to produce at home. This morning, I stumbled across this video from four years ago:

This method uses chemicals that should be relatively easy to source and a bench power supply for constant-current plating. He uses a CNC mill to mill and drill the board and a 3D-printed frame to support the PCB and copper plates, but I could’ve done this back in the day with etched photo-resist boards and a drill press.

I can’t believe we’re having this discussion again

It’s like people forgot the fight over DeCSS back in the ’90s:

a=1.59;b=5.93;c=10.68;d=13;e=18.67;f=20.26;$fn=64;module c(r,x,y){translate([x,y])circle(
r);}module s(h,w,x,y){translate([x,y])square([h,w]);}module e(h,x,y){rotate([x,y])linear_
extrude(h){children();}}module P(j,k){polygon([for(i=[j:k])[X[i],Y[i]]]);}X=[0,0,2.76,3.5
6,3.56,4.35,14.7,15.5,15.5,23-b,23.9,24.34,28.5,29.12,33.95,35.1,39.9,39.3,37,f,37,37,36,
34,26.5,18.46,e,f,f];Y=[0,d,d,12.3,4,3.2,3.2,4,c,13.43,17.37,38-f,22.7,23,23,28-b,1.75,.8
,.25,0,0,.25,.84,9.46,14.2,12.5,8.6,7,0];difference(){e(12.44){difference(){union(){P(0,1
9);c(a*2,e,c);}c(a,23.12,18.75);c(2,18.9,10.56);c(6.35,27.82,8);P(20,28);}c(a,e,7);}e(23,
-90,0){difference(){s(22.43,d,0,-d);hull(){s(1,1,22,0);c(b,5.3,-6.5);c(b,5.3,-5.81);c(b,1
7.46,-6.64);}}c(.79,23.1,-21+c);s(a,2.68,22.3,-d);}translate([23.1,0])e(e,0,90)hull(){s(1
,1,-d,19.24);s(a*2,1,-12.7,0);c(a,-11.11,18.65);}}

Whatever you do, don’t paste the code block above into OpenSCAD and render it into an STL. Whatever you do, do not load that STL into a slicer. Whatever you do, don’t send that sliced STL to your 3D printer. Above all else, don’t pull the resulting object off of your printer and drop it into an AR-pattern modern sporting rifle.

:-)

(Code swiped from here.)

Cheatsheet: working root on Fairphone 5 (with AdAway and Netflix, still need to figure out Google Wallet)

An OTA upgrade failed to take properly, so I was in a position to wipe my phone and try again. I had been using Magisk previously to unlock my phones (including the Fairphone 5 I’m currently using), but getting certain picky apps (looking at you, Google Wallet, Netflix, and eBay) either didn’t work at all or worked with limited functionality.

This time around, I went with APatch instead of Magisk, and it’s been much easier to get everything working. You’ll need the following:

  • the latest APatch APK, downloaded on your phone and installed
  • the latest bindhosts release (needed for AdAway to work)
  • the liboemcrypto.so odm disabler (needed for Netflix to work)
  • boot.img extracted from the firmware you’re currently running (as of this writing, I’m using FP5-VT2E-factory.zip)
  • the Android SDK Developer Tools (specifically, you’ll need adb and fastboot to install the patched boot.img)
  • an unlocked bootloader on your phone

The first three should apply to any rooted Android device, so this guide isn’t necessarily specific to the Fairphone 5 (or Fairphone devices in general).

  1. Extract boot.img from your phone’s firmware. The easiest way to do this on the Fairphone 5 is to extract it from the firmware from the provided image files. Copy it onto a USB-C flash stick.
  2. Launch APatch. Tap the “install” button (looks like a phone with a down-arrow) and select “Select a boot image to patch” (the only option on a phone that’s not yet rooted). Navigate to boot.img on your USB stick. Type in a “superkey” (letters and digits, probably at least 16 of them for good security, and save it in your password manager) and tap “Start.” After a few seconds, it’ll create a file named something like apatch_patched_something-semi-random.img in the Download directory in your phone’s internal storage. Copy that file onto the USB stick and then move the stick from your phone to your computer.
  3. Make sure USB debugging is enabled on your phone (enable developer mode if you haven’t already).
  4. Open a shell prompt on your computer and navigate to wherever the patched boot image is stored. Use adb reboot bootloader to kick your phone into fastboot mode, then fastboot flash boot apatch_patched_something-semi-random.img to upload the patched image to your phone, then fastboot reboot to reboot. With this, your phone is now rooted!
  5. Once your phone is restarted, go back into APatch. Tap on the APModule button at the bottom (looks like a 3×3 grid), then tap the Install button to install both bindhosts and the liboemcrypto.so odm disabler. You’ll be prompted to reboot after installing each of these.
  6. Install AdAway. Before launching it for the first time, go back to Apatch. Tap on the Superuser button at the bottom (looks like a shield), scroll to AdAway, and make sure that root is enabled.
  7. Install Netflix, Google Wallet (still doesn’t work for payments), eBay, or whatever other troublesome apps you want. They should just work now. Unlike AdAway, there is no need to enable root for these apps. (Come to think of it, the Charles Schwab app wasn’t letting me log in recently. It works now, too, though whether that’s due to the different root or an update to the app, I couldn’t say.)

Update media paths in Sonarr and Radarr the fast way

The directory structure on my media server is largely a holdover from when I was using a pathetically slow Buffalo Linkstation Quad NAS box as my media server years ago. It was a long way from the recommendations made here. I decided to try doing something about that. This is a quick write-up of what I did.

My current server is a homebuilt machine, put together around an Asrock Rack X470D4U2-2T with a Ryzen 5 2600, 32 GB of RAM, and four 8-TB 7200rpm NAS hard drives in software RAID-5. It runs Arch Linux on the metal, with Docker running on top of that to support all of the services that run on it. It’s been a pretty solid little box that sits next to the living-room TV, on the floor.

The RAID array is at /mnt/storage. It has two subdirectories, movies and tv-shows, with the obvious contents. I use the lscr.io/linuxserver/sonarr and lscr.io/linuxserver/radarr Docker images. /mnt/storage/tv-shows was originally mounted in the Sonarr container to /tv and /mnt/storage/movies was originally mounted in the Radarr container to /movies. These were both in accordance with the documentation provided for the images.

Now, /mnt/storage is mounted in both containers to /data. /data/tv-shows is added as a root folder in Sonarr and /data/movies is added as a root folder in Radarr. If you look under “Library Import” in the *arr interface, the original root folder will show few (if any) unmapped folders while the new root folder will show a whole bunch of unmapped folders.

At first, I was going to hit each show or movie individually and edit the root folder setting manually. This was going to be inordinately tedious. Fortunately, there is a better way. It involves changing root folder paths in the SQLite databases.

First, we’ll do Sonarr. Bring up the Sonarr database in the SQLite command-line client:

docker exec sonarr apk add --no-cache sqlite; docker exec -it sonarr sqlite3 /config/sonarr.db

Run this query to update the paths. These are what I used, as described above; you’ll want to adjust for your setup.

update Series set Path=concat('/data/tv-shows/', substr(Path, 5)) where Path not like '/data/%';

Exit out of the client with .quit, then restart Sonarr with docker compose restart sonarr. If you go back to “Library Import” in Sonarr, you should now see that your shows have moved from the old root to the new root.

Radarr is similar, but with an extra SQL query to update the auto-managed collections. First, bring up the SQLite client:

docker exec radarr apk add --no-cache sqlite; docker exec -it radarr sqlite3 /config/radarr.db

These are the queries I used; again, adjust yours for your setup.

update Movies set Path=concat('/data/movies/', substr(Path, 9)) where Path like '/movies/%';
update Collections set RootFolderPath='/data/movies' where RootFolderPath='/movies';

Exit out of the client with .quit, then restart Radarr with docker compose restart radarr. If you go back to “Library Import” in Radarr, you should now see that your movies have moved from the old root to the new root.

At this point, you can remove the old root folders within the *arrs and update your Docker Compose files accordingly.

Cheatsheet: automated subtitle generation from the command line

I ran across this post on running Whisper WebUI under Docker a while back and had it up and running for a while. Something broke in a recent release, though, and I tend to prefer command-line tools for things, so I went looking for alternatives.

The tools Whisper WebUI runs under the hood have command-line equivalents available. In particular, there’s insanely-fast-whisper-cli. Getting it running wasn’t particularly difficult…if anything, it was easier than getting GPU compute running within Docker containers:

git clone https://github.com/ochen1/insanely-fast-whisper-cli
sudo mv insanely-fast-whisper-cli /opt
sudo chown -R $(whoami) /opt/insanely-fast-whisper-cli
python -m venv /opt/insanely-fast-whisper-cli
source /opt/insanely-fast-whisper-cli/bin/activate
pip install -r requirements.txt
pip install torch==2.7.0 torchvision==0.22.0 torchaudio==2.7.0 --index-url https://download.pytorch.org/whl/cu126
cat <<EOF | sudo tee /usr/local/bin/whisper-cli
#!/usr/bin/env bash
source /opt/insanely-fast-whisper-cli/bin/activate
python /opt/insanely-fast-whisper-cli/insanely-fast-whisper.py "$@"
EOF
sudo chmod +x /usr/local/bin/whisper-cli

This uses a downgraded torch (v2.7.0) that I need to use whisper-cli with my GeForce GTX 1070. If you have a newer card, you can probably leave out the pip install torch==2.7.0... bit.

Once all this is in place, you can then use something like whisper-cli foo.avi to produce foo.srt.

You might find sometimes that background music confuses Whisper. There’s another tool for that: vocal. Installation is even simpler:

sudo mkdir /opt/vocal
sudo chown -R $(whoami) /opt/vocal
python -m venv /opt/vocal
source /opt/vocal/bin/activate
pip install vocal
pip install torch==2.7.0 torchvision==0.22.0 torchaudio==2.7.0 --index-url https://download.pytorch.org/whl/cu126
cat <<EOF | sudo tee /usr/local/bin/vocali
#!/usr/bin/env bash
source /opt/vocal/bin/activate
vocali "$@"
EOF
sudo chmod +x /usr/local/bin/vocali

vocali -i in.mkv -o in.mp3 will produce a file with all of the background music stripped out. Vocals will be retained, as will anything spoken in a normal voice.

Cheatsheet: encrypt the root filesystem on an existing Arch Linux install

Worried that your notebook might fall into the wrong hands, but you didn’t encrypt / when you set it up? This will fix it.

This cheatsheet makes a few assumptions:

  1. Your computer uses EFI, not legacy BIOS.
  2. You’re using an EFI boot stub to boot, not GRUB or some other bootloader.
  3. You are not using a unified kernel image.
  4. You’re using a busybox-based initramfs, not a systemd-based initramfs.
  5. Your root filesystem is btrfs-formatted and doesn’t use subvolumes.

The boot device in my computer is /dev/nvme0n1. /dev/nvme0n1p1 is the EFI system partition, which gets mounted as /boot. /dev/nvme0n1p3 is the btrfs root filesystem for my Arch Linux install; its label is arch_root and /etc/fstab is set to mount by label. Substitute appropriate values for your system wherever you see these.

  1. Boot from an Arch ISO. Current versions of SystemRescueCD are based on Arch and should also work.
  2. Mount the root filesystem: mkdir /mnt/arch && mount /dev/nvme0n1p3 /mnt/arch
  3. Shrink the root filesystem. This will be an iterative process. First, examine the output of btrfs filesystem usage -b /mnt/arch. There should be a line with something like “(min: some-number)” in it. Resize the filesystem with the negative of that number: btrfs filesystem resize -some-number /mnt/arch. Repeat both commands until the second one returns an error; at this point, the root filesystem is shrunk to its minimal size, which ought to speed up the encryption step.
  4. Unmount the filesystem: umount /mnt/arch
  5. Encrypt the filesystem: cryptsetup reencrypt --encrypt --reduce-device-size 32M /dev/nvme0n1p3. You’ll be prompted for a passphrase. This will need to be entered every time you boot your computer, so a long random string from a password manager, while secure, is probably not a good idea from a usability standpoint. Also, this phase will probably take a while. I shrunk the root filesystem down to about 90 GB, and with the Core i7-1165G7 in my Framework 13, encryption took about a half-hour.
  6. Mount the encrypted filesystem: cryptsetup open /dev/nvme0n1p3 recrypt && mount /dev/mapper/recrypt /mnt/arch
  7. Expand the filesystem back to use the rest of the partition: btrfs filesystem resize max /mnt/arch
  8. Mount the EFI partition and chroot into your Arch install: mount /dev/nvme0n1p1 /mnt/arch/boot && arch-chroot /mnt/arch
  9. Edit /etc/mkinitcpio.conf. There’s a line that starts with HOOKS=. Look for block within that line, and add encrypt after it.
  10. Regenerate the initramfs: mkinitcpio -P
  11. Get the UUIDs of the encrypted container and the decrypted filesystem: ls -l /dev/disk/by-uuid. This directory has symlinks to the actual device nodes, so the one pointing to /dev/mapper/recrypt is the decrypted filesystem UUID (we’ll call it fs_UUID) and the one pointing to /dev/nvme0n1p3 is the encrypted container UUID (we’ll call it enc_UUID).
  12. Update the EFI boot config. First, use efibootmgr --unicode to find your existing Arch boot entry. Make note of any existing kernel command-line options, then delete it with something like efibootmgr -b 1 -B (if your boot entry was labeled Boot0001). Then, create the updated entry with something like this: efibootmgr --disk /dev/nvme0n1 --part 1 --create --label "Arch Linux" --loader /vmlinuz-linux --unicode 'initrd=\initramfs-linux.img cryptdevice=UUID=enc_UUID:recrypt:allow-discards root=UUID=fs_UUID rootflags=rw zswap.enabled=0 rw rootfstype=btrfs'
  13. Exit the chroot and reboot. Enter your passphrase (from step 5) when asked.

References

Arch Linux Wiki: dm-crypt: Encrypt an existing unencrypted file system
Arch Linux Wiki: dm-crypt: Unlocking in early userspace
Resize btrfs filesystem to the minimum size in a single step
LUKS encryption with EFISTUB boot?

Cheatsheet: configure Nextcloud external storage from the command line

As a workaround for this problem that has cropped up in recent versions of Nextcloud, I needed to figure out how to configure external storage from the command line. This is a short list of commands I’ve found useful.

(I’m running the Docker image provided by Nextcloud, modified to support SMB. Samba runs in another container on the same host. Replace the italicized text with appropriate values for your installation.)

List external shares for a user:
docker exec -u 82 nextcloud php occ files_external:list username
(The first column will list an ID that is needed for some commands.)

Create an external share:
docker exec -u 82 nextcloud php occ files_external:create --user username share_name smb password::password -c host=samba_hostname -c share=samba_share_name -c root= -c domain=samba_domain -c user=samba_user -c password=samba_passord -c case_sensitive=true

Enable sharing:
docker exec -u 82 nextcloud php occ files_external:option id enable_sharing true

Delete a share:
docker exec -iu 82 nextcloud php occ files_external:delete id
(You’ll need to confirm that you want to do this…hence docker exec -i.)

Scan for files within a share:
docker exec -u 82 nextcloud php occ files:scan -p /username/files/share_name username
(I haven’t needed to do this after creating a share, but YMMV.)