Writing these notes helped me to overcome my hesitation about deploying a Linux distribution with such a ‘manual’ installation procedure. And along the way I discovered that it really wasn’t all that complicated ; there are just a great many forks in the road. I hope these notes may be of some assistance to others.
Table of contents:
This is a fairly simple build, with these goals:
The network configuration is DHCP / IPv6 autoconfig (SLAAC) to start, but there is a section that discusses static addressing.
(Ordinarily I wouldn’t configure systemd-resolved, but in this instance, I’m using it to handle the DHCP-assigned DNS servers.)
First, disable Secure Boot, as the Arch installion image doesn’t support it. Then, boot the Arch ISO, and execute the following commands. Note that the fdisk commands are somewhat abbreviated ; you’ll figure it out.
# ip addr # check that DHCP did its thing # ls /sys/firmware/efi/efivars # check that it booted in EFI mode # fdisk /dev/sda # or use lsblk or fdisk -l to find it g # create a new GPT partition table n 1 2048 +1G # create the EFI System Partition (ESP) t 1 # change partition 1 to "EFI System" n 2 [defaults] # create the LVM partition t 2 43 # change partition 2 to "Linux LVM" w # pvcreate /dev/sda2 # create the LVM physical volume # vgcreate filament /dev/sda2 # create the LVM volume group # lvcreate -n swap_0 -l 256 filament # create the swap volume (256 extents = 1 GB) # lvcreate -n root -l 100%FREE filament # create the root volume # mkfs.fat -F32 /dev/sda1 # format the ESP # mkfs.ext4 /dev/filament/root # format the root filesystem # mkswap /dev/filament/swap_0 # create the swap # swapon /dev/filament/swap_0 # activate swap # mount /dev/filament/root /mnt # mount / # mkdir /mnt/boot # make a space for the boot partition # mount /dev/sda1 /mnt/boot # mount /boot # alias vi=vim # fix annoyances :-) # vi /etc/pacman.d/mirrorlist # leave only the desired mirror(s) # pacman -Sy archlinux-keyring # prevent PGP signature failures # pacstrap /mnt base linux linux-firmware lvm2 efibootmgr vim # add packages # genfstab -U /mnt >> /mnt/etc/fstab # create /etc/fstab in the target # # A bit out of order but it has to be done outside of the chroot: # ln -sf /run/systemd/resolve/stub-resolv.conf /mnt/etc/resolv.conf # arch-chroot /mnt # chroot to the target system
At this point, you’re in a chroot of the new system. Things you might want to change for your own setup are in italics.
[root@archiso /]# printf '\nalias vi=vim\n' >> /etc/bash.bashrc [root@archiso /]# source /etc/bash.bashrc # fix missing vi permanently [root@archiso /]# ln -sf /usr/share/zoneinfo/America/Vancouver /etc/localtime [root@archiso /]# hwclock --systohc # creates /etc/adjtime [root@archiso /]# vi /etc/locale.gen # choose your locale(s) [root@archiso /]# locale-gen # generate locales [root@archiso /]# echo "LANG=en_CA.UTF-8" > /etc/locale.conf # set locale [root@archiso /]# echo filament > /etc/hostname # set hostname [root@archiso /]# vi /etc/mkinitcpio.conf # add lvm2 to HOOKS (before filesystems) [root@archiso /]# mkinitcpio -P # make /boot/initramfs-linux.img [root@archiso /]# efibootmgr --verbose --disk /dev/sda --part 1 --create \ --label "Arch Linux" --loader /vmlinuz-linux --unicode \ "root=UUID=`lsblk -no uuid /dev/filament/root` rw initrd=\\initramfs-linux.img" [root@archiso /]# systemctl enable systemd-networkd [root@archiso /]# systemctl enable systemd-resolved [root@archiso /]# cat >/etc/systemd/network/20-wired.network <<EOF [Match] Name=ens192 [Network] DHCP=yes IPv6PrivacyExtensions=yes LLMNR=no EOF [root@archiso /]# mkdir /etc/systemd/resolved.conf.d [root@archiso /]# printf '[Resolve]\nLLMNR=no\n' > \ /etc/systemd/resolved.conf.d/20-disable-llmnr.conf # disable LLMNR [root@archiso /]# passwd root [root@archiso /]# exit
Now the system is ready. Unmount disks:
# umount /mnt/boot # umount /mnt
You can power off and take a snapshot, clone the disk, etc ; or reboot and proceed directly to using Arch.
There are a few things you may want to add to the above. You can install anything you like during the installation chroot, but don’t start it.
# useradd -m username # passwd username
The stock configuration is a good start, but I had some trouble with the rate limiting bits sending RST instead of silently dropping packets to blocked ports. So I comment out those lines.
# pacman -S nftables # sed -i 's,\( *\)\(.*counter\),\1#\2,' nftables.conf # comment out 'counter' lines # systemctl enable nftables # systemctl start nftables # but not in the installation environment
# pacman -S open-vm-tools # systemctl enable vmtoolsd # systemctl start vmtoolsd # but not in the installation environment
# pacman -S openssh # systemctl enable sshd # systemctl start sshd # but not in the installation environment
My favourite setup is to set a static IPv4 address and add a static IPv6 address to the autoconfigured one, with privacy extensions enabled. This way outbound traffic uses autoconfigured IPv6 addresses, and inbound traffic can be received on the static one.
This is very straightforward. Use multiple Address= lines if you need more than one address on this interface. If you have more than one interface, add additional .network files as needed.
# cat > /etc/systemd/network/20-wired.network << EOF [Match] Name=ens192 [Network] Address=10.1.10.9/24 Gateway=10.1.10.1 IPv6PrivacyExtensions=yes EOF
Because IPv6 will be autoconfigured anyway – unless you take steps to disable it, which I don’t recommend – I keep the same IPv6 options as in the DHCP scenario (IPv6PrivacyExtensions=yes).
I recommend you don’t bother setting a DNS= line in the network definition, and take this opportunity to get rid of systemd-resolved.
# systemctl disable systemd-resolved # systemctl stop systemd-resolved # rm -r /etc/systemd/resolved.conf.d # rm /etc/resolv.conf # this was the 'stub' one from systemd-resolved # echo nameserver 10.1.10.1 > /etc/resolv.conf
Finally, restart systemd-networkd to reconfigure the adapter(s), and use ip addr to check the result.
# systemctl restart systemd-networkd # ip addr
This configuration is useful if you want to run a server (eg, httpd) listening on a static IPv6 address, but you also want outbound connections to use a dynamic, temporary address.
Just add an Address= line (or multiple lines) to the 20-wired.network file. No need for Gateway= as the RA will hand out the gateway address already.
# echo Address=2001:470:eb96:2::ffff/64 >> /etc/systemd/network/20-wired.network
You could also optionally add an IPv6 DNS server:
# echo nameserver 2001:470:eb96:2::1 >> /etc/resolv.conf
Finally, restart systemd-networkd to reconfigure the adapter(s), and use ip addr to check the result.
# systemctl restart systemd-networkd # ip addr
This could be handy if you want to leave autoconfig enabled, but you want your default IPv6 source address to be static. [Many thanks to Celada on Server Fault for the answer to this vexing question.]
Translating that into a format systemd-networkd likes looks like this:
# cat > /etc/systemd/network/20-wired.network << EOF [Match] Name=ens192 [Network] Address=10.1.10.9/24 Gateway=10.1.10.1 IPv6PrivacyExtensions=yes Address=2001:470:eb96:2::ffff/64 [IPv6AddressLabel] Prefix=2001:470:eb96:2::/64 Label=99 [IPv6AddressLabel] Prefix=2001:470:eb96:2::ffff/128 Label=1 EOF # systemctl restart systemd-networkd # ip addr
Essentially, this marks IPv6 addresses (eg autoconfig addresses) as label 99 (a “martian label”), except the preferred source address, which gets label 1. Thus the static address is chosen as the source address.
Apparently the outbound source will be the static address for all destinations except the local subnet. Let’s test that:
# (tcpdump -c 2 -npti ens192 host 2001:470:eb96:2::1 2>/dev/null &) ; sleep 1 ; \ ping -c 1 2001:470:eb96:2::1 >/dev/null ; sleep 1 IP6 2001:470:eb96:2:e97c:9a9c:5ffd:ba52 > 2001:470:eb96:2::1: ICMP6, echo request, id 28, seq 1, length 64 IP6 2001:470:eb96:2::1 > 2001:470:eb96:2:e97c:9a9c:5ffd:ba52: ICMP6, echo reply, id 28, seq 1, length 64 # (tcpdump -c 2 -npti ens192 host 2607:f8b0:400a:801::2003 2>/dev/null &) ; sleep 1 ; \ ping -c 1 2607:f8b0:400a:801::2003 >/dev/null ; sleep 1 IP6 2001:470:eb96:2::ffff > 2607:f8b0:400a:801::2003: ICMP6, echo request, id 29, seq 1, length 64 IP6 2607:f8b0:400a:801::2003 > 2001:470:eb96:2::ffff: ICMP6, echo reply, id 29, seq 1, length 64
Yes, that’s exactly what happens.
If you really need to use a static IPv6 address for all outbound connections, this is the section for you.
It took quite a bit of trial and error to get the magic incantation to prevent IPv6 autoconfiguration (SLAAC) while not completely breaking routing ; there were a lot of false starts with IPv6AcceptRA=no (don’t do that).
[Why not IPv6AcceptRA=no? Routing doesn’t seem to work properly with this set, even if you also set Gateway=. You can ‘force’ it to work by pinging the address from another system, causing NDP to fire on the IPv6 router, but this is hardly reliable.]
The essential bit is PrefixDenyList=. That should be set to the prefix(es) that your router normally hands out. Or, you can use PrefixAllowList= with a bogus network if you want to allow none.
# cat > /etc/systemd/network/20-wired.network << EOF [Match] Name=ens192 [Network] Address=10.1.10.9/24 Gateway=10.1.10.1 Address=2001:470:eb96:2::ffff/64 [IPv6AcceptRA] PrefixDenyList=2001:470:eb96:2::/64 EOF # systemctl restart systemd-networkd # ip addr
I can’t say that I really recommend this, though perhaps there are some edge cases for it.
In these examples, templatevm is the name of the template VM, and newvm is the clone.
Rather than actually cloning a VM, typically I just build a new VM without a hard drive, then copy the VMDK from a template VM. The NVRAM has to be copied, too, as the Arch Linux EFI bootloader entry is there.
# cd /vmfs/volumes/datastore1/templatevm # vmkfstools -i templatevm.vmdk ../newvm/newvm.vmdk Destination disk format: VMFS zeroedthick Cloning disk 'templatevm.vmdk'... Clone: 100% done. # cp templatevm.nvram ../newvm/newvm.nvram cp: overwrite '../newvm/newvm.nvram'? y
Then I attach the new VMDK to the new VM and boot it.
Most of the installation is already very generic, except:
Changing the hostname requires no more than adjusting /etc/hostname.
# echo newvm > /etc/hostname
This, too, is very straightforward:
# vgrename templatevm newvm
Strictly speaking, this isn’t necessary, as /etc/fstab uses filesystem UUIDs to mount the volumes ; though you may find it convenient to have the volume group name match the hostname.
If you started sshd in the template VM, it will generate SSH server keys, which shouldn’t be the same for any two hosts. To fix that, you need to remove the keys on the cloned system and regenerate them.
Caution: be very careful you don’t introduce any extra spaces near the * in this command!
# rm /etc/ssh/ssh_host_*_key* # systemctl restart sshdgenkeys
Normally, it’s not a problem for many systems to share the same LVM UUIDs or volume group names. There is one scenario, though, where it will conflict: when you attach LVM volumes to another system that was cloned from the same template. Here’s how to deal with that situation. This example assumes that you have attached a second disk to a VM that had the same ‘parent’.
# pvdisplay WARNING: Not using device /dev/sdb2 for PV H18lfO-g5sN-hZSH-LPV3-yBbB-TdFL-2fQbQE. WARNING: PV H18lfO-g5sN-hZSH-LPV3-yBbB-TdFL-2fQbQE prefers device /dev/sda2 because device is used by LV. --- Physical volume --- PV Name /dev/sda2 VG Name darkstar PV Size <15.00 GiB / not usable 2.00 MiB Allocatable yes (but full) PE Size 4.00 MiB Total PE 3839 Free PE 0 Allocated PE 3839 PV UUID H18lfO-g5sN-hZSH-LPV3-yBbB-TdFL-2fQbQE
Although there are two PVs attached (sda2 and sdb2), only one shows up because of the UUID conflict. Use vgimportclone to change both PV and VG UUIDs of the second disk.
# vgimportclone /dev/sdb2 # pvdisplay -C PV VG Fmt Attr PSize PFree /dev/sda2 darkstar lvm2 a-- <15.00g 0 /dev/sdb2 darkstar1 lvm2 a-- <15.00g 0 # vgchange -a y darkstar1 2 logical volume(s) in volume group "darkstar1" now active
The PV and VG UUIDs are different now, so both VGs can be used independently. Notice that the second VG has been renamed (again to avoid conflict) ; but if you don’t like the name it gave, you can always use vgrename.
Interestingly, the LV and filesystem (ext4/swap) UUIDs have not been changed. There is no way – and apparently no reason – to change the LV UUID. But you can change the filesystem (ext4/swap) UUIDs. This would avoid any potential conflict with EFI booting and /etc/fstab entries that used a UUID.
Caution: If you do this, the original system (ie, the one that this second drive belonged to) will no longer be bootable, unless you modify both its EFI bootloader entry and /etc/fstab.
# e2fsck -f /dev/darkstar1/root e2fsck 1.46.5 (30-Dec-2021) Pass 1: Checking inodes, blocks, and sizes Pass 2: Checking directory structure Pass 3: Checking directory connectivity Pass 4: Checking reference counts Pass 5: Checking group summary information /dev/darkstar1/root: 41645/917504 files (0.1% non-contiguous), 485673/3668992 blocks # tune2fs -U random /dev/darkstar1/root tune2fs 1.46.5 (30-Dec-2021) Setting the UUID on this filesystem could take some time. Proceed anyway (or wait 5 seconds to proceed) ? (y,N) y
For swap, it’s even easier, just mkswap and a new UUID will be generated:
# mkswap /dev/darkstar1/swap_0 mkswap: /dev/darkstar1/swap_0: warning: wiping old swap signature. Setting up swapspace version 1, size = 1024 MiB (1073737728 bytes) no label, UUID=24cb7b64-e287-4552-8a0c-f257197b2973
Until recently, I was a (relatively) happy Ubuntu user. Although I had a number of small issues with it, I worked through them, until I encountered this:
$ sudo apt install vim Reading package lists... Done Building dependency tree Reading state information... Done The following additional packages will be installed: alsa-topology-conf alsa-ucm-conf libasound2 libasound2-data libcanberra0 libltdl7 libogg0 libpython3.8 libtdb1 libvorbis0a libvorbisfile3 sound-theme-freedesktop Suggested packages: libasound2-plugins alsa-utils libcanberra-gtk0 libcanberra-pulse ctags vim-doc vim-scripts The following NEW packages will be installed: alsa-topology-conf alsa-ucm-conf libasound2 libasound2-data libcanberra0 libltdl7 libogg0 libpython3.8 libtdb1 libvorbis0a libvorbisfile3 sound-theme-freedesktop vim 0 upgraded, 13 newly installed, 0 to remove and 0 not upgraded. Need to get 3,884 kB of archives. After this operation, 12.1 MB of additional disk space will be used. Do you want to continue? [Y/n]
I don’t want to install sound libraries on a headless server just to run vim! And neither Ubuntu nor Debian (upstream) seem interested in fixing this. (“It’s a feature, not a bug!”)
Arch, on the other hand, did a totally reasonable thing, by compiling with sound support in gvim, but not ‘plain’ vim.
So, this was the proverbial straw that broke the camel’s back. I guess I should thank the Debian package maintainers for botching their vim build, and the Ubuntu package maintainers for not fixing it in their release either, so that I was pushed to explore other Linux distributions.
Feel free to contact me with any questions, comments, or feedback.