From 3783267593552ff521471ccdfcd907e286bdabd0 Mon Sep 17 00:00:00 2001 From: JustinOros Date: Mon, 10 Nov 2025 09:55:01 -0700 Subject: [PATCH] Monero (XMR) Mining Script --- miner-monero.py | 294 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 miner-monero.py diff --git a/miner-monero.py b/miner-monero.py new file mode 100644 index 0000000..f5227b3 --- /dev/null +++ b/miner-monero.py @@ -0,0 +1,294 @@ +#!/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()