5 Commits

Author SHA1 Message Date
Chuck
15fc9003ac fix: address three wifi_manager and one plugins_manager review findings
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>
2026-04-30 18:33:53 -04:00
Chuck
55a6a53fca fix(plugins): fix async race in refreshPlugins; use cache TTL to gate re-swap metadata fetch
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>
2026-04-30 18:33:53 -04:00
Chuck
c54718af2d fix(wifi): address Codacy review findings in AP mode implementation
- 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>
2026-04-30 18:33:53 -04:00
Chuck
e8afd23c98 fix(wifi): create truly open AP via nmcli connection add; add captive portal to nmcli path
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>
2026-04-30 18:33:53 -04:00
Chuck
5e6c40ad55 fix(plugins): plugin manager tab doesn't always load on first visit (#319)
* 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>
2026-04-30 11:57:26 -04:00

Diff Content Not Available