Eric Radman : a Journal

Automated Alpine Linux Installation

Alpine Linux is a minimal distribution that also includes the most essential Unix utilities. The versatility and consistent design of this distribution has made it successful. Alpine also has some significant limitations, although most problems can be solved by applying custom OpenRC scripts.

The Alpine Linux answer file is sourced as a shell script, so it is able to write files. In practice this is difficult to take advantage of because the new rootfs is not yet mounted, and it is unmounted after setup-alpine completes. This leaves several missing features:

  1. No means of setting root or user passwords
  2. No means of modifying doas configuration.
  3. No syntax for specifying LVM mount points

These limitations can be overcome by patching the MFS root.

PXE boot

If TFTP and HTTP are used, unpack the netboot files in two locations

dist in "/tftpboot/alpine" "/var/www/htdocs/pub/alpine"; do
    mkdir $dist
    cd $dist
    tar -zxvf /var/www/htdocs/iso/alpine-netboot-3.23.3-x86_64.tar.gz
    chmod o+r boot/initramfs-*
done

iPXE configuration

#!ipxe

set tftp_url tftp://192.168.0.2/alpine
set http_url http://192.168.0.2/pub/alpine
set alpine_repo https://dl-cdn.alpinelinux.org/alpine/v3.23/main

kernel ${tftp_url}/boot/vmlinuz-lts initrd=initramfs-lts modules=loop,squashfs quiet alpine_repo=${alpine_repo} modloop=${http_url}/boot/modloop-lts
initrd ${tftp_url}/boot/initramfs-lts

Unattended Install

Alpine has a built-in local backup utility which creates a tar.gz file that can be loaded at boot. This is somewhat similar to OpenBSD's siteNN.tgz file, except it is applied early in the ramdisk boot, allowing us to add functionality to the initramfs. To use this feature, add apkovl= to the boot line

#!ipxe

set tftp_base tftp://192.168.0.2/alpine3.23
set http_base http://192.168.0.2/pub/alpine3.23
set alpine_repo https://dl-cdn.alpinelinux.org/alpine/v3.23/main
set overlay http://192.168.0.2/alpine/autoinstall.apkovl.tar.gz

kernel ${tftp_base}/boot/vmlinuz-lts initrd=initramfs-lts modules=loop,squashfs quiet alpine_repo=${alpine_repo} modloop=${http_base}/boot/modloop-lts apkovl=${overlay}
initrd ${tftp_base}/boot/initramfs-lts

The autoinstall overlay will have this structure

.
`-- etc
    |-- fstab
    |-- init.d
    |   `-- firstboot
    `-- runlevels
        `-- default
            `-- modloop -> /etc/init.d/modloop
#!/sbin/openrc-run

start() {
    local rc=0
    mac=$(ifconfig eth0 | awk '/HWaddr/ { print $NF }')
    setup-apkrepos -1
    apk add tzdata
    # prevent repeat DHCP requests
    pkill udhcpc
    printf "auto lo\n iface lo inet loopback\n" > /etc/network/interfaces
    # inject post-setup script
    sed -i -e '/cleanup_chroot_mounts /i\\tsh -x /tmp/chroot-final.sh' /usr/sbin/setup-disk
    # run setup
    setup-alpine -ef "http://192.168.0.2/alpine/${mac}.answers"
    rc=$?
    if [ $rc -eq 0 ]; then
        echo "Rebooting in 10 seconds"
        sleep 10
        reboot
    fi
    eend $rc
}

Create this overlay using relative base paths

cd overlay
tar -czf /var/www/htdocs/alpine/autoinstall.apkovl.tar.gz *

Answers File

HOSTNAMEOPTS="alpine"
KEYMAPOPTS=none
DEVDOPTS=mdev
TIMEZONEOPTS="-z US/Eastern"
PROXYOPTS=none
APKREPOSOPTS="-1"
SSHDOPTS="openssh"
NTPOPTS="openntpd"

USE_EFI=1
DISKOPTS="-v -L -m sys /dev/nvme0n1"

USEROPTS="-a -u -g audio,video,netdev eradman"
USERSSHKEY="ssh-ed25519 AAAAC3N ..."

DNSOPTS="-d lan -n 192.168.2.3"
INTERFACESOPTS="auto lo
iface lo inet loopback

auto eth0
iface eth0 inet6 manual
    address 192.168.2.21/24
    gateway 192.168.2.7

    address fd00:52::15/64
    gateway fd00:52::7

    hostname alpine
"

export ERASE_DISKS="/dev/nvme0n1"
export ROOT_SIZE="7000"  # about 30GB

cat > /tmp/chroot-final.sh <<'EOF'
#!/bin/sh
echo 'root:$5$47Z1ja56Y2ltHKyx$sw/oGtkvLP81S/dfC4XIwX9TAfGh1zEnT4yOILEE.gB' | chpasswd -e -R /mnt
echo 'eradman:$5$47Z1ja56Y2ltHKyx$sw/oGtkvLP81S/dfC4XIwX9TAfGh1zEnT4yOILEE.gB' | chpasswd -e -R /mnt
echo 'permit nopass :wheel' > /mnt/etc/doas.d/20-wheel.conf
EOF

By using LVM and limiting the root size additional mount points can be created after the base install completes. Confusingly, the size is the number of LVM extents, which is 4MB by default.

Generate password hashes using printf '******' | mkpasswd -m sha256.