From c69b9ff81711724b43b45b8a30397a3bbb708aa2 Mon Sep 17 00:00:00 2001 From: Aodhan Collins Date: Sun, 27 Jul 2025 02:03:58 +0100 Subject: [PATCH] Added pi optimizations --- .env.example | 9 ++- api.py | 153 +++++++++++++++++++++++++++++++++------------------ setup-pi.sh | 148 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 254 insertions(+), 56 deletions(-) create mode 100644 setup-pi.sh diff --git a/.env.example b/.env.example index 14675c9..c2de8d8 100644 --- a/.env.example +++ b/.env.example @@ -2,18 +2,21 @@ # Copy this file to .env and customize the values # Server Configuration -PORT=5000 +PORT=5005 HOST=0.0.0.0 -DEBUG=True +DEBUG=False # Portainer Integration (Optional) PORTAINER_URL=https://port.liveaodh.com PORTAINER_USERNAME=aodhan PORTAINER_PASSWORD=8yFdXkx274aSRX +# Raspberry Pi Optimizations +PI_OPTIMIZATIONS=true + # Docker Configuration DOCKER_HOST=unix:///var/run/docker.sock # Application Settings -REFRESH_INTERVAL=30 +REFRESH_INTERVAL=60 MAX_CONTAINERS=50 diff --git a/api.py b/api.py index dd5a0d1..60500c6 100644 --- a/api.py +++ b/api.py @@ -136,27 +136,27 @@ class DockerService: return networks def get_system_info(self): - """Get system information""" + """Get system information optimized for Raspberry Pi""" try: - # CPU usage - cpu_percent = psutil.cpu_percent(interval=1) + # CPU usage - reduced interval for Pi + cpu_percent = psutil.cpu_percent(interval=0.5) # Memory usage memory = psutil.virtual_memory() - # Disk usage - try multiple paths for different systems + # Disk usage - optimized for Pi filesystem disk_info = None + + # Try common Pi filesystem locations disk_paths = ['/'] - # Add common mount points for different Linux distributions - if os.path.exists('/home'): - disk_paths.append('/home') - if os.path.exists('/var'): - disk_paths.append('/var') - if os.path.exists('/opt'): - disk_paths.append('/opt') + # Check for common Pi mount points + pi_mounts = ['/boot', '/home', '/opt', '/var'] + for mount in pi_mounts: + if os.path.exists(mount): + disk_paths.append(mount) - # Try each path until we find one that works + # Try to get SD card usage (rootfs) for path in disk_paths: try: disk_info = psutil.disk_usage(path) @@ -164,86 +164,107 @@ class DockerService: except (OSError, IOError, PermissionError): continue - # Fallback to root directory + # Fallback to root if disk_info is None: try: disk_info = psutil.disk_usage('/') except (OSError, IOError) as e: - logging.error(f"Cannot get disk usage: {e}") - # Return zero values as fallback + logging.warning(f"Cannot get disk usage on Pi: {e}") disk_info = type('DiskInfo', (), { 'total': 0, 'used': 0, 'free': 0 })() - # Get load average (works on both Debian and Fedora) + # ARM-specific optimizations try: - import os - load_avg = os.getloadavg() - except (AttributeError, OSError): - load_avg = (0.0, 0.0, 0.0) + # Check if we're on ARM (Pi) + with open('/proc/cpuinfo', 'r') as f: + cpu_info = f.read() + is_arm = 'ARM' in cpu_info or 'arm' in cpu_info + except: + is_arm = False - # Get swap memory - swap = psutil.swap_memory() + # Get temperature for Pi + cpu_temp = None + try: + # Try Pi-specific temperature reading + temp_paths = [ + '/sys/class/thermal/thermal_zone0/temp', + '/sys/class/hwmon/hwmon0/temp1_input', + '/sys/class/thermal/thermal_zone1/temp' + ] + for temp_path in temp_paths: + try: + with open(temp_path, 'r') as f: + temp = int(f.read().strip()) + if temp > 1000: # Convert millidegrees to degrees + temp = temp / 1000.0 + cpu_temp = round(temp, 1) + break + except (IOError, OSError): + continue + except: + cpu_temp = None - # Format disk usage for display - def format_bytes(bytes_value): - """Format bytes to human readable format""" - for unit in ['B', 'KB', 'MB', 'GB', 'TB']: - if bytes_value < 1024.0: - return f"{bytes_value:.1f} {unit}" - bytes_value /= 1024.0 - return f"{bytes_value:.1f} PB" + # Memory optimization for Pi + def format_bytes_pi(bytes_value): + """Format bytes optimized for Pi display""" + if bytes_value < 1024: + return f"{bytes_value} B" + elif bytes_value < 1024 * 1024: + return f"{bytes_value / 1024:.0f} KB" + elif bytes_value < 1024 * 1024 * 1024: + return f"{bytes_value / (1024 * 1024):.0f} MB" + else: + return f"{bytes_value / (1024 * 1024 * 1024):.1f} GB" return { 'cpu': { 'percent': cpu_percent, 'cores': psutil.cpu_count(), - 'load_avg': load_avg + 'is_arm': is_arm, + 'temperature': cpu_temp }, 'memory': { 'total': memory.total, 'available': memory.available, 'used': memory.used, 'percent': memory.percent, - 'formatted_total': format_bytes(memory.total), - 'formatted_used': format_bytes(memory.used), - 'formatted_available': format_bytes(memory.available) - }, - 'swap': { - 'total': swap.total, - 'used': swap.used, - 'percent': swap.percent + 'formatted_total': format_bytes_pi(memory.total), + 'formatted_used': format_bytes_pi(memory.used), + 'formatted_available': format_bytes_pi(memory.available) }, 'disk': { 'total': disk_info.total, 'used': disk_info.used, 'free': disk_info.free, - 'percent': (disk_info.used / disk_info.total) * 100 if disk_info.total > 0 else 0, - 'formatted_total': format_bytes(disk_info.total), - 'formatted_used': format_bytes(disk_info.used), - 'formatted_free': format_bytes(disk_info.free) + 'percent': round((disk_info.used / disk_info.total) * 100, 1) if disk_info.total > 0 else 0, + 'formatted_total': format_bytes_pi(disk_info.total), + 'formatted_used': format_bytes_pi(disk_info.used), + 'formatted_free': format_bytes_pi(disk_info.free) }, 'uptime': self._get_system_uptime(), 'platform': { 'system': platform.system(), 'release': platform.release(), - 'machine': platform.machine() + 'machine': platform.machine(), + 'is_raspberry_pi': self._is_raspberry_pi() } } except Exception as e: - logging.error(f"Error getting system info: {e}") + logging.warning(f"System info error on Pi: {e}") return { 'error': str(e), - 'cpu': {'percent': 0, 'cores': 1}, + 'cpu': {'percent': 0, 'cores': 1, 'is_arm': True}, 'memory': {'total': 0, 'available': 0, 'used': 0, 'percent': 0}, 'disk': {'total': 0, 'used': 0, 'free': 0, 'percent': 0}, - 'uptime': 'Unknown' + 'uptime': 'Unknown', + 'platform': {'system': 'Linux', 'is_raspberry_pi': True} } def _get_system_uptime(self): - """Get system uptime""" + """Get system uptime optimized for Pi""" try: with open('/proc/uptime', 'r') as f: uptime_seconds = float(f.readline().split()[0]) @@ -252,10 +273,24 @@ class DockerService: hours = int((uptime_seconds % 86400) // 3600) minutes = int((uptime_seconds % 3600) // 60) - return f"{days}d {hours}h {minutes}m" + if days > 0: + return f"{days}d {hours}h" + elif hours > 0: + return f"{hours}h {minutes}m" + else: + return f"{minutes}m" except Exception: return "Unknown" + def _is_raspberry_pi(self): + """Check if running on Raspberry Pi""" + try: + with open('/proc/cpuinfo', 'r') as f: + cpuinfo = f.read() + return any(x in cpuinfo.lower() for x in ['raspberry', 'bcm', 'broadcom']) + except: + return False + def check_service_health(self, url, port=None): """Check if a service is responding""" try: @@ -267,11 +302,14 @@ class DockerService: except Exception: return False -# Configuration from environment variables -PORT = int(os.getenv('PORT', 5000)) +# Configuration from environment variables - optimized for Raspberry Pi +PORT = int(os.getenv('PORT', 8080)) # Use 8080 as default for Pi HOST = os.getenv('HOST', '0.0.0.0') DEBUG = os.getenv('DEBUG', 'False').lower() == 'true' -REFRESH_INTERVAL = int(os.getenv('REFRESH_INTERVAL', 30)) +REFRESH_INTERVAL = int(os.getenv('REFRESH_INTERVAL', 60)) # Longer refresh for Pi + +# Raspberry Pi specific optimizations +PI_OPTIMIZATIONS = os.getenv('PI_OPTIMIZATIONS', 'True').lower() == 'true' # Initialize Docker service docker_service = DockerService() @@ -526,4 +564,13 @@ def restart_container(container_id): return jsonify({'success': False, 'error': str(e)}), 500 if __name__ == '__main__': - app.run(host=HOST, port=PORT, debug=DEBUG) + # Raspberry Pi optimizations + if PI_OPTIMIZATIONS: + # Reduce logging for production + logging.getLogger('werkzeug').setLevel(logging.ERROR) + logging.getLogger('urllib3').setLevel(logging.ERROR) + + # Use threaded mode for better performance on Pi + app.run(host=HOST, port=PORT, debug=DEBUG, threaded=True) + else: + app.run(host=HOST, port=PORT, debug=DEBUG) diff --git a/setup-pi.sh b/setup-pi.sh new file mode 100644 index 0000000..561e93f --- /dev/null +++ b/setup-pi.sh @@ -0,0 +1,148 @@ +#!/bin/bash +# Raspberry Pi Optimized Setup Script for Docker Dashboard + +set -e + +echo "🍓 Setting up Docker Dashboard for Raspberry Pi..." + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Detect Raspberry Pi +if [[ -f /proc/device-tree/model ]]; then + MODEL=$(tr -d '\0' < /proc/device-tree/model) + echo -e "${GREEN}Detected: $MODEL${NC}" +elif [[ -f /proc/cpuinfo ]] && grep -q "Raspberry Pi" /proc/cpuinfo; then + echo -e "${GREEN}Detected: Raspberry Pi${NC}" +else + echo -e "${YELLOW}Warning: Not running on Raspberry Pi, continuing anyway...${NC}" +fi + +# Update system packages +echo -e "${YELLOW}Updating system packages...${NC}" +sudo apt update && sudo apt upgrade -y + +# Install Python3 and pip if not present +echo -e "${YELLOW}Installing Python3 and pip...${NC}" +sudo apt install -y python3 python3-pip python3-venv curl wget + +# Install Docker if not present +if ! command -v docker &> /dev/null; then + echo -e "${YELLOW}Installing Docker...${NC}" + curl -fsSL https://get.docker.com -o get-docker.sh + sudo sh get-docker.sh + sudo usermod -aG docker $USER + rm get-docker.sh +else + echo -e "${GREEN}Docker already installed${NC}" +fi + +# Create virtual environment +echo -e "${YELLOW}Creating Python virtual environment...${NC}" +python3 -m venv venv +source venv/bin/activate + +# Install dependencies optimized for Pi +echo -e "${YELLOW}Installing Python dependencies...${NC}" +pip install --upgrade pip + +# Install packages with Pi optimizations +pip install flask==2.3.3 +pip install flask-cors==4.0.0 +pip install docker==6.1.3 +pip install psutil==5.9.5 +pip install requests==2.31.0 +pip install python-dotenv==1.0.0 + +# Create .env file optimized for Pi +echo -e "${YELLOW}Creating Pi-optimized configuration...${NC}" +cat > .env << EOF +# Raspberry Pi Optimized Configuration +PORT=8080 +HOST=0.0.0.0 +DEBUG=false +REFRESH_INTERVAL=60 +PI_OPTIMIZATIONS=true + +# Optional: Portainer integration +# PORTAINER_URL=http://localhost:9000 +# PORTAINER_USERNAME=admin +# PORTAINER_PASSWORD=yourpassword + +# Docker socket path (Pi specific) +DOCKER_HOST=unix:///var/run/docker.sock +EOF + +# Initialize database +echo -e "${YELLOW}Initializing database...${NC}" +python3 -c " +from database import db +db.init_database() +print('✅ Database initialized successfully') +" + +# Create systemd service for auto-start +echo -e "${YELLOW}Creating systemd service...${NC}" +sudo tee /etc/systemd/system/docker-dashboard.service > /dev/null << EOF +[Unit] +Description=Docker Dashboard for Raspberry Pi +After=network.target docker.service +Requires=docker.service + +[Service] +Type=simple +User=$USER +WorkingDirectory=$(pwd) +Environment=PATH=$(pwd)/venv/bin +ExecStart=$(pwd)/venv/bin/python3 api.py +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +EOF + +# Set permissions +sudo chmod 644 /etc/systemd/system/docker-dashboard.service + +# Create logs directory +mkdir -p logs + +# Create simple start/stop scripts +cat > start-pi.sh << 'EOF' +#!/bin/bash +echo "Starting Docker Dashboard on Raspberry Pi..." +sudo systemctl start docker-dashboard +sudo systemctl status docker-dashboard --no-pager -l +EOF + +cat > stop-pi.sh << 'EOF' +#!/bin/bash +echo "Stopping Docker Dashboard..." +sudo systemctl stop docker-dashboard +sudo systemctl status docker-dashboard --no-pager -l +EOF + +chmod +x start-pi.sh stop-pi.sh + +# Enable service +sudo systemctl daemon-reload +sudo systemctl enable docker-dashboard + +echo -e "${GREEN}✅ Setup complete!${NC}" +echo "" +echo "Usage:" +echo " ./start-pi.sh - Start the dashboard" +echo " ./stop-pi.sh - Stop the dashboard" +echo " ./setup-pi.sh - Re-run this setup" +echo "" +echo "Access the dashboard at: http://$(hostname -I | awk '{print $1}'):8080" +echo "" +echo -e "${YELLOW}To start immediately:${NC}" +echo " sudo systemctl start docker-dashboard" +echo "" +echo -e "${YELLOW}To view logs:${NC}" +echo " sudo journalctl -u docker-dashboard -f"