Files
Python/miner-monero.py
T

567 lines
21 KiB
Python

#!/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
import urllib.request
import tarfile
import re
from pathlib import Path
from typing import Dict, Optional, Tuple
class MoneroMiner:
def __init__(self, config_file: str = "miner-monero.json"):
self.config_file = config_file
self.script_dir = os.path.dirname(os.path.abspath(__file__))
self.config = None
self.process: Optional[subprocess.Popen] = None
def detect_os(self) -> str:
"""Detect the operating system"""
system = platform.system()
if system == 'Darwin':
return 'macos'
elif system == 'Linux':
return 'linux'
elif system == 'Windows':
return 'windows'
else:
return 'unknown'
def detect_cpu_arch(self) -> str:
"""Detect CPU architecture"""
machine = platform.machine().lower()
if machine in ['arm64', 'aarch64']:
return 'arm64'
elif machine in ['x86_64', 'amd64', 'x64']:
return 'x64'
else:
return machine
def get_latest_xmrig_release(self) -> Optional[Tuple[str, list]]:
"""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:
data = json.loads(response.read().decode())
version = data['tag_name'].lstrip('v')
# Extract list of asset filenames
assets = [asset['name'] for asset in data.get('assets', [])]
return version, assets
except Exception as e:
print(f"Error fetching latest release info: {e}")
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"""
# Build search patterns based on OS and architecture
if os_type == 'macos':
if arch == 'arm64':
patterns = ['macos-arm64.tar.gz', 'macos-aarch64.tar.gz']
elif arch == 'x64':
patterns = ['macos-x64.tar.gz', 'macos-x86_64.tar.gz']
else:
return None
elif os_type == 'linux':
if arch == 'arm64':
patterns = ['linux-static-arm64.tar.gz', 'linux-arm64.tar.gz', 'linux-aarch64.tar.gz']
elif arch == 'x64':
patterns = ['linux-static-x64.tar.gz', 'linux-x64.tar.gz', 'linux-x86_64.tar.gz']
else:
return None
else:
return None
# Search for matching file
for asset in assets:
for pattern in patterns:
if pattern in asset.lower():
return asset
return None
def check_xmrig_installed(self) -> bool:
"""Check if xmrig is installed"""
# Check in script directory first
local_paths = [
os.path.join(self.script_dir, 'xmrig'),
os.path.join(self.script_dir, 'xmrig.exe'),
]
for path in local_paths:
if os.path.exists(path) and os.access(path, os.X_OK):
return True
# Check in system PATH
try:
result = subprocess.run(['which', 'xmrig'],
capture_output=True,
text=True,
timeout=5)
if result.returncode == 0:
return True
except:
pass
return False
def download_and_install_xmrig(self) -> bool:
"""Download and install XMRig based on OS and architecture"""
os_type = self.detect_os()
arch = self.detect_cpu_arch()
print(f"\nDetected OS: {os_type}")
print(f"Detected Architecture: {arch}")
if os_type not in ['macos', 'linux']:
print(f"\nAutomatic installation is currently only supported for macOS and Linux.")
print(f"Please install XMRig manually from: https://github.com/xmrig/xmrig/releases")
return False
# Get latest release info
print("\nFetching latest XMRig release from GitHub...")
release_info = self.get_latest_xmrig_release()
if not release_info:
print("Could not fetch release information from GitHub.")
print("Please install XMRig manually from: https://github.com/xmrig/xmrig/releases")
return False
version, assets = release_info
print(f"Latest version: {version}")
print(f"Found {len(assets)} release files")
# Find matching file for this OS and architecture
filename = self.find_matching_file(assets, os_type, arch)
if not filename:
print(f"\nCould not find a compatible XMRig release for {os_type} {arch}")
print(f"Available files:")
for asset in assets:
if asset.endswith('.tar.gz'):
print(f" - {asset}")
print("\nPlease download manually from: https://github.com/xmrig/xmrig/releases")
return False
print(f"Found matching file: {filename}")
download_url = f"https://github.com/xmrig/xmrig/releases/download/v{version}/{filename}"
print(f"\nReady to download: {filename}")
print(f"URL: {download_url}")
# Prompt user
while True:
response = input("\nDownload and install XMRig? (Y/N): ").strip().upper()
if response in ['Y', 'N']:
break
print("Please enter Y or N")
if response == 'N':
print("Installation cancelled.")
return False
# Download with retry logic
print(f"\nDownloading {filename}...")
local_filename = os.path.join(self.script_dir, filename)
max_retries = 3
for attempt in range(max_retries):
try:
req = urllib.request.Request(download_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:
total_size = int(response.headers.get('Content-Length', 0))
downloaded = 0
with open(local_filename, 'wb') as f:
while True:
chunk = response.read(8192)
if not chunk:
break
f.write(chunk)
downloaded += len(chunk)
if total_size > 0:
percent = (downloaded / total_size) * 100
print(f"\rProgress: {percent:.1f}% ({downloaded}/{total_size} bytes)", end='', flush=True)
print("\nDownload complete!")
break
except urllib.error.HTTPError as e:
print(f"\nHTTP Error {e.code}: {e.reason}")
if attempt < max_retries - 1:
print(f"Retrying... (Attempt {attempt + 2}/{max_retries})")
time.sleep(2)
else:
print(f"\nFailed to download after {max_retries} attempts.")
print(f"Please download manually from: {download_url}")
return False
except Exception as e:
print(f"\nError downloading file: {e}")
if attempt < max_retries - 1:
print(f"Retrying... (Attempt {attempt + 2}/{max_retries})")
time.sleep(2)
else:
print(f"\nFailed to download after {max_retries} attempts.")
print(f"Please download manually from: {download_url}")
return False
# Extract
print("Extracting archive...")
try:
with tarfile.open(local_filename, 'r:gz') as tar:
# Find the xmrig binary in the archive
for member in tar.getmembers():
if member.name.endswith('/xmrig') or member.name == 'xmrig':
member.name = 'xmrig'
tar.extract(member, self.script_dir)
break
xmrig_path = os.path.join(self.script_dir, 'xmrig')
# Make executable
os.chmod(xmrig_path, 0o755)
print(f"XMRig installed successfully at: {xmrig_path}")
# Clean up
os.remove(local_filename)
print("Cleaned up temporary files.")
return True
except Exception as e:
print(f"Error extracting archive: {e}")
return False
def prompt_wallet_address(self) -> str:
"""Prompt user for wallet address"""
default_wallet = "85NGrygcwq36kjLaNKZevT53H4EeHRQYeje5Y3gq3GYkKsEpwKpGjqd1tGxAVEjDfxEha6SYtyNYPbmPXduYqy47Q6JwbuW"
print("\n" + "="*60)
print("Monero Wallet Configuration")
print("="*60)
print(f"\nDefault wallet address:")
print(f"{default_wallet}")
print("\nPress ENTER to use the default wallet, or paste your own:")
wallet = input("Wallet Address: ").strip()
if not wallet:
wallet = default_wallet
print("Using default wallet address.")
else:
print("Using your wallet address.")
return wallet
def setup_initial_config(self):
"""Setup initial configuration with user input"""
os_type = self.detect_os()
arch = self.detect_cpu_arch()
is_mac = os_type == 'macos'
is_arm = arch == 'arm64'
# Get wallet address from user
wallet_address = self.prompt_wallet_address()
# Determine xmrig path - use ./xmrig for both macOS and Linux
if os_type in ['macos', 'linux']:
miner_executable = "./xmrig"
else:
miner_executable = "xmrig.exe" # Windows
config = {
"wallet_address": wallet_address,
"mining_mode": "pool",
"miner_executable": miner_executable,
"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(config, f, indent=4)
print(f"\nConfiguration saved to: {self.config_file}")
return config
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("Setting up initial configuration...")
return None
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 initialize(self):
"""Initialize the miner - check for xmrig and setup config"""
# Check if xmrig is installed
xmrig_was_just_installed = False
if not self.check_xmrig_installed():
print("\n" + "="*60)
print("XMRig Not Detected")
print("="*60)
print("\nXMRig mining software is not installed.")
# Try to install
if not self.download_and_install_xmrig():
print("\nPlease install XMRig manually and try again.")
sys.exit(1)
xmrig_was_just_installed = True
else:
print("\nXMRig detected ✓")
# Load or create config
self.config = self.load_config()
# If config doesn't exist OR xmrig was just installed, setup new config with wallet prompt
if self.config is None or xmrig_was_just_installed:
self.config = self.setup_initial_config()
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']
# 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("\nPlease ensure XMRig is properly installed.")
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 miner instance
miner = MoneroMiner(config_file)
try:
# Initialize (check xmrig, setup config)
miner.initialize()
# Show stats info and start mining
miner.show_stats()
miner.start_mining()
except KeyboardInterrupt:
print("\n\nInterrupted by user")
miner.stop_mining()
except Exception as e:
print(f"\nError: {e}")
if miner.process:
miner.stop_mining()
sys.exit(1)
if __name__ == "__main__":
main()