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
196 lines
5.0 KiB
PHP
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());
|
|
}
|
|
}
|