Void Linux musl serves as the "Linux Pre-Execution Environment" (UnixPE) in NETSetup. It boots from a squashfs image on the target disk’s ESP partition and runs NETSetup to install Linux-based operating systems (Proxmox, NixOS, etc.).
Architecture Overview
USB Stick (WinPE)
│
├─ NETSetup.exe install (Phase 1: WinPE)
│ ├─ Partitions target disk: GPT, 4GB FAT32 ESP (label=SYSTEM)
│ ├─ Copies linux/ folder → Z:\ (GRUB + Void squashfs)
│ ├─ Copies NETSetup binary → Z:\NETSetup\NETSetup.musl
│ ├─ Copies config + OS image (e.g. proxmox.iso) → Z:\NETSetup\
│ └─ Reboots (USB can be removed)
│
Target Disk ESP (label=SYSTEM)
│
├─ /EFI/BOOT/BOOTX64.EFI ← GRUB bootloader
├─ /boot/vmlinuz ← Void Linux kernel
├─ /boot/initrd ← Void Linux initramfs
├─ /boot/grub/grub.cfg → grub_void.cfg
├─ /LiveOS/squashfs.img ← Void Linux rootfs (compressed)
│ └─ LiveOS/ext3fs.img ← Void Linux rootfs (ext3, ~3GB)
│ └─ / (full Void musl system)
│ └─ /etc/rc.local ← entrypoint: searches for autoexecute.sh
├─ /NETSetup/NETSetup.musl ← NETSetup binary (linux-musl-x64)
├─ /NETSetup/Config/*.json ← customer config
└─ /NETSetup/Images/{OS}/ ← OS installer ISO
Boot Chain
-
UEFI firmware boots GRUB from ESP (
/EFI/BOOT/BOOTX64.EFI) -
GRUB loads
grub_void.cfg→ boots "UnixPE" menu entry -
Kernel + initrd boot, dracut live module:
-
Finds
squashfs.imgon partitionLABEL=SYSTEM -
Mounts squashfs (read-only) → loop-mounts
ext3fs.img -
Creates overlayfs (rw tmpfs on top of ro rootfs)
-
Pivots root to overlayfs
-
-
Void Linux init runs →
/etc/rc.localexecutes -
rc.localscans all partitions forautoexecute.sh -
Finds it on the SYSTEM partition → runs it
-
autoexecute.shlaunchesNETSetup.musl install -
NETSetup Stage 4 (LinuxPE) installs the target OS via GRUB chainload
Disk Layout on Target (after Phase 1)
/dev/sdX (or nvme0n1)
├─ Partition 1: FAT32 ESP, 4GB, label=SYSTEM
│ ├─ EFI/BOOT/BOOTX64.EFI
│ ├─ boot/ (vmlinuz, initrd, grub/)
│ ├─ LiveOS/squashfs.img
│ ├─ NETSetup/ (binary, config, images)
│ └─ answer.toml (for Proxmox auto-install)
└─ Partition 2: (created by Stage 4) raw ISO partition for OS installer
squashfs.img Structure
The squashfs contains a single file: LiveOS/ext3fs.img — a ~3GB ext3 filesystem with the full Void Linux musl rootfs.
Key files inside:
| Path | Purpose |
|---|---|
|
Boot entrypoint — scans drives for |
|
NTFS mount support (needed to read USB sticks) |
|
C++ standard library — must match .NET runtime requirements |
How to Modify the Void PE Image
Prerequisites
-
WSL (Ubuntu) with
squashfs-toolsinstalled:sudo apt install squashfs-tools -
The squashfs.img lives at:
src/NETSetup/linux/LiveOS/squashfs.img -
Helper scripts in the same folder:
open-void-pe.sh,close-void-pe.sh,edit-netsetup-call.sh
Open (unsquash + mount)
From WSL Ubuntu, cd to the LiveOS/ folder:
cd /mnt/c/_gh/main/netsetup/src/NETSetup/linux/LiveOS
# Step 1: unsquash (creates void/LiveOS/ext3fs.img)
sudo unsquashfs -f -d void squashfs.img
# Step 2: copy ext3fs.img to Linux FS (loop mount doesn't work on /mnt/c)
sudo mkdir -p /mnt/void
sudo cp void/LiveOS/ext3fs.img /tmp/ext3fs.img
sudo mount -o loop /tmp/ext3fs.img /mnt/void
|
Note
|
Loop-mounting on /mnt/c (Windows FS via 9p/DrvFs) does not work.
You MUST copy ext3fs.img to the native Linux filesystem (e.g. /tmp/) before mounting.
|
Modify files
# Edit rc.local
sudo nano /mnt/void/etc/rc.local
# Or browse the filesystem
ls /mnt/void/usr/lib/libstdc++*
Chroot (for package management)
|
Important
|
You CANNOT chroot from glibc Ubuntu into musl Void — the binaries will not run. Use static xbps instead: |
# Download static xbps (runs on any Linux x86_64)
cd /tmp
wget https://repo-default.voidlinux.org/static/xbps-static-latest.x86_64-musl.tar.xz
tar xf xbps-static-latest.x86_64-musl.tar.xz
# Upgrade all packages in the mounted rootfs
sudo /tmp/usr/bin/xbps-install.static -r /mnt/void -Su
# Install a specific package
sudo /tmp/usr/bin/xbps-install.static -r /mnt/void -S <package-name>
# Query installed packages
sudo /tmp/usr/bin/xbps-query.static -r /mnt/void -l
Alternatively, if you have a real Void Linux musl system (VM, live USB, etc.), you can chroot /mnt/void directly and use normal xbps-install -Su.
Close (unmount + repack)
# Step 1: unmount
sudo umount /mnt/void
# Step 2: copy modified image back
sudo cp /tmp/ext3fs.img void/LiveOS/ext3fs.img
# Step 3: fsck
sudo e2fsck -p -f void/LiveOS/ext3fs.img
# Step 4: verify
dumpe2fs -h void/LiveOS/ext3fs.img | grep "Filesystem state"
# Step 5: repack squashfs (xz compression)
sudo rm squashfs.img
sudo mksquashfs void squashfs.img -comp xz -noappend
# Step 6: cleanup
sudo rm -rf void
sudo rm /tmp/ext3fs.img
How to Create a New Void PE Image from Scratch
When upgrading to a new Void Linux version or starting clean.
Step 1: Download Void Linux musl ROOTFS
cd /tmp
# Get the latest musl rootfs tarball from:
# https://voidlinux.org/download/ → "Base Tarballs" → x86_64-musl
wget https://repo-default.voidlinux.org/live/current/void-x86_64-musl-ROOTFS-<DATE>.tar.xz
Step 2: Create ext3fs.img
# Create a 3GB ext3 image (adjust size as needed)
dd if=/dev/zero of=/tmp/ext3fs.img bs=1M count=3072
mkfs.ext3 /tmp/ext3fs.img
# Mount it
sudo mkdir -p /mnt/void
sudo mount -o loop /tmp/ext3fs.img /mnt/void
# Extract rootfs
sudo tar xpf void-x86_64-musl-ROOTFS-*.tar.xz -C /mnt/void
Step 3: Install required packages
# Copy DNS config so xbps can resolve repos
sudo cp /etc/resolv.conf /mnt/void/etc/resolv.conf
# Use static xbps to install packages
cd /tmp
wget https://repo-default.voidlinux.org/static/xbps-static-latest.x86_64-musl.tar.xz
tar xf xbps-static-latest.x86_64-musl.tar.xz
# Update package database and install required packages
sudo /tmp/usr/bin/xbps-install.static -r /mnt/void -Syu
sudo /tmp/usr/bin/xbps-install.static -r /mnt/void -S \
ntfs-3g \
parted \
dosfstools \
e2fsprogs \
grub-x86_64-efi \
lsblk \
udev \
kmod \
dracut \
wifi-firmware \
wpa_supplicant \
dhclient
Step 4: Configure rc.local
Copy the rc.local from the existing image or write a new one.
The rc.local must:
-
Scan all partitions (
lsblk) forautoexecute.sh -
Mount each partition (handle NTFS via
ntfs-3g, other viamount -t) -
When found, copy to
/root/autoexecute.sh,chmod +x, andexecit
See the current rc.local in the existing image for the full implementation.
Step 5: Extract kernel + initrd
# The kernel and initrd for GRUB must come from this rootfs
sudo cp /mnt/void/boot/vmlinuz-<version> /mnt/c/_gh/main/netsetup/src/NETSetup/linux/boot/vmlinuz
sudo cp /mnt/void/boot/initramfs-<version>.img /mnt/c/_gh/main/netsetup/src/NETSetup/linux/boot/initrd
|
Important
|
The kernel (vmlinuz) and initrd in linux/boot/ must match the kernel installed in the squashfs rootfs.
If you update the kernel inside the image, you MUST also update these files.
|
Step 6: Pack into squashfs
sudo umount /mnt/void
# Create squashfs directory structure
mkdir -p /tmp/void-squash/LiveOS
mv /tmp/ext3fs.img /tmp/void-squash/LiveOS/ext3fs.img
# Pack
sudo mksquashfs /tmp/void-squash /mnt/c/_gh/main/netsetup/src/NETSetup/linux/LiveOS/squashfs.img -comp xz -noappend
# Cleanup
sudo rm -rf /tmp/void-squash
Updating libstdc++ (fixing .NET 10 compatibility)
The current image has libstdc++.so.6.0.28 (GCC 10) which causes:
Error relocating NETSetup.musl: _ZSt28__throw_bad_array_new_lengthv: symbol not found
Fix: Update packages in existing image
Follow "How to Modify the Void PE Image" above, then:
# Upgrade all packages (pulls in GCC 12+ libstdc++)
sudo /tmp/usr/bin/xbps-install.static -r /mnt/void -Su
# Verify libstdc++ version (should be 6.0.32 or newer)
ls -la /mnt/void/usr/lib/libstdc++.so*
Then close/repack the image.
Updating the kernel + initrd
After a full system upgrade, the kernel version may change.
You need to update the boot files in linux/boot/:
# Check which kernel version is installed
ls /mnt/void/boot/vmlinuz-*
ls /mnt/void/boot/initramfs-*.img
# Copy to the boot folder
sudo cp /mnt/void/boot/vmlinuz-<new-version> /mnt/c/_gh/main/netsetup/src/NETSetup/linux/boot/vmlinuz
sudo cp /mnt/void/boot/initramfs-<new-version>.img /mnt/c/_gh/main/netsetup/src/NETSetup/linux/boot/initrd
GRUB Configuration
grub.cfg (main)
Loads filesystem modules and sources grub_void.cfg:
insmod usbms
insmod part_gpt
insmod fat
insmod iso9660
insmod ntfs
insmod linux
search --file --no-floppy --set=voidlive "/boot/grub/grub_void.cfg"
source "(${voidlive})/boot/grub/grub_void.cfg"
grub_void.cfg (Void Linux boot entry)
menuentry "UnixPE" {
linux (${voidlive})/boot/vmlinuz \
root=live:LABEL=SYSTEM ro init=/sbin/init \
rd.live.overlay.overlayfs=1 lockdown=none
initrd (${voidlive})/boot/initrd
}
Key kernel parameters:
-
root=live:LABEL=SYSTEM— dracut live module finds squashfs on partition labeled SYSTEM -
rd.live.overlay.overlayfs=1— use overlayfs (writable tmpfs layer on top of read-only rootfs) -
lockdown=none— allow unsigned modules
NETSetup adds installer entry at runtime
Stage 4 (LinuxPE) appends a second GRUB menu entry to grub_void.cfg that boots the OS installer ISO from partition 2 via rdinit= wrapper.
See BootISOGrub2.cs and the process diagram in docs/NETSetup.puml.
rc.local Boot Flow
/etc/rc.local
│
├─ Lock file check (/var/lock/autoexecute.lock)
├─ Scan all partitions (lsblk: sd*, nvme*, hd*, sr*)
│ ├─ Skip: already mounted, no filesystem, swap
│ ├─ Mount: NTFS via ntfs-3g, others via mount -t $fstype
│ └─ Search for autoexecute.sh on each partition
│
├─ Found autoexecute.sh:
│ ├─ Copy to /root/autoexecute.sh (strip \r)
│ ├─ chmod +x
│ └─ exec → NETSetup.musl install
│
└─ Already has /root/autoexecute.sh:
└─ Re-exec if not already running
File Inventory
| File | Purpose | Tracked in git? |
|---|---|---|
|
GRUB EFI bootloader |
Yes |
|
Void Linux kernel |
Yes (LFS recommended) |
|
Void Linux initramfs |
Yes (LFS recommended) |
|
GRUB main config |
Yes |
|
Void Linux boot menu |
Yes |
|
GRUB background |
Yes |
|
Void Linux rootfs (compressed) |
Yes (LFS recommended) |
|
Extracted squashfs (working dir) |
No (.gitignore) |
|
Helper: unsquash + mount |
Yes |
|
Helper: unmount + repack |
Yes |
|
Helper: edit rc.local |
Yes |
Troubleshooting
_ZSt28__throw_bad_array_new_lengthv: symbol not found
libstdc is too old for .NET 10. See <<Updating libstdc (fixing .NET 10 compatibility)>>.
Loop mount fails on /mnt/c
WSL’s DrvFs (9p) does not support loop devices. Copy ext3fs.img to native Linux FS (/tmp/) first.
Cannot chroot from Ubuntu into Void
Ubuntu uses glibc, Void uses musl — binaries are incompatible.
Use static xbps (xbps-install.static -r /mnt/void) or a real Void Linux system.
Kernel panic on boot
Kernel (vmlinuz) and initrd in linux/boot/ don’t match the rootfs.
After upgrading the kernel inside the squashfs, copy the new vmlinuz + initrd to linux/boot/.