Files
pand03-CEOandClaude Sonnet 5 2a9b28581c Rapihin setup/member/lot-parking + fitur intercom, exclude WIP controller & file sampah dari git
Kumpulan perubahan yang udah numpuk di working tree: refactor Setup
(Gate/Vehicle/Member), rapihin memberin & stiker, service parking baru
(ParkingUtilsServices, NetworkService), model/migration lot-parking &
DHCP reservation, dan fitur baru Intercom (controller/model/migration/
view).

ReportTransaksiController versi lokal ini WIP (~90%, masih didebug) -
versi yang bener ada di server, jadi dikeluarin dari git (ditambahin
ke .gitignore) biar gak numpuk nindihin punya server pas git pull di
sana. Sekalian keluarin dump mentah tabel softseting (1.8MB, nyasar
ke-commit), file .bak, dan arsip ad-hoc (api.tgz, c.zip) yang emang
bukan source code.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EAJc2mrpj2cSTe4oowLPzQ
2026-10-04 07:15:54 +07:00

196 lines
5.0 KiB
PHP

<?php
namespace App\Services\Parking;
use App\Models\ConfigPosNetwork;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process;
use RuntimeException;
class NetworkService
{
private string $tempFile = "/var/lib/dhcp-panel/reservations.conf";
private string $lastAppliedFile = "/var/lib/dhcp-panel/last-applied";
public function apply(): void
{
Log::info("DHCP APPLY DEBUG", [
"uid" => 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 .= <<<CONF
host {$reservation->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());
}
}