====== Bare metal server provisioning (debian trixie) ======
Install of a Debian 13 (Trixie) on a dedicated server with:
* Full LUKS encryption (except ''/boot'' and ESP)
* RAID1 on two NVMe disks
* LVM on LUKS
* Boot UEFI **or** legacy/BIOS (auto-detected)
* Remote cryptroot unlock with ''dropbear-initramfs''
===== Target disk layout =====
nvme0n1 (419 Go) nvme1n1 (419 Go)
├── p1 : see note below ├── p1 : see note below
├── p2 : 512 Mo ────┐ ├── p2 : 512 Mo ────┐
│ ├── md0 (RAID1) → /boot (ext4)
└── p3 : ~418 Go ───┤ └── p3 : ~418 Go ───┤
└── md1 (RAID1) → LUKS → LVM (vg0)
├── root (10 Go, ext4)
└── thin pool
p1 depends on boot mode:
* UEFI : 512 Mo ESP (ef00, vfat, mounted /boot/efi), one per disk
* legacy : 1 Mo BIOS boot partition (ef02, raw), one per disk
===== Procedure =====
==== 1. Boot in rescue mode ====
Detect the rescue boot mode once, everything downstream branches on $MODE.
The mode is imposed by the rescue boot (Hetzner PXE), not locally adjustable.
$ if [ -d /sys/firmware/efi ]; then MODE=uefi; else MODE=legacy; fi
$ echo "Boot mode: $MODE"
==== 2. Disk setup ====
$ apt update
$ apt install -y mdadm cryptsetup lvm2 parted debootstrap gdisk rsync
# Cleanup (/!\ this delete all data on disks /!\)
$ mdadm --stop --scan
$ mdadm --zero-superblock /dev/nvme0n1 2>/dev/null
$ mdadm --zero-superblock /dev/nvme1n1 2>/dev/null
$ wipefs -a /dev/nvme0n1 /dev/nvme1n1
$ sgdisk --zap-all /dev/nvme0n1
$ sgdisk --zap-all /dev/nvme1n1
# GPT partitioning on both disks
# p1 differs by mode:
# uefi -> ESP (ef00, 512M, vfat, mounted /boot/efi)
# legacy -> BIOS boot (ef02, 1M, raw, used by grub-install)
$ for D in /dev/nvme0n1 /dev/nvme1n1; do
if [ "$MODE" = uefi ]; then
sgdisk -n 1:0:+512M -t 1:ef00 -c 1:"EFI" $D
else
sgdisk -n 1:0:+1M -t 1:ef02 -c 1:"BIOS boot" $D
fi
sgdisk -n 2:0:+512M -t 2:fd00 -c 2:"boot RAID" $D
sgdisk -n 3:0:0 -t 3:fd00 -c 3:"luks RAID" $D
done
$ partprobe
==== 3. RAID1 mdadm ====
$ mdadm --create /dev/md0 \
--level=1 --raid-devices=2 --metadata=1.2 \
--homehost=schwartz --name=boot \
/dev/nvme0n1p2 /dev/nvme1n1p2
$ mdadm --create /dev/md1 \
--level=1 --raid-devices=2 --metadata=1.2 \
--homehost=schwartz --name=cryptroot \
/dev/nvme0n1p3 /dev/nvme1n1p3
==== 4. LUKS on md1 ====
$ cryptsetup luksFormat --type luks2 --iter-time 5000 /dev/md1
$ cryptsetup open /dev/md1 cryptroot
Passphrase generated with ''pass generate luks/schwartz 30 --no-symbols'' and stored in password-store.
==== 5. LVM ====
$ pvcreate /dev/mapper/cryptroot
$ vgcreate vg0 /dev/mapper/cryptroot
$ lvcreate -L 10G -n root vg0
==== 6. Formatting & partitions setup ====
# ESP only in UEFI mode; in legacy p1 is a raw BIOS boot partition (no fs)
$ if [ "$MODE" = uefi ]; then
mkfs.vfat -F32 -n EFI0 /dev/nvme0n1p1
mkfs.vfat -F32 -n EFI1 /dev/nvme1n1p1
fi
$ mkfs.ext4 -L boot /dev/md0
$ mkfs.ext4 -L root /dev/vg0/root
$ mount /dev/vg0/root /mnt
$ mkdir -p /mnt/boot
$ mount /dev/md0 /mnt/boot
$ if [ "$MODE" = uefi ]; then
mkdir -p /mnt/boot/efi
mount /dev/nvme0n1p1 /mnt/boot/efi
fi
==== 7. Debootstrap ====
$ debootstrap --arch amd64 trixie /mnt http://deb.debian.org/debian/
==== 8. Chroot setup ====
$ for d in dev dev/pts proc sys run; do
mount --bind /$d /mnt/$d
done
# efivars only exists (and is only needed) in UEFI mode
$ if [ "$MODE" = uefi ]; then
mount --bind /sys/firmware/efi/efivars /mnt/sys/firmware/efi/efivars
fi
$ cp /etc/resolv.conf /mnt/etc/resolv.conf
# Get UUIDs
$ blkid -s UUID -o value /dev/md0 # BOOT_UUID
$ blkid -s UUID -o value /dev/vg0/root # ROOT_UUID
$ blkid -s UUID -o value /dev/md1 # LUKS_UUID
# EFI UUIDs only relevant in UEFI mode:
$ if [ "$MODE" = uefi ]; then
blkid -s UUID -o value /dev/nvme0n1p1 # EFI0_UUID
blkid -s UUID -o value /dev/nvme1n1p1 # EFI1_UUID
fi
$ chroot /mnt /bin/bash
==== 9. Base configuration (in chroot) ====
Re-evaluate the mode inside the chroot (the rescue variable is not inherited).
/sys/firmware/efi is still visible here because /sys is bind-mounted from the
rescue, so the detection stays consistent with section 1.
$ if [ -d /sys/firmware/efi ]; then MODE=uefi; else MODE=legacy; fi
# Hostname
$ echo "schwartz" > /etc/hostname
$ sed -i "2i 127.0.1.1\tschwartz" /etc/hosts
# Locales / timezone
$ apt update && apt install -y locales
$ echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
$ echo "fr_FR.UTF-8 UTF-8" >> /etc/locale.gen
$ locale-gen
$ echo "LANG=en_US.UTF-8" > /etc/default/locale
$ ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime
$ dpkg-reconfigure -f noninteractive tzdata
# APT setup
$ cat > /etc/apt/sources.list <
==== 10. Network configuration ====
$ apt install -y ifupdown
$ cat > /etc/network/interfaces <
==== 11. crypttab, fstab, mdadm.conf ====
# crypttab
$ cat > /etc/crypttab < /etc/fstab <> /etc/fstab
fi
# mdadm.conf
$ apt install -y mdadm
$ cat > /etc/mdadm/mdadm.conf <
MAILADDR root
EOF
$ mdadm --detail --scan >> /etc/mdadm/mdadm.conf
==== 12. Kernel and boot tools ====
$ apt install -y \
linux-image-amd64 \
cryptsetup cryptsetup-initramfs \
lvm2 \
thin-provisioning-tools \
intel-microcode \
firmware-linux \
initramfs-tools \
openssh-server
==== 13. GRUB (UEFI or legacy) ====
$ echo 'GRUB_ENABLE_CRYPTODISK=y' >> /etc/default/grub
$ if [ "$MODE" = uefi ]; then
## --- UEFI ---
apt install -y grub-efi-amd64 efibootmgr
grub-install --target=x86_64-efi \
--efi-directory=/boot/efi \
--bootloader-id=debian \
--recheck
update-grub
# Sync ESP2
mkdir -p /tmp/efi2
mount /dev/nvme1n1p1 /tmp/efi2
cp -av /boot/efi/* /tmp/efi2/
umount /tmp/efi2
# UEFI fallback for ESP2
efibootmgr --create \
--disk /dev/nvme1n1 --part 1 \
--label "debian-backup" \
--loader '\EFI\debian\shimx64.efi'
# Reorder to make debian (ESP1) as priority
# efibootmgr -o 000A,000B,... (adapt IDs from `efibootmgr`)
else
## --- legacy/BIOS ---
apt install -y grub-pc
# Install GRUB core into the BIOS boot partition (ef02) of BOTH disks
# for redundancy: if nvme0 fails, the machine still boots on nvme1.
grub-install --target=i386-pc --recheck /dev/nvme0n1
grub-install --target=i386-pc --recheck /dev/nvme1n1
update-grub
fi
==== 14. User et SSH ====
$ passwd # root password setup
$ adduser phil
$ usermod -aG sudo phil
# SSH key
$ mkdir -p /home/phil/.ssh
$ cat > /home/phil/.ssh/authorized_keys <<'EOF'
ssh-ed25519 AAAA... phil@host
EOF
$ chmod 700 /home/phil/.ssh
$ chmod 600 /home/phil/.ssh/authorized_keys
$ chown -R phil:phil /home/phil/.ssh
# sshd hardening
$ cat > /etc/ssh/sshd_config.d/10-hardening.conf <
==== 15. Dropbear-initramfs ====
$ apt install -y dropbear-initramfs busybox
# Config dropbear : port 4222 and force cryptroot-unlock command
$ cat > /etc/dropbear/initramfs/dropbear.conf <<'EOF'
DROPBEAR_OPTIONS="-p 4222 -s -j -k -I 60 -c cryptroot-unlock"
EOF
# ssh key allowed to connect
$ cat > /etc/dropbear/initramfs/authorized_keys <<'EOF'
ssh-ed25519 AAAA... phil@host
EOF
$ chmod 600 /etc/dropbear/initramfs/authorized_keys
# network config for initramfs
cat >> /etc/initramfs-tools/initramfs.conf <
==== 16. reboot ====
$ exit # exit chroot
# $MODE from section 1 is still set in the rescue shell here
$ if [ "$MODE" = uefi ]; then
umount /mnt/sys/firmware/efi/efivars
umount /mnt/boot/efi
fi
$ umount /mnt/boot
$ umount /mnt/run
$ umount /mnt/sys
$ umount /mnt/proc
$ umount /mnt/dev/pts
$ umount /mnt/dev
$ umount /mnt
$ vgchange -an vg0
$ cryptsetup close cryptroot
$ mdadm --stop /dev/md0 /dev/md1
$ reboot
==== 17. First boot and unlock ====
# Wait dropbear to be up
nc -zv xx.xx.xx.xx 4222
# remote unlocking oneliner:
pass show luks/schwartz | tr -d '\n' | ssh -p 4222 root@xx.xx.xx.xx cryptroot-unlock
After few seconds, normal SSD become available.
==== 18. (EXTRA) hugepage & swap & LVM thin configuration ====
sudo lvcreate -L 4G -n swap vg0
sudo mkswap /dev/vg0/swap
sudo swapon /dev/vg0/swap
echo "/dev/vg0/swap none swap sw 0 0" | sudo tee -a /etc/fstab
If you want to configure hugepage for a qemu hypervisor:
sudo tee /etc/sysctl.d/10-hugepages.conf > /dev/null <<'EOF'
vm.nr_hugepages = 13312 # EXAMPLE FOR 26GB/32GB
EOF
LVM Thin initialization to store VM storage
sudo lvcreate --type thin-pool -L 250G -n thin vg0