#!/usr/bin/env python3 # Description: Monero (XMR) Mining Script - Optimized for CPU mining with support for solo and pool mining # Usage: python3 miner-monero.py # Author: Justin Oros # Source: https://github.com/JustinOros import json import subprocess import sys import os import time import signal import platform from pathlib import Path from typing import Dict, Optional class MoneroMiner: def __init__(self, config_file: str = "miner-monero.json"): self.config_file = config_file self.config = self.load_config() self.process: Optional[subprocess.Popen] = None def load_config(self) -> Dict: """Load configuration from JSON file""" if not os.path.exists(self.config_file): print(f"Configuration file '{self.config_file}' not found.") print("Creating example configuration file...") self.create_example_config() sys.exit(1) try: with open(self.config_file, 'r') as f: config = json.load(f) self.validate_config(config) return config except json.JSONDecodeError as e: print(f"Error: Invalid JSON in configuration file: {e}") sys.exit(1) except Exception as e: print(f"Error loading configuration: {e}") sys.exit(1) def validate_config(self, config: Dict): """Validate required configuration fields""" required_fields = ['wallet_address', 'mining_mode'] for field in required_fields: if field not in config: raise ValueError(f"Missing required field: {field}") if config['mining_mode'] not in ['solo', 'pool']: raise ValueError("mining_mode must be 'solo' or 'pool'") if config['mining_mode'] == 'pool' and 'pool_address' not in config: raise ValueError("pool_address is required for pool mining") def create_example_config(self): """Create an example configuration file with platform-specific defaults""" is_mac = platform.system() == 'Darwin' is_arm = platform.machine().lower() in ['arm64', 'aarch64'] example_config = { "wallet_address": "YOUR_MONERO_WALLET_ADDRESS_HERE", "mining_mode": "pool", "miner_executable": "xmrig" if not is_mac else "./xmrig", "pool_address": "pool.supportxmr.com:443", "pool_password": "x", "worker_name": f"m2-miner" if is_arm else "worker1", "node_address": "127.0.0.1:18081", "threads": 0, "cpu_max_usage": 75, "huge_pages": True if not is_mac else False, "randomx_mode": "auto", "tls_enabled": True, "nicehash": False, "extra_args": [], "log_file": "monero-miner.log", "api_port": 0, "donate_level": 1 } with open(self.config_file, 'w') as f: json.dump(example_config, f, indent=4) print(f"Created example configuration file: {self.config_file}") print("\n" + "="*60) print("IMPORTANT: Edit the configuration file and set your wallet!") print("="*60) if is_mac: print("\nFor Mac users:") print("1. Download XMRig from: https://github.com/xmrig/xmrig/releases") print("2. Extract and place 'xmrig' binary in the same folder as this script") print("3. Run: chmod +x xmrig") print("\nPopular Monero pools:") print(" - SupportXMR: pool.supportxmr.com:443") print(" - MoneroOcean: gulf.moneroocean.stream:10128") print(" - Nanopool: xmr-us-east1.nanopool.org:14433") print(" - MineXMR: pool.minexmr.com:443") def build_command(self) -> list: """Build the XMRig mining command based on configuration""" cmd = [self.config.get('miner_executable', 'xmrig')] # Add wallet address wallet = self.config['wallet_address'] if wallet == "YOUR_MONERO_WALLET_ADDRESS_HERE": print("Error: Please set your Monero wallet address in the config file!") sys.exit(1) # Configure based on mining mode if self.config['mining_mode'] == 'solo': # Solo mining - connect to local node node_addr = self.config.get('node_address', '127.0.0.1:18081') cmd.extend(['--daemon', '--daemon-poll-interval=1000']) cmd.extend(['--url', node_addr]) cmd.extend(['--user', wallet]) print(f"Solo mining mode - connecting to node at {node_addr}") else: # Pool mining pool_addr = self.config['pool_address'] cmd.extend(['--url', pool_addr]) cmd.extend(['--user', wallet]) # Add worker name if specified worker = self.config.get('worker_name') if worker: cmd.extend(['--rig-id', worker]) # Pool password pool_pass = self.config.get('pool_password', 'x') cmd.extend(['--pass', pool_pass]) # TLS if self.config.get('tls_enabled', True): cmd.append('--tls') # NiceHash mode if self.config.get('nicehash', False): cmd.append('--nicehash') print(f"Pool mining mode - connecting to pool at {pool_addr}") # CPU Thread configuration threads = self.config.get('threads', 0) if threads > 0: cmd.extend(['--threads', str(threads)]) # CPU max usage cpu_max = self.config.get('cpu_max_usage') if cpu_max: cmd.extend(['--cpu-max-threads-hint', str(cpu_max)]) # Huge pages (not supported on macOS) if self.config.get('huge_pages', False) and platform.system() != 'Darwin': cmd.append('--hugepages') # RandomX mode rx_mode = self.config.get('randomx_mode', 'auto') if rx_mode in ['light', 'fast']: cmd.extend(['--randomx-mode', rx_mode]) # API port for monitoring api_port = self.config.get('api_port', 0) if api_port > 0: cmd.extend(['--http-port', str(api_port)]) cmd.append('--http-enabled') # Donate level donate = self.config.get('donate_level', 1) cmd.extend(['--donate-level', str(donate)]) # Add any extra arguments extra_args = self.config.get('extra_args', []) if extra_args: cmd.extend(extra_args) return cmd def start_mining(self): """Start the mining process""" cmd = self.build_command() print("\n" + "="*60) print("Monero Miner Starting") print("="*60) print(f"Wallet: {self.config['wallet_address'][:20]}...{self.config['wallet_address'][-10:]}") print(f"Mode: {self.config['mining_mode'].upper()}") print(f"Platform: {platform.system()} ({platform.machine()})") print(f"Command: {' '.join(cmd)}") print("="*60 + "\n") # Setup logging if specified log_file = self.config.get('log_file') log_handle = None try: if log_file: log_handle = open(log_file, 'a') log_handle.write(f"\n{'='*60}\n") log_handle.write(f"Mining started at {time.strftime('%Y-%m-%d %H:%M:%S')}\n") log_handle.write(f"Command: {' '.join(cmd)}\n") log_handle.write(f"{'='*60}\n\n") log_handle.flush() # Start the miner process self.process = subprocess.Popen( cmd, stdout=subprocess.PIPE if log_handle else None, stderr=subprocess.STDOUT if log_handle else None, universal_newlines=True, bufsize=1 ) # Monitor output if logging if log_handle and self.process.stdout: try: for line in self.process.stdout: print(line, end='') log_handle.write(line) log_handle.flush() except KeyboardInterrupt: print("\n\nReceived interrupt signal. Stopping miner...") self.stop_mining() else: # Wait for process to complete self.process.wait() except FileNotFoundError: print(f"\nError: Miner executable '{cmd[0]}' not found.") print("\nFor macOS/Apple Silicon:") print("1. Download XMRig from: https://github.com/xmrig/xmrig/releases") print("2. Look for 'xmrig-X.X.X-macos-arm64.tar.gz' for M2 Mac") print("3. Extract and place 'xmrig' in the same folder as this script") print("4. Run: chmod +x xmrig") print("\nFor Linux:") print(" sudo apt install xmrig # or download from GitHub") sys.exit(1) except Exception as e: print(f"Error starting miner: {e}") sys.exit(1) finally: if log_handle: log_handle.close() def stop_mining(self): """Stop the mining process""" if self.process: print("Stopping miner...") self.process.terminate() try: self.process.wait(timeout=10) except subprocess.TimeoutExpired: print("Force killing miner process...") self.process.kill() print("Miner stopped.") def show_stats(self): """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""" print("\n\nShutdown signal received...") sys.exit(0) def main(): # Register signal handlers signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) # Parse command line arguments config_file = "miner-monero.json" if len(sys.argv) > 1: config_file = sys.argv[1] # Create and start miner miner = MoneroMiner(config_file) try: miner.show_stats() miner.start_mining() except KeyboardInterrupt: print("\n\nInterrupted by user") miner.stop_mining() except Exception as e: print(f"\nError: {e}") miner.stop_mining() sys.exit(1) if __name__ == "__main__": main()