This repository contains instructions and scripts used to install and setup an Alpine Linux minimal root filesystem on WSL2.
Alpine Linux is an independent, non-commercial, general purpose Linux distribution. Alpine Linux is built around musl libc and BusyBox. This makes it small and very resource efficient. A container requires no more than 8 MB and a minimal installation to disk requires around 130 MB of storage.
This setup is used mainly for Python development on WSL, but is also used for front-end projects and embedded projects using MicroPython and C on Raspberry Pi devices, which must be attached to WSL using usbipd-win.
OpenRC is the init system used in alpine. The init system manages the services, such as mdev, which is the default device manager on Alpine Linux. It is provided by busybox as alternative to systemd's udev. This setup allows mdev to act as a hotplug manager and facilitates access to USB and COM devices when attached to WSL distributions, by dynamically updating /dev
based on your mdev config rules.
I prefer to use Windows Terminal to manage my profiles for PowerShell and WSL distributions.
Note
mdev is not recommended for a full blown desktop environment, but it is perfect for a minimal WSL install.
The minimal root filesystem can be downloaded from Alpine Linux Downloads. I create a WSL directory and a subdirectory for each distribution (e.g. C:\WSL\Alpine
), where I keep my distribution images and where I have WSL create the vhdx image files.
On an administrator PowerShell, Alpine can be installed using the following commands:
wsl --import Alpine C:\WSL\Alpine C:\WSL\Alpine\alpine-minirootfs-3.20.1-x86_64.tar
Check installation by listing distributions with verbose output:
wsl -l -v
I use a profile within Windows Terminal (Microsoft Store), which is set to run Alpine with the following command C:\windows\system32\wsl.exe -d Alpine
. See here Alpine Linux post-install guide for a useful post-install guide.
New user setup - replace with your username:
adduser -h /home/username -s /bin/ash username
su -l root
This setup uses doas rather than sudo and is more minimal:
apk update && apk upgrade
apk add doas
Add the new user to the wheel group:
echo 'permit :wheel' > /etc/doas.d/doas.conf
adduser username wheel
Microcontrollers attached to WSL are often placed in the dialout group by mdev rules (/dev/ttyACM[0-9]). The command below adds the user to the dialout group to facilitate connecting to these devices (if needed):
adduser username dialout
In Alpine, the doas.conf file is at the path /etc/doas.d/
:
su -l username
doas vi /etc/doas.d/doas.conf
Below is an example for /etc/doas.d/doas.conf
settings, which allow a user to perform
apk update and apk upgrade as root without a password:
# permit apk update & apk upgrade without password for user <username>
permit nopass username as root cmd apk args update
permit nopass username as root cmd apk args upgrade
Test doas config settings:
doas apk update && doas apk upgrade
Below details installation & setup of OpenRC with mdev and hwdrivers on WSL2. mdev and OpenRC can be installed using apk:
doas apk add busybox-mdev-openrc
Enable mdev & hwdrivers services at the sysinit runlevel:
doas rc-update add mdev sysinit
doas rc-update add hwdrivers sysinit
Check sysinit runlevel services:
rc-status sysinit
Start mdev & hwdrivers, if currently stopped:
doas rc-service mdev --ifstopped start
doas rc-service hwdrivers --ifstopped start
Manually seed /dev
with device nodes based on current mdev config file:
doas mdev -s
View changes:
ls -la /dev
Below is my init file for mdev at /etc/init.d/mdev
, which I modified to run mdev as a daemon, which allowed me to
have mdev run as a hotplug manager similar to udev. Whenever I plug in a Raspberry Pi device and share and attach it
to WSL, I want mdev to respond to a uevent and parse the /etc/mdev.conf
looking for matching rules for the attached
device, which is usually ttyACM[0-9]. The modified line is mdev -d
in the _start_service
function, which replaced
mdev -s
.
#!/sbin/openrc-run
description="the mdev device manager"
depend() {
provide dev
need sysfs dev-mount
before checkfs fsck
keyword -containers -vserver -lxc
}
_start_service () {
ebegin "Starting busybox mdev"
mkdir -p /dev
echo >/dev/mdev.seq
echo "/sbin/mdev" > /proc/sys/kernel/hotplug
eend $?
}
_start_coldplug () {
ebegin "Scanning hardware for mdev"
# mdev -s will not create /dev/usb[1-9] devices with recent kernels
# so we manually trigger events for usb
for i in $(find /sys/devices -name 'usb[0-9]*'); do
[ -e $i/uevent ] && echo add > $i/uevent
done
# trigger the rest of the coldplug
# mdev -s is replaced with mdev -d to have it run as a daemon
mdev -d
eend $?
}
start() {
_start_service
_start_coldplug
}
stop() {
ebegin "Stopping busybox mdev"
echo > /proc/sys/kernel/hotplug
eend
}
Below is the mdev rule in the /etc/mdev.conf
file which is relevant to RPi Microcontrollers and probably Arduino devices:
ttyACM[0-9] root:dialout 0660 @ln -sf $MDEV modem
Here is the full /etc/mdev.conf
file and corresponding rules:
#
# This is a sample mdev.conf.
#
# Devices:
# Syntax: %s %d:%d %s
# devices user:group mode
$MODALIAS=.* root:root 0660 @modprobe -q -b "$MODALIAS"
# null does already exist; therefore ownership has to be changed with command
null root:root 0666 @chmod 666 $MDEV
zero root:root 0666
full root:root 0666
random root:root 0666
urandom root:root 0444
hwrandom root:root 0660
console root:tty 0600
# load frambuffer console when first frambuffer is found
fb0 root:video 0660 @modprobe -q -b fbcon
fd0 root:floppy 0660
kmem root:kmem 0640
mem root:kmem 0640
port root:kmem 0640
ptmx root:tty 0666
# Kernel-based Virtual Machine.
kvm root:kvm 660
# ram.*
ram([0-9]*) root:disk 0660 >rd/%1
loop([0-9]+) root:disk 0660 >loop/%1
# persistent storage
dasd.* root:disk 0660 */lib/mdev/persistent-storage
mmcblk.* root:disk 0660 */lib/mdev/persistent-storage
nbd.* root:disk 0660 */lib/mdev/persistent-storage
nvme.* root:disk 0660 */lib/mdev/persistent-storage
sd[a-z].* root:disk 0660 */lib/mdev/persistent-storage
sr[0-9]+ root:cdrom 0660 */lib/mdev/persistent-storage
vd[a-z].* root:disk 0660 */lib/mdev/persistent-storage
xvd[a-z].* root:disk 0660 */lib/mdev/persistent-storage
md[0-9] root:disk 0660
tty root:tty 0666
tty[0-9] root:root 0600
tty[0-9][0-9] root:tty 0660
ttyS[0-9]* root:dialout 0660
ttyGS[0-9] root:root 0660
pty.* root:tty 0660
vcs[0-9]* root:tty 0660
vcsa[0-9]* root:tty 0660
# rpi bluetooth
#ttyAMA0 root:tty 660 @btattach -B /dev/$MDEV -P bcm -S 115200 -N &
ttyACM[0-9] root:dialout 0660 @ln -sf $MDEV modem
ttyUSB[0-9] root:dialout 0660 @ln -sf $MDEV modem
ttyLTM[0-9] root:dialout 0660 @ln -sf $MDEV modem
ttySHSF[0-9] root:dialout 0660 @ln -sf $MDEV modem
slamr root:dialout 0660 @ln -sf $MDEV slamr0
slusb root:dialout 0660 @ln -sf $MDEV slusb0
fuse root:root 0666
# mobile broadband modems
cdc-wdm[0-9]+ root:dialout 0660
# dri device
dri/.* root:video 0660
card[0-9] root:video 0660 =dri/
# alsa sound devices and audio stuff
pcm.* root:audio 0660 =snd/
control.* root:audio 0660 =snd/
midi.* root:audio 0660 =snd/
seq root:audio 0660 =snd/
timer root:audio 0660 =snd/
adsp root:audio 0660 >sound/
audio root:audio 0660 >sound/
dsp root:audio 0660 >sound/
mixer root:audio 0660 >sound/
sequencer.* root:audio 0660 >sound/
SUBSYSTEM=sound;.* root:audio 0660
# PTP devices
ptp[0-9] root:root 0660 */lib/mdev/ptpdev
# virtio-ports
SUBSYSTEM=virtio-ports;vport.* root:root 0600 @mkdir -p virtio-ports; ln -sf ../$MDEV virtio-ports/$(cat /sys/class/virtio-ports/$MDEV/name)
# misc stuff
agpgart root:root 0660 >misc/
psaux root:root 0660 >misc/
rtc root:root 0664 >misc/
# input stuff
SUBSYSTEM=input;.* root:input 0660
# v4l stuff
vbi[0-9] root:video 0660 >v4l/
video[0-9]+ root:video 0660 >v4l/
# dvb stuff
dvb.* root:video 0660 */lib/mdev/dvbdev
# VideoCore VC4 BCM GPU specific (as in Pi devices)
vchiq root:video 0660
vcio root:video 0660
vcsm-cma root:video 0660
vc-mem root:video 0660
# load drivers for usb devices
usb[0-9]+ root:root 0660 */lib/mdev/usbdev
# net devices
# 666 is fine: https://www.kernel.org/doc/Documentation/networking/tuntap.txt
net/tun[0-9]* root:netdev 0666
net/tap[0-9]* root:netdev 0666
# zaptel devices
zap(.*) root:dialout 0660 =zap/%1
dahdi!(.*) root:dialout 0660 =dahdi/%1
dahdi/(.*) root:dialout 0660 =dahdi/%1
# raid controllers
cciss!(.*) root:disk 0660 =cciss/%1
cciss/(.*) root:disk 0660 =cciss/%1
ida!(.*) root:disk 0660 =ida/%1
ida/(.*) root:disk 0660 =ida/%1
rd!(.*) root:disk 0660 =rd/%1
rd/(.*) root:disk 0660 =rd/%1
# tape devices
nst[0-9]+.* root:tape 0660
st[0-9]+.* root:tape 0660
# VirtualBox devices
vboxguest root:root 0600
vboxuser root:root 0666
vboxdrv root:root 0600
vboxdrvu root:root 0666
vboxnetctl root:root 0600
# fallback for any!device -> any/device
(.*)!(.*) root:root 0660 =%1/%2
I configure WSL with the following config file at /etc/wsl.conf
. I set the default WSL username under the [user]
settings and facilitate openrc starting with WSL, by using the command command = "/sbin/openrc default"
under [boot]
.
The appendWindowsPath = true
setting under [interop]
, allows the addition of Windows tools to be added to the WSL distro
$PATH
automatically. The command code .
would for example, be available in WSL and launch VSCode in the current working WSL
directory.
# /etc/wsl.conf
[automount]
enabled = true
mountFsTab = true
[network]
generateHosts = true
generateResolvConf = true
[interop]
enabled = true
appendWindowsPath = true
[user]
default = username
[boot]
command = "/sbin/openrc default"
When we imported this distro of Alpine Linux manually, the /etc/profile
file overwrites the $PATH
environment variable and therefore
overwrites the directories added by Windows from the aforementioned appendWindowsPath = true
setting. I correct this by editing the
/etc/profile
file. You could also add anything else to the path here such as export PATH="$PATH:/home/username/projects/.venv-poetry/bin"
,
which in my case, would add my Poetry package manager to the PATH.
# replace the existing PATH declaration:
export PATH="$PATH"
export PAGER=less
umask 022
# use nicer PS1 for bash and busybox ash
if [ -n "$BASH_VERSION" -o "$BB_ASH_VERSION" ]; then
PS1='\h:\w\$ '
# use nicer PS1 for zsh
elif [ -n "$ZSH_VERSION" ]; then
PS1='%m:%~%# '
# set up fallback default PS1
else
: "${HOSTNAME:=$(hostname)}"
PS1='${HOSTNAME%%.*}:$PWD'
[ "$(id -u)" -eq 0 ] && PS1="${PS1}# " || PS1="${PS1}\$ "
fi
for script in /etc/profile.d/*.sh ; do
if [ -r "$script" ] ; then
. "$script"
fi
done
unset script
I reboot and restart Alpine linux to let the new configs take effect.
With the below command, you should see mdev & hwdrivers under syinit runlevel:
rc-update show -v
Check mdev and hwdrivers services are started:
# should see * status: started for both services
rc-service mdev status && rc-service hwdrivers status
I use usbipd-win to share locally connected USB devices to other machines, including Hyper-V guests and WSL 2. By default devices are not shared with USBIP clients. To lookup and share devices, run the following commands with administrator privileges:
Install usbipd-win with winget:
winget install usbipd
List devices with busid & state information using usbipd list
command:
usbipd list
The below image shows a connected Raspberry Pi Pico W device (highlighted in blue), with MicroPython installed. Raspberry Pi Vendor ID (VID) is '0x2e8a' and the
Product ID (PID) for MicroPython is '0x0005'. The device has been 'Shared' using the command usbipd bind --busid 1-7
. All devices must be shared before they can be attached.
To share your device run the following command with the relevant busid value seen in the usbipd list
output:
usbipd bind --busid 1-7
As long as WSL is running, you can then attach the device to WSL using the following command:
usbipd attach --wsl --busid 1-7
In Alpine Linux you can test the device is attached using the lsusb
command and by checking that the mdev hotplug settings have been
utilised with the following command:
ls -la /dev/ttyACM*
The below image shows the command result before and after attachment:
The Watch-PicoConnect.ps1
script can be used to automate Pico attachment to WSL on connection. I run this script in an Administrator PowerShell instance using the following command:
. .\Watch-PicoConnect.ps1
Warning
This script is still in development and might need tweaking for your requirements.