After using EndeavourOS, an Arch-based distro, for some time with much pleasure and appreciating Arch mechanisms (packages and AUR), I decided it was time to try the “real thing” and install Arch the “hard way” ๐ Spoiler: it’s not that hard!
I thought it was hard. For sure, it’s more complicated than other distro installation procedures, but, to be honest, after using Linux for more than 20 years, I thought there was not much to be scared of ๐
I now use Arch (besides other distros) on my machines greatly. Of course, I did many experiments with virtual machines before installing Arch on bare metal. I know there are many guides and tutorials, but I’d like to summarize my steps for installing Arch (with a SWAP partition, an EXT4 partition for data to be shared among distros, and a BTRFS primary partition). In particular, in this blog post, I’ll describe my steps for installing Arch on a virtual machine, which, as I’ve just said, it’s the best way to get confident with Arch and not be scared of installing Arch on a real computer. Moreover, on many guides, I noticed a few missing points, which, instead, are essential.
Of course, the best reference is the excellent official guide, and I’ll use the official guide as a reference while following along, https://wiki.archlinux.org/title/installation_guide. Note that there are still a few parts in the guide that refer to other parts of the excellent Arch wiki, and I had a few minor problems the first time I tried the Arch installation.
Here we go!
Create and configure the virtual machine with enough disk space (dynamically allocated so you won’t waste space on your disk), let’s say 100Gb. Make sure you enable EFI in the virtual machine configuration. Of course, insert the Arch Linux ISO as a live CD in the virtual machine. I’m going to use archlinux-2022.09.03-x86_64.iso.
As described in a previous post, I’d suggest performing the installation by connecting via SSH to the virtual machine. This way, you’re using a local terminal: copy and paste will work (since you’re on a local terminal). In particular, since the Arch installer is textual, being able to copy and paste commands from a local terminal makes everything easier. Moreover, the keyboard layout will be the host system’s keyboard layout. Thus, the keyboard layout will be already configured correctly. While in the virtual machine, youโd have to configure the keyboard layout.
(On a side note, even when installing Arch on a real computer, I prefer doing that via SSH, of course, from another computer.)
Before starting the virtual machine, we must map the SSH port of the virtual machine to a local port to connect from our computer. This requires knowing the name you gave to your virtual machine. In this example, I called the virtual machine “Arch Gnome” (because I’ll then install Gnome on the Arch installation). We must run these instructions from the host computer:
1 |
VBoxManage modifyvm "Arch Gnome" --natpf1 "SSH,tcp,127.0.0.1,2522,10.0.2.15,22" |
Port 2522 is the one we’ll have to use later for connecting to the virtual machine via localhost. Of course, feel free to use another free port number as long as you’ll use it consistently from now on.
Start the virtual machine:
Inside the live environment, the SSH server is already up and running. However, since we’ll connect with the root account (the only one present), we must give the root account a password. By default, it’s empty, and SSH will not allow you to log in with a blank password. Choose a password. This password is temporary, and if you’re in a trusted local network, you can choose an easy one.
Now, we can connect via SSH to the virtual machine through localhost. if you have already connected via SSH to localhost, you might get an error of the shape:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)! It is also possible that a host key has just been changed. The fingerprint for the ED25519 key sent by the remote host is SHA256: ... Please contact your system administrator. Add correct host key in <YOUR HOME>/.ssh/known_hosts to get rid of this message. Offending ECDSA key in <YOUR HOME>/.ssh/known_hosts:<A LINE NUMBER> Host key for [127.0.0.1]:2522 has changed and you have requested strict checking. Host key verification failed. |
All you have to do is edit the known_hosts file by removing the offending lines and try again. You will have to remove all the lines that start with “[127.0.0.1]:2522”.
Note that we’re using port 2522 because we previously used that for creating the port mapping. Let’s connect to the virtual machine and type the password we have previously specified for the root account inside the virtual machine (Accept the fingerprint when asked.):
1 |
ssh -p 2522 root@127.0.0.1 |
In your local terminal, you see that you get the colors of the virtual machine (now, you’re inside the virtual machine):
Let’s set the console keyboard layout (the default layout is US; if that’s fine with you, skip the next step). This step is not strictly required for our local terminal: even if we’re inside the virtual machine, we’re using our local terminal, so we already use the correct layout. However, let’s do that anyway since we want to simulate an actual installation. Moreover, having the proper layout is good if we want to run commands directly from the VirtualBox window.
I already know the layout I want for my Italian keyboard, so I run:
1 |
loadkeys it |
If you don’t know the exact layout, you can list the available ones with
1 |
ls /usr/share/kbd/keymaps/**/*.map.gz |
You can verify in the VirtualBox window that the layout is applied correctly.
Since we are in a virtual machine, the machine should already be able to access the Internet if your host is correctly connected (and that’s required to install Arch Linux). However, if you want to simulate what you would do with an actual installation on bare metal, you can ping a remote host and verify that everything’s OK:
1 |
ping archlinux.org |
Before going on, as suggested in the official guide, it’s better to make sure the system clock is accurate by enabling network synchronization NTP:
1 |
timedatectl set-ntp true |
Partitioning the disk
How to partition the disk is your choice. In this example, I will partition the disk according to my needs. However, you need at least two partitions: one for booting in UEFI mode and one for the root filesystem.
In this example, I’ll create four partitions:
- the one for booting in UEFI mode, formatted as FAT32, 300Mb (it should be enough for UEFI, but if unsure, go on with 512Mb)
- a swap partition, 20Gb (I have 16Gb, and if I want to enable hibernation, i.e., suspend to disk, that should be enough)
- a partition meant to host common data that I want to share among several Linux installations on the same machine (maybe I’ll blog about that in the future), formatted as EXT4, 30Gb
- the root partition, formatted as BTRFS, the rest of the disk
To do that, I’m using cfdisk, a textual partition manager, which I find easy to use.
Now, it is time to get to know the device name of our disk using the command lsblk:
1 2 3 4 5 |
root@archiso ~ # lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS loop0 7:0 0 683.2M 1 loop /run/archiso/airootfs sda 8:0 0 100G 0 disk sr0 11:0 1 794.3M 0 rom /run/archiso/bootmnt |
As you might guess, sda is the disk we are installing Arch Linux upon. In a virtual machine, that’s probably always like that. On a real machine, it might be different (for example, if you have an NVME SSD, it will be something like nvme0n1). It’s needless to say that using the correct device name is crucial, especially on a real machine, or you might end up wiping away essential data. The nice thing about a virtual machine is that you’re in a “sandbox,” so, at worst, you’ll break your virtual machine.
So I run
1 |
cfdisk /dev/sda |
If it is a new virtual machine, you’ll be asked a partition table: choose gpt.
Start creating your partitions. Just use the menus of cfdisk; it’s easy (on the bottom, you will find some help). Once you create a partition, set the “Type” correctly. By default, the type is “Linux filesystem”. For UEFI, you have to specify the type “EFI System,” and for the swap partition, “Linux swap”.
That’s my final result:
Let’s “Write” the partition table to disk and “Quit”. We can also verify with lsblk that the result is as expected:
1 2 3 4 5 6 7 8 9 |
root@archiso ~ # lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS loop0 7:0 0 683.2M 1 loop /run/archiso/airootfs sda 8:0 0 100G 0 disk โโsda1 8:1 0 300M 0 part โโsda2 8:2 0 20G 0 part โโsda3 8:3 0 30G 0 part โโsda4 8:4 0 49.7G 0 part sr0 11:0 1 794.3M 0 rom /run/archiso/bootmnt |
Of course, you must know which partition is meant for what. In my example, sda1 is for UEFI, sda2 for swap, sda3 for my shared data, and sda4 for root.
Format the partitions
According to my intended layout shown above, I’ll format the four partitions with the following commands:
1 2 3 4 |
mkfs.fat -F 32 /dev/sda1 mkswap /dev/sda2 mkfs.ext4 /dev/sda3 mkfs.btrfs /dev/sda4 |
Mount the partitions
This is also delicate, so you must use the correct device names. What follows is, of course, correct according to my layout.
First of all, let’s deal with the swap partition:
1 |
swapon /dev/sda2 |
The presence of the BTRFS file system for the root partition makes things a bit more interesting (or a bit more complicated, as you prefer ๐
First, we must mount the BTRFS filesystem on /mnt. Note that we are mounting the BTRFS on /mnt only temporarily and to create the subvolumes (in a minute, we will mount the subvolumes in their final shape on /mnt, together with the other partitions):
1 |
mount /dev/sda4 /mnt |
This will allow us to create the subvolumes. Again, what follows is the BTRFS subvolume layout I prefer. You might want to choose a different one. To use Timeshift, you must have at least @ for / and @home for /home. This is how I create the subvolumes I want:
1 2 3 4 5 6 |
btrfs su cr /mnt/@ btrfs su cr /mnt/@home btrfs su cr /mnt/@cache btrfs su cr /mnt/@log btrfs su cr /mnt/@machines btrfs su cr /mnt/@portables |
As I said, this mount was temporary, just for creating subvolumes. In fact, we now unmount /mnt:
1 |
umount /mnt |
And we mount every single subvolume in its final “position” inside /mnt by also specifying a few additional options like the general “noatime” and the BTRFS-specific “compress” to enable the ztsd compression:
1 2 3 4 5 6 |
mount -o subvol=/@,defaults,noatime,compress=zstd /dev/sda4 /mnt mount -o subvol=/@home,defaults,noatime,compress=zstd -m /dev/sda4 /mnt/home mount -o subvol=/@cache,defaults,noatime,compress=zstd -m /dev/sda4 /mnt/var/cache mount -o subvol=/@log,defaults,noatime,compress=zstd -m /dev/sda4 /mnt/var/log mount -o subvol=/@machines,defaults,noatime,compress=zstd -m /dev/sda4 /mnt/var/lib/machines mount -o subvol=/@portables,defaults,noatime,compress=zstd -m /dev/sda4 /mnt/var/lib/portables |
The “-m” option makes mount create the target directory if it does not exist.
Finally, we can mount the remaining partitions. The UEFI one should be mounted to “/boot/efi” inside “/mnt”. I like to mount the “common” partition in “/media/bettini/common” inside “/mnt” because that’s where I’ll use it (relying on the fact that I’ll create a user “bettini” for myself). Again, choose something else for yourself. These are the commands:
1 2 |
mount -o defaults,noatime -m /dev/sda1 /mnt/boot/efi mount -o defaults,noatime -m /dev/sda3 /mnt/media/bettini/common |
This is the final layout of /mnt, which, remember, is where our system will be installed:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/mnt โโโ boot โย ย โโโ efi โโโ home โโโ media โย ย โโโ bettini โย ย โโโ common โย ย โโโ lost+found โโโ var โโโ cache โโโ lib โย ย โโโ machines โย ย โโโ portables โโโ log |
Select the mirrors
This part, documented in the official installation guide, is usually skipped in several blog posts I found online. Instead, this step is essential.
The mirrors are specified in the file /etc/pacman.d/mirrorlist.
The guide says:
On the live system, after connecting to the internet, reflector updates the mirror list by choosing 20 most recently synchronized HTTPS mirrors and sorting them by download rate.
The higher a mirror is placed in the list, the more priority it is given when downloading a package. You may want to inspect the file to see if it is satisfactory. If it is not, edit the file accordingly, and move the geographically closest mirrors to the top of the list, although other criteria should be taken into account
You can verify that by inspecting the file /etc/pacman.d/mirrorlist. In my case, the Italian mirror is the last one, so it will be given the lowest priority. This sounds wrong to me. In particular, the documentation also points out:
This file will later be copied to the new system by pacstrap, so it is worth getting right.
Thus, I prefer to run the program reflector myself (see the reflector documentation for the single arguments; of course, I’m using “Italy” as the country because that’s where I leave; I could also specify several values separated by a comma, e.g., “Italy,Germany”):
1 2 3 4 5 6 7 8 |
reflector \ --country Italy \ --age 12 \ --protocol https \ --fastest 5 \ --latest 20 \ --sort rate \ --save /etc/pacman.d/mirrorlist |
The following step does not seem to be required in the installation guide. However, to make sure we have an updated PGP keyring (for checking signatures of packages), at this point, I also run:
1 |
pacman -Sy --noconfirm archlinux-keyring |
Running pacstrap
Now, it’s time to install the base packages, Linux kernel, and firmware for standard hardware using the pacstrap script. You specify the target directory, which, as you might guess, it’s /mnt, and the packages.
This is the command I run (I prefer to use the LTS kernel; if you want the latest kernel, use “linux” package instead of “linux-lts”; you can also install them both and then select one from the grub menu):
1 2 3 4 5 |
pacstrap /mnt base linux-lts linux-firmware \ nano vim \ intel-ucode \ btrfs-progs \ sof-firmware alsa-firmware |
This command will download about 500Mb, which might take time depending on your Internet speed.
Configuring the system
Since we have already manually mounted all our partitions (on /mnt), the Arch ISO can generate for us the file fstab automatically through the command genfstab:
1 |
genfstab -U /mnt >> /mnt/etc/fstab |
The “-U” option tells genfstab to use UUID to refer to partitions (alternatively, “-L” can be used to use labels instead).
You can have a look at the result (of course, UUID will be different in your case):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# Static information about the filesystems. # See fstab(5) for details. # <file system> <dir> <type> <options> <dump> <pass> # /dev/sda4 UUID=bdbb3168-40cb-4c09-99c3-545a8b195dd1 / btrfs rw,noatime,compress=zstd:3,space_cache=v2,subvolid=256,subvol=/@ 0 0 # /dev/sda1 UUID=84BE-A81A /boot/efi vfat rw,noatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro 0 2 # /dev/sda4 UUID=bdbb3168-40cb-4c09-99c3-545a8b195dd1 /home btrfs rw,noatime,compress=zstd:3,space_cache=v2,subvolid=257,subvol=/@home 0 0 # /dev/sda4 UUID=bdbb3168-40cb-4c09-99c3-545a8b195dd1 /var/cache btrfs rw,noatime,compress=zstd:3,space_cache=v2,subvolid=258,subvol=/@cache 0 0 # /dev/sda4 UUID=bdbb3168-40cb-4c09-99c3-545a8b195dd1 /var/log btrfs rw,noatime,compress=zstd:3,space_cache=v2,subvolid=259,subvol=/@log 0 0 # /dev/sda4 UUID=bdbb3168-40cb-4c09-99c3-545a8b195dd1 /var/lib/machines btrfs rw,noatime,compress=zstd:3,space_cache=v2,subvolid=260,subvol=/@machines 0 0 # /dev/sda4 UUID=bdbb3168-40cb-4c09-99c3-545a8b195dd1 /var/lib/portables btrfs rw,noatime,compress=zstd:3,space_cache=v2,subvolid=261,subvol=/@portables 0 0 # /dev/sda3 UUID=8751420a-136e-4076-b748-bb74675bf979 /media/bettini/common ext4 rw,noatime 0 2 # /dev/sda2 UUID=c1e636ae-6452-4724-9197-062f19968bdd none swap defaults 0 0 |
Note that although we used the option “compress=zstd” when mounting our BTRFS subvolumes, genfstab turned that into “compress=zstd:3” because “3” is the default compression value for zstd in BTRFS. If we wanted to make the compression value explicit, e.g., “1”, we should have done that when mounting the subvolumes. Of course, you can always tweak the generate fstab as you see fit.
Now, we can “enter” our installation with “chroot” or, better, with the enhanced arch-chroot, which automatically binds other things like /dev and /proc:
1 |
arch-chroot /mnt |
The root directory is what’s inside /mnt, so / refers to what’s inside /mnt. Also, the prompt has changed to reflect this:
1 2 |
root@archiso ~ # arch-chroot /mnt [root@archiso /]# |
We now set the timezone of the installed system. You must use Region/City according to your location. Timezones are available in the directory /usr/share/zoneinfo/. In my case (Italy), I run:
1 |
ln -sf /usr/share/zoneinfo/Europe/Rome /etc/localtime |
Then, we use hwclock to set the Hardware Clock from the System Clock:
1 |
hwclock --systohc |
Then, we edit /etc/locale.gen and uncomment the locales we need; in my case, en_US.UTF-8 UTF-8 and it_IT.UTF-8. Since I already know the two locales, instead of editing the file (where all locales are commented out), I append the two locales to the end of that file:
1 2 |
echo en_US.UTF-8 UTF-8 >> /etc/locale.gen echo it_IT.UTF-8 UTF-8 >> /etc/locale.gen |
And we generate the locales:
1 |
locale-gen |
We must also create the /etc/locale.conf file, and set the LANG variable accordingly. This can be done as follows:
1 |
echo LANG=en_US.UTF-8 >> /etc/locale.conf |
We also make permanent the initial changes to the console layout (remember, I used “it”; in your case, you need to use the code you previously specified):
1 |
echo KEYMAP=it >> /etc/vconsole.conf |
We’ll deal with the network configuration (of the installed system) in a minute. But we can already create the files /etc/hostname and /etc/hosts. You have to choose your preferred hostname. In this example, I’m going to use “arch-vm-gnome”. So I generate the two files with the following two commands:
1 2 3 4 5 6 7 |
echo arch-vm-gnome >> /etc/hostname cat >> /etc/hosts << EOF 127.0.0.1 localhost ::1 localhost 127.0.1.1 myhostname.localdomain arch-vm-gnome EOF |
The boot loader
Since we use BTRFS, we might want to tweak the file /etc/mkinitcpio.conf with these two modules:
1 |
MODULES=(crc32c-intel btrfs) |
And regenerate the initramfs images:
1 |
mkinitcpio -P |
Let’s now install the bootloader. I prefer GRUB. So let’s install a few packages:
1 |
pacman -S --noconfirm --needed grub efibootmgr |
During the installation, you might want to take note of the recommendation for installing and configuring grub and the optional dependencies:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
:: Install your bootloader and generate configuration with: $ grub-install ... $ grub-mkconfig -o /boot/grub/grub.cfg Optional dependencies for grub freetype2: For grub-mkfont usage fuse2: For grub-mount usage dosfstools: For grub-mkrescue FAT FS and EFI support lzop: For grub-mkrescue LZO support efibootmgr: For grub-install EFI support [pending] libisoburn: Provides xorriso for generating grub rescue iso using grub-mkrescue os-prober: To detect other OSes when generating grub.cfg in BIOS systems mtools: For grub-mkrescue FAT FS support |
Now we install grub in the UEFI partition. Note that, unlike standard GUI Linux installations, you can specify the “–bootloader-id”, which will be the identifier of this grub installation in UEFI. This is useful if you have several bootloaders on your machine. In this example, I’m using ArchGnome:
1 |
grub-install --target=x86_64-efi --bootloader-id=ArchGnome --efi-directory=/boot/efi |
Hopefully, the installation should succeed, and we can generate the grub menu:
1 |
grub-mkconfig -o /boot/grub/grub.cfg |
Here’s the output of these two commands:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[root@archiso /]# grub-install --target=x86_64-efi --bootloader-id=ArchGnome --efi-directory=/boot/efi Installing for x86_64-efi platform. Installation finished. No error reported. [root@archiso /]# grub-mkconfig -o /boot/grub/grub.cfg Generating grub configuration file ... Found linux image: /boot/vmlinuz-linux-lts Found initrd image: /boot/intel-ucode.img /boot/initramfs-linux-lts.img Found fallback initrd image(s) in /boot: intel-ucode.img initramfs-linux-lts-fallback.img Warning: os-prober will not be executed to detect other bootable partitions. Systems on them will not be added to the GRUB boot configuration. Check GRUB_DISABLE_OS_PROBER documentation entry. Adding boot menu entry for UEFI Firmware Settings ... done |
User accounts
Don’t forget to set the following passwords, or you will not be able to log in to the installed system once you reboot later.
We can now set the root password. This is the effective password for root in the installed system. This should be chosen carefully:
1 |
passwd |
I prefer to use “sudo”, so I first install that
1 |
pacman -S sudo |
This is also the moment to create your own user account; in my case, it is “bettini”.
1 2 3 |
useradd -m bettini usermod -aG wheel,sys,rfkill bettini |
I add the user to a few essential groups, in particular, “wheel” which makes my user a superuser account. Just relying on the group “wheel” is not enough: we must allow members of the group wheel to execute any command. This is done by uncommenting this line in the /etc/sudoers: “%wheel ALL=(ALL:ALL) ALL”. A sed command will accomplish that:
1 |
sed -i 's/# %wheel ALL=(ALL:ALL) ALL/%wheel ALL=(ALL:ALL) ALL/' /etc/sudoers |
That would be enough to reboot and try our installation. However, we would have no networking (actually, since this is a virtual machine, networking should work out of the box since you don’t need to configure any WiFi network, for example) and no desktop environment. So let’s go on with some further installations and configurations:
Install Gnome
In this example, I’m going to install the GNOME desktop environment. Besides GNOME, I’m installing other necessary packages like the “NetworkManager” (for easily configuring networking in GNOME), “firewall”, “firefox”, the package for choosing a power profile (useful for laptops), and other base packages, including the kernel headers (here I’m using “linux-lts-headers” because I installed the LTS kernel; otherwise, use “linux-headers”):
1 2 3 4 |
pacman -S --noconfirm --needed \ pipewire-jack pipewire-media-session xdg-desktop-portal-gnome gnome noto-fonts \ firefox power-profiles-daemon networkmanager firewalld \ base-devel linux-lts-headers |
About 700Mb will be downloaded.
Once done, we have to enable the services at boot (in particular, GDM, the login manager, and the NetworkManager):
1 2 3 4 |
systemctl enable gdm.service systemctl enable NetworkManager.service systemctl enable power-profiles-daemon.service systemctl enable firewalld.service |
Time to reboot!
Now, it’s time to leave the environment and unmount all the partitions:
1 2 |
exit umount -R /mnt |
And reboot into our new Arch Linux installation.
If everything goes fine, we should see the login manager, and we can enter Gnome:
So, in the end, it’s not so hard to install Arch ๐
Maybe it’s a long procedure, but… most of that can be scripted! That’s will be the subject of another blog post, so stay tuned! ๐
“not so hard”. Well I’m not so sure. I’ve installed Arch a lot but I always make a typo or miss a step. Like today I forgot the step grub-mkconfig and of course had to start over. This is why I switched to EndearourOS. It gives me Arch without the pain.
Nice article, though.
Jim,
If you make a mistake like that ( forgetting to grub-mkconfig ) just boot back into the install ISO, then mount /dev/sdX /mnt to mount your installed system where you forgot to run that, then arch-chroot /mnt.
Now you are in the chroot and you can run the grub-mkconfig that you forgot to run, type exit the chroot, umount -R /mnt, and reboot into your new installation without having to start over.
I just started messing around with arch and have used that trick several times because of things I forgot so the new install wouldn’t boot.
Works great!
Yes, arch-chroot is a life saver! ๐
Pingback: Snapper and grub-btrfs in Arch Linux | Lorenzo Bettini