search icon

My quality of life improvements to Arch Linux

· 23 min read
Arch Linux
Feels good man facing a Yakuake terminal window

Introduction

The first time I distractedly tried Ubuntu in 2012, I thought it had a terrible user experience and stopped using it after a few minutes. My impression of Raspbian in 2015 was just slightly better, and typing commands in the terminal still felt super clunky.

However, when I went back to Raspbian in 2019, I saw for the first time the colorful syntax highlighting in the nano text editor, and understood that there are plenty of changes that can make using the terminal much more enjoyable.

After using Arch Linux extensively as my daily laptop and server OS, I want to share the little customization that I apply to every fresh installation to improve my user experience.

I run these commands as the “root” user unless otherwise specified.

Prerequisites

An existing Linux installation. Apart from the Packages section, these changes can be applied to other Linux distributions like Ubuntu and Raspberry Pi OS (remember to replace pacman and yay with apt).

To run commands, we need a terminal interface. This can be an SSH client (when the Linux device is remote), a desktop app like Konsole, or a Linux TTY.

To edit configuration files, we need a text editor like micro or nano, that can be installed by running:

pacman -S micro

From micro, you can press Alt + g to show the main keybindings.

System

Set the keyboard layout

I use an Italian keyboard, so I need Linux to map keys correctly:

localectl set-keymap --no-convert "it"
localectl

Set the hostname

Fantasy is the limit!

hostnamectl hostname "MyServer"
hostnamectl

Since Arch Linux in WSL is not using systemd, I manually edit the hostname file:

echo "MyServer" > /etc/hostname

Set the time zone

I first set the right timezone, and then I sync the time via the Internet:

timedatectl set-timezone "Europe/Rome"
timedatectl set-ntp true
timedatectl

If a device has a hardware clock, I also run:

hwclock --systohc

Set the locale and language

I first open the file containing the locale settings to be generated:

micro /etc/locale.gen

Then I add at the beginning the languages I need:

/etc/locale.gen
en_US.UTF-8 UTF-8
it_IT.UTF-8 UTF-8
en_DK.UTF-8 UTF-8
 
# ...

Then I generate locale settings through this command:

locale-gen

Now that they are generated, I can use them:

localectl set-locale "en_US.UTF-8"

For further configurations, I can set additional variables like “LC_TIME” in /etc/locale.conf:

/etc/locale.conf
LANG=en_US.UTF-8
LC_TIME=en_DK.UTF-8

Add a swap file

If a device has only few GBs of RAM, I create a swap file to avoid out-of-memory issues:

swapoff --all || true
swapSize="$(free | awk '/Mem:/ {print $2}')"
dd if=/dev/zero bs=1k count="$swapSize" of=/swapfile
chmod 600 /swapfile
mkswap /swapfile

Then, I enable the swap file and check that it is available:

swapon /swapfile
free

Finally, I append a new line to the file system tab:

/etc/fstab
# ...
 
/swapfile   none       swap    defaults    0   0

Add hosts aliases

I often reference other devices with their hostnames rather than their IP addresses:

micro /etc/hosts
/etc/hosts
# local 
127.0.0.1       localhost MyPC
  
# lan
192.168.1.1     MyRouter
192.168.1.5     MyPrinter
192.168.1.10    MyServer
192.168.1.20    MyPC
 
# vpn  
10.10.10.10     MyServerVPN
10.10.10.20     MyPCVPN
 
# external
1.1.1.1         cf

Now I can run ssh MyServer or ping cf instead of entering IP addresses by hand.

Install a firewall

In the case of a server, a firewall is another security layer that sits between applications and external clients. I install nftables also on my laptop, to prevent accidentaly exposing services to other devices in a public network.

pacman -S iptables-nft
systemctl enable --now nftables

By default, only ping and ssh on port 22 will work out-of-the-box, so new rules must be added to expose services to external clients.

Users

Change the root password

It’s as easy as running:

passwd root

This will change the password hash stored in /etc/shadow

Change the default user name

Arch Linux for ARM devices comes with a default “alarm” user and group with ID 1000, which I rename to “jack”:

groupmod --new-name jack alarm
usermod --login jack alarm
usermod --move-home --home /home/jack jack
passwd jack
id jack

Create a new user

Arch Linux for x86_64 devices doesn’t have any user other than “root”, so I create “jack” from scratch:

groupadd --gid 1000 jack
useradd --create-home --uid 1000 --gid 1000 --groups wheel --shell /bin/bash jack
passwd jack
id jack

The “wheel” group above grants additional privileges, e.g. running sudo.

Grant elevated privileges to normal users

If I need to make system-wide changes, there are two main options:

  • log in as the root user and run commands normally
  • log in as an unprivileged user, and prepend commands with sudo

By default, only users in the group wheel will be allowed to run sudo.

I first install sudo and modify its configuration:

pacman -S sudo
EDITOR=micro visudo

I append this at the end:

/etc/sudoers
# ...
 
Defaults timestamp_timeout=30
Defaults !tty_tickets
Defaults pwfeedback
Defaults editor=/usr/bin/micro
%wheel ALL=(ALL) ALL

These settings will require re-entering the user password just once every 30 minutes from the last time sudo was run.

Packages

Improvements to Pacman

To enable parallel downloads, show colored output and progress bar in pacman, I edit its configuration by running:

sed -i "/etc/pacman.conf" \
    -e "s|^#Color|&\nColor\nILoveCandy|" \
    -e "s|^#VerbosePkgLists|&\nVerbosePkgLists|" \
    -e "s|^#ParallelDownloads.*|&\nParallelDownloads = 20|"

Improvements to Makepkg

When compiling packages from scratch, I store source files in /tmp/makepkg/ and use parallel compilation whenever possible:

sed -i "/etc/makepkg.conf" \
    -e "s|^#BUILDDIR=.*|&\nBUILDDIR=/tmp/makepkg|" \
    -e "s|^PKGEXT.*|PKGEXT='.pkg.tar'|" \
    -e "s|^OPTIONS=.*|#&\nOPTIONS=(docs \!strip \!libtool \!staticlibs emptydirs zipman purge \!debug lto)|" \
    -e "s|-march=.* -mtune=generic|-march=native|" \
    -e "s|^#RUSTFLAGS=.*|&\nRUSTFLAGS=\"-C opt-level=2 -C target-cpu=native\"|" \
    -e "s|^#MAKEFLAGS=.*|&\nMAKEFLAGS=\"-j$(($(nproc --all)-1))\"|"

Install Yay

I use yay to install any package from the AUR and from the official repositories.

yay is not available in the official Arch Linux repositories, I guess to highlight that AUR packages are community-maintained, meaning that anyone can upload malware there.

For this reason, before installing a new AUR package, I suggest to check

  • if the source inside .SRCINFO is a link to the official repository or website of the package
  • if there are no suspicious commands inside the PKGBUILD and eventual .install files

To install AUR packages like yay, I first need some utilities:

pacman -S git base-devel

As an unprivileged user (e.g. jack), I then run:

git clone https://aur.archlinux.org/yay-bin.git
cd yay-bin
makepkg
sudo pacman -U yay-bin*
yay -V
cd ../
rm -r yay-bin

From now on, I run yay as jack instead of pacman as root:

# install a package from the repos or AUR
yay -S package
 
# update the system (including AUR packages)
yay -Syu

Clean package cache automatically

This periodic timer will clean pacman cache once every week:

systemctl enable --now paccache.timer

To also delete yay cache, I run

yay -Scc

Shell

Install ZSH

Instead of bash, I use ZSH and some plugins:

yay -S zsh zsh-completions

I configure my ZSH by creating $HOME/.zshrc as my jack user:

micro $HOME/.zshrc

Then, I write inside:

/home/jack/.zshrc
# ################################ VARIABLES
# ################ GLOBAL
 
# default text editor for sudo, git
export EDITOR=micro
 
# ################ ZSH-RELATED
 
# list of past commands that can be run with up or down arrows
HISTFILE="$HOME/.zsh_history"
# entries shown with history command
HISTSIZE=100000
# entries saved
SAVEHIST=100000
# define word delimiters
WORDCHARS=${WORDCHARS//\/[&.;]}
# define `time` output
TIMEFMT=$'%E'
 
# ################################ OPTIONS
 
# load autocompletion and colors
autoload -Uz compinit colors promptinit
# allow completions and its cache
compinit
# allow prompt customizations
promptinit
# enable loaded colors
colors
# enable aliases
setopt aliases
# prevent the shell from triggering the pc speaker
setopt nobeep
# keep track of commands
setopt appendhistory
# don't put commands starting with space in history
setopt HIST_IGNORE_SPACE
# allow to customize the prompt
setopt prompt_subst
# change folder by typing nits name
setopt autocd
# allow comments in interactive mode
setopt interactivecomments
# report status of background jobs
setopt notify
# sort files numerically when it makes sense
setopt numericglobsort
# delete history duplicates before older entries
setopt hist_expire_dups_first
# show commands typed in other terminal sessions
setopt share_history
 
# ################################ COMPLETIONS
 
# case-insensitive tab completion
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}'
# colored completion (different colors for dirs/files/etc)
zstyle ':completion:*' list-colors "${(s.:.)LS_COLORS}"
# automatically find new executables in path
zstyle ':completion:*' rehash true
 
# ################################ GIT
 
# show git branch in prompt
git_branch(){
 branch=$(git symbolic-ref HEAD 2> /dev/null | awk '{gsub(/refs\/heads\//, ""); print}')
 if [[ $branch != "" ]]; then
  echo " ($branch)"
 fi
}
 
# ################################ PROMPT
 
_color1=green
_color2=white
 
PS1='%B%F{$_color1}%(4~|%-1~/.../%2~|%~)%u%b$(git_branch) %F{$_color2}${(C)USER}@${HOST/*-/}%F{$_color1}|>%f%b '
PS2='%B%F{$_color1}%_|>%f%b '

I try the new shell to see if it works as expected:

zsh

Once everything works fine, I change the default shell for my jack user:

chsh jack -s "/usr/bin/zsh"

Custom commands

Sometimes I create short aliases and functions for commands I run often, or for which I don’t want to type all the flags every time. I store them in a separate file, that I reference in my .zshrc file:

/home/jack/.zshrc
# ...
 
# ################################ ALIASES AND FUNCTIONS
 
aliasFile="$HOME/.zsh_aliases.sh"
[[ -e $aliasFile ]] && source "$aliasFile"

Then I add my ZSH aliases and functions here:

micro $HOME/.zsh_aliases.sh

For instance:

/home/jack/.zsh_aliases.sh
# ################################ ALIASES
 
# place this sudo entry before other aliases
alias "sudo=sudo -E "
 
alias "h=history"
alias "hg=history | grep"
alias "ip=ip -c"
alias "lol=ls -lah --color=tty"
alias "m=micro"
alias "n=nano"
# outputs file content after stripping comments 
alias "nocomment=grep -Ev '^(\s*|\\\t*)?(#|;|!|%|\\\/\\\/|$)'"
alias "server=mosh MyServer -- tmux"
alias "myrsync=rsync -azzvhP"
alias "sn=sudo nano"
 
alias "ssa=sudo systemctl start"
alias "ssd=sudo systemctl disable"
alias "ssdr=sudo systemctl daemon-reload"
alias "sse=sudo systemctl enable --now"
alias "ssl=sudo systemctl reload"
alias "sso=sudo systemctl stop"
alias "ssr=sudo systemctl restart"
alias "sss=sudo systemctl status"
 
alias "sua=systemctl --user start"
alias "sud=systemctl --user disable"
alias "sudr=systemctl --user daemon-reload"
alias "sue=systemctl --user enable --now"
alias "sul=systemctl --user reload"
alias "suo=systemctl --user stop"
alias "sur=systemctl --user restart"
alias "sus=systemctl --user status"
alias "update=yay -Syu --noconfirm"
 
# ################################ FUNCTIONS
# these are zsh functions, and may be incompatible with bash ones
 
# journal logs since boot
jb(){
 journalctl -a -b -u "$1" | less +G
}
 
jub(){
 journalctl --user -a -b -u "$1" | less +G
}
 
# follow journal logs
jf(){
 journalctl -a -f -u "$1"
}
 
juf(){
 journalctl --user -a -f -u "$1"
}
 
# delete a remote file from the server
server-remove(){
 ssh MyServer "rm $1"
}
 
# download a remote file from the server to the current directory
server-retrieve(){
 myrsync "MyServer:$1" .
}
 
# send a local file to the server, placing it under /home/jack
server-send(){
 myrsync "$1" "MyServer:/$HOME"
}
 
# show different stuff, e.g. 'show timers'
show(){
 case "$1" in
  "ip") curl -s https://icanhazip.com/ ;;
  "lanip") ip route get 1 | awk '/src/ {print $7}' ;;
  "timers") systemctl list-timers --all --no-pager ;;
 esac

Third-party ZSH plugins

I use antidote to manage my ZSH plugins. First, I install it from the AUR:

yay -S zsh-antidote

On my ZSH configuration file, I add a new section:

/home/jack/.zshrc
# ...
 
# ################################ PLUGINS
 
zsh_plugins="$HOME/.zsh_antidote"
if [[ ! -d "$HOME/.cache/antidote" ]] || [[ ! -f "${zsh_plugins}.zsh" ]] || [[ "${zsh_plugins}.txt" -nt "${zsh_plugins}.zsh" ]]; then
 (
  source "/usr/share/zsh-antidote/antidote.zsh"
  antidote bundle < "${zsh_plugins}.txt" > "${zsh_plugins}.zsh"
 )
fi
source "${zsh_plugins}.zsh"

I add my ZSH plugins to $HOME/.zsh_antidote.txt:

/home/jack/.zsh_antidote.txt
ohmyzsh/ohmyzsh path:lib
ohmyzsh/ohmyzsh path:plugins/extract
zsh-users/zsh-autosuggestions kind:defer
zsh-users/zsh-history-substring-search kind:defer

The next time I launch a shell window, antidote will download and activate these plugins.

Different prompt colors for root

I like to use a red interface when running commands as the root user.

As the root user, I copy my ZSH configuration file into root’s home directory:

cp /home/jack/.zshrc /root/.zshrc

And then I adjust the colors in the prompt session:

/root/.zshrc
# ...
 
# ################################ PROMPT
 
_color1=yellow
_color2=red
 
# ...
ZSH prompts for jack and root
ZSH prompts for jack and root

Physical CLI access

If I need to access a device through a Linux TTY (e.g. a problematic Raspberry Pi or laptop), I like to use a larger font, set the right keyboard layout, enable mouse commands, and disable the annoying beep sound.

Set font and keyboard layout

I first install Terminus Font:

yay -S terminus-font 

Then I create the file “/etc/vconsole.conf” and select the appropriate keyboard layout:

/etc/vconsole.conf
FONT=ter-128b
KEYMAP=it
XKBLAYOUT=it
XKBMODEL=pc105

Enable mouse

With gpm, the mouse cursor will appear as a white rectangle instead of the typical arrow, but it can still be helpful to copy and paste:

yay -S gpm
sudo systemctl enable --now gpm

Silence the beep sound

I find the beep sound annoying, so I silence it as soon as I can:

rmmod pcspkr
echo "blacklist pcspkr" > "/etc/modprobe.d/no-beep-sound.conf"

Remote CLI access

If I need to access a machine servers remotely, I always enable key authentication, set up measures against connection drop, and add a welcome message.

Use SSH keys instead of passwords

Client: generate a key pair

On my local PC, I generate an SSH key pair, adding a comment to specify the device I connect from:

ssh-keygen -t ed25519 -C "jack@Jack-PC"

The keys can be accessed by running ls -la $HOME/.ssh/:

4.0K -rw------- 1 jack jack  444 Apr  5 15:58 id_ed25519
4.0K -rw------- 1 jack jack   94 Apr  5 15:58 id_ed25519.pub

The file ending in “.pub” is the public key that must be sent to the server, while the one without extension is the private key that must not be shared.

To content of the public key can be shown with cat $HOME/.ssh/id_ed25519.pub:

/home/jack/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK0wmN/Cr3JXqmLW7u+g9pTh+wyqDHpSQEIQczXkVx3d jack@Jack-PC

I login to my server using the usual password (for the last time):

ssh jack@192.168.1.10

Server: authorize the public key

As my unprivileged user (jack), I create on the server the file that contains my public keys:

mkdir -p $HOME/.ssh
micro $HOME/.ssh/authorized_keys

I copy inside the content of the public key id_ed25519.pub:

/home/jack/.ssh/authorized_keys
# one public key per line
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK0wmN/Cr3JXqmLW7u+g9pTh+wyqDHpSQEIQczXkVx3d jack@Jack-PC

Key authentication should already be enabled on the server, you can check by running:

grep "PubkeyAuthentication" /etc/ssh/sshd_config

Client: check connectivity

I add my private key to my SSH client or SSH agent, depending on the client OS.

I open a new connection while keeping the previous one alive:

ssh -v jack@192.168.1.10

It should not prompt me anymore for my password, as the client is using the private key to authenticate.

Server: disable password login

Now I can tell the server to accept SSH keys as the only authentication method, by disabling password authentication.

On the server, I run:

micro /etc/ssh/sshd_config

Then, I add this setting or change it to “no”:

/etc/ssh/sshd_config
# ...
 
PasswordAuthentication no

Client: backup the key pair

I highly recommend making a backup of your SSH private key, as you risk locking yourself out of the server if something happens to your client and you don’t have physical access.

If you lose your public key, you can still derive it from the private one by running:

ssh-keygen -y -f $HOME/.ssh/id_ed25519 > $HOME/.ssh/id_ed25519.pub

Persist session even if the connection drops

Imagine running a remote system update through SSH, and suddenly the Wi-Fi / 5G network disconnects, or your laptop battery dies. You cannot resume an SSH session, so you risk missing important warnings from the update command.

Luckily, MoSh makes SSH connection persistent to network disconnections, and Tmux makes commands run in dedicated sessions, so I can easily resume in case of a client issue.

On the server, I install these programs with

yay -S mosh tmux

On the client, I access via mosh and tmux instead of ssh:

mosh jack@192.168.1.10 -- tmux

If you have a firewall on your server, be aware that MoSh uses UDP ports 60000-61000 for incoming connections, so they should be open.

Tmux key shortcuts can be daunting at first, so take a look at the Tmux cheatsheet.

I eventually adjusted my tmux configuration by creating $HOME/.tmux.conf:

/home/jack/.tmux.conf
# split the screen vertically and horizontally with vertical bar and dash symbols  
bind | split-window -h  
bind - split-window -v  
  
# use more colors  
set -g terminal-overrides 'xterm*:smcup@:rmcup@'  
set -g default-terminal "screen-256color"
 
# toggle mouse on (scrollable with wheel) with uppercase M  
bind-key M \  
 set -g mouse on \; \  
 display "Mouse: ON"  
  
# toggle mouse off (select text with cursor) with lowercase M  
bind-key m \  
 set -g mouse off \; \  
 display "Mouse: OFF"

Custom welcome message

I like to give a bit of personality to each server with an ASCII art banner, that I create as /etc/profile.d/banner.sh:

/etc/profile.d/banner.sh
cat <<BANNER  
  
       ██╗ █████╗  ██████╗██╗  ██╗     ██████╗ ██████╗  
       ██║██╔══██╗██╔════╝██║ ██╔╝     ██╔══██╗╚════██╗  
       ██║███████║██║     █████╔╝█████╗██████╔╝ █████╔╝  
  ██   ██║██╔══██║██║     ██╔═██╗╚════╝██╔═══╝ ██╔═══╝  
  ╚█████╔╝██║  ██║╚██████╗██║  ██╗     ██║     ███████╗  
   ╚════╝ ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝     ╚═╝     ╚══════╝  
  
  Ciao! $HOST has been $(uptime --pretty)  
  
BANNER

Every time I connect remotely, I am greeted by this:

My Raspberry Pi 2 welcome message
My Raspberry Pi 2 welcome message

Edit files with Nano

While I found micro to have syntax highlighting and modern keybindings out of the box, I relied heavily on my customized nano in the past years.

Highlight syntax

First of all, I install nano with the syntax highlighting plugin:

yay -S nano nano-syntax-highlighting

Then, I fix a little bug that has been there for years:

sed -i "/usr/share/nano-syntax-highlighting/nanorc.nanorc" \
    -e 's|^icolor brightnormal " brightnormal"|icolor normal " normal"|'

Then I modify the global configuration file to enable syntax highlighting for all users (jack and root):

mv /etc/nanorc /etc/nanorc.old
nano /etc/nanorc

I write inside:

/etc/nanorc
include "/usr/share/nano/*.nanorc"
include "/usr/share/nano-syntax-highlighting/*.nanorc"
 
set autoindent
set constantshow
set minibar
set multibuffer
set positionlog
set smarthome
set softwrap
set stateflags
set tabsize 4
set tabstospaces
 
bind ^Z undo main
bind ^Y redo main
bind ^R cancel yesno
bind ^X cut all
bind ^C copy all
bind ^V paste all
bind ^Q exit all
bind ^F whereis all
bind ^G findnext all
bind ^B wherewas all
bind ^D findprevious all
bind ^R replace main
bind ^S writeout main
bind ^O insert main
bind M-X flipnewbuffer all
bind ^T gotoline main
bind ^T gotodir browser
bind ^L speller main
unbind M-J main
unbind M-T main
unbind ^V all
unbind ^K all
unbind ^U all

Then, I check that the keybindings and syntax highlighting work well:

nano

Different colors for root

I like to use a red interface when editing files as the root user.

To do so, I created a configuration file for root:

nano /root/.nanorc

And I write inside:

/root/.nanorc
set errorcolor black,red  
set functioncolor yellow  
set keycolor black,yellow  
set numbercolor yellow  
set promptcolor black,yellow  
set scrollercolor yellow  
set selectedcolor black,red  
set statuscolor black,yellow  
set stripecolor yellow  
set titlecolor black,yellow
Nano interfaces for jack and root
Nano interfaces for jack and root

Other helpful commands

I use pkgfile to find which package I should install before I can run a certain command:

yay -S pkgfile
sudo pkgfile --update
 
# which package provides the 'drill' command?
pkgfile drill

I use tldr to quickly understand how to use a certain command:

yay -S tealdeer
tldr --update
 
# how to extract a tar archive?
tldr tar

Wrap up

This list is long, perhaps too much. It would be unwise for a beginner to blindly apply all of these changes before even getting familiar with a Linux system.

I invite you to start from the things you find the most annoying or repetitive and find a way to fix or automate them. The Wiki and the Forum are great places to find resource and support.

You’ll be satisfied with the result earlier than you think, and your future self will be thankful for improving your setup without spending too much time doing that.

Did you enjoy the article?

heart icon

Discuss it with others:

Other posts for you

Pingu the archer shooting at a laptop
Pingu the archer shooting at a Raspberry Pi