Skip to content
Utility-Muffin-Research-KitchenPublic

About

Deployment orchestrator for the UMRK launcher workspace

Topics

Resources

Stars

48 stars

Watchers

2 watching

Forks

Latest commit

 

History

205 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Leaf — custom firmware for the Miniloong Pocket 1

release downloads license last commit


Translated

Español translation coverage Français translation coverage Tiếng Việt translation coverage 中文 translation coverage 日本語 translation coverage

Translated by the Leaf community. Help translate Leaf.

Leaf is the developer command surface for the UMRK launcher workspace. It owns workspace bootstrap, cross-repo status/preflight, payload assembly, and SD-card deployment. Product repos remain independent siblings and keep their own build and package targets.

Setup

Create a parent workspace, clone Leaf, then let Leaf clone the public sibling repos. If your GitHub account has access to the private internal planning repo, bootstrap will clone it too; otherwise it is silently skipped.

mkdir -p ~/dev/UMRK
cd ~/dev/UMRK
git clone https://github.com/Utility-Muffin-Research-Kitchen/Leaf.git
cd Leaf

make bootstrap
docker pull ghcr.io/utility-muffin-research-kitchen/mlp1-toolchain:latest
toolchain_image="$(docker image inspect --format '{{.Id}}' \
  ghcr.io/utility-muffin-research-kitchen/mlp1-toolchain:latest)"
make release-zips DEVICE=mlp1 TOOLCHAIN_IMAGE="$toolchain_image" REBUILD_CORES=1

The first dev build may compile missing or stale cores. Once all core outputs are valid for the selected image ID, omit REBUILD_CORES=1 on later dev runs. If a core becomes stale, add it again to permit compilation. The install and recovery ZIPs are written to build/release/; you do not need a connected device or ScreenScraper credentials for a dev build.

By default Leaf treats its parent directory as the workspace root:

~/dev/UMRK/
  Leaf/
  Catastrophe/
  Jawaka/
  Thing-File/
  ssh-server/
  CentralScrutinizer/
  Fugazi/
  PPSSPP-spruce/
  steward-fu-nds/
  N64-standalone/
  Flycast-standalone/
  retroarch-builds/
  Cores-spruce/
  mlp1-toolchain/
  miniloong-launcher-switcher/
  miniloong-adb-keeper/
  umrk-workspace/      # optional internal docs/plans, only with access

For unusual checkout layouts, set LEAF_WORKSPACE_DIR:

LEAF_WORKSPACE_DIR=/Volumes/Storage/UMRK make status

Connected-device staging

When your MLP1 is connected over ADB, check the device and stage the payload:

make doctor
make stage DEVICE=mlp1 TOOLCHAIN_IMAGE="$toolchain_image"

make doctor checks ADB and the device, so use it for staging rather than the device-free ZIP build.

Commands

Run commands from the Leaf repo:

make bootstrap                              # clone public repos; privately clone internal docs when accessible
make status                                 # git status across public siblings
make status-internal                        # git status including private maintainer repos

make stage DEVICE=mlp1                      # full: launcher payload + all apps
make stage-refresh DEVICE=mlp1              # full stage, then restart Jawaka GUI
make refresh-jawaka DEVICE=mlp1             # restart Jawaka/Loong GUI stack only
make stage-jawaka DEVICE=mlp1               # launcher payload only
make stage-retroarch DEVICE=mlp1            # RetroArch binary + cores + info + shaders
make stage-core-test CORE=np2kai DEVICE=mlp1 # testing only: one targeted, verified core
make stage-emulator EMULATOR=drastic DEVICE=mlp1
make stage-emulator EMULATOR=mupen64plus DEVICE=mlp1
make stage-emulator EMULATOR=flycast DEVICE=mlp1
make stage-emulators DEVICE=mlp1            # PPSSPP + DraStic + N64 + Dreamcast
make stage-app APP=ssh-server DEVICE=mlp1   # stage one app
make stage-app APP=Thing-File DEVICE=mlp1
make stage-app APP=CentralScrutinizer DEVICE=mlp1
make stage-app APP=Fugazi DEVICE=mlp1
make stage-app APP=Leaf-Itchio-Pak DEVICE=mlp1  # optional developer/acceptance stage only
make stage-app APP=DiscoBoy DEVICE=mlp1         # optional developer/acceptance stage only
make stage-app APP=VideoFromHell DEVICE=mlp1    # optional developer/acceptance stage only
make stage-app APP=Nimbus DEVICE=mlp1           # optional developer/acceptance stage only
make stage-app APP=PortMaster-mlp1 DEVICE=mlp1  # optional developer/acceptance stage only

make release-zips DEVICE=mlp1 TOOLCHAIN_IMAGE="$toolchain_image" # build both ZIPs
make release-sd-zip DEVICE=mlp1             # build end-user install ZIP only
make release-recovery-zip DEVICE=mlp1       # build end-user recovery ZIP only

stage-core-test is the fast device-iteration path after a targeted Cores-spruce/build-mlp1.sh <core> build. It stages only the named core and matching info file from the separate checksum-bound targeted-build-report.json. Full staging and release ZIPs still require the complete canonical stock-parity report.

Full staging and release ZIPs also require every shipped core's extra artifacts, not just its binary: matching .info, a checksum-bound report row, the catalog entry, and its default settings template. A payload missing any of those fails before bytes reach a card or a ZIP. Staging replaces cores/ and info/ by managed name, so developer experiment cores kept beside the shipped set survive a stage.

Release preparation runs the Cores-spruce read-only cache preflight before it accepts that full report. Any missing or stale core aborts before compilation. REBUILD_CORES=1 explicitly permits an incremental stock-parity run; FORCE_REBUILD_CORES=1 additionally bypasses valid hits for an intentional full rebuild.

Leaf owns the release catalog source and generated MLP1 metadata in scripts/retroarch_generate_metadata.py, scripts/system_folder_policy.json, and config/retroarch/mlp1/. After assembling a complete stock-parity core report, regenerate and validate the catalogs from the Leaf checkout:

python3 scripts/retroarch_generate_metadata.py --umrk-root "$(pwd)/.." \
  --allium-root /path/to/Allium --spruce-root /path/to/spruceOS \
  --build-report ../Cores-spruce/output/mlp1/build-report.json
python3 scripts/retroarch_catalog_audit.py --quiet
python3 scripts/retroarch_validate_package.py \
  --canonical-systems config/retroarch/mlp1/systems.json
cp config/retroarch/mlp1/cores.json ../miniloong-launcher-switcher/device/mlp1/defaults/cores.json
cp config/retroarch/mlp1/systems.json ../miniloong-launcher-switcher/device/mlp1/defaults/systems.json

The reference checkouts are authoring inputs. ZIP builds use the checked-in catalogs and do not need those checkouts.

On MLP1, stage-app uses Jawaka's package-quiesce-v1 barrier before it removes or pushes any package bytes. Close foreground apps first. A missing daemon, stale/unverified service generation, or rejected barrier fails the stage without modifying the destination. After a successful push (and also on a failed push), the helper asks Jawaka to rescan manifests and restore only persistent Start-with-Leaf intent. make package-quiesce-smoke covers the ordering and failure cleanup with a fake ADB endpoint.

The five first-party optional apps above are registered only for an explicit developer stage. They remain Pak Rat-owned and are excluded from default full staging, release ZIPs, managed_apps, and bootstrap requirements. Store install/update/uninstall testing must still use Pak Rat rather than stage-app.

For a local Pak Rat catalog, pass one or more app repositories containing pakrat.json. The generator builds each selected package and writes only below build/pakrat-local:

python3 scripts/pakrat-local-feed.py \
  --app-dir ../Leaf-Itchio-Pak \
  --app-dir ../PortMaster-mlp1

To exercise the exact ZIP downloaded from a draft release without repackaging it, add an app-id-to-file override:

python3 scripts/pakrat-local-feed.py \
  --app-dir ../Leaf-Itchio-Pak \
  --skip-build \
  --artifact org.umrk.itchio=/path/to/Itch-io.mlp1.pak.zip

The exact-artifact path is copied byte-for-byte into the local feed. Pak Rat lifecycle testing still uses the generated catalog and Jawaka ownership state.

make bootstrap is idempotent. Existing sibling repos are reported as present and left untouched. Missing public repos are cloned into Leaf/..; optional private repos are cloned only when credentials are available.

End-User SD Install Package

Leaf can build root-extractable ZIPs for installing Leaf on a Miniloong Pocket 1 without requiring ADB first. This is the preferred path for making a device installation package for end users.

From the Leaf repo:

make bootstrap
docker pull ghcr.io/utility-muffin-research-kitchen/mlp1-toolchain:latest
toolchain_image="$(docker image inspect --format '{{.Id}}' \
  ghcr.io/utility-muffin-research-kitchen/mlp1-toolchain:latest)"
make release-zips DEVICE=mlp1 TOOLCHAIN_IMAGE="$toolchain_image" REBUILD_CORES=1

The release command builds missing MLP1 components, assembles the launcher and platform payload, packages standalone emulators and first-party apps, and asks miniloong-launcher-switcher to generate the stock loong_upgrade install and recovery payloads.

Local release-zips builds use the dev channel. ScreenScraper works when you provide developer credentials and is disabled when you do not. To enable it, copy Jawaka/.env.example to Jawaka/.env.local and set SCREENSCRAPER_DEV_ID and SCREENSCRAPER_DEV_PASSWORD, or export both variables in your environment. Keep .env.local private. Beta and stable ZIPs require both credentials and stop during preflight if they are missing. The build log, the install ZIP's platform manifest, and leaf-update.json record whether ScreenScraper was enabled; the credential values are never written there.

Output is written to:

build/release/leaf-mlp1-sd-<release_id>.zip
build/release/leaf-mlp1-recovery-<release_id>.zip

<release_id> defaults to the current date plus the Leaf git short SHA for untagged local/development builds. To choose it explicitly:

make release-zips DEVICE=mlp1 RELEASE_ID=2026-06-05-test1

Published builds use the Git tag as their exact release ID. Stable builds require an explicit semantic version and matching tag:

make release-zips DEVICE=mlp1 \
  RELEASE_ID=v0.7.0 \
  LEAF_RELEASE_CHANNEL=stable \
  LEAF_RELEASE_VERSION=0.7.0 \
  LEAF_RELEASE_TAG=v0.7.0

Both channels have a guarded one-input target. Prefer these over setting the four values by hand:

make stable-zips TAG=v0.9.0 DEVICE=mlp1
make beta-zips   TAG=v0.9.0-beta.1 DEVICE=mlp1

stable-zips accepts only a bare vX.Y.Z and publishes to the main Leaf repository; beta-zips accepts only vX.Y.Z-beta.N and publishes to Leaf-beta. Each derives the complete identity from the tag and verifies the built artifact afterwards.

For beta or stable ZIPs, set TOOLCHAIN_IMAGE to the full local sha256: image ID above and provide your ScreenScraper developer credentials. Run the channel preflight before building, for example:

make release-preflight DEVICE=mlp1 LEAF_RELEASE_CHANNEL=beta \
  TOOLCHAIN_IMAGE="$toolchain_image"

Preflight checks that the image contains the same MLP1 build flags as your mlp1-toolchain checkout and that all 31 stock-parity cores are cache hits. The tagged build then assembles a report with 31 reused and 0 compiled cores. These channels reject REBUILD_CORES=1 and FORCE_REBUILD_CORES=1. The selected image ID is recorded in provenance/components.json; mlp1-toolchain/README.md shows how to pull the published image and resolve its ID.

Before building a tagged release, run the Publish corresponding source workflow in Yabasanshiro-standalone with the Leaf tag. If that repository's main already has a published source release, the workflow reuses it: nothing is rebuilt or uploaded, and the Leaf tag is added to that release's Leaf releases: notes line. Pass the reused tag, which the run summary names, to the Leaf build as YABASANSHIRO_SOURCE_TAG. Otherwise the workflow publishes new source under the Leaf tag, and the override can stay unset. If you revise the standalone source while that Leaf release is still unpublished, publish an immutable source revision tag and pass it as YABASANSHIRO_SOURCE_TAG. The source tag identifies corresponding source only; it does not change the Leaf release identity. The workflow publishes the exact GPL source archive and checksum, but no emulator binary. Leaf downloads and hashes that archive, packages the runtime, and records its tag, commit, direct asset URL, and SHA-256. Tagged Leaf builds fail closed if the source tag is missing, points at a different standalone commit, lacks its checksum sidecar, or serves an archive that does not match the published checksum. Untagged development builds record the public source repository without requiring a release asset.

Both build the install and recovery ZIPs and verify the embedded provenance plus leaf-update.json. Release-candidate tags are a separate main-repository/local-manifest rehearsal lane and are intentionally not accepted by beta-zips.

A tagged build carries four related values:

Value Role
RELEASE_ID Exact artifact name, on-card release directory, and OTA equality identity.
LEAF_RELEASE_VERSION Installed Device Info label and semantic Pak Rat compatibility identity. Prerelease suffixes remain visible; compatibility compares the MAJOR.MINOR.PATCH core.
LEAF_RELEASE_CHANNEL Build/publication policy and default release-notes repository. The device's own update-channel setting chooses which feed it checks.
LEAF_RELEASE_TAG GitHub publication reference recorded in provenance.

All tagged install builds fail before packaging if Leaf, the launcher, the launcher switcher, Catastrophe, or a bundled app has uncommitted changes. Tagged builds also require RELEASE_ID to match LEAF_RELEASE_TAG. Exact component commits are recorded inside the release at provenance/components.json. Unqualified local builds use the dev channel and may fall back to RELEASE_ID for version. LEAF_RELEASE_REPOSITORY can override the default host when calling release-zips directly; beta-zips pins it to Leaf-beta.

The install ZIP is extracted directly to the SD-card root. It must not be placed inside another folder. The SD card should be FAT32 or ext4; do not use exFAT because the MLP1 stock update path ignores exFAT media.

End-user flow:

  1. Extract leaf-mlp1-sd-<release_id>.zip to the SD-card root.
  2. Boot the MLP1 with the SD card inserted.
  3. Wait on the stock update screen while the installer runs. The progress indicator may sit at 50 percent while files are copying.
  4. Wait for the device to reboot by itself.
  5. Boot normally with the SD card inserted; Leaf should start automatically.

The install package does not silently enable or pin ADB. ADB can be enabled later from Leaf/Jawaka settings.

To return a device to stock boot without ADB, extract leaf-mlp1-recovery-<release_id>.zip to the SD-card root and boot the device once with that card inserted. Wait for the device to reboot by itself. Recovery removes the Leaf hook/session and leaves SD-card user content intact.

Ownership

Leaf owns deployment and orchestration:

  • Bootstrap and repo discovery.
  • Cross-repo status.
  • Environment and device preflight.
  • Launcher payload assembly.
  • SD-card path resolution.
  • ADB staging.
  • Activation marker control.
  • Launcher restart and log-tail helpers.
  • End-user SD install/recovery ZIP generation.

Sibling repos own their build/package outputs:

  • Jawaka builds launcher binaries.
  • Catastrophe builds shared assets.
  • retroarch-builds builds/packages RetroArch.
  • Cores-spruce builds libretro cores and info files.
  • App repos package their .pak directories.
  • mlp1-toolchain owns the cross-compile Docker image.

The runtime stock-firmware switcher mechanism remains in miniloong-launcher-switcher.

SD Layout

Leaf stages launcher-owned internals under one hidden SD root:

$SDCARD_PATH/.system/leaf/platforms/mlp1/enabled
$SDCARD_PATH/.system/leaf/platforms/mlp1/launcher/env.sh
$SDCARD_PATH/.system/leaf/platforms/mlp1/launcher/bin/...
$SDCARD_PATH/.system/leaf/platforms/mlp1/bin/
$SDCARD_PATH/.system/leaf/platforms/mlp1/cores/
$SDCARD_PATH/.system/leaf/platforms/mlp1/shaders/ # validated release source
$SDCARD_PATH/.system/leaf/platforms/mlp1/emulators/
$SDCARD_PATH/.umrk/mlp1/                       # launcher control state (library.db, wifi.conf, ...)
$SDCARD_PATH/.umrk/mlp1/retroarch/.config/retroarch/shaders/
                                                # durable Leaf/updater/custom shader browser root
$SDCARD_PATH/.umrk/mlp1/adb-enabled
$SDCARD_PATH/.userdata/mlp1/logs/              # durable user/app data + logs
$SDCARD_PATH/.userdata/shared/

Everything under .system/leaf/platforms/mlp1 is release-managed (replaced on install/update). Durable state lives at the SD root under .umrk/ and .userdata/, so a manual upgrade never overwrites it.

state/adb-enabled is Jawaka's durable request for the Leaf init hook to restore the stock ADB pin at boot. The launcher activation marker is separate: platforms/mlp1/enabled.

User-facing content folders remain at the SD root:

Roms/
Images/
Apps/
  mlp1/<Name>.pak/
  shared/<Name>.pak/
BIOS/
  SATURN/            # Saturn firmware for RetroArch and standalone selection
Saves/
States/
Cheats/

Apps/ is a namespace root. Leaf stages native apps under Apps/<platform>/ and wrapper/runtime-delegating apps under Apps/shared/; flat Apps/<Name>.pak/ entries are not part of the Jawaka discovery contract.

Roms/ and Images/ use one canonical public folder per console — one folder per system, not one per emulator. Legacy alias folders (e.g. Roms/FC for NES) still scan and fold into the canonical system; emulator variants are a launcher Core choice, not a separate folder. The canonical folder per system plus its accepted aliases come from the staged platform systems.json, driven by Leaf/scripts/system_folder_policy.json; the release path enforces the invariant via retroarch_validate_package.py --canonical-systems.

The current first-party app policies are:

ssh-server        -> Apps/mlp1/SSHServer.pak/
Thing-File        -> Apps/mlp1/Thing-File.pak/
CentralScrutinizer -> Apps/mlp1/CentralScrutinizer.pak/
Fugazi            -> Apps/mlp1/Fugazi.pak/
retroarch-builds  -> Apps/shared/RetroArch.pak/

Standalone game emulator payloads are staged under .system/leaf/platforms/mlp1/emulators/. PPSSPP is built from the UMRK PPSSPP-spruce sibling repo. Mupen64Plus standalone is built from the N64-standalone sibling repo and launched as a selectable path core for the canonical Roms/N64 library. Flycast standalone is built from the Flycast-standalone sibling repo and initially offered as a selectable alternate for the canonical Roms/DC library; RetroArch Flycast remains the default during compatibility soak. DraStic is packaged from the local steward-fu-nds sibling repo: Leaf bundles the cross-built steward SDL2 custom menu stack with the prebuilt drastic64 binary, keeps runtime state under state/drastic/, and runs it through SDL_VIDEODRIVER=NDS. Single-emulator ADB staging also refreshes the platform defaults that Jawaka scans for packaged standalone launch targets. DraStic's in-game menu is handled by the steward custom menu, with Jawaka passing the MLP1 Menu button through to the emulator.

This is a hard cutover from the old umrk-launcher, UMRK, .umrk, .userdata, and .umrk-launcher layout. Leaf does not migrate or delete old folders automatically.

About

Deployment orchestrator for the UMRK launcher workspace

Topics

Resources

Stars

48 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages