diff --git a/miner-kaspa.py b/miner-kaspa.py new file mode 100644 index 0000000..cb127b5 --- /dev/null +++ b/miner-kaspa.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +# Description: Kaspa Mining Script - Supports both solo mining and pool mining with JSON configuration +# Usage: python3 miner-kaspa.py +# Author: Justin Oros +# Source: https://github.com/JustinOros + +import json +import subprocess +import sys +import os +import time +import signal +from pathlib import Path +from typing import Dict, Optional + +class KaspaMiner: + def __init__(self, config_file: str = "miner-kaspa.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"Error: 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""" + example_config = { + "wallet_address": "kaspa:qz1234567890abcdefghijklmnopqrstuvwxyz", + "mining_mode": "solo", + "miner_executable": "kaspa-miner", + "kaspad_address": "127.0.0.1:16110", + "pool_address": "pool.example.com:5555", + "worker_name": "worker1", + "threads": 0, + "gpu_enable": True, + "gpu_devices": [0], + "intensity": 20, + "extra_args": [], + "log_file": "kaspa-miner.log" + } + + 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("Please edit it with your mining details.") + + def build_command(self) -> list: + """Build the mining command based on configuration""" + cmd = [self.config.get('miner_executable', 'kaspa-miner')] + + # Add wallet address + cmd.extend(['--mining-address', self.config['wallet_address']]) + + # Configure based on mining mode + if self.config['mining_mode'] == 'solo': + # Solo mining - connect to local kaspad + kaspad_addr = self.config.get('kaspad_address', '127.0.0.1:16110') + cmd.extend(['--kaspad-address', kaspad_addr]) + print(f"Solo mining mode - connecting to kaspad at {kaspad_addr}") + else: + # Pool mining + pool_addr = self.config['pool_address'] + cmd.extend(['--pool', pool_addr]) + + # Add worker name if specified + worker = self.config.get('worker_name') + if worker: + cmd.extend(['--worker-name', worker]) + + print(f"Pool mining mode - connecting to pool at {pool_addr}") + + # Thread configuration + threads = self.config.get('threads', 0) + if threads > 0: + cmd.extend(['--threads', str(threads)]) + + # GPU configuration + if self.config.get('gpu_enable', True): + gpu_devices = self.config.get('gpu_devices', [0]) + if gpu_devices: + cmd.extend(['--cuda-devices', ','.join(map(str, gpu_devices))]) + + intensity = self.config.get('intensity') + if intensity: + cmd.extend(['--intensity', str(intensity)]) + else: + cmd.append('--no-cuda') + + # 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("Kaspa Miner Starting") + print("="*60) + print(f"Wallet: {self.config['wallet_address']}") + print(f"Mode: {self.config['mining_mode'].upper()}") + 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"Error: Miner executable '{cmd[0]}' not found.") + print("Please ensure the miner is installed and the path is correct.") + 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 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-kaspa.json" + if len(sys.argv) > 1: + config_file = sys.argv[1] + + # Create and start miner + miner = KaspaMiner(config_file) + + try: + 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()