Add MaxMind geoipupdate and upload fallback

This commit is contained in:
root
2026-01-28 19:51:00 +02:00
parent 02c756d077
commit b51139f65f
6 changed files with 286 additions and 5 deletions

View File

@@ -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.

View File

@@ -1 +1 @@
VERSION=0.9-rc20
VERSION=0.9-rc21

View File

@@ -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(),
];
}

View File

@@ -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', [

View File

@@ -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 = [

View File

@@ -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