Skip to content

Instantly share code, notes, and snippets.

@c0m4r
Last active November 3, 2024 09:43
Show Gist options
  • Save c0m4r/8bdd2e6925fc78bffff78af83778c4e1 to your computer and use it in GitHub Desktop.
Save c0m4r/8bdd2e6925fc78bffff78af83778c4e1 to your computer and use it in GitHub Desktop.
Void Linux installation on Hetzner Cloud VPS (Arm64/IPv6-only)

Void Linux installation on Hetzner Cloud VPS

(Arm64/IPv6-only)

This guide describes how to install Void Linux on ARM Hetzner Cloud instances with IPv6-only setup.

image

Table of contents:

Unfortunately Hetzner forces the use of EFI boot, which makes custom installations a pain in the ass. Fortunately there are workarounds that clever people comes up with. I'm not that clever though so it took me some time, but after a few hours of swearing on how crap up EFI is, I managed to get through the installation.

Installation

Reboot into Rescue

Get the latest aarch64 rootfs tarball from https://voidlinux.org/download/#arm

Log into the rescue SSH and follow all the steps.

First wipe the drive and create new partitions:

wipefs -a /dev/sda
fdisk /dev/sda

First 1 GB EFI and then linux partition on the remaining space:

Welcome to fdisk (util-linux 2.38.1).
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 0xb643a8b6.

Command (m for help): g
Created a new GPT disklabel (GUID: 29add4d7-e644-4709-9b3c-209b68765ff2).

Command (m for help): n
Partition number (1-128, default 1):
First sector (2048-80003038, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-80003038, default 80001023): +1G

Created a new partition 1 of type 'Linux filesystem' and of size 1 GiB.
Partition #1 contains a vfat signature.

Do you want to remove the signature? [Y]es/[N]o: Y

The signature will be removed by a write command.

Command (m for help): t
Selected partition 1
Partition type or alias (type L to list all): 1
Changed type of partition 'Linux filesystem' to 'EFI System'.

Command (m for help): n
Partition number (2-128, default 2):
First sector (2099200-80003038, default 2099200):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2099200-80003038, default 80001023):

Created a new partition 2 of type 'Linux filesystem' and of size 37.1 GiB.
Partition #2 contains a ext4 signature.

Do you want to remove the signature? [Y]es/[N]o: Y

The signature will be removed by a write command.

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

fdisk -l
Disk /dev/sda: 38.15 GiB, 40961572864 bytes, 80003072 sectors
Disk model: QEMU HARDDISK   
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: gpt
Disk identifier: 29add4d7-e644-4709-9b3c-209b68765ff2

Device       Start      End  Sectors  Size Type
/dev/sda1     2048  2099199  2097152    1G EFI System
/dev/sda2  2099200 80001023 77901824 37.1G Linux filesystem

Create required filesystems, deploy the rootfs and mount everything that wee need for chroot:

mkfs.vfat /dev/sda1
mkfs.ext4 /dev/sda2
mount /dev/sda2 /mnt
wget https://repo-default.voidlinux.org/live/20240314/void-aarch64-ROOTFS-20240314.tar.xz
tar xvf void-aarch64-ROOTFS-*.tar.xz -C /mnt
mkdir /mnt/boot/efi
mount /dev/sda1 /mnt/boot/efi
mount -t proc none /mnt/proc
mount -t sysfs none /mnt/sys
mount --rbind /dev /mnt/dev
mount --rbind /run /mnt/run
mount -t efivarfs /sys/firmware/efi/efivars /mnt/sys/firmware/efi/efivars

To proceed make sure the DNS is set inside mounted environment. Also copy ip configuration for later.

resolvectl | grep "::" | head -n 1 | awk '{print "nameserver " $4}' > /mnt/etc/resolv.conf
echo "/usr/bin/ip link set eth0 up" >> /mnt/etc/rc.local
echo "/usr/bin/ip -6 a a $(ip a s | grep "::1\/64" | awk '{print $2}') dev eth0" >> /mnt/etc/rc.local
echo "/usr/bin/ip -6 r a $(ip -6 r s | grep "default via" | awk '{print $3}')/64 dev eth0" >> /mnt/etc/rc.local
echo "/usr/bin/ip -6 r a default via $(ip -6 r s | grep "default via" | awk '{print $3}') dev eth0" >> /mnt/etc/rc.local

Now chroot into /mnt and proceed with all the installation steps:

chroot /mnt
echo "example" > /etc/hostname
sed -i 's/localhost.localdomain/example/g;' /etc/hosts
passwd
echo "Europe/Warsaw" > /etc/timezone
ln -s /usr/share/zoneinfo/Europe/Warsaw /etc/localtime
xbps-install -Su xbps
xbps-install -u
xbps-install base-system
xbps-remove base-voidstrap
xbps-install grub-arm64-efi linux linux-base linux-headers linux-tools
grub-install /dev/sda
cp -r /boot/efi/EFI/void /boot/efi/EFI/boot
mv /boot/efi/EFI/boot/grubaa64.efi /boot/efi/EFI/boot/bootx64.efi
xbps-reconfigure -fa
ln -s /etc/sv/sshd /etc/runit/runsvdir/default/
sed -i 's/#ListenAddress ::/ListenAddress ::/g;' /etc/ssh/sshd_config
sed -i 's/loglevel=4/loglevel=7 console=tty1/g;' /etc/default/grub
update-grub
exit
cp -r /root/.ssh /mnt/root/
rm -f /mnt/root/.ssh/authorized_keys2
umount -R /mnt # you can safely ignore errors about /dev/pts being in-use
reboot

Post-installation

NTP

xbps-install chrony
sed -i 's/pool.ntp.org/2.pool.ntp.org/g;' /etc/chrony.conf
sed -i 's/exec chronyd/exec chronyd -6/g;' /etc/sv/chronyd/run
ln -s /etc/sv/chronyd /etc/runit/runsvdir/default/

Install useful tools

xbps-install -S bash-completion bind-utils binutils curl cronie git htop iftop logrotate lsof lynis nano ncdu neofetch net-tools mc mtr unzip rsync rsyslog screen tcpdump vim wget whois xz

Install build essentials

xbps-install autoconf autogen automake bc binutils bison cargo cmake flex gcc gettext libtool m4 make patch pkg-config unzip xz
xbps-install ncurses-devel openssl-devel pcre-devel pcre2-devel

Enable services

ln -s /etc/sv/rsyslogd /var/service
ln -s /etc/sv/cronie /var/service

Change shell

cp /etc/skel/.* ./
chsh -s /bin/bash root
bash

NAT64

For use-cases where a specific domain doesn't resolve on IPv6 (f.e. github.com) we can use a public NAT64 service. However, we don't necessarily want all traffic being routed through that service. Therfore, we're using dnsmasq to use NAT64 only for domains we need.

xbps-install dnsmasq
cat <<EOF > /etc/dnsmasq.conf
proxy-dnssec
no-resolv
no-poll
listen-address=::1
bind-interfaces
no-hosts
# Default DNS: quad9
server=2620:fe::fe
server=2620:fe::9
# Alternative DNS: https://meta.wikimedia.org/wiki/Wikimedia_DNS
server=2001:67c:930::1
# For specific hosts use Public NAT64 service: https://nat64.net/
server=/github.com/2a00:1098:2c::1
server=/github.com/2a01:4f9:c010:3f02::1
server=/github.com/2a01:4f8:c2c:123f::1
server=/api.github.com/2a00:1098:2c::1
server=/api.github.com/2a01:4f9:c010:3f02::1
server=/api.github.com/2a01:4f8:c2c:123f::1
server=/objects.githubusercontent.com/2a00:1098:2c::1
server=/objects.githubusercontent.com/2a01:4f9:c010:3f02::1
server=/objects.githubusercontent.com/2a01:4f8:c2c:123f::1
EOF

Be aware that the public NAT64 solution isn't perfect and may slow down or not work as expected at times due to rate limiting. As an alternative, you can look for IPv6 proxy services, but be careful because they can be used for MITM attacks.

ln -s /etc/sv/dnsmasq /var/service
echo "nameserver ::1" > /etc/resolv.conf

Public NAT64 services are listed here: https://nat64.net/public-providers and here: https://nat64.xyz/

Docker

xbps-install docker docker-compose
ln -s /etc/sv/docker /var/service

See also: Docker inside IPv6-only host

Minecraft Server

xbps-install openjdk17-jre
wget https://download.getbukkit.org/spigot/spigot-1.20.4.jar
java -Xms1G -Xmx1G -XX:+UseG1GC -Djava.net.preferIPv6Addresses=true -jar spigot-1.20.4.jar --nogui

IPv6-only HTTP Server

Anti-malware

Consider paranoya - Simple IOC and YARA scanner


If you're looking for more info, head over to https://docs.voidlinux.org/.

If you found this article helpful, please consider making a donation to a charity on my behalf. Thank you.

Enjoy your Void Linux adventure!

image

@steewbsd
Copy link

Thank you so much for this comprehensive tutorial.

@c0m4r
Copy link
Author

c0m4r commented Jan 20, 2024

Thank you so much for this comprehensive tutorial.

@steewbsd nice to hear that, thank you too!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment