wifi_manager.py:
- _create_hostapd_config: use _validate_ap_config() for ssid/channel instead
of raw self.config values; strip newlines from SSID to prevent config-file
injection via the generated hostapd.conf
- _setup_iptables_redirect: check return codes of sysctl ip_forward enable and
both iptables -A calls; on any failure log the error output, call
_teardown_iptables_redirect() to restore state, and return False instead of
silently succeeding
- _enable_ap_mode_nmcli_hotspot: on AP verification failure roll back fully —
tear down iptables redirect, delete the LEDMatrix-Setup-AP connection profile,
clear the LED message — before returning False
plugins_manager.js:
- initializePlugins: chain searchPluginStore(!isReswapWarm) inside
loadInstalledPlugins().then() so window.installedPlugins is populated before
the store renders Installed/Reinstall badges (same pattern applied to
refreshPlugins() in the previous commit)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
refreshPlugins() called searchPluginStore(true) and showNotification() immediately
after refreshInstalledPlugins() without awaiting the returned Promise, so
window.installedPlugins could still be stale when the store rendered its
Installed/Reinstall badges. Chain .then() so both run only after the fetch
completes.
In initializePlugins(), the re-swap path always passed fetchCommitInfo=false to
searchPluginStore, skipping GitHub metadata even when the 5-minute cache TTL had
expired. Add storeCacheExpired() helper and compute isReswapWarm = _reswap &&
!storeCacheExpired() so fresh metadata is fetched whenever the cache is cold,
regardless of whether the render is a first load or a tab re-swap.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Validate ap_ssid/ap_channel from config before passing to subprocess
(printable ASCII ≤32 chars; channel 1-14) to prevent command injection
- Fix INPUT iptables rule: PREROUTING redirects port 80→5000 so the INPUT
chain sees dport=5000, not 80. Old INPUT rule on port 80 was a no-op.
- Refactor iptables setup/teardown into _setup_iptables_redirect() and
_teardown_iptables_redirect() helpers, eliminating duplicate logic in
the hostapd and nmcli paths
- Save/restore ip_forward state (via /tmp/ledmatrix_ip_forward_saved)
instead of forcing it to 0 on cleanup, which could break VPNs or
bridges already relying on forwarding
- nmcli path skips ip_forward management entirely: NM's ipv4.method=shared
already manages it for the duration of the connection
- Fix _get_ap_status_nmcli() verification: new 'connection add type wifi'
profiles have type '802-11-wireless', not 'hotspot', so verification was
always returning False. Now also matches by our known connection name.
- Remove SSID-based connection deletion: deleting any profile whose SSID
matched the AP SSID could destroy a user's saved home WiFi profile.
Now only deletes by our application-managed profile names.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
nmcli device wifi hotspot always attaches a WPA2 PSK on Bookworm/Trixie
and silently ignores post-creation security modifications, causing users
to be prompted for an unknown password. Switch to nmcli connection add
with 802-11-wireless.mode ap and no security section — NM cannot auto-add
a password to a profile that has no 802-11-wireless-security block.
Also:
- Remove dead DEFAULT_AP_PASSWORD / ap_password config field (stored but
never passed to hostapd or nmcli, causing user confusion)
- Add iptables port 80→5000 redirect to the nmcli AP path so captive portal
auto-popup works on phones without hostapd (previously only worked on
the hostapd path)
- Clean up iptables rules on disable for the nmcli path
- Improve LED message on AP enable: show SSID, "No password", and IP:port
on both paths so users know exactly how to connect
- Fix systemd template: replace hardcoded /home/ledpi/LEDMatrix/ with
__PROJECT_ROOT_DIR__ placeholder (install script already writes correct path)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(plugins): reset pluginsInitialized on HTMX re-swap; use refreshInstalledPlugins in refreshPlugins
HTMX's `revealed` trigger fires each time the plugins tab becomes
visible (Alpine's x-show toggles display:none which re-triggers the
IntersectionObserver). Each re-reveal fetches a fresh empty HTML
skeleton via hx-swap="innerHTML". The htmx:afterSwap handler reset
window.pluginManager.initialized/initializing but not pluginsInitialized,
so initializePlugins() hit its guard and skipped loadInstalledPlugins()
and searchPluginStore() — leaving the fresh empty DOM unpopulated.
Fix: also reset pluginsInitialized = false in the afterSwap handler.
Existing caches (3s for installed plugins, 5min for store) mean tab
revisits within the TTL render from cache instantly with no extra
API traffic.
Also change refreshPlugins() to call refreshInstalledPlugins() (which
already exists and explicitly invalidates the cache) instead of the
bare loadInstalledPlugins() call that could silently skip the fetch
if the 3-second cache happened to still be valid.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(plugins): use cached store on HTMX re-swap; reserve searchPluginStore(true) for first load
searchPluginStore(true) bypasses the isCacheValid check unconditionally,
so every tab revisit was hitting the GitHub commit-info API even within
the 5-minute cache window.
Set window.pluginManager._reswap = true in the htmx:afterSwap handler
and read it in initializePlugins() to call searchPluginStore(false) on
re-swaps (respects the 5-minute cache) vs searchPluginStore(true) on
first load (always fetches fresh). Explicit user refresh via
refreshPlugins() already calls searchPluginStore(true) directly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Chuck <chuck@example.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>