Skip to content

Gentoo Linux Setup Guide

This is to document my way of installing Gentoo Linux.

Fresh Installation

Some useful information:

Prepare partition(s) and create file systems

The partition layout is as follows:

/dev/nvme0n1
 ├── /dev/nvme0n1p1 [EFI]       /efi       1 GB         fat32       Bootloader
 └── /dev/nvme0n1p2 [ROOT]      (root)     ->END        luks        encrypted root partition
      └──  rootfs               /          ->END        btrfs       root filesystem

Partition the disk as follows:

livecd ~ # fdisk /dev/nvme0n1

Welcome to fdisk (util-linux 2.41.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


Command (m for help): g
Created a new GPT disklabel (GUID: CC35EF1A-FB56-4854-9E38-813D07344959).

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

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

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-4000797326, default 2099200):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2099200-4000797326, default 4000796671):

Created a new partition 2 of type 'Linux filesystem' and of size 1.9 TiB.

Command (m for help): t
Partition number (1,2, default 2):
Partition type or alias (type L to list all): 23

Changed type of partition 'Linux filesystem' to 'Linux root (x86-64)'.

Command (m for help): p
Disk /dev/nvme0n1: 1.86 TiB, 2048408248320 bytes, 4000797360 sectors
Disk model: SAMSUNG MZVLC2T0HBLD-00BLL
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: CC35EF1A-FB56-4854-9E38-813D07344959

Device           Start        End    Sectors  Size Type
/dev/nvme0n1p1    2048    2099199    2097152    1G EFI System
/dev/nvme0n1p2 2099200 4000796671 3998697472  1.9T Linux root (x86-64)

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

Format the EFI partition:

livecd ~ # mkfs.vfat -F 32 -n EFI /dev/nvme0n1p1
mkfs.fat 4.2 (2021-01-31)

Initialize and open the LUKS partition:

livecd ~ # cryptsetup luksFormat /dev/nvme0n1p2

WARNING!
========
This will overwrite data on /dev/nvme0n1p2 irrevocably.

Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/nvme0n1p2:
Verify passphrase:
livecd ~ # cryptsetup luksOpen /dev/nvme0n1p2 root
Enter passphrase for /dev/nvme0n1p2:
livecd ~ # cryptsetup refresh --persistent --allow-discards root
Enter passphrase for /dev/nvme0n1p2:

[!INFO] The last commant to make the discard option persistent. Use lsblk -D to print information about the discarding capabilities (SSD).

Format the LUKS root partition:

livecd ~ # mkfs.btrfs -L ROOT /dev/mapper/root
btrfs-progs v6.17.1
See https://btrfs.readthedocs.io for more information.

Performing full device TRIM /dev/mapper/root (1.86TiB) ...
Label:              ROOT
UUID:               4845dc3a-07fc-48df-a631-9b285d8822bb
Node size:          16384
Sector size:        4096        (CPU page size: 4096)
Filesystem size:    1.86TiB
Block group profiles:
  Data:             single            8.00MiB
  Metadata:         DUP               1.00GiB
  System:           DUP               8.00MiB
SSD detected:       yes
Zoned device:       no
Features:           extref, skinny-metadata, no-holes, free-space-tree
Checksum:           crc32c
Number of devices:  1
Devices:
   ID        SIZE  PATH
    1     1.86TiB  /dev/mapper/root

Mount the root partition and create subvolumes:

livecd ~ # mount -t btrfs -o noatime,compress=zstd,ssd,subvol=/ -L ROOT /mnt/gentoo
livecd ~ # mount
/dev/mapper/root on /mnt/gentoo type btrfs (rw,relatime,compress=zstd,subvolid=5,subvol=/)
livecd ~ # cd /mnt/gentoo/
livecd /mnt/gentoo # btrfs sub cr @
Create subvolume './@'
livecd /mnt/gentoo # btrfs sub cr @root
Create subvolume './@root'
livecd /mnt/gentoo # btrfs sub cr @home
Create subvolume './@home'
livecd /mnt/gentoo # btrfs sub cr @swap
Create subvolume './@swap'
livecd /mnt/gentoo # btrfs sub cr @usr_local
Create subvolume './@usr_local'
livecd /mnt/gentoo # btrfs sub cr @snapshots
Create subvolume './@snapshots'
livecd /mnt/gentoo # cd
livecd ~ # umount /mnt/gentoo

Mount the root and all other subvolumes and create the swap file:

livecd ~ # mount -t btrfs -o noatime,compress=zstd,ssd,subvol=@ -L ROOT /mnt/gentoo
livecd ~ # mkdir --parents /mnt/gentoo/efi
livecd ~ # mount -L EFI /mnt/gentoo/efi
livecd ~ # cd /mnt/gentoo/
livecd /mnt/gentoo # mkdir -p root home swap snapshots usr/local
livecd /mnt/gentoo # mount -t btrfs -o noatime,compress=zstd,ssd,subvol=@root -L ROOT /mnt/gentoo/root/
livecd /mnt/gentoo # mount -t btrfs -o noatime,compress=zstd,ssd,subvol=@home -L ROOT /mnt/gentoo/home/
livecd /mnt/gentoo # mount -t btrfs -o noatime,compress=zstd,ssd,subvol=@swap -L ROOT /mnt/gentoo/swap/
livecd /mnt/gentoo # mount -t btrfs -o noatime,compress=zstd,ssd,subvol=@usr_local -L ROOT /mnt/gentoo/usr/local/
livecd /mnt/gentoo # mount -t btrfs -o noatime,compress=zstd,ssd,subvol=@snapshots -L ROOT /mnt/gentoo/snapshots/
livecd /mnt/gentoo # lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
loop0         7:0    0   3.7G  1 loop  /run/rootfsbase
sda           8:0    1 119.5G  0 disk
├─sda1        8:1    1 119.5G  0 part
│ ├─ventoy  253:0    0   3.8G  1 dm    /run/initramfs/live
│ └─sda1    253:1    0 119.5G  0 dm
└─sda2        8:2    1    32M  0 part
nvme0n1     259:0    0   1.9T  0 disk
├─nvme0n1p1 259:6    0     1G  0 part  /mnt/gentoo/efi
└─nvme0n1p2 259:7    0   1.9T  0 part
  └─root    253:2    0   1.9T  0 crypt /mnt/gentoo/snapshots
                                       /mnt/gentoo/usr/local
                                       /mnt/gentoo/swap
                                       /mnt/gentoo/root
                                       /mnt/gentoo/home
                                       /mnt/gentoo
livecd /mnt/gentoo # btrfs fi mkswapfile -s 8g swap/swapfile
create swapfile swap/swapfile size 8.00GiB (8589934592)
livecd /mnt/gentoo # swapon !$
swapon swap/swapfile
livecd /mnt/gentoo # free
               total        used        free      shared  buff/cache   available
Mem:        65233972     4431536    59317876     1668348     3781696    60802436
Swap:        8388604           0     8388604

Download the stage file and configure portage

Download the stage file (update the url):

livecd /mnt/gentoo # wget https://distfiles.gentoo.org/releases/amd64/autobuilds/20251221T154556Z/stage3-amd64-desktop-systemd-20251221T154556Z.tar.xz
livecd /mnt/gentoo # tar xpvf stage3-*.tar.xz --xattrs-include='*.*' --numeric-owner -C /mnt/gentoo

Edit /mnt/gentoo/etc/portage/make.conf to include

...
COMMON_FLAGS="-march=native -O3 -pipe"
...
RUSTFLAGS="${RUSTFLAGS} -C target-cpu=native"
...
MAKEOPTS="-j8 -l10"
...
ACCEPT_LICENSE="-* @FREE @BINARY-REDISTRIBUTABLE"
...
ACCEPT_KEYWORDS="~amd64"
...

Copy DNS info and mounting the necessary filesystems:

livecd /mnt/gentoo # cp --dereference /etc/resolv.conf /mnt/gentoo/etc/
livecd /mnt/gentoo # mount --types proc /proc /mnt/gentoo/proc
livecd /mnt/gentoo # mount --rbind /sys /mnt/gentoo/sys
livecd /mnt/gentoo # mount --make-rslave /mnt/gentoo/sys
livecd /mnt/gentoo # mount --rbind /dev /mnt/gentoo/dev
livecd /mnt/gentoo # mount --make-rslave /mnt/gentoo/dev
livecd /mnt/gentoo # mount --bind /run /mnt/gentoo/run
livecd /mnt/gentoo # mount --make-slave /mnt/gentoo/run

Chroot:

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

Fetch the latest snapshot and choose the profile:

(chroot) / # emerge-webrsync
(chroot) / # eselect news read
(chroot) / # eselect profile list

Configuring the USE variables and updating the @world set:

(chroot) / # emerge -a1 app-portage/cpuid2cpuflags
(chroot) / # mkdir -p /etc/portage/package.{accept_keywords,license,mask,unmask,use}
(chroot) / # echo "*/* $(cpuid2cpuflags)" > /etc/portage/package.use/00-cpu-flags
(chroot) / # echo "*/* VIDEO_CARDS: nvidia" > /etc/portage/package.use/00-video_cards
(chroot) / # echo "*/* INPUT_DEVICES: libinput" > /etc/portage/package.use/00-input_devices
(chroot) / # emerge -avuDN @world
(chroot) / # emerge -ac

Install logv:

(chroot) / # echo app-portage/elogv | tee -a /etc/portage/package.use/elogv
(chroot) / # emerge -a --norep app-portage/elogv

and edit /etc/portage/make.conf to include

PORTAGE_ELOG_CLASSES="info log warn error"
PORTAGE_ELOG_SYSTEM="echo save"

FEATURES="... clean-logs"

Install gentoolkit, eclean-kernel, vim, and nano:

(chroot) / # echo app-portage/gentoolkit | tee -a /etc/portage/package.use/gentoolkit
(chroot) / # emerge -a --norep app-portage/gentoolkit
(chroot) / # echo app-admin/eclean-kernel | tee -a /etc/portage/package.use/eclean-kernel
(chroot) / # emerge -a --norep app-admin/eclean-kernel
(chroot) / # echo app-editors/nano | tee -a /etc/portage/package.use/nano
(chroot) / # emerge -a --norep app-editors/nano
(chroot) / # echo app-editors/vim | tee -a /etc/portage/package.use/vim
(chroot) / # emerge -a --norep app-editors/vim
(chroot) / # cat << EOF | tee -a ~/.vimrc
syntax on
filetype plugin on
filetype indent on

set noswapfile
set number
set relativenumber
EOF

Switch the default editor to vim:

(chroot) / # eselect editor list
(chroot) / # eselect editor set vim
(chroot) / # eselect vi list
(chroot) / # eselect vi set vim

Set the timezone:

(chroot) / # ln -sf ../usr/share/zoneinfo/Asia/Seoul /etc/localtime

Set the locale:

(chroot) / # vi /etc/locale.gen
(chroot) / # locale-gen
(chroot) / # eselect locale list
(chroot) / # eselect locale set en_US.utf8
(chroot) / # env-update && source /etc/profile && export PS1="(chroot) ${PS1}"

Install kernel

Install firmware and microcode:

(chroot) / # echo sys-kernel/linux-firmware | tee -a /etc/portage/package.use/linux-firmware
(chroot) / # emerge -a --norep sys-kernel/linux-firmware
(chroot) / # echo sys-firmware/sof-firmware | tee -a /etc/portage/package.use/sof-firmware
(chroot) / # emerge -a --norep sys-firmware/sof-firmware
(chroot) / # echo sys-firmware/intel-microcode | tee -a /etc/portage/package.use/intel-microcode
(chroot) / # emerge -a --norep sys-firmware/intel-microcode

Install the bootloader (systemd-boot):

(chroot) / # echo "sys-apps/systemd boot cryptsetup ukify" | tee -a /etc/portage/package.use/systemd
(chroot) / # emerge -a1N sys-apps/systemd
(chroot) / # bootctl install
(chroot) / # systemctl enable systemd-boot-update.service
(chroot) / # LUKS_VOLUME_UUID=<LUKS_VOLUME_UUID>
(chroot) / # cat << EOF > /etc/kernel/cmdline
init=/usr/lib/systemd/systemd
rd.luks.name="${LUKS_VOLUME_UUID}"=root
rd.luks.options=discard
root=/dev/mapper/root
rootflags=compress=zstd,subvol=/@
EOF

Edit /efi/loader/loader.conf to comment out timeout (and set it to 3, if not done so).

Install installkernel:

(chroot) / # echo sys-kernel/installkernel systemd systemd-boot dracut uki ukify | tee -a /etc/portage/package.use/installkernel
(chroot) / # emerge -a --norep sys-kernel/installkernel
(chroot) / # mkdir -p /etc/cmdline.d
(chroot) / # ln -s ../kernel/cmdline /etc/cmdline.d/00-installkernel.conf
(chroot) / # mkdir -p /etc/dracut.conf.d
(chroot) / # cat << EOF > /etc/dracut.conf.d/crypto_luks.conf
add_dracutmodules+=" crypt dm rootfs-block "
EOF

[!NOTE] To disable generating a unified kernel, remove the uki ukify USE flags from sys-apps/systemd and sys-kernel/installkernel.

This command may be needed for distribution kernel configuration (by dracut):

$ echo net-nds/rpcbind | tee -a /etc/portage/package.use/rpcbind
$ emerge -a --norep net-nds/rpcbind

Install the distribution kernel (either the binary or source variant):

(chroot) / # echo sys-kernel/gentoo-kernel-bin | tee -a /etc/portage/package.mask/dist-kernel
(chroot) / # cat << EOF | tee -a /etc/portage/package.use/dist-kernel
sys-kernel/gentoo-kernel
*/* dist-kernel
EOF
(chroot) / # emerge -a1 virtual/dist-kernel

Update the @world set to apply the new use variable:

(chroot) / # emerge -avuDN @world

Configure the system

Edit /etc/fstab to include:

...
LABEL="EFI"    /efi       vfat  defaults,noatime,umask=0077,tz=UTC                1 2
LABEL="ROOT"   /          btrfs defaults,noatime,compress=zstd,subvol=/@          0 1
LABEL="ROOT"   /root      btrfs defaults,noatime,compress=zstd,subvol=/@root      0 1
LABEL="ROOT"   /home      btrfs defaults,noatime,compress=zstd,subvol=/@home      0 1
LABEL="ROOT"   /usr/local btrfs defaults,noatime,compress=zstd,subvol=/@usr_local 0 1
LABEL="ROOT"   /snapshots btrfs defaults,noatime,compress=zstd,subvol=/@snapshots 0 1
LABEL="ROOT"   /swap      btrfs defaults,noatime,compress=zstd,subvol=/@swap      0 1
/swap/swapfile  none      swap  sw

Schedule periodic fstrim run:

(chroot) / # systemctl enable fstrim.timer

Set the hostname:

(chroot) / # echo thinkpad > /etc/hostname

Install NetworkManager:

(chroot) / # cat << EOF | tee -a /etc/portage/package.use/networkmanager
net-misc/networkmanager -wext -ppp
*/* networkmanager
EOF
(chroot) / # emerge -a net-misc/networkmanager
(chroot) / # systemctl enable NetworkManager.service

Set the root password, time sync daemon, and first system setup:

(chroot) / # passwd
(chroot) / # systemd-machine-id-setup
(chroot) / # systemd-firstboot --prompt
(chroot) / # systemctl enable systemd-timesyncd.service

Install filesystem userspace programs:

(chroot) / # echo sys-block/io-scheduler-udev-rules | tee -a /etc/portage/package.use/io-scheduler-udev-rules
(chroot) / # emerge -a --norep sys-block/io-scheduler-udev-rules
(chroot) / # echo sys-fs/xfsprogs | tee -a /etc/portage/package.use/xfsprogs
(chroot) / # emerge -a --norep sys-fs/xfsprogs
(chroot) / # echo sys-fs/e2fsprogs | tee -a /etc/portage/package.use/e2fsprogs
(chroot) / # emerge -a --norep sys-fs/e2fsprogs
(chroot) / # echo sys-fs/dosfstools | tee -a /etc/portage/package.use/dosfstools
(chroot) / # emerge -a --norep sys-fs/dosfstools
(chroot) / # echo sys-fs/btrfs-progs | tee -a /etc/portage/package.use/btrfs-progs
(chroot) / # emerge -a --norep sys-fs/btrfs-progs
(chroot) / # echo sys-fs/lvm2 lvm | tee -a /etc/portage/package.use/lvm2
(chroot) / # emerge -a1N --norep sys-fs/lvm2

Install some miscellaneous programs:

(chroot) / # echo sys-apps/mlocate | tee -a /etc/portage/package.use/mlocate
(chroot) / # emerge -a sys-apps/mlocate
(chroot) / # systemctl enable updatedb.timer
(chroot) / # echo app-shells/bash-completion | tee -a /etc/portage/package.use/bash-completion
(chroot) / # emerge -a app-shells/bash-completion

Post-installation Configuration

Check make.conf and make adjustment

For example, Localization

$ cat << EOF | sudo tee -a /etc/portage/package.use/00-localization
*/* LINGUAS: en ko
*/* L10N: en ko
EOF

Considerations for minimizing SSD wear

[!INFO] See SSD and Portage TMPDIR on tmpfs.

  1. Portage TMPDIR on tmpfs

    Edit /etc/portage/make.conf to include

    # https://wiki.gentoo.org/wiki/SSD#Portage_TMPDIR_on_tmpfs
    # https://wiki.gentoo.org/wiki/Portage_TMPDIR_on_tmpfs#fstab
    # It is confirmed that the system reserves 32G of the /tmp storage.
    PORTAGE_TMPDIR=/tmp
    

    Optionally, packages that require large disk space can be built outside of the tmpfs space by following the guide and also this. For example, edit /etc/portage/env/00-notmpfs.conf to include

    PORTAGE_TMPDIR="/var/tmp/notmpfs"
    

    and add large packages to /etc/portage/package.env/00-notmpfs-large_pkgs:

    app-office/libreoffice  00-notmpfs.conf
    dev-lang/rust           00-notmpfs.conf
    sys-devel/gcc           00-notmpfs.conf
    llvm-core/clang         00-notmpfs.conf
    llvm-core/llvm          00-notmpfs.conf
    www-client/firefox      00-notmpfs.conf
    mail-client/thunderbird 00-notmpfs.conf
    dev-qt/qtwebengine      00-notmpfs.conf
    sci-libs/tensorflow     00-notmpfs.conf
    
  2. XDG cache on tmpfs

    To remap the cache directory location, create /etc/profile.d/50-xdg_cache_home.sh:

    if [ ${LOGNAME} ]; then
    export XDG_CACHE_HOME="/run/user/${UID}/cache"
    fi
    
  3. Web browser profile(s) and cache on tmpfs

    Install the browser component relocator:

    $ echo www-misc/profile-sync-daemon | sudo tee -a /etc/portage/package.use/profile-sync-daemon
    $ sudo emerge -a www-misc/profile-sync-daemon
    

    Close all the browsers, start and enable the daemon:

    $ systemctl --user enable --now psd
    $ psd p
    
  4. NVMe specifics

    Userspace tool:

    $ echo sys-apps/nvme-cli | sudo tee -a /etc/portage/package.use/nvme-cli
    $ sudo emerge -a sys-apps/nvme-cli
    

    I/O testing:

    $ # if not installed
    $ echo sys-apps/hdparm | sudo tee -a /etc/portage/package.use/hdparm
    $ sudo emerge -a sys-apps/hdparm
    $ # end of installation
    $ sudo hdparm -tT --direct /dev/nvme0n1
    

    Use the simple kernel I/O scheduler: Create NVMe I/O scheduler rules file at /etc/udev/rules.d/60-ioschedulers.rules:

    # Set scheduler for NVMe devices
    ACTION=="add|change", KERNEL=="nvme[0-9]n[0-9]", ATTR{queue/scheduler}="none"
    

Disable btrfs quota

This can really slow down the filesystem performance when many snapshots are taken.

Check if its enabled:

$ sudo btrfs qgroup show /

If enabled, disable the quota function:

$ sudo btrfs quota disable /

Enable guru repository

Install eselect-repository and sync the repo:

$ echo app-eselect/eselect-repository | sudo tee -a /etc/portage/package.use/eselect-repository
$ sudo emerge -a app-eselect/eselect-repository
$ sudo eselect-repository enable guru
$ sudo emaint -r guru sync

Edit /etc/portage/package.mask/guru to automatically mask all packages under this repo:

*/*::guru

sshd

Edit /etc/ssh/sshd_config.d/50-authentication.conf to include

PermitRootLogin prohibit-password
PubkeyAuthentication yes
PermitEmptyPasswords no
ChallengeResponseAuthentication yes

dev-vcs/git

This may already been pulled as a dependency of other packages. This adds an extra USE flag:

$ echo dev-vcs/git keyring | sudo tee -a /etc/portage/package.use/git
$ sudo emerge -aN --norep dev-vcs/git

sudo

$ echo app-admin/sudo | sudo tee -a /etc/portage/package.use/sudo
$ sudo emerge -a --norep app-admin/sudo
$ sudo visudo

and edit visudo to grant the users in the wheel group the sudoer priviliage.

System monitoring programs

$ echo sys-process/procps modern-top | sudo tee -a /etc/portage/package.use/procps
$ sudo emerge -a1N sys-process/procps
$
$ cat << EOF | sudo tee -a /etc/portage/package.use/htop
sys-process/htop delayacct hwloc lm-sensors

# dependencies
sys-apps/lm-sensors contrib sensord
EOF
$ sudo emerge -a --norep sys-process/htop
$
$ echo sys-process/iotop | sudo tee -a /etc/portage/package.use/iotop
$ sudo emerge -a --norep sys-process/iotop
$
$ sudo mkdir -p /etc/sysctl.d
$ echo "kernel.task_delayacct = 1" | sudo tee -a /etc/sysctl.d/50-task_delayacct.conf

Reboot the system for the new kernel config to take effect.

snapper

$ echo app-backup/snapper | sudo tee -a /etc/portage/package.use/snapper
$ sudo emerge -a --norep app-backup/snapper
$ sudo systemctl enable --now snapper-cleanup.timer

Follow Configuration to setup new snapper configurations.

[!NOTE] Comment out the QGROUP setting from all configuration files at /etc/snapper/configs/<for_each_config>.

If the btrfs quota is enabled (check with sudo btrfs qgroup show /), then disable it using sudo btrfs quota disable /.

Follow Rollback to add the snapshot rollback support.

Follow Tips and tricks for automatic snapshoting during installations by portage.

systemd-homed

[!NOTE] See systemd-homed.service regarding migration to another host.

Add USE flags (the newline is intentional), update the @world set, and reload the systemd process:

$ cat << EOF | sudo tee -a /etc/portage/package.use/systemd

# systemd-homed
sys-auth/pambase homed
sys-apps/systemd cryptsetup homed
EOF
$ sudo emerge -avuDN @world
$ systemctl daemon-reexec   # or systemctl soft-reboot

Enable the systemd-homed service:

$ sudo systemctl enable --now systemd-homed

The Name Service Switch needs to be imformed to use systemd for authenticating users. Confirm that /etc/nsswitch.conf contains something like this

passwd:      files systemd
shadow:      files systemd
group:       files systemd

See the homectl and userdbctl manpages for reference.

Create a new user:

$ sudo homectl create UNAME --alias=ALIAS \
    -c "Real Name" --email-address=EMAIL \
    --uid=1000 -G audio,input,video,wheel \
    --ssh-authorized-keys="@$HOME/.ssh/authorized_keys" \
    --recovery-key=on --storage=subvolume

Or, with a luks-based storage combined with a btrfs filesystem type:

$ sudo homectl create UNAME --alias=ALIAS \
    -c "Real Name" --email-address=EMAIL \
    --uid=1000 -G audio,input,video,wheel \
    --ssh-authorized-keys="@$HOME/.ssh/authorized_keys" \
    --recovery-key=on --storage=luks --fs-type=btrfs \
    --luks-extra-mount-options=noatime

Set the keymap options:

$ localectl set-x11-keymap us "" "" caps:ctrl_modifier

Generate the ssh key:

$ ssh-keygen -t ed25519 -C "your_email@example.com"

Google authenticator PAM module

Following the instruction, install the necessary packages:

$ echo sys-auth/google-authenticator | sudo tee -a /etc/portage/package.use/google-authenticator
$ sudo emerge -a --norep sys-auth/google-authenticator
$ echo media-gfx/qrencode | sudo tee -a /etc/portage/package.use/qrencode
$ sudo emerge -a --norep media-gfx/qrencode

The second package is optional.

Edit /etc/ssh/sshd_config.d/50-authentication.conf to include

UsePAM yes

And, add the following line to /etc/pam.d/sshd (to the front):

auth       required pam_google_authenticator.so

Generate the secret by following this.

firewalld

$ cat << EOF | sudo tee -a /etc/portage/package.use/firewalld
net-firewall/firewalld -gui

# required by net-firewall/firewalld-2.4.0::gentoo[python_single_target_python3_13]
# required by net-firewall/firewalld (argument)
>=net-firewall/nftables-1.1.6 json python xtables
EOF
$ sudo emerge -a net-firewall/firewalld
$ sudo systemctl enable --now firewalld

docker

  • Install docker and cli programs:

    $ cat << EOF | sudo tee -a /etc/portage/package.use/docker
    # installed
    app-containers/docker-cli
    app-containers/docker-compose
    app-containers/docker btrfs cuda
    
    # depencencies
    app-containers/containerd btrfs device-mapper
    EOF
    

    Because of circular dependencies, these packages may need to be emerged first by temporarily turning off USE flags and the second time after enabling the USE flags.

    $ # disable USE flags
    $ sudo emerge -a --norep app-containers/docker
    $ # re-enable USE flags
    $ sudo emerge -a1N app-containers/docker
    $ sudo emerge -a --norep app-containers/docker-cli app-containers/docker-compose
    
  • Add relevant users to the docker group:

    $ sudo homectl update $USER -G $(groups | sed -e "s/${USER}/docker/" -e 's/ \+/,/g')
    

    [!INFO] The shenanigan at the -G argument is because there is no "append" option and as such the homectl update is destructive. The hack is using the fact that groups returns the current groups which $USER is a member of including the group with the same name as $USER. It appears that the $USER needs not be explicitly added to the list.

  • Set the docker storage driver to use btrfs:

    $ sudo mkdir -p /etc/docker/
    $ cat << EOF | sudo tee -a /etc/docker/daemon.json
    {
        "storage-driver": "btrfs"
    }
    EOF
    
  • Enable ip forwarding persistently:

    $ sudo mkdir -p /etc/sysctl.d
    $ echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.d/50-ip_forward.conf
    
  • Run the docker daemon:

    $ sudo systemctl enable --now docker.service
    

See Usage for general use.

GNU Stow

$ echo app-admin/stow | sudo tee -a /etc/portage/package.use/stow
# sudo emerge -a app-admin/stow

Brandon Invergo thumbs its usage up well in this blog.

pass

Additional Info: GnuPG

  • Install pass

    $ echo app-admin/pass importers | sudo tee -a /etc/portage/package.use/pass
    # sudo emerge -a app-admin/pass
    

    Additional supporting programs are also available.

  • Initialize password store

    $ # create GPG key pair if not done so
    $ gpg --full-generate-key    # answer a few basic questions
    $ gpg --list-keys            # check which key to use
    $ pass init <gpg_key_id>
    
  • Optionally, sync with a git repo

    $ pass git init
    $ pass git remote add origin <git-repo>
    $ pass git push -u origin <default-branch>
    

    Also see the Setting it up section of this article for syncing using git.

Hardware detection

  • Dmidecode

    $ echo sys-apps/dmidecode | sudo tee -a /etc/portage/package.use/dmidecode
    $ sudo emerge -a sys-apps/dmidecode
    
  • hwinfo

    $ echo sys-apps/hwinfo | sudo tee -a /etc/portage/package.use/hwinfo
    $ sudo emerge -a sys-apps/hwinfo
    
  • hdparm

    $ echo sys-apps/hdparm | sudo tee -a /etc/portage/package.use/hdparm
    $ sudo emerge -a sys-apps/hdparm
    
  • lshw

    $ echo sys-apps/lshw | sudo tee -a /etc/portage/package.use/lshw
    $ sudo emerge -a sys-apps/lshw
    
  • pciutils

    $ cat << EOF | sudo tee -a /etc/portage/package.use/pciutils
    sys-apps/pciutils
    sys-apps/hwdata
    EOF
    $ sudo emerge -a sys-apps/pciutils sys-apps/hwdata
    
  • usbutils

    $ echo sys-apps/usbutils | sudo tee -a /etc/portage/package.use/usbutils
    $ sudo emerge -a sys-apps/usbutils
    
  • lm-sensors

    $ echo sys-apps/lm-sensors | sudo tee -a /etc/portage/package.use/lm-sensors
    $ sudo emerge -a sys-apps/lm-sensors
    
  • I2C

    $ echo sys-apps/i2c-tools | sudo tee -a /etc/portage/package.use/i2c-tools
    $ sudo emerge -a sys-apps/i2c-tools
    
  • Userspace system daemon to enable security levels for Thunderbolt 3

    $ echo sys-apps/bolt | sudo tee -a /etc/portage/package.use/bolt
    $ sudo emerge -a sys-apps/bolt
    

Power management

See Power management/Guide, Power management, Power management/Processor, and Power management/Soundcard.

To make use of Intel's Linux thermal daemon you first need to emerge sys-power/thermald:

$ echo sys-power/thermald | sudo tee -a /etc/portage/package.use/thermald
$ sudo emerge -a --norep sys-power/thermald
$ sudo systemctl enable --now thermald

Power management daemons

For detailed settings, see the relevant guides here.

  1. TLP is similar to laptop-mode-tools but aims to work out of the box with safe, modern defaults.

    $ echo sys-power/tlp | sudo tee -a /etc/portage/package.use/tlp
    $ sudo emerge -a --norep sys-power/tlp
    # sudo systemctl enable --now tlp
    
  2. Using Laptop Mode Tools

    $ echo app-laptop/laptop-mode-tools | sudo tee -a /etc/portage/package.use/laptop-mode-tools
    $ sudo emerge -a --norep app-laptop/laptop-mode-tools
    $ sudo systemctl enable --now laptop-mode.service
    

    INFO: Users should distinguish laptop_mode, a kernel feature and laptop-mode-tools, a package.

  3. power-profiles-daemon modify system behavior using profiles. There are 3 different power profiles, "balanced", "power-saver", and "performance".

    $ echo sys-power/power-profiles-daemon | sudo tee -a /etc/portage/package.use/power-profiles-daemon
    $ sudo emerge -a --norep sys-power/power-profiles-daemon
    $ sudo systemctl enable --now power-profiles-daemon
    

Window Managers

Pipewire and Wireplumber

Note:

  • Ensure media-sound/pulseaudio-daemon is not installed. This is necessary to avoid issues resulting from running more than one sound server.
  • Ensure media-libs/libpulse is installed, which will allow PipeWire to emulate a PulseAudio sound server. Not many applications currently support PipeWire's native API.
  • Ensure the pulseaudio USE flag is still set globally.

Set pulseaudio globally, set the package USE flags, and emerge the @world set (these new USE flags pull automatically the needed packages):

$ cat << EOF | sudo tee -a /etc/portage/package.use/pipewire
media-video/pipewire echo-cancel extra ffmpeg flatpak man pipewire-alsa sound-server
*/* pulseaudio
EOF
$ sudo emerge -avuDN @world

Now, set the pipewire USE flag globally and re-emerge the @world set. This is due to avoid circular dependency.

Enable services and sockets:

$ systemctl --user enable --now pipewire-pulse.socket
$ systemctl --user enable --now pipewire.service
$ systemctl --user enable --now wireplumber.service

Add the user to the pipewire group:

$ sudo homectl update $USER -G $(groups | sed -e "s/audio//" -e "s/${USER}/pipewire/" -e 's/ \+/,/g')

[!INFO] For the best experience with fast user switching, it is recommended that you remove your user from the audio group.

Bluetooth

See Bluetooth, Bluetooth input devices, and Bluetooth headset.

[!INFO] Bluetooth support can be enabled system-wide by setting the USE variable to bluetooth.

Install BlueZ (should have already been pulled by pipewire) and start the daemon:

$ cat << EOF | sudo tee -a /etc/portage/package.use/bluez
net-wireless/bluez man experimental
*/* bluetooth
EOF
$ sudo emerge -a1N --norep net-wireless/bluez
$ sudo emerge -avuDN @world    # to apply the global bluetooth USE flag
$ sudo systemctl enable --now bluetooth

Enabling battery reporting

  • The experimental USE flag needs to be enabled in net-wireless/bluez.
  • Edit /etc/bluetooth/main.conf to include

    [General]
    
    Experimental=true
    
  • Restart the daemon

    $ sudo systemctl restart bluetooth
    

Device pairing

See Device pairing.

Wake from suspend

See Wake from suspend.

Utilities

$ echo net-wireless/bluez-tools | sudo tee -a /etc/portage/package.use/bluez
$ sudo emerge -a --norep net-wireless/bluez-tools

Nvidia

See NVIDIA and NVIDIA/nvidia-drivers.

Edit /etc/portage/package.use/00-video_cards to include:

*/* VIDEO_CARDS: -* nvidia

Install NVIDIA drivers:

$ echo x11-drivers/nvidia-drivers kernel-open | sudo tee -a /etc/portage/package.use/nvidia-drivers
$ sudo emerge -a --norep x11-drivers/nvidia-drivers

Enable the nvidia USE flag globally and emerge the @world set:

$ echo "*/* nvidia" | sudo tee -a /etc/portage/package.use/nvidia-drivers
$ sudo emerge -avuDN @world

The user(s) needing to access the video card will need to be added to the video group:

$ sudo homectl update $USER -G $(groups | sed -e "s/video//" -e "s/${USER}/video/" -e 's/ \+/,/g')

Note that users will be able to run X without permission to the DRI subsystem, but hardware acceleration will be disabled. For Wayland sessions not setting this may result in a very low FPS.

Optional: Dracut configuration

[!INFO] When using Dracut, it may be worthwhile to ensure that the NVIDIA modules are not bundled in the generated ramdisk (initramfs) image. Otherwise, every update may require regeneration of the image.

Edit /etc/dracut.conf.d/nvidia_omit_modules.conf to include

# Omit the nvidia driver from the ramdisk, to avoid needing to regenerate
# the ramdisk on updates.
omit_drivers+=" nvidia nvidia-drm nvidia-modeset nvidia-uvm "

Optional: PCI-Express Runtime D3 (RTD3) Power Management

Edit /etc/udev/rules.d/80-nvidia-pm.rules to include:

# Enable runtime PM for NVIDIA VGA/3D controller devices on driver bind
ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="auto"
ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="auto"

# Disable runtime PM for NVIDIA VGA/3D controller devices on driver unbind
ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="on"
ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="on"

And edit /etc/modprobe.d/nvidia-pm.conf to include:

# Enable RTD3
options nvidia NVreg_DynamicPowerManagement=0x02

Flatpak

Install flatpak:

$ cat << EOF | sudo tee -a /etc/portage/package.use/flatpak
sys-apps/flatpak
*/* flatpak
EOF
$ sudo sudo emerge -a --norep sys-apps/flatpak

Add the flathub repo:

$ sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo

Install Flatseal:

$ sudo flatpak install com.github.tchx84.Flatseal

Enable flatpak update timer:

$ sudo systemctl enable --now flatpak-update.timer

For theming, see Theming and Flatpak documentiation.

Also, when certain flatpak applications fail to access proper cursor, see this guide.

See below for installing portals.

XDG Desktop Portals

$ cat << EOF | sudo tee -a /etc/portage/package.use/xdg-desktop-portal
gui-libs/xdg-desktop-portal-wlr
sys-apps/xdg-desktop-portal-gtk
EOF
$ sudo emerge -a --norep gui-libs/xdg-desktop-portal-wlr sys-apps/xdg-desktop-portal-gtk

Fonts

A variety of fonts are provided by the media-fonts package category. In particular,

$ echo media-fonts/fonts-meta cjk ms | sudo tee -a /etc/portage/package.use/fonts-meta
$ cat << EOF | sudo tee -a /etc/portage/package.license/fonts-meta
media-fonts/mikachan-font-otf free-noncomm
media-fonts/corefonts MSttfEULA
EOF
$ sudo emerge -a --norep media-fonts/fonts-meta

Google's "Noto" typeface family for fallback (it appears that these are automatically pulled by fonts-meta):

$ cat << EOF | sudo tee -a /etc/portage/package.use/noto
media-fonts/noto
media-fonts/noto-cjk
media-fonts/noto-emoji
EOF
$ sudo emerge -a --norep media-fonts/noto media-fonts/noto-cjk media-fonts/noto-emoji
$ sudo eselect fontconfig enable 75-noto-emoji-fallback.conf

Symbola:

$ echo media-fonts/ttf-ancient-fonts::guru | sudo tee -a /etc/portage/package.{unmask,use}/ttf-ancient-fonts
$ sudo emerge -a --norep media-fonts/ttf-ancient-fonts

Nerd font symbols:

$ echo media-fonts/symbols-nerd-font | sudo tee -a /etc/portage/package.use/symbols-nerd-font
$ sudo emerge -a --norep media-fonts/symbols-nerd-font

Nerd fonts:

$ echo media-fonts/nerdfonts::guru | sudo tee -a /etc/portage/package.unmask/nerdfonts
$ cat << EOF | sudo tee -a /etc/portage/package.use/nerdfonts
media-fonts/nerdfonts::guru 0xproto 3270 adwaitamono agave anonymouspro arimo atkinsonhyperlegiblemono aurulentsansmono bigblueterminal bitstreamverasansmono cascadiacode cascadiamono codenewroman comicshannsmono commitmono cousine d2coding daddytimemono dejavusansmono departuremono droidsansmono envycoder fantasquesansmono firacode firamono geistmono gohu gomono hack hasklig heavydata hermit iawriter ibmplexmono inconsolata inconsolatago inconsolatalgc intelonemono iosevka iosevkaterm iosevkatermslab jetbrainsmono lekton liberationmono lilex martianmono meslo monaspace monofur monoid mononoki mplus noto opendyslexic overpass profont proggyclean recursive robotomono sharetechmono sourcecodepro spacemono terminus tinos ubuntu ubuntumono ubuntusans victormono zedmono
EOF
$ sudo emerge -a --norep media-fonts/nerdfonts
$
$ echo media-fonts/maple-mono::guru | sudo tee -a /etc/portage/package.unmask/maple-mono
$ echo media-fonts/maple-mono::guru ligature nerd | sudo tee -a /etc/portage/package.use/maple-mono
$ sudo emerge -a --norep media-fonts/maple-mono

Terminal emulators

Kitty

$ echo x11-terms/kitty | sudo tee -a /etc/portage/package.use/kitty
$ sudo emerge -a --norep x11-terms/kitty

Alacritty

$ echo x11-terms/alacritty | sudo tee -a /etc/portage/package.use/alacritty
$ sudo emerge -a --norep x11-terms/alacritty

Ghostty

[!TIP] To avoid x11-terms/ghostty pulling in dev-lang/zig as a dependency, emerge dev-lang/zig-bin with the --oneshot option:

$ sudo emerge -a1 --norep dev-lang/zig-bin

$ echo x11-terms/ghostty man | sudo tee -a /etc/portage/package.use/ghostty
$ sudo emerge -a --norep x11-terms/ghostty

Greetd display manager

For TUIGreet,

$ cat << EOF | sudo tee -a /etc/portage/package.use/greetd
gui-libs/greetd man
gui-apps/tuigreet
EOF
$ sudo emerge -a --norep gui-apps/tuigreet
$ sudo systemctl enable greetd

For configuration, edit /etc/greetd/config.toml to include:

[default_session]
...
command = "tuigreet --cmd sway"

For auto-login, include

...
[initial_session]
command = "Hyprland"
user = "<UNAME>"

Sway

$ cat << EOF | sudo tee -a /etc/portage/package.use/sway
gui-wm/sway man swaybar swaynag tray wallpapers
EOF
$ sudo emerge -a --norep gui-wm/sway

autotiling-rs

$ echo gui-apps/autotiling-rs::guru | sudo tee -a /etc/portage/package.{unmask,use}/autotiling-rs
$ sudo emerge -a --norep gui-apps/autotiling-rs

MangoWC

[!NOTE] This is a guide to build from the git version.

Unmask gui-wm/mangowc::guru and gui-libs/scenefx::guru:

$ cat << EOF | sudo tee -a /etc/portage/package.unmask/mangowc
gui-wm/mangowc::guru
gui-libs/scenefx::guru
EOF

Unmask the live ebuilds:

$ cat << EOF | sudo tee -a /etc/portage/package.accept_keywords/mangowc
gui-wm/mangowc::guru **
gui-libs/scenefx::guru **
EOF

Specify the particular git commits:

$ sudo mkdir -p /etc/portage/env/gui-wm
$ echo "EGIT_COMMIT=0.10.9" | sudo tee -a /etc/portage/env/gui-wm/mangowc-9999
$ sudo mkdir -p /etc/portage/env/gui-libs
$ echo "EGIT_COMMIT=0.4.1" | sudo tee -a /etc/portage/env/gui-libs/scenefx-9999

Emerge:

$ echo gui-wm/mangowc::guru | sudo tee -a /etc/portage/package.use/mangowc
$ sudo emerge -a gui-wm/mangowc

See Live ebuilds for updating live packages.

$ sudo emerge -a1 @live-rebuild

Support programs

  • clipman and cliphist

    $ echo gui-apps/clipman::guru | sudo tee -a /etc/portage/package.{unmask,use}/clipman
    $ sudo emerge -a --norep gui-apps/clipman
    $ echo app-misc/cliphist::guru | sudo tee -a /etc/portage/package.{unmask,use}/cliphist
    $ sudo emerge -a --norep app-misc/cliphist
    
  • fuzzel

    $ echo gui-apps/fuzzel::guru | sudo tee -a /etc/portage/package.{unmask,use}/fuzzel
    $ sudo emerge -a --norep gui-apps/fuzzel
    
  • rofi

    $ echo x11-misc/rofi | sudo tee -a /etc/portage/package.use/rofi
    $ sudo emerge -a --norep x11-misc/rofi
    
  • Snapshot tools: grim and slurp

    $ echo gui-apps/grim | sudo tee -a /etc/portage/package.use/grim
    $ sudo emerge -a --norep gui-apps/grim
    $ echo gui-apps/slurp | sudo tee -a /etc/portage/package.use/slurp
    $ sudo emerge -a --norep gui-apps/slurp
    
  • swaync

    $ echo gui-apps/swaync::guru | sudo tee -a /etc/portage/package.{unmask,use}/swaync
    $ sudo emerge -a --norep gui-apps/swaync
    
  • swaybg

    $ echo gui-apps/swaybg gdk-pixbuf | sudo tee -a /etc/portage/package.use/swaybg
    $ sudo emerge -a --norep gui-apps/swaybg
    
  • swayidle

    $ echo gui-apps/swayidle | sudo tee -a /etc/portage/package.use/swayidle
    $ sudo emerge -a --norep gui-apps/swayidle
    
  • swaylock

    $ echo gui-apps/swaylock | sudo tee -a /etc/portage/package.use/swaylock
    $ sudo emerge -a --norep gui-apps/swaylock
    
  • swayosd

    $ echo gui-apps/swayosd::guru | sudo tee -a /etc/portage/package.{unmask,use}/swayosd
    $ sudo emerge -a --norep gui-apps/swayosd
    
  • sway-audio-idle-inhibit

    $ echo gui-apps/sway-audio-idle-inhibit::guru | sudo tee -a /etc/portage/package.{unmask,use}/sway-audio-idle-inhibit
    $ sudo emerge -a --norep gui-apps/sway-audio-idle-inhibit
    
  • waybar

    $ cat << EOF | sudo tee -a /etc/portage/package.use/waybar
    gui-apps/waybar backlight experimental niri tray wifi mpd
    EOF
    $ sudo emerge -a --norep gui-apps/waybar
    
  • wlr-dpms

    $ echo gui-apps/wlr-dpms::guru | sudo tee -a /etc/portage/package.{unmask,use}/wlr-dpms
    $ sudo emerge -a --norep gui-apps/wlr-dpms
    
  • xev

    $ echo x11-apps/xev | sudo tee -a /etc/portage/package.use/xev
    $ sudo emerge -a --norep x11-apps/xev
    
  • xwayland-satellite

    $ echo gui-apps/xwayland-satellite::guru | sudo tee -a /etc/portage/package.{unmask,use}/xwayland-satellite
    $ sudo emerge -a --norep gui-apps/xwayland-satellite
    
  • xdg-user-dirs and xdg-user-dirs-gtk (the latter is pulled by the gtk USE flag)

    See XDG user directories.

    $ echo x11-misc/xdg-user-dirs gtk | sudo tee -a /etc/portage/package.use/xdg-user-dirs
    $ sudo emerge -a --norep x11-misc/xdg-user-dirs
    

    Link the autostart scripts:

    $ mkdir -p ~/.config/autostart
    $ ln -s /etc/xdg/autostart/xdg-user-dirs.desktop ~/.config/autostart/xdg_user_dirs_update.desktop
    $ ln -s /etc/xdg/autostart/xdg-user-dirs-gtk.desktop ~/.config/autostart/xdg_user_dirs_update_gtk.desktop
    

TODOs

[!TODO]: virt-manage and qemu, zsh, Wayland over a network, autofs Pay attention to libvirt guests' graceful shutdown setting (libvirt-guests.service and /etc/libvirt/libvirt-guests.conf)