Friday December 14 2018

Installing Void Linux on Digital Ocean - A Void Linux VPS Part I

Not too long ago I was able to get OpenBSD installed on Digital Ocean, I figure since it’s not really a “foreign” operating system, this should be even easier. Spoiler, it’s not. But it’s not too much harder either.

Create a new VM, boot to the recovery ISO

You should be able to SSH to the machine with the SSH keys you tied to the instance straight away.

Partition the disk

I first used gdisk’s advanced option to “zap” the disk and clear all GPT and MBR entries:

root@void:~# gdisk /dev/vda
GPT fdisk (gdisk) version 1.0.3

Partition table scan:
  MBR: protective
  BSD: not present
  APM: not present
  GPT: present

Found valid GPT with protective MBR; using GPT.

Command (? for help): p
Disk /dev/vda: 52428800 sectors, 25.0 GiB
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): 858A66B1-0062-4F12-A9A8-B52406B9C71D
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 52428766
Partitions will be aligned on 2048-sector boundaries
Total free space is 2014 sectors (1007.0 KiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1          227328        52428766   24.9 GiB    8300  
  14            2048           10239   4.0 MiB     EF02  
  15           10240          227327   106.0 MiB   0700  

Command (? for help): x

Expert command (? for help): z
About to wipe out GPT on /dev/vda. Proceed? (Y/N): y
GPT data structures destroyed! You may now partition the disk using fdisk or
other utilities.
Blank out MBR? (Y/N): y
root@void:~# 

If you have trouble reading the output above, the commands are x to get to advanced mode and z to zap the disk

Now to setup our new partitions:

root@void:~# gdisk /dev/vda
GPT fdisk (gdisk) version 1.0.3

Partition table scan:
  MBR: not present
  BSD: not present
  APM: not present
  GPT: not present

Creating new GPT entries.

Command (? for help): n
Partition number (1-128, default 1): 
First sector (34-52428766, default = 2048) or {+-}size{KMGTP}: 
Last sector (2048-52428766, default = 52428766) or {+-}size{KMGTP}: +500m
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): 
Changed type of partition to 'Linux filesystem'

Command (? for help): n
Partition number (2-128, default 2): 
First sector (34-52428766, default = 1026048) or {+-}size{KMGTP}: 
Last sector (1026048-52428766, default = 52428766) or {+-}size{KMGTP}: 
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): 8e00
Changed type of partition to 'Linux LVM'

Command (? for help): n
Partition number (3-128, default 3): 
First sector (34-2047, default = 34) or {+-}size{KMGTP}: 
Last sector (34-2047, default = 2047) or {+-}size{KMGTP}: 
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): ef02
Changed type of partition to 'BIOS boot partition'

Command (? for help): p
Disk /dev/vda: 52428800 sectors, 25.0 GiB
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): CF56F288-92ED-4FA2-BA8F-462FBE075BB0
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 52428766
Partitions will be aligned on 2048-sector boundaries
Total free space is 0 sectors (0 bytes)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048         1026047   500.0 MiB   8300  Linux filesystem
   2         1026048        52428766   24.5 GiB    8E00  Linux LVM
   3              34            2047   1007.0 KiB  EF02  BIOS boot partition

Command (? for help): wq

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to /dev/vda.
The operation has completed successfully.

We’re creating a small 500mb partition for /boot, a large one for LVM and a tiny one for the grub bios boot partition. Although you could get away with one large, single XFS filesystem I rather like the snapshotting abilities that LVM gives you.

Setup LVM and mount the filesystems

# lvm pvcreate /dev/vda2
# lvm vgcreate vg /dev/vda2
# lvm lvcreate vg --name root --size 4G
# lvm lvcreate vg --name var --size 5G
# lvm lvcreate vg --name home --size 2G
# mkfs.xfs /dev/vg/root
# mkfs.xfs /dev/vg/var
# mkfs.xfs /dev/vg/home
# mkfs.ext4 /dev/vda1
# mount /dev/vg/root /mnt
# mkdir /mnt/var
# mount /dev/vg/var /mnt/var
# mkdir /mnt/home
# mount /dev/vg/home /mnt/home
# mkdir /mnt/boot
# mount /dev/vda1 /mnt/boot

Download minimal root filesystem

Be sure to check the website for a mirror near you and change the URL accordingly. You might need to browse the live/current/ path to figure out the current ROOTFS tarball

# cd /mnt
# wget http://mirror.clarkson.edu/voidlinux/live/current/void-x86_64-musl-ROOTFS-20181111.tar.xz
# tar xJf void-*ROOTFS*

Chroot into the new system and setup the basics

# cd /mnt
# mount -t proc none proc
# mount -o bind /sys sys
# mount -o bind /dev dev
# chroot /mnt /bin/bash
bash-4.4# export PS1="(CHROOT) # "

Setup nameservers

(CHROOT) # echo nameserver 8.8.8.8 > /etc/resolv.conf

Setup the package repository to point at your local mirror

(CHROOT) # echo repository=http://mirror.clarkson.edu/voidlinux/current/musl \
	> /etc/xbps.d/00-repository-main.conf

Install the base system, lvm2, and a few other useful packages

(CHROOT) # xbps-install -Syu base-system lvm2 curl wget ed grub gptfdisk lzop \
		lzip openvpn nmap pv rrdtool rsync sipcalc whois xz

Configure bootloader, initrd and /etc/fstab

Kind of a hack to setup the fstab, but it works:

(CHROOT) # cat /proc/mounts | grep -E '(xfs|ext4)'  >> /etc/fstab

The Void Linux kernel by default does not build in the necessary module to handle booting off of a logical volume with associated snapshots. To make matters worse, it does not load it automatically even if you put it in the initial ramdisk. Thankfully dracut has an option to force load kernel modules

Simply:

# echo 'force_drivers="dm_snapshot"' >> /etc/dracut.conf

Then go ahead and rebuild the initrd:

(CHROOT) # dracut -f --kver $(ls -t /lib/modules | sed 1q)

Now to check the generated initrd and make sure that it has LVM support:

(CHROOT) # lsinitrd /boot/initramfs-* | grep lvm
lvm
drwxr-xr-x   2 root     root            0 Dec 14 04:02 etc/lvm
-rw-r--r--   1 root     root           44 Dec 14 04:02 etc/lvm/lvm.conf
-rw-r--r--   1 root     root          776 Oct 30 10:51 etc/udev/rules.d/64-lvm.rules
-rwxr-xr-x   1 root     root      2110744 Nov 18 17:54 usr/bin/lvm
-rwxr-xr-x   1 root     root         3527 Oct 30 10:51 usr/bin/lvm_scan
-rwxr-xr-x   1 root     root          487 Oct 30 10:51 usr/lib/dracut/hooks/cmdline/30-parse-lvm.sh
-r--r--r--   1 root     root         2453 Nov 18 17:54 usr/lib/udev/rules.d/11-dm-lvm.rules
-r--r--r--   1 root     root         6371 Dec 14 04:02 usr/lib/udev/rules.d/69-dm-lvm-metad.rules
(CHROOT) #

Grub is straightforward

(CHROOT) # grub-install --target=i386-pc /dev/vda
Installing for i386-pc platform.
Installation finished. No error reported.
(CHROOT) # grub-mkconfig -o /boot/grub/grub.cfg
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-4.19.9_1
Found initrd image: /boot/initramfs-4.19.9_1.img
done
(CHROOT) #

Network configuration

This one is a little bit more tricky at first, but somewhat more straightfoward than other distrubitions. Simply edit /etc/rc.local to contain the normal commands you would run to setup the network by hand.

The contents of the file are as follows:

/etc/rc.local

# Default rc.local for void; add your custom commands here.
#
# This is run by runit in stage 2 before the services are executed
# (see /etc/runit/2).

ip link set up eth0

ip -4 addr add 10.156.156.156/20 dev eth0
ip -4 route add default via 10.156.144.1 dev eth0

ip -6 addr add fc00:a880:400:d0::76e:6001/64 dev eth0
ip -6 route add default via fc00:a880:400:d0::1 dev eth0 

You will need to replace the addresses above with the ones on your machine, you can see these in the recovery environment via ip addr show | grep inet and the routes with ip -4 route and ip -6 route for IPv4 and IPv6 respectively

Take care of user configuration and remote [ssh] login

Set a password, enable SSH:

(CHROOT) # passwd
(CHROOT) # ln -sv /etc/sv/sshd/ /etc/runit/runsvdir/default/

I highly recommend disabling PasswordAuthenticatoin, relevant lines to change in /etc/ssh/sshd_config:

PermitRootLogin without-password
PasswordAuthenticatoin no

I also recommend creating a user account

(CHROOT) # useradd -m -g users -G wheel $username
(CHROOT) # passwd $uesrname

Set a hostname

(CHROOT) # echo mysvr.example.com > /etc/hostname

(optional) Set a timezone

(CHROOT) # ln -sv /usr/share/zoneinfo/America/Detroit /etc/localtime

Where America/Detroit is replaced with your timezone.

(optional) Remove unnecessary gettys

(CHROOT) # rm /etc/runit/runsvdir/default/agetty-tty2
(CHROOT) # rm /etc/runit/runsvdir/default/agetty-tty3
(CHROOT) # rm /etc/runit/runsvdir/default/agetty-tty4
(CHROOT) # rm /etc/runit/runsvdir/default/agetty-tty5
(CHROOT) # rm /etc/runit/runsvdir/default/agetty-tty6
(CHROOT) # rm /etc/runit/runsvdir/default/agetty-tty7

If you’re confident that you won’t need the console whatsoever, you can also disable agetty-tty1 But this means that if you muck up SSH for whatever reason it’s going to be a hard reboot into the recovery ISO to fix it.

End of Part I, and the future

If you followed along you now have a VPS sitting somewhere in a data center with Void Linux doing nothing but serving up a shell for you. In Part II we’ll setup logging, a firewall and backups.