Update setup.py

This commit is contained in:
DJObleezy 2025-03-25 08:11:38 -07:00 committed by GitHub
parent f34dec00a5
commit 0cbbe8c6a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

411
setup.py
View File

@ -1,12 +1,43 @@
#!/usr/bin/env python3
"""
Setup script to prepare project structure and directories.
Enhanced setup script for Bitcoin Mining Dashboard.
This script prepares the project structure, installs dependencies,
verifies configuration, and provides system checks for optimal operation.
"""
import os
import sys
import shutil
import logging
import argparse
import subprocess
import json
import re
from pathlib import Path
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# Configure logging with color support
try:
import colorlog
handler = colorlog.StreamHandler()
handler.setFormatter(colorlog.ColoredFormatter(
'%(log_color)s%(asctime)s - %(levelname)s - %(message)s',
log_colors={
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red,bg_white',
}
))
logger = colorlog.getLogger()
logger.addHandler(handler)
logger.setLevel(logging.INFO)
except ImportError:
# Fallback to standard logging if colorlog is not available
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger()
# Directory structure to create
DIRECTORIES = [
@ -14,7 +45,8 @@ DIRECTORIES = [
'static/js',
'static/img',
'templates',
'logs'
'logs',
'data' # For temporary data storage
]
# Files to move to their correct locations
@ -26,11 +58,14 @@ FILE_MAPPINGS = {
'boot.css': 'static/css/boot.css',
'error.css': 'static/css/error.css',
'retro-refresh.css': 'static/css/retro-refresh.css',
'blocks.css': 'static/css/blocks.css',
# JS files
'main.js': 'static/js/main.js',
'workers.js': 'static/js/workers.js',
'retro-refresh.js': 'static/js/retro-refresh.js',
'blocks.js': 'static/js/blocks.js',
'block-animation.js': 'static/js/block-animation.js',
'BitcoinProgressBar.js': 'static/js/BitcoinProgressBar.js',
# Template files
'base.html': 'templates/base.html',
@ -38,48 +73,372 @@ FILE_MAPPINGS = {
'workers.html': 'templates/workers.html',
'boot.html': 'templates/boot.html',
'error.html': 'templates/error.html',
'blocks.html': 'templates/blocks.html',
}
# Default configuration
DEFAULT_CONFIG = {
"power_cost": 0.0,
"power_usage": 0.0,
"wallet": "bc1py5zmrtssheq3shd8cptpl5l5m3txxr5afynyg2gyvam6w78s4dlqqnt4v9"
}
def parse_arguments():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(description='Setup the Bitcoin Mining Dashboard')
parser.add_argument('--debug', action='store_true', help='Enable debug logging')
parser.add_argument('--wallet', type=str, help='Set your Ocean.xyz wallet address')
parser.add_argument('--power-cost', type=float, help='Set your electricity cost per kWh')
parser.add_argument('--power-usage', type=float, help='Set your power consumption in watts')
parser.add_argument('--skip-checks', action='store_true', help='Skip dependency checks')
parser.add_argument('--force', action='store_true', help='Force file overwrite')
parser.add_argument('--config', type=str, help='Path to custom config.json')
return parser.parse_args()
def create_directory_structure():
"""Create the necessary directory structure."""
logger.info("Creating directory structure...")
success = True
for directory in DIRECTORIES:
os.makedirs(directory, exist_ok=True)
logging.info(f"Created directory: {directory}")
try:
os.makedirs(directory, exist_ok=True)
logger.debug(f"Created directory: {directory}")
except Exception as e:
logger.error(f"Failed to create directory {directory}: {str(e)}")
success = False
if success:
logger.info("✓ Directory structure created successfully")
else:
logger.warning("⚠ Some directories could not be created")
return success
def move_files():
"""Move files to their correct locations."""
def move_files(force=False):
"""
Move files to their correct locations.
Args:
force (bool): Force overwriting of existing files
"""
logger.info("Moving files to their correct locations...")
success = True
moved_count = 0
skipped_count = 0
missing_count = 0
for source, destination in FILE_MAPPINGS.items():
if os.path.exists(source):
# Create the directory if it doesn't exist
os.makedirs(os.path.dirname(destination), exist_ok=True)
# Copy the file to its destination
shutil.copy2(source, destination)
logging.info(f"Moved {source} to {destination}")
# Check if destination exists and handle according to force flag
if os.path.exists(destination) and not force:
logger.debug(f"Skipped {source} (destination already exists)")
skipped_count += 1
continue
try:
# Copy the file to its destination
shutil.copy2(source, destination)
logger.debug(f"Moved {source} to {destination}")
moved_count += 1
except Exception as e:
logger.error(f"Failed to copy {source} to {destination}: {str(e)}")
success = False
else:
logging.warning(f"Source file not found: {source}")
logger.warning(f"Source file not found: {source}")
missing_count += 1
if success:
logger.info(f"✓ File movement completed: {moved_count} moved, {skipped_count} skipped, {missing_count} missing")
else:
logger.warning("⚠ Some files could not be moved")
return success
def create_empty_config():
"""Create an empty config.json file if it doesn't exist."""
if not os.path.exists('config.json'):
with open('config.json', 'w') as f:
f.write('{\n "power_cost": 0.0,\n "power_usage": 0.0,\n "wallet": "bc1py5zmrtssheq3shd8cptpl5l5m3txxr5afynyg2gyvam6w78s4dlqqnt4v9"\n}')
logging.info("Created empty config.json")
def validate_wallet_address(wallet):
"""
Validate Bitcoin wallet address format.
Args:
wallet (str): Bitcoin wallet address
Returns:
bool: True if valid, False otherwise
"""
# Basic validation patterns for different Bitcoin address formats
patterns = [
r'^1[a-km-zA-HJ-NP-Z1-9]{25,34}$', # Legacy
r'^3[a-km-zA-HJ-NP-Z1-9]{25,34}$', # P2SH
r'^bc1[a-zA-Z0-9]{39,59}$', # Bech32
r'^bc1p[a-zA-Z0-9]{39,59}$', # Taproot
r'^bc1p[a-z0-9]{73,107}$' # Longform Taproot
]
# Check if the wallet matches any of the patterns
for pattern in patterns:
if re.match(pattern, wallet):
return True
return False
def create_config(args):
"""
Create or update config.json file.
Args:
args: Command line arguments
"""
config_file = args.config if args.config else 'config.json'
config = DEFAULT_CONFIG.copy()
# Load existing config if available
if os.path.exists(config_file):
try:
with open(config_file, 'r') as f:
existing_config = json.load(f)
config.update(existing_config)
logger.info(f"Loaded existing configuration from {config_file}")
except json.JSONDecodeError:
logger.warning(f"Invalid JSON in {config_file}, using default configuration")
except Exception as e:
logger.error(f"Error reading {config_file}: {str(e)}")
# Update config from command line arguments
if args.wallet:
if validate_wallet_address(args.wallet):
config["wallet"] = args.wallet
else:
logger.warning(f"Invalid wallet address format: {args.wallet}")
logger.warning("Using default or existing wallet address")
if args.power_cost is not None:
if args.power_cost >= 0:
config["power_cost"] = args.power_cost
else:
logger.warning("Power cost cannot be negative, using default or existing value")
if args.power_usage is not None:
if args.power_usage >= 0:
config["power_usage"] = args.power_usage
else:
logger.warning("Power usage cannot be negative, using default or existing value")
# Save the configuration
try:
with open(config_file, 'w') as f:
json.dump(config, f, indent=2, sort_keys=True)
logger.info(f"✓ Configuration saved to {config_file}")
except Exception as e:
logger.error(f"Failed to save configuration: {str(e)}")
return False
# Print current configuration
logger.info("Current configuration:")
logger.info(f" ├── Wallet address: {config['wallet']}")
logger.info(f" ├── Power cost: ${config['power_cost']} per kWh")
logger.info(f" └── Power usage: {config['power_usage']} watts")
return True
def check_dependencies(skip=False):
"""
Check if required Python dependencies are installed.
Args:
skip (bool): Skip the dependency check
"""
if skip:
logger.info("Skipping dependency check")
return True
logger.info("Checking dependencies...")
try:
# Check if pip is available
subprocess.run([sys.executable, "-m", "pip", "--version"],
check=True, capture_output=True, text=True)
except Exception as e:
logger.error(f"Pip is not available: {str(e)}")
logger.error("Please install pip before continuing")
return False
# Check if requirements.txt exists
if not os.path.exists('requirements.txt'):
logger.error("requirements.txt not found")
return False
# Check currently installed packages
try:
result = subprocess.run(
[sys.executable, "-m", "pip", "freeze"],
check=True, capture_output=True, text=True
)
installed_output = result.stdout
installed_packages = {
line.split('==')[0].lower(): line.split('==')[1] if '==' in line else ''
for line in installed_output.splitlines()
}
except Exception as e:
logger.error(f"Failed to check installed packages: {str(e)}")
installed_packages = {}
# Read requirements
try:
with open('requirements.txt', 'r') as f:
requirements = f.read().splitlines()
except Exception as e:
logger.error(f"Failed to read requirements.txt: {str(e)}")
return False
# Check each requirement
missing_packages = []
for req in requirements:
if req and not req.startswith('#'):
package = req.split('==')[0].lower()
if package not in installed_packages:
missing_packages.append(req)
if missing_packages:
logger.warning(f"Missing {len(missing_packages)} required packages")
logger.info("Installing missing packages...")
try:
subprocess.run(
[sys.executable, "-m", "pip", "install", "-r", "requirements.txt"],
check=True, capture_output=True, text=True
)
logger.info("✓ Dependencies installed successfully")
except Exception as e:
logger.error(f"Failed to install dependencies: {str(e)}")
logger.error("Please run: pip install -r requirements.txt")
return False
else:
logger.info("✓ All required packages are installed")
return True
def check_redis():
"""Check if Redis is available."""
logger.info("Checking Redis availability...")
redis_url = os.environ.get("REDIS_URL")
if not redis_url:
logger.info("⚠ Redis URL not configured (REDIS_URL environment variable not set)")
logger.info(" └── The dashboard will run without persistent state")
logger.info(" └── Set REDIS_URL for better reliability")
return True
try:
import redis
client = redis.Redis.from_url(redis_url)
client.ping()
logger.info(f"✓ Successfully connected to Redis at {redis_url}")
return True
except ImportError:
logger.warning("Redis Python package not installed")
logger.info(" └── Run: pip install redis")
return False
except Exception as e:
logger.warning(f"Failed to connect to Redis: {str(e)}")
logger.info(f" └── Check that Redis is running and accessible at {redis_url}")
return False
def perform_system_checks():
"""Perform system checks and provide recommendations."""
logger.info("Performing system checks...")
# Check Python version
python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
if sys.version_info.major < 3 or (sys.version_info.major == 3 and sys.version_info.minor < 9):
logger.warning(f"⚠ Python version {python_version} is below recommended (3.9+)")
else:
logger.info(f"✓ Python version {python_version} is compatible")
# Check available memory
try:
import psutil
memory = psutil.virtual_memory()
memory_gb = memory.total / (1024**3)
if memory_gb < 1:
logger.warning(f"⚠ Low system memory: {memory_gb:.2f} GB (recommended: 1+ GB)")
else:
logger.info(f"✓ System memory: {memory_gb:.2f} GB")
except ImportError:
logger.debug("psutil not available, skipping memory check")
# Check write permissions
log_dir = 'logs'
try:
test_file = os.path.join(log_dir, 'test_write.tmp')
with open(test_file, 'w') as f:
f.write('test')
os.remove(test_file)
logger.info(f"✓ Write permissions for logs directory")
except Exception as e:
logger.warning(f"⚠ Cannot write to logs directory: {str(e)}")
# Check port availability
port = 5000 # Default port
try:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('localhost', port))
s.close()
logger.info(f"✓ Port {port} is available")
except Exception:
logger.warning(f"⚠ Port {port} is already in use")
logger.info("System checks completed")
def main():
"""Main setup function."""
logging.info("Starting setup process")
args = parse_arguments()
# Set logging level
if args.debug:
logger.setLevel(logging.DEBUG)
logger.debug("Debug logging enabled")
logger.info("=== Bitcoin Mining Dashboard Setup ===")
# Check dependencies
if not check_dependencies(args.skip_checks):
logger.error("Dependency check failed. Please install required packages and retry.")
return 1
# Create directory structure
create_directory_structure()
if not create_directory_structure():
logger.error("Failed to create directory structure.")
return 1
# Move files to their correct locations
move_files()
if not move_files(args.force):
logger.warning("Some files could not be moved, but continuing...")
# Create empty config.json
create_empty_config()
# Create or update configuration
if not create_config(args):
logger.error("Failed to create configuration file.")
return 1
logging.info("Setup completed successfully")
# Check Redis if available
check_redis()
# Perform system checks
if not args.skip_checks:
perform_system_checks()
logger.info("=== Setup completed successfully ===")
logger.info("")
logger.info("Next steps:")
logger.info("1. Verify configuration in config.json")
logger.info("2. Start the application with: python App.py")
logger.info("3. Access the dashboard at: http://localhost:5000")
return 0
if __name__ == "__main__":
main()
exit_code = main()
sys.exit(exit_code)