Posts in category “Linux”

WSL SSL Certificate Errors on Corporate Networks

If curl throws SSL certificate problem: unable to get local issuer certificate every time in WSL, it's usually a stale CA bundle — especially common on corporate networks running SSL inspection (Zscaler, etc.).

First, refresh the bundle:

sudo apt-get update && sudo apt-get install -y ca-certificates
sudo update-ca-certificates

That fixes most cases. If not, try a full reinstall:

sudo apt-get install --reinstall ca-certificates
sudo update-ca-certificates --fresh

On corporate networks, you likely need your company's root CA certs. You probably already have them somewhere in your Windows filesystem (.crt or .cer files). Copy them all into the trusted store:

sudo cp /c/Certificates/*.crt /usr/local/share/ca-certificates/
sudo cp /c/Certificates/*.cer /usr/local/share/ca-certificates/
sudo update-ca-certificates

Skip .pfx files — those contain private keys and are a different format.

If you don't have the certs handy, export them from Windows:

# PowerShell — list all root certs, look for your company name
Get-ChildItem Cert:\LocalMachine\Root | Select-Object Subject, Issuer | Sort-Object Subject

Or use certmgr.msc (Win+R → certmgr.msc) → Trusted Root Certification Authorities → find your company's cert → right-click → Export → Base-64 encoded X.509.

Test with curl https://google.com after each step.

whoseport: Find What's Listening on a Port (With Working Directory)

ss -tlnp and lsof -i :PORT tell you the PID and command name, but for Node.js or Python processes, "node" or "python" alone doesn't tell you which project is running. The working directory is what you actually need — and it's sitting right there in /proc/$pid/cwd.

Put this in ~/.bashrc:

whoseport() {
  if [ -z "$1" ]; then
    sudo ss -tlnp | tail -n +2 | while read -r line; do
      port=$(echo "$line" | grep -oP ':\K[0-9]+(?=\s)')
      pid=$(echo "$line" | grep -oP 'pid=\K[0-9]+' | head -1 | tr -dc '0-9')
      [ -n "$pid" ] && echo "PORT: $port | PID: $pid | CMD: $(ps -p $pid -o comm=) | CWD: $(readlink /proc/$pid/cwd)"
    done
  else
    whoseport | grep 'PORT: '$1
  fi
}

Usage:

$ whoseport 6173
PORT: 6173 | PID: 1326886 | CMD: node | CWD: /home/davidw/Projects/ccode_viewer/server

$ whoseport          # list all listening ports
PORT: 61217 | PID: 1356 | CMD: tailscaled | CWD: /
PORT: 22     | PID: 1385  | CMD: sshd      | CWD: /

A few things that went wrong before arriving at this version:

Don't use an alias for this — $1 in a single-quoted alias gets swallowed by inner sh -c calls, and local variables don't survive xargs boundaries. A function avoids both problems. The tr -dc '0-9' on the PID is not cosmetic — ss output can carry trailing whitespace or newlines that break ps -p with a "process ID list syntax error".

Linux suddenly preferring IPv6 and breaking connectivity? Fix gai.conf

Your Linux box resolves a host to its AAAA (IPv6) record, connects, but the remote isn't actually listening on IPv6. You see telnet hang on an IPv6 address. This happens when your system starts preferring IPv6 over IPv4.

One-off fix — force IPv4 for a single command:

telnet -4 api.z.ai 80

Permanent fix — edit /etc/gai.conf and uncomment this line:

precedence ::ffff:0:0/96  100

This tells getaddrinfo() to return IPv4-mapped addresses with higher precedence than IPv6. Changes take effect immediately — no restart needed. gai.conf is re-read on every getaddrinfo() call, so the next DNS lookup picks up the new rule. Existing connections are unaffected.

If /etc/gai.conf doesn't exist, just create it with that single line.

PopOS System Recovery and Partition Migration Notes

Background

Yesterday, my PopOS system broke unexpectedly. The refresh install option failed, and attempting to reinstall while preserving the old /var and /home partitions was unsuccessful.

Solution: Fresh Custom Installation

Performed a clean custom installation with:

  • /boot/efi - formatted
  • / - formatted
  • /recovery - formatted

This approach worked successfully, then migrated data from old partitions.

Partition Migration Process

Tools Used

parted --list    # Get partition information
blkid           # Get partition UUIDs

Note: Couldn't stop GDM or enter single-user mode (system would hang). Performed migration on running system instead.

1. Home Partition Migration (/dev/nvme0n1p8)

sudo mv /home /home.bak          # Backup current home
sudo mkdir /home
sudo mount /dev/nvme0n1p8 /home
ls /home                         # Confirm correct partition
sudo blkid /dev/nvme0n1p8       # Get UUID
sudo vi /etc/fstab

Added to /etc/fstab:

UUID=32e4ed56-6ed9-4f14-9585-ffff54f997b2 /home ext4 defaults 0 2

Rebooted to verify system stability.

2. Backup Partition Setup (/dev/nvme0n1p10)

sudo parted --list
sudo blkid /dev/nvme0n1p10
sudo vi /etc/fstab

Added to /etc/fstab:

UUID=8aba0dd5-7058-4036-8a08-fcd1e0e002bc /backup ext4 defaults 0 2
sudo mount -a                   # Test mount configuration

3. Var Partition Migration (/dev/nvme0n1p7)

# Mount old var partition and backup data
sudo mkdir /mnt/var
sudo mount /dev/nvme0n1p7 /mnt/var
sudo tar -czpvf /backup/old-var-backup.tar.gz -C /mnt var

or backup docker/crontabs only

sudo tar -czpvf /backup/old-docker-backup.tar.gz -C /mnt var/lib/docker
sudo tar -czpvf /backup/old-cron-backup.tar.gz -C /mnt var/spool/cron

# Prepare new var partition
sudo umount /mnt/var
sudo mkfs.ext4 /dev/nvme0n1p7   # Format old var partition

# Copy current var data to new partition
sudo mkdir /mnt/new-var
sudo mount /dev/nvme0n1p7 /mnt/new-var
sudo cp -av /var/* /mnt/new-var/

# Update fstab and switch to new partition
sudo blkid /dev/nvme0n1p7
sudo vi /etc/fstab              # Add var partition entry
sudo mv /var /var.old && sudo mkdir /var && sudo mount -a

Restore old var data

# update system
apt update && apt upgrade

sudo apt purge nano # enable vi EDITOR in visudo
sudo apt install curl

# re-enable davidwei ALL=(ALL:ALL) NOPASSWD:ALL

# install docker
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor --yes -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # add docker apt source
sudo apt update && sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# add current user to docker group
sudo usermod -aG docker davidwei

# restore old docker/cron data
# 1. Stop Docker services properly
sudo systemctl stop docker.socket
sudo systemctl stop docker.service

# 2. Extract docker data in one command
sudo tar -xzpf /backup/old-var-backup.tar.gz -C / var/lib/docker
sudo tar -xzpf /backup/old-var-backup.tar.gz -C / var/spoon/cron

or if there are spearate backups:

sudo tar -xzpf /backup/old-var-backup.tar.gz -C / var/lib/docker
sudo tar -xzpf /backup/old-var-backup.tar.gz -C / var/spool/cron/crontabs

sudo chmod og+rx /var/spool/cron/crontabs/
sudo chown davidwei:davidwei /var/spool/cron/crontabs/davidwei
sudo systemctl restart cron

# 3. Start Docker services
sudo systemctl start docker.service
sudo systemctl start docker.socket

# 4. Verify
docker volume ls
docker images
docker ps -a

# re-install tailscale / set no-expiry / rename to xps / change cloudflare dns to new IP
curl -fsSL https://tailscale.com/install.sh | sh

# re-install google-chrome
sudo dpkg -i ~/Downloads/google-chrome-stable_current_amd64.deb

# reinstall other utils
sudo apt install tree ncdu vim gnome-sushi mc clang ninja-build mesa-utils libgtk-3-dev cmake mysql-client-core-8.0 libstdc++-12-dev build-essential pkg-config htop mc ssh meld

# reinstall fcitx
sudo apt install fcitx5-rime fcitx5-chinese-addons

Key Lessons Learned

  • Fresh installation with custom partitioning was more reliable than trying to preserve old partitions during install
  • System migration on a running system worked fine when rescue mode wasn't accessible
  • Always backup critical data before formatting partitions
  • Test each partition mount configuration before proceeding to the next step

Status: Migration completed successfully. System running normally with all data preserved.

Quick Fix: Claude Code Image Paste in Linux Terminal

Can't paste images to Claude Code in your Linux terminal? Here's a one-minute fix for Kitty users.

The Fix

1. Create the script (~/bin/clip2path):

#!/usr/bin/env bash
set -e

if [ -n "$WAYLAND_DISPLAY" ]; then
    types=$(wl-paste --list-types)
    if grep -q '^image/' <<<"$types"; then
        ext=$(grep -m1 '^image/' <<<"$types" | cut -d/ -f2 | cut -d';' -f1)
        file="/tmp/clip_$(date +%s).${ext}"
        wl-paste > "$file"
        printf '%q' "$file" | kitty @ send-text --stdin
    else
        wl-paste --no-newline | kitty @ send-text --stdin
    fi
elif [ -n "$DISPLAY" ]; then
    types=$(xclip -selection clipboard -t TARGETS -o)
    if grep -q '^image/' <<<"$types"; then
        ext=$(grep -m1 '^image/' <<<"$types" | cut -d/ -f2 | cut -d';' -f1)
        file="/tmp/clip_$(date +%s).${ext}"
        xclip -selection clipboard -t "image/${ext}" -o > "$file"
        printf '%q' "$file" | kitty @ send-text --stdin
    else
        xclip -selection clipboard -o | kitty @ send-text --stdin
    fi
fi

2. Make executable:

chmod +x ~/bin/clip2path

3. Add to ~/.config/kitty/kitty.conf:

allow_remote_control yes
listen_on unix:/tmp/kitty-socket
map ctrl+v launch --type=background --allow-remote-control --keep-focus ~/bin/clip2path

4. Install dependencies:

# X11 users only
sudo apt install xclip

5. Restart Kitty

6. Setup automatic cleanup (optional):

# Add to crontab to clean old screenshots daily
(crontab -l 2>/dev/null; echo "0 3 * * * find /tmp -name 'clip_*' -type f -mtime +1 -delete") | crontab -

Now Ctrl+V automatically saves clipboard images as temp files and pastes their paths. Works on both Wayland and X11.