gopher://gopher.someodd.zip:70/1/catalog/tech/workstation/ Workstation Laptop and desktop user-facing tooling: window managers, browsers, password managers, dev utilities. 2026-06-12T00:00:00Z gopher.someodd.zip gopher://gopher.someodd.zip:70/0/tech/workstation/hibernation-nightmares.txt Hibernation Nightmares (Framework 13, Ryzen AI 300) 2026-03-13T00:00:00Z 2026-06-12T00:00:00Z The full saga, now CLOSED: resume was TWO bugs. The firmware-S4 reb... amdgpu RDNA 3.5 hang; kernel version is the dial. Recover with SysRq, not power-off. (UPDATE 2026-06-12: caught it. Async device-resume race; pm_async=0 kills it. Kernel version was never the dial here either. Smoking-gun chapter below. closed.) * reboot to a fresh LUKS prompt -> firmware S4 flaking out (PM: Image not found, NO S4 wake). Fix is HibernateMode= shutdown PLUS aligning the initramfs resume target to the swapfile; shutdown alone wasn't enough. (closed 2026-06-03.) - Don't force-power-off a blanked resume; it invalidates the swap header. Use SysRq S-U-B. - Lid now does DIRECT hibernate on battery, nothing on AC. suspend-then-hibernate made resume worse, not better. - Bluetooth (MT7925) is dead on kernel 7.0.7, fine on 6.19.13. The hardware (so I stop misdiagnosing it) ----------------------------------------- Ryzen AI 7 350 / Radeon 860M, codename "Krackan", RDNA 3.5, gfx1152, PCI 1002:1114, Display Core 3.5. Wi-Fi and Bluetooth are one MediaTek MT7925 combo: Wi-Fi mt7925e on PCIe, BT btusb/btmtk on USB 0e8d:0717. Framework board FRANMGCP07, BIOS 03.05. It is NOT a Phoenix 7040 -- I assumed that for a while and it sent me chasing the wrong fixes. Also: this platform offers s2idle only, no "deep" S3, so mem_sleep_default=deep in my grub line is inert. The core nightmare: resume -------------------------- The image writes fine (battery barely moves overnight), but RESUME comes up to a black screen -- often with the machine alive underneath -- and sometimes reboots instead. The crash happens before journald persists anything, so there is no log of it. The only trace is on the next cold boot: PM: Image not found (code -22) A part-way resume crash invalidates the swap header (so it won't retry into a wedged state), which is exactly why the retry boot comes up clean and empty-handed. (UPDATE 2026-05-26: that story is right for the black-screen variant. For the REBOOT variant it's backwards -- the image was never restored at all, because the firmware never came back in S4. See "the lever I never pulled" below.) Cause of the black-screen variant: an upstream amdgpu regression on RDNA 3.5. Framework's own AI 300 threads put the failure rate at 20-50% and quote support saying hibernate-to- disk on these "is spotty at best ... wait for the bug to be patched." The lever that moves it is kernel version -- people report specific kernels working where neighbours fail. The setup (still valid) ----------------------- 1. Big swapfile to hold the image: fallocate -l 64G /swapfile-hibernate chmod 600 /swapfile-hibernate mkswap /swapfile-hibernate swapon /swapfile-hibernate /etc/fstab: /swapfile-hibernate none swap defaults 0 0 2. Resume from it. Find the offset: filefrag -v /swapfile-hibernate | grep " 0:" /etc/default/grub: resume=/dev/mapper/framework--vg-root resume_offset= then: update-grub && reboot Swap must be >= RAM or hibernate fails with "Not enough suitable swap space". Gotcha I left myself: the initramfs resume target DISAGREES with the cmdline -- cmdline: resume=/dev/mapper/framework--vg-root + offset initramfs: RESUME=/dev/mapper/framework--vg-swap_1 (1G LV) It works only because the cmdline wins. The 1G LV couldn't hold an image anyway. Should be aligned; left as a TODO. (UPDATE 2026-06-03: NOT harmless under shutdown mode -- this was the bug. See below.) Lid policy: the part that flipped --------------------------------- History: s2idle only -> overnigh]]> gopher://gopher.someodd.zip:70/0/tech/workstation/wireguard-nm.txt wireguard-nm.txt 2026-06-10T00:00:00Z 2026-06-10T00:00:00Z WireGuard + NetworkManager (nm-applet) — setup notes ==============... home server (friend0) over WireGuard on the LAN. DDNS (friend0.crabdance.com) for reaching it from outside. TOPOLOGY -------- server (friend0 @ 192.168.1.210) wg0 10.8.0.1/32 listen 51820 laptop friend0 10.8.0.2/32 Keys cross over: each side's [Peer] PublicKey = the OTHER side's PUBLIC key. A private key NEVER leaves its own box. SERVER /etc/wireguard/wg0.conf [Interface] Address = 10.8.0.1/32 ListenPort = 51820 PrivateKey = [Peer] # laptop PublicKey = AllowedIPs = 10.8.0.2/32 # /32 per peer LAPTOP /etc/wireguard/friend0.conf [Interface] Address = 10.8.0.2/32 PrivateKey = [Peer] # server PublicKey = Endpoint = 192.168.1.210:51820 AllowedIPs = 10.8.0.0/32 PersistentKeepalive = 25 NM-APPLET: SET THE WG IPv4 ADDRESS (the big trap) ------------------------------------------------- NM stores the tunnel IP in IPv4 settings, NOT the WireGuard section. New WG connections default IPv4 to Automatic (DHCP). WG has no DHCP -> you get NO ipv4 addr (only fe80:: link-local). GUI fix: nm-applet > Edit Connections > > IPv4 Settings Method: Manual Add Address 10.8.0.2 Netmask 32 Gateway (blank) Save, toggle the connection off/on. CLI: nmcli connection modify friend0 ipv4.method manual \ ipv4.addresses 10.8.0.2/32 nmcli connection up friend0 Best: import the conf, it sets IPv4 Manual for you: nmcli connection import type wireguard file friend0.conf Split tunnel: IPv4 > Routes > tick "use this connection only for resources on its network". MISTAKES I MADE (the derp log) ------------------------------ 1. Put the LAPTOP's private key into the SERVER [Interface]. -> server's identity became the laptop's; pubkey mismatch; no handshake. Each box uses ITS OWN private key. 2. AllowedIPs = x/24 on a peer. Must be /32 per peer (server side). /24 collides with the interface's own subnet route. 3. No IPv4 on the tunnel (the real blocker): NM "Automatic" left only fe80::. ping had no source -> server couldn't route the reply back. Set IPv4 Manual = 10.8.0.2/24. 4. Fought the wrong interface: laptop's wg0 is a DIFFERENT VPN. The friend0 tunnel is iface "friend0". Don't down/up wg0. 5. wg syncconf does NOT set Address (wg-quick-only directive, stripped by wg-quick strip). Use wg-quick up, or add the IP by hand. TROUBLESHOOTING (in order) -------------------------- Keys pair? on each box: echo "" | wg pubkey -> must equal what the OTHER box lists as [Peer] PublicKey. Server listening? sudo ss -ulnp | grep 51820 Firewall? (no ufw here; check nft/iptables) sudo nft list ruleset sudo iptables -L INPUT -n -v allow: sudo iptables -I INPUT -p udp --dport 51820 -j ACCEPT Handshake + transfer: sudo wg show - "latest handshake" present = crypto OK - X sent / 0 received = reply blocked (firewall, return route, wrong AllowedIPs, or no IP on the iface) - counters are cumulative; old failed retries inflate "sent" Does the iface actually have its IP? ip -br addr show - only fe80:: = NO ipv4 (the NM-Automatic trap) LAN reachable at all? ping Apply a config change: edit file, then sudo wg syncconf <(wg-quick strip ) # peers/keys only (NOT Address) — or a full sudo wg-quick up DDNS note: WG resolves Endpoint ONCE at bring-up. When the home IP changes the tunnel goes stale -> use the reresolve-dns timer (wireguard-tools examples) or just restart the tunnel. ]]> gopher://gopher.someodd.zip:70/1/tech/workstation/window_maker/rofi-omni rofi-omni: a unified rofi launcher for Window Maker (windows + apps + files) 2026-06-09T00:00:00Z 2026-06-09T00:00:00Z Companion to the rofi-windows switcher: a single rofi prompt that f... Companion to the rofi-windows switcher: a single rofi prompt that fuzzy-filters across open windows (minimized ones included), installed apps, and recently-modified files under $HOME, ranked windows-then-apps-then-files with files newest-first. Bash + wmctrl + xdotool + fd, with background-refreshed caches so it stays instant -- files are mtime-sorted and capped to keep the prompt snappy. Bound to Mod4+s. gopher://gopher.someodd.zip:70/0/tech/workstation/keepass/keepassxc-no-plaintext-secrets.txt Stop Storing API Keys in Plaintext Config Files (KeepassXC + Secret Service) 2026-03-31T00:00:00Z 2026-03-31T00:00:00Z Stop storing API keys in plaintext by pulling them from KeepassXC t... /usr/share/dbus-1/services/org.freedesktop.secrets.service << EOF [D-BUS Service] Name=org.freedesktop.secrets Exec=/usr/bin/false EOF' # ALSO stop D-Bus auto-activation via org.gnome.keyring # This is a separate service file that launches gnome-keyring-daemon # with --components=secrets. If anything pokes the org.gnome.keyring # D-Bus name, gnome-keyring starts and claims org.freedesktop.secrets # too. Blocking only org.freedesktop.secrets is not enough. sudo bash -c 'cat > /usr/share/dbus-1/services/org.gnome.keyring.service << EOF [D-BUS Service] Name=org.gnome.keyring Exec=/usr/bin/false EOF' # Hide the autostart entry (idempotent -- safe to run again) mkdir -p ~/.config/autostart cp /etc/xdg/autostart/gnome-keyring-secrets.desktop ~/.config/autostart/ grep -q '^Hidden=true' ~/.config/autostart/gnome-keyring-secrets.desktop \ || echo "Hidden=true" >> ~/.config/autostart/gnome-keyring-secrets.desktop # Kill any running instance killall gnome-keyring-daemon # KeepassXC won't automatically reclaim org.freedesktop.secrets -- # it tried at startup, lost to gnome-keyring, and gave up. # Restart it (or toggle Secret Service off/on in its settings). ``` Since apt upgrade will silently restore these service files (see caveats below), I keep a script that re-neuters both in one shot. Save as ~/fix-gnome-keyring-secrets.sh: ``` #!/bin/bash set -euo pipefail if [[ $EUID -ne 0 ]]; then echo "Run this with sudo." >&2 exit 1 fi REAL_USER="${SUDO_USER:?}" cat > /usr/share/dbus-1/services/org.freedesktop.secrets.service << 'EOF' [D-BUS Service] Name=org.freedesktop.secrets Exec=/usr/bin/false EOF cat > /usr/share/dbus-1/services/org.gnome.keyring.service << 'EOF' [D-BUS Service] Name=org.gnome.keyring Exec=/usr/bin/false EOF sudo -u "$REAL_USER" killall gnome-keyring-daemon 2>/dev/null || true if pgrep -u "$REAL_USER" keepassxc >/dev/null 2>&1; then echo "Restart KeePassXC to reclaim org.freedesktop.secrets." else echo "Start KeePassXC to claim org.freedesktop.secrets." fi ``` Run with `sudo ~/fix-gnome-keyring-secrets.sh` after any gnome-keyring package update. Then delete the old gnome-keyring data if you don't need it: ``` rm ~/.local/share/keyrings/login.keyring ``` ## 3. Enable KeepassXC Secret Servi]]> gopher://gopher.someodd.zip:70/0/tech/workstation/mcp-claude-habitica-trello.txt Computer, merge all my redundant habitica todos, then identify which should be trello cards instead. 2026-01-15T00:00:00Z 2026-01-15T00:00:00Z Experiments with an MCP setup that uses Claude to merge redundant H... gopher://gopher.someodd.zip:70/0/tech/workstation/phone-files.txt phone files 2025-12-31T00:00:00Z 2025-12-31T00:00:00Z Troubleshooting notes on getting CalyxOS and Android files to show ... /dev/null) if [[ -n "$exif_date" ]]; then echo "$exif_date" return fi fi # Fallback to modification date echo "$(date -r "$file" +"%Y:%m")" } # Function to copy files if they are missing or newer copy_files() { local file_type="$1" local target_dir="$2" shift 2 local extensions=("$@") for src_dir in "${SOURCE_DIRECTORIES[@]}"; do for ext in "${extensions[@]}"; do find "$src_dir" -type f -iname "*.$ext" | while read -r file; do # Extract year and month from EXIF or modification date year_month=$(get_file_date "$file" "$file_type") year=${year_month%%:*} month=${year_month##*:} # Determine target directory target_path="$target_dir/$year/$month" mkdir -p "$target_path" # Check if the file needs to be copied dest_file="$target_path/$(basename "$file")" if [[ ! -e "$dest_file" ]] || [[ "$file" -nt "$dest_file" ]]; then cp "$file" "$des]]> gopher://gopher.someodd.zip:70/1/tech/workstation/keepass/keepass-keyring-manager KeepassXC as Key Ring Manager for Minimal DEs & WMs 2025-10-15T00:00:00Z 2025-10-15T00:00:00Z Uses KeepassXC as an SSH agent and Secret Service provider for mini... Uses KeepassXC as an SSH agent and Secret Service provider for minimal window managers like Window Maker. Targets setups where a full DE keyring (GNOME/KDE) isn't available. gopher://gopher.someodd.zip:70/0/tech/workstation/restic-on-my-laptop.txt Restic + Custom TUI on the Laptop 2025-09-16T00:00:00Z 2025-09-16T00:00:00Z Adapting the server's restic-based backups for the laptop after mov... gopher://gopher.someodd.zip:70/0/tech/workstation/projectm-audio-visualizations.txt projectm: audio visualizer (classic milkdrop/old winamp visualizations) 2025-03-13T00:00:00Z 2025-03-13T00:00:00Z Notes on installing projectM, a modern port of classic Milkdrop/Win... gopher://gopher.someodd.zip:70/0/tech/workstation/gpg_messages.txt Using GPG to encrypt messages (like email) 2024-12-30T00:00:00Z 2024-12-30T00:00:00Z Step-by-step guide to encrypting and signing arbitrary text with GP... ``` I'm not going to get into keyservers. Set key expiration. You may also want to add your key for decrypting the message you sent as well. ]]> gopher://gopher.someodd.zip:70/0/tech/workstation/monero.txt Cashing out Monero in Debian 2024-11-26T00:00:00Z 2024-11-26T00:00:00Z Cashing out Monero (XMR) from a long-neglected Debian server. Colle... gopher://gopher.someodd.zip:70/0/tech/workstation/terminals_linux_vt320.txt Using old terminals (like the VT320) with Linux 2024-10-20T00:00:00Z 2024-10-20T00:00:00Z Connecting a VT320 terminal to a Debian server over serial via a US... key` * `n key Tab setup: * clear all tabs * set 8 column tabs I think this is a reference to the tab setup: ``` T T T T T T T T 12345678901234567890123456789012345678901234567890123456789012345678901234567890 ``` ### Server setup Let's get the server to accept serial login/TTY. As you can see the device I'm dealing with is a USB/serial adapter/TTY. ``` systemctl enable serial-getty@ttyUSB0.service reboot systemctl start getty@ttyUSB0 ps -ef | grep agetty ``` I think I also edited `/etc/default/grub` (yikes!): ``` #GRUB_CMDLINE_LINUX_DEFAULT="quiet" GRUB_CMDLINE_LINUX_DEFAULT="console=ttyUSB0 console=tty0" GRUB_CMDLINE_LINUX="" ``` After editing grub, run `updategrub`. I think I may have set the speed of the TTY, but I'm not sure if that actually helped much: `stty -F /dev/ttyUSB0 speed 9600`. A handy test command: `cat /dev/ttyUSB0` and then try typing stuff on the terminal. I got a tip to set `$TERM` variable on the server. Try `echo $TERM`. I think it may help to set `TERM=vt220` or whatever you have a terminfo entry for. Try checking `/lib/terminfo/v/` and see the entries. You don't want to set that in your login script, since it'll be different if you're in an xterm versus on the terminal itself. Set it in the systemd script for the getty on the serial line. Edit `/lib/systemd/system/serial-getty@.service` and change: ``` ExecStart=-/sbin/agetty -o '-p -- \\u' --keep-baud 115200,38400,9600 %I $TERM ``` to: ``` ExecStart=-/sbin/agetty -o '-p -- \\u' --keep-baud 115200,38400,9600 %I vt220 ``` ### Updated instructions I did some extra stuff like adding to grub I'll document later... ## Terminal advice: living inside screen This advice comes from someone (sorry I forget their name, so no credit). Type 'screen', and then you get basically a fresh terminal instance. Now, you can run whatever programs you want. When you want a new "window", hit Control-a, then press c. Tapping control-a a will switch back and forth between this window and the window you were in. You can have lots of windows open, and switch between them with control-a #, where # is the window number you want to go to. (it starts at 0) There are a whole ton of other things you can do with screen, of course, but one of the more powerful things you can do is to type control-a d - which detaches the screen session. Then you can log out - and when you log back in, you can type 'screen -r' to reattach to the session you detached. And... it gets better. If you connect to this machine from another terminal, or from the network, you can type 'screen -x' on the other terminal, and attach to the same running screen session again. So you have two terminals, both interacting with the same set of "windows". And you can have different wi]]> gopher://gopher.someodd.zip:70/0/tech/workstation/lyx.txt LyX: What You See is What You Mean 2024-10-02T00:00:00Z 2024-10-02T00:00:00Z Quick notes on LyX, a WYSIWYM front end for LaTeX. Covers exporting... gopher://gopher.someodd.zip:70/0/tech/workstation/using-gnu-screen.txt Using GNU screen 2024-07-03T00:00:00Z 2024-07-03T00:00:00Z Why the author prefers GNU screen for persistent sessions — especia... gopher://gopher.someodd.zip:70/1/tech/workstation/window_maker/window_maker_megapost Productivity in Window Maker 2024-05-17T00:00:00Z 2024-05-17T00:00:00Z Living inside the lightweight Window Maker window manager on Debian... Living inside the lightweight Window Maker window manager on Debian Unstable, with tips for making it feel like a complete environment. Covers dockapps, app selection, and open TODOs the author still wants to solve. gopher://gopher.someodd.zip:70/0/tech/workstation/dillo.txt Dillo Browser 2024-04-01T00:00:00Z 2024-04-01T00:00:00Z Compiling the very lightweight Dillo browser (with Gemini and Gophe...