From 945ece7fb965918c375af085a271aa99c9febb8f Mon Sep 17 00:00:00 2001 From: Justin Oros Date: Mon, 26 Jan 2026 20:42:40 -0700 Subject: [PATCH] Fix SSL certificate verification on macOS by adding certifi support and proper SSL context --- miner-monero.py | 113 ++++++++++++++++++++++++++++-------------------- 1 file changed, 65 insertions(+), 48 deletions(-) diff --git a/miner-monero.py b/miner-monero.py index e315e5a..37c798f 100644 --- a/miner-monero.py +++ b/miner-monero.py @@ -19,6 +19,7 @@ import re import threading import tempfile import shutil +import ssl from pathlib import Path from typing import Dict, Optional, Tuple, List from datetime import datetime @@ -33,6 +34,7 @@ class MoneroMiner: self.stats_thread = None self.stop_stats = False self.has_nvidia_gpu = False + self.ssl_context = self.setup_ssl_context() # Known mining pools (with minimum payout amounts) self.known_pools = [ @@ -43,9 +45,24 @@ class MoneroMiner: {"name": "HeroMiners", "address": "monero.herominers.com:1111", "tls": False, "coin": "XMR", "min_payout": "0.1 XMR"}, {"name": "C3Pool", "address": "mine.c3pool.com:13333", "tls": False, "coin": None, "min_payout": "0.001 XMR", "no_api": True}, ] + + def setup_ssl_context(self): + # Setup SSL context with proper certificates for macOS + try: + import certifi + # Create SSL context with certifi certificates + context = ssl.create_default_context(cafile=certifi.where()) + return context + except ImportError: + # Fallback: use default SSL context + try: + return ssl.create_default_context() + except: + # Last resort: unverified context (not recommended but works) + return ssl._create_unverified_context() def detect_os(self) -> str: - """Detect the operating system""" + # Detect the operating system system = platform.system() if system == 'Darwin': return 'macos' @@ -57,7 +74,7 @@ class MoneroMiner: return 'unknown' def detect_cpu_arch(self) -> str: - """Detect CPU architecture""" + # Detect CPU architecture machine = platform.machine().lower() if machine in ['arm64', 'aarch64']: return 'arm64' @@ -69,7 +86,7 @@ class MoneroMiner: return machine def detect_cpu_model(self) -> str: - """Detect the specific CPU model for worker naming""" + # Detect the specific CPU model for worker naming try: system = platform.system() @@ -146,7 +163,7 @@ class MoneroMiner: return "arm64" if arch == 'arm64' else "x64" def detect_nvidia_gpu(self) -> bool: - """Detect if an NVIDIA GPU is present""" + # Detect if an NVIDIA GPU is present os_type = self.detect_os() try: @@ -185,13 +202,13 @@ class MoneroMiner: return False def get_latest_xmrig_release(self) -> Optional[Tuple[str, list]]: - """Fetch the latest XMRig version and available files from GitHub""" + # Fetch the latest XMRig version and available files from GitHub try: url = "https://api.github.com/repos/xmrig/xmrig/releases/latest" req = urllib.request.Request(url) req.add_header('User-Agent', 'Mozilla/5.0') - with urllib.request.urlopen(req, timeout=10) as response: + with urllib.request.urlopen(req, timeout=10, context=self.ssl_context) as response: data = json.loads(response.read().decode()) version = data['tag_name'].lstrip('v') @@ -204,13 +221,13 @@ class MoneroMiner: return None def get_latest_gminer_release(self) -> Optional[Tuple[str, List[dict]]]: - """Fetch the latest GMiner version and available files from GitHub""" + # Fetch the latest GMiner version and available files from GitHub try: url = "https://api.github.com/repos/develsoftware/GMinerRelease/releases/latest" req = urllib.request.Request(url) req.add_header('User-Agent', 'Mozilla/5.0') - with urllib.request.urlopen(req, timeout=10) as response: + with urllib.request.urlopen(req, timeout=10, context=self.ssl_context) as response: data = json.loads(response.read().decode()) version = data['tag_name'].lstrip('v') @@ -228,7 +245,7 @@ class MoneroMiner: return None def find_matching_file(self, assets: list, os_type: str, arch: str) -> Optional[str]: - """Find the matching file for the given OS and architecture""" + # Find the matching file for the given OS and architecture # Build search patterns based on OS and architecture if os_type == 'macos': if arch == 'arm64': @@ -263,7 +280,7 @@ class MoneroMiner: return None def find_matching_gminer_file(self, assets: List[dict], os_type: str) -> Optional[dict]: - """Find the matching GMiner file for the given OS""" + # Find the matching GMiner file for the given OS for asset in assets: name = asset['name'].lower() if os_type == 'windows' and 'windows' in name and name.endswith('.zip'): @@ -273,7 +290,7 @@ class MoneroMiner: return None def check_xmrig_installed(self) -> bool: - """Check if xmrig is installed""" + # Check if xmrig is installed # Check in script directory first local_paths = [ os.path.join(self.script_dir, 'xmrig'), @@ -310,7 +327,7 @@ class MoneroMiner: return False def check_gminer_installed(self) -> bool: - """Check if GMiner is installed in script directory""" + # Check if GMiner is installed in script directory os_type = self.detect_os() if os_type == 'windows': @@ -321,14 +338,14 @@ class MoneroMiner: return os.path.exists(gminer_path) and os.access(gminer_path, os.X_OK if os.name != 'nt' else os.F_OK) def download_file_with_progress(self, url: str, local_filename: str, max_retries: int = 3) -> bool: - """Download a file with progress indication and retry logic""" + # Download a file with progress indication and retry logic for attempt in range(max_retries): try: req = urllib.request.Request(url) req.add_header('User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36') req.add_header('Accept', '*/*') - with urllib.request.urlopen(req, timeout=30) as response: + with urllib.request.urlopen(req, timeout=30, context=self.ssl_context) as response: total_size = int(response.headers.get('Content-Length', 0)) downloaded = 0 @@ -365,7 +382,7 @@ class MoneroMiner: return False def download_and_install_xmrig(self) -> bool: - """Download and install XMRig based on OS and architecture""" + # Download and install XMRig based on OS and architecture os_type = self.detect_os() arch = self.detect_cpu_arch() @@ -551,7 +568,7 @@ class MoneroMiner: return False def download_and_install_gminer(self) -> bool: - """Download and install GMiner for GPU mining""" + # Download and install GMiner for GPU mining os_type = self.detect_os() # GMiner only supports Windows and Linux @@ -671,7 +688,7 @@ class MoneroMiner: return False def prompt_wallet_address(self) -> str: - """Prompt user for wallet address""" + # Prompt user for wallet address default_wallet = "85NGrygcwq36kjLaNKZevT53H4EeHRQYeje5Y3gq3GYkKsEpwKpGjqd1tGxAVEjDfxEha6SYtyNYPbmPXduYqy47Q6JwbuW" print("\n" + "="*60) @@ -692,7 +709,7 @@ class MoneroMiner: return wallet def prompt_pool_selection(self) -> Tuple[str, bool, Optional[str]]: - """Prompt user to select a mining pool""" + # Prompt user to select a mining pool print("\n" + "="*60) print("Mining Pool Selection") print("="*60) @@ -748,7 +765,7 @@ class MoneroMiner: print("Please enter a valid number or press ENTER for default") def check_windows_admin(self) -> bool: - """Check if script is running with Administrator privileges on Windows""" + # Check if script is running with Administrator privileges on Windows try: import ctypes return ctypes.windll.shell32.IsUserAnAdmin() != 0 @@ -756,7 +773,7 @@ class MoneroMiner: return False def prompt_msr_mod(self, os_type: str) -> bool: - """Prompt user to enable MSR MOD for performance optimization""" + # Prompt user to enable MSR MOD for performance optimization # MSR MOD is only supported on Windows and Linux if os_type not in ['windows', 'linux']: return False @@ -806,7 +823,7 @@ class MoneroMiner: return False def setup_linux_msr(self): - """Setup MSR kernel module on Linux""" + # Setup MSR kernel module on Linux try: # Check if msr module is loaded result = subprocess.run(['lsmod'], capture_output=True, text=True) @@ -841,7 +858,7 @@ class MoneroMiner: print(" 2. Start miner with: sudo python3 miner-monero.py") def prompt_gpu_mining(self) -> bool: - """Prompt user to enable GPU mining if NVIDIA GPU is detected""" + # Prompt user to enable GPU mining if NVIDIA GPU is detected print("\n" + "="*60) print("GPU Mining Configuration") print("="*60) @@ -866,7 +883,7 @@ class MoneroMiner: return False def setup_initial_config(self): - """Setup initial configuration with user input""" + # Setup initial configuration with user input os_type = self.detect_os() arch = self.detect_cpu_arch() is_mac = os_type == 'macos' @@ -930,7 +947,7 @@ class MoneroMiner: return config def load_config(self) -> Dict: - """Load configuration from JSON file""" + # Load configuration from JSON file if not os.path.exists(self.config_file): print(f"Configuration file '{self.config_file}' not found.") print("Setting up initial configuration...") @@ -949,7 +966,7 @@ class MoneroMiner: sys.exit(1) def validate_config(self, config: Dict): - """Validate required configuration fields""" + # Validate required configuration fields required_fields = ['wallet_address', 'mining_mode'] for field in required_fields: @@ -963,7 +980,7 @@ class MoneroMiner: raise ValueError("pool_address is required for pool mining") def is_supportxmr_pool(self) -> bool: - """Check if the configured pool is SupportXMR""" + # Check if the configured pool is SupportXMR if self.config['mining_mode'] != 'pool': return False @@ -971,7 +988,7 @@ class MoneroMiner: return 'supportxmr.com' in pool_addr def is_moneroocean_pool(self) -> bool: - """Check if the configured pool is MoneroOcean""" + # Check if the configured pool is MoneroOcean if self.config['mining_mode'] != 'pool': return False @@ -979,7 +996,7 @@ class MoneroMiner: return 'moneroocean' in pool_addr def is_nanopool_pool(self) -> bool: - """Check if the configured pool is NanoPool""" + # Check if the configured pool is NanoPool if self.config['mining_mode'] != 'pool': return False @@ -987,7 +1004,7 @@ class MoneroMiner: return 'nanopool' in pool_addr def is_hashvault_pool(self) -> bool: - """Check if the configured pool is HashVault""" + # Check if the configured pool is HashVault if self.config['mining_mode'] != 'pool': return False @@ -995,7 +1012,7 @@ class MoneroMiner: return 'hashvault' in pool_addr def is_herominers_pool(self) -> bool: - """Check if the configured pool is HeroMiners""" + # Check if the configured pool is HeroMiners if self.config['mining_mode'] != 'pool': return False @@ -1003,7 +1020,7 @@ class MoneroMiner: return 'herominers' in pool_addr def fetch_pool_stats(self) -> Optional[Dict]: - """Fetch mining statistics from pool API""" + # Fetch mining statistics from pool API try: wallet = self.config['wallet_address'] @@ -1023,7 +1040,7 @@ class MoneroMiner: req = urllib.request.Request(url) req.add_header('User-Agent', 'Mozilla/5.0') - with urllib.request.urlopen(req, timeout=10) as response: + with urllib.request.urlopen(req, timeout=10, context=self.ssl_context) as response: data = json.loads(response.read().decode()) # NanoPool wraps data differently @@ -1064,7 +1081,7 @@ class MoneroMiner: return None def format_hashrate(self, hash_rate: float) -> str: - """Format hash rate in human-readable format""" + # Format hash rate in human-readable format if hash_rate >= 1000000: return f"{hash_rate / 1000000:.2f} MH/s" elif hash_rate >= 1000: @@ -1073,12 +1090,12 @@ class MoneroMiner: return f"{hash_rate:.2f} H/s" def format_xmr(self, piconero: int) -> str: - """Convert piconero to XMR""" + # Convert piconero to XMR xmr = piconero / 1000000000000 return f"{xmr:.12f} XMR" def format_time_ago(self, timestamp: int) -> str: - """Format timestamp as time ago""" + # Format timestamp as time ago now = int(time.time()) diff = now - timestamp @@ -1092,7 +1109,7 @@ class MoneroMiner: return f"{diff // 86400} days ago" def display_pool_stats(self, stats: Dict): - """Display pool statistics in a readable format""" + # Display pool statistics in a readable format print("\n" + "="*60) print(f"Pool Statistics - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print("="*60) @@ -1246,7 +1263,7 @@ class MoneroMiner: print("="*60 + "\n") def stats_monitor_thread(self): - """Background thread to periodically fetch and display pool stats""" + # Background thread to periodically fetch and display pool stats interval = self.config.get('stats_update_interval', 300) # Display stats immediately @@ -1274,7 +1291,7 @@ class MoneroMiner: self.display_pool_stats(stats) def start_stats_monitor(self): - """Start the stats monitoring thread""" + # Start the stats monitoring thread if not self.is_supportxmr_pool() and not self.is_moneroocean_pool() and not self.is_nanopool_pool() and not self.is_hashvault_pool() and not self.is_herominers_pool(): return @@ -1298,13 +1315,13 @@ class MoneroMiner: self.stats_thread.start() def stop_stats_monitor(self): - """Stop the stats monitoring thread""" + # Stop the stats monitoring thread if self.stats_thread: self.stop_stats = True self.stats_thread.join(timeout=2) def initialize(self): - """Initialize the miner - check for xmrig and setup config""" + # Initialize the miner - check for xmrig and setup config # Check for NVIDIA GPU first print("\nChecking for NVIDIA GPU...") self.has_nvidia_gpu = self.detect_nvidia_gpu() @@ -1382,7 +1399,7 @@ class MoneroMiner: self.config['gpu_mining'] = False def build_command(self) -> list: - """Build the XMRig mining command based on configuration""" + # Build the XMRig mining command based on configuration cmd = [self.config.get('miner_executable', 'xmrig')] # Add wallet address @@ -1468,7 +1485,7 @@ class MoneroMiner: return cmd def build_gminer_command(self) -> list: - """Build the GMiner command for GPU mining""" + # Build the GMiner command for GPU mining os_type = self.detect_os() if os_type == 'windows': @@ -1493,7 +1510,7 @@ class MoneroMiner: return cmd def start_gpu_mining(self): - """Start the GPU mining process with GMiner""" + # Start the GPU mining process with GMiner if not self.config.get('gpu_mining', False): return @@ -1538,7 +1555,7 @@ class MoneroMiner: print(f"Error starting GPU miner: {e}") def stop_gpu_mining(self): - """Stop the GPU mining process""" + # Stop the GPU mining process if self.gpu_process: print("Stopping GPU miner...") self.gpu_process.terminate() @@ -1550,7 +1567,7 @@ class MoneroMiner: print("GPU miner stopped.") def start_mining(self): - """Start the mining process""" + # Start the mining process cmd = self.build_command() # Check if MSR is enabled but not running with proper privileges @@ -1646,7 +1663,7 @@ class MoneroMiner: self.stop_gpu_mining() def stop_mining(self): - """Stop the mining process""" + # Stop the mining process self.stop_stats_monitor() self.stop_gpu_mining() @@ -1661,14 +1678,14 @@ class MoneroMiner: print("CPU miner stopped.") def show_stats(self): - """Display mining statistics if API is enabled""" + # Display mining statistics if API is enabled api_port = self.config.get('api_port', 0) if api_port > 0: print(f"\nMining stats available at: http://127.0.0.1:{api_port}") print("You can view real-time statistics in your browser") def signal_handler(signum, frame): - """Handle interrupt signals""" + # Handle interrupt signals print("\n\nShutdown signal received...") sys.exit(0)