A complete, reproducible operating system for the MiSTer DE10-Nano — kernel, root
filesystem, a real-time kernel variant, a flashable SD-card image, and the update channel
that delivers them. Built in the open from a modern Buildroot and a mainline LTS kernel,
with every MiSTer kernel patch carried in-tree as a plain .patch file applied to a
pristine, hash-verified kernel.org tarball.
It is a drop-in replacement: the unmodified, stock MiSTer binary and every existing
core run on it unchanged.
| Artifact | What it is |
|---|---|
linux.img + zImage_dtb |
The OS itself — rootfs and kernel, delivered as a normal update through the stock on-device Downloader |
zImage_dtb-rt |
A PREEMPT_RT kernel variant, built by CI. Its modules ride inside the same linux.img, so switching kernels is a one-line u-boot.txt edit in either direction — no rootfs flash |
sdcard.img.xz |
A complete, flashable SD card that self-expands to the card's real size on first boot — no mr-fusion, no Windows SD installer |
release_YYYYMMDD.7z |
The stock-layout release archive, byte-compatible with the Downloader's expectations |
legal-info.tar.gz |
A full SBOM — every package, version, license, and upstream source tarball |
SHA256SUMS + attestations |
Build provenance for the image assets |
db.json |
The opt-in update channel, served from GitHub Pages — drop one script into Scripts/, run it, and releases arrive through the standard on-device Downloader |
| An exportable kernel tree | The carried series, rendered deterministically into Linux-Kernel_MiSTer layout so upstream can consume the work without a second fork |
Status — personal use only. Phases 0–3 are complete with hardware validation on a real DE10-Nano; Phase 4 (release engineering & sustainability) is in progress. Nothing here will be offered publicly until the sustainability commitment in ADR 0014 is signed by a named maintainer. If you are trying it anyway, read
docs/user/beta-testing.mdfirst.
- What ships
- The one-paragraph version
- Stock vs. this image, at a glance
- Project status
- Install it on a real MiSTer
- What this improves
- What is not better — the honest list
- How it is put together
- Building it yourself
- Verification & CI
- Releases & distribution
- Goals
- Documentation map
- Contributing
- License layering
MiSTer's operating system ships as an opaque archive containing a 375 MiB ext4 image
(93% full) built from Buildroot 2021.02.4 with glibc 2.31, running Linux
5.15.1 — a kernel forked in November 2021 that has never merged a single 5.15.y
stable release. There is no public build recipe, no CI, no SBOM, and no update path for
any of it. This project rebuilds the whole thing from Buildroot 2026.05.2 and a
mainline 6.18 LTS kernel in a public repository, with reproducible builds, a
signed-hash supply chain, a nine-workflow CI pipeline, and a per-commit reconciliation
of the entire kernel fork — then ships it through the same update channel users already
have, alongside a real-time kernel variant and a flashable card image that stock has no
equivalent of. All of it boots the unmodified, stock MiSTer binary and every
existing core.
The core posture: prove parity first, then improve. Carry the smallest possible delta against a pristine kernel.org tree, and hand each subsystem back to mainline as soon as mainline can hold it.
| Stock MiSTer | This project | |
|---|---|---|
| Kernel | 5.15.1, forked Nov 2021, zero 5.15.y stable updates ever merged; 5.15 EOL Oct 2026 |
6.18 LTS, on a live .y line with security backports |
| Kernel delta | 110 commits on a squashed-import fork with no shared ancestry with mainline — so no merge-base, and no per-commit disposition |
37 patch files against a pristine tarball, each with provenance, upstream status, and an evidence-backed record |
| Buildroot | 2021.02.4 | 2026.05.2 (~5 years of upstream work) |
| glibc / gcc | 2.31 / gcc 10-era | 2.43 / 14.4.0 |
| OpenSSL | 1.1.1 — EOL since 2023-09-11, no upstream fixes since | 3.6.3 |
| OpenSSH | 8.6p1 | 10.5p1 |
| Samba | ~4.14 | 4.24.5 |
| Python | 3.9 | 3.14.7 |
| SSH host keys | Identical on every MiSTer on Earth, baked into the public download, dated 2016 | Generated per device on first boot, persisted to the FAT card (ADR 0015) |
| Timezone on a fresh card | Unset — /media/fat/linux/timezone does not exist, so glibc falls back to UTC and stays there until the user finds the community timezone.sh script |
Detected once, from the box's public IP, using the same provider and the same destination file as timezone.sh so the two are interchangeable. It is one dhcpcd hook, firing on the first connection the box makes — so a card that was offline on its first boot still gets it once Wi-Fi is set up, and a box with no network does nothing at all. Never overwrites a timezone you set, and it is opt-out-able before it ever runs (ADR 0025) |
| Wi-Fi chipset coverage | Six out-of-tree vendor forks, plus in-kernel mac80211 USB drivers for MediaTek/Ralink/Marvell/older Realtek; no Broadcom, no Wi-Fi 6/6E, no Atheros USB Wi-Fi, no Redpine; several chips can't do WPA3 at all |
Mainline-first: in-kernel mac80211 (rtl8xxxu/rtw88/rtw89/mt76/ath9k_htc/rsi…) for every chip mainline can drive that is worth building here — six symbols are deliberately left off (one upstream itself calls non-working, four 2000s-era 802.11b/g parts, one that is optical LiFi rather than Wi-Fi; wifi-parity §7) — plus Broadcom/Cypress and Wi-Fi 6/6E support stock never had. One out-of-tree driver remains, for a Wi-Fi 6E chip mainline has no USB driver for at all. WPA3/SAE hardware-verified; see the hardware-support table for the full build- vs. hardware-verified breakdown (ADR 0016) |
| Bluetooth firmware | ath3k shipped with no ath3k-1.fw and no ar3k/*.dfu at all; no MediaTek MT79xx and no Qualcomm QCA firmware (its Realtek combos were covered — stock-fw.txt carries rtl8761bu, rtl8822b, rtl8822cu, rtl8821c) |
Firmware audited driver-by-driver against what each one actually requests at runtime, gaps closed (MediaTek combo, Qualcomm QCA Rome USB, one Broadcom .hcd, Atheros AR3011/AR3012) — including one asymmetry this project itself introduced: we shipped the MT7961 Wi-Fi blob without its Bluetooth sibling, so that combo dongle's Wi-Fi worked and its BT did not. Found and closed. Two built drivers still ship with no firmware, deliberately and on the record (bluetooth-parity §9) |
The .7z extractor that unpacks every OS update |
p7zip 16.02 — dated 2016-05-21, in two places: usr/bin/7zr in the rootfs, and /media/fat/linux/7za, which the Downloader downloads off the internet the first time it updates and then reuses forever |
7-Zip 26.02, built from source in-tree. The /media/fat/linux/7za copy is shipped by us — statically linked, so it survives a rollback to an older or even stock rootfs — so that download never happens (ADR 0023) |
| NTFS | Not supported at all | ntfs3 in-kernel module + ntfs-3g automount |
| Image size | 375 MiB, 13.6% free ("93% full") | 512 MiB, 39.5% free — 310 MiB used / 202 MiB free, temporary debug block included, measured by ./scripts/check-size-budget.sh output/images/linux.img on the image built 2026-07-27 with this branch's full package set (the T5 utilities, 7-Zip, and the rtl8852cu driver all included). Floor is 15%. docs/size-budget.md still reports 60.6% free: it is a P3.3-vintage report and needs regenerating |
| Rootfs build recipe | Not published. MiSTer-devel/Linux_Image_creator_MiSTer exists, but it is an assembler, not a builder: create_img.sh runs mkfs.ext4 and untars a prebuilt 82 MiB rootfs.tar.bz2 committed to git. Nothing public turns source into that tarball, so the image can be re-packed but its contents cannot be changed, patched or audited beyond ls |
This repository |
| Reproducible | No published recipe, so unverifiable | Byte-identical across independent builds, proven by CI (docs/reproducibility.md) |
| SBOM / license manifest | None published | Full legal-info bundle published with every release |
| Dependency updates | Manual, ad-hoc | Renovate, covering kernel pins, firmware, and every package hash |
| Real-time kernel | No | PREEMPT_RT variant built by CI, booted on hardware |
Every number in that table is sourced. The versions come from the shipped artifacts on
both sides — stock from the extracted stock linux.img, ours read off the built tree.
On version drift, since this table cites documents that can lag it. The ground truth
for "ours" is always the build pins — BUILDROOT_VERSION in the Makefile and
configs/mister_de10nano_defconfig — plus whatever Buildroot's own .mk files resolve
to at that pin. The documents below are dated analyses, not a live mirror of those
pins: a Renovate bump or a Buildroot line bump moves a package without rewriting the
prose that reasoned about it. Where a document is behind the pin it now says so at the
top, and lists what has and has not been re-checked. Nothing in CI asserts package
versions, so that gap is closed by reading, not by a test — see
the honest list.
Full detail with citations: docs/version-delta.md (the stack,
re-read most recently), docs/package-manifest.md (the
251-SONAME mapping, established against Buildroot 2026.02.3),
docs/verification/stock-release-20250402.md
(the stock side, fixed forever — stock does not move).
| Phase | State | What that means |
|---|---|---|
| 0 — Recon & decisions | ✅ Complete | Patch triage, ABI-contract verification, five open questions decided (ADRs 0010–0014) |
| 1 — Kernel & initramfs | ✅ Complete | 6.18 LTS pinned; all 37 patches apply cleanly; zImage_dtb builds warning-free, boots under QEMU and on real hardware — from the CI-built artifact, not a local build |
| 2 — Rootfs & testing | ✅ Complete | Buildroot 2026.05.2, glibc 2.43, reproducible ext4 image with full SBOM; menu and cores load on hardware — the ABI contract holds in practice, not just on paper |
| 3 — Module packages & HW matrix | ✅ Complete | Wi-Fi, Bluetooth, controllers and special devices packaged; hardware-validated for the chips actually present on the one test board. The v10/v10.1/v10.2 driver + firmware expansion (Broadcom, Wi-Fi 6/6E, MediaTek, Atheros USB, Redpine) is packaged and mostly CI-asserted but not hardware-validated — see the ledger and the chipset table. The remaining matrix rows (Samba, MIDI) are build/CI-verified only |
| 4 — Release & sustainability | 🔄 In progress | CI/CD, db.json distribution, beta program, governance, publication gate |
| 5 — Full SD image & U-Boot | 🔄 Partially landed | sdcard.img builds, and release.yml verifies it with scripts/check-sdcard.sh (ADR 0020); U-Boot-from-source is planned but not started — now targeting mainline U-Boot, not the 2017.03 fork (ADR 0024, plan, tasks) — and the SD image has not been flashed to a fresh card on hardware (P5.4) |
Validated on one real DE10-Nano, booting CI-built artifacts:
| Subsystem | Status |
|---|---|
| Boot to MiSTer menu, cores load | ✅ Confirmed |
| All out-of-tree modules present, no BUG/Oops/panic | ✅ Confirmed |
| Bluetooth — firmware load + controller pairing | ✅ Confirmed |
Wi-Fi — WPA3/SAE (PMF required) 5 GHz auto-connect via mainline rtw88 |
✅ Confirmed |
| Downloader over HTTPS | ✅ Confirmed |
PREEMPT_RT kernel boots and runs MiSTer |
✅ Confirmed on 7.2-rc4 (2026-07-20) and 7.2-rc7 (2026-08-14). The pin has since moved to 7.2 final, which builds green (make rt, 2026-08-17) but has not been booted — boot is a per-version claim |
| Samba, MIDI | |
| RT latency measurement | ⏳ Not yet taken |
Test logs live in docs/testlogs/. Anything not listed above should be
treated as unverified in practice — that includes most of the
Wi-Fi/Bluetooth chipset table below: this ledger's
"all out-of-tree modules present" row is a snapshot from before the current branch's
driver/firmware expansion (Broadcom, Wi-Fi 6/6E, and the rest), which has not yet had its
own hardware pass.
One command, on the MiSTer itself, over SSH. This converts an ordinary install — including one flashed with Mr. Fusion — to this image. It works on a stock card; you do not need to reflash anything.
curl -fsSL https://raw.githubusercontent.com/mcfbytes/Buildroot_MiSTer/master/install.sh | bashOn a stock card the one-liner above will fail, and fail quietly. Verified on real hardware: stock's
curlcannot verify GitHub (curl: (60)),bashgets an empty pipe, and the pipeline still exits 0 — so it looks like nothing happened at all.The cause is not an expired bundle. Stock ships exactly one file in
/etc/ssl/certs/,cacert.pem, and it works fine — it simply is not in the hashed-symlink layout curl searches by default, so curl finds no issuer. Passing--cacertexplicitly fixes it, which is whatScripts/update.shhas always done.So fetch the installer with
-kfor that one request and let it take over — it detects this, verifies the card's own bundle actually works before adopting it, and uses it for everything afterwards:curl -fsSLk https://raw.githubusercontent.com/mcfbytes/Buildroot_MiSTer/master/install.sh -o /tmp/mlm.sh \ && sh /tmp/mlm.shIf you would rather not use
-kat all, downloadinstall.shon a machine whose TLS works, check itssha256sumagainst the repo, and copy it onto the card. And if a card ever turns up whose bundle is genuinely broken rather than merely mislocated,install.sh --bootstrap-cafetches a fresh one to/tmpfor a single run — it explains that trade-off in full before doing anything.You only need any of this once: this image ships a current CA bundle in the layout curl expects, and refreshes it on every release.
wgetis not an alternative. Thewgeton a MiSTer is the BusyBox applet with no TLS support at all — it answers everyhttps://URL withnot an http or ftp url. Stock ships a realcurl, and MiSTer's ownupdate.shuses it exclusively.
Prefer to read before you run — the better habit, and this script is written to be read:
curl -fsSL https://raw.githubusercontent.com/mcfbytes/Buildroot_MiSTer/master/install.sh -o mlm.sh
less mlm.sh
sh mlm.sh --dry-run # prints exactly what it would change, touches nothing--dry-run prints the full plan and exits. --yes skips the 10-second countdown,
--no-reboot leaves the reboot to you.
Read
docs/user/beta-testing.mdfirst. This is a personal project with an unmet sustainability gate (ADR 0014), validated on one board. The ledger directly above is the honest account of what has and has not been tested on hardware.
The installer prints all of this and pauses before doing anything.
linux/linux.img, linux/zImage_dtb |
Replaced — the root filesystem and kernel. This is the point |
linux/7za |
Replaced — 7-Zip 26.02 instead of the 2016 p7zip |
downloader.ini |
One key changed: [MiSTer] update_linux = false. A surgical edit; your comments, sections and databases are left alone, and the original is saved to linux/.mlm-backup/downloader.ini.orig. Created (declaring the official database) only if you don't have one |
Scripts/update_linux_modernization.sh |
Installed — this is what updates the image from now on |
Scripts/check_storage.sh, Scripts/pair_logitech.sh |
Installed — two small launchers for tools that live in the image (exFAT check, Logitech pairing) |
Everything else under linux/ is rewritten with byte-identical content. Our release
archive is the stock archive with those three files swapped in, so MidiLink.INI,
ppp_options, uboot.img, updateboot, the _-prefixed templates, mt32-rom-data/ and
soundfonts/ come back exactly as they were. It is a no-op unless you had customised one
— and the small ones are copied to linux/.mlm-backup/ first in case you had.
Two side effects, both caused by any Linux update rather than by this one: the saved
U-Boot environment (first 512 bytes of the card) is wiped by updateboot, and SSH host
keys change, because this image generates them per device instead of shipping one set to
everybody (ADR 0015) — expect a
one-time host-key warning.
Afterwards, routine updates stop touching /media/fat/linux/ altogether.
update_linux = false means no Linux update runs unless you run the updater, so these
files end up more stable than on stock, where every official Linux update rewrites them.
MiSTer.iniand every core.ini— not touched. Norgames/, ROMs, saves, states,config/, cores or_Arcade/.linux/gamecontrollerdb/— explicitly excluded from the sync.- Your live
u-boot.txt(holds this card's MAC),wpa_supplicant.conf,user-startup.sh,samba.sh— only the_-prefixed templates ship, so the real ones survive. linux/{hostname,hosts,interfaces,resolv.conf,dhcpcd.conf,fstab}— copied into the new image before it goes live, so your network identity carries over.
Your cores keep updating normally — update_all.sh and Scripts/update.sh simply stop
touching the Linux image, in either direction. That is what stops the official image
overwriting this one; see ADR 0025.
Updating this image is one deliberate action:
/media/fat/Scripts/update_linux_modernization.sh # update
/media/fat/Scripts/update_linux_modernization.sh --status # where am I?To undo, at any time: update_linux_modernization.sh --restore-stock, then run your
normal update. Full procedure in docs/user/rollback.md.
- A fresh card: flash
sdcard.img.xzfrom a release. It self-expands on first boot and arrives already configured, with Jotego's cores enabled. Seedocs/user/sdcard-flashing.md. - By hand:
docs/user/onboarding.mdwalks through the same steps individually, if you would rather not run an installer at all.
You are piping a remote script into a shell as root, which is worth a moment's thought.
It is the same trust model MiSTer's own Scripts/update.sh already uses — it fetches
dont_download.sh from raw.githubusercontent.com and executes it — and the anchor is
HTTPS to GitHub. The installer does not pin a hash of the updater it downloads: both
come from the same repository over the same TLS connection, so a pin would add no real
assurance while guaranteeing the file goes stale. If that trade isn't one you want to
make, use the download-and-read form above, or the by-hand route.
Stock forked Linux 5.15.1 in November 2021 and never took a single subsequent 5.15.y
stable release. 5.15 itself reaches end-of-life in October 2026. This project tracks
6.18 LTS — the exact patch level is
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE in configs/mister_de10nano_defconfig,
and it is deliberately not repeated in prose here because stable .y releases
land weekly. Pinned by version and SHA-256 against kernel.org, with
Renovate opening a PR on every .y bump.
The interesting part is not the version number — it's the shape of the delta. The
fork's 126 reconciled commits (110 on the shipped MiSTer-v5.15 branch, 1 on
upstream's own MiSTer-v6.18 branch, plus 15 residue commits that existed only on the
older v5.14/v5.13.12 branches) are down to 36 carried patch files. Every remaining
drop is either verifiably in mainline 6.18, replaced by a maintained package, or
recorded as a deliberate decision. A 37th file, 0047, is not part of that delta at
all — it backports a mainline commit (ce21a5cf3d1f, first released in 7.2) that the
6.18.y line never received.
Every commit in the fork was independently reconciled, each with a machine-readable,
evidence-backed disposition record, 100% of them verified by a second independent
review pass (docs/kernel-recon/). That reconciliation caught and
closed several silent gaps the original triage had misclassified — Joy-Con combining, the
DualSense player-ID and mic-mute/BTN_Z interfaces, stock NES/Famicom A/B button mapping,
Pro-Controller-clone tolerance, fake-CSR Bluetooth dongle detection, and two config drifts
(macvlan, xpad-as-module) — now carried as patches 0032–0037 plus config parity.
Because the kernel is stored as {pinned tarball + ordered series} rather than as a
materialized tree, scripts/export-kernel-tree.sh can
render it back into a Linux-Kernel_MiSTer-style git tree deterministically — the export
is a build output, not a second source of truth, so upstream can consume the work
without this repo forking the kernel a second time. A separate
linux-patches-upstream/ series carries what that exported tree needs but our image
deliberately does not (the loop= boot patch — see below).
These are not forward-porting regressions. Every one is present verbatim in the 5.15 fork, and therefore in every MiSTer image shipped to date. They surfaced only because forward-porting forces you to actually read code that has been copied forward untouched since 2021.
| Where | The bug | Status | |
|---|---|---|---|
| B1 | GameCube adapter | Use-after-free on unplug. Teardown cancels the rumble work item but never the four per-port work_connect items, then kfree()s the adapter the handler container_of()s back into. Same defect on the probe error path. |
✅ Fixed |
| B2 | MiSTer_fb |
memremap() returns NULL on failure, not an ERR_PTR — the code tests IS_ERR(), which is false for NULL, so a failed mapping falls through with screen_base unset. Oops on the first fbcon draw. |
✅ Fixed |
| B3 | MiSTer audio SPI | class_create()/device_create() return ERR_PTR, never NULL — so the == NULL checks are dead code and a failed device_create() was treated as success. |
✅ Fixed |
| B4 | MiSTer audio SPI | Bogus diagnostics on SPI failure: the ring length is computed from a -1 read, so the status string you read to diagnose a broken SPI link is itself wrong — precisely when you need it. |
✅ Fixed |
| B5 | Logitech K400 Fn | The fork's own patch reuses SetFeature at the wrong feature index, writing the wrong HID++ feature. |
✅ Fixed |
| B6 | Cyclone V cpufreq | wait_for_fsm() passes a mask where wait_on_bit() wants a bit number, so it polls bit 1 instead of bit 0 — and does a plain test_bit() on __iomem. Harmless today only because the call returns immediately. |
B6 is not fixed on purpose: making it actually wait changes the timing of a live PLL reprogramming sequence, and a forward-port is the wrong place to smuggle in an untested change to clock sequencing. It is recorded, not hidden.
Notably, all six live in MiSTer-original or fork-modified code. None is in mainline code. That is the sharpest argument for the small-delta posture this project takes.
Full write-up with the reasoning for each: docs/patch-provenance.md §10.
- OpenSSL 1.1.1 → 3.6.3. Stock ships a TLS library that has been end-of-life since 2023-09-11 and has received no upstream fixes since. This is the single strongest security argument for the whole project, and it is a plain, checkable fact.
- Per-device SSH host keys. Every stock MiSTer ships the same host keys, baked into
the public release archive, dated 2016-12-31, on a read-only root that makes
ssh-keygen -Aa no-op. The private host key of every MiSTer on Earth is public and identical — so SSH server impersonation is trivial and produces no host-key warning. This image generates unique keys on first boot and persists them to an ext4 image on the FAT partition, reusing stock's own proven mechanism for Bluetooth pairing keys. (ADR 0015) - OpenSSH 8.6p1 → 10.5p1, Samba ~4.14 → 4.24.5, BlueZ → 5.79, wpa_supplicant 2.9 → 2.12 — the network-facing surface, several release cycles of hardening each.
- Python 3.9 → 3.14.7 — the on-device interpreter that runs the Downloader and
community scripts (compatibility tested; see
docs/python-compat.md). - An actual update path. Renovate tracks the kernel, firmware, and every pinned package; CI proves each bump still builds and still passes the parity suite. Stock's security model is "the image is frozen."
What has not changed: the root password is still 1, deliberately, for stock
parity. That is publicly known and always has been. See
the FAQ for the
plain-language version of what that means on an untrusted network.
| Capability | Stock | Here |
|---|---|---|
| exFAT | Out-of-tree Samsung driver (also handled FAT12/16/32) | Mainline exfat, with the Samsung symlink extension carried as patch 0031 (ADR 0010, ADR 0019) |
| exFAT repair | None. exFAT has no journal, the card is never cleanly unmounted (even the OSD's "Reboot" is a raw HPS reset-controller write), so lost clusters accumulate and nothing ever checks | Scripts > check_storage.sh — a read-only scan while running, and, only if it finds something and you confirm, a one-shot fsck.exfat on the next boot from the initramfs, the only place a repair can run (ADR 0026) |
| Logitech pairing | None. hid-logitech-dj drives every already-paired device, but the pairing handshake has no kernel interface, so a replacement or second-hand Unifying receiver cannot be bound to a device on-box at all |
Scripts > pair_logitech.sh — enumerates every receiver, refuses the families it cannot drive (Bolt, Lightspeed) by name, and confirms the result from the receiver's own slot table (docs/logitech-pairing.md) |
| NTFS | Not supported | ntfs3 module + ntfs-3g automount via util-linux mount (ADR 0013) |
| USB automount | Debian usbmount 0.0.24 |
Buildroot usbmount, functionally identical, plus NTFS (docs/usb-automount-parity.md) |
| Archive extraction | 7zr = p7zip 16.02 (2016); the updater additionally fetches the same 2016 build over the network as /media/fat/linux/7za |
7-Zip 26.02 as 7zz (+7za), and a static copy shipped to /media/fat/linux/7za so nothing is fetched (ADR 0023) |
| CIFS kernel mounts | No mount.cifs shipped |
cifs-utils included — a deliberate, documented beyond-parity addition for community storage scripts (docs/netfs-parity.md) |
| Wi-Fi | Six out-of-tree vendor forks plus in-kernel mac80211 USB drivers for MediaTek/Ralink/Marvell/older Realtek; no Broadcom, no Wi-Fi 6/6E, no Atheros USB Wi-Fi, no Redpine |
Mainline drivers for every chip mainline can drive that is worth building here (six symbols deliberately left off — see below), plus chipset families stock never supported at all. One out-of-tree driver left — see below |
The short version: if your dongle is Realtek, Broadcom/Cypress, MediaTek, Atheros/Qualcomm, or Redpine, a driver for it is almost certainly built into this image — with two named exceptions, both called out below: the newest Realtek Wi-Fi 6E chip (RTL8852CU — a driver is in the tree, but it is not even build-verified yet) and Qualcomm/Atheros QCA9377 over USB (no driver built at all, deliberately, because upstream says its own driver does not work).
Stock's out-of-tree set was six Realtek chips, each via its own vendor fork —
but that was not all it had: it also carried in-kernel mac80211 USB drivers
for MediaTek (mt7601u, mt76x0u, mt76x2u, mt7663u), Ralink (rt2500usb,
rt73usb, rt2800usb), Marvell (mwifiex_usb, libertas/usb8xxx) and
older Realtek parts (rtl8xxxu, rtl8192cu, rtl8187), per
stock-mods.txt.
What it had none of was Broadcom, Wi-Fi 6/6E, Atheros USB Wi-Fi, or Redpine — and
for at least one chip it did have a driver for, it shipped no firmware, so
the driver probed and then failed (MT7663U; same story for ath3k on the
Bluetooth side).
Getting there was a from-scratch, 36-symbol audit of
every USB Wi-Fi driver Linux 6.18 offers, cross-checked against every USB
device-ID table in drivers/net/wireless/ so the answer is evidence, not
recollection (docs/wifi-parity.md §7), plus the same
method applied a second time to Bluetooth firmware
(docs/bluetooth-parity.md §9).
Read the last column before the rest — it says how far each row was actually checked, and most of this table is newly added, unreleased hardware support that has not yet had a device plugged into it:
| If your dongle uses… | Stock MiSTer | This image | Checked how far? |
|---|---|---|---|
| Realtek 802.11n/ac — RTL8188EU/8188FU, RTL8710BU, RTL8811AU/8821AU, RTL8812AU, RTL8814AU, RTL8821CU, RTL8822BU/8822CU, RTL8723DU | Out-of-tree vendor fork for six of them (RTL8188EU, RTL8188FU, RTL8811AU/8821AU, RTL8812AU, RTL8821CU, RTL8822BU — ADR 0016, stock-mods.txt); several of those can't do WPA3 at all. No driver at all for RTL8710BU, RTL8814AU, RTL8822CU, RTL8723DU: none of the six forks covers them, stock-mods.txt carries no rtw88 module, and stock's own kernel branch (MiSTer-v5.15) has an rtl8xxxu/ with only the 8192c/8192e/8723a/8723b chip files — no 8710b |
In-kernel rtl8xxxu / rtw88 (mac80211) |
Hardware-verified: an RTL8822BU auto-connects to a WPA3-only 5 GHz network at boot. (The fork it replaced advertised SAE but failed WPA3-only association with status_code=1 — hardware-verified regression the switch fixed, ADR 0016.) Every other chip in this row builds the same way but has not been tested against real hardware. |
| Realtek Wi-Fi 6 — RTL8851BU, RTL8852BU | Not supported | In-kernel rtw89 |
Build-verified only |
| Realtek Wi-Fi 6E — RTL8852CU / RTL8832CU | Not supported | Out-of-tree rtl8852cu-morrownr — the only driver that exists for this chip on USB anywhere, in or out of tree: mainline's rtw89 has the chip's radio HAL but only a PCIe bus file, and this board has no PCIe |
Not yet built, let alone tested — added by reading the driver's source, not compiled (docs/wifi-parity.md §8) |
| Broadcom / Cypress — BCM43xx, CYW43xx USB adapters | Not supported at all | In-kernel brcmfmac |
Build-verified only |
| MediaTek Wi-Fi 5 — MT7663U (802.11ac) | Driver was shipped (mt7663u, mt7663-usb-sdio-common, mt7615-common are all in stock-mods.txt) — but none of the four firmware files it requests (stock-fw.txt has no mt7663* at all), so it probed and then failed at request_firmware() |
Same in-kernel mt76 driver, plus the four mediatek/mt7663* blobs it actually loads (wifi-parity §6.2) |
Build-verified only |
| MediaTek Wi-Fi 6 / Wi-Fi 7 — MT7921U (802.11ax), MT7925U (802.11be, 6 GHz-capable) | Not supported | In-kernel mt76 (mt7921u / mt7925u) |
Build-verified only |
Atheros — AR6003/AR6004, AR9271/AR9287 (ath9k_htc), AR9170 (carl9170) |
Not supported | In-kernel | Build-verified only |
| Redpine RS9113 / RS9116 | Not supported | In-kernel rsi |
Build-verified only |
| RTL8192DU (dual-band 802.11n) | Not supported | In-kernel rtlwifi |
Build-verified only |
Bluetooth — MediaTek MT7921/MT7925 combo, Qualcomm QCA Rome 6174A (USB), one Broadcom .hcd, Atheros AR3011/AR3012, plus Realtek RTL8761B/BU and every Wi-Fi+BT combo chip above |
ath3k built with no ath3k-1.fw, no ar3k/*.dfu; no MediaTek MT79xx or Qualcomm QCA blobs. Its Realtek combos were covered |
Firmware shipped for every Bluetooth driver here where an upstream blob exists and the hardware is reachable on this board. Two built drivers deliberately ship without it: Intel btintel (30 MiB of intel/ibt-* for silicon that only comes on M.2 cards this board cannot host — CONFIG_BT_HCIBTUSB selects the driver unconditionally, so it cannot be turned off) and bcm203x (a ~2003 BCM2033 whose BCM2033-MD.hex/BCM2033-FW.bin upstream linux-firmware does not carry at all). Both are recorded in bluetooth-parity §9's "Deliberately NOT shipped" |
General Bluetooth pairing is hardware-verified; these specific chipset gaps were closed by reading each driver's firmware-request code, not by plugging one in |
One deliberate exclusion, not an oversight. ATH10K_USB (Qualcomm/Atheros
QCA9377 over USB — e.g. the Linksys WUSB6100M) is the one modern 802.11ac USB
chip this image does not build a driver for. Upstream's own kernel Kconfig
calls it out: "Currently work in progress and will not fully work." Binding
a dongle to a driver that then fails is worse than binding it to nothing, so
it stays off. Every other driver family the audit found — 30 of 36 — is
built. Of the six that are not, this is one; four are 2000s-era 802.11b/g parts
(AR5523 ~2004, AT76C50X_USB ~2001, P54_USB ~2004, ZD1211RW ~2005) with
no realistic install base today; and the sixth, PLFXLC, is pureLiFi optical
hardware that happens to live under drivers/net/wireless/ without being Wi-Fi
at all (docs/wifi-parity.md §7).
Why "build-verified" and "hardware-verified" are called out separately, and
not glossed over: this table represents a large, single-branch expansion —
several new chip vendors, a new Wi-Fi generation, and a Bluetooth firmware
pass — landed and documented before a second device could be sourced and
tested against most of it. The driver source and firmware request paths were
read line-by-line for every entry above (file:line citations in
docs/wifi-parity.md and
docs/bluetooth-parity.md), and scripts/ci-tests.sh
asserts that the newly-added modules and firmware land in the built image:
the seven rtw88 USB modules, brcmfmac+brcmutil, rtl8192du/ath6kl_usb/
rsi_usb, the one permitted out-of-tree 8852cu.ko, and a fixed blob list
(MT7663's four files, ath3k-1.fw, the four brcmfmac*.bin, rtl8192dufw.bin,
two rsi/*.rps, the three MediaTek BT ram-codes, the QCA USB pair, both
.hcds, rtl8761b/rtl8761bu). It does not yet assert rtw89_8851bu/
rtw89_8852bu, mt7663u/mt7921u/mt7925u, ath9k_htc or carl9170, nor
the rtw89/*.bin, htc_9271.fw/htc_7010.fw, carl9170-1.fw and ar3k/*.dfu
blobs those need — all of which do ship, but unguarded. Concretely: the Realtek
Wi-Fi 6 row has no CI presence check at all; the MediaTek Wi-Fi 5 row has its
firmware checked but not its module; the MediaTek Wi-Fi 6/7 row has neither; and
the Atheros row is covered only for ath6kl_usb, not for ath9k_htc/carl9170.
And even where CI does check, a file landing in the image is not the same thing as
a dongle actually associating on this board.
The hardware validation ledger is the single
current source of what has been touched with real hardware; treat anything
not listed there as unverified in practice, exactly as that section says.
This is the half of the project that has no stock counterpart at all, because stock publishes no build recipe.
- No vendored Buildroot, ever. The
Makefiledownloads the pinned release, verifies its SHA-256 against upstream's GPG-signed release manifest, unpacks it underwork/, and forwards every target into it. A hash mismatch refuses to unpack and tells you — at length — why pasting the new hash in is the wrong fix. - Byte-identical reproducible builds. Two independent builds of the same commit
produce identical
linux.imgandzImage_dtb, and a dedicated CI workflow exists to prove it. This is what makes the four layered build caches trustworthy as inputs only. (docs/reproducibility.md) - Full SBOM. Every release ships
legal-info.tar.gz:manifest.csv(package, version, license, upstream URL), license texts, and the upstream source tarball of every package conveyed — the GPL accompanying-source obligation, discharged properly. The same manifest is submitted to GitHub's dependency graph on release, so the package list is browsable at Insights → Dependency graph without downloading anything (docs/ci.md#dependency-graph-submission). - Provenance attestation on the release images.
- No binaries in git. Ever. Firmware, payloads, and stock blobs are fetched at build time, pinned by commit or release and hash.
- Renovate (
renovate.json,docs/renovate.md) with a companion hash-sync workflow, because several pins need a companion hash that a naive version bump would leave stale. Kernel-rcbumps fail closed on a missing hash by design, requiring a human pin.
Parity is a feature, and most of the work is invisible for exactly that reason. This image
reproduces stock's behaviour — not merely its file list — for: the MiSTer binary's ABI
and every SONAME it links, the boot chain and U-Boot contract, /media/fat mount flags
(sync,dirsync,noatime,nodiratime — mounting async would be a real power-off corruption
regression), the read-only root with the login-time remount,rw, the Bluetooth key store,
the firmware set, the init-script set, busybox applet coverage, the RTC, MIDI/MT-32,
Samba, SSH/FTP, /MiSTer.version semantics, and the Downloader's update contract.
Each of those has its own audited parity document — see the
documentation map. The uboot.img in the default release channel is
shipped byte-identical to stock's, fetched by hash.
- One known regression. The Logitech G923 PlayStation variant loses force
feedback and range control; steering, pedals and buttons still work as a plain joystick.
The G923 Xbox variant and all G29/G27/G25 wheels are fully supported with force
feedback intact. (
docs/patch-provenance.md§9.3) - Not all hardware is validated. Samba and MIDI are build- and CI-verified only. One
board, one set of peripherals. The same is true of most of the Wi-Fi/Bluetooth chipset
table — one chip family (Realtek
rtw88) has actually associated on hardware; the rest, including the whole Broadcom, MediaTek, and Wi-Fi 6/6E additions, compile but have not had a dongle plugged into this board — and CI's presence checks do not even cover all of them (rtw89_885*bu,mt7663u,mt7921u/mt7925u,ath9k_htc,carl9170and their firmware are unasserted; see the paragraph under that table). The matrix indocs/testlogs/p3-matrix.mdis the truth; this README is a summary of it. - This forward-port has already shipped one real bug, on hardware. Early builds
auto-overclocked the board to 1.2 GHz on boot and produced hard hangs. The carried
socfpga overclock patch was written against 5.15, where
CPUFREQ_BOOST_FREQkept the governor off the boost rows by default; on 6.18 the defaultpolicy->maxresolves differently, so the overclock became default-on. It applied cleanly, compiled clean, and behaved differently. Fixed in PR #24 — proper->set_boost, boost-only 1000/1200 MHz rows, 800 MHz default — and the board has been stable since. The lesson generalises: any other 5.15-era patch we carry can have the same class of defect, and no amount of CI catches a silent semantic change to a kernel flag. This is the strongest argument for the small-delta posture, and the reason the hardware list stays short. - RT is a developer variant.
PREEMPT_RTboots and runs on hardware, but no latency measurement has been taken yet — so there is currently no evidence it improves anything for a normal user. It exists for testing, not for daily driving. - Debug tooling is temporarily in-tree.
gdb/strace/perf/rt-testsandCONFIG_COREDUMPare enabled for two investigations. One of them — a field hard-hang — closed on 2026-07-21; the RT latency measurement has not been taken, so the block stays for now. It costs image size and diverges from stock's config, and is designed to revert as one unit. (docs/debug-tooling.md) - The sustainability gate is not met. Nobody has yet signed up, in writing, to track
6.18.ysecurity releases through end-of-life. Until that happens this is a personal project, and saying otherwise would be the one claim that undermines all the others. (ADR 0014) - Version numbers drift, and two parity analyses are currently behind the pins.
Every figure here was read off the built tree at the time of writing, but nothing in
CI asserts a package version, so a Renovate or Buildroot-line bump can move a package
out from under the document that reasoned about it. That has already happened twice.
The Buildroot 2026.05.1 bump moved Samba 4.23.8 → 4.24.3 and ProFTPD 1.3.8d →
1.3.9a without re-running the P3.6 / P3.7 audits that gated those packages. The
2026.05.2 bump then moved OpenSSH 10.3p1 → 10.5p1, crossing the skipped 10.4
release where upstream made a failed seccomp
prctl()fatal rather than adebug()— which broke every SSH connection on the shipped image and forced a release to be pulled. Buildroot classified that bump asopenssh: (no CVE assigned). Both parity documents now say so at the top, with the specific unchecked question named. The defconfig andMakefilepins are the ground truth; the prose is a dated reading of it. For the kernel this is handled by not writing the number down: narrative prose says "6.18 LTS", andBR2_LINUX_KERNEL_CUSTOM_VERSION_VALUEin the defconfig is the only place the.ylives.
Makefile wrapper: fetches + hash-verifies Buildroot, forwards targets
Config.in / external.mk BR2_EXTERNAL definition for the 16 in-tree packages
configs/ mister_de10nano_defconfig (the shipped image)
mister_kernel_defconfig (kernel-only base, shared by variants)
mister_rt.fragment (PREEMPT_RT / 7.x delta)
mister_initramfs_defconfig (stage-1 cpio)
mister_installer_defconfig (SD-card installer cpio)
board/mister/de10nano/
linux.config minimal kernel defconfig (an absent CONFIG_X is NOT "off")
linux-patches/ 37 carried patches: 36 MiSTer + 1 mainline backport (0047)
linux-patches-beta/ 36 of those (32 symlinks + 4 re-anchored 7.x copies; 0047 is
already in 7.2) + 4 beta-local = the 40-entry series
linux-patches-upstream/what the exported tree carries but our image must not
rootfs-overlay/ init scripts, sshd wiring, MiSTer-specific files
post-build.sh /MiSTer.version stamping, parity fixups
post-image.sh linux.img assembly + contract checks
package/ 16 packages: Realtek Wi-Fi, xone, libchdr, lzma-sdk, midilink, munt…
scripts/ the verification suite, hash-sync, SD-card builder, kernel export
docs/ ADRs, parity audits, the kernel reconciliation, user docs
.github/ 9 workflows + 4 composite actions
The DE10-Nano's Cyclone V is a dual-core Cortex-A9 (ARMv7-A, 32-bit) — there is no
AArch64 path on this silicon, and PREEMPT_RT cannot be a boot-time toggle on ARM32. So
RT must be a separately compiled kernel image. PREEMPT_RT for 32-bit ARM merged into
mainline in Linux 7.1, which means on 7.2 it is a plain kconfig option with no
out-of-tree RT patch to carry.
The design (ADR 0021): make rt runs a
kernel-only Buildroot build into output-rt/, hard-asserting CONFIG_PREEMPT_RT=y in
the result, then stages its depmod'd module tree into an overlay that the next make all
folds into the one shipped linux.img. You get zImage_dtb and zImage_dtb-rt as
separate boot images, both of whose modules live in the same rootfs, selected on-device
via u-boot.txt. CI builds the variant on every gated run, so a PR that breaks
make rt goes red instead of rotting silently.
zImage_dtb-rt is a manual download from the Release page — it is deliberately not on
the sdcard.img installer payload and has no db.json entry, so no device is ever pushed
an unvalidated real-time kernel. Because its modules are already in the shipped linux.img,
opting in is: download it, drop it in /media/fat/linux/, add one u-boot.txt line.
Stock patches init/do_mounts.c so the kernel itself parses a loop= boot parameter,
mounts /media/fat, and loop-mounts linux/linux.img as the root filesystem. That is a
patch to the most delicate part of early boot, carried forever, for a job userspace does
better.
This project replaces it with a two-stage boot: a tiny BusyBox initramfs, embedded in
the kernel via CONFIG_INITRAMFS_SOURCE, does the mount dance in a shell script — with
rootwait retries, vfat-then-exfat probing, and a rescue shell on failure. One patch
deleted, one whole class of boot failure made debuggable.
The details matter more than they look: -o sync,dirsync is not a tuning choice (async
would be a real power-off-corruption regression), the loop device must stay writable
even though the rootfs is mounted read-only (or /etc/profile's login-time
remount,rw breaks), and mount -o move is required because BusyBox mount has no long
options. All of it is annotated in PLAN.md §5 and
docs/loop-boot-6.18.md.
Forward-looking: the stock patch is 32-bit-ARM-only by construction. Stock's
loop_setup() issues the loop attach as a direct call into the syscall entry points —
sys_ioctl(device_fd, LOOP_SET_FD, file_fd) then sys_close() — from inside
init/do_mounts.c. That compiles on the DE10-Nano's Cortex-A9 only because 32-bit ARM
still defines sys_ioctl() as an ordinary C function. Every 64-bit ARM kernel since 4.19
selects ARCH_HAS_SYSCALL_WRAPPER, which turns each syscall into
__arm64_sys_ioctl(const struct pt_regs *) and drops the C-callable sys_* prototypes
altogether (include/linux/syscalls.h declares them only under
#ifndef CONFIG_ARCH_HAS_SYSCALL_WRAPPER). On aarch64 the stock patch does not merely
misbehave; it has no symbol to call and cannot build. The kernel's sanctioned
replacements for init-time work — the init_mount(), init_mkdir(), init_chdir()
family in fs/init.c — deliberately include no init_ioctl(), because an ioctl is
driver-defined and there is nothing generic to wrap, and the internal vfs_ioctl() /
blkdev_ioctl() paths are private to fs/ and block/. So the only way to keep loop=
in-kernel on a 64-bit ARM SoC is the rewrite this project already had to do for 6.18:
export a purpose-built attach helper from the loop driver itself and call it with
filp_open()ed files instead of descriptors (linux-patches-upstream/0100-…, carried
for the upstream fork, not shipped in this image). The initramfs design, by contrast,
contains no architecture-specific line at all: mount, losetup, and
switch_root behave identically on any CPU the kernel runs on. Should this image ever
need to boot something that is not a Cyclone V, the boot path comes along unchanged, and
the kernel carries one patch fewer than it otherwise would.
make sdcard produces a complete, dd-able sdcard.img — a two-partition MBR with the
data partition on p1 and a type-0xA2 partition carrying uboot.img raw at its start,
per the SPL contract. The image ships small, so the compressed asset stays small and
the write is fast; a throwaway installer OS then reformats the card to exFAT at the
device's real size on first boot — because Linux cannot grow exFAT in place, and exFAT
is what every mr-fusion'd card in the wild uses. That is not an invention: it is
mr-fusion's own mechanism, adopted rather than replaced, per the standing principle of
reusing a proven reference implementation in anything on the boot path.
(ADR 0020)
What that first boot looks like. The installer draws a progress splash — banner, nine
numbered steps, a bar and an elapsed clock — on the serial console, and blinks the
DE10-Nano's on-board HPS_LED at about 0.5 Hz through the long steps. Solid on is the only
state that means trouble (stopped — failed, or the card was already installed); a dark LED
is just a gap between phases, or the reboot. Note HPS_LED is on the board itself, so a
fully-enclosed case will not show it.
The card also carries menu.rbf at the FAT root, which is what U-Boot loads into the
FPGA before Linux starts — without it the fabric is never configured and HDMI reads "no
signal" for the whole install (ADR 0020 §7).
There is still deliberately no splash picture on that output: /dev/fb0 is only scanned
out once Main_MiSTer programs the frame reader, and Main_MiSTer does not run in the
installer. Painting a picture there would mean shipping mr-fusion's entire stack — its
U-Boot, a second DTB and a framebuffer bitstream — a trade ADR 0020 §6
explains and declines.
Install time does not scale with card capacity — a 1 TiB card formats as fast as an
8 GiB one (mkfs.exfat writes 88 KiB–1.15 MiB of metadata either way, in ~10 ms). The
dominant cost is the fixed 512 MiB linux.img expansion, so what matters is the card's
write speed, not its size.
It ships as a separate release asset, never inside release_*.7z and never referenced
by db.json, because its blast radius is the bootloader. The payload it installs is
inventoried against real mr-fusion output in
docs/verification/sdcard-payload.md.
make # prints help — deliberately NOT a build
make mister_de10nano_defconfig # load the config
make all # build (first run bootstraps a cross-toolchain — hours, not minutes)Two things that will bite you otherwise:
makeon its own prints help rather than building. A reflexive baremakein a Buildroot tree with no config starts a full x86 toolchain build that nothing here wants. Usemake all— it runs the stage-1 initramfs build first, then the image.- Do not pass
-j. Buildroot's top level is not parallel-safe; it parallelises each package internally, defaulting to your CPU count. CI runs a baremake all.
| Target | What it does |
|---|---|
make all |
The shipped image: linux.img + zImage_dtb |
make rt |
Kernel-only PREEMPT_RT build → zImage_dtb-rt + module overlay |
make sdcard |
Full sdcard.img(.xz) — run after make all. The card carries no variant kernel, so make rt is not required first; if you do build RT, run it before make all so its modules land in the image |
make initramfs |
Stage-1 cpio only, and print its size |
make menuconfig / linux-menuconfig |
Interactive Buildroot / kernel config |
make savedefconfig |
Write the config back to the defconfig (always do this after editing) |
make buildroot-verify |
Download + SHA-256-verify the pinned Buildroot tarball |
make buildroot-showsig |
Print upstream's GPG-signed manifest — the only valid hash source |
make legal-info |
Generate the SBOM |
make clean / distclean |
Buildroot's own meanings, applied across every output dir (dl/ is kept — it's a shared cache) |
Standard build tools (gcc, make, bc, flex, bison, cpio, rsync, unzip,
wget/curl, python3), plus dtc, qemu-user/qemu-system-arm, and shellcheck for
the test and inventory scripts.
One sharp edge. Buildroot refuses to build if /usr/bin/install is uutils coreutils
0.8.0 — it detects that exact version and rejects it (upstream bug
uutils/coreutils#12166).
Debian/Ubuntu's coreutils-from-uutils package installs precisely that as the default
install. You do not need to fix this yourself and you should not need sudo: the
Makefile detects it and transparently shims a GNU install into PATH for Buildroot only
(work/.hostshim/), touching nothing outside this repo. It is inert on a host whose
install is already GNU, and fails fast with a clear message if no GNU install exists
at all.
Nothing here is asserted by hand. The parity claims above are backed by scripts that run on every push and pull request.
scripts/ci-tests.sh is one command that runs the whole
non-hardware suite and prints a self-contained digest last (| tail -n 30 tells you what
broke and why, without grepping):
- the image-contract checks —
check-zimage-dtb.sh,check-linux-img.sh,check-size-budget.sh - the structural initramfs checks, plus a full QEMU boot test of the initramfs
/init(booted six times, across the failure paths) - an ABI smoke test running the stock
MiSTerbinary underqemu-useragainst the built rootfs: dynamic linking must resolve clean, and it must die at FPGA access and not one instruction earlier - per-service parity assertions harvested from each Phase 3 parity document, checked
against the shipped
rootfs.tar— not againstoutput/target/ - a sandboxed functional test of the timezone autodetect dhcpcd hook
(
scripts/test-timezone.sh) — it turns a string off the network into a filesystem path, so every class of hostile answer is asserted rejected, run twice: under the host shell and under the target's own BusyBoxashviaqemu-arm
Alongside it, scripts/check-abi.sh runs the full
SONAME/loader checklist from docs/abi-contract.md. The overlap on
the two highest-value gates is deliberate.
| Workflow | Trigger | Job |
|---|---|---|
build.yml |
push to master, every PR |
Kernel-variant matrix, then the image, then the parity + ABI suite — plus the patch-header lint and the kernel-defconfig sync check |
release.yml |
v* tags |
Rebuilds from scratch (never adopts a CI run), assembles release_YYYYMMDD.7z, drafts the GitHub Release |
publish-db.yml |
release published | Regenerates db.json, schema-checks it, deploys it to GitHub Pages |
reproducibility.yml |
manual dispatch | Proves two independent builds are byte-identical. Manual on purpose: it is a double build, and its input caches are only warm after build.yml has run |
renovate-hash-sync.yml |
Renovate PRs | Refreshes companion hashes a version bump leaves stale; validates the one pin that has none (the _Console cores snapshot) |
renovate-validate.yml |
config changes | An invalid Renovate config makes Renovate skip the repo silently — this is the only signal you would ever get |
fork-sync.yml |
weekly | Diffs the last-reconciled fork commits against upstream's live HEADs and keeps one issue updated with the backport queue |
lint.yml |
push, PR | actionlint over the workflows, shellcheck over scripts/** and the composite actions' run: bodies |
cache-prune.yml |
PR closed, daily | Deletes the ~5 GB Actions cache scope a closed PR leaves behind — dead PRs were evicting master's warm dl/ under GitHub's 10 GB LRU ceiling |
The full rationale, incident history, and measured numbers live in
docs/ci.md — the workflows keep the imperative and the run ID, that
document keeps the narrative and the "we tried X and it failed."
Release artifacts are published as GitHub Release assets; no binaries live in git.
The main set is seven files (the Downloader-contract set: the release_YYYYMMDD.7z,
linux.img, zImage_dtb, both .configs, legal-info.tar.gz, SHA256SUMS), plus three
-rt files per kernel variant, plus the separately contracted sdcard.img.xz.
Distribution is opt-in and requires zero cooperation from anyone: a community
db.json, served from GitHub Pages, that the standard on-device Downloader reads.
Coexistence with the official channel is the hard part, and it is solved
(ADR 0025). The
Downloader applies at most one Linux image per run, and distribution_mister always
offers one too. When several databases offer one, it sorts the candidates by their
position in downloader.ini and takes the first — and since drop-in databases are
merged after the base ini's own sections, and Update All pins [distribution_mister] to
the top on every rewrite, the official image wins whenever that database is actually processed — which is most real runs, since its content changes whenever any core does. (A run where nothing changed skips the database entirely and applies no Linux image at all: can_skip_db returns true when file_checking is FASTEST, the steady-state default, and a skipped database's linux entry is never looked at.) Not a race that might go
our way: a loss whenever it is contested, silently reverting the user on a routine core
update.
So this project doesn't compete for that slot; it closes it:
[MiSTer] update_linux = falseindownloader.inistops every normal run from applying any Linux image. Cores, ROMs, MRAs and Jotego keep updating untouched.Scripts/update_linux_modernization.shruns the Downloader against its own private ini naming one database, so there is nothing to sort and nothing to lose.- That is the entire mechanism. No boot script, no daemon, no state files: the running
system already tells you what you need (
/MiSTer.versionsays which image, andupdate_linuxsays whether it is protected), and nothing re-applies the setting behind you — the updater is the only thing that writes it, and only when you run it. Which is also why reverting is one edit, with nothing to opt out of.
Verified on hardware: a full update_all.sh run installed 379 cores, rebooted, and came
back with linux.img and zImage_dtb byte-identical, the Downloader's own config
dump recording update_linux: false and UPDATE_LINUX: undefined — Update All never
overrides it. The shipped downloader.ini also reproduces Update All's default database
set plus Jotego, so a fresh sdcard.img flash
needs no user action at all. Rolling back is one command,
update_linux_modernization.sh --restore-stock — see
docs/user/rollback.md.
One subtlety worth calling out because getting it wrong bricks the update loop: stock's
Downloader compares versions with a strict string inequality, not an ordering, and
/MiSTer.version is derived from SOURCE_DATE_EPOCH — which reproducible builds pin to a
constant. Two releases from the same Buildroot pin would therefore have carried an
identical version string, and a user would never be offered the newer one. Both the
image stamp and the published version now derive from the tagged release date instead.
(ADR 0018,
docs/db-json-versioning.md)
Start here if you want to run it: one-command install ·
docs/user/onboarding.md ·
docs/user/rollback.md ·
docs/user/faq.md ·
docs/user/serial-recovery.md
| # | Goal |
|---|---|
| G1 | A linux.img + zImage_dtb that boots the unmodified, stock MiSTer binary |
| G2 | Modern kernel on a supported LTS with a real security-update path |
| G3 | Modern package set (Buildroot 2026.05) with a real security-update path |
| G4 | No separate kernel repo. All kernel patches live as .patch files in the Buildroot external tree, applied to a pristine kernel.org tarball |
| G5 | Fully reproducible: pinned Buildroot, pinned kernel + hash, checked-in .config, published SBOM |
| G6 | Release artifacts published as GitHub Release assets. No binaries in git. Ever. |
| G7 | Opt-in distribution via a community db.json — zero cooperation required |
Start here
| Document | Read it when |
|---|---|
PLAN.md |
You want the goals, the ABI contract, the design rationale, and the risk analysis |
TASKS.md |
You want the phase-by-phase execution plan with acceptance criteria |
CONTRIBUTING.md |
You are about to change something |
docs/user/ |
You are running this on real hardware |
The kernel
| Document | Contents |
|---|---|
docs/patch-provenance.md |
Every carried patch: origin, upstream status, disposition |
docs/kernel-recon/ |
The full per-commit reconciliation — 123 evidence-backed records, the ledger, the silent-regression triage, and the reproducible pipeline that produced them |
docs/kernel-config-deltas.md |
Every kernel-config divergence from stock, and why |
docs/rt-beta-kernel.md |
The PREEMPT_RT / 7.x variant |
docs/loop-boot-6.18.md |
Why the fork's loop= patch cannot be ported to 6.18, what porting it would cost, and what we did instead — written for whoever maintains the fork |
MISTER-KERNEL-PATCH-RECON.md |
The task spec the reconciliation was executed from |
The contracts we must not break
| Document | Contents |
|---|---|
docs/abi-contract.md |
What the kernel and rootfs must honor for the stock binary to run |
docs/boot-chain.md |
U-Boot contract and its kernel-config implications |
docs/uboot-mainline-port.md |
The plan for a mainline U-Boot built to behave like stock's — design, measured port surface, brick modes. Ships nowhere |
docs/uboot-tasks.md |
Execution tasks for the above (U0–U7), split out of TASKS.md |
docs/downloader-contract.md |
How the on-device Downloader decides to update |
docs/package-manifest.md |
All 251 stock SONAMEs mapped to a package, with major-bump flags |
docs/stock-inventory/ |
The audited inventory of the stock image everything above is measured against |
Parity audits — one per subsystem:
bluetooth ·
wifi ·
firmware ·
init ·
usb-automount ·
netfs ·
samba ·
ssh-ftp ·
midi/mt32 ·
rtc ·
util-linux ·
stock reconciliation — the cross-subsystem
one, diffed file-by-file against stock's actual firmware.tar.gz/
modules.tar.gz/addon.tar and the base rootfs, source of the hardware
coverage numbers above
Engineering
| Document | Contents |
|---|---|
docs/ci.md |
The pipeline, the caching traps, and the incident index |
docs/reproducibility.md |
What byte-identical means here, and the four mechanisms that deliver it |
docs/renovate.md |
What Renovate manages, the automerge posture, the hash rules a human must follow |
docs/size-budget.md |
Image headroom and where the bytes go |
docs/version-delta.md |
Five years of upstream movement, package by package |
docs/main-shared-libs.md |
Shared-library coverage for Main_MiSTer |
docs/dualsense-tooling.md |
dualsensectl, and why the DualSense kernel patches are not replaceable by udev/userspace |
docs/logitech-pairing.md |
ltunify + mister-pair-logitech — the one Logitech gap the kernel has no interface for, and why not Solaar |
docs/azcopy.md |
azcopy for off-device backup — what it costs in image space, and what building it for an architecture Microsoft does not support took |
docs/debug-tooling.md |
⚠ temporary — the debug block and how to revert it as one unit |
docs/decisions/ |
The ADRs: the open questions, the trade-offs, and who decided what |
Contributions are welcome once the Phase 4 publication gate is passed. Until then this is a personal-use project.
When it opens, all contributions must follow the discipline in
CONTRIBUTING.md: patch provenance tracking, developer sign-off (DCO),
and the standing rules from TASKS.md — reproducibility, no vendored binaries,
hash-pinned upstream sources, and no behaviour changes smuggled into build fixes.
See PLAN.md §13 for the sustainability requirements and the risk discussion
behind the gate.
- Repository code (Buildroot external tree, scripts, overlays) — GPLv3, see
LICENSE - Kernel patches (
board/mister/de10nano/linux-patches*/) — GPLv2, because they modify the Linux kernel - Packages in
package/— inherit their upstream licenses (GPLv2, BSD, MIT, …); thelegal-infoartifact in each release is the complete, authoritative SBOM