option('wallet-only')) { $this->startDaemon($binDir, $dataDir, $miningAddress, $miningThreads); } if (! $this->option('daemon-only')) { $this->waitForDaemon($config['daemon_rpc'] ?? 'http://127.0.0.1:46941/json_rpc'); $this->startWallet($binDir, $walletDir, $walletFile); } return self::SUCCESS; } private function startDaemon(string $binDir, string $dataDir, string $miningAddress, int $threads): void { if ($this->isRunning('lethean-testnet-chain-node')) { $this->info('Daemon already running.'); return; } @unlink("{$dataDir}/lock.lck"); $binary = "{$binDir}/lethean-testnet-chain-node"; if (! is_executable($binary)) { $this->error("Daemon binary not found: {$binary}"); return; } $args = [ $binary, "--data-dir {$dataDir}", '--rpc-bind-ip 0.0.0.0 --rpc-bind-port 46941', '--p2p-bind-port 46942 --api-bind-port 46943', '--rpc-enable-admin-api --rpc-ignore-offline', '--allow-local-ip --log-level 1 --disable-upnp', ]; if ($miningAddress) { $args[] = "--start-mining {$miningAddress} --mining-threads {$threads}"; } $cmd = implode(' ', $args); $logFile = "{$dataDir}/lethean-testnet-chain-node.log"; // Safe: all values from config, no user input exec("nohup {$cmd} > {$logFile} 2>&1 &"); // @codeCoverageIgnore $this->info("Daemon started. Mining: {$threads} threads."); } private function startWallet(string $binDir, string $walletDir, string $walletFile): void { if ($this->isRunning('lethean-wallet-cli')) { $this->info('Wallet already running.'); return; } $binary = "{$binDir}/lethean-testnet-wallet-cli"; if (! is_executable($binary)) { $binary = '/opt/lethean/bin/lethean-wallet-cli'; } if (! is_executable($binary)) { $this->error('Wallet binary not found.'); return; } $walletPath = "{$walletDir}/{$walletFile}"; if (! file_exists($walletPath)) { $this->error("Wallet file not found: {$walletPath}"); return; } $cmd = implode(' ', [ $binary, "--wallet-file {$walletPath}", '--password ""', '--daemon-address 127.0.0.1:46941', '--rpc-bind-port 46944 --rpc-bind-ip 127.0.0.1', ]); // Safe: all values from config, no user input exec("nohup {$cmd} > {$walletDir}/wallet-rpc.log 2>&1 &"); // @codeCoverageIgnore $this->info('Wallet RPC started on port 46944.'); } private function waitForDaemon(string $rpcUrl, int $timeout = 30): void { $this->info('Waiting for daemon RPC...'); $endpoint = str_replace('/json_rpc', '', $rpcUrl) . '/json_rpc'; $start = time(); while (time() - $start < $timeout) { try { $response = @file_get_contents($endpoint, false, stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => "Content-Type: application/json\r\n", 'content' => '{"jsonrpc":"2.0","id":"0","method":"getinfo"}', 'timeout' => 3, ], ])); if ($response) { $this->info('Daemon ready.'); return; } } catch (\Throwable $e) { // Not ready yet } sleep(2); } $this->warn("Daemon not responding after {$timeout}s."); } private function isRunning(string $processName): bool { exec("pgrep -f " . escapeshellarg($processName), $output); return ! empty($output); } }