Alpine Linux on a Laptop How-To

A few notes before we get started, we’re going to partition and install by-hand with minimal help from the installer. This is so you can easily have Alpine co-exist with other operating systems on the same disk, so as long as you have another ef00 and 8e00 partition for it to reside. We won’t be using any bootloader tools either, just the kernel’s built in EFI stub and efibootmgr to tell the UEFI where to look.

Once your done with the main sections you should have an Xfce desktop, from there you can pick and choose the other pieces, if any, that you want.

Boot into the Alpine Linux ISO:

You will probably want to start form the extended image, though this method works just fine from the standard image as well.

# setup-interfaces
# /etc/init.d/networking start
# setup-apkrepos
# apk add cryptsetup lvm2 gptfdisk xfsprogs btrfs-progs efibootmgr

Now let’s partition the disk:

# gdisk /dev/<device>
Command (? for help): n
Partition number (1-128, default 1): 
First sector : 2048
Last sector : +500M
Current type is 8300 (Linux filesystem)
Hex code or GUID (L to show codes, Enter = 8300): ef00
Changed type of partition to 'EFI system partition'

Command (? for help): n
Partition number (2-128, default 2): 
First sector :  <hit enter>
Last sector  :  <hit enter>
Current type is 8300 (Linux filesystem)
Hex code or GUID (L to show codes, Enter = 8300): 8e00
Changed type of partition to 'Linux LVM'
Command (? for help): w

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to /dev/<device>.
The operation has completed successfully.
# 

Where <device> is probably sda or nvme0n1, but double check. Instead of +500M for the uEFI partition, you could also do 1, or 2gb even if you’re going to be sharing /boot with multiple operating systems.

# cryptsetup luksFormat /dev/<device> 

WARNING!
========
This will overwrite data on /dev/<device> irrevocably.

Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/<device>: 
Verify passphrase: 
# 
# cryptsetup luksOpen /dev/<device> crypt
Enter passphrase for /dev/<device>: 
# lvm pvcreate /dev/mapper/crypt
  Physical volume "/dev/mapper/crypt" successfully created.
# lvm vgcreate vg /dev/mapper/crypt
  Volume group "vg" successfully created
# lvm lvcreate vg --name root --size=<size>
  Logical volume "root" created.
# lvm lvcreate vg --name home --size=<size>
  Logical volume "home" created.
# mkfs.xfs /dev/vg/root
# mkfs.xfs /dev/vg/home
# mkfs.vfat -f32 /dev/<device>1

I’ve used xfs above, you can use ext4 or btrfs as well.

Mount the filesystems and install

# modprobe xfs
# mount /dev/vg/root /mnt
# mkdir /mnt/boot /mnt/home
# modprobe vfat
# mount /dev/<device>1 /mnt/boot
# mkdir -p /mnt/etc/apk
# cp -a /etc/apk/keys /mnt/etc/apk/
# cp /etc/apk/repositories /mnt/etc/apk/
# apk -p /mnt --initdb -U add $(cat /etc/apk/world)
# cp /etc/resolv.conf /mnt/etc/
# cd /mnt
# mount -o bind --rbind /dev dev
# mount -o bind --rbind /sys sys
# mount -t proc none proc
# chroot . /bin/ash
# export PS1="(CHROOT)# "
(CHROOT)# apk add linux-lts tzdata
(CHROOT)# apk add wpa_supplicant # If you need wifi
(CHROOT)# grep -E 'xfs|vfat|btrfs|ext4' /proc/mounts >> /etc/fstab
(CHROOT)# echo none /tmp tmpfs rw,defaults 0 0 >> /etc/fstab
(CHROOT)# echo 'auto lo
iface lo inet loopback' > /etc/network/interfaces
(CHROOT)# setup-sshd
(CHROOT)# setup-ntp
(CHROOT)# for svc in devfs dmesg hwdrivers mdev ; do rc-update add $svc sysinit ; done
(CHROOT)# for svc in bootmisc hostname hwclock lvm modules networking \
              seedrng swap sysctl syslog ; do rc-update add $svc boot ; done
(CHROOT)# for svc in acpid crond klogd local ; do rc-update add $svc default ; done
(CHROOT)# sed -i.bak -e's/ext4/ext4 xfs btrfs lvm cryptsetup/' \
            /etc/mkinitfs/mkinitfs.conf
(CHROOT) # mkinitfs $(ls -t /lib/modules | sed 1q) # Note if `ls` is aliased, use `command ls` to avoid a trailing slash
(CHROOT) # efibootmgr -c -p 1 -d /dev/<device>1 -L Alpine -l /vmlinuz-lts \
-u 'initrd=/initramfs-lts cryptroot=/dev/<device>2 cryptdm=crypt root=/dev/vg/root rootfstype=xfs rootflags=rw' \
(CHROOT) # passwd
(CHROOT) # exit
# sync && reboot

You may wish to replace cryptroot=/dev/<device>2 with cryptroot=/dev/disk/by-uuid/<uuid> for that particular partition.

Now, if all goes well, you should be able to reboot into your new Alpine Linux system.

# ln -s /usr/share/zoneinfo/America/New_York /etc/localtime
# adduser <username>
# adduser <username> wheel
# apk add doas
# echo "permit :wheel as root" >> /etc/doas.d/wheel.conf
# setup-xorg-base
# apk add xfce4 lightdm lightdm-gtk-greeter dbus xfce4-terminal \
	xfce4-screensaver firefox openssh git gpg gnupg-scdaemon libfido2 \
	openssh-sk-helper pcsc-lite fido2 pulseaudio pulseaudio-alsa \
    alsa-plugins-pulse pavucontrol pinentry-gnome
# adduser <username> plugdev
# adduser <username> gnupg
# adduser <username> audio
# adduser <username> pulse-access
# addgroup autologin
# adduser <username> autologin
# sed -i.bak 's/^#autologin-user=.*/autologin-user=<username>/' /etc/lightdm/lightdm.conf
# /etc/init.d/lightdm start

Now, if all has gone well you should be greeted with a login screen. Once you’ve logged in the first time, subsequent starts of lightdm will have you automatically logged in. ( Which is fine, considering we have an encrypted root )

From the terminal you can now have the login manager start on boot:

Congrats, you can stop here if you’d like.

$ doas rc-update add lightdm default

(Optional) Switching to the dwm window manager

There are a lot of tutorials on dwm out there, I’m not to make one here, just to simply outline how I compile and set it up on Alpine Linux ( and, the process is very similar on most linux distros )

First let’s install the required packages:

$ doas apk add build-base xorg-server-dev libxft-dev libxinerama-dev

The easiest way to retain automatic login and all of that is to simply add a session to lightdm:

$ git clone https://git.riedstra.dev/x/session
$ cd session
$ make install

Now you have a custom session that can execute your ~/.xinitrc on login, just as if you used startx.

A basic ~/.xinitrc example:

$ echo '#!/bin/sh
# If you'd like, you can source other files here.
# . ~/.kshrc
# Useful if you're running oksh
# export ENV=$HOME/.kshrc
$(eval ssh-agent)
# See https://git.riedstra.dev/x/dmenu for the patch and script
export SSH_ASKPASS=dmenu_askpass
# Add SSH keys if you'd like.
# ssh-add ~/.ssh/keys/<key>
# Optional, compositor. You'll have to install it yourself
picom --config /dev/null &
# Start a program to update the status bar, I just use a short shell
# script with `xsetroot`
status-bar &
# You can use `feh` directly or similar. This is just a shell script that
# sets the wallpaper every 15 seconds, so when I change screen resolutions it
# goes back to normal.
wallpaper ~/.wallpaper.jpg &
# If you want to inherit xfce settings
# xfsettingsd &
# Alternatively you can set the gtk themes and such directly.
# Finally, do not forget that all of the commands above either need to finish
# or fork to the background (`&`) otherwise we don't get here and it appears
# that your login "hangs"
exec dwm' > ~/.xinitrc
$ chmod +x ~/.xinitrc

Finally, fetch and make dwm:

$ git clone https://git.suckless.org/dwm
$ cd dwm
$ doas make clean install

If you’d like my version of dmenu which includes dmenu_askpass for SSH keys:

$ git clone https://git.riedstra.dev/x/dmenu
$ cd dmenu
$ doas make clean install

Now you should be able logout, and login under the custom session and be greeted with dwm.

Some additional packages you may like to add:

$ doas apk add slock xsetroot lm-sensors feh slock st # dmenu, if you didn't install mine

(Optional) System suspend without pm-utils and root/sudo/doas

If you want to be able to quickly suspend, say from dmenu I have a useful c shim for that:

$ doas apk add build-base
$ cd $(mktemp -d)
$ curl https://git.riedstra.dev/mitch/dotfiles/plain/bin/zzz.c > zzz.c
$ # This may be where you wish to read the file you just downloaded :-)
$ cc -o zzz zzz.c
$ export dest=/sbin/zzz
$ doas sh -c "cp zzz $dest && chown root:wheel $dest && chmod 6750 $dest"

Once you’re done, zzz should work to suspend the system for any user in the wheel group

If you’re interested, you could also clone down the entire git repo and use the makefile in bin/

(Optional) Backlight control without root/sudo/doas

In a similar vein to the zzz program:

$ doas apk add build-base
$ cd $(mktemp -d)
$ curl https://git.riedstra.dev/mitch/dotfiles/plain/bin/backlight.c > backlight.c
$ # This may be where you wish to read the file you just downloaded :-)
$ cc -o backlight backlight.c
$ export dest=/sbin/backlight
$ doas sh -c "cp backlight $dest && chown root:wheel $dest && chmod 6750 $dest"

Once you’re done, backlight <percentage> should work to suspend the system for any user in the wheel group.

It simply tries to deciver backlights in /sys/class/backlight, out of the box this should work on most intel and amd systems.

If you’re interested, you could also clone down the entire git repo and use the makefile in bin/

(Optional) Bluetooth

# apk add bluez bluez-alsa
# rc-update add bluetooth default
# /etc/init.d/bluetooth start

From there interacting with bluetooth is pretty straightforward:

# bluetoothctl
[bluetooth]# scan on
[bluetooth]# pair <MACADDR>
[bluetooth]# connect <MACADDR>
[bluetooth]# scan off
[bluetooth]# <c-d>
#

Optionally you can also issue a trust <MACADDR>, useful for keyboards and such.

Bluetooth audio should work out of the box.

(Optional) Enable Network Manager

From just about any point in our install process you can install NetworkManager if you’re having troubple with manual network configuration

# apk add networkmanager networkmanager-tui
# /etc/init.d/networking stop
# /etc/init.d/networkmanager start
# nmtui
# rc-update del networking boot
# rc-update add networkmanager boot

(Optional) Disable SSH

If you’re not going to be logging into your system remotely, you might as well disable the SSH service.

# rc-update del sshd default

(Optional) Setup WiFi with wpa_supplicant

Create a configuration file:

network={
	ssid="<your network name>"
	scan_ssid=1
	key_mgmt=WPA-PSK
	psk="<your network password>"
}

Then run:

# wpa_supplicant -i wlan0 -c /path/to/conf &

That should spit out a few messages, and return you to your terminal.

From there, get a DHCP lease:

# udhcpc -i wlan0

Now you should be hooked up to the wifi by-hand for now.