Triaxx Web Log

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: