It has been a very long time since I install Gentoo Linux on anything, so just noting in a blog post what I did. As always, the basic process is RTFM in the form of the Gentoo Handbook. In this post, I got as far as completing what I will call the “modern stage 1” (i.e. the process for rebuilding everything with an optimised toolchain, which used to be a “stage 1” install but became a “stage 3 with bootstrap.sh script” around 2010) but not configuring/building/installing the kernel and bootloader. Unfortunately I ran out of time to play with this and went back to Debian on the system, in order to get some other work done.

In order to enable the ‘latest-…’ symlinks to work, I had to enable following symlinks on the NAS I previously setup to act as a mirror server.

I edited /etc/frontview/apache/Shares.conf:

<Directory "/c/mirrors">
Options FollowSymlinks
</Directory>

And then restarted the webserver:

/etc/init.d/frontview stop
/etc/init.d/frontview start

I suspect that the changes to Shares.conf will get overwritten if I reconfigure any http-access to shares but, for now, this is working.

Choosing the right installation medium

As my machine was already booted from a Debian Live USB drive, I did the install from this.

Configuring the network

I am building this system in my air-gapped home lab, so there is not internet to download files from - however there is a local mirror (and my Debian Live systems is already connected to the network) which I will mount (later) for the source files.

Preparing the disks

I created a new GUID Partition Table (GPT) type partition table, added an EFI system partition (ESP) and partitioned the rest for and encrypted volume for LVM:

# parted /dev/sda
(parted) mklabel gpt
(parted) mkpart "EFI system partition" fat32 0% 512MiB
(parted) set 1 esp on
(parted) mkpart "encrypted container" ext4 512MiB 100%
(parted) set 2 lvm on

Setup encryption

cryptsetup luksFormat /dev/sda2
cryptsetup luksOpen /dev/sda2 lvm-base

Setup LVM

I had to install the lvm package, as it was not already available in my Debain live environment:

apt-get install lvm2 -n swap 

I am creating the following partition scheme, which is my preferred choice:

  • 15G - / (historically this would have been 10G /usr and 5G / but systemd objects to /usr and / being separate filesystems)
  • 10G - /var
  • 10G - /home
  • 5G - /srv
  • 5G - /opt
  • 5G - /tmp

N.B. I use the hostname for the volume group name, it is helpful to do this in case it needs mounting on another machine (e.g. for recovery) to avoid clashing volume names.

pvcreate /dev/mapper/lvm-base
vgcreate create sovereign /dev/mapper/lvm-base
lvcreate -L 16G -n swap sovereign
lvcreate -L 15G -n root sovereign
lvcreate -L 10G -n var sovereign
lvcreate -L 10G -n home sovereign
lvcreate -L 5G -n srv sovereign
lvcreate -L 5G -n opt sovereign
lvcreate -L 5G -n tmp sovereign

Setup filesystems

The Gentoo handbook page on preparing disks says to specify -T small to mkfs.ext4 for partitions less than 8GiB in size. The mkfs.ext4 handbook says this is for partitions less than 512 megabytes - which is much smaller than 8 GiB.

mkfs.vfat -F 32 /dev/sda1
mkswap -L swap /dev/mapper/sovereign-swap
for fs in var home srv opt tmp root
do
   mkfs.ext4 -L $fs /dev/mapper/sovereign-$fs
done

Mount the filesystems

swapon /dev/mapper/sovereign-swap
mkdir /mnt/gentoo
mount /dev/mapper/sovereign-root /mnt/gentoo
mkdir -p /mnt/gentoo/{boot/EFI,var,home,srv,opt,tmp}
mount /dev/sda1 /mnt/gentoo/boot/EFI
for fs in var home srv opt tmp
do
  mount /dev/mapper/sovereign-$fs /mnt/gentoo/$fs
done
chmod 1777 /mnt/gentoo/tmp

Installing the stage tarball

The Gentoo handbook’s page tells us to being by setting the date. Since I now have an NTP source in my air-gapped network, I just used that:

apt-get install ntpdate
ntpdate ntp

Download the tarball (noting I already made a mirror of it locally):

apt-get install wget
cd /tmp
wget http://mirror/mirrors/gentoo/latest-stage3-amd64-nomultilib-systemd.tar.xz

Extract the tarball:

tar -C /mnt/gentoo -xpvf /tmp/latest-stage3-amd64-nomultilib-systemd.tar.xz --xattrs-include='*.*' --numeric-owner

Configure the system variables

In /etc/portage/make.conf I set COMMON_FLAGS and added MAKEOPTS (for parallel makes, set to number of CPU threads) & PORTAGE_RO_DISTDIRS (for the aforementioned local mirror):

COMMON_FLAGS="-march=native -O2 -pipe -ftree-vectorize -funroll-loops"
# ...

MAKEOPTS="-j2"

# ...
PORTAGE_RO_DISTDIRS="/var/cache/distfiles_mirror"
# ...

And mounted the mirror:

mkdir /mnt/gentoo/var/cache/distfiles_mirror
mount -t nfs -o ro mirror:/mirror/gentoo/distfiles /mnt/gentoo/var/cache/distfiles_mirror

Chrooting

Configure mirrors

Because I am using local sources, I do need to do some mirror selection. By NFS mounting the dist files, no downloading should be necessary (and portage attempting to go off an fetch a source indicates a problem with my setup, which needs rectifying) so setting a distfiles mirror is unnecessary but the ebuild repository needs to be got from the local mirror:

mkdir /mnt/gentoo/etc/portage/repos.conf
cp /mnt/gentoo/usr/share/portage/config/repos.conf /mnt/gentoo/etc/portage/repos.conf/gentoo.conf

Then edit /mnt/gentoo/etc/portage/repos.conf/gentoo.conf (I changed sync-uri to my mirror and added sync-openpgp-key-refresh = no to stop it trying to get keys from Gentoo’s key server):

[DEFAULT]
main-repo = gentoo

[gentoo]
location = /var/db/repos/gentoo
sync-type = rsync
sync-uri = rsync://mirror/mirrors/gentoo/portage-db/gentoo
auto-sync = yes
sync-rsync-verify-jobs = 1
sync-rsync-verify-metamanifest = yes
sync-rsync-verify-max-age = 24
sync-openpgp-key-path = /usr/share/openpgp-keys/gentoo-release.asc
sync-openpgp-keyserver = hkps://keys.gentoo.org
sync-openpgp-key-refresh = no
sync-openpgp-key-refresh-retry-count = 40
sync-openpgp-key-refresh-retry-overall-timeout = 1200
sync-openpgp-key-refresh-retry-delay-exp-base = 2
sync-openpgp-key-refresh-retry-delay-max = 60
sync-openpgp-key-refresh-retry-delay-mult = 4
sync-webrsync-verify-signature = yes

Copy DNS information

cp --dereference /etc/resolv.conf /mnt/gentoo/etc/resolv.conf

Mount necessary filesystems

From the Gentoo handbook page:

The --make-rslave operations are needed for systemd support later in the installation.

mount -t proc none /mnt/gentoo/proc
mount --rbind /sys /mnt/gentoo/sys
mount --make-rslave /mnt/gentoo/sys
mount --rbind /dev /mnt/gentoo/dev
mount --make-rslave /mnt/gentoo/dev
mount --bind /run /mnt/gentoo/run
mount --make-slave /mnt/gentoo/run

Entering the new environment

(At least this stage has not changed since I last used Gentoo ;) ):

chroot /mnt/gentoo /bin/bash
source /etc/profile
export PS1="(chroot) ${PS1}"

Configure portage

Now we get to test if the ebuild mirror is configured correctly:

emerge --sync

The handbook recommends updating world at this point, which is a good point to test if our read-only distfiles mirror is working:

emerge -avuDN @world

I encountered a couple of problems doing this (so it was a good sanity-check):

  1. initially the first build failed with “C compiler cannot make executables”, so I checked my /etc/portage/make.conf - turned out I had spelled vectorize as vectorise (my native, British English, spelling). Correcting that and it installed just fine.
  2. missing sources for net-misc/iputils, so added that explicitly to the packages downloaded by my mirror Ansible playbook

Setup USE in /etc/portage/make.conf:

USE="X alsa doc"

Setup CPU_FLAGS_* - from the Gentoo handbook :

emerge -av app-portage/cpuid2cpuflags
echo "*/* $(cpuid2cpuflags)" > /etc/portage/package.use/00cpu-flags

Set VIDEO_CARDS in /etc/portage/make.conf for this machine’s hardware:

VIDEO_CARDS="fbdev vesa intel"

Configure the system

systemd-firstboot --prompt --setup-machine-id
systemctl preset-all
ln -sf ../usr/share/zoneinfo/Europe/London /etc/localtime

Configure locales by setting /etc/locale.conf:

LANG="en_GB.UTF-8"
LC_COLLATE="C.UTF-8"

and /etc/locale.gen:

en_GB UTF-8

before running:

locale-gen
env-update && source /etc/profile && export PS1="(chroot) ${PS1}"

Bootstrapping

Per the Gentoo FAQ, this stage is performed before proceeding to configuring the kernel.

cd /var/db/repos/gentoo/scripts

There is a bug in the current script, according to Sakaki’s EFI Install Guide so make the change they suggest:

The second problem is that, for modern versions of gcc, we need to allow the openmp USE flag. With the /var/db/repos/gentoo/scripts/bootstrap.sh file still open, issue Ctrlw to start a second search, and then type export USE=”- and press Enter. Modify the line selected by adding openmp at the end, so it now reads:

export USE="-* bootstrap ${ALLOWED_USE} ${BOOTSTRAP_USE} openmp"

Note As this file exists as part of the main Gentoo ebuild repository, your changes will be overwritten next time you sync. As we only want to bootstrap our system now, that’s not a problem (but you can of course make a copy of the modified bootstrap.sh file at this point, should you wish).

cp /etc/locale.gen{,.bak}
time ./bootstrap.sh

This took around 5 minutes for me. Next, check the rebuilt compiler is valid:

gcc-config --list-profiles

Run bootstrap again to rebuild with the optimised toolchain:

time ./bootstrap.sh
# Pure sanity check - if it did not break the first time, should be fine here:
gcc-config --list-profiles
mv /etc/locale.gen{.bak,}
locale-gen
env-update && source /etc/profile && export PS1="(chroot) ${PS1}"

Rebuild all of the packages:

touch /tmp/prebuild_checkpoint  # Used to check everything has been updated, after the build
# The 'doc' flag I added creates dependency loops, which will need to be resolved later
USE="-doc" emerge -ave @world
dispatch-conf  # Review any configuration files the need updating

Verify everything was rebuild successfully, by removing unnecessary dependencies then checking for files older than our /tmp/prebuild_checkpoint file.

# Remove anything not in the dependency tree
emerge --depclean
# If every executable on the system has been rebuilt, this command should produce no output
find / -type d -path /boot/efi -prune -o -path /proc -prune -o -type f -executable -not -newer /tmp/prebuild_checkpoint -print0 2>/dev/null | xargs -0 file --no-pad --separator="@@@" | grep -iv '@@@.* text'
# If every file recognised by `file` as a libarary (ELF or archive) has been rebuilt, this command should produce no output
find / -type d -path /boot/efi -prune -o -path /proc -prune -o -type f -not -executable -not -newer /tmp/prebuild_checkpoint -print0 2>/dev/null | xargs -0 file --no-pad --separator="@@@" | grep '@@@.*\( ELF\| ar archive\)'