Alpine Linux rootfs builder for MSM8916-based devices (dongles and MiFi routers) with USB gadget networking, Docker, and LTE modem support.
- Alpine Linux v3.21 with postmarketOS v25.06 kernel (6.12+)
- USB Gadget Mode: NCM (Linux/Mac) or RNDIS (Windows) networking
- Direct USB Networking: Simple usb0 interface with DHCP (no bridge complexity)
- WiFi Client: WPA2 support via NetworkManager
- LTE Modem: ModemManager with QMI support (MSM8916 cellular)
- Docker: Pre-installed with user in docker group
- NTP Sync: Chrony for time synchronization (optional, via
PACKAGES) - zram Swap: Compressed in-RAM swap (~256MB effective headroom)
- Auto-expand rootfs: First boot partition and filesystem expansion
- Dropbear SSH: Lightweight SSH server
- WireGuard: VPN support (optional, via
PACKAGES) - OTG Host Mode: Optional USB host mode for peripherals
- Zoraxy: Reverse proxy with HTTPS and web dashboard (optional)
- Homer: Static dashboard served by Zoraxy (optional)
- Vagrant + QEMU (for local builds:
brew install vagrant qemu && vagrant plugin install vagrant-qemu) - Python 3 with
edltool (for flashing via EDL mode)
Copy variables.env.example to variables.env and edit to customize your build:
cp variables.env.example variables.envHOST_NAME="uz801a"
USERNAME="user"
PASSWORD="changeme" # Required — build fails if not set
WIFI_SSID="MyNetwork" # Optional — WiFi SSID to connect to
WIFI_PASS="MyPassword" # Optional — WiFi password
WIFI_IP="" # Optional static WiFi IPv4 CIDR; empty = DHCP
WIFI_GW="" # Optional gateway for WIFI_IP
WIFI_DNS="" # Optional DNS for WIFI_IP
DTB_FILE="msm8916-yiming-uz801v3.dtb" # DTB to use in extlinux.conf
USB0_IP="192.168.42.1/24" # Static IP for USB gadget (with DHCP server)
# Set to "dhcp" when plugged into a router (OpenWrt, etc.)
# Optional mirror overrides
RELEASE="v3.21"
PMOS_RELEASE="v25.06"
# MIRROR="http://dl-cdn.alpinelinux.org/alpine"
# PMOS_MIRROR="http://mirror.postmarketos.org/postmarketos"
# Extra packages to install (essentials are hardcoded in the script)
PACKAGES="
chrony
zoraxy
wireguard-tools
wireguard-tools-wg-quick
neofetch
htop
"
# Extra services to auto-start (essentials are hardcoded in the script)
SERVICES_AUTOSTART="
chronyd
zoraxy
"variables.env is git-ignored so your credentials are never committed.
Set WIFI_SSID and WIFI_PASS in variables.env. The build will substitute them into the NetworkManager connection automatically.
By default WiFi uses DHCP. To bake in a static WiFi address for any profile/stack, set:
WIFI_IP="192.168.1.50/24"
WIFI_GW="192.168.1.1"
WIFI_DNS="1.1.1.1;8.8.8.8;"Set DTB_FILE in variables.env to select which compiled DTB the bootloader uses. See dtbs/readme.md for available options.
USB0_IP controls how the USB gadget interface is configured:
- Static IP (default):
USB0_IP="192.168.42.1/24"— acts as a DHCP server for the connected host. - DHCP client:
USB0_IP="dhcp"— useful when the device is plugged into a router (e.g. OpenWrt) that assigns IPs.
USB gadget uses a simple configuration file /etc/usb-gadget.conf:
# MSM8916 USB Gadget Configuration
USE_NCM=1 # 1 = NCM (Linux/Mac), 0 = RNDIS (Windows)
ENABLE_OTG=0 # 1 = OTG Host mode, 0 = Gadget modeManagement commands:
# Enable NCM mode (Linux/Mac compatible)
usb-gadget enable_ncm
# Enable RNDIS mode (Windows compatible)
usb-gadget disable_ncm
# Enable OTG Host mode (for USB peripherals)
usb-gadget enable_otg
# Disable OTG (back to gadget mode)
usb-gadget disable_otg
# View current config
usb-gadget status
# Apply changes
rc-service usb-gadget restartThe build system can compile DTS (Device Tree Source) files from the upstream Linux kernel and from the local dts/ directory.
make build automatically fetches the kernel DTS tree (cached in .kernel-dts/) and compiles these upstream files:
msm8916-yiming-uz801v3.dtsmsm8916-thwc-uf896.dtsmsm8916-thwc-ufi001c.dts
Add your own .dts files to the dts/ directory. They are compiled with the same flags and include paths as the upstream files, so you can reference kernel DTSI files:
// dts/msm8916-mydevice.dts
/dts-v1/;
#include "msm8916-ufi.dtsi"
// ... your customizations
Compile only DTS (without full build):
make dtsOutput goes to files/dtbs/. See dtbs/readme.md for the list of precompiled fallback DTBs.
Preferred — profile builds:
Pick an appliance profile. Run these commands inside the builder VM (via make builder) or in CI; they build rootfs, boot image, firmware.zip, and GPT table:
make octoprint # OctoPrint: native 3D printer interface, no Docker
make docker # Docker-enabled base image
make zoraxy # Zoraxy: native reverse proxy, no DockerProfile images are minimal: only the packages and services for the chosen appliance are installed. Installer scripts for other stacks are not copied to the device — the selected stack is wired in at build time.
USB gadget tooling (usb-gadget, /etc/usb-gadget.conf) is installed and auto-started in the default, docker, and zoraxy profiles. The octoprint profile is the exception: it sets USB_GADGET_INSTALL="no" because the USB port is used as host for the printer — gadget tooling is omitted and USB OTG host mode is forced at boot instead (USB_GADGET_OTG="yes").
Vagrant artifact flow: Profile targets (
make octoprint,make docker,make zoraxy) runmake build-all PROFILE=...inside the VM or CI and do not callmake fetch. After the build finishes, exit the VM and runmake fetchon the host to copy artifacts. The host targetsmake build-vm/make build-all-vmhandle the full cycle (up → build → fetch) automatically but are generic — they do not set a profile.
Option A — interactive shell inside the VM:
# Open a shell in the builder VM (first time provisions automatically)
make builder
# Inside the VM: build rootfs + boot image
make build
# Or build everything including firmware.zip and GPT table
make build-all
# Exit the VM
exit
# Back on the host — copy artifacts from the VM to host files/
make fetchNote: vagrant-qemu uses SLIRP user networking; NFS and shared folders are not available.
make fetchis the only way to copy artifacts to the host.
Option B — one-shot from the host (recommended, no interactive shell needed):
make build-vm # build inside VM and fetch artifacts automatically
make build-all-vm # same but includes firmware.zip and GPT tablemake fetch is always safe to re-run — it overwrites artifacts with the latest build.
Build output in files/:
rootfs.bin- Alpine rootfs sparse imagerootfs.tgz- Alpine rootfs tarballboot.bin- Kernel + initramfs boot imagegpt_both0.bin- GPT partition table for 4GB eMMC (withbuild-all)firmware.zip- Complete firmware package (withbuild-all)
- Power off the device completely
- Hold Volume Up button
- Connect USB cable while holding button
- Device enters EDL mode (no screen indication)
./flash.shAfter flashing and reboot:
- Device boots Alpine Linux (~30-45 seconds)
- Rootfs automatically expands to fill eMMC
- WiFi connects to configured network
- USB gadget activates (NCM/RNDIS interface)
- USB interface gets IP 192.168.42.1/24 with DHCP server
- SSH server starts on port 22
Access via SSH:
# Via WiFi (check router for IP)
ssh user@192.168.77.XXX
# Via USB
ssh user@192.168.42.1
# Default credentials
Username: user
Password: (configured in variables.env)The stacks/ directory contains install scripts and Docker Compose files for optional services.
- Profile builds (
make octoprint,make docker,make zoraxy): the selected stack is preinstalled at build time. No installer scripts are left on the device. - Base builds (
make build): install scripts are not copied automatically — copy and run them from the repo'sstacks/directory as a manual/custom workflow, or customisevariables.env.
Native OctoPrint core for a Creality Ender-3 V3 SE (or similar USB-serial printer). No Docker, no webcam, no bundled plugins — designed for the ~384 MB RAM constraint. See docs/octoprint.md for the full guide including USB OTG host mode, WiFi prerequisites, serial device detection, memory tradeoffs, and troubleshooting.
sudo ~/install-octoprint.sh- Web UI:
http://<device-ip>:5000 - Service:
rc-service octoprint start|stop|restart|status - Logs:
/var/log/octoprint/octoprint.log - Data:
/var/lib/octoprint - DTB: use
msm8916-yiming-uz801v3-octoprint.dtbto reclaim ~91 MB from unused LTE + video decode
Zoraxy provides a reverse proxy with HTTPS termination and a web admin panel. The zoraxy profile installs it as a native service (no Docker required). A Docker Compose file is also available if the docker profile is selected.
Native install (default for the zoraxy profile):
sudo ~/install-zoraxy.sh- Admin panel:
http://<device-ip>:8000 - Config:
/opt/zoraxy/config/ - Service:
rc-service zoraxy start|stop|restart
Docker install:
docker compose -f stacks/zoraxy.docker-compose.yml up -dHomer is a lightweight static homepage served by Zoraxy's built-in web server.
sudo ~/install-homer.sh- Installs to
/opt/homer/html/ - Automatically configures Zoraxy to serve it via
-webroot - Config:
/opt/homer/html/assets/config.yml
Re-run the script to update to the latest Homer release. User config is preserved on updates.
docker compose -f stacks/portainer.docker-compose.yml up -d- Web UI:
http://<device-ip>:9000
docker compose -f stacks/watchtower.docker-compose.yml up -d- Checks for container updates daily at 04:00
- Automatically pulls and restarts updated containers
The device exposes itself as a USB network adapter when connected to a PC.
Default configuration:
- Interface: usb0
- IP: 192.168.42.1/24
- DHCP: NetworkManager shared connection (192.168.42.10-100)
- Mode: NCM (Linux/Mac) or RNDIS (Windows)
- MAC addresses: Auto-generated from machine-id
- No default route: Host traffic stays on primary network
Features:
- Plug-and-play networking
- Automatic DHCP without extra dnsmasq
- No bridge complexity
- Doesn't steal host's default route
- Switchable NCM/RNDIS modes
- OTG Host mode support
Service control:
# Start/stop/restart
rc-service usb-gadget start|stop|restart
# Check status and current mode
usb-gadget status
# Switch modes
usb-gadget enable_ncm # Linux/Mac
usb-gadget disable_ncm # Windows (RNDIS)
usb-gadget enable_otg # USB Host modeUSB Networking (usb0):
- Method: NetworkManager shared connection
- IP: 192.168.42.1/24
- DHCP Range: 192.168.42.10-192.168.42.100
- Gateway: Not advertised (host keeps existing default route)
NetworkManager configuration in /etc/NetworkManager/system-connections/usb0.nmconnection:
[connection]
id=usb0
type=ethernet
interface-name=usb0
autoconnect=true
[ipv4]
method=shared
address1=192.168.42.1/24
never-default=true
[ipv6]
method=disabledWiFi (wlan0):
- Managed by NetworkManager
- Auto-connects on boot
- Configuration in
/etc/NetworkManager/system-connections/wlan.nmconnection
LTE (wwan0qmi0):
- Managed by ModemManager
- Configuration in
/etc/NetworkManager/system-connections/lte.nmconnection
Pre-installed and configured with overlay2 storage driver.
User permissions:
- User added to
dockergroup (no sudo required)
Configuration: /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2",
"iptables": true
}Usage:
docker --version
docker run hello-world
docker ps -aA 256MB compressed in-RAM swap device is configured automatically at boot via /etc/local.d/zram.start. This uses the lz4 algorithm and effectively provides ~256MB of extra memory headroom on constrained devices.
ModemManager with QMI support for cellular connectivity.
Service: modemmanager + rmtfs
Check status:
# List modems
mmcli -L
# Get modem details
mmcli -m 0
# Check connection
mmcli -m 0 --simple-statusManual connection:
nmcli connection up lte
ip addr show wwan0qmi0Dropbear SSH server on port 22.
Default credentials:
- Username:
user(or configured in variables.env) - Password: configured in variables.env
- Sudo: NOPASSWD enabled
Connect:
# Via WiFi
ssh user@192.168.77.XXX
# Via USB
ssh user@192.168.42.1Best for: Linux and macOS
- High performance
- Native driver support in Linux 2.6.31+, macOS 10.9+
- No driver installation needed
Enable:
usb-gadget enable_ncm
rc-service usb-gadget restartBest for: Windows
- Native Windows driver support
- Plug-and-play on Windows 7+
Enable:
usb-gadget disable_ncm
rc-service usb-gadget restartBest for: USB peripherals (flash drives, keyboards, etc.)
- Enables USB host functionality
- Disables USB gadget mode
- Requires USB OTG adapter
Enable:
usb-gadget enable_otg
rc-service usb-gadget restartWarning: WiFi or LTE connectivity required for remote access when in OTG mode.
On first boot, the system will automatically:
- Expand rootfs partition to fill eMMC
- Resize ext4 filesystem
- Start all services (NetworkManager, Docker, ModemManager, etc.)
- Connect to configured WiFi
- Create USB gadget (NCM interface)
- Configure usb0 with IP 192.168.42.1/24
- Start DHCP server on usb0
- Enable zram swap
- Sync time via Chrony (if installed)
Boot time: ~30-45 seconds to full network connectivity
After flashing and first boot, confirm the selected profile is active:
# USB gadget tooling (default, docker, zoraxy profiles — not octoprint)
usb-gadget status
# OctoPrint profile
rc-service octoprint status # should show 'started'
ls /var/lib/octoprint/ # data directory exists
# Docker profile
rc-service docker status # should show 'started'
docker info # daemon responds
# Zoraxy profile
rc-service zoraxy status # should show 'started'
curl -s http://localhost:8000 | head -5 # admin panel responds
# No stray installer scripts from unused stacks
ls ~/install-*.sh 2>/dev/null || echo "clean — no unused installer scripts"For an automated check of the OctoPrint profile, run inside the builder VM:
make verify-octoprint- @kinsamanka (https://github.com/kinsamanka/OpenStick-Builder): For almost all the project.