Arch Linux Fresh Install: Bare Metal to Productive Desktop
Complete runbook for installing Arch Linux on a new machine — from BIOS settings to a fully productive Hyprland desktop with all tools, repos, and secrets. Each phase is self-contained and can be re-run independently.
1. Phase 0: Pre-Installation
1.1. Find Latest Arch ISO
curl -s https://archlinux.org/download/ | grep -oP 'archlinux-\d{4}\.\d{2}\.\d{2}-x86_64\.iso' | head -1
1.2. Download ISO + Signature
curl -LO https://geo.mirror.pkgbuild.com/iso/latest/archlinux-x86_64.iso
curl -LO https://geo.mirror.pkgbuild.com/iso/latest/archlinux-x86_64.iso.sig
1.3. Verify Signature
gpg --keyserver-options auto-key-retrieve --verify archlinux-x86_64.iso.sig archlinux-x86_64.iso
1.4. Identify USB Device
# Plug in USB — identify the DEVICE (sda), NOT a partition (sda1)
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT
1.5. Write USB
1.5.1. Option A: Ventoy (recommended — multi-ISO, reusable)
Ventoy is AUR — not in main repos:
# Install from AUR
yay -S ventoy
# Or manually
git clone https://aur.archlinux.org/ventoy.git /tmp/ventoy
cd /tmp/ventoy && makepkg -si
# Write Ventoy bootloader to USB (WIPES the drive)
sudo ventoy -i /dev/sdX
# Copy ISO to Ventoy data partition
sudo mount -o uid=$(id -u),gid=$(id -g) /dev/sdX1 /mnt
cp archlinux-x86_64.iso /mnt/
sudo umount /mnt
1.5.2. Option B: Direct dd (single ISO, destroys USB)
# TRIPLE CHECK lsblk — wrong device = wiped disk
lsblk
sudo dd bs=4M if=archlinux-x86_64.iso of=/dev/sdX status=progress oflag=sync
1.6. BIOS/UEFI Settings
|
Do this BEFORE booting the USB. Every vendor hides these differently. |
| Setting | Value |
|---|---|
Secure Boot |
Disabled (re-enable after install with signed bootloader if desired) |
Boot Mode |
UEFI only (disable Legacy/CSM) |
Boot Order |
USB first, then NVMe |
Intel VMX / AMD-V |
Enabled (for KVM/libvirt later) |
TPM |
Enabled (optional, for measured boot) |
1.7. Boot the USB
-
Power off, insert USB
-
Power on, press boot menu key (F12 / F2 / Del — vendor-specific)
-
Select USB UEFI entry
-
At Arch menu, select Arch Linux install medium
1.8. Verify UEFI Mode
# This directory must exist — if not, you're in Legacy mode
ls /sys/firmware/efi/efivars
1.9. Set Console Font (HiDPI laptops)
# Tiny text on 4K screen? Fix it
setfont ter-132b
1.10. Connect to Network
1.10.2. WiFi with iwctl (iPSK networks)
|
If the WiFi network uses iPSK authentication (e.g., DOMUS-IoT), the device MAC must be registered in the iPSK identity group BEFORE connecting. ISE validates the MAC via ODBC against the iPSK Manager.
|
iwctl
# Inside iwctl shell:
device list
station wlan0 scan
station wlan0 get-networks
station wlan0 connect "YourSSID"
# Enter iPSK password when prompted
exit
ping -c 3 archlinux.org
1.11. Enable SSH (Remote Install from Razer)
This lets you SSH in from modestus-razer and run the entire install remotely — copy/paste from the rendered runbook, no squinting at unscaled HiDPI.
These 3 commands are typed on the T16g console. Everything after this is from the Razer.
# Set root password for the live environment
passwd
# Start SSH daemon
systemctl start sshd
# Get the T16g's IP address
ip -4 -o addr show | awk '$2!="lo" {print $2, $4}'
1.11.1. From the Razer
# SSH into the T16g live environment (replace IP)
ssh root@<T16G-IP>
ssh root@10.50.10.42
|
After reboot (end of Phase 3), the live SSH session dies. The installed system will get a new IP via DHCP. SSH is enabled in Phase 4 (
Or check your DHCP server / router for the new lease. |
1.13. Update Mirrors
reflector --country US --age 12 --protocol https --sort rate --save /etc/pacman.d/mirrorlist
2. Phase 1: Disk Setup
2.1. Identify Target Disk
lsblk
fdisk -l
|
NVMe drives: |
2.2. Partition Layout (UEFI + Dual LUKS + Btrfs)
Production layout — four partitions with separate LUKS volumes for root and home:
| Partition | Size | Type | Mount |
|---|---|---|---|
|
512M |
EFI System (ef00) |
|
|
2G |
Linux filesystem (8300) |
|
|
250G |
Linux LUKS (8309) |
LUKS → cryptroot → btrfs → |
|
Remaining |
Linux LUKS (8309) |
LUKS → crypthome → btrfs → |
Btrfs subvolumes per LUKS volume:
| Volume | Subvolumes |
|---|---|
cryptroot |
|
crypthome |
|
|
Why this layout (not single-LUKS):
Dual-NVMe machines (e.g., modestus-razer): use the second NVMe as a third LUKS volume ( |
2.3. Wipe and Partition
|
This destroys ALL data on the target disk. Triple-check |
# Wipe partition table and create GPT
sgdisk -Z /dev/nvme0n1
# Create EFI partition (512M)
sgdisk -n 1:0:+512M -t 1:ef00 /dev/nvme0n1
# Create /boot partition (2G — holds main + LTS + fallback initramfs)
sgdisk -n 2:0:+2G -t 2:8300 /dev/nvme0n1
# Create root partition (250G)
sgdisk -n 3:0:+250G -t 3:8309 /dev/nvme0n1
# Create home partition (remaining space)
sgdisk -n 4:0:0 -t 4:8309 /dev/nvme0n1
# Verify — should show 4 partitions
sgdisk -p /dev/nvme0n1
2.4. LUKS Encryption
2.4.2. Home Volume
# Use a DIFFERENT passphrase than root, or the same — your call
cryptsetup luksFormat /dev/nvme0n1p4
cryptsetup open /dev/nvme0n1p4 crypthome
# Verify both LUKS containers are open
ls /dev/mapper/crypt*
2.5. Format Filesystems
# EFI — FAT32
mkfs.fat -F32 /dev/nvme0n1p1
# /boot — ext4
mkfs.ext4 /dev/nvme0n1p2
# Root — Btrfs on LUKS (label for fstab readability)
mkfs.btrfs -L archroot /dev/mapper/cryptroot
# Home — Btrfs on LUKS
mkfs.btrfs -L archhome /dev/mapper/crypthome
2.6. Create Btrfs Subvolumes
2.6.1. Root subvolumes (cryptroot)
mount /dev/mapper/cryptroot /mnt
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@snapshots
btrfs subvolume create /mnt/@var_log
# Verify — 3 subvolumes
btrfs subvolume list /mnt
umount /mnt
2.6.2. Home subvolumes (crypthome)
mount /dev/mapper/crypthome /mnt
btrfs subvolume create /mnt/@home
# Verify — 1 subvolume
btrfs subvolume list /mnt
umount /mnt
2.7. Mount Everything
Mount order matters — root first, then create mount points, then the rest.
# Mount root subvolume with production options
mount -o noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=@ /dev/mapper/cryptroot /mnt
# Create mount points
mkdir -p /mnt/{boot/efi,home,.snapshots,var/log}
# Mount remaining root subvolumes
mount -o noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=/@snapshots /dev/mapper/cryptroot /mnt/.snapshots
mount -o noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=/@var_log /dev/mapper/cryptroot /mnt/var/log
# Mount home from separate LUKS volume
mount -o noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=/@home /dev/mapper/crypthome /mnt/home
# Mount /boot (ext4)
mount /dev/nvme0n1p2 /mnt/boot
# Mount EFI
mount /dev/nvme0n1p1 /mnt/boot/efi
# Final verification — all 6 mount points visible
lsblk /dev/nvme0n1
# Detailed mount verification
findmnt -t btrfs,vfat,ext4 --output TARGET,SOURCE,FSTYPE,OPTIONS -n
|
Mount options explained:
|
3. Phase 2: Base System
3.1. Install Base Packages
pacstrap -K /mnt \
base \
linux \
linux-headers \
linux-lts \
linux-lts-headers \
linux-firmware \
base-devel \
btrfs-progs \
git \
neovim \
zsh \
sudo \
networkmanager \
openssh \
intel-ucode \
iwd \
wireless_tools \
zram-generator
|
3.2. Generate fstab
genfstab -U /mnt >> /mnt/etc/fstab
# Verify — should show entries for:
# / (cryptroot, subvol=/@)
# /.snapshots (cryptroot, subvol=/@snapshots)
# /var/log (cryptroot, subvol=/@var_log)
# /home (crypthome, subvol=/@home)
# /boot (ext4)
# /boot/efi (vfat)
cat /mnt/etc/fstab
|
Verify every btrfs mount has the full options: |
3.3. Enter Chroot
arch-chroot /mnt
3.4. Configure crypttab (Dual LUKS)
The kernel cmdline handles cryptroot at boot. Additional LUKS volumes need /etc/crypttab so systemd opens them during init.
# Get the UUID of the home LUKS partition (nvme0n1p4, NOT the mapper)
blkid -s UUID -o value /dev/nvme0n1p4
# Create crypttab — replace <HOME-LUKS-UUID> with the UUID above
cat > /etc/crypttab << 'EOF'
# <name> <device> <password> <options>
crypthome UUID=<HOME-LUKS-UUID> none luks,timeout=90
EOF
|
Optional — auto-unlock with keyfile (eliminates second prompt):
The keyfile lives on encrypted root — it’s only accessible after cryptroot is unlocked. |
3.6. Locale
sed -i 's/^#en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
locale-gen
echo "LANG=en_US.UTF-8" > /etc/locale.conf
3.7. Hostname
# Replace with your hostname (e.g., modestus-t16g, modestus-legion)
echo "modestus-t16g" > /etc/hostname
cat > /etc/hosts << 'EOF'
127.0.0.1 localhost
::1 localhost
127.0.1.1 modestus-t16g.inside.domusdigitalis.dev modestus-t16g
EOF
3.8. Configure zram Swap
cat > /etc/systemd/zram-generator.conf << 'EOF'
[zram0]
zram-size = ram / 2
compression-algorithm = zstd
EOF
|
This creates compressed swap in RAM at half your physical memory (32G for 64GB RAM). No swap partition needed. zstd compression typically achieves 2-3x ratio, giving effectively 64-96GB of usable swap space. The Razer runs 31.1G zram — verify after first boot with |
3.9. Root Password
passwd
3.10. Create User
useradd -m -G wheel -s /bin/zsh evanusmodestus
passwd evanusmodestus
# Enable sudo for wheel group
EDITOR=nvim visudo
# Uncomment: %wheel ALL=(ALL:ALL) ALL
4. Phase 3: Bootloader
4.1. mkinitcpio Configuration
nvim /etc/mkinitcpio.conf
Set the following:
MODULES=()
HOOKS=(base udev autodetect modconf kms keyboard keymap consolefont block encrypt btrfs filesystems fsck)
|
Key choices (matching modestus-razer production):
|
mkinitcpio -P
This generates four images (main + fallback for both kernels):
-
/boot/initramfs-linux.img -
/boot/initramfs-linux-fallback.img -
/boot/initramfs-linux-lts.img -
/boot/initramfs-linux-lts-fallback.img
4.2. Install systemd-boot
bootctl --esp-path=/boot/efi --boot-path=/boot install
|
|
4.3. Loader Configuration
cat > /boot/efi/loader/loader.conf << 'EOF'
default arch.conf
timeout 3
console-mode max
editor no
EOF
4.4. Get UUIDs
# LUKS partition UUID for cryptdevice= (the RAW partition, not the mapper)
blkid -s UUID -o value /dev/nvme0n1p3
Record this UUID — it goes in every boot entry’s options line.
4.5. Create Boot Entries
4.5.1. Main Entry
cat > /boot/efi/loader/entries/arch.conf << 'EOF'
title Arch Linux
linux /vmlinuz-linux
initrd /intel-ucode.img
initrd /initramfs-linux.img
options cryptdevice=UUID=<LUKS-UUID>:cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rw nvidia_drm.modeset=1 mem_sleep_default=s2idle
EOF
4.5.2. Fallback Entry
cat > /boot/efi/loader/entries/arch-fallback.conf << 'EOF'
title Arch Linux (fallback)
linux /vmlinuz-linux
initrd /intel-ucode.img
initrd /initramfs-linux-fallback.img
options cryptdevice=UUID=<LUKS-UUID>:cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rw nvidia_drm.modeset=1 mem_sleep_default=s2idle
EOF
4.5.3. LTS Entry
cat > /boot/efi/loader/entries/arch-lts.conf << 'EOF'
title Arch Linux LTS
linux /vmlinuz-linux-lts
initrd /intel-ucode.img
initrd /initramfs-linux-lts.img
options cryptdevice=UUID=<LUKS-UUID>:cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rw nvidia_drm.modeset=1 mem_sleep_default=s2idle
EOF
|
Replace Kernel parameters explained:
|
4.6. Verify Boot Entries
bootctl list
# Should show 3 entries: Arch Linux, Arch Linux (fallback), Arch Linux LTS
ls /boot/efi/loader/entries/
4.7. Exit Chroot and Reboot
exit
umount -R /mnt
reboot
Remove the USB when the system powers off.
|
At boot you will see TWO passphrase prompts (unless you configured a keyfile):
This is expected behavior for dual-LUKS. |
5. Phase 4: First Boot
5.1. Login & Re-establish SSH
Log in as evanusmodestus on the T16g console (not root). You’ll enter the LUKS passphrase(s) during boot, then get a TTY login.
On the T16g console (last commands before going back to Razer):
# Connect to network
ip addr
If wired, DHCP should auto-assign. If WiFi:
nmcli device wifi connect "YourSSID" password "YourPassword"
ping -c 3 archlinux.org
# Enable and start SSH immediately
sudo systemctl enable --now sshd
# Get the new IP (different from live ISO)
ip -4 -o addr show | awk '$2!="lo" {print $2, $4}'
Back on the Razer — reconnect:
ssh evanusmodestus@<NEW-T16G-IP>
|
From this point forward, everything runs via SSH from the Razer. The T16g console is only needed again if SSH breaks or for LUKS passphrase entry at reboot. |
5.2. Full System Update
sudo pacman -Syu
5.3. Install AUR Helper (yay)
git clone https://aur.archlinux.org/yay.git /tmp/yay
cd /tmp/yay && makepkg -si
cd ~
5.4. Enable Multilib (32-bit support)
# Uncomment [multilib] section
sudo nvim /etc/pacman.conf
sudo pacman -Syu
5.5. Install Essential System Packages
sudo pacman -S \
stow \
age \
man-db \
man-pages \
htop \
btop \
tree \
wget \
curl \
unzip \
zip \
p7zip
|
|
6. Phase 5: Desktop Environment
6.1. GPU Driver
6.1.1. NVIDIA (Lenovo Legion / discrete GPU)
sudo pacman -S nvidia nvidia-utils nvidia-settings lib32-nvidia-utils
# Verify
nvidia-smi
6.1.2. Intel (integrated only)
sudo pacman -S mesa intel-media-driver vulkan-intel
6.2. Hyprland Stack
sudo pacman -S \
hyprland \
xdg-desktop-portal-hyprland \
waybar \
wofi \
mako \
hyprlock \
hypridle \
hyprpaper \
grim \
slurp \
wl-clipboard \
cliphist \
swappy
6.4. Fonts
sudo pacman -S \
ttf-jetbrains-mono-nerd \
ttf-nerd-fonts-symbols \
ttf-font-awesome \
noto-fonts \
noto-fonts-emoji \
noto-fonts-cjk
6.5. Audio
sudo pacman -S \
pipewire \
pipewire-alsa \
pipewire-pulse \
pipewire-jack \
wireplumber \
pavucontrol
systemctl --user enable --now pipewire pipewire-pulse wireplumber
6.7. Display Manager (optional)
# SDDM for graphical login, or just use TTY login + Hyprland auto-start
sudo pacman -S sddm
sudo systemctl enable sddm
6.8. File Manager & Utilities
sudo pacman -S \
thunar \
thunar-volman \
gvfs \
tumbler \
ffmpegthumbnailer \
zathura \
zathura-pdf-mupdf \
imv \
polkit-gnome
6.9. Screenshot & Screen Recording
# Already installed grim + slurp above
# For screen recording:
sudo pacman -S wf-recorder
6.10. Theme & Appearance
sudo pacman -S \
qt5-wayland \
qt6-wayland \
gtk3 \
gtk4 \
nwg-look
yay -S catppuccin-gtk-theme-mocha catppuccin-cursors-mocha
7. Phase 6: Dotfiles & Stow
7.1. Create Directory Structure
mkdir -p ~/atelier/{_bibliotheca,_projects/personal}
7.2. Clone dots-quantum
git clone git@github.com:EvanusModestus/dots-quantum.git \
~/atelier/_projects/personal/dots-quantum
7.3. Install CLI Tools (stow dependencies)
sudo pacman -S \
fzf \
fd \
ripgrep \
bat \
eza \
jq \
yq \
lazygit \
tmux
yay -S oh-my-posh-bin
7.4. Stow Essential Packages (Tier 1)
cd ~/atelier/_projects/personal/dots-quantum
stow -t ~ \
zsh bash shell git \
hyprland waybar wofi mako \
kitty oh-my-posh \
bin share \
btop fastfetch fzf fd ripgrep \
claude
7.5. Verify Stow
# Check symlinks were created
ls -la ~/.zshrc
ls -la ~/.config/hypr/hyprland.conf
ls -la ~/.config/waybar/config.jsonc
7.6. Clone and Setup Neovim
git clone git@github.com:EvanusModestus/domus-nvim.git \
~/atelier/_projects/personal/domus-nvim
# Symlink nvim config
ln -sf ~/atelier/_projects/personal/domus-nvim ~/.config/nvim
# Launch nvim — plugins will auto-install on first run
nvim
7.7. Stow Tier 2 Packages
cd ~/atelier/_projects/personal/dots-quantum
stow -t ~ \
tmux vim lazygit \
vscodium zathura thunar \
libvirt ghostty
7.8. Machine-Specific Host Config
# Check if host-specific directory exists
ls ~/atelier/_projects/personal/dots-quantum/hosts/
# Create host config for new machine if needed
# (monitor layout, GPU settings, etc.)
mkdir -p ~/atelier/_projects/personal/dots-quantum/hosts/modestus-legion
8. Phase 7: Secrets & Credentials
|
This phase requires access to your existing secrets infrastructure. Have ready:
|
8.1. age Setup
mkdir -p ~/.age/{recipients,identities}
chmod 700 ~/.age/identities
# Copy your age identity from secure backup
# (USB drive, password manager, or SCP from existing machine)
# DO NOT paste identity content here — execute manually
# Verify age can decrypt
age -d -i ~/.age/identities < /dev/null 2>&1 | head -1
# Should show a decryption error, not "no identity file"
8.2. SSH Keys
mkdir -p ~/.ssh
chmod 700 ~/.ssh
# Option A: Restore from backup (SCP from existing machine)
# scp modestus-razer:~/.ssh/id_ed25519* ~/.ssh/
# Option B: Generate new keypair
ssh-keygen -t ed25519 -C "evanusmodestus@modestus-t16g"
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
8.3. Decrypt SSH Config from dots-quantum
age -d -i ~/.age/identities \
~/atelier/_projects/personal/dots-quantum/ssh/.ssh/config.age \
> ~/atelier/_projects/personal/dots-quantum/ssh/.ssh/config
cd ~/atelier/_projects/personal/dots-quantum
stow -t ~ ssh
# Verify SSH config is symlinked
ls -la ~/.ssh/config
8.4. YubiKey & GPG
8.4.1. Install packages
sudo pacman -S gnupg pcsc-tools ccid yubikey-manager
sudo systemctl enable --now pcscd
8.4.2. Detect YubiKey
# Insert YubiKey, then:
gpg --card-status
# Should show:
# Reader ...........: Yubico YubiKey ...
# Application Type .: OpenPGP
# Signature key ....: [your key fingerprint]
# Encryption key ...: [your key fingerprint]
# Authentication key: [your key fingerprint]
8.4.3. Import GPG public key
# The YubiKey holds private subkeys. You still need the public key.
# Option A: Fetch from keyserver
gpg --keyserver keys.openpgp.org --recv-keys <YOUR-KEY-ID>
# Option B: Import from file
gpg --import /path/to/public-key.gpg
# Option C: Export from existing machine
# (on razer): gpg --export --armor <KEY-ID> > /tmp/pub.gpg
# (on t16g): gpg --import /tmp/pub.gpg
# Trust the key (ultimate trust for your own key)
gpg --edit-key <KEY-ID>
# At gpg> prompt: trust → 5 (ultimate) → quit
8.4.4. Verify GPG + YubiKey
# List secret keys — should show "ssb>" (stub pointing to card)
gpg -K
# Test decryption (touch YubiKey when prompted)
echo "test" | gpg --encrypt --recipient <KEY-ID> | gpg --decrypt
8.4.5. GPG Agent Troubleshooting
# If YubiKey not detected after replug
gpgconf --kill gpg-agent
gpg --card-status
# Force agent reload
gpg-connect-agent reloadagent /bye
# Check udev rules exist
ls /etc/udev/rules.d/*yubikey* 2>/dev/null || ls /usr/lib/udev/rules.d/*yubi* 2>/dev/null
8.5. gopass Setup & Sync
8.5.1. Clone store
sudo pacman -S gopass
# Clone from Gitea (internal) or GitHub
gopass clone ssh://git@gitea-01.inside.domusdigitalis.dev:2222/evanusmodestus/password-store v3
# Verify — touch YubiKey to decrypt
gopass ls
8.5.2. Sync and verify remotes
# Sync pulls and pushes all mounted stores
gopass sync
# Verify gopass git remotes are configured
gopass git remote -v --store v3
# Verify recipients (your GPG key should be listed)
gopass recipients --store v3
# Test a known entry decrypts correctly
gopass show v3/test/entry 2>/dev/null || echo "Create a test entry to verify"
8.5.3. gopass configuration
# Recommended settings
gopass config core.cliptimeout 45
gopass config core.autosync true
gopass config core.autoclip false
gopass config core.showsafecontent true
8.6. Add SSH Key to Git Remotes
# Copy public key (if new key was generated)
cat ~/.ssh/id_ed25519.pub | wl-copy
Add to:
8.7. Verify Git Remote Access
ssh -T git@github.com
ssh -T git@gitlab.com
ssh git@gitea-01.inside.domusdigitalis.dev
8.8. Vault SSH Certificates
Certificate-based SSH eliminates authorized_keys management. Vault signs your public key with an 8-hour TTL — hosts trust the CA, not individual keys.
8.8.1. Prerequisites
# Install Vault CLI
sudo pacman -S vault
# Set Vault address (from gopass or environment)
export VAULT_ADDR="https://vault-01.inside.domusdigitalis.dev:8200"
8.8.2. Authenticate to Vault
# Interactive login (uses LDAP, userpass, or token — depends on your auth method)
vault login
# Verify access
vault token lookup
8.8.3. Generate a dedicated keypair for Vault signing
# Separate key for Vault-signed certs (don't mix with static SSH keys)
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_vault -C "vault-signed@modestus-t16g"
8.8.4. Sign the key
vault write -field=signed_key ssh/sign/domus-client \
public_key=@~/.ssh/id_ed25519_vault.pub \
valid_principals="evanusmodestus,admin,root" \
> ~/.ssh/id_ed25519_vault-cert.pub
# Inspect the certificate
ssh-keygen -L -f ~/.ssh/id_ed25519_vault-cert.pub
# Should show:
# Type: ssh-ed25519-cert-v01@openssh.com user certificate
# Valid: from <now> to <now + 8h>
# Principals:
# evanusmodestus
# admin
# root
8.8.5. Test certificate-based login
# SSH to a host that trusts the Vault CA
ssh -i ~/.ssh/id_ed25519_vault evanusmodestus@<target-host>
|
Certificates expire after 8 hours (TTL configured on the Vault role). Re-sign daily:
Consider a shell alias or script for this. The key pair is static — only the cert rotates. |
8.9. Borg Backup Setup
8.9.1. Install
sudo pacman -S borg nfs-utils
8.9.2. Mount Synology NAS
Borg uses a local NFS mount to the Synology, not SSH transport.
sudo mkdir -p /mnt/synology
sudo mount -t nfs nas-01.inside.domusdigitalis.dev:/volume1/borg_backups /mnt/synology
8.9.3. Load Credentials
# Load BORG_PASSPHRASE from dsource
dsource d000 dev/storage
8.9.4. Verify existing repo
sudo -E BORG_PASSPHRASE="$BORG_PASSPHRASE" borg info /mnt/synology/borg-repo
sudo -E BORG_PASSPHRASE="$BORG_PASSPHRASE" borg list /mnt/synology/borg-repo --last 3
8.9.5. Initialize repo for new machine (if needed)
sudo -E BORG_PASSPHRASE="$BORG_PASSPHRASE" borg init --encryption=repokey /mnt/synology/borg-repo-<HOSTNAME>
8.9.6. Validate backup script
The backup script is stowed from dots-quantum:
ls -la ~/.local/bin/borg-backup-synology.sh
# Check for hardcoded paths that may reference old machine
grep -i "optimus\|dotfiles-optimus" ~/.local/bin/borg-backup-synology.sh
8.9.7. Run first backup
sudo -E BORG_PASSPHRASE="$BORG_PASSPHRASE" ~/.local/bin/borg-backup-synology.sh
# Verify the archive
sudo -E BORG_PASSPHRASE="$BORG_PASSPHRASE" borg list /mnt/synology/borg-repo --last 3
8.9.8. Persistent NFS mount (optional)
# Add to /etc/fstab for auto-mount
echo "nas-01.inside.domusdigitalis.dev:/volume1/borg_backups /mnt/synology nfs defaults,noauto,x-systemd.automount 0 0" | sudo tee -a /etc/fstab
9. Phase 8: Development Environment
9.1. Programming Languages & Runtimes
9.1.1. Python (uv)
sudo pacman -S python
# Install uv (fast Python package manager)
curl -LsSf https://astral.sh/uv/install.sh | sh
9.1.2. Node.js
sudo pacman -S nodejs npm
9.1.4. Go (optional)
sudo pacman -S go
9.2. Documentation Toolchain (Antora)
# Antora runs via npx — no global install needed
# Verify node/npm versions
node --version
npm --version
9.3. Clone Documentation Repos (Tier 2)
cd ~/atelier/_bibliotheca
# Hub
git clone git@github.com:EvanusModestus/domus-docs.git
# Core spokes
for repo in domus-captures domus-infra-ops domus-ise-linux domus-secrets-ops domus-linux-ops domus-antora-ui; do
git clone git@github.com:EvanusModestus/$repo.git
done
# Verify build
cd ~/atelier/_bibliotheca/domus-docs
make
9.4. Clone Active Project Repos
cd ~/atelier/_projects/personal
for repo in netapi netapi-tui domus-cli ise-automation domus-digitalis evanusmodestus-site; do
git clone git@github.com:EvanusModestus/$repo.git
done
9.5. Add Multi-Remote Push for All Repos
# For each repo that needs GitLab + Gitea mirrors:
# Example for domus-captures
cd ~/atelier/_bibliotheca/domus-captures
git remote add gitlab git@gitlab.com:EvanusModestus/domus-captures.git
git remote add gitea ssh://git@gitea-01.inside.domusdigitalis.dev:2222/evanusmodestus/domus-captures.git
9.6. Claude Code
# Install Claude Code CLI
npm install -g @anthropic-ai/claude-code
# Verify (dots-quantum/claude already stowed settings + hooks)
claude --version
9.7. Container Tools
sudo pacman -S podman buildah skopeo
9.8. Virtualization (KVM/libvirt)
sudo pacman -S qemu-full libvirt virt-manager dnsmasq
sudo systemctl enable --now libvirtd
sudo usermod -aG libvirt evanusmodestus
9.9. Additional Dev Tools
sudo pacman -S \
httpie \
tokei \
dust \
duf \
procs \
bandwhich \
hyperfine
yay -S rmapi
10. Phase 9: Verification & Hardening
10.1. System Verification Checklist
| Check | Command | Expected |
|---|---|---|
Boot |
System boots without errors |
[ ] Pass |
LUKS |
|
[ ] Pass |
Btrfs subvolumes |
|
[ ] 4 subvolumes (@, @home, @var, @snapshots) |
Network (wired) |
|
[ ] Pass |
Network (WiFi) |
|
[ ] SSIDs visible |
GPU |
|
[ ] Driver loaded |
Audio |
|
[ ] PipeWire running |
Bluetooth |
|
[ ] Powered yes |
10.2. Desktop Verification
| Check | Test | Expected |
|---|---|---|
Hyprland launches |
Login from TTY or SDDM |
[ ] Desktop visible |
Waybar |
Status bar at top |
[ ] Clock, workspaces, tray |
Wofi |
|
[ ] App launcher opens |
Terminal |
|
[ ] Kitty opens |
Notifications |
|
[ ] Mako popup |
Screenshots |
|
[ ] Region capture works |
Clipboard |
Copy text, |
[ ] Clipboard works |
10.3. Development Verification
| Check | Command | Expected |
|---|---|---|
Neovim |
|
[ ] Pass |
Git push (GitHub) |
|
[ ] Authenticated |
Git push (GitLab) |
|
[ ] Authenticated |
Git push (Gitea) |
SSH connection test |
[ ] Authenticated |
Antora build |
|
[ ] Site builds |
Python |
|
[ ] Both available |
Node |
|
[ ] Both available |
Rust |
|
[ ] Both available |
gopass |
|
[ ] Store accessible |
Claude Code |
|
[ ] Installed |
10.4. Security Hardening
10.4.1. Firewall
sudo pacman -S ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw enable
sudo systemctl enable ufw
10.4.2. Disable Root Login via SSH
sudo sed -i 's/^#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
10.4.3. Enable Automatic Security Updates (optional)
# Consider a pacman hook or timer for unattended upgrades
# For now, manual: sudo pacman -Syu
10.4.4. Verify No Listening Services
ss -tlnp | awk '{print $4}' | sort -u
10.5. Snapshot Clean State
# Take a btrfs snapshot of the clean install
sudo btrfs subvolume snapshot -r / /.snapshots/fresh-install-$(date +%Y%m%d)
# Verify
sudo btrfs subvolume list /.snapshots
This snapshot is your rollback point. If anything goes wrong during Tier 3 setup, you can restore to this known-good state.
11. Hardware-Specific Notes
11.1. ThinkPad T16g Gen 3 (Travel: modestus-t16g)
| Component | Notes |
|---|---|
CPU |
Intel Core Ultra 9 275HX (24C) — same as Razer. Use |
GPU |
NVIDIA RTX 5090 24GB GDDR7 — same driver as Razer ( |
RAM |
64 GB DDR5-5600 |
Storage |
Single 2TB Gen5 NVMe — 4 partitions (EFI, /boot, cryptroot 250G, crypthome ~1.7T) |
Display |
16" 3.2K Tandem OLED — may need HiDPI scaling in Hyprland ( |
WiFi |
Intel AX — works out of box with |
BIOS |
F1 for setup, F12 for boot menu (ThinkPad standard). Disable Secure Boot, set UEFI only, enable dGPU mode. |
Keyboard |
TrackPoint + ThinkPad keyboard. No RGB control needed. Fn keys: check |
Battery |
~99Wh. Install |
Docking |
Thunderbolt 4 — verify external display output via |
# Post-install: check hardware detection
lspci | grep -E "(VGA|Network|Audio)"
# HiDPI scaling (add to hyprland.conf if text is tiny on 3.2K OLED)
# monitor=,preferred,auto,1.5
# ThinkPad battery charge thresholds (via TLP)
sudo pacman -S tlp tlp-rdw
sudo systemctl enable --now tlp
# Edit /etc/tlp.conf:
# START_CHARGE_THRESH_BAT0=40
# STOP_CHARGE_THRESH_BAT0=80
11.2. Razer Blade 18 (Primary: modestus-razer)
| Component | Notes |
|---|---|
GPU |
NVIDIA RTX — same driver setup as Legion |
WiFi |
Intel AX — works out of box with |
Keyboard |
|
Fingerprint |
Check |
Battery |
|
11.3. Common to Both Machines
# Brightness control (laptop screens)
sudo pacman -S brightnessctl
# Backlight for keyboard
brightnessctl -d *kbd* set 50%
# Power profiles
sudo pacman -S power-profiles-daemon
sudo systemctl enable --now power-profiles-daemon
# Check current power profile
powerprofilesctl get
12. Appendix: System Reference Commands
Reusable commands for verifying and inspecting any Arch workstation. Run these after install, after changes, or when auditing an existing system.
12.1. Disk & Partitions
# Partition layout with sizes and mount points
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
# Partition layout with UUIDs and labels
lsblk -f
# GPT partition table details
sgdisk -p /dev/nvme0n1
# Disk usage summary for key mount points
df -h / /home /boot /boot/efi
12.2. LUKS Encryption
# List all open LUKS mappings
dmsetup ls --target crypt
# LUKS header details (cipher, key slots, UUID)
sudo cryptsetup luksDump /dev/nvme0n1p3
# Status of an open LUKS container
sudo cryptsetup status cryptroot
# Verify crypttab (additional LUKS volumes opened at boot)
cat /etc/crypttab
12.3. Btrfs
# List subvolumes on root
sudo btrfs subvolume list /
# List subvolumes on home (if separate LUKS)
sudo btrfs subvolume list /home
# Detailed space usage (data, metadata, system)
sudo btrfs filesystem usage /
# Allocation profile (single, DUP, etc.)
sudo btrfs filesystem df /
# Device error stats (corruption detection)
sudo btrfs device stats /
# Scrub status (last integrity check)
sudo btrfs scrub status /
# List snapshots
sudo btrfs subvolume list /.snapshots
12.4. Mounts & fstab
# All btrfs + vfat + ext4 mounts with full options (the master view)
findmnt -t btrfs,vfat,ext4 --output TARGET,SOURCE,FSTYPE,OPTIONS -n
# fstab contents (persistent mounts)
cat /etc/fstab
# Verify all fstab entries are mounted
findmnt --verify
12.5. Boot
# systemd-boot status (ESP path, entries, default)
bootctl status
# List boot entries
bootctl list
# Loader configuration
cat /boot/efi/loader/loader.conf
# All boot entry files
cat /boot/efi/loader/entries/*.conf
# mkinitcpio MODULES and HOOKS
grep -E '^(MODULES|HOOKS)=' /etc/mkinitcpio.conf
# Kernel versions installed
ls /boot/vmlinuz-*
# Running kernel
uname -r
12.6. Swap (zram)
# zram device status (size, compression, algorithm)
zramctl
# All swap devices
swapon --show
# zram-generator config
cat /etc/systemd/zram-generator.conf
12.7. System
# Boot time breakdown
systemd-analyze
# Slowest services at boot
systemd-analyze blame | head -20
# Failed services
systemctl --failed
# Hardware detection (GPU, network, audio)
lspci | grep -E "(VGA|Network|Audio)"
# GPU status (NVIDIA)
nvidia-smi
# NVIDIA driver and kernel module version
nvidia-smi --query-gpu=driver_version --format=csv,noheader
cat /proc/driver/nvidia/version 2>/dev/null | head -1
12.8. Network
# IPv4 addresses (concise)
ip -4 -o addr show | awk '{print $2, $4}'
# Network device status
nmcli device status
# DNS resolver status
resolvectl status
# Listening ports (what services are exposed)
ss -tlnp | awk '{print $4}' | sort -u
12.9. Firewall
# UFW status and rules
sudo ufw status verbose
12.10. Ollama (Post-Install)
# List installed models with sizes
ollama list
# Verify bind mount for model storage
findmnt /var/lib/ollama/.ollama/models
# Ollama service status
systemctl status ollama