Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Techne: A practical knowledge base

This is my practical knowledge base. The intended audience is mostly me, but some notes are written in a tutorial-like format.

I can make no guarantees that any of the content here will work for others. If you break things after following something here, you’re the one responsible.

Anyway, you may find something useful here after all. But still, reader beware 🤨.

Apache

Install mod-evasive

sudo apt install libapache2-mod-evasive

Add mod_evasive to VirtualHost

<IfModule mod_evasive20.c>
    DOSHashTableSize 3097
    DOSPageCount 2
    DOSSiteCount 50
    DOSPageInterval 1
    DOSSiteInterval 1
    DOSBlockingPeriod 60
    DOSEmailNotify <hyperreal@moonshadow.dev>
</IfModule>

Restart apache2:

sudo systemctl restart apache2.service

Atop

Get lowest memfree for given analysis date

atopsar -r /var/log/atop/atop_20240703 -m -R 1 | awk 'NR<7{print $0;next}{print $0| "sort -k 3,4"}' | head -11
ArgumentDescription
atopsaratop’s system activity report
-r /var/log/atop/atop_20240703Log file to use
-mMemory- and swap-occupation
-R 1Summarize 1 sample into 1 sample. Log file contains samples of 10 minutes, so this will summarize each sample. -R 6 will summarize one sample per 60 minutes.
`awk ’NR<7{print $0;next}{print $0“sort -k 3,4”}’`
head -11Get the top 11 lines of output

Get top 3 memory processes for given analysis date

atopsar -G -r /var/log/atop/atop_20240710

Identify top-five most frequently executed process during logging period

atop -r /var/log/atop/atop_20241123 -P PRG | grep -oP "(?<=\()[[:alnum:]]{1,}(?=\))" | sort | uniq -c | sort -k1rn | head -5

Count the number of times a particular process has been detected during logging period

atop -r /var/log/atop/atop_20241123 -P PRG | egrep "docker" | awk '{print $5}' | uniq -c -w5

Generate a chart of the number of instances of a particular process during logging period

atop -r /var/log/atop/atop_20241123 -P PRG | egrep "docker" | awk '{print $5}' | uniq -c -w8 | \
    gnuplot -e "set terminal dumb 80 20; unset key; set style data labels; set xdata time; set xlabel 'Time'; set ylabel 'docker'; set timefmt '%H:%M:%S'; plot '-' using 2:1:ytic(1) with histeps"

Generate a PNG chart of the number of instances of a particular process during logging period

atop -r /var/log/atop/atop_20241123 -P PRG | awk '{print $5}' | uniq -c -w8 | \
    gnuplot -e "set title 'Process Count'; set offset 1,1,1,1; set autoscale xy; set mxtics; set mytics; \
        set style line 12 lc rgb '#ddccdd' lt 1 lw 1.5; set style line 13 lc rgb '#ddccdd' lt 1 lw 0.5; set grid xtics mxtics ytics mytics \
        back ls 12, ls 13; set terminal png size 1920,1280 enhanced font '/usr/share/fonts/liberation/LiberationSans-Regular.ttf,10'; \
        set output 'plot_$(date +'%Y-%m-%d_%H:%M:%S')_${RANDOM}.png'; set style data labels; set xdata time; set xlabel 'Time' font \
        '/usr/share/fonts/liberation/LiberationSans-Regular.ttf,8'; set ylabel 'Count' font \
        '/usr/share/fonts/liberation/LiberationSans-Regular.ttf,8'; set timefmt '%H:%M:%S'; plot '-' using 2:1 with histeps"

Identify top-ten most frequently executed binaries from /sbin or /usr/sbin during logging period

for i in $(atop -r /var/log/atop/atop_20241123 -P PRG | grep -oP "(?<=\()[[:alnum:]]{1,}(?=\))" | sort | uniq -c | sort -k1rn | head -10); do 
    which "${i}" 2>/dev/null | grep sbin; 
done

Identify disks with over 90% activity during logging period

atopsar -r /var/log/atop/atop_20241123 -d | egrep '^[0-9].*|(9[0-9]|[0-9]{3,})%'

Identify processes responsible for most disk I/O during logging period

atopsar -r /var/log/atop/atop_20241123 -D | sed 's/\%//g' | awk -v k=50 '$4 > k || $8 > k || $12 > k' | sed -r 's/([0-9]{1,})/%/5;s/([0-9]{1,})/%/7;s/([0-9]{1,})/%/9'

Identify periods of heavy swap activity during logging period

atopsar -r /var/log/atop/atop_20241123 -s | awk -v k=1000 '$2 > k || $3 > k || $4 > k'

Identify logical volumes with high activity or high average queue during logging period

atopsar -r /var/log/atop/atop_20241123 -l -S | sed 's/\%//g' | awk -v k=50 -v j=100 '$3 > k || $8 > j' | sed -r 's/([0-9]{1,})/%/4'

Identify processes consuming more than half of all available CPUs during logging period

(( k = $(grep -c proc /proc/cpuinfo) / 2 * 100 ))
atopsar -r /var/log/atop/atop_20241123 -P | sed 's/\%//g' | awk -v k=$k '$4 > k || $8 > k || $12 > k' | sed -r 's/([0-9]{1,})/%/5;s/([0-9]{1,})/%/7;s/([0-9]{1,})/%/9'

Identify time of peak memory utilization during logging period

atopsar -r /var/log/atop/atop_20241123 -m -R 1 | awk 'NR<7{print $0;next}{print $0| "sort -k 3,3"}' | head -15

Bash

sed 'Nq;d' file.txt

Prepend text to beginning of file

echo "new content" | cat - file.txt > temp
mv temp file.txt
sed -i '1s/^/new content\n/' file.txt

Remove lines containing only whitespace

sed -i '/^\s*$/d' file.txt

Delete nth line from file

# N should be the target line number
sed -i 'Nd' file.txt

Replace an entire line in file by line number

# N should be the target line number
sed -i 'Ns/.*/replacement-line/' file.txt

Heredoc

cat << EOF > file.txt
The current working directory is $PWD.
You are logged in as $(whoami).
EOF

Plain-print the difference between two files

Suppose we have two files: packages.fedora and packages.

packages.fedora:

autossh
bash-completion
bat
bc
borgmatic
bzip2
cmake
curl
diff-so-fancy
diffutils
dnf-plugins-core

packages:

bash-completion
bc
bzip2
curl
diffutils
dnf-plugins-core

To plain-print the lines that exist in packages.fedora but do not exist in packages:

comm -23 <(sort packages.fedora) <(sort packages)

Output:

autossh
bat
borgmatic
cmake
diff-so-fancy
  • The comm command compares two sorted files line by line.
  • The -23 flag is shorthand for -2 and -3.
  • -2 suppresses column 2 (lines unique to packages).
  • -3 suppresses column 3 (lines that appear in both files).

Split large text file into smaller files with equal number of lines

split -l 60 bigfile.txt prefix-

Loop through lines of file

while read line; do
    echo "$line";
done </path/to/file.txt

Use grep to find URLs from HTML file

cat urls.html | grep -Eo "(http|https)://[a-zA-Z0-9./?=_%:-]*"

Use Awk to print the first line of ps aux output followed by each grepped line

To find all cron processes with ps aux:

ps aux | awk 'NR<2{print $0;next}{print $0 | grep "cron"}' | grep -v "awk"

Btrfs

Create systemd.mount unit for Btrfs on external HDD

Get the UUID of the Btrfs partition:

sudo blkid -s UUID -o value /dev/sda1

d3b5b724-a57a-49a5-ad1d-13ccf3acc52f

Edit /etc/systemd/system/mnt-internet_archive.mount:

[Unit]
Description=internet_archive Btrfs subvolume
DefaultDependencies=yes

[Mount]
What=/dev/disk/by-uuid/d3b5b724-a57a-49a5-ad1d-13ccf3acc52f
Where=/mnt/internet_archive
Type=btrfs
Options=subvol=@internet_archive,compress=zstd:1

[Install]
WantedBy=multi-user.target
DefaultDependencies=yesThe mount unit automatically acquires Before=umount.target and Conflicts=umount.target. Local filesystems automatically gain After=local-fs-pre.target and Before=local-fs.target. Network mounts, such as NFS, automatically acquire After=remote-fs-pre.target network.target network-online.target and Before=remote-fs.target.
Options=subvol=@internet_archive,compress=zstd:1Use the subvolume @internet_archive and use zstd compression level 1.

Note that the name of the unit file, e.g. mnt-internet_archive.mount, must correspond to the Where=/mnt/internet_archive directive, such that the filesystem path separator / in the Where directive is replaced by an en dash in the unit file name.

Reload the daemons and enable the mount unit:

sudo systemctl daemon-reload
sudo systemctl enable --now mnt-internet_archive.mount

Setup encrypted external drive for backups

Prepare the external drive

sudo cryptsetup --type luks2 -y -v luksFormat /dev/sda1
sudo cryptsetup -v luksOpen /dev/sda1 cryptbackup
sudo mkfs.btrfs /dev/mapper/cryptbackup
sudo mkdir /srv/backup
sudo mount -o noatime,compress=zstd:1 /dev/mapper/cryptbackup /srv/backup
sudo restorecon -Rv /srv/backup

Setup /etc/crypttab

sudo blkid -s UUID -o value /dev/sda1 | sudo tee -a /etc/crypttab

Add the following line to /etc/crypttab:

cryptbackup UUID=<UUID of /dev/sda1> none discard

Setup /etc/fstab

sudo blkid -s UUID -o value /dev/mapper/cryptbackup | sudo tee -a /etc/fstab

Add the following line to /etc/fstab:

UUID=<UUID of /dev/mapper/cryptbackup> /srv/backup btrfs compress=zstd:1,nofail 0 0

Reload the daemons:

sudo systemctl daemon-reload

Mount the filesystems:

sudo mount -av

Btrfs backup script

#!/usr/bin/env bash

LOGFILE="/var/log/btrfs-backup.log"
SNAP_DATE=$(date '+%Y-%m-%d_%H%M%S')

# Check if device is mounted
if ! grep "/srv/backup" /etc/mtab >/dev/null; then
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup device is not mounted." | tee -a "$LOGFILE"
    notify-send -i computer-fail "Backup device is not mounted"
    exit 1
fi

create_snapshot() {
    if ! btrfs subvolume snapshot -r "$1" "${1}/.snapshots/$2-$SNAP_DATE" >/dev/null; then
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] Error creating snapshot of $1" | tee -a "$LOGFILE"
        notify-send -i computer-fail "Error creating snapshot of $1"
        exit 1
    else
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] Create snapshot of $1: OK" | tee -a "$LOGFILE"
    fi
}

send_snapshot() {
    mkdir -p "/srv/backup/$SNAP_DATE"
    if ! btrfs send -q "${1}/.snapshots/$2-$SNAP_DATE" | btrfs receive -q "/srv/backup/$SNAP_DATE"; then
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] Error sending snapshot of $1 to /srv/backup" | tee -a "$LOGFILE"
        notify-send -i computer-fail "Error sending snapshot of $1 to /srv/backup"
        exit 1
    else
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] Send snapshot of $1 to /srv/backup: OK" | tee -a "$LOGFILE"
    fi
}

# Create root and home snapshots
create_snapshot "/" "root"
create_snapshot "/home" "home"

# Send root and home snapshots
send_snapshot "/" "root"
send_snapshot "/home" "home"

Move/copy the script to /etc/cron.daily/btrfs-backup.

CachyOS

Gaming

Resources:

DLSS

Edit /etc/environment.

PROTON_DLSS_UPGRADE=1
PROTON_DLSS_INDICATOR=1

Fix possible RTX 4000+ performance issues

Edit /etc/environment.

PROTON_NVIDIA_LIBS_NO_32BIT=1

Increase maximum shader cache size (NVIDIA)

Edit /etc/environment.

__GL_SHADER_DISK_CACHE_SIZE=12000000000

Reboot the system.

scx_bpfland

Source: https://wiki.cachyos.org/configuration/sched-ext

Edit /etc/scx_loader.toml.

default_sched = "scx_bpfland" # Edit this line to the scheduler you want scx_loader to start at boot
default_mode = "Auto" # Possible values: "Auto", "Gaming", "LowLatency", "PowerSave".

Enable or restart the scx_loader.service

sudo systemctl enable --now scx_loader.service
sudo systemctl restart scx_loader.service

Caddy

IP whitelist

irc.hyperreal.coffee {
    @me {
        client_ip 1.2.3.4
    }
    handle @me {
        reverse_proxy localhost:9000
    }
    respond "You are attempting to access protected resources!" 403
}

Reverse proxy for qBittorrent over Tailscale

hostname.tailnet.ts.net:8888 {
    reverse_proxy localhost:8080 {
        header_up Host localhost:8080
        header_up X-Forwarded-Host {host}:{hostport}
        header_up -Origin
        header_up -Referer
    }
}

Cgit

Install Cgit with Caddy

Dependencies

  • xcaddy package from releases page.

Install caddy-cgi:

xcaddy build --with github.com/aksdb/caddy-cgi/v2

Install remaining dependencies:

sudo apt install gitolite3 cgit python-is-python3 python3-pygments python3-markdown docutils-common groff

Configuration

Make a git user.

sudo adduser --system --shell /bin/bash --group --disabled-password --home /home/git git

Configure gitolite for the git user in ~/.gitolite.rc.

UMASK => 0027,
GIT_CONFIG_KEYS => 'gitweb.description gitweb.owner gitweb.homepage gitweb.category',

Add caddy user to the git group.

sudo usermod -aG git caddy

Configure cgit in /etc/cgitrc:

#
# cgit config
# see cgitrc(5) for details

css=/cgit/cgit.css
logo=/cgit/cgit.png
favicon=/cgit/favicon.ico

enable-index-links=1
enable-commit-graph=1
enable-log-filecount=1
enable-log-linecount=1
enable-git-config=1

branch-sort=age
repository-sort=name

clone-url=https://git.hyperreal.coffee/$CGIT_REPO_URL git://git.hyperreal.coffee/$CGIT_REPO_URL ssh://git@git.hyperreal.coffee:$CGIT_REPO_URL

root-title=hyperreal.coffee Git repositories
root-desc=Source code and configs for my projects

##
## List of common mimetypes
##
mimetype.gif=image/gif
mimetype.html=text/html
mimetype.jpg=image/jpeg
mimetype.jpeg=image/jpeg
mimetype.pdf=application/pdf
mimetype.png=image/png
mimetype.svg=image/svg+xml

# Enable syntax highlighting
source-filter=/usr/lib/cgit/filters/syntax-highlighting.py

# Format markdown, rst, manpages, text files, html files, and org files.
about-filter=/usr/lib/cgit/filters/about-formatting.sh

##
### Search for these files in the root of the default branch of repositories
### for coming up with the about page:
##
readme=:README.md
readme=:README.org

robots=noindex, nofollow

section=personal-config

repo.url=doom-emacs-config
repo.path=/home/git/repositories/doom-emacs-config.git
repo.desc=My Doom Emacs config

Chimera Linux

Installation

Requirements

  • UEFI
  • LVM on LUKS with unencrypted /boot

Disk partitioning

Use cfdisk to create the following partition layout.

Partition TypeSize
EFI+600M
boot+900M
LinuxRemaining space

Format the unencrypted partitions:

mkfs.vfat /dev/nvme0n1p1
mkfs.ext4 /dev/nvme0n1p2

Create LUKS on the remaining partition:

cryptsetup luksFormat /dev/nvme0n1p3
cryptsetup luksOpen /dev/nvme0n1p3 crypt

Create a LVM2 volume group for /dev/nvme0n1p3, which is located at /dev/mapper/crypt:

vgcreate chimera /dev/mapper/crypt

Create logical volumes in the volume group:

lvcreate --name swap -L 8G chimera
lvcreate --name root -l 100%FREE chimera

Create the filesystems for the logical volumes:

mkfs.ext4 /dev/chimera/root
mkswap /dev/chimera/swap

Create mount points for the chroot and mount the filesystems:

mkdir /media/root
mount /dev/chimera/root /media/root
mkdir /media/root/boot
mount /dev/nvme0n1p2 /media/root/boot
mkdir /media/root/boot/efi
mount /dev/nvme0n1p1 /media/root/boot/efi

Bootstrap

Chimera-bootstrap and chroot

chimera-bootstrap /media/root
chimera-chroot /media/root

Update the system:

apk update
apk upgrade --available

Install kernel, cryptsetup, and lvm2 packages:

apk add linux-stable cryptsetup-scripts lvm2

Fstab

genfstab / >> /etc/fstab

Crypttab

echo "crypt /dev/disk/by-uuid/$(blkid -s UUID -o value /dev/nvme0n1p3) none luks" > /etc/crypttab

Initramfs refresh

update-initramfs -c -k all

GRUB

apk add grub-x86_64-efi
grub-install --efi-directory=/boot/efi --target=x86_64-efi

Post-installation

passwd root
apk add zsh bash
useradd -c "Jeffrey Serio" -m -s /usr/bin/zsh -U jas
passwd jas

Add the following lines to /etc/doas.conf:

# Give jas access
permit nopass jas

Set hostname, timezone, and hwclock:

echo "falinesti" > /etc/hostname
ln -sf /usr/share/zoneinfo/America/Chicago /etc/localtime
echo localtime > /etc/hwclock

Xorg and Xfce4

apk add xserver-xorg xfce4

Reboot the machine.

Post-reboot

Login as jas. Run startxfce4. Connect to the internet via NetworkManager.

Ensure wireplumber and pipewire-pulse are enabled:

dinitctl enable wireplumber
dinitctl start wireplumber
dinitctl enable pipewire-pulse
dinitctl start pipewire-pulse

Install CPU microcode:

doas apk add ucode-intel
doas update-initramfs -c -k all

Install other packages

doas apk add chrony
doas dinitctl enable chrony
doas apk add ...

Containers

Self-hosted container registry with web UI

Source: https://github.com/Joxit/docker-registry-ui

Docker/Podman compose

services:
  registry-ui:
    image: joxit/docker-registry-ui:main
    restart: always
    ports:
      - "127.0.0.1:4433:80"
    environment:
      - SINGLE_REGISTRY=true
      - REGISTRY_TITLE=hyperreal's Container Registry
      - DELETE_IMAGES=true
      - SHOW_CONTENT_DIGEST=true
      - NGINX_PROXY_PASS_URL=http://registry-server:5000
      - SHOW_CATALOG_NB_TAGS=true
      - CATALOG_MIN_BRANCHES=1
      - CATALOG_MAX_BRANCHES=1
      - TAGLIST_PAGE_SIZE=100
      - REGISTRY_SECURED=false
      - CATALOG_ELEMENTS_LIMIT=1000
    container_name: registry-ui

  registry-server:
    image: registry:2.8.2
    restart: always
    environment:
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Origin: '[http://aux-remote.carp-wyvern.ts.net]'
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Methods: '[HEAD,GET,OPTIONS,DELETE]'
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Credentials: '[true]'
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers: '[Accept,Cache-Control]'
      REGISTRY_HTTP_HEADERS_Access-Control-Expose-Headers: '[Docker-Content-Digest]'
      REGISTRY_STORAGE_DELETE_ENABLED: 'true'
    volumes:
      - ./registry/data:/var/lib/registry
    container_name: registry-server

Authorization and Authentication

For a public registry with authentication, the following headers are needed:

environment:
  REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers: '[Authorization,Accept,Cache-Control]'

For a private registry without authentication, the following headers are needed:

environment:
  REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers: '[Accept,Cache-Control]'

Caddy reverse proxy

Public registry

registry.hyperreal.coffee {
    reverse_proxy localhost:4433
}

Private registry via Tailnet

aux-remote.carp-wyvern.ts.net {
    reverse_proxy localhost:4433
}

Ensure the following is added to /etc/default/tailscaled:

TS_PERMIT_CERT_UID=caddy

The above will ensure Caddy receives SSL certs from the Tailscale daemon.

Debian

Install Debian with LUKS2 Btrfs and GRUB via Debootstrap

Source: https://gist.github.com/meeas/b574e4bede396783b1898c90afa20a30

  • Use a Debian Live ISO
  • Single LUKS2 encrypted partition
  • Single Btrfs filesystem with @, @home, @swap, and other subvolumes
  • Encrypted swapfile in Btrfs subvolume
  • systemd-boot bootloader
  • Optional removal of crypto keys from RAM during laptop suspend
  • Optional configurations for laptops

Pre-installation setup

Boot into the live ISO, open a terminal, and become root. Install the needed packages.

sudo -i
apt update
apt install -y debootstrap cryptsetup arch-install-scripts

Create partitions.

cfdisk /dev/nvme0n1
  • GPT partition table
  • 512M /dev/nvme0n1p1 EFI System partition (EF00)
  • 100%+ /dev/nvme0n1p2 Linux filesystem
mkfs.fat -F 32 -n EFI /dev/nvme0n1p1
cryptsetup -y -v --type luks2 luksFormat --label Debian /dev/nvme0n1p2
cryptsetup luksOpen /dev/nvme0n1p2 cryptroot
mkfs.btrfs /dev/mapper/cryptroot

Make Btrfs subvolumes.

mount /dev/mapper/cryptroot /mnt
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
btrfs subvolume create /mnt/@swap
umount -lR /mnt

Re-mount subvolumes as partitions.

mount -t btrfs -o defaults,subvol=@,compress=zstd:1 /dev/mapper/cryptroot /mnt
mkdir -p /mnt/{boot,home}
mkdir /mnt/boot/efi
mount /dev/nvme0n1p1 /mnt/boot/efi
mount -t btrfs -o defaults,subvol=@home,compress=zstd:1 /dev/mapper/cryptroot /mnt/home

Setup swapfile.

mkdir -p /mnt/swap
mount -t btrfs -o subvol=@swap /dev/mapper/cryptroot /mnt/swap
touch /mnt/swap/swapfile
chmod 600 /mnt/swap/swapfile
chattr +C /mnt/swap/swapfile
btrfs property set ./swapfile compression none
dd if=/dev/zero of=/mnt/swap/swapfile bs=1M count=16384
mkswap /mnt/swap/swapfile
swapon /mnt/swap/swapfile

Base installation

Create a nested subvolume for /var/log under the @ subvolume. This will be automounted with @ so there is no need to add it to /etc/fstab. Nested subvolumes are not included in snapshots of the parent subvolume. Creating a nested subvolume for /var/log will ensure the log files remain untouched when we restore the rootfs from a snapshot.

mkdir -p /mnt/var
btrfs subvolume create /mnt/var/log
debootstrap --arch amd64 <suite> /mnt

Copy the mounted file systems table.

Bind the pseudo-filesystems for chroot.

mount --rbind /dev /mnt/dev
mount --rbind /sys /mnt/sys
mount -t proc proc /mnt/proc

Generate fstab.

genfstab -U /mnt >> /mnt/etc/fstab

Chroot into the new system.

cp -v /etc/resolv.conf /mnt/etc/
chroot /mnt

Configure the new installation

Set the timezone, locale, keyboard configuration, and console.

apt install -y locales
dpkg-reconfigure tzdata locales keyboard-configuration console-setup

Set the hostname.

echo 'hostname' > /etc/hostname
echo '127.0.1.1 hostname.localdomain hostname' >> /etc/hosts

Configure APT sources on /etc/apt/sources.list

deb https://deb.debian.org/debian <suite> main contrib non-free non-free-firmware
deb https://deb.debian.org/debian <suite>-updates main contrib non-free non-free-firmware
deb https://deb.debian.org/debian <suite>-backports main contrib non-free non-free-firmware
deb https://deb.debian.org/debian-security <suite>-security main contrib non-free non-free-firmware

Install essential packages.

apt update -t <suite>-backports
apt dist-upgrade -t <suite>-backports
apt install -y neovim linux-image-amd64 linux-headers-amd64 firmware-linux firmware-linux-nonfree sudo command-not-found systemd-timesyncd systemd-resolved cryptsetup cryptsetup-initramfs efibootmgr btrfs-progs grub-efi

Install desktop environment.

apt install task-gnome-desktop task-desktop task-ssh-server

If installing on a laptop:

sudo apt install -y task-laptop powertop

Create users and groups.

passwd root
adduser jas
echo "jas ALL=(ALL) NOPASSWD: ALL" | tee -a /etc/sudoers.d/jas
chmod 440 /etc/sudoers.d/jas
usermod -aG systemd-journal jas

Setting up the bootloader

Optional package for extra protection of suspended laptops.

apt install cryptsetup-suspend

Setup encryption parameters.

blkid -s UUID -o value /dev/nvme0n1p2

Edit /etc/crypttab.

cryptroot UUID=<uuid> none luks

Setup bootloader.

grub-install --target=x86_64-efi --efi-directory=/boot/efi --recheck --bootloader-id="Debian"

Edit /etc/default/grub.

GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX=""
GRUB_ENABLE_CRYPTODISK=y
GRUB_TERMINAL=console

Update grub.

update-grub

Exit chroot and reboot.

exit
umount -lR /mnt
reboot

Emergency recovery from live ISO

sudo -i
cryptsetup luksOpen /dev/nvme0n1p2 cryptroot
mount -t btrfs -o defaults,subvol=@,compress=zstd:1 /dev/mapper/cryptroot /mnt
mount /dev/nvme0n1p1 /mnt/boot/efi
mount -t btrfs -o defaults,subvol=@home,compress=zstd:1 /dev/mapper/cryptroot /mnt/home
mount -t btrfs -o subvol=@swap /dev/mapper/cryptroot /mnt/swap
swapon /mnt/swap/swapfile
mount --rbind /dev /mnt/dev
mount --rbind /sys /mnt/sys
mount -t proc proc /mnt/proc
chroot /mnt

DietPi

systemd-logind

Install libpam-systemd:

sudo apt install -y libpam-systemd

Unmask and enable systemd-logind:

sudo systemctl unmask systemd-logind
sudo systemctl enable systemd-logind
sudo systemctl reboot

DRM

Nix-shell

Install libgourou in nix-shell.

nix-shell -p libgourou

Docker

docker run \
    -v "${PATH_TO_ADOBE_CREDS}:/home/libgourou/.adept" \
    -v "$(pwd):/home/libgourou/files" \
    --rm \
    bcliang/docker-libgourou \
    <name_of_adept_metafile.acsm>

Extract PDF or EPUB from ACSM file

Register the device with Adobe username and password.

adept_activate -u user -p password

Download the ACSM file. Make sure the ACSM file is in the current working directory.

acsmdownloader -f Dragon_Age_The_Missing_1.acsm

The downloaded file requires a password to open. Remove the DRM from the files.

find . type -f -name "Dragon_Age_The_Missing*.pdf" -exec adept_remove {} \;

Fail2Ban

Configure fail2ban on Linux with firewalld

sudo cp -v /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nvim /etc/fail2ban/jail.local
bantime = 1h
findtime = 1h
maxretry = 5
sudo cp -v /etc/fail2ban/jail.d/00-firewalld.conf /etc/fail2ban/jail.d/00-firewalld.local
sudo nvim /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled = true

bantime = 1d
maxretry = 3
sudo systemctl restart fail2ban.service
sudo fail2ban-client status

Configure fail2ban on FreeBSD with PF

sudo pkg install -y py311-fail2ban

Edit /usr/local/etc/fail2ban/jail.local.

[DEFAULT]
bantime = 86400
findtime = 3600
maxretry = 3
banaction = pf

[sshd]
enabled = true

Enable and start fail2ban.

sudo sysrc fail2ban_enable="YES"
sudo service fail2ban start

# If not enabled already:
sudo sysrc pf_enable="YES"
sudo service pf start

Configure /etc/pf.conf

table <fail2ban> persist

set skip on lo0

block in all
block in quick from <fail2ban>
...

Check and reload PF rules.

sudo pfctl -nf /etc/pf.conf
sudo pfctl -f /etc/pf.conf

Fedora Atomic

Access USB serial device in container

Create a udev rule on the host for all usb-serial devices. Set OWNER to your 1000 user.

cat << EOF | sudo tee /etc/udev/rules.d/50-usb-serial.rules
SUBSYSTEM=="tty", SUBSYSTEMS=="usb-serial", OWNER="jas"
EOF

Reload udev.

sudo udevadm control --reload-rules
sudo udevadm trigger

The serial device should now be owned by your user.

ls -l /dev/ttyUSB0
crw-rw----. 1 jas dialout 188, 0 Mar 15 11:09 /dev/ttyUSB0

You can now run minicom inside the toolbox container.

distrobox enter default
minicom -D /dev/ttyUSB0

Firewalld

Allow connections only from tailnet

Create a new zone for the tailscaled0 interface.

sudo firewall-cmd --permanent --new-zone=tailnet
sudo firewall-cmd --permanent --zone=tailnet --add-interface=tailscale0
sudo firewall-cmd --reload

Add services and ports to the tailnet zone.

sudo firewall-cmd --permanent --zone=tailnet --add-service={http,https,ssh}
sudo firewall-cmd --permanent --zone=tailnet --add-port=9100/tcp
sudo firewall-cmd --reload

Ensure the public zone does not have any interfaces or sources.

sudo firewall-cmd --permanent --zone=public --remove-interface=eth0
sudo firewall-cmd --reload

The firewall should now only allow traffic coming from the tailnet interface.

FreeBSD

USB 3.1 Type-C to RJ45 Gigabit Ethernet adapter

The Amazon Basics Aluminum USB 3.1 Type-C to RJ45 Gigabit Ethernet Adapter works well with FreeBSD 14.1-RELEASE. It uses the AX88179 chipset from ASIX Electronics Corp.

Install the ports tree

Source: Chapter 4. Installing Applications: Packages and Ports | FreeBSD Documentation Portal

Ensure the FreeBSD source code is checked out

sudo git clone -o freebsd -b releng/14.1 https://git.FreeBSD.org/src.git /usr/src

Check out the ports tree

sudo git clone --depth 1 https://git.FreeBSD.org/ports.git -b 2024Q3 /usr/ports

To switch to a different quarterly branch:

sudo git -C /usr/ports switch 2024Q4

drm-61-kmod

Install from the ports tree.

cd /usr/ports/graphics/drm-61-kmod
sudo make install clean

Alternatively, for Alderlake GPUs:

sudo pkg install drm-kmod

Edit /etc/rc.conf

kld_list="i915kms"

Add user to video group:

sudo pw groupmod video -m jas

Mount filesystems in single-user mode

When booted into single-user mode.

fsck
mount -u /
mount -a -t zfs
zfs mount -a

You should now be able to edit files, add/remove packages, etc.

Mount encrypted zroot in LiveCD

Boot into the LiveCD environment.

mkdir /tmp/mnt
geli attach /dev/nda0p4
zpool import -f -R /tmp/mnt zroot
zfs mount zroot/ROOT/default

The root directory of the zroot, zroot/ROOT/default, is labeled to not be automounted when imported, hence the need for the last command.

Setup Podman (FreeBSD >= 14)

The following is a condensed version of the guide found at CloudSpinx: Install Podman and run Containers in FreeBSD 14.

sudo pkg install podman-suite
sudo mount -t fdescfs fdesc /dev/fd

Add the following line to /etc/fstab:

fdesc /dev/fd fdescfs rw 0 0

Enable the Podman service.

sudo sysrc podman_enable="YES"

Container networking requires a NAT to allow the container network’s packets to reach the host’s network. Copy the sample pf.conf for Podman.

sudo cp -v /usr/local/etc/containers/pf.conf.sample /etc/pf.conf

Change v4egress_if and v6egress_if to the host’s main network interface in /etc/pf.conf.

v4egress_if="igc0"
v6egree_if="igc0"

Enable and start PF.

sudo sysrc pf_enable="YES"
sudo service pf start

FreeBSD >= 13.3 has support for rerouting connections from the host to services inside the container. To enable this, load the PF kernel module, then use sysctl to activate PF support for this rerouting.

echo 'pf_load="YES"' | sudo tee -a /boot/loader.conf
sudo kldload pf
sudo sysctl net.pf.filter_local=1
echo 'net.pf.filter_local=1' | sudo tee -a /etc/sysctl.conf.local
sudo service pf restart

The rerouting rules will only work if the destination address is localhost. Ensure the following exists in /etc/pf.conf.

nat-anchor "cni-rdr/*"

Container images and related state is stored in /var/db/containers. Create a ZFS dataset for this with the mountpoint set to that directory.

sudo zfs create -o mountpoint=/var/db/containers zroot/containers

If the system is not using ZFS, change storage.conf to use the vfs storage driver.

sudo sed -I .bak -e 's/driver = "zfs"/driver = "vfs"/' /usr/local/etc/containers/storage.conf

If there are any errors caused by the /var/db/containers/storage database, remote it.

sudo rm -rfv /var/db/containers/storage

Note: Podman can only be run with root privileges on FreeBSD at this time.

Enable the Linux service.

sudo sysrc linux_enable="YES"
sudo service linux start

To run Linux containers, add the --os=linux argument to Podman commands.

sudo podman run --os=linux ubuntu /usr/bin/cat "/etc/os-release"

Everything else should work as expected.

Install Linux VM in Bhyve

Based on How to install Linux VM on FreeBSD using bhyve and ZFS, but condensed and collated for my use-case.

Setting up the network interfaces

Make the tap device UP by default in /etc/sysctl.conf.

echo "net.link.tap.up_on_open=1" >> /etc/sysctl.conf
sysctl net.link.tap.up_on_open=1

Load the kernel modules needed for bhyve.

kldload vmm
kldload nmdm

Make sure the modules are loaded at boot time.

echo 'vmm_load="YES"' >> /boot/loader.conf
echo 'nmdm_load="YES"' >> /boot/loader.conf
echo 'if_tap_load="YES"' >> /boot/loader.conf
echo 'if_bridge_load="YES"' >> /boot/loader.conf

Create the bridge and tap device. If you already have a bridge created, use that instead. We’ll assume this is the case, and the bridge is called igb0bridge.

ifconfig bridge create

If a bridge is already created and the main network interface igc0 is attached to it, the following command is not necessary.

ifconfig igb0bridge addm igc0

Create tap interface and attach it to the igb0bridge.

ifconfig tap0 create ifconfig igb0bridge addm tap0

If there wasn’t a bridge already being used for jails, then /etc/rc.conf should contain the following:

cloned_interfaces="igb0bridge tap0"
ifconfig_igb0bridge="addm igc0 addm tap0 up"

If there was already a bridge used for jails, then /etc/rc.conf should contain the following:

cloned_interfaces="igb0bridge tap0"
ifconfig_igb0bridge="inet 10.0.0.8/24 addm igc0 addm tap0 up"

Setting up the ZFS volumes for Linux bhyve VM

zfs create -V128G -o volmode=dev zroot/debianvm

Downloading Debian installer ISO

cd /tmp/
DEBIAN_VERSION=12.10.0
wget "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-${DEBIAN_VERSION}-amd64-netinst.iso"

Installing Debian in VM

Install the grub-bhyve binary to allow booting of non-FreeBSD guest OSes.

pkg install grub2-bhyve bhyve-firmware

Install Debian by running bhyve with the netinstall iso image and the zvol attached.

bhyve -c 4 -m 8G -w -H \
  -s 0,hostbridge \
  -s 3,ahci-cd,/tmp/debian-12.10.0-amd64-netinst.iso \
  -s 4,virtio-blk,/dev/zvol/zroot/debianvm \
  -s 5,virtio-net,tap0 \
  -s 29,fbuf,tcp=0.0.0.0:5900,w=800,h=600,wait \
  -s 30,xhci,tablet \
  -s 31,lpc \
  -l com1,stdio \
  -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd \
  debianvm

When the command runs, use a remote VNC view to connect to and start the netinstall iso.

The following step is required to boot from UEFI.

Run the Debian installer with desired configuration. When you reach the “Finish the installation” stage, select “Go Back”, then select “Execute a shell”. Once in the shell, run the following commands:

mkdir /target/boot/efi/EFI/BOOT/
cp -v /target/boot/efi/EFI/debian/grubx64.efi /target/boot/efi/EFI/BOOT/bootx64.efi
exit

Now continue with “Finish the installation”.

Booting Debian bhyve VM

The instance of the virtual machine needs to be destroyed before it can be started again.

bhyvectl --destroy --vm=debianvm

Boot the Debian VM.

bhyve -c 4 -m 8G -w -H \
  -s 0,hostbridge \
  -s 4,virtio-blk,/dev/zvol/zroot/debianvm \
  -s 5,virtio-net,tap0 \
  -s 29,fbuf,tcp=0.0.0.0:5900,w=1024,h=768 \
  -s 30,xhci,tablet \
  -s 31,lpc \
  -l com1,stdio \
  -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd \
  debianvm

Starting the Debian VM on boot with a shell script

#!/bin/sh
# Name: startdebianvm
# Purpose: Simple script to start my Debian 10 VM using bhyve on FreeBSD
# Author: Vivek Gite {https://www.cyberciti.biz} under GPL v2.x+
-------------------------------------------------------------------------
# Lazy failsafe (not needed but I will leave them here)
ifconfig tap0 create
ifconfig em0bridge addm tap0
if ! kldstat | grep -w vmm.ko 
then
 kldload -v vmm
fi
if ! kldstat | grep -w nmdm.ko
then
 kldload -v nmdm
fi
bhyve -c 1 -m 1G -w -H \
-s 0,hostbridge \
-s 4,virtio-blk,/dev/zvol/zroot/debianvm \
-s 5,virtio-net,tap0 \
-s 29,fbuf,tcp=0.0.0.0:5900,w=1024,h=768 \
-s 30,xhci,tablet \
-s 31,lpc -l com1,stdio \
-l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd \
debianvm

Create a crontab entry:

@reboot /path/to/startdebianvm

Installing a Linux jail

Create the ZFS datasets for the base jail and Linux jail.

sudo zfs create naspool/jails/debian
sudo zfs create naspool/jails/14.2-RELEASE

Download the base userland system for FreeBSD.

fetch https://download.freebsd.org/ftp/releases/amd64/amd64/14.2-RELEASE/base.txz

Extract the base userland into the base jail’s directory.

sudo tar -xf base.txz -C /jails/14.2-RELEASE --unlink

Copy DNS and timezone files.

sudo cp -v /etc/resolv.conf /jails/14.2-RELEASE/etc/resolv.conf
sudo cp -v /etc/localtime /jails/14.2-RELEASE/etc/localtime

Update the base jail to the latest patch level.

sudo freebsd-update -b /jails/14.2-RELEASE/ fetch install

Create a ZFS snapshot from the base jail.

sudo zfs snapshot naspool/jails/14.2-RELEASE@base

Clone the base jail to create a thin jail for the Linux distribution.

sudo zfs clone naspool/jails/14.2-RELEASE@base naspool/jails/debian

Enable the Linux ABI.

sudo sysrc linux_enable="YES"
sudo service linux start

Run the jail command with a quick configuration.

sudo jail -cm \
  name=debian \
  host.hostname="debian" \
  path="/jails/debian" \
  interface="igc0" \
  ip4.addr="10.0.0.21" \
  exec.start="/bin/sh /etc/rc" \
  exec.stop="/bin/sh /etc/rc.shutdown" \
  mount.devfs \
  devfs_ruleset=11 \
  allow.mount \
  allow.mount.devfs \
  allow.mount.fdescfs \
  allow.mount.procfs \
  allow.mount.linprocfs \
  allow.mount.linsysfs \
  allow.mount.tmpfs \
  enforce_statfs=1

Access the jail.

sudo jexec -u root debian

Install the debootstrap program and prepare the Debian environment.

pkg install debootstrap
debootstrap bookworm /compat/debian

When the process finishes, stop the jail from the host system.

sudo service jail onestop debian

Add an entry in /etc/jail.conf for the Debian jail.

debian {
  # STARTUP/LOGGING
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.consolelog = "/var/log/jail_console_${name}.log";

  # PERMISSIONS
  allow.raw_sockets;
  exec.clean;
  mount.devfs;
  devfs_ruleset = 11;

  # HOSTNAME/PATH
  host.hostname = "${name}";
  path = "/jails/${name}";

  # NETWORK
  ip4.addr = 10.0.0.21;
  interface = igc0;

  # MOUNT
  mount += "devfs       $path/compat/debian/dev       devfs     rw 0 0";
  mount += "tmpfs       $path/compat/debian/dev/shm   tmpfs     rw,size=1g,mode=1777 0 0";
  mount += "fdescfs     $path/compat/debian/dev/fd    fdescfs   rw,linrdlnk 0 0";
  mount += "linprocfs   $path/compat/debian/proc      linprocfs rw 0 0";
  mount += "linsysfs    $path/compat/debian/sys       linsysfs  rw 0 0";
  mount += "/tmp        $path/compat/debian/tmp       nullfs    rw 0 0";
  mount += "/home       $path/compat/debian/home      nullfs    rw 0 0";
}

Start the jail.

sudo service jail start debian

The Debian environment can be accessed using the following command:

sudo jexec debian chroot /compat/debian /bin/bash

Internet Archive

Install Python command line client

pipx install internetarchive

Use Python client to download torrent files from given collection

Ensure “Automatically add torrents from” > Monitored Folder is set to /mnt/torrent_files and the Override save path is Default save path.

Get itemlist from collection

ia search --itemlist "collection:bbsmagazine" | tee bbsmagazine.txt

Download torrent files from each item using parallel

cat bbsmagazine.txt | parallel 'ia download --format "Archive BitTorrent" --destdir=/mnt/torrent_files {}'

Move .torrent files from their directories to /mnt/torrent_files

find /mnt/torrent_files -type f -name "*.torrent" -exec mv {} /mnt/torrent_files \;

Note: .torrent files will be removed from /mnt/torrent_files by qBittorrent once they are added to the instance.

Remove empty directories

find /mnt/torrent_files -maxdepth 1 -mindepth 1 -type d -delete

Kernel

Disable core dumps in Linux

limits.conf and sysctl

Edit /etc/security/limits.conf and append the following lines:

* hard core 0
* soft core 0

Edit /etc/sysctl.d/9999-disable-core-dump.conf:

fs.suid_dumpable=0
kernel.core_pattern=|/bin/false
sudo sysctl -p /etc/sysctl.d/9999-disable-core-dump.conf
  • /bin/false exits with a failure status code. The default value for kernel.core_pattern is core on a Debian server and |/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h on a Fedora desktop. These commands are executed upon crashes. In the case of /bin/false, nothing happens, and core dump is disabled.
  • fs.suid_dumpable=0 Any process that has changed privilege levels or is execute only will not be dumped. Other values include 1, which is debug mode, and all processes dump core when possible. The current user owns the core dump, no security is applied. 2, suidsafe mode, in which any Linux program that would generally not be dumped is dumped regardless, but only if the kernel.core_pattern is sysctl is set to a valid program.

systemd

sudo mkdir /etc/systemd/coredump.conf.d/
sudo nvim /etc/systemd/coredump.conf.d/custom.conf
[Coredump]
Storage=none
ProcessSizeMax=0
  • Storage=none and ProcessSizeMax=0 disables all coredump handling except for a log entry under systemd.
sudo systemctl daemon-reload

Edit /etc/systemd/system.conf. Make sure DefaultLimitCORE is commented out.

#DefaultLimitCORE=infinity
sudo systemctl daemon-reexec

Lutris

Add game-performance

  1. Go to Preferences -> Global options.
  2. Enable Advanced mode.
  3. Scroll down to Command prefix.
  4. Add game-performance to command prefix.

LVM

Add disk to LVM volume

Create a new physical volume on the new disk:

sudo pvcreate /dev/vdb
sudo lvmdiskscan -l

Add the newly created physical volume to an existing logical volume:

sudo vgextend almalinux /dev/vdb

Extend the /dev/almalinux/root to create a total 1000GB:

sudo lvm lvextend -l +100%FREE /dev/almalinux/root

Grow the filesystem of the root volume:

# ext4
sudo resize2fs -p /dev/mapper/almalinux-root

# xfs
sudo xfs_growfs /

MinIO

Bucket replication to remote MinIO instance

Use mcli to create aliases for the local and remote instances.

mcli alias set nas-local http://localhost:9000 username password
mcli alias set nas-remote http://ip.addr:9000 username password

Add configuration rule on source bucket for nas-local to nas-remote to replicate all operations in an active-active replication setup.

mcli replicate add nas-local/sourcebucket --remote-bucket nas-remote/targetbucket --priority 1

Show replication status.

mcli replicate status nas-local/sourcebucket

FreeBSD setup

Create a ZFS dataset to store MinIO data.

sudo zfs create naspool/minio_data

Install the MinIO package.

sudo pkg install -y minio

Configure the MinIO daemon settings in /etc/rc.conf.

minio_enable="YES"
minio_disks="/naspool/minio_data"

Set the required permissions on /naspool/minio_data.

sudo chown -R minio:minio /naspool/minio_data
sudo chmod u+rxw /naspool/minio_data

Start the MinIO daemon.

sudo service minio start

Check the logs for any important info.

sudo grep "minio" /var/log/messages

Neovim cheatsheet

NERDCommenter

ModeKeybindAction
normalSPACE + ccNERDCommenterComment
normalSPACE + c + SPACENERDCommenterToggle
normalSPACE + cmNERDCommenterMinimal
normalSPACE + cnNERDCommenterNested
normalSPACE + c$NERDCommenterToEOL
normalSPACE + ciNERDCommenterInvert
normalSPACE + csNERDCommenterSexy
normalSPACE + cyNERDCommenterYank
normalSPACE + cANERDCommenterAppend
normalSPACE + clNERDCommenterAlignLeft
normalSPACE + cbNERDCommenterAlignBoth
normalSPACE + cuNERDCommenterUncomment
normalSPACE + caNERDCommenterAltDelims
ModeKeybindAction
normalSPACE + sh[S]earch [H]elp
normalSPACE + sk[S]earch [K]eymaps
normalSPACE + sf[S]earch [F]iles
normalSPACE + ss[S]earch [S]elect Telescope
normalSPACE + sw[S]earch current [W]ord
normalSPACE + sg[S]earch by [G]rep
normalSPACE + sd[S]earch [D]iagnostics
normalSPACE + sr[S]earch [R]esume
normalSPACE + s.[S]earch Recent Files (“.” for repeat)
normalSPACE + SPACE[ ] Find existing buffers
normalSPACE + /[/] Fuzzily search in current buffer
normalSPACE + s/[S]earch [/] in Open Files
normalSPACE + sn[S]earch [N]eovim files
normalEscnohlsearch

Movement

ModeKeybindAction
normalhMove cursor left
normallMove cursor right
normaljMove cursor down
normalkMove cursor up
normalCtrl-HMove focus to the left window
normalCtrl-LMove focus to the right window
normalCtrl-JMove focus to the lower window
normalCtrl-KMove focus to the upper window

Editing

ModeKeybindAction
normalsaAdd surrounding
normalsdDelete surrounding
normalsrReplace surrounding
normalsfFind right surrounding
normalsFFind left surrounding
normalshHighlight surrounding
normalShift-oAdd empty line below cursor
normalShift-OAdd empty line above cursor
normalSPACE + fFormat buffer
insertArrowsTraverse completion menu
insertCtrl-YSelect completion

Diagnostics

ModeKeybindAction
normal]dJump to next diagnostic in current buffer
normal]DJump to last diagnostic in current buffer
normal[dJump to previous diagnostic in current buffer
normal[DJump to first diagnostic in current buffer
normalSPACE + qOpen diagnostic [Q]uickfix list
normalCtrl-W + dShow diagnostics under cursor

Splits and tabs

ModeKeybindAction
command:spSplit window horizontally
command:vspSplit window vertically
command:tabeCreate a new tab
command:tabcClose the current tab
command:tabnGo to next tab
command:tabpGo to previous tab

Networking

Disable IPv6 on Debian

Edit /etc/sysctl.conf.

net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1

Apply the changes.

sudo sysctl -p

Disable IPv6 on Fedora

sudo grubby --args=ipv6.disable=1 --update-kernel=ALL
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

Rename network interface when using systemd-networkd

Create a udev rule at /etc/udev/rules.d/70-my-net-names.rules.

SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="your-mac-address", NAME="wlan0"

Using 70-my-net-names.rules as the filename ensures the rule is ordered before /usr/lib/udev/rules.d/80-net-setup-link.rules.

Connecting to WiFi network using systemd-networkd and wpa_supplicant

Create a file at /etc/wpa_supplicant/wpa_supplicant-wlan0.conf. Use wpa_passphrase to hash the passphrase.

wpa_passphrase your-ssid your-ssid-passphrase | sudo tee -a /etc/wpa_supplicant/wpa_supplicant-wlan0.conf

Edit /etc/wpa_supplicant/wpa_supplicant-wlan0.conf.

ctrl_interface=/var/run/wpa_supplicant
ctrl_interface_group=0
update_config=1

network={
  ssid="your-ssid"
  psk="your-hashed-ssid-passphrase"
  key_mgmt=WPA-PSK
  proto=WPA2
  scan_ssid=1
}

Create a file at /etc/systemd/network/25-wlan.network.

[Match]
Name=wlan0

[Network]
DHCP=ipv4

Enable and start the network services.

sudo systemctl enable --now wpa_supplicant@wlan0.service
sudo systemctl restart systemd-networkd.service
sudo systemctl restart wpa_supplicant@wlan0.service

Check the interface status with ip a.

Use tailnet DNS and prevent DNS leaks

After the above WiFi interface is setup, disable IPv6 as per the above sections, and enable the Tailscale service.

sudo systemctl enable --now tailscaled.service
sudo tailscale up

Edit /etc/systemd/networkd/25-wlan.network again, and add the following contents.

[Match]
Name=wlan0

[Network]
DHCP=ipv4
DNS=100.100.100.100
DNSSEC=allow-downgrade

[DHCPv4]
UseDNS=no

This will tell the wlan0 interface to use Tailscale’s MagicDNS, along with DNSSEC if it is available, and not to get the nameservers from the DHCPv4 connection.

Nextcloud

Migrating

Install dependencies.

sudo apt update
sudo apt dist-upgrade
sudo apt install apache2 mariadb-server libapache2-mod-php php-gd php-mysql php-curl php-mbstring php-intl php-gmp php-bcmath php-xml php-imagick php-zip php-apcu redis-server

Setup the database.

sudo mysql
CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'password';
CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextcloud'@'localhost';
FLUSH PRIVILEGES;
quit;

On the original machine, put Nextcloud into maintenance mode.

cd /var/www/nextcloud
sudo -u www-data php occ maintenance:mode --on

Wait 6-7 minutes for the sync clients to register the server is in maintenance mode before proceeding.

Stop the web server that runs Nextcloud.

sudo systemctl stop apache2.service

Copy over the Nextcloud directory to the new machine.

rsync -aAX /var/www/nextcloud root@new-machine:/var/www

Copy the PHP configurations to the new machine.

rsync -aAX /etc/php/8.2/apache2/ root@new-machine:/etc/php/8.2/apache2
rsync -aAX /etc/php/8.2/cli/ root@new-machine:/etc/php/8.2/cli

The PHP version on the new machine must match that from the original machine.

On the new machine, ensure /etc/php/8.2/mods-available/apcu.ini is configured correctly.

extension=apcu.so
apc.enable_cli=1

On the new machine, ensure permissions are set correctly on /var/www/nextcloud.

sudo chown -R www-data:www-data /var/www/nextcloud

On the original machine, dump the database.

mysql --single-transaction --default-character-set=utf8mb4 -h localhost -u nextcloud -p nextcloud > nextcloud-sqlbkp.bak

Copy the database backup to the new machine.

rsync -aAX nextcloud-sqlbkp.bak root@new-machine:/root

On the new machine, import the database backup.

mysql -h localhost -u nextcloud -p -e "DROP DATABASE nextcloud"
mysql -h localhost -u nextcloud -p -e "CREATE DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci"
mysql -h localhost -u nextcloud -p nextcloud < /root/nextcloud-sqlbkp.bak

On the new machine, ensure redis-server service is started.

sudo systemctl enable --now redis-server.service

On the new machine, run the following command to update the data-fingerprint:

cd /var/www/nextcloud
sudo -u www-data php occ maintenance:data-fingerprint

Ensure DNS records are changed to the new machine and the web server is running before taking Nextcloud out of maintenance mode.

On the new machine, take Nextcloud out of maintenance mode.

cd /var/www/nextcloud
sudo -u www-data php occ maintenance:mode --off

NFS

Setup NFS server on Debian

sudo apt install -y nfs-kernel-server nfs-common

Configure NFSv4 in /etc/default/nfs-common.

NEED_STATD="no"
NEED_IDMAPD="yes"

Configure NFSv4 in /etc/default/nfs-kernel-server. Disable NFSv2 and NFSv3.

RPCNFSDOPTS="-N 2 -N 3"
RPCMOUNTDOPTS="--manage-gids -N 2 -N 3"
sudo systemctl restart nfs-server

Configure Firewalld.

sudo firewall-cmd --zone=public --permanent --add-service=nfs
sudo firewall-cmd --reload

Setup pseudo filesystem and exports.

sudo mkdir /shared
sudo chown -R nobody:nogroup /shared

Add exported directory to /etc/exports.

/shared <ip address of client>(rw,no_root_squash,no_subtree_check,crossmnt,fsid=0)

Create the NFS table.

sudo exportfs -a

Setup NFS client on Debian

sudo apt install -y nfs-common

Create shared directory.

sudo mkdir -p /mnt/shared

Mount NFS exports. We’ll assume the IP address of the NFS server is 10.0.0.64.

sudo mount.nfs4 10.0.0.64:/ /mnt/shared

Note that the 10.0.0.65:/ is relative to the exported directory. So /mnt/shared on the client is /shared on the server. If you try to mount with mount -t nfs 10.0.0.64:/shared /mnt/shared you will get a no such file or directory error.

/etc/fstab

10.0.0.64:/ /mnt/shared nfs4 soft,intr,rsize=8192,wsize=8192
sudo systemctl daemon-reload
sudo mount -av

Setup NFS server on FreeBSD

Edit /etc/rc.conf.

nfs_server_enable="YES"
nfs_server_flags="-u -t -n 4"
rpcbind_enable="YES"
mountd_flags="-r"
mountd_enable="YES"

Edit /etc/exports.

/data1 -alldirs -mapall=user1 host1 host2 host3
/data2 -alldirs -maproot=user2 host2

Start the services.

sudo service rpcbind start
sudo service nfsd start
sudo service mountd start

After making changes to the exports file, you need to restart NFS for the changes to take effect.

kill -HUP `cat /var/run/mountd.pid`

Setup NFS client on FreeBSD

Edit /etc/rc.conf.

nfs_client_enable="YES"
nfs_client_flags="-n 4"
rpc_lockd_enable="YES"
rpc_statd_enable="YES"

Mount NFS share on client with systemd

Create a file at /etc/systemd/system/mnt-backup.mount.

[Unit]
Description=borgbackup NFS share from FreeBSD
DefaultDependencies=no
Conflicts=umount.target
After=network-online.target remote-fs.target
Before=umount.target

[Mount]
What=10.0.0.119:/coffeeNAS/borgbackup/repositories
Where=/mnt/backup
Type=nfs
Options=defaults,vers=3

[Install]
WantedBy=multi-user.target

Nmap

Target specification

ExampleDescription
nmap 192.168.1.1Scan a single IP
nmap 192.168.1.1 192.168.2.1Scan specific IPs
nmap 192.168.1.1-254Scan a range
nmap scanme.nmap.orgScan a domain
nmap 192.168.1.0/24Scan using CIDR notation
nmap -iL targets.txtScan targets from a file
nmap -iR 100Scan 100 random hosts
nmap -exclude 192.168.1.1Exclude listed hosts

Nmap scan techniques

ExampleDescription
nmap 192.168.1.1 -sSTCP SYN port scan (default)
nmap 192.168.1.1 -sTTCP connect port scan
nmap 192.168.1.1 -sUUDP port scan
nmap 192.168.1.1 -sATCP ACK port scan
nmap 192.168.1.1 -sWTCP Window port scan
nmap 192.168.1.1 -sMTCP Maimon port scan

Host discovery

ExampleDescription
nmap 192.168.1.1-3 -sLNo scan. List targets only
nmap 192.168.1.1/24 -snDisable port scanning. Host discovery only.
nmap 192.168.1.1-5 -PnDisable host discovery. Port scan only.
nmap 192.168.1.1-5 -PS22-25,80TCP SYN discovery on ports 22-25, 80 (Port 80 by default)
nmap 192.168.1.1-5 -PA22-25,80TCP ACK discovery on ports 22-25, 80 (Port 80 by default)
nmap 192.168.1.1-5 -PU53UDP discovery on port 53. (Port 40125 by default)
nmap 192.168.1.1-1/24 -PRARP discovery on local network
nmap 192.168.1.1 -nNever do DNS resolution

Port specification

ExampleDescription
nmap 192.168.1.1 -p 21Port scan for port 21
nmap 192.168.1.1 -p 21-100Port scan for range 21-100
nmap 192.168.1.1 -p U:53,T:21-25,80Port scan multiple TCP and UDP ports
nmap 192.168.1.1 -p-Port scan all ports
nmap 192.168.1.1 -p http,httpsPort scan from service name
nmap 192.168.1.1 -FFast port scan (100 ports)
nmap 192.168.1.1 -top-ports 2000Port scan the top 2000 ports
nmap 192.168.1.1 -p-65535Leaving off the initial port in range makes the scan start at port 1
nmap 192.168.1.1 -p0-Leaving off the end port in range makes the scan go through to port 65535

Service and version detection

ExampleDescription
nmap 192.168.1.1 -sVAttempts to determine version of the service running on port.
nmap 192.168.1.1 -sV -version-intensity 8Intensity level 0-9. Higher number increases possibility of correctness.
nmap 192.168.1.1 -sV -version-lightEnable light mode. Lower possibility of correctness. Faster.
nmap 192.168.1.1 -sV -version-allEnable intensity level 9. Higher possibility of correctness. Slower.
nmap 192.168.1.1 -AEnable OS detection, version detection, script scanning, and traceroute.

OS detection

ExampleDescription
nmap 192.168.1.1 -ORemote OS detection using TCP/IP stack fingerprinting
nmap 192.168.1.1 -O -osscan-limitIf at least one open and one closed TCP port are not found it will not try OS detection against host.
nmap 192.168.1.1 -P -osscan-guessMakes nmap guess more aggressively.
nmap 192.168.1.1 -O -max-os-tries 1Set the maximum number of OS detection tries
nmap 192.168.1.1 -AEnables OS detection, version detection, script scanning, and traceroute.

Timing and performance

ExampleDescription
nmap 192.168.1.1 -T0Paranoid (0) IDS evasion
nmap 192.168.1.1 -T1Sneaky (1) IDS evasion
nmap 192.168.1.1 -T2Polite (2) slows down the scan to use less bandwidth and use less target machine resources.
nmap 192.168.1.1 -T3Normal (3) which is default speed.
nmap 192.168.1.1 -T4Aggressive (4) speed scans. Assumes you are on a reasonably fast and reliable network.
nmap 192.168.1.1 -T5Insane (5) speed scan. Assumes you are on an extraordinarily fast network.

Timing and performance switches

ExampleDescription
-host-timeout 1s; -host-timeout 4m;Give up on target after this long.
-min-rtt-timeout/max-rtt-timeout/initial-rtt-timeout 4m;Specifies probe round trip time.
-min-hostgroup/max-hostgroup 50Parallel host scan group sizes
-min-parallelism/max-parallelism 10Probe parallelization
-max-retries 3Specify the max number of port scan probe retransmissions.
-min-rate 100Send packets to no slower than 100 per second
-max-rate 100Send packets no faster than 100 per second

NSE scripts

ExampleDescription
nmap 192.168.1.1 -sCScan with default NSE scripts. Useful and safe.
nmap 192.168.1.1 -script defaultScan with default NSE scripts.
nmap 192.168.1.1 -script=bannerScan with single script. Example banner.
nmap 192.168.1.1 -script=http*Scan with a wildcard. Example http.
nmap 192.168.1.1 -script=http,bannerScan with two scripts. http and banner.
nmap 192.168.1.1 -script "not intrusive"Scan default, but remove intrusive scripts.
nmap -script snmp-sysdescr -script-args snmpcommunity=admin 192.168.1.1NSE script with arguments

Useful NSE script examples

ExampleDescription
nmap -Pn -script=http-sitemap-generator scanme.nmap.orghttp site map generator
nmap -n -Pn -p 80 -open -sV -vvv -script banner,http-title -iR 1000Fast search for random web servers
nmap -Pn -script=dns-brute domain.comBrute forces DNS hostnames guessing subdomains
nmap -n -Pn -vv -O -sV -script smb-enum*,smb-ls,smb-mbenum,smb-os-discovery,smb-s*,smb-vuln*,smbv2* -vv 192.168.1.1Safe SMB scripts to run
nmap -script whois* domain.comWhois query
nmap -p80 -script http-unsafe-output-escaping scanme.nmap.orgDetect cross site scripting vulnerabilities
nmap -p80 -script http-sql-injection scanme.nmap.orgCheck for SQL injections

Firewall/IDS evasion and spoofing

ExampleDescription
nmap 192.168.1.1 -fRequested scan (including ping scans) use tiny fragmented IP packets. Harder for packet filters.
nmap 192.168.1.1 -mtu 32Set your own offset size
nmap -D 192.168.1.101,192.168.1.102,192.168.1.103Send scans from spoofed IPs
nmap -D decoy-ip1,decoy-ip2,your-own-ipSame as above
nmap -S www.microsoft.com www.facebook.comScan Facebook from Microsoft (-e eth0 -Pn may be required
nmap -g 53 192.168.1.1Use given source port number
nmap -proxies http://192.168.1.1:8080,http://192.168.1.2:8080 192.168.1.1Relay connections through HTTP/SOCKS4 proxies
nmap -data-length 200 192.168.1.1Appends random data to sent packets

Output

ExampleDescription
nmap 192.168.1.1 -oN normal.fileNormal output to the file normal.file
nmap 192.168.1.1 -oX xml.fileXML output to the file xml.file
nmap 192.168.1.1 -oG grep.fileGrepable output to the file grep.file
nmap 192.168.1.1 -oA resultsOutput in the three major formats at once
nmap 192.168.1.1 -oG -Grepable output to screen. -oN, -oX also usable.
nmap 192.168.1.1 -oN file.txt -append-outputAppend a scan to a previous scan file
nmap 192.168.1.1 -vIncrease verbosity level (use -vv or more)
nmap 192.168.1.1 -dIncrease debugging level (use -dd or more)
nmap 192.168.1.1 -reasonDisplay the reason a port is in a particular state, same output as -vv
nmap 192.168.1.1 -openOnly show open (or possibly open) ports
nmap 192.168.1.1 -T4 -packet-traceShow all packets sent and received
nmap -iflistShows the host interfaces and routes
nmap -resume results.fileResume a scan from results.file

Helpful nmap output examples

ExampleDescription
`nmap -p80 -sV -oG - -open 192.168.1.1/24grep open`
`nmap -iR 10 -n -oX out.xmlgrep “Nmap”
`nmap -iR 10 -n -oX out2.xmlgrep “Nmap”
ndiff scan.xml scan2.xmlCompare the output of two scan results
xsltproc nmap.xml -o nmap.htmlConvert nmap xml files to html files

Other useful nmap commands

ExampleDescription
nmap -iR 10 -PS22-25,80,113,1050,35000 -v -snDiscovery only on ports X, no port scan
nmap 192.168.1.1-1/24 -PR -sn -vvARP discovery only on local network, no port scan
nmap -iR 10 -sn -tracerouteTraceroute to random targets, no port scan
nmap 192.168.1.1-50 -sL -dns-server 192.168.1.1Query the internal DNS for hosts, list targets only
nmap 192.168.1.1 --packet-traceShow the details of the packets that are sent and received during a scan and capture the traffic

Orange Pi 5+

Disable blinky LEDs

Edit /etc/udev/rules.d/led_control.rules.

SUBSYSTEM=="leds", KERNEL=="blue_led", ACTION=="add", ATTR{trigger}="none"
SUBSYSTEM=="leds", KERNEL=="green_led", ACTION=="add", ATTR{trigger}="none"
SUBSYSTEM=="leds", KERNEL=="mmc0::", ACTION=="add", ATTR{trigger}="none"

Reboot the system.

Packet Tracer

Fix GUI issues with KDE Plasma dark theme

mkdir ~/.config-pt
cd ~/.config
cp -rf dconf gtk-3.0 gtk-4.0 xsettingsd ~/.config-pt
  1. Right-click on Menu button.
  2. Click Edit Applications.
  3. Select Packet Tracer.
  4. Add XDG_CONFIG_HOME=/home/jas/.config-pt to Environment variables.
  5. Save.

Parallel

Pulling files from remote server with rsync

To transfer just the files:

ssh user@remote -- find /path/to/parent/directory -type f | parallel -v -j16 rsync -Havessh -aAXP user@remote:{} /local/path

To transfer the entire directory:

echo "/path/to/parent/directory" | parallel -v -j16 rsync -Havessh -aAXP user@remote:{} /local/path

Pushing files to remote server with rsync

To transfer just the files:

find /path/to/local/directory -type f | parallel -v -j16 -X rsync -aAXP /path/to/local/directory/{} user@remote:/path/to/dest/dir

Running the same command on multiple remote hosts

parallel --tag --nonall -S remote0,remote1,remote2 uptime

PostgreSQL

Change password for user

sudo -u user_name psql db_name
ALTER USER user_name WITH PASSWORD 'new_password';

Update password auth method to SCRAM

Edit /etc/postgresql/16/main/postgresql.conf.

password_encryption = scram-sha-256

Restart postgresql.service.

At this point, any services using the old MD5 auth method will fail to connect to their PostgreSQL databases.

Update the settings in /etc/postgresql/16/main/pg_hba.conf.

TYPE    DATABASE        USER            ADDRESS         METHOD
local   all             mastodon                        scram-sha-256
local   all             synapse_user                    scram-sha-256

Enter a psql shell and determine who needs to upgrade their auth method.

SELECT rolname, rolpassword ~ '^SCRAM-SHA-256\$' AS has_upgraded FROM pg_authid WHERE rolcanlogin;

\password username

Restart postgresql.service and all services using a PostgreSQL database.

Qcow2

Mount qcow2 image

Enable NBD on the host.

sudo modprobe nbd max_part=8

Connect qcow2 image as a network block device.

sudo qemu-nbd --connect=/dev/nbd0 /path/to/image.qcow2

Find the VM’s partitions.

sudo fdisk /dev/nbd0 -l

Mount the partition from the VM.

sudo mount /dev/nbd0p3 /mnt/point

To unmount:

sudo umount /mnt/point
sudo qemu-nbd --disconnect /dev/nbd0
sudo rmmod nbd

Resize qcow2 image

Install guestfs-tools (required for virt-resize command).

sudo dnf install -y guestfs-tools
sudo apt install -y guestfs-tools libguestfs-tools

To resize qcow2 images, you’ll have to create a new qcow2 image with the size you want, then use virt-resize on the old qcow2 image to the new one.

You’ll need to know the root partition within the old qcow2 image.

Create a new qcow2 image with the size you want.

qemu-img create -f qcow2 -o preallocation=metadata newdisk.qcow2 100G

Now resize the old one to the new one.

virt-resize --expand /dev/vda3 olddisk.qcow2 newdisk.qcow2

Once you boot into the new qcow2 image, you’ll probably have to adjust the size of the logical volume if it has LVM.

sudo lvresize -l +100%FREE /dev/mapper/sysvg-root

Then resize the XFS root partition within the logical volume.

sudo xfs_growfs /dev/mapper/sysvg-root

Qemu

Take snapshot of VM

sudo virsh domblklist vm1

Target          Source
-----------------------------------------------
vda             /var/lib/libvirt/images/vm1.img

sudo virsh snapshot-create-as \
        --domain vm1 \
        --name guest-state1 \
        --diskspec vda,file=/var/lib/libvirt/images/overlay1.qcow2 \
        --disk-only \
        --atomic \
        --quiesce

Ensure qemu-guest-agent is installed inside the VM. Otherwise omit the --quiesce flag, but when you restore the VM it will be as if the system had crashed. Not that big of a deal since the VM’s OS should flush required data and maintain consistency of its filesystems.

sudo rsync -avhW --progress /var/lib/libvirt/images/vm1.img /var/lib/libvirt/images/vm1-copy.img
sudo virsh blockcommit vm1 vda --active --verbose --pivot

Full disk backup of VM

Start the guest VM.

sudo virsh start vm1

Enumerate the disk(s) in use.

sudo virsh domblklist vm1

Target          Source
-------------------------------------------------
vda             /var/lib/libvirt/images/vm1.qcow2

Begin the backup.

sudo virsh backup-begin vm1

Backup started

Check the job status. “None” means the job has likely completed.

sudo virsh domjobinfo vm1

Job type:        None

Check the completed job status.

sudo virsh domjobinfo vm1 --completed

Job type:               Completed
Operation:              Backup
Time elapsed:           182     ms
File processed:         39.250 MiB
File remaining:         0.000 B
File total:             39.250 MiB

Now we see the copy of the backup.

sudo ls -lash /var/lib/libvirt/images/vm1.qcow2*

15M -rw-r--r--. 1 qemu qemu 15M May 10 12:22 vm1.qcow2
21M -rw-------. 1 root root 21M May 10 12:23 vm1.qcow2.1620642185

RAID

Mounting RAID1 mirror

  • /dev/sda1
  • /dev/sdb1

Assemble the RAID array.

sudo mdadm --assemble --run /dev/md0 /dev/sda1 /dev/sdb1

Mount the RAID device.

sudo mount /dev/md0 /mnt

Configure msmtp for mdmonitor.service (Ubuntu 24.04)

sudo apt install msmtp msmtp-mta

Edit /etc/msmtprc.

# Resend account
account resend
host smtp.resend.com
from admin@hyperreal.coffee
port 2587
tls on
tls_starttls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
auth on
user resend
password APIKEY GO HERE
syslog LOG_MAIL

Edit /etc/mdadm.conf.

MAILADDR hyperreal@moonshadow.dev
MAILFROM admin@hyperreal.coffee
PROGRAM msmtp

ARRAY ...
ARRAY ...

Rename sendmail and symlink msmtp to sendmail.

sudo mv /usr/sbin/sendmail /usr/sbin/sendmail.bak
sudo ln -s /usr/bin/msmtp /usr/sbin/sendmail

Send a test email.

sudo mdadm --monitor --scan --test --oneshot

Restart mdmonitor.service.

Resident Evil HD

Installation

  1. Download Resident Evil Classic Triple Pack PC from archive.org. This contains the Sourcenext versions of all three games.
  2. Install all three games using their installers.
  3. Download the following files:
    1. Biohazard PC CD-ROM Mediakite patch version 1.01
    2. Resident Evil Classic REbirth
    3. Resident Evil 2 Classic REbirth
    4. Resident Evil 3 Classic REbirth
    5. Biohazard Mediakite
    6. Resident Evil HD mod by TeamX
    7. Resident Evil 2 HD mod by TeamX
    8. Resident Evil 3 HD mod by TeamX
    9. Resident Evil Seamless HD Project v1.1
    10. Resident Evil 2 Seamless HD Project v2.0
    11. Resident Evil 3: Nemesis Seamless HD Project v2.0
  4. Open the Biohazard Mediakite disc image with 7zip and drag the JPN folder from the disc into C:\Program Files (x86)\Games Retro\Resident Evil Classic

Resident Evil Director’s Cut

Extract the following files to %ProgramFiles(x86)%\Games Retro\Resident Evil Classic:

  • Biohazard.exe from Mediakite v1.01
  • ddraw.dll from Resident Evil Classic REbirth
  • All from Resident Evil HD mod by TeamX
  • All from Resident Evil Seamless HD Project v1.1

Resident Evil 2

Extract the following files to %ProgramFiles(x86)%\Games Retro\BIOHAZARD 2 PC:

  • ddraw.dll from Resident Evil 2 Classic REbirth
  • All from Resident Evil 2 HD mod by TeamX
  • All from Resident Evil 2 Seamless HD Project v2.0

Resident Evil 3: Nemesis

Extract the following files to %ProgramFiles(x86)%\Games Retro\BIOHAZARD 3 PC:

  • ddraw.dll from Resident Evil 3 Classic REbirth
  • All from Resident Evil 3 HD mod by TeamX
  • All from Resident Evil 3: Nemesis Seamless HD Project v2.0

Testing

Test each game by launching them with the following config changes:

  • Resolution 1280x960
  • RGB88 colors
  • Disable texture filtering

RetroPie

Bluetooth: protocol not available

sudo apt install pulseaudio-module-bluetooth

Add to /lib/systemd/system/bthelper@.service:

ExecStartPre=/bin/sleep 4
sudo systemctl start sys-subsystem-bluetooth-devices-hci0.device
sudo hciconfig hci0 down
sudo killall pulseaudio
systemctl --user enable --now pulseaudio.service
sudo systemctl restart bluetooth.service

Router

  • Ubuntu 24.04
  • Orange Pi 5 Plus
  • ISP router in bridge mode
  • Ethernet from ISP router -> Orange Pi 5 Plus WAN port
  • Ethernet from Orange Pi 5 Plus LAN port to switch

Install packages

sudo apt install neovim firewalld fail2ban atop htop python3-dev nmap tcpdump rsync rsyslog iptraf-ng iftop sysstat conntrack logwatch unattended-upgrades byobu

curl -fsSL https://tailscale.com/install.sh | sh

Register router as Tailnet node.

sudo systemctl enable --now tailscaled.service
sudo tailscale up

Netplan with DHCP WAN

/etc/netplan/01-netcfg.yaml:

network:
    version: 2
        renderer: networkd
        ethernets:
            eth0:   # WAN interface (connected to internet)
                dhcp4: true
                dhcp6: false
                nameservers:
                    addresses:
                        - 9.9.9.9
                        - 149.112.112.112
            eth1:   # LAN interface (connected to local network)
                dhcp4: false
                dhcp6: false
                addresses:
                    - 10.0.2.1/24
                nameservers:
                    addresses:
                        - 9.9.9.9
                        - 149.112.112.112

Bridged LAN+WiFi AP

network:
    version: 2
    renderer: networkd
    ethernets:
        eth0:
            dhcp4: true
            dhcp6: false
            nameservers:
                addresses:
                    - 9.9.9.9
                    - 149.112.112.112
        eth1:
            dhcp4: false
            dhcp6: false
            addresses:
                - 10.0.2.1/24
            nameservers:
                addresses:
                    - 9.9.9.9
                    - 149.112.112.112
    wifis:
        wlan0:
            access-points:
                coffeenet:
                    auth:
                        key-management: psk
                        password: "password" 
    bridges:
        br0:
            interfaces:
                - eth1
                - wlan0
            addresses:
                - 10.0.2.1/24
            nameservers:
                addresses:
                    - 9.9.9.9
                    - 149.112.112.112

Netplan with static IP

network:
    version: 2
    renderer: networkd
    ethernets:
        eth0: # WAN interface (connected to internet)
            addresses:
                - WAN public IP/prefix
            nameservers:
                addresses:
                    - 9.9.9.9
                    - 149.112.112.112
            routes:
                - to: default
                  via: WAN default gateway
                  metric: 100
        eth1:
            dhcp4: false
            dhcp6: false
            addresses:
                - 10.0.2.1/24
            nameservers:
                addresses:
                    - 9.9.9.9
                    - 149.112.112.112

Bridged LAN+WiFi AP

network:
    version: 2
    renderer: networkd
    ethernets:
        eth0:
            dhcp4: false
            dhcp6: false
            addresses:
                - WAN public IP
            nameservers:
                addresses:
                    - 9.9.9.9
                    - 149.112.112.112
            routes:
                - to: default
                  via: WAN default gateway
                  metric: 100
        eth1:
            dhcp4: false
            dhcp6: false
            addresses:
                - 10.0.2.1/24
            nameservers:
                addresses:
                    - 9.9.9.9
                    - 149.112.112.112
    wifis:
        wlan0:
            access-points:
                coffeenet:
                    auth:
                        key-management: psk
                        password: "password"
    bridges:
        br0:
            interfaces:
                - eth1
                - wlan0
            addresses:
                - 10.0.2.1/24
            nameservers:
                addresses:
                    - 9.9.9.9
                    - 149.112.112.112

Apply the netplan settings.

sudo netplan apply

IP forwarding

echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Firewalld

sudo firewall-cmd --permanent --zone=home --add-interface=br0
sudo firewall-cmd --permanent --zone=home --add-service={ssh,dns,http,https,dhcp}
sudo firewall-cmd --permanent --zone=home --add-forward
sudo firewall-cmd --permanent --zone=external --add-interface=eth0
sudo firewall-cmd --permanent --zone=external --add-service=dhcpv6-client
sudo firewall-cmd --permanent --zone=external --add-forward

Create /etc/firewalld/policies/masquerade.xml to allow traffic to flow from LAN to WAN.

<?xml version="1.0" encoding="utf-8"?>
<policy target="ACCEPT">
<masquerade/>
<ingress-zone name="home"/>
<egress-zone name="external"/>
</policy>

Reload the firewalld configuration.

sudo firewall-cmd --reload

RPM repository

Create an RPM repository

Install dependencies

sudo dnf install -y gnupg createrepo dnf-utils rpm-sign wget

Setup GnuPG

echo "%echo Generating a PGP key
Key-Type: RSA
Key-Length: 4096
Name-Real: Jeffrey Serio
Name-Email: hyperreal@moonshadow.dev
Expire-Date: 0
%no-ask-passphrase
%no-protection
%commit" > ~/hyperreal-pgp-key.batch
gpg --no-tty --batch --gen-key ~/hyperreal-pgp-key.batch

Export the public key.

gpg --armor --export "Jeffrey Serio" > ~/hyperreal-pgp-key.pub

Export the private key to back it up somewhere safe.

gpg --armor --export-secret-keys "Jeffrey Serio" > ~/hyperreal-pgp-key.sec

After backing it up, shred it from the working directory.

shred -xu ~/hyperreal-pgp.key.sec

Setup RPM signing

Replace E1933532750E9EEF with your key’s ID.

echo "%_signature gpg
%_gpg_name E1933532750E9EEF" > ~/.rpmmacros

Create a directory to serve the repository.

mkdir -p ~/rpm-repo/packages

Move RPM packages into the repo directory. Then sign them with the following command:

rpm --addsign ~/rpm-repo/packages/*.rpm

Create repo index

Once all the packages are signed, create the repository with the following command:

createrepo ~/rpm-repo/packages/

The above command will create a directory in the repo named repodata containing a file named repomd.xml.

Note that the createrepo must be run against each directory in the repo containing .rpm files.

Now sign the repo metadata with the following command:

gpg --detach-sign --armor ~/rpm-repo/packages/repodata/repomd.xml

Create a .repo file

echo "[hyperreal-kernel-bazzite]
name=hyperreal kernel bazzite $releasever
baseurl=https://rpm.hyperreal.coffee/kernel-bazzite/fedora-$releasever/$basearch
enabled=1
gpgcheck=1
gpgkey=https://rpm.hyperreal.coffee/hyperreal-pgp-key.pub" > ~/rpm-repo/hyperreal-kernel-bazzite.repo

The RPM repository should now be ready to be served on a web server with ~/rpm-repo as the web root.

Example Caddy configuration

rpm.hyperreal.coffee {
        root * /home/jas/rpm-repos/
        file_server browse
}

RSS

Source: Simple RSS, Atom, and JSON feed for your blog

RSS

<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Example website title</title>
        <link>https://example.com</link>
        <description>Example website description.</description>
        <atom:link href="https://example.com/rss.xml" rel="self" type="application/rss+xml" />
        <item>
            <title>Post one</title>
            <link>https://example.com/posts-one</link>
            <description>Post one content.</description>
            <guid isPermaLink="true">https://example.com/posts-one</guid>
            <pubDate>Mon, 22 May 2023 13:00:00 -0600</pubDate>
        </item>
        <item>
            <title>Post two</title>
            <link>https://example.com/posts-two</link>
            <description>Post two content.</description>
            <guid isPermaLink="true">https://example.com/posts-two</guid>
            <pubDate>Mon, 15 May 2023 13:00:00 -0600</pubDate>
        </item>
    </channel>
</rss>

Atom

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>http://example.com/</id>
    <title>Example website title</title>
    <updated>2023-05-22T13:00:00.000Z</updated>
    <author>
        <name>John Doe</name>
    </author>
    <link href="https://example.com/atom.xml" rel="self" type="application/rss+xml" />
    <subtitle>Example website description.</subtitle>
    <entry>
        <id>https://example.com/posts-one</id>
        <title>Post one</title>
        <link href="https://example.com/posts-one"/>
        <updated>2023-05-22T13:00:00.000Z</updated>
        <summary type="html">https://example.com/posts-one</summary>
        <content type="html">Post one content.</content>
    </entry>
    <entry>
        <id>https://example.com/posts-two</id>
        <title>Post two</title>
        <link href="https://example.com/posts-two"/>
        <updated>2023-05-15T13:00:00.000Z</updated>
        <summary type="html">https://example.com/posts-two</summary>
        <content type="html">Post two content.</content>
    </entry>
</feed>

JSON

{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Example website title",
  "home_page_url": "https://example.com",
  "feed_url": "https://example.com/feed.json",
  "description": "Example website description.",
  "items": [
    {
      "id": "https://example.com/posts-one",
      "url": "https://example.com/posts-one",
      "title": "Post one content.",
      "content_text": "Post one content.",
      "date_published": "2023-05-22T13:00:00.000Z"
    },
    {
      "id": "https://example.com/posts-two",
      "url": "https://example.com/posts-two",
      "title": "Post two content.",
      "content_text": "Post two content.",
      "date_published": "2023-05-15T13:00:00.000Z"
    }
  ]
}

Resources

Steam

Capturing and sharing Proton logs

  1. Right-click on the game in Steam and select Properties.
  2. Under Launch Options, add the following:
PROTON_LOG=1 PROTON_LOG_DIR=/home/jas/steam-proton-logs %command%

game-performance

To use CachyOS’s game-performance script to switch power profiles while a game is running, add the following to each Steam game’s Launch Options:

game-performance %command%

Combined with the Proton logging options:

PROTON_LOG=1 PROTON_LOG_DIR=/home/jas/steam-proton-logs game-performance %command%

Pre-caching shaders with Proton-CachyOS, -GE and -EM

Pre-caching of shaders is not needed on CachyOS, as Proton-CachyOS already contains all the necessary codecs.

  1. Go to Steam Setttings -> Downloads
  2. Go to the SHADER PRE-CACHING section
  3. Disable Shader Pre-caching
  4. Disable Allow background processing of Vulkan shaders

Systemd

Install systemd-boot on Debian

sudo mkdir /boot/efi/loader
printf "default systemd\ntimeout 5\neditor 1\n" | sudo tee /boot/efi/loader/loader.conf
sudo mkdir -p /boot/efi/loader/entries
sudo apt install -y systemd-boot
sudo bootctl install --path=/boot/efi

Check efibootmgr.

sudo efibootmgr

Output:
BootOrder: 0000,0001
Boot0000* Linux Boot Manager

Mount NFS share

Create a unit file at /etc/systemd/system/mnt-backup.mount. The name of the unit file must match the Where directive. Ex. Where=/mnt/backup –> mnt-backup.mount.

[Unit]
Description=borgbackup NFS share from TrueNAS (10.0.0.81)
DefaultDependencies=no
Conflicts=umount.target
After=network-online.target remote-fs.target
Before=umount.target

[Mount]
What=10.0.0.81:/mnt/coffeeNAS/backup
Where=/mnt/backup
Type=nfs
Options=defaults

[Install]
WantedBy=multi-user.target

Tailscale

Using Codeberg as an OIDC provider

These steps also apply to Gitea and Forgejo instances.

Requirements

  • Use moonshadow.dev for the domain.
  • Use hyperreal@moonshadow.dev for the email. This must be the primary email on your Codeberg, Gitea, or Forgejo account.
  • Setup a web server to host the webfinger file at moonshadow.dev.

Webfinger

In the web root of the web server, create .well-known/webfinger.

{
  "subject": "acct:hyperreal@moonshadow.dev",
  "links": [
    {
      "rel": "http://openid.net/specs/connect/1.0/issuer",
      "href": "https://codeberg.org"
    }
  ]
}

Use the Webfinger lookup tool to make sure it is setup correctly.

Create an OAuth2 application on Codeberg

Go to User Settings -> Applications -> Manage OAuth2 applications.

Application nametailscale
Redirect URIhttps://login.tailscale.com/a/oauth_response
Confidential clientChecked

Click on Create. Copy and save the Client ID and Client secret that were generated.

Sign up with Tailscale

  1. Go to the Tailscale login page, and select “Sign up with OIDC”.
  2. Enter hyperreal@moonshadow.dev for the email.
  3. Choose Codeberg as the identity provider. (optional)
  4. Select “Get OIDC Issuer”.
  5. Enter the Client ID and Client secret saved from the OAuth2 application. Leave everything else as default, and make sure that “consent” is checked under Prompts.
  6. Click “Sign up with OIDC”, and you should be able to login to Tailscale and be redirected to your Tailscale admin console.

Resources

Torrenting

Setup a FreeBSD thick VNET jail for torrenting Anna’s Archive

Setup the VNET bridge

Create the bridge.

ifconfig bridge create

Attach the bridge to the main network interface. igc0 in this case. For some reason, the resulting bridge device is named igb0bridge, rather than bridge0.

ifconfig igb0bridge addm igc0

To make this persistent across reboots, add the following to /etc/rc.conf.

defaultrouter="10.0.0.1"
cloned_interfaces="igb0bridge"
ifconfig_igc0bridge="inet 10.0.0.8/24 addm igc0 up"

Create the classic (thick) jail

Create the ZFS datasets for the jails. We’ll use basejail as a template for subsequent jails.

zfs create -o mountpoint=/jails naspool/jails
zfs create naspool/jails/basejail

Use the bsdinstall utility to bootstrap the base system to the basejail.

export DISTRIBUTIONS="base.txz"
export BSDINSTALL_DISTSITE=https://download.freebsd.org/ftp/releases/amd64/14.2-RELEASE/
bsdinstall jail /jails/basejail

Run freebsd-update to update the base jail.

freebsd-update -b /jails/basejail fetch install
freebsd-update -b /jails/basejail IDS

We now snapshot the basejail and create a clone of this snapshot for the torrenting jail that we will use for Anna’s Archive.

zfs snapshot naspool/jails/basejail@`freebsd-version`
zfs clone naspool/jails/basejail@`freebsd-version` naspool/jails/torrenting

We now use the following configuration for /etc/jail.conf.

torrenting {
    exec.consolelog = "/var/log/jail_console_${name}.log";
    allow.raw_sockets;
    exec.clean;
    mount.devfs;
    devfs_ruleset = 11;
    path = "/jails/${name}";
    host.hostname = "${name}";
    vnet;
    vnet.interface = "${epair}b";
    $id = "127";
    $ip = "10.0.0.${id}/24";
    $gateway = "10.0.0.1";
    $bridge = "igb0bridge";
    $epair = "epair${id}";
    
    exec.prestart = "/sbin/ifconfig ${epair} create up";
    exec.prestart += "/sbin/ifconfig ${epair}a up descr jail:${name}";
    exec.prestart += "/sbin/ifconfig ${bridge} addm ${epair}a up";
    exec.start += "/sbin/ifconfig ${epair}b ${ip} up";
    exec.start += "/sbin/route add default ${gateway}";
    exec.start += "/bin/sh /etc/rc";
    exec.stop = "/bin/sh /etc/rc.shutdown";
    exec.poststop = "/sbin/ifconfig ${bridge} deletem ${epair}a";
    exec.poststop += "/sbin/ifconfig ${epair}a destroy";
}

Now we create the devfs ruleset to enable access to devices under /dev inside the jail. Add the following to /etc/devfs.rules.

[devfsrules_jail_vnet=11]
add include $devfsrules_hide_all
add include $devfsrules_unhide_basic
add include $devfsrules_unhide_login
add include $devfsrules_jail
add path 'tun*' unhide
add path 'bpf*' unhide

Enable the jail utility in /etc/rc.conf.

sysrc jail_enable="YES"
sysrc jail_parallel_start="YES"

Start the jail service for torrenting jail.

service jail start torrenting

Setting up Wireguard inside the jail

Since we have the /dev/tun* devfs rule, we now need to install Wireguard inside the jail.

jexec -u root torrenting

pkg install wireguard-tools wireguard-go

Download a Wireguard configuration for ProtonVPN, and save it to /usr/local/etc/wireguard/wg0.conf.

Enable Wireguard to run when the jail boots up.

sysrc wireguard_enable="YES"
sysrc wireguard_interfaces="wg0"

Start the Wireguard daemon and make sure you are connected to it properly.

service wireguard start

curl ipinfo.io

The curl command should display the IP address of the Wireguard server defined in /usr/local/etc/wireguard/wg0.conf.

Setting up qBittorrent inside the jail

Install the qbittorrent-nox package.

pkg install -y qbittorrent-nox

Before running the daemon from /usr/local/etc/rc.d/qbittorrent, we must run the qbittorrent command from the shell so that we can see the default password generated for the web UI. For some reason it is not shown in any logs, and the qbittorrent nox manpage wrongly says the password is “adminadmin”.

pkg install -y sudo
sudo -u qbittorrent qbittorrent-nox --profile=/var/db/qbittorrent/conf --save-path=/var/db/qbittorrent/Downloads --confirm-legal-notice 

Copy the password displayed after running the command. Login to the qBittorrent web UI at http://10.0.0.127:8080 with login admin and the password you copied. In the web UI, open the options menu and go over to the Web UI tab. Change the login password to your own. Save the options to close the menu.

Now press CTRL-c to stop the qbittorrent-nox process. Make the following changes to the torrenting jail’s /etc/rc.conf.

sysrc qbittorrent_enable="YES"
sysrc qbittorrent_flags="--confirm-legal-notice"

Enable the qBittorrent daemon.

service qbittorrent start

Go back to the web UI at http://10.0.0.127:8080. Go to the options menu and go over to the Advanced tab, which is the very last tab. Change the network interface to wg0.

Finding the forwarded port that the ProtonVPN server is using

Install the libnatpmp package.

Make sure that port forwarding is allowed on the server you’re connected to, which it should be if you enabled it while creating the Wireguard configuration on the ProtonVPN website. Run the natpmpc command against the ProtonVPN Wireguard gateway.

natpmpc -g 10.2.0.1

If the output looks like the following, you’re good.

initnatpmp() returned 0 (SUCCESS)
using gateway : 10.2.0.1
sendpublicaddressrequest returned 2 (SUCCESS)
readnatpmpresponseorretry returned 0 (OK)
Public IP address : 62.112.9.165
epoch = 58081
closenatpmp() returned 0 (SUCCESS)

Now create the UDP and TCP port mappings, then loop natpmpc so that it doesn’t expire.

while true ; do date ; natpmpc -a 1 0 udp 60 -g 10.2.0.1 && natpmpc -a 1 0 tcp 60 -g 10.2.0.1 || { echo -e "ERROR with natpmpc command \a" ; break ; } ; sleep 45 ; done

The port allocated for this server is shown on the line that says “Mapped public port XXXXX protocol UDP to local port 0 lifetime 60”. Port forwarding is now activated. Copy this port number and, in the qBittorrent web UI options menu, go to the Connections tab and enter it into the “Port used for incoming connections” box. Make sure to uncheck the “Use UPnP/NAT-PMP port forwarding from my router” box.

If the loop terminates, you’ll need to re-run this loop script each time you start a new port forwarding session or the port will only stay open for 60 seconds.

P2P NAT port forwarding script with supervisord

Install supervisord.

sudo pkg install -y py311-supervisor

Enable the supervisord service.

sudo sysrc supervisord_enable="YES"

Edit /usr/local/etc/supervisord.conf, and add the following to the bottom of the file.

[program:natpmpcd]
command=/usr/local/bin/natpmpcd
autostart=true

Add the following contents to a file at /usr/local/bin/natpmpcd.

#!/bin/sh

port=$(/usr/local/bin/natpmpc -a 1 0 udp 60 -g 10.2.0.1 | grep "Mapped public port" | awk '{print $4}')
echo $port | tee /usr/local/etc/natvpn_port.txt

while true; do
    date
    if ! /usr/local/bin/natpmpc -a 1 0 udp 60 -g 10.2.0.1 && /usr/local/bin/natpmpc -a 1 0 tcp 60 -g 10.2.0.1; then
        echo "error Failure natpmpc $(date)"
        break
    fi
    sleep 45
done

Ensure the script is executable with chmod +x /usr/local/bin/natpmpcd.

supervisord will start the above shell script automatically. Ensure supervisord service is started.

sudo service supervisord start

The script will print out the forwarded port number at /usr/local/etc/natvpn_port.txt.

cat /usr/local/etc/natvpn_port.txt
48565

UFW

Allow ports only on specific interface

ufw allow in on tailscale0 to any port 22 proto tcp

Weechat

Smart filter for JOIN, PART, QUIT

/set irc.look.smart_filter on
/filter add irc_smart * irc_smart_filter *

Windows

  • Download Windows 11 ISO from Microsoft and write to USB.
  • Boot into Windows setup utility.
  • Select Repair computer –> Troubleshoot –> Advanced –> Cmd prompt.

This procedure assumes the following:

  • main disk is disk 0
  • EFI partition is part 1
  • Windows OS drive letter is c:

The following command will format the old EFI partition, mount it to s:, and copy the boot files to it.

diskpart
> list disk
> sel disk 0
> list part
> sel part 1
> format fs=fat32 quick label=System
> list vol
> exit
mountvol S: /S
bcdboot c:\windows /s s: /f UEFI /v
exit