From b51139f65fd1f154ce580c78ae33971729e7e450 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 28 Jan 2026 19:51:00 +0200 Subject: [PATCH] Add MaxMind geoipupdate and upload fallback --- README.md | 2 +- VERSION | 2 +- .../GeoBlockRules/Pages/ListGeoBlockRules.php | 60 +++++++ app/Services/Agent/AgentClient.php | 8 + bin/jabali-agent | 148 +++++++++++++++++- install.sh | 71 ++++++++- 6 files changed, 286 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 107b305..52f7213 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A modern web hosting control panel for WordPress and general PHP hosting. Built with Laravel 12, Filament v5, Livewire 4, and Tailwind CSS v4. -Version: 0.9-rc20 (release candidate) +Version: 0.9-rc21 (release candidate) This is a release candidate. Expect rapid iteration and breaking changes until 1.0. diff --git a/VERSION b/VERSION index 8ba4fa6..3dec98f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -VERSION=0.9-rc20 +VERSION=0.9-rc21 diff --git a/app/Filament/Admin/Resources/GeoBlockRules/Pages/ListGeoBlockRules.php b/app/Filament/Admin/Resources/GeoBlockRules/Pages/ListGeoBlockRules.php index 57600fe..faaf943 100644 --- a/app/Filament/Admin/Resources/GeoBlockRules/Pages/ListGeoBlockRules.php +++ b/app/Filament/Admin/Resources/GeoBlockRules/Pages/ListGeoBlockRules.php @@ -10,9 +10,11 @@ use App\Services\Agent\AgentClient; use Exception; use Filament\Actions\Action; use Filament\Actions\CreateAction; +use Filament\Forms\Components\FileUpload; use Filament\Forms\Components\TextInput; use Filament\Notifications\Notification; use Filament\Resources\Pages\ListRecords; +use Illuminate\Support\Facades\Storage; class ListGeoBlockRules extends ListRecords { @@ -75,6 +77,64 @@ class ListGeoBlockRules extends ListRecords ->send(); } }), + Action::make('uploadGeoIpDatabase') + ->label(__('Upload GeoIP Database')) + ->icon('heroicon-o-arrow-up-tray') + ->form([ + FileUpload::make('mmdb_file') + ->label(__('GeoIP .mmdb File')) + ->required() + ->acceptedFileTypes(['application/octet-stream', 'application/x-maxmind-db']) + ->helperText(__('Upload a MaxMind GeoIP .mmdb file (GeoLite2 or GeoIP2).')), + TextInput::make('edition') + ->label(__('Edition ID')) + ->helperText(__('Example: GeoLite2-Country')) + ->default(fn (): string => (string) (DnsSetting::get('geoip_edition_ids') ?? 'GeoLite2-Country')), + ]) + ->action(function (array $data): void { + if (empty($data['mmdb_file'])) { + Notification::make() + ->title(__('No file uploaded')) + ->danger() + ->send(); + + return; + } + + $edition = trim((string) ($data['edition'] ?? 'GeoLite2-Country')); + if ($edition === '') { + $edition = 'GeoLite2-Country'; + } + + $filePath = Storage::disk('local')->path($data['mmdb_file']); + if (! file_exists($filePath)) { + Notification::make() + ->title(__('Uploaded file not found')) + ->danger() + ->send(); + + return; + } + + $content = base64_encode((string) file_get_contents($filePath)); + + try { + $agent = new AgentClient; + $result = $agent->geoUploadDatabase($edition, $content); + + Notification::make() + ->title(__('GeoIP database uploaded')) + ->body($result['path'] ?? null) + ->success() + ->send(); + } catch (Exception $e) { + Notification::make() + ->title(__('GeoIP upload failed')) + ->body($e->getMessage()) + ->danger() + ->send(); + } + }), CreateAction::make(), ]; } diff --git a/app/Services/Agent/AgentClient.php b/app/Services/Agent/AgentClient.php index dbf0b83..132b0bc 100644 --- a/app/Services/Agent/AgentClient.php +++ b/app/Services/Agent/AgentClient.php @@ -1359,6 +1359,14 @@ class AgentClient ]); } + public function geoUploadDatabase(string $edition, string $content): array + { + return $this->send('geo.upload_database', [ + 'edition' => $edition, + 'content' => $content, + ]); + } + public function databasePersistTuning(string $name, string $value): array { return $this->send('database.persist_tuning', [ diff --git a/bin/jabali-agent b/bin/jabali-agent index 700f425..dc87341 100755 --- a/bin/jabali-agent +++ b/bin/jabali-agent @@ -548,6 +548,7 @@ function handleAction(array $request): array 'waf.apply' => wafApplySettings($params), 'geo.apply_rules' => geoApplyRules($params), 'geo.update_database' => geoUpdateDatabase($params), + 'geo.upload_database' => geoUploadDatabase($params), 'database.persist_tuning' => databasePersistTuning($params), 'database.get_variables' => databaseGetVariables($params), 'database.set_global' => databaseSetGlobal($params), @@ -2926,8 +2927,9 @@ function geoUpdateDatabase(array $params): array $editionIdsRaw = $params['edition_ids'] ?? 'GeoLite2-Country'; $useExisting = !empty($params['use_existing']); - if (!toolExists('geoipupdate')) { - return ['success' => false, 'error' => 'geoipupdate is not installed']; + $toolError = ensureGeoIpUpdateTool(); + if ($toolError !== null) { + return ['success' => false, 'error' => $toolError]; } if (!$useExisting && ($accountId === '' || $licenseKey === '')) { @@ -3002,6 +3004,148 @@ function geoUpdateDatabase(array $params): array return ['success' => false, 'error' => 'GeoIP database not found after update']; } +function geoUploadDatabase(array $params): array +{ + $edition = trim((string) ($params['edition'] ?? 'GeoLite2-Country')); + $content = (string) ($params['content'] ?? ''); + + if ($content === '') { + return ['success' => false, 'error' => 'No database content provided']; + } + + if (!preg_match('/^[A-Za-z0-9._-]+$/', $edition)) { + return ['success' => false, 'error' => 'Invalid edition name']; + } + + $decoded = base64_decode($content, true); + if ($decoded === false) { + return ['success' => false, 'error' => 'Invalid database content']; + } + + $targetDir = '/usr/share/GeoIP'; + if (!is_dir($targetDir)) { + @mkdir($targetDir, 0755, true); + } + + $target = $targetDir . '/' . $edition . '.mmdb'; + if (file_put_contents($target, $decoded) === false) { + return ['success' => false, 'error' => 'Failed to write GeoIP database']; + } + + @chmod($target, 0644); + + return ['success' => true, 'path' => $target]; +} + +function ensureGeoIpUpdateTool(): ?string +{ + if (toolExists('geoipupdate')) { + return null; + } + + $error = installGeoIpUpdateBinary(); + if ($error !== null) { + return $error; + } + + if (!toolExists('geoipupdate')) { + return 'geoipupdate is not installed'; + } + + return null; +} + +function installGeoIpUpdateBinary(): ?string +{ + $arch = php_uname('m'); + $archMap = [ + 'x86_64' => 'amd64', + 'amd64' => 'amd64', + 'aarch64' => 'arm64', + 'arm64' => 'arm64', + ]; + $archToken = $archMap[$arch] ?? $arch; + + $apiUrl = 'https://api.github.com/repos/maxmind/geoipupdate/releases/latest'; + $metadata = @shell_exec('curl -fsSL ' . escapeshellarg($apiUrl) . ' 2>/dev/null'); + if (!$metadata) { + $metadata = @shell_exec('wget -qO- ' . escapeshellarg($apiUrl) . ' 2>/dev/null'); + } + + if (!$metadata) { + return 'Failed to download geoipupdate release metadata'; + } + + $data = json_decode($metadata, true); + if (!is_array($data)) { + return 'Invalid geoipupdate release metadata'; + } + + $downloadUrl = null; + foreach (($data['assets'] ?? []) as $asset) { + $name = strtolower((string) ($asset['name'] ?? '')); + $url = (string) ($asset['browser_download_url'] ?? ''); + if ($name === '' || $url === '') { + continue; + } + if (strpos($name, 'linux') === false) { + continue; + } + if (strpos($name, $archToken) === false) { + if (!($archToken === 'amd64' && strpos($name, 'x86_64') !== false)) { + continue; + } + } + if (!str_ends_with($name, '.tar.gz') && !str_ends_with($name, '.tgz')) { + continue; + } + $downloadUrl = $url; + break; + } + + if (!$downloadUrl) { + return 'No suitable geoipupdate binary found for ' . $arch; + } + + $tmpDir = sys_get_temp_dir() . '/jabali-geoipupdate-' . bin2hex(random_bytes(4)); + @mkdir($tmpDir, 0755, true); + $archive = $tmpDir . '/geoipupdate.tgz'; + + $downloadCmd = toolExists('curl') + ? 'curl -fsSL ' . escapeshellarg($downloadUrl) . ' -o ' . escapeshellarg($archive) + : 'wget -qO ' . escapeshellarg($archive) . ' ' . escapeshellarg($downloadUrl); + + exec($downloadCmd . ' 2>&1', $output, $code); + if ($code !== 0) { + return 'Failed to download geoipupdate binary'; + } + + exec('tar -xzf ' . escapeshellarg($archive) . ' -C ' . escapeshellarg($tmpDir) . ' 2>&1', $output, $code); + if ($code !== 0) { + return 'Failed to extract geoipupdate archive'; + } + + $binary = null; + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($tmpDir, FilesystemIterator::SKIP_DOTS)); + foreach ($iterator as $file) { + if ($file->isFile() && $file->getFilename() === 'geoipupdate') { + $binary = $file->getPathname(); + break; + } + } + + if (!$binary) { + return 'geoipupdate binary not found in archive'; + } + + exec('install -m 0755 ' . escapeshellarg($binary) . ' /usr/local/bin/geoipupdate 2>&1', $output, $code); + if ($code !== 0) { + return 'Failed to install geoipupdate'; + } + + return null; +} + function ensureGeoIpModuleEnabled(): ?string { $modulePaths = [ diff --git a/install.sh b/install.sh index 6b7b741..2948c3b 100755 --- a/install.sh +++ b/install.sh @@ -12,7 +12,7 @@ set -e # Version - will be read from VERSION file after clone, this is fallback -JABALI_VERSION="0.9-rc20" +JABALI_VERSION="0.9-rc21" # Colors RED='\033[0;31m' @@ -720,6 +720,74 @@ install_packages() { log "System packages installed" } +install_geoipupdate_binary() { + if command -v geoipupdate &>/dev/null; then + return + fi + + info "geoipupdate not found, installing from MaxMind releases..." + + local arch + arch="$(uname -m)" + local arch_token="$arch" + if [[ "$arch" == "x86_64" ]]; then + arch_token="amd64" + elif [[ "$arch" == "aarch64" || "$arch" == "arm64" ]]; then + arch_token="arm64" + fi + + local api_url="https://api.github.com/repos/maxmind/geoipupdate/releases/latest" + local metadata + metadata=$(curl -fsSL "$api_url" 2>/dev/null || true) + if [[ -z "$metadata" ]]; then + metadata=$(wget -qO- "$api_url" 2>/dev/null || true) + fi + + if [[ -z "$metadata" ]]; then + warn "Failed to download geoipupdate release metadata" + return + fi + + local download_url + download_url=$(echo "$metadata" | grep -Eo "https://[^\"]+${arch_token}[^\"]+\\.tar\\.gz" | head -n1) + if [[ -z "$download_url" && "$arch_token" == "amd64" ]]; then + download_url=$(echo "$metadata" | grep -Eo "https://[^\"]+x86_64[^\"]+\\.tar\\.gz" | head -n1) + fi + + if [[ -z "$download_url" ]]; then + warn "No suitable geoipupdate binary found for ${arch}" + return + fi + + local tmp_dir + tmp_dir=$(mktemp -d) + local archive="${tmp_dir}/geoipupdate.tgz" + + if command -v curl &>/dev/null; then + curl -fsSL "$download_url" -o "$archive" 2>/dev/null || true + else + wget -qO "$archive" "$download_url" 2>/dev/null || true + fi + + if [[ ! -s "$archive" ]]; then + warn "Failed to download geoipupdate binary" + rm -rf "$tmp_dir" + return + fi + + tar -xzf "$archive" -C "$tmp_dir" 2>/dev/null || true + local binary + binary=$(find "$tmp_dir" -type f -name geoipupdate | head -n1) + if [[ -z "$binary" ]]; then + warn "geoipupdate binary not found in archive" + rm -rf "$tmp_dir" + return + fi + + install -m 0755 "$binary" /usr/local/bin/geoipupdate 2>/dev/null || true + rm -rf "$tmp_dir" +} + # Install Composer install_composer() { header "Installing Composer" @@ -3248,6 +3316,7 @@ main() { add_repositories install_packages + install_geoipupdate_binary install_composer clone_jabali configure_php