Feature/wifi setup improvements (#187)

* fix: Handle permission errors when removing plugin directories

- Added _safe_remove_directory() method to handle permission errors gracefully
- Fixes permissions on __pycache__ directories before removal
- Updates uninstall_plugin() and install methods to use safe removal
- Resolves [Errno 13] Permission denied errors during plugin install/uninstall

* feat(wifi): Add grace period for AP mode and improve setup documentation

- Add 90-second grace period (3 checks at 30s intervals) before enabling AP mode
- Change AP to open network (no password) for easier initial setup
- Add verification script for WiFi setup
- Update documentation with grace period details and open network info
- Improve WiFi monitor daemon logging and error handling

* feat(wifi): Add Trixie compatibility and dynamic interface discovery

- Add dynamic WiFi interface discovery instead of hardcoded wlan0
  - Supports traditional (wlan0), predictable (wlp2s0), and USB naming
  - Falls back gracefully if detection fails

- Add Raspberry Pi OS Trixie (Debian 13) detection and compatibility
  - Detect Netplan configuration and connection file locations
  - Disable PMF (Protected Management Frames) on Trixie for better
    client compatibility with certain WiFi adapters

- Improve nmcli hotspot setup for Trixie
  - Add explicit IP configuration (192.168.4.1/24)
  - Add channel configuration to hotspot creation
  - Handle Trixie's default 10.42.0.1 IP override

- Add dnsmasq conflict detection
  - Warn if Pi-hole or other DNS services are using dnsmasq
  - Create backup before overwriting config

- Improve error handling
  - Replace bare except clauses with specific exceptions
  - All subprocess calls now have explicit timeouts

- Document sudoers requirements in module docstring
  - List all required NOPASSWD entries for ledpi user

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(wifi): Use NM_CONNECTIONS_PATHS constant in _detect_trixie

Replace hardcoded Path instances with references to the
NM_CONNECTIONS_PATHS constant for consistency.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(verify): Use ETH_CONNECTED and AP_ACTIVE in summary output

Add connectivity summary section that displays Ethernet and AP mode
status using the previously unused ETH_CONNECTED and AP_ACTIVE flags.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Chuck <chuck@example.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-01-19 16:43:02 -05:00
committed by GitHub
parent 2381ead03f
commit 1833e30c1d
5 changed files with 771 additions and 121 deletions

View File

@@ -1415,10 +1415,10 @@ class PluginStoreManager:
try:
# Try to fix permissions on __pycache__ directories recursively
import stat
for root, _dirs, files in os.walk(path):
for root, dirs, files in os.walk(path):
root_path = Path(root)
try:
# Make directory writable (0o777 is acceptable here - temporary before deletion)
# Make directory writable
os.chmod(root_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
except (OSError, PermissionError):
pass
@@ -1436,7 +1436,7 @@ class PluginStoreManager:
self.logger.info(f"Successfully removed {path} after fixing permissions")
return True
except Exception as e2:
self.logger.exception(f"Failed to remove {path} even after fixing permissions: {e2}")
self.logger.error(f"Failed to remove {path} even after fixing permissions: {e2}")
# Last resort: try with ignore_errors
try:
shutil.rmtree(path, ignore_errors=True)
@@ -1448,10 +1448,10 @@ class PluginStoreManager:
self.logger.error(f"Could not remove {path} even with ignore_errors")
return False
except Exception as e3:
self.logger.exception(f"Final removal attempt failed for {path}: {e3}")
self.logger.error(f"Final removal attempt failed for {path}: {e3}")
return False
except Exception as e:
self.logger.exception(f"Unexpected error removing {path}: {e}")
self.logger.error(f"Unexpected error removing {path}: {e}")
return False
def _find_plugin_path(self, plugin_id: str) -> Optional[Path]: