This commit adds the complete Dangerous Pi web management interface with all MVP features implemented and tested locally. ## New Features ### Backend (Python + FastAPI) - Complete FastAPI backend with async support - 40+ API endpoints (Health, PM3, WiFi, Updates, UPS, BLE, Plugins) - 6 managers: Session, WiFi, Update, UPS, BLE, Plugin - SQLite database with sessions, config, history, crash reports - Server-Sent Events (SSE) for real-time notifications - Mock PM3 worker for development without hardware ### WiFi Manager - Interface detection (USB vs built-in) - Network scanning with signal strength - Mode switching (AP/Client/Dual/Auto/Off) - Network connection with password support - Hidden SSID and saved networks support - Static IP and DHCP configuration - 10 WiFi API endpoints ### Update Manager - GitHub releases API integration - Automatic periodic update checks - Semantic version comparison - Update download with progress tracking - SHA256 checksum verification - Automatic installation with backup and rollback - PM3 client rebuild after updates - 6 Update API endpoints ### UPS Manager - I2C battery monitoring (MAX17040-compatible) - Battery percentage, voltage, current tracking - Power source detection (AC/Battery) - Safe shutdown triggers at configurable thresholds - Event callbacks for battery warnings - SSE and BLE notification integration - 3 UPS API endpoints ### BLE Manager - Bluetooth Low Energy notification support - Auto-detects BLE capability - Multiple notification types (updates, battery, shutdown, etc.) - BLE advertising management - Device connection tracking - 4 BLE API endpoints ### Plugin Framework - Dynamic plugin loading/unloading - Plugin lifecycle management (load, enable, disable, unload) - Hook system for extensibility - JSON-based metadata - Example "Hello World" plugin included - 7 Plugin API endpoints ### Frontend (Remix.js + React) - Cyberpunk-themed responsive UI - Dashboard with system status - PM3 command interface with history - Settings page with WiFi and Update management - Command logs viewer - Theme toggle (Dark/Light/Auto) - Server-side rendering (SSR) - Mobile-first responsive design ### System Integration - Systemd service with security hardening - Automated install/uninstall scripts - Environment configuration template - Hardware access groups (i2c, bluetooth, gpio, dialout) - Pi-gen stage 04 integration for OS image building - Port conflict resolution with ttyd-bash - I2C interface auto-enable for UPS HAT ### Testing - test_backend.py - Backend API tests - test_ups.py - UPS manager tests - test_ble.py - BLE manager tests - test_plugins.py - Plugin manager tests - All tests passing locally ### Documentation - 12 comprehensive documentation files - claude.md - AI development guide - WIFI_MANAGER.md - WiFi management guide - UPDATE_MANAGER.md - Update system guide - PORT_CONFLICT.md - Port conflict resolution guide - MVP_COMPLETE.md - MVP implementation summary - PROJECT_STATUS.md - Project status and roadmap - systemd/README.md - Service management docs - pi-gen integration documentation ## Technical Details - ~5,000+ lines of backend code - 11 Python dependencies (smbus2 added for UPS) - FastAPI with async/await throughout - Type hints and docstrings on all functions - RESTful API design with SSE for notifications - Security hardening (non-root, protected dirs, resource limits) ## Next Steps - Deploy to Raspberry Pi Zero 2 W hardware - Test with real Proxmark3 device - Test UPS HAT integration - Test BLE on Pi hardware - Build custom OS image with pi-gen - Performance optimization for Pi Zero 2 W 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
133 lines
3.8 KiB
Python
133 lines
3.8 KiB
Python
"""Session manager for single-user access control."""
|
|
import asyncio
|
|
import time
|
|
from typing import Optional
|
|
from dataclasses import dataclass
|
|
import uuid
|
|
|
|
from .. import config
|
|
|
|
|
|
@dataclass
|
|
class Session:
|
|
"""Active session information."""
|
|
session_id: str
|
|
client_ip: str
|
|
user_agent: Optional[str]
|
|
created_at: float
|
|
last_activity: float
|
|
|
|
|
|
class SessionManager:
|
|
"""Manages single active session for PM3 access."""
|
|
|
|
def __init__(self):
|
|
"""Initialize session manager."""
|
|
self._active_session: Optional[Session] = None
|
|
self._lock = asyncio.Lock()
|
|
|
|
def has_active_session(self) -> bool:
|
|
"""Check if there's an active session."""
|
|
if not self._active_session:
|
|
return False
|
|
|
|
# Check if session has timed out
|
|
if time.time() - self._active_session.last_activity > config.SESSION_TIMEOUT:
|
|
self._active_session = None
|
|
return False
|
|
|
|
return True
|
|
|
|
async def create_session(
|
|
self,
|
|
client_ip: str,
|
|
user_agent: Optional[str] = None,
|
|
force_takeover: bool = False
|
|
) -> tuple[bool, Optional[str], Optional[str]]:
|
|
"""Create a new session.
|
|
|
|
Args:
|
|
client_ip: Client IP address
|
|
user_agent: Client user agent string
|
|
force_takeover: Force takeover of existing session
|
|
|
|
Returns:
|
|
Tuple of (success, session_id, error_message)
|
|
"""
|
|
async with self._lock:
|
|
# Check if another session is active
|
|
if self.has_active_session() and not force_takeover:
|
|
return False, None, "Another session is active"
|
|
|
|
# Create new session
|
|
session_id = str(uuid.uuid4())
|
|
current_time = time.time()
|
|
|
|
self._active_session = Session(
|
|
session_id=session_id,
|
|
client_ip=client_ip,
|
|
user_agent=user_agent,
|
|
created_at=current_time,
|
|
last_activity=current_time
|
|
)
|
|
|
|
return True, session_id, None
|
|
|
|
async def release_session(self, session_id: str) -> bool:
|
|
"""Release a session.
|
|
|
|
Args:
|
|
session_id: Session ID to release
|
|
|
|
Returns:
|
|
True if session was released, False if not found
|
|
"""
|
|
async with self._lock:
|
|
if self._active_session and self._active_session.session_id == session_id:
|
|
self._active_session = None
|
|
return True
|
|
return False
|
|
|
|
def update_activity(self, session_id: str) -> bool:
|
|
"""Update session activity timestamp.
|
|
|
|
Args:
|
|
session_id: Session ID to update
|
|
|
|
Returns:
|
|
True if updated, False if session not found
|
|
"""
|
|
if self._active_session and self._active_session.session_id == session_id:
|
|
self._active_session.last_activity = time.time()
|
|
return True
|
|
return False
|
|
|
|
def can_execute(self, session_id: Optional[str]) -> bool:
|
|
"""Check if a session can execute commands.
|
|
|
|
Args:
|
|
session_id: Session ID to check (None for no session)
|
|
|
|
Returns:
|
|
True if session can execute, False otherwise
|
|
"""
|
|
# No active session - allow execution
|
|
if not self.has_active_session():
|
|
return True
|
|
|
|
# Check if the provided session ID matches active session
|
|
if session_id and self._active_session and self._active_session.session_id == session_id:
|
|
return True
|
|
|
|
return False
|
|
|
|
def get_active_session(self) -> Optional[Session]:
|
|
"""Get the currently active session.
|
|
|
|
Returns:
|
|
Active session or None
|
|
"""
|
|
if self.has_active_session():
|
|
return self._active_session
|
|
return None
|