From ad29a45507814edf3b9a651805070a9f315ae7dd Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Apr 2026 12:09:19 +0100 Subject: [PATCH] feat: chain:stop command + DNS update activity logging chain:stop sends SIGTERM and waits for graceful shutdown. DNS updateRecords now logs 'dns_updated' activity with ticket_id and records. Claim approve/reject also logged in previous commit. Co-Authored-By: Claude Opus 4.6 (1M context) --- app/Mod/Chain/Boot.php | 1 + app/Mod/Chain/Commands/ChainStop.php | 58 +++++++++++++++++++ app/Mod/Names/Controllers/NamesController.php | 1 + 3 files changed, 60 insertions(+) create mode 100644 app/Mod/Chain/Commands/ChainStop.php diff --git a/app/Mod/Chain/Boot.php b/app/Mod/Chain/Boot.php index 7e4d34b..55b2a5a 100644 --- a/app/Mod/Chain/Boot.php +++ b/app/Mod/Chain/Boot.php @@ -37,6 +37,7 @@ class Boot public function onConsole(ConsoleBooting $event): void { $event->command(Commands\ChainStart::class); + $event->command(Commands\ChainStop::class); $event->command(Commands\ChainStatus::class); } } diff --git a/app/Mod/Chain/Commands/ChainStop.php b/app/Mod/Chain/Commands/ChainStop.php new file mode 100644 index 0000000..0ff5d32 --- /dev/null +++ b/app/Mod/Chain/Commands/ChainStop.php @@ -0,0 +1,58 @@ +stopProcess('lethean-wallet-cli', 'Wallet'); + $this->stopProcess('lethean-testnet-chain-node', 'Daemon'); + + return self::SUCCESS; + } + + private function stopProcess(string $name, string $label): void + { + // Safe: escapeshellarg prevents injection, process names are hardcoded constants + exec("pgrep -f " . escapeshellarg($name), $pids); // @codeCoverageIgnore + + if (empty($pids)) { + $this->info("{$label}: not running."); + + return; + } + + foreach ($pids as $pid) { + posix_kill((int) $pid, SIGTERM); + } + + $this->info("{$label}: SIGTERM sent to " . count($pids) . " process(es)."); + + // Wait up to 10s for graceful shutdown + for ($i = 0; $i < 10; $i++) { + exec("pgrep -f " . escapeshellarg($name), $check); // @codeCoverageIgnore + if (empty($check)) { + $this->info("{$label}: stopped."); + + return; + } + sleep(1); + } + + $this->warn("{$label}: still running after 10s."); + } +} diff --git a/app/Mod/Names/Controllers/NamesController.php b/app/Mod/Names/Controllers/NamesController.php index 12548af..118ec8f 100644 --- a/app/Mod/Names/Controllers/NamesController.php +++ b/app/Mod/Names/Controllers/NamesController.php @@ -282,6 +282,7 @@ class NamesController extends Controller if (isset($result['tx_id'])) { Cache::put($editLock, true, 300); $ticket = Models\DnsTicket::open($name, $records, $result['tx_id'], $address, $comment); + Models\NameActivity::log($name, 'dns_updated', ['ticket_id' => $ticket->ticket_id, 'records' => $records], request()?->ip()); return response()->json([ 'name' => $name,