Some notes on anything… Most of the stuff noted here are probably (i) not really clear, (ii) not really true and (iii) not really useful.
Let's Encrypt
Obtain or renew a certificate, but do not install it:
email=triaxx@triaxx.io
domains="triaxx_io triaxx_net"
triaxx_io_subdomains="dev mx ns0 ns1 www"
triaxx_net_subdomains="dns mail ns www"
for _domain in ${domains} ; do
eval subdomains=\$${_domain}_subdomains
domain=`echo ${_domain} | sed -e 's,_,\.,g'`
domain_args="--domain ${domain}"
for _subdomain in ${subdomains} ; do
domain_args="${domain_args} --domain ${_subdomain}.${domain}"
done
certbot certonly \
--non-interactive \
--agree-tos \
--email ${email} \
--nginx \
${domain_args} \
--expand
done
Plan the automatic (monthly) update:
crontab -e
0 0 1 * * /usr/bin/certbot renew
Restart NGINX when new certificate is deployed:
Loop device
Setup
Mount a GPT disk stored on LVM:
losetup --partscan loop0 /dev/vg0/lv-vm0
mount /dev/loop0p3 /mnt
Gentoo
Installation
Fetch an installation image and extract it in a logical volume for use with Xen)
Partitioning
fdisk /dev/xvda
Welcome to fdisk (util-linux 2.41.3).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
Device does not contain a recognized partition table.
Created a new DOS (MBR) disklabel with disk identifier 0xeada774e.
Command (m for help): o
Created a new DOS (MBR) disklabel with disk identifier 0x10aabe84.
Command (m for help): n
Partition type
p primary (0 primary, 0 extended, 4 free)
e extended (container for logical partitions)
Select (default p):
Using default response p.
Partition number (1-4, default 1):
First sector (2048-67108863, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-67108863, default 67108863): +1G
Created a new partition 1 of type 'Linux' and of size 1 GiB.
Command (m for help): n
Partition type
p primary (1 primary, 0 extended, 3 free)
e extended (container for logical partitions)
Select (default p):
Using default response p.
Partition number (2-4, default 2):
First sector (2099200-67108863, default 2099200):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2099200-67108863, default 67108863): +4G
Created a new partition 2 of type 'Linux' and of size 4 GiB.
Command (m for help): t
Partition number (1,2, default 2):
Hex code or alias (type L to list all): 82
Changed type of partition 'Linux' to 'Linux swap / Solaris'.
Command (m for help): n
Partition type
p primary (2 primary, 0 extended, 2 free)
e extended (container for logical partitions)
Select (default p):
Using default response p.
Partition number (3,4, default 3):
First sector (10487808-67108863, default 10487808):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (10487808-67108863, default 67108863):
Created a new partition 3 of type 'Linux' and of size 27 GiB.
Command (m for help): p
Disk /dev/xvda: 32 GiB, 34359738368 bytes, 67108864 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x10aabe84
Device Boot Start End Sectors Size Id Type
/dev/xvda1 2048 2099199 2097152 1G 83 Linux
/dev/xvda2 2099200 10487807 8388608 4G 82 Linux swap / Solaris
/dev/xvda3 10487808 67108863 56621056 27G 83 Linux
Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
Filesystem creation
mkfs.xfs -c options=/usr/share/xfsprogs/mkfs/lts_6.18.conf /dev/xvda3
mkfs.ext4 /dev/xvda1
mkswap /dev/xvda2
mkdir --parents /mnt/gentoo
mount /dev/xvda3 /mnt/gentoo
swapon /dev/xvda2
mkdir /mnt/gentoo/boot
mount /dev/xvda1 /mnt/gentoo/boot
Stage installation
wget https://distfiles.gentoo.org/releases/amd64/autobuilds/20260503T164604Z/stage3-amd64-hardened-openrc-20260503T164604Z.tar.xz
Verify files:
DNS
Configure a DNS server on OpenBSD.
nsd
# rcctl enable nsd
Create configuration file:
# cat > /var/nsd/etc/nsd.conf << EOF
remote-control:
control-enable: yes
control-interface: /var/run/nsd.sock
server:
hide-version: yes
zone:
name: "triaxx.xyz"
zonefile: "master/triaxx.io.zone"
EOF
Check that configuration is correct:
# nsd-checkconf /var/nsd/etc/nsd.conf
# echo $?
0
Create zone file:
# cat > /var/nsd/zones/master/triaxx.io.zone << EOF
$TTL 86400
$ORIGIN triaxx.io.
@ IN SOA ns0.triaxx.io. hostmaster.triaxx.io. (
2026043000 ; serial
1800 ; refresh (30 mins)
900 ; retry (15 mins)
604800 ; expire (7 days)
1200 ) ; minimum (20 mins)
@ IN NS ns0
@ IN NS ns1
@ IN MX 10 mx
@ IN A 175.52.128.39
mx IN A 175.52.128.39
ns0 IN A 175.52.128.39
ns1 IN A 105.7.42.123
EOF
Check that zone is correct:
OpenBSD
Installation
Fetch the installation image (that can be use with Xen):
# wget https://cdn.openbsd.org/pub/OpenBSD/7.8/amd64/install78.img
The installer can be used to easily install a functional system.
Packages
Install pkg_locate to find files in OpenBSD packages:
# pkg_add pkglocatedb
quirks-7.147 signed on 2025-10-10T23:15:26Z
pkglocatedb-1.5: ok
Services
Enable and start nsd:
# rcctl enable nsd
# rcctl start nsd
nftables
Netfilter configuration
Enable IP forwarding:
# sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1
Make it persitent:
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
Connection forwarding
#!/usr/sbin/nft -f
flush ruleset
define inet_if = "enp1s0"
define inet_ip = "194.153.151.88"
define vm1_ip = "192.168.0.2"
table inet filter {
chain input {
type filter hook input priority filter;
}
chain forward {
type filter hook forward priority filter;
ct state established,related accept
}
chain output {
type filter hook output priority filter;
}
}
table ip nat {
chain prerouting {
type nat hook prerouting priority dstnat;
iifname $inet_if tcp dport 80 dnat to $vm1_ip
}
chain postrouting {
type nat hook postrouting priority srcnat;
ip saddr $vm1_ip oifname $inet_if snat to $inet_ip
}
}
ACL
Access Control List
Set read + execute permissions to /etc/fooctl for jdoe user:
setfactl --modify user:jdoe:rX /etc/fooctl
ls -adl /etc/fooctl
drwxr-x---+ 2 root root 4096 Apr 8 11:59 /etc/fooctl
Set read permission to /etc/fooctl/fooctl.conf for jdoe user:
setfactl --modify user:jdoe:r /etc/fooctl/fooctl.conf
ls -adl /etc/fooctl/fooctl.conf
-rw-rw-r--+ 1 root root 1943 Apr 8 11:59 /etc/fooctl/fooctl.conf
getfacl /etc/fooctl/fooctl.conf
getfacl: Removing leading '/' from absolute path names
# file: etc/fooctl/fooctl.conf
# owner: root
# group: root
user::rw-
user:jdoe:r--
group::rw-
mask::rw-
other::r--
Remove read permission from /etc/fooctl/fooctl.conf for jdoe user:
Netstat
Show listening TCP connections:
netstat --listening --tcp
Show programs associated to listening TCP and UDP connections (root privilege needed):
netstat --listening --tcp --udp --program
Xen
Dom0 is here Debian GNU/Linux Trixie. Install hypervisor:
apt install xen-hypervisor
systemctl reboot
OpenBSD as DomU
Add file /etc/xen/auto/obsd.cfg
type = "hvm"
name = "obsd"
memory = 4096
vcpus = 2
disk = [ '/opt/xen/install78.iso,,xvdc,cdrom', '/dev/vg0/lv-obsd,,xvda' ]
vif = [ 'mac=00:16:3e:a1:ff:01,bridge=xenbr0' ]
serial = "pty"
vnc = 1
vnclisten = 127.0.0.1
vncdisplay = 42
Create bridge interface:
cat > /etc/systemd/network/xenbr0.netdev << EOF
Name=xenbr0
Kind=bridge
EOF
cat > /etc/systemd/network/xenbr0.network << EOF
[Match]
Name=xenbr0
[Network]
Address=192.168.0.1/24
EOF
systemctl enable --now systemd-networkd
Redirect output to serial console:
xl create -c /etc/xen/auto/obsd.cfg
From the VNC viewer:
boot> set tty com0
LVM
Initialize partition sda5 for use by LVM:
pvcreate /dev/sda5
Create Volume Group vg0 on partition sda5:
vgcreate vg0 /dev/sda5
Create Logical Volume lv-data in Volum Group vg0:
lvcreate --size 64g vg0 --name lv-data
Reduce size of Logical Volume lv-data in Volum Group vg0 when lv-data does not contain filesystem:
lvreduce --size 16g vg0/lv-data
Disk encryption
LUKS
Erase table and filesystem signatures and overwrite device with random data:
wipefs --all /dev/sde
dd if=/dev/urandom of=/dev/sde bs=4M status=progress
Initialize a LUKS partition and open it:
cryptsetup luksFormat /dev/sde
WARNING!
========
This will overwrite data on /dev/sde irrevocably.
Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/sde:
Verify passphrase:
cryptsetup open /dev/sde foo
Enter passphrase for /dev/sde:
Make filesystem and mount it:
mkfs.ext4 /dev/mapper/foo
mke2fs 1.47.4 (6-Mar-2025)
Creating filesystem with 111616 1k blocks and 27888 inodes
Filesystem UUID: e4805c08-4d97-48c8-921f-91b203279131
Superblock backups stored on blocks:
8193, 24577, 40961, 57345, 73729
Allocating group tables: done
Writing inode tables: done
Creating journal (4096 blocks): done
Writing superblocks and filesystem accounting information: done
mount /dev/mapper/foo /mnt
GPG
Configuration
See short key id when listing keys:
echo "keyid-format short" >> ~/.gnupg/gpg.conf
Generation
Generate a main key to sign and to certificate and a subkey to encrypt:
gpg --full-generate-key
gpg (GnuPG) 2.5.18; Copyright (C) 2025 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Please select what kind of key you want:
(1) RSA and RSA
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
(9) ECC (sign and encrypt) *default*
(10) ECC (sign only)
(14) Existing key from card
(16) ECC and Kyber
Your selection?
Please select which elliptic curve you want:
(1) Curve 25519 *default*
(4) NIST P-384
(6) Brainpool P-256
Your selection?
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y
GnuPG needs to construct a user ID to identify your key.
Real name: John Doe
Email address: john@doe.net
Comment:
You selected this USER-ID:
"John Doe <john@doe.net>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: revocation certificate stored as '/home/jdoe/.gnupg/openpgp-revocs.d/4960E99609DA1753CC6798800DB4D7504AFFB5E4.rev'
public and secret key created and signed.
pub ed25519/4AFFB5E4 2026-03-25 [SC]
4960E99609DA1753CC6798800DB4D7504AFFB5E4
uid John Doe <john@doe.net>
sub cv25519/D76499B8 2026-03-25 [E]
58C2231CAEAF923DB403051E3CFD617FD76499B8
Edit key to add a signing subkey:
Restic
Use restic to backup a set of files/directories on several repositories.
Repo initialization
mkdir -p ~/.config/restic
echo "sftp:bak.triaxx.io:/srv/restic-repo" > .config/restic/repo-bak
restic --repository-file ~/.config/restic/repo-bak init
Backup
find /etc /home -print > ~/.config/restic/files
restic --repository-file ~/.config/restic/repo-bak \
backup \
--files-from-verbatim ~/.config/restic/files \
--skip-if-unchanged
Linux Automation
Create a systemd template service to execute restic backup for each repository:
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/restic-backup@.service << EOF
[Unit]
Description=Restic backup service for %i repository
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/restic backup --files-from-verbatim %E/restic/files --skip-if-unchanged
ExecStartPost=/usr/bin/restic forget --keep-last 5 --prune
Environment="RESTIC_PASSWORD_COMMAND=%E/restic/passcmd"
Environment="RESTIC_REPOSITORY_FILE=%E/restic/repo-%i"
[Install]
WantedBy=default.target
EOF
Create a target to backup on several repositories:
EFI
Efibootmgr
List UEFI entries:
efibootmgr
Delete entry 0013:
efibootmgr --delete-bootnum --bootnum 0013
Create new entry:
efibootmgr --create --label "HD001" --loader "\EFI\BOOT\BOOTX64.EFI"
Disk check
Bad blocks
Install on NetBSD:
# pkgin install e2fsprogs
or on macOS:
# brew install e2fsprogs
Unmount device first on macOS:
# diskutil unmountDisk /dev/<device>
Start test:
# badblocks -n -s -v /dev/<device>
where <device> can be identified by executing:
# dmesg | tail
right after plugging in the USB flash storage on GNU/Linux or NetBSD and
# diskutil list
on macOS.
Smartmontools
Print health status of device /dev/sdb:
# smartctl --health /dev/sdb
smartctl 7.5 2025-04-30 r5714 [x86_64-linux-7.0.3-arch1-1] (local build)
Copyright (C) 2002-25, Bruce Allen, Christian Franke, www.smartmontools.org
=== START OF READ SMART DATA SECTION ===
SMART overall-health self-assessment test result: PASSED
Start long off-line test on device /dev/sdb:
Arch Linux
Installation
Boot image
curl --output archlinux-x86_64.iso https://mirror.srv.fail/archlinux/iso/2026.03.01/archlinux-x86_64.iso
# on macOS
sudo dd if=archlinux-x86_64.iso of=/dev/disk4 bs=1m status=progress
# on NetBSD
dd if=/home/triaxx/archlinux-x86_64.iso of=/dev/sd2d bs=1m progress=100
System configuration
After booting from USB disk:
localectl list-keymaps | grep fi
loadkeys fi
timedatectl set-timezone Europe/Helsinki
Partitionning
Erase table or filesystem signatures:
wipefs --all /dev/sda
Create empty partition table:
echo 'label: gpt' | sfdisk /dev/sda
List available types for GPT:
sfdisk --label gpt --list-types
Create partitions:
echo "size=512MiB, name=efi, type=efi-system" | sfdisk /dev/sda
echo "size=512MiB, name=boot, type=linux-extended-boot" | sfdisk /dev/sda --append
echo "size=36GiB, name=linux-swap, type=linux-swap" | sfdisk /dev/sda --append
echo "size=128GiB, name=linux-root, type=linux-root-x86-64" | sfdisk /dev/sda --append
echo "size=36GiB, name=freebsd-swap, type=freebsd-swap" | sfdisk /dev/sda --append
echo "size=128GiB, name=freebsd-root, type=freebsd-ufs" | sfdisk /dev/sda --append
echo "size=+, name=data, type=freebsd-zfs" | sfdisk /dev/sda --append
Make filesystems:
Figlet
File header
figlet Makefile | sed -e "s|^|# |" > Makefile
Mercurial
Initialization
hg init
cat >> .hg/hgrc << EOF
[ui]
username = triaxx <triaxx@triaxx.io>
EOF
Usage
hg status
hg add <file>
hg forget <file>
hg commit
hg log --graph
Change the message of the last commit:
hg commit --amend
Revert to the last commit version:
hg revert
hg revert --no-backup
Extensions
Enable histedit:
cat >> .hg/hgrc << EOF
[extensions]
histedit =
Hugo
Abstract
Hugo is an open-source static website generator. It is written in Go and uses Markdown file format as input to produce static HTML website.
Initialization
Site
hugo new project <path>
cd <path>
cat > hugo.toml << EOF
baseURL = 'https://triaxx.io/'
languageCode = 'fr-FR'
title = 'Triaxx Web Log'
EOF
Theme
A theme is mandatory to build sites.
hugo new theme <name>
echo "theme = '<name>' >> hugo.toml
A example menu can be copied from themes/<name>/hugo.toml: