Skip to content
Building locally

Building locally

You don’t need to build anything to use the firmware: every push to main that touches config/ builds it on GitHub Actions, and Flashing downloads the result. Build locally to try keymap changes before pushing them, or to work on the firmware itself.

The repo is a ZMK user config with a local west workspace, and mise runs every task. These steps are for Arch Linux.

1. System packages

sudo pacman -S --needed cmake ninja dtc git dfu-util mise

2. Zephyr SDK 0.17.0

The ARM toolchain goes into tools/, which git ignores:

mkdir -p tools
wget -P tools https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.17.0/zephyr-sdk-0.17.0_linux-x86_64_minimal.tar.xz
tar xf tools/zephyr-sdk-0.17.0_linux-x86_64_minimal.tar.xz -C tools/
tools/zephyr-sdk-0.17.0/setup.sh -t arm-zephyr-eabi -h -c
rm tools/zephyr-sdk-0.17.0_linux-x86_64_minimal.tar.xz

Warning

Don’t use the AUR zephyr-sdk package. It installs SDK 1.0, which targets Zephyr 4.2 and later; this firmware is on Zephyr 4.1, and 0.17.0 is the matching SDK. .mise.toml points ZEPHYR_SDK_INSTALL_DIR at tools/zephyr-sdk-0.17.0 and pins ZEPHYR_TOOLCHAIN_VARIANT.

3. The workspace (once)

The build runs on the system Python from pacman (3.14 at the time of writing). mise doesn’t provide Python here and there is no .venv. Install the modules a build imports:

sudo pacman -S --needed python-yaml python-pyelftools python-packaging python-protobuf python-pkg_resources
yay -S python-west python-pykwalify   # not in the official repos

python-protobuf and python-pkg_resources are for the left half’s Studio build, which generates nanopb code. python-pkg_resources is the CachyOS package name; on plain Arch, pkg_resources comes with python-setuptools. Then:

mise run setup

setup first checks that those seven packages import. If any are missing, it prints the install line and stops. Then it runs west init (only when .west/ doesn’t exist yet), west update and west zephyr-export, and finally the SDK’s setup.sh -h -c. That relocates the SDK host tools such as dtc, which embed their absolute path, and registers the SDK with CMake. Re-run mise run setup whenever you move or rename the checkout.

ZMK lands in zmk/, Zephyr in zephyr/, the other west projects in modules/ and the repo root; all of them are git-ignored.

4. Check the workspace

mise run doctor        # fast, offline: is the workspace what config/west.yml says?
mise run doctor-fix    # repair it
mise run doctor-ci     # optional: compare with the last green main build (needs gh)
mise run deps-check    # the other direction: has upstream moved past the pins?

A west workspace can drift from config/west.yml without west update saying so: west names a project’s remote from the manifest but never repoints an existing clone. doctor compares every project’s HEAD with the manifest-rev ref west keeps in it, checks every remote URL against the manifest, and checks that the nice_nano//zmk board exists and that Zephyr is 4.1. Run it first whenever a build fails in a way that looks environmental; see Troubleshooting.

deps-check reads the # track: comment beside each pin and reports the projects whose upstream has moved, with one git ls-remote each. See Firmware for the pins and how to take an update.

5. Build

mise run build-left    # left half: Studio, the custom RPC modules, zmk-rgb-layer
mise run build-right   # right half
mise run build-reset   # settings_reset.uf2

They write eyelash_sofle_left.uf2, eyelash_sofle_right.uf2 and settings_reset.uf2 in the repo root. Under the hood, each is one west build -s zmk/app -b nice_nano//zmk with the shield (eyelash_sofle_left nice_view or eyelash_sofle_right nice_view), the config/ directory and the in-repo zmk-rgb-layer module; the left half adds Studio and the studio-rpc-usb-uart snippet for USB.

6. Flash

mise run flash-left    # or flash-right, flash-reset
mise run build-flash   # build both halves, flash both, print the memory budget

Each flash-* task asks you to double-tap reset on the half, waits up to 60 seconds for NICENANO to mount, copies the file and syncs.

The keymap render

After editing config/eyelash_sofle.keymap, regenerate the drawing:

mise run draw

It renders keymap-drawer/eyelash_sofle.svg with keymap-drawer, using the same version and physical layout as CI, so the result matches what the Draw Keymap workflow commits on main. That SVG is what the Keymap page on this site shows.

The keymap PDF

The site publishes a printable keymap at /sofleii-keymap.pdf, four A4 portrait pages: a one-page summary of the basics, then every layer’s grid, three to a page. The Pages workflow prints the print view with headless Chromium after each site build. To make it locally:

mise run docs-pdf        # build the site, print site/public/sofleii-keymap.pdf
mise run docs-pdf-open   # the same, then open it

It needs chromium or google-chrome. Both CI and the task run .github/scripts/print-keymap-pdf.sh, so the PDFs match.

This site

The docs site lives in site/ and builds with Hugo:

mise run docs         # dev server on http://127.0.0.1:1313/
mise run docs-build   # minified build into site/public/

The keymap render, the hold-tap and combo tables and the dependency pins on this site are read from the repo’s own files on every build, so they never need updating by hand.