function_exists("posix_geteuid") ? posix_geteuid() : null, "user" => get_current_user(), "temp_file" => $this->tempFile, "file_exists" => file_exists($this->tempFile), "dir_writable" => is_writable(dirname($this->tempFile)), "file_writable" => file_exists($this->tempFile) ? is_writable($this->tempFile) : null, ]); $config = $this->generate(); if (file_put_contents($this->tempFile, $config) === false) { throw new RuntimeException("Failed to write temporary DHCP configuration."); } $process = new Process(["sudo", "/usr/local/bin/dhcp-reservation", "apply"]); $process->run(); if (!$process->isSuccessful()) { throw new RuntimeException(trim($process->getErrorOutput() ?: $process->getOutput())); } // if (!$process->isSuccessful()) { // throw new RuntimeException(...); // } // HANYA disimpan kalau apply benar-benar berhasil $this->saveLastApplied(); } public function status(): array { $service = "isc-dhcp-server"; $running = false; $process = new \Symfony\Component\Process\Process(["systemctl", "is-active", $service]); $process->run(); if ($process->isSuccessful()) { $running = trim($process->getOutput()) === "active"; } return [ "running" => $running, "hostname" => $this->hostname(), "ip" => $this->serverIp(), "interface" => config("network.dhcp_interface", "eth0"), "status" => $running ? "running" : "stopped", "active_clients" => $this->activeClients(), "last_applied" => $this->lastApplied(), ]; } public function lastApplied(): ?string { if (!file_exists($this->lastAppliedFile)) { return null; } return trim(file_get_contents($this->lastAppliedFile)); } public function logs(int $limit = 30): array { $process = new Process([ "journalctl", "-u", $this->dhcpServiceName(), "-n", (string) $limit, "--no-pager", "-o", "short", ]); $process->setTimeout(5); $process->run(); if (!$process->isSuccessful()) { return []; } return collect(preg_split('/\r\n|\r|\n/', trim($process->getOutput())))->filter()->values()->all(); } private function dhcpServiceName(): string { return config("network.dhcp_service", "isc-dhcp-server"); } private function hostname(): string { return trim(gethostname() ?: php_uname("n")); } private function serverIp(): ?string { $interface = config("network.dhcp_interface", "enx00e04c6802a9"); $process = new Process(["ip", "-4", "-o", "addr", "show", "dev", $interface]); $process->run(); if (!$process->isSuccessful()) { return null; } if (preg_match("/inet\s+([0-9.]+)\//", $process->getOutput(), $matches)) { return $matches[1]; } return null; } private function generate(): string { $reservations = ConfigPosNetwork::query()->where("enabled", true)->orderBy("hostname")->get(); $config = ""; foreach ($reservations as $reservation) { $config .= <<hostname} { hardware ethernet {$reservation->mac_address}; fixed-address {$reservation->ip_address}; } CONF; } return ltrim($config); } private function activeClients(): int { $leaseFile = "/var/lib/dhcp/dhcpd.leases"; if (!file_exists($leaseFile)) { return 0; } $content = file_get_contents($leaseFile); preg_match_all("/lease\s+([0-9.]+)\s*\{(.*?)\}/s", $content, $matches, PREG_SET_ORDER); $leases = []; foreach ($matches as $match) { $ip = $match[1]; $block = $match[2]; preg_match("/binding state\s+(\w+);/", $block, $state); $leases[$ip] = [ "state" => $state[1] ?? null, ]; } return collect($leases)->where("state", "active")->count(); } private function saveLastApplied(): void { $directory = dirname($this->lastAppliedFile); if (!is_dir($directory)) { mkdir($directory, 0755, true); } file_put_contents($this->lastAppliedFile, now()->toDateTimeString()); } }