Neanderfunk 26091920sta Release: Packages, Patches, Build System

Neanderfunk 2023.2.6: Packages, Patches, Build System

New Neanderfunk-Stable firmware 26091920sta, still Gluon 2023.2.6, for reasons (see below). What of it lies in our own packages and what remained as patches:

In the Images

Selection in templates/common/image-customization.lua, feed pin in templates/common/modules.

  • On all devices: nodeplacer, ssid-changer, respondd, setup-mode, setup-wifi, preserve-wifichannel, hotfix, linkcheck, wifi-blackout, weeklyreboot, ap-timer, button-bind, node-whisperer, banner
  • Only ramips-mt7621, mediatek-mt7622, mediatek-filogic: mt7915-backlog
  • Only as DEPENDS: config-mode-theme, common
  • 64 MB RAM, two manually maintained device lists (18 dual-band, 29 single-band): add zram-swap, remove procd-ujail. Measured on the TL-WR1043ND v2: ujail cost 3.7 MB RSS for dnsmasq, hostapd and ntpd combined.
  • How the lists are created: candidates from a survey of RAM per device (Gluon targets, OpenWrt Table of Hardware), included is what showed up as affected in the field. The lists are only complete for ath79; single-band devices of other targets with 64 MB are still missing, there it remains at the standard.
  • The settings are not tied to these lists, which decide at runtime based on MemTotal. These apply to every device with 64 MB, including those not on any list; the patches for this are further down.
  • None of these measures are free, usually throughput pays the price.
  • Not included: haveged (under 23.05, urngd provides entropy, 5.15 initializes CRNG itself; 1.6 MB RSS), ffmuc-ipv6-ra-filter (remains too sticky on fast supernode switchover)

Packages in Detail

For the line Upstream: it almost always means freifunk-gluon/community-packages or the feed of the respective original maintainer, not Gluon-Core. Core is only affected for preserve-wifichannel, setup-wifi and setup-mode.

neanderfunk-preserve-wifichannel

  • Function: preserves channel and channel width across updates, resets them after site or domain change
  • Mechanics: makes wifi24.preserve_channels from site.conf the default for Gluon’s uci option
  • Origin: Gluon 2023.2 does not evaluate this site.conf key. For us it has been set and ineffective for years.
  • site.conf: wifi24.preserve_channels = 1
  • Dependency: hiding the outdoor switch in the wizard when preserve_channels=1 still requires a Gluon patch (patches/gluon-config-mode/outdoor-schalter.patch), the package alone is not sufficient.
  • Upstream: affects Gluon-Core. Gluon knows the uci option gluon.wireless.preserve_channels, but no site.conf default for it. In progress: Gluon#3550 („RFC: configure wifi through gluon config“, open since 22.07.2025) rebuilds WLAN configuration, Gluon#3601 (open PR) adds an htmode override per radio.

neanderfunk-setup-wifi

  • Function: WLAN setup.gluon_<MAC> in setup mode, setup without network cable
  • Origin: previously a firmware patch with hardcoded values
  • site.conf: setup_mode.wifi with start (button, off, boot), security (open, wpa2), timeout (1200)
  • Start: via button during operation (button), on startup into setup mode (boot) or not at all (off). After timeout seconds the WLAN turns off again.
  • Remains patch, even when reusing: S60dnsmasq from gluon-setup-mode (first instance fixed to br-setup) and cgi-bin/portal from gluon-config-mode-core (redirect to SERVER_ADDR). Two packages cannot own the same file.
  • Upstream: touches Gluon-Core. Without extensibility of these two files, it remains outside a pair of patch and package. Older PR for this: Gluon#1563 („[RFC] gluon-setup-mode: add wifi interfaces“, PR from 04.11.2018, unmerged closed on 05.07.2020). Open point there: a PSK derived from the primary_mac does not apply to all devices. Our default key is also derivable from the MAC.

neanderfunk-setup-mode and neanderfunk-config-mode-theme

  • Function: setup mode on one page, wizard and advanced settings, one save. Theme responsive, with dark mode.
  • Origin: new development.
  • DEPENDS: setup-mode on +neanderfunk-config-mode-theme
  • Inclusion: '-gluon-config-mode-theme' is mandatory, both provide view/theme/layout.html.
  • Why one page: in Gluon’s setup mode, if you switch from the wizard to advanced settings, you lose your input. Without switching no loss. Top the wizard, below each form of advanced settings as a collapsible group with status line, one „Save & Restart“. Information and firmware upgrade remain separate pages.
  • The forms remain those from Gluon and the packages. A new package with model() appears as an additional group without intervention. „Outdoors“ Gluon queries at two places, on the page it appears once.
  • Storage rules: all or nothing, writing only happens with completely valid forms; only changed forms are written (an empty password form would otherwise have executed passwd -l root); the wizard last and always, it sets configured, calls gluon-reconfigure and restarts; as long as the advanced forms are writing, commit and reconfigure remain out, otherwise a form with an old uci cursor would write a stale copy back.
  • Theme: replaces Gluon’s theme via PROVIDES gluon-config-mode-theme, only provides view/theme/layout.html and a stylesheet and styles Gluon’s own markup without changing any of its templates. No Gluon patch needed. Same pattern as ffgraz-config-mode-theme-funkfeuer.
  • CSS without breakpoints: not a single width media query, the layout is arranged entirely via flex and grid (26 places). The only two @media rules are prefers-color-scheme: dark and prefers-reduced-motion: reduce. Dark mode follows the browser, there is no toggle.
  • Sizes, raw bytes: neanderfunk.css 19,651 B (gzip -9: 5,168 B), layout.html 5,142 B. Gluon by comparison: gluon.css 7,073 B, layout.html 3,194 B. Our stylesheet, however, also covers the collection page, so the comparison is off.
  • All local: no @import, no url(), no @font-face, no external link. Only system fonts (system-ui, -apple-system, Segoe UI, Roboto and further), logo as inline SVG. In setup mode the client usually has no internet, and fonts cost flash.
  • Cache: the stylesheet is loaded with ?v=<Release>. Gluon serves /static/ without Cache-Control and with fixed Last-Modified, otherwise the browser keeps the old CSS after an update.
  • i18n: own i18n/*.po (de, fr), fetched with i18n('neanderfunk-config-mode-theme'), because the dispatcher renders the layout under the name gluon-config-mode-theme.
  • Upstream: the theme declares PROVIDES gluon-config-mode-theme, just like the existing ffgraz-config-mode-theme-funkfeuer in community-packages; themes as a separate package are thus envisioned there. For the single-page form that touches Gluon-Core, no issue and no PR found. Older issues and PRs: Gluon#2386 („Feature responsive“, PR, unmerged closed on 01.01.2023), Gluon#528 (changeable colors, open since 16.10.2015), Gluon#1165 (status page does not use config-mode-theme, open since 2017).

Setup Mode: Networks, Portal Detection, Button, LED, Timer

  • Two networks. On cable br-setup: node 192.168.1.1, DHCP .2 to .254, router option empty, so the client gets no default route via the node. In setup WLAN br-setupwifi: 198.51.100.1/24, DHCP .2 to .254, router and DNS are the node.
  • 198.51.100.0/24 is TEST-NET-2 (RFC 5737), deliberately not RFC 1918. Android only opens its login window if the probe host does not resolve to an RFC-1918 address; on Galaxy S24 Ultra with 192.168.2.1 we only got „no internet“.
  • DNS on cable targeted, in WLAN catch-all. On cable, dnsmasq only resolves gluon.setup, setup.gluon and the probe hosts, everything else REFUSED, so a laptop that is also connected to the internet asks its other resolver and gets the OSM tiles in the wizard. In setup WLAN every name points to the node, so the map does not load there.
  • The probes are manufacturer conventions, not standards. Registered are among others captive.apple.com, connectivitycheck.gstatic.com, connectivitycheck.android.com, clients3.google.com, www.msftconnecttest.com, dns.msftncsi.com, detectportal.firefox.com, nmcheck.gnome.org, connectivity-check.ubuntu.com, network-test.debian.org, networkcheck.kde.org, ping.archlinux.org, connect.rom.miui.com, connectivitycheck.platform.hicloud.com.
  • All URLs: uhttpd runs with -E /cgi-bin/portal, so every path it does not find on the node goes to this handler, /generate_204 just as much as /hotspot-detect.html, /connecttest.txt or /success.txt.
  • The response is the same for all: 302 Found, Location: http://<address>/cgi-bin/config, Cache-Control: no-store (RFC 9110, 15.4.3). Address is the one through which the request came. Because the expected response does not arrive, the system reports „Sign in to network“ and opens the wizard.
  • Not implemented: standardized signaling per RFC 8910 (DHCPv4 option 114, DHCPv6 option 103, RA option 37) with Captive Portal API per RFC 8908. The API would need HTTPS with a valid certificate, otherwise the client must ignore it, and a node in setup mode does not have one: it is offline until configured, and in this state also cannot obtain one. Add to that iOS not using the API on first connection and DHCPv6 as well as RA falling away because Gluon’s dnsmasq is built without both and setup mode provides no IPv6. RFC 6585 (HTTP 511) also not, we respond with 302.
  • Tested is Android, Galaxy S24 Ultra and a device with Android 16 on 14.09.2026, plus lab runs on Archer C25 v1. iOS, macOS, Windows, the Linux desktops and the Xiaomi and Huawei ROMs are registered, their effect is not verified.
  • Button: Gluon itself starts on 3 s hold on reset, wps or phone into setup mode. Our package takes the short press under 3 s and only in setup mode: turn on setup WLAN, or the timeout starts over. A factory reset by button does not exist in the image, /etc/rc.button/reset is missing.
  • LED: setup mode 1000 ms on, 300 ms off, so about 0.77 Hz. If setup WLAN is running, 200/60 ms, so about 3.8 Hz. Until 14.09.2026 it was 333/100, that was too hard to distinguish from the setup mode beat.
  • Timer: default 1200 s, allowed 60 to 86400. Only extended by another short press, client traffic does not extend it. After that wifi down and a log line, no restart; access via cable remains.
  • For us: start = button, security = open. So the setup WLAN is open, but only goes on via short press on the device. check_site.lua forces this combination, open only exists together with button. On Genexis Pulse EX400 the capacitive wps area counts for the short press, there the open setup WLAN can go on without fingers.

neanderfunk-hotfix

  • Function: 16 state checks that reinitialize WLAN or reboot. Intervals: healthcheck 7 min, IfNoWificlient 15 min, watchdog 5 min, WLAN firmware 3 min.
  • Checks and reaction: kernel_bug, ath_malloc, ksoftirqd_malloc, tunneldigger, br_client_ipv6, load, respondd, dropbear, wifi_firmware, ath10k_rxhang, eth_tx_stall, watchdog (reboot); hostapd_pids, dfs_failcheck, no_wifi_clients (WLAN restart); logremote (restart the logread instance)
  • uci: hotfix.settings.check_uptime_min (5), .reboot_uptime_min (60), .watchdog_interval_min (5), .autoupdater_stale_min (300), .hostapd_cooldown_min (60), .ath10k_restart_min (3); per check hotfix.<check>.disabled and .immediate; hotfix.load.per_cpu (2, threshold is per_cpu times cores over two runs); hotfix.eth_tx_stall.dry_run (1 = log only), .strikes (3), .rx_strikes (5), .soft_reset (1, ethtool -r before reboot)
  • site.conf: hotfix.check_uptime_min, .reboot_uptime_min, .disabled_checks, .load_per_cpu, .eth_tx_stall_dry_run
  • Origin: eulenfunk/packages (Ruben Kelevra), contains and replaces ffac-mt7915-hotfix. New compared to the predecessor are both uptime thresholds; the original could reboot two minutes after boot.
  • Upstream: contains and replaces ffac-mt7915-hotfix (community-packages#132, 2024). The causes lie deeper: eth_tx_stall belongs to openwrt/openwrt#17505 („Cudy TR3000: MT7981 ethernet disconnects“, open, last 07.01.2025), kernel_bug stems from the time of Gluon#680 (closed).
  • DEPENDS: +neanderfunk-common

neanderfunk-linkcheck

  • Function: checks WLAN and batman neighbors as well as gateway reachability. Intervals: linkcheck 5 min, gateway 8 min.
  • Checks: batadv_neighbours, bsses, batinterfaces, batman_originators, bridges, bridge_ports, mesh_neighbours (first WLAN restart, then reboot); no_gateway, ipv6_anycast, public_prefix (reboot)
  • uci: linkcheck.settings.check_uptime_min (5), .reboot_uptime_min (60), per check linkcheck.<check>.disabled
  • site.conf: linkcheck.check_uptime_min, .reboot_uptime_min, .disabled_checks
  • Limits:
    • no_gateway reboots after 4 runs, so about 32 min, but only if a gateway was seen once since boot (marker /tmp/linkcheck.gw-seen). With flickering gateway results in a reboot loop of about 65 min; reboot_uptime_min delays it, it does not prevent it. A node that never had a gateway stays at rest.
    • bsses skips wifi6 and mt7915 radios, scanning breaks mesh links there.
  • Origin: eulenfunk/packages (Ruben Kelevra).
  • DEPENDS: +neanderfunk-common

neanderfunk-wifi-blackout

  • Function: no associated client on any radio for blackoutwait minutes, then WLAN restart; if that does not help, reboot.
  • uci: wifi_blackout.settings.check_uptime_min (5), .restarts_before_reboot (1), .disabled (0)
  • site.conf, not via uci: wifi_blackout.blackoutwait (171 min), .resetwait (281 min), .stepsize (10 min)
  • False positive: a node where clients legitimately never hang, i.e., a pure mesh or backbone node, periodically restarts. Measured on D-Link COVR-X1860 without clients: about every 14 h.
  • Origin: FreifunkHochstift, via eulenfunk-ath9kblackout. Renamed because the issue is not ath9k-specific.
  • DEPENDS: +neanderfunk-common
  • CONFLICTS: eulenfunk-ath9kblackout, gluon-ath9kblackout, neanderfunk-ath9kblackout

neanderfunk-mt7915-backlog

  • Function: WLAN restart if the txq backlog of an mt7915 radio grows over 50 packets. Only on ramips-mt7621, mediatek-mt7622, mediatek-filogic.
  • uci: mt7915backlog.settings.threshold (50), .cooldown_min (30), .disabled (0), .check_uptime_min (5), .reboot_uptime_min (60)
  • Origin: port of ffac-mt7915-backlog. New is 30 min cooldown between restarts, the original restarted up to every 2 min.
  • Classification: symptom treatment, for us together with ffac-mt7915-maxinactivity, which addresses the cause.
  • Upstream: ffac-mt7915-backlog only exists as a branch in ffac/gluon-packages, PR #19 from 30.09.2025 was unmerged closed. The cause is open as openwrt/mt76#1009 („mt7915: wifi unresponsive - backlog fills - no tx activity“, last 22.04.2026). Gluon#3154 (MT7915e crashes) closed since 15.04.2025, fixed by Gluon#3436 („mt76: fix system recovery routine for MT7915“, merged 09.03.2025), but only in main. The backport Gluon#3710 for v2023.2.x was not merged, reasoning in the PR: „This issue is properly fixed in future Gluon releases“. On 2023.2.6 the fix is thus missing.

neanderfunk-ssid-changer

  • Function: switches the client SSID to an offline SSID when no gateway is reachable, and back when it is available again.
  • uci: ssid-changer.settings.enabled, .switch_timeframe (30 min), .first (5 min), .prefix (FF_Offline_), .prefix_owe (FF_Off_OWE_), .suffix (nodename, mac, none), .tq_limit_enabled (false), .tq_limit_max (45), .tq_limit_min (35), .gwofflinemaxcount (3), .debug_log_enabled
  • site.conf: block ssid_changer
  • Limitation: the TQ limits do not work with BATMAN_V per README.
  • Counters are not in uci, but as respondd values under statistics.neanderfunk.ssid_changer.{gateway_losses, offline, switches}.
  • Origin: fork of ffac-ssid-changer (base commit 91e5fa8).
  • DEPENDS: +neanderfunk-common
  • CONFLICTS: ffac-ssid-changer, gluon-ssid-changer
  • Upstream: at the original open: community-packages#180 („set offline ssid correctly for larger switch_timeframe“, open PR from 08.01.2026), #172 and #173 (offloader without WLAN, 09/2025), #157 („Never sets offline SSID“, closed).

neanderfunk-nodeplacer

  • Function: server-side moves individual nodes to a different domain, controlled via a signed manifest on the firmware server.
  • site.conf, all optional, the block itself too: installed means active. nodeplacer.mirrors (only http://), .pubkeys, .good_signatures (at most number of pubkeys), .disable (0), .config_mode (tab in setup mode). Without specification, mirrors, keys and threshold of the autoupdater branch on which the node is running apply.
  • uci: nodeplacer.settings.*
  • Trust anchor: same keys and same threshold as the autoupdater.
  • Limitations: only within the same community; tested with single-domain firmware, multidomain not yet on devices.
  • CONFLICTS: gluon-hoodselector
  • Documentation: neanderfunk-nodeplacer/docs/HOWTO.md and docs/MANIFEST-FORMAT.md
  • Upstream: new development. Related, but different purpose: gluon-scheduled-domain-switch (whole domain, time-controlled), gluon-hoodselector (by geo coordinates), gluon-config-mode-domain-select (selection by operator). None of those cover individual nodes server-side. README and docs/DECISIONS.md lie in the package, the README requests testing by other communities: packages/neanderfunk-nodeplacer at afccfac4709d5a8627a3592f51cedc847c984f2c · Neanderfunk/packages · GitHub

neanderfunk-respondd

  • Function: respondd module in C, provides statistics.neanderfunk.*: hardware, radios (channel, htmode, txpower, country), offline SSID counters, ethernet links (carrier, speed, possible), memory pressure (refault_file, forks, zram), temperatures.
  • Configuration: none, neither uci nor site.conf.
  • For the values to appear on a map, yanic needs an extension. Ours is publicly available as a commit in our yanic fork: statistics.neanderfunk lesen und in die Zeitreihen schreiben · Neanderfunk/yanic@20c5b02 · GitHub
  • Not all of it lands there: hardware information (vendor, soc, cpu_model, flash) yanic does not read, they are only on the node.
  • Upstream: new development. Field names are deliberately under statistics.neanderfunk.*, i.e., as community extension not as Gluon standard field.

neanderfunk-ap-timer

  • Function: time switch for the client WLAN, with page in setup mode.
  • uci: ap-timer.settings.enabled, .type (day or week), then per day or week list on 'HH:MM' and list off 'HH:MM'
  • site.conf: ap_timer.web (page in setup mode, default true)
  • Origin: fork of ff-ap-timer and ff-web-ap-timer, combined into one package (community-packages#80, 2023, originally from FFHO).
  • CONFLICTS: ff-ap-timer, ff-web-ap-timer

neanderfunk-button-bind

  • Function: assigns functions to device buttons.
  • uci: button-bind.wifi.function: 0 = WLAN on/off (OpenWrt behavior), 1 = no function (default), 2 = WLAN reset, 3 = night mode (LEDs off, only on press, requires reboot)
  • Origin: port of ffffm-button-bind (GitHub - freifunk-ffm/packages: Frankfurter Packages · GitHub), with bugfix in upgrade script.
  • CONFLICTS: ffffm-button-bind
  • Upstream: the original repo is not archived, last push 10.07.2024.

neanderfunk-node-whisperer

  • Function: diagnostic information via WLAN beacons.
  • site.conf: node_whisperer.enabled, .information (selection from hostname, node_id, uptime, site_code, domain, system_load, firmware_version, batman_adv)
  • Origin: fork of ffda-node-whisperer. Our changes lie as patches in the package: return -ENODATA instead of incorrect values, gateway TQ via BATADV_CMD_GET_GATEWAYS, each originator counted once.
  • CONFLICTS: ffda-node-whisperer
  • Upstream: community-packages#182 (open since 03.04.2026) reports literally node-whisperer: Error collecting Information for id=4 name=domain code=-1. Our patch 0001 addresses exactly this message: gluonutil_get_domain() returns NULL on single-domain firmware, the collector returned -1 for that. In the issue the cause is suspected to be incomplete WLAN detection.

neanderfunk-banner

  • Function: replaces /etc/banner and /etc/profile with variants showing node status and helper commands.
  • Note: makes DNS queries to asn.cymru.com for ASN display.
  • CONFLICTS: ffmuc-custom-banner (community-packages#115, 2024).

neanderfunk-common

  • Function: shared helpers for hotfix, linkcheck, weeklyreboot, ssid-changer and wifi-blackout, including the reboot log /lib/gluon/neanderfunk/reboot.log which preserves the last reboot reasons across restarts.
  • uci: neanderfunk.settings.reboot_log_max (6, 0 = off)
  • site.conf: neanderfunk.reboot_log_max

neanderfunk-weeklyreboot

  • Function: reboot Thursday, cron 15 3 * * 4 plus 0 to 9999 s randomness, so between 3:15 and about 6:02.
  • Skipped while autoupdater is working, two-stage: flock -n on /var/lock/autoupdater.lock covers download and check, pgrep sysupgrade the flash itself, both fail-safe.
  • Without set clock the reboot depends on uptime instead of day of week. Most routers have no RTC. After boot OpenWrt sets the clock to the timestamp of the newest file under /etc, so about the last configuration change. If that was shortly before Thursday 3:15, the node reaches the cron time after every boot after the same short period, and earlier versions restarted it that way every time. The script therefore checks a marker in /tmp which the ntp hotplug event stratum sets. Without it, the Thursday run only reboots from seven days uptime, the restart then falls between 7 and 14 days.
  • Configuration: none. The schedule is fixed; for other times the cron line must be changed and rebuilt.
  • CONFLICTS: ffac-weeklyreboot, gluon-weeklyreboot
  • DEPENDS: +neanderfunk-common
  • Upstream: counterpart is ffac-weeklyreboot (community-packages#34, 2023). It checked /tmp/autoupdate.lock, a file that nobody creates. Corrected in community-packages#191, upstream merged on 10.09.2026, now also flock -n on /var/lock/autoupdater.lock. flock is present on 2023.2.6 images (busybox 1.36.1, checked on x86 and TL-WR1043ND v2). The second stage pgrep sysupgrade and the ntp protection are not upstream.

What Remained as Patch and Why

40 patches, fixed order in templates/common/prepare.sh, grouped under patches/<group>/. Each group is self-contained, the scripts are idempotent; patches/README.md describes how to take individual groups.

Backports: upstream solved, not yet in 2023.2

  • lowmem/limit-wireless-buffers - WLAN buffers based on RAM. Gluon 8f38662f, from v2025.1.
  • lowmem/sysctl-64m-min-free - min_free_kbytes 2048, small fragment buffers. Gluon a505f767 plus fix c6ac8914, only in main, in no release.
  • status-page/statuspage-ssid - SSID and HT mode per radio. Gluon 7c040c2d04ee, from v2025.1, for us with two deliberate deviations.
  • kernel/rtl8221b-skip-mmd30 - skip MMD 30 on C45 PHY scan, otherwise reading RTL8221B hangs if a cable is connected at boot. OpenWrt 88dcd8c303b6, only in main, neither 24.10 nor 23.05.
  • devices/add-dlink-m30 - in Gluon v2025.1.3 and main, not in v2023.2.x. OpenWrt 23.05 6e51ff88b0 plus fix from 24.10 d92fc99360. Without recovery image, as Gluon itself since freifunk-gluon/gluon#3816.

TLB Kernel Freeze, Upstream Regression in Gluon 2023.2.6 Release, Resolved

  • kernel/revert-mips-tlb-uniquify removes the call to r4k_tlb_uniquify(). Affected MIPS boards otherwise hang on cold start in tlb_init(), but survive warm starts and updates.
  • This is the reason for the „for reasons“ above. This bug failed a stable for us in March: rolled out on 23.03.2026, rolled back on 27.03.2026. Devices continued running after the update, the bug only showed on the next power outage, and then the device was gone. This could neither show up during rollout nor in the update statistics.
  • Rolled back via manifest: a pre-dated, newly signed manifest with the old version on its own branch. The autoupdater takes an older firmware if the manifest names it as the current one.
  • Upstream solved it differently: 5.15.209 rewrites uniquification (540760b77b8f, Fixes: 9f048fa48740). For routers no functional difference.
  • openwrt-23.05 has 5.15.211 since 11.07.2026, Gluon v2023.2.x since 22.09.2026 (freifunk-gluon/gluon#3841).
  • Our patch will necessarily be dropped on the next build, it applies to code that 5.15.209 rewrote.
  • Analysis and measurement public: freifunk-docs/mips-tlb-cold-start-5.15.md at 15fcc7bc579df8d400b12c3650a87fbf45994ea7 · Neanderfunk/freifunk-docs · GitHub

Depends on Open Issues Upstream

  • kernel/mt7530-phy-disable-eee - disable EEE on MT7530 PHY. Rebuild of OpenWrt PR #25058 (open, base main), transferred to 5.15 by us. Unverified: built, the effect on the device was not specifically measured.
  • build/add-ffac-package-patches - max_inactivity in ffac package, workaround for openwrt/mt76#1009 (open): mt7915 becomes unresponsive, backlog fills.

Issues and Gaps without Bug Report Upstream

  • kernel/ag71xx-rx-ring-no-bug - under memory pressure filling the RX ring fails, ag71xx_assert(0) triggers a panic, even though the driver has a fallback with oom_timer. OpenWrt main has that section unchanged (ag71xx_main.c, ag71xx_rx_packets).
  • bugfixes/tunneldigger-reinit-backoff - mesh VPN on, no WAN: reinit without pause, two modprobe per reinit. Measured on WDR3600: 115 modprobe in 30 s, CPU 0% idle.
  • lowmem/sysctl-no-watermark-boost-64mb and bugfixes/sysctl-firmware-no-sysfs-fallback (missing firmware like tg3 held boot 60 s under RTNL). Both could also be solved as a package with sysctl.d file.
  • gluon-config-mode/wizard-save-lock - double-click on „Save & Restart“ starts parallel gluon-reconfigure runs, result hostname OpenWrt and timezone UTC.
  • status-page/web-static-version - CSS and JS with ?v=<Release> against old browser cache.
  • Airtime part of build/add-gluon-package-patches - respondd-module-airtime omits values where busy, rx or tx is greater than active; the mt76 survey counters run per vif.

Principally Not a Package

  • lowmem/state-check-shell, lowmem/tunneldigger-watchdog-shell - gluon-state-check and tunneldigger-watchdog as shell instead of Lua, because the Lua runtime on 64 MB devices came from flash every minute respectively every five minutes. Not solvable as a package: they replace files owned by Gluon packages, that would cause an opkg conflict.
  • status-page/* - Gluon’s status page has no extension point in 2023.2. The data now comes from neanderfunk-respondd, as a patch the display remains.
  • setup-mode-network/* - skeleton of 14 lines: the binding of dnsmasq to br-setup and the portal redirect. The rest neanderfunk-setup-wifi has taken over.
  • Devices that Gluon 2025.1 also does not have: eap225-wall-v2, zyxel-nbg6616, fritz-repeater-3000, ea8300-dallas, cudy-m1800, rb750gr3, Cudy ap3000-v1 and tr3000-256mb-v1, FRITZ!Box 3390, two MikroTik ath79, zte-mf286r, MikroTik wAP ac, NanoPi R2C.
  • device-fixes/fix-xiaomi-ax6s-bootflags - the A/B bootloader of Redmi AX6S otherwise falls back to stock firmware after a few reboots. Verified on device; whether OpenWrt now solves it differently is not checked.
  • Local only: network/interface-role-migration21 (our 2021 migration), build/patch-gluon-makefiles (GLUON_TARGETS via override).

What Sits Next to the Images

In each domain directory next to ./sysupgrade, ./factory and ./other is a directory ./site describing the build. Whoever wants can thus repeat the build. It is not a reproducible build, it is about transparency:

  • Gluon standard, handled in one line: site.conf, site.mk, modules, image-customization.lua and i18n/ are what every Gluon site has.
  • build-info.txt - the origin of the image: release, domain, template, branch, start and end of run, buildhost and call, full commit IDs of firmware repo and Gluon with note clean or N changes, per module desired commit and current state, target and domain list, kernel and WLAN driver versions from the OpenWrt manifest and each applied patch with result.
  • build.conf - the run parameters of build.sh, commented: version scheme, make clean and git reset, -j, BROKEN, autoupdater, log options, name of signing key file, worker count and space check. Which targets and domains to build is not here.
  • targets.conf - which Gluon targets to build. Deselected ones are prefixed with -, each with node count commented.
  • domains.conf - which domains in which order to build.
  • build.sh - the build system itself: reset Gluon tree, call prepare, golden tree and worker overlays, build loop over targets and domains, sign manifest, place result with this directory.
  • prepare.sh - applies patches from patches/ in fixed order, in two phases before and after Gluon’s make update.
  • prepare.log - the complete log of preparation, once per run and same for all domains: reset, make update with Gluon’s patches, our patches, feeds and downloads. Without the compiling.
  • build.log.gz - the complete make log with V=s for this one domain over all 20 targets, about 367,000 lines. Note: the first built domain compiles everything, in each further one almost only image assembly remains.

How It Is Built

Measurement report (in English) with raw data and analysis script, including the release run: firmware/docs/parallel-builds/README.md at de00b9dba382a347eab20bbc783fc7b1fd1627ff · Neanderfunk/firmware · GitHub

  • Golden tree. The Gluon tree is fully built once, serially with full -j: git reset, make clean, make update, our patches, then the first domain for all targets. After that it is read-only. It contains everything OpenWrt builds, toolchain, packages, kernel, build_dir and staging_dir per target.
  • Reused via a fingerprint of inputs: Gluon commit, target list, device selection, BROKEN, gcc and libc version of the host, SHA-256 of all patches and common template files. If it matches, prepare and golden tree are skipped entirely. Patches are then not even re-applied, because new mtimes would cause OpenWrt to rebuild.
  • Why it helps at all: a domain change compiles no single package, only package/gluon-site hangs on the site directory. Costs lie per image, not per config, measured about 94 min per domain for 448 images, so about 12.6 s per image.
  • Workers on kernel overlayfs, not fuse-overlayfs: lower is the golden tree, held via bind mount, upper a private directory per worker. Mounted at the original tree path because OpenWrt writes absolute paths into its .prepared stamps.
  • Rootless: two user namespaces (unshare -Urm), overlay with userxattr, then back to normal UID, because OpenWrt does not build as root. Prerequisites: Linux from 5.11, util-linux from 2.38, bash from 5.1, on Ubuntu from 23.10 additionally kernel.apparmor_restrict_unprivileged_userns=0. build.sh pre-tries a real overlay mount and continues serially if it fails.
  • Split by targets, not domains. A worker builds one target for all domains, then its upper is deleted. Reason is space: the overlay grows per target (first domain 2.8 GB, each further 0.2 GB), on the target axis about 11 GB per worker, on the domain axis it would be about 62 GB.
  • Cost of the overlay, measured: mount 0.00 s, discard about 3 s for 3 GB and 70,000 files. Overhead with all devices from ramips-mt7621 was unmeasurable (241/219 s direct vs. 238/224 s in overlay); with only two devices, where fixed costs dominate, 2.6%.
  • What parallelism brings, measured: subsequent domains ran with six workers 2.7 to 2.8 times faster than serially, not six times. Each individual step takes about 1.7 times as long under load, with nine workers about 1.85 times. The cause is narrowed down but not proven, most likely the clock under full load.
  • The release run: 86 variants times 20 targets, 1720 steps, 8 workers in three waves, 37,066 images totaling 325.7 GB. From zero about 40 h, distributed over two runs because the first was halted after 14 of 20 golden steps. The second run took 36 h 29 min: prepare 82 min, remaining golden steps 136 min, parallel phase 32.4 h at an average 6.8 of 8 workers used, wrap-up 29 min. A serial comparison run of this scope does not exist. Extrapolated from measured individual costs it would be about 138 h, calculated for 22 targets.
  • Limits:
    • fuse-overlayfs is unsuitable. OpenWrt does rm -rf root.orig-<target> and immediately after cp -fpR; there the lower directory reappears and cp fails with „File exists“. Kernel overlayfs sets the whiteout correctly.
    • No overlayfs on overlayfs. In the container /tmp is not suitable as upper, a bind-mounted ext4 path is.
    • The golden tree itself is serial and with few domains the largest item. Parallelizing it is hard because all targets build into the same tree.
    • The parallel phase is never shorter than the longest target, for us ath79-generic, and the last wave runs with few workers.
    • Occupied on a host with few runs. Only one config was repeated, deviation about 1%. x86-64 was not part of the overlay measurements.

If You Want to Reuse Something from This

The packages go individually, the build system less so.

  • Feed in site/modules, pinned to the state from this release: PACKAGES_NEANDERFUNK_REPO=https://github.com/Neanderfunk/packages.git, _BRANCH=v2023.2.x, _COMMIT=afccfac4709d5a8627a3592f51cedc847c984f2c. Built for Gluon v2023.2.x.
  • Package names in image-customization.lua. neanderfunk-setup-mode brings its own theme, without '-gluon-config-mode-theme' the build breaks.
  • CONFLICTS are listed per package above, the original must come out of the package list. Exception: hotfix and mt7915-backlog in the release state declare no CONFLICTS against ffac-mt7915-hotfix and ffac-mt7915-backlog. If you have both, you get two watchdogs running in parallel, without error message. Remove the ffac packages by hand.
  • Patches: patches/README.md in the tag, section „Take individual patches“. Copy group, patches/lib-patch.sh with it, call script from the Gluon directory, mind the phase.
  • Do not take: kernel/revert-mips-tlb-uniquify (in v2023.2.x superfluous since 22.09.2026), lowmem/limit-wireless-buffers (dropped with 2025.1), network/interface-role-migration21 (our 2021 migration) and everything under build/ (our opkg keys).
  • build.sh, targets.conf, domains and site templates are tuned to us and not built to run elsewhere.

Questions welcome here in the thread.

Nice, thanks for the detailed description.

But I have a question:

But if …

… that’s already solved for v2025.1 or can be solved, why still v2023.2? Or what am I misunderstanding here?

Because a 2025.1.x is not a 2023.2.6.

See the section „for reasons“.

A fixed, thus usable 2023.2.6 release has been pending for months.

Or in other words: test releases of 2025.1.x should not overtake a release that hasn’t been rolled out via autoupdater yet.

Update: Build system split, plus commands flash, portrole, channel, offlinescan

For potential reusers: The build system is now split. All links pinned to the state of a test run with the new structure:

While we’re at it, in neanderfunk-banner, starting with the next build:

aber wer die 2025.1.3 testen möchte: