Phase 4 enhancements: WebSocket auth, HTTPS UI, plugin hooks, build fixes

Security:
- Add token-based WebSocket authentication (closes critical security gap)
  - In-memory token store with 24h TTL (token_store.py)
  - POST /api/auth/token exchanges Basic Auth for WS token
  - GET /api/auth/status public endpoint for auth check
  - WebSocket validates token query param, rejects with close code 4401
  - Frontend LoginPrompt modal for credential entry
  - WebSocket manager handles full auth flow with auth_required state
  - No-op when AUTH_ENABLED=false (preserves existing behavior)

HTTPS:
- Wire HTTPS toggle in Settings UI (POST /api/system/ssl/toggle)
- Add certificate regeneration button
- Display SSL info (expiration, SANs, SHA256 fingerprint)

Plugins:
- Wire trigger_hook("pm3_command") in PM3 service
- Wire trigger_hook("update_check") in update manager

Build/Infrastructure:
- Enable NetworkManager in pi-gen AP setup stage
- Add HF booster board detection patch for Proxmark3
- Update LED PWM control patch
- Fix BLE adapter, UPS drivers, WiFi manager improvements
- Update HTTPS support stage script

Documentation:
- Update PROJECT_STATUS.md and IMPLEMENTATION_PRIORITIES.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
michael
2026-03-03 11:45:11 -08:00
parent 4f35df1781
commit 2ec89041ef
24 changed files with 1249 additions and 362 deletions

View File

@@ -111,6 +111,13 @@ class UpdateManager:
"message": "No releases found"
}
# Trigger plugin hooks (non-critical)
try:
from .plugin_manager import get_plugin_manager
await get_plugin_manager().trigger_hook("update_check")
except Exception:
pass
# Compare versions
if self._is_newer_version(release.version, self._current_version):
self._latest_release = release

View File

@@ -16,7 +16,9 @@ from .i2c_driver import I2CFuelGaugeDriver
async def auto_detect_driver(
pisugar_host: str = "127.0.0.1",
pisugar_port: int = 8423,
prefer_native_i2c: bool = True
prefer_native_i2c: bool = True,
i2c_retries: int = 5,
i2c_retry_delay: float = 1.0
) -> Tuple[Optional[UPSDriver], str]:
"""Auto-detect available UPS hardware and return appropriate driver.
@@ -29,6 +31,8 @@ async def auto_detect_driver(
pisugar_host: PiSugar server host for daemon detection (legacy)
pisugar_port: PiSugar server port (legacy)
prefer_native_i2c: If True, prefer native I2C driver over daemon
i2c_retries: Number of I2C detection retries (for boot timing)
i2c_retry_delay: Delay between I2C retries in seconds
Returns:
Tuple of (driver_instance or None, detection_message)
@@ -37,13 +41,16 @@ async def auto_detect_driver(
# Try native PiSugar I2C first (no daemon overhead, minimal CPU)
if prefer_native_i2c:
print("UPS auto-detect: Trying PiSugar I2C (native)...")
detected, driver = await PiSugarI2CDriver.detect()
print(f"UPS auto-detect: Trying PiSugar I2C (native, {i2c_retries} retries)...")
detected, driver = await PiSugarI2CDriver.detect(
retries=i2c_retries,
retry_delay=i2c_retry_delay
)
if detected and driver:
model = driver.get_model_name()
print(f"UPS auto-detect: SUCCESS - PiSugar I2C: {model}")
return (driver, f"Detected PiSugar UPS via I2C: {model}")
print("UPS auto-detect: PiSugar I2C not detected")
print("UPS auto-detect: PiSugar I2C not detected after all retries")
# Try PiSugar daemon (legacy fallback)
print("UPS auto-detect: Trying PiSugar daemon...")

View File

@@ -103,7 +103,7 @@ class UPSManager:
print(f"Unknown UPS type '{ups_type}', will auto-detect")
return None
async def initialize(self, startup_delay: float = 1.0) -> bool:
async def initialize(self, startup_delay: float = 2.0) -> bool:
"""Initialize connection to UPS hardware.
Args:
@@ -124,15 +124,19 @@ class UPSManager:
self._status.error_message = "UPS disabled"
return False
# Small delay to ensure I2C bus is ready after boot
# Delay to ensure I2C bus is ready after boot
# PiSugar and other I2C devices may not be ready immediately
if startup_delay > 0:
print(f"UPS: Waiting {startup_delay}s for I2C bus to settle...")
await asyncio.sleep(startup_delay)
# Run auto-detection
# Run auto-detection with extended retries for boot scenarios
print("Auto-detecting UPS hardware...")
driver, message = await auto_detect_driver(
pisugar_host=config.UPS_PISUGAR_HOST,
pisugar_port=config.UPS_PISUGAR_PORT
pisugar_port=config.UPS_PISUGAR_PORT,
i2c_retries=5,
i2c_retry_delay=1.0
)
if driver:

View File

@@ -714,16 +714,27 @@ class WiFiManager:
if "successfully activated" in result.lower():
logger.warning(f"[WiFi Connect] SUCCESS - connected to {ssid}")
# Verify we have an IP address
# Verify we have a non-AP IP address
await asyncio.sleep(2)
ip_result = await self._run_command(f"ip addr show {interface}")
logger.warning(f"[WiFi Connect] IP result: {ip_result}")
if "inet " in ip_result and "192.168.4." not in ip_result:
# Extract all IP addresses and check for non-AP IPs
# AP uses 192.168.4.x subnet
ip_matches = re.findall(r'inet (\d+\.\d+\.\d+\.\d+)', ip_result)
client_ips = [ip for ip in ip_matches if not ip.startswith("192.168.4.")]
if client_ips:
logger.warning(f"[WiFi Connect] Found client IP(s): {client_ips}")
# Remove the AP static IP since we're in client mode now
await self._run_command(f"ip addr del 192.168.4.1/24 dev {interface}", check=False)
# Save client mode for boot persistence
self._current_mode = WiFiMode.CLIENT
self._save_mode(WiFiMode.CLIENT)
logger.info(f"WiFi client mode saved for boot persistence")
return True
else:
logger.warning(f"[WiFi Connect] No client IP found, only AP IPs: {ip_matches}")
# Connection failed
logger.warning(f"[WiFi Connect] FAILED - {result}")