From 12670f35462fb1dfc492c557c0f870cb5a4068f6 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 29 Jan 2026 20:18:36 +0200 Subject: [PATCH] Improve user deletion summary and installer --- README.md | 3 +- VERSION | 2 +- app/Filament/Admin/Pages/EmailLogs.php | 123 +++++++++++++++--- .../Admin/Resources/Users/Pages/EditUser.php | 27 ++-- .../Resources/Users/Tables/UsersTable.php | 25 +++- app/Providers/AppServiceProvider.php | 24 +++- app/Services/System/LinuxUserService.php | 12 +- bin/jabali-agent | 104 +++++++++++++-- docs/architecture/control-panel-blueprint.md | 1 + install.sh | 40 +++++- 10 files changed, 313 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 1660ce6..83cbeb8 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-rc33 (release candidate) +Version: 0.9-rc34 (release candidate) This is a release candidate. Expect rapid iteration and breaking changes until 1.0. @@ -156,6 +156,7 @@ php artisan test --compact ## Initial Release +- 0.9-rc34: User deletion summary steps; notification re-dispatch on repeated actions; ModSecurity packages added to installer. - 0.9-rc33: Email Logs unified with Mail Queue; journald fallback; agent response reading hardened. - 0.9-rc32: Server Updates list loads reliably; admin sidebar order aligned; apt update parsing expanded. - 0.9-rc31: File manager navigation uses Livewire actions; parent row excluded from bulk select. diff --git a/VERSION b/VERSION index 2f4e6d2..3a9c5c5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -VERSION=0.9-rc33 +VERSION=0.9-rc34 diff --git a/app/Filament/Admin/Pages/EmailLogs.php b/app/Filament/Admin/Pages/EmailLogs.php index 5e029e1..c139933 100644 --- a/app/Filament/Admin/Pages/EmailLogs.php +++ b/app/Filament/Admin/Pages/EmailLogs.php @@ -17,6 +17,8 @@ use Filament\Tables\Concerns\InteractsWithTable; use Filament\Tables\Contracts\HasTable; use Filament\Tables\Table; use Illuminate\Contracts\Support\Htmlable; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Str; class EmailLogs extends Page implements HasActions, HasTable { @@ -132,7 +134,7 @@ class EmailLogs extends Page implements HasActions, HasTable return $table ->paginated([25, 50, 100]) ->defaultPaginationPageOption(25) - ->records(function () { + ->records(function (?array $filters, ?string $search, int|string $page, int|string $recordsPerPage, ?string $sortColumn, ?string $sortDirection) { if ($this->viewMode === 'queue') { if (! $this->queueLoaded) { $this->loadQueue(false); @@ -145,20 +147,10 @@ class EmailLogs extends Page implements HasActions, HasTable $records = $this->logs; } - return collect($records) - ->mapWithKeys(function (array $record, int $index): array { - $queueId = $record['queue_id'] ?? ''; - $timestamp = (int) ($record['timestamp'] ?? 0); - $keyParts = array_filter([ - $queueId, - $timestamp > 0 ? (string) $timestamp : '', - ], fn (string $part): bool => $part !== ''); + $records = $this->filterRecords($records, $search); + $records = $this->sortRecords($records, $sortColumn, $sortDirection); - $key = implode('-', $keyParts); - - return [$key !== '' ? $key : (string) $index => $record]; - }) - ->all(); + return $this->paginateRecords($records, $page, $recordsPerPage); }) ->columns($this->viewMode === 'queue' ? $this->getQueueColumns() : $this->getLogColumns()) ->recordActions($this->viewMode === 'queue' ? $this->getQueueActions() : []) @@ -243,11 +235,31 @@ class EmailLogs extends Page implements HasActions, HasTable ->searchable(), TextColumn::make('recipients') ->label(__('Recipients')) - ->formatStateUsing(function (array $record): string { - $recipients = $record['recipients'] ?? []; + ->formatStateUsing(function ($state): string { + if (is_array($state)) { + $recipients = $state; + } elseif ($state === null || $state === '') { + $recipients = []; + } else { + $recipients = [(string) $state]; + } + + $recipients = array_values(array_filter(array_map(function ($recipient): ?string { + if (is_array($recipient)) { + return (string) ($recipient['address'] + ?? $recipient['recipient'] + ?? $recipient['email'] + ?? $recipient[0] + ?? ''); + } + + return $recipient === null ? null : (string) $recipient; + }, $recipients), static fn (?string $value): bool => $value !== null && $value !== '')); + if (empty($recipients)) { return __('Unknown'); } + $first = $recipients[0] ?? ''; $count = count($recipients); @@ -256,7 +268,7 @@ class EmailLogs extends Page implements HasActions, HasTable ->wrap(), TextColumn::make('size') ->label(__('Size')) - ->formatStateUsing(fn (array $record): string => $record['size'] ?? ''), + ->formatStateUsing(fn ($state): string => is_scalar($state) ? (string) $state : ''), TextColumn::make('status') ->label(__('Status')) ->wrap(), @@ -303,4 +315,81 @@ class EmailLogs extends Page implements HasActions, HasTable }), ]; } + + protected function filterRecords(array $records, ?string $search): array + { + $search = trim((string) $search); + if ($search === '') { + return $records; + } + + $search = Str::lower($search); + + return array_values(array_filter($records, function (array $record) use ($search): bool { + if ($this->viewMode === 'queue') { + $recipients = $record['recipients'] ?? []; + $haystack = implode(' ', array_filter([ + (string) ($record['id'] ?? ''), + (string) ($record['sender'] ?? ''), + implode(' ', $recipients), + (string) ($record['status'] ?? ''), + ])); + } else { + $haystack = implode(' ', array_filter([ + (string) ($record['queue_id'] ?? ''), + (string) ($record['from'] ?? ''), + (string) ($record['to'] ?? ''), + (string) ($record['status'] ?? ''), + (string) ($record['message'] ?? ''), + (string) ($record['component'] ?? ''), + ])); + } + + return str_contains(Str::lower($haystack), $search); + })); + } + + protected function sortRecords(array $records, ?string $sortColumn, ?string $sortDirection): array + { + $direction = $sortDirection === 'asc' ? 'asc' : 'desc'; + + if (! $sortColumn) { + return $records; + } + + usort($records, function (array $a, array $b) use ($sortColumn, $direction): int { + $aValue = $a[$sortColumn] ?? null; + $bValue = $b[$sortColumn] ?? null; + + if (is_numeric($aValue) && is_numeric($bValue)) { + $result = (float) $aValue <=> (float) $bValue; + } else { + $result = strcmp((string) $aValue, (string) $bValue); + } + + return $direction === 'asc' ? $result : -$result; + }); + + return $records; + } + + protected function paginateRecords(array $records, int|string $page, int|string $recordsPerPage): LengthAwarePaginator + { + $page = max(1, (int) $page); + $perPage = max(1, (int) $recordsPerPage); + + $total = count($records); + $items = array_slice($records, ($page - 1) * $perPage, $perPage); + + return new LengthAwarePaginator( + $items, + $total, + $perPage, + $page, + [ + 'path' => request()->url(), + 'pageName' => $this->getTablePaginationPageName(), + ], + ); + } } diff --git a/app/Filament/Admin/Resources/Users/Pages/EditUser.php b/app/Filament/Admin/Resources/Users/Pages/EditUser.php index 3cfecb0..50893bb 100644 --- a/app/Filament/Admin/Resources/Users/Pages/EditUser.php +++ b/app/Filament/Admin/Resources/Users/Pages/EditUser.php @@ -96,22 +96,22 @@ class EditUser extends EditRecord ->action(function (array $data) { $removeHome = $data['remove_home'] ?? false; $username = $this->record->username; + $steps = []; try { $linuxService = new LinuxUserService; + $domains = $this->record->domains()->pluck('domain')->all(); if ($linuxService->userExists($username)) { - $linuxService->deleteUser($username, $removeHome); + $result = $linuxService->deleteUser($username, $removeHome, $domains); - $body = $removeHome - ? __("System user ':username' has been deleted along with home directory.", ['username' => $username]) - : __("System user ':username' has been deleted.", ['username' => $username]); + if (! ($result['success'] ?? false)) { + throw new Exception($result['error'] ?? __('Failed to delete Linux user')); + } - Notification::make() - ->title(__('Linux user deleted')) - ->body($body) - ->success() - ->send(); + $steps = array_merge($steps, $result['steps'] ?? []); + } else { + $steps[] = __('Linux user not found on the server'); } } catch (Exception $e) { Notification::make() @@ -124,6 +124,15 @@ class EditUser extends EditRecord // Delete from database $this->record->delete(); + $steps[] = __('Removed user from admin list'); + $details = implode("\n", array_map(fn ($step): string => '• '.$step, $steps)); + + Notification::make() + ->title(__('User :username removed', ['username' => $username])) + ->body($details) + ->success() + ->send(); + $this->redirect($this->getResource()::getUrl('index')); }), ]; diff --git a/app/Filament/Admin/Resources/Users/Tables/UsersTable.php b/app/Filament/Admin/Resources/Users/Tables/UsersTable.php index d9da880..acca0af 100644 --- a/app/Filament/Admin/Resources/Users/Tables/UsersTable.php +++ b/app/Filament/Admin/Resources/Users/Tables/UsersTable.php @@ -125,18 +125,22 @@ class UsersTable ->action(function ($record, array $data) { $removeHome = $data['remove_home'] ?? false; $username = $record->username; + $steps = []; try { $linuxService = new LinuxUserService; + $domains = $record->domains()->pluck('domain')->all(); if ($linuxService->userExists($username)) { - $linuxService->deleteUser($username, $removeHome); + $result = $linuxService->deleteUser($username, $removeHome, $domains); - Notification::make() - ->title(__('Linux user deleted')) - ->body(__("System user ':username' has been deleted.", ['username' => $username])) - ->success() - ->send(); + if (! ($result['success'] ?? false)) { + throw new Exception($result['error'] ?? __('Failed to delete Linux user')); + } + + $steps = array_merge($steps, $result['steps'] ?? []); + } else { + $steps[] = __('Linux user not found on the server'); } } catch (Exception $e) { Notification::make() @@ -147,6 +151,15 @@ class UsersTable } $record->delete(); + + $steps[] = __('Removed user from admin list'); + $details = implode("\n", array_map(fn ($step): string => '• '.$step, $steps)); + + Notification::make() + ->title(__('User :username removed', ['username' => $username])) + ->body($details) + ->success() + ->send(); }), ]) ->bulkActions([ diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 0c5cf5c..547ea7b 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,9 +2,13 @@ namespace App\Providers; -use Illuminate\Support\ServiceProvider; use App\Models\Domain; use App\Observers\DomainObserver; +use Illuminate\Support\ServiceProvider; +use Livewire\Component; +use Livewire\Livewire; + +use function Livewire\on; class AppServiceProvider extends ServiceProvider { @@ -25,5 +29,23 @@ class AppServiceProvider extends ServiceProvider // Note: AuthEventListener is auto-discovered by Laravel 11+ // Do not manually subscribe - it causes duplicate audit log entries + on('dehydrate', function (Component $component): void { + static $dispatched = false; + + if ($dispatched) { + return; + } + + if (! Livewire::isLivewireRequest()) { + return; + } + + if (count(session()->get('filament.notifications') ?? []) <= 0) { + return; + } + + $dispatched = true; + $component->dispatch('notificationsSent'); + }); } } diff --git a/app/Services/System/LinuxUserService.php b/app/Services/System/LinuxUserService.php index a955151..6e243e6 100644 --- a/app/Services/System/LinuxUserService.php +++ b/app/Services/System/LinuxUserService.php @@ -14,7 +14,7 @@ class LinuxUserService public function __construct(?AgentClient $agent = null) { - $this->agent = $agent ?? new AgentClient(); + $this->agent = $agent ?? new AgentClient; } /** @@ -28,6 +28,7 @@ class LinuxUserService $user->update([ 'home_directory' => $response['home_directory'] ?? "/home/{$user->username}", ]); + return true; } @@ -36,11 +37,13 @@ class LinuxUserService /** * Delete a Linux system user + * + * @param array $domains + * @return array */ - public function deleteUser(string $username, bool $removeHome = false): bool + public function deleteUser(string $username, bool $removeHome = false, array $domains = []): array { - $response = $this->agent->deleteUser($username, $removeHome); - return $response['success'] ?? false; + return $this->agent->deleteUser($username, $removeHome, $domains); } /** @@ -57,6 +60,7 @@ class LinuxUserService public function setPassword(string $username, string $password): bool { $response = $this->agent->setUserPassword($username, $password); + return $response['success'] ?? false; } diff --git a/bin/jabali-agent b/bin/jabali-agent index 353a5bb..d1a4a2e 100755 --- a/bin/jabali-agent +++ b/bin/jabali-agent @@ -834,6 +834,7 @@ function deleteUser(array $params): array $username = $params['username'] ?? ''; $removeHome = $params['remove_home'] ?? false; $domains = $params['domains'] ?? []; // List of user's domains to clean up + $steps = []; if (!validateUsername($username)) { return ['success' => false, 'error' => 'Invalid username format']; @@ -860,12 +861,17 @@ function deleteUser(array $params): array } } + $domainConfigRemoved = false; + $domainsDirExists = is_dir("$homeDir/domains"); + // Clean up domain-related files for each domain foreach ($domains as $domain) { if (!validateDomain($domain)) { continue; } + $domainTouched = false; + // Remove nginx vhost configs (with .conf extension) $nginxAvailable = "/etc/nginx/sites-available/{$domain}.conf"; $nginxEnabled = "/etc/nginx/sites-enabled/{$domain}.conf"; @@ -877,12 +883,14 @@ function deleteUser(array $params): array if (file_exists($file) || is_link($file)) { @unlink($file); logger("Removed nginx symlink: $file"); + $domainTouched = true; } } foreach ([$nginxAvailable, $nginxAvailableOld] as $file) { if (file_exists($file)) { @unlink($file); logger("Removed nginx config: $file"); + $domainTouched = true; } } @@ -891,6 +899,7 @@ function deleteUser(array $params): array if (file_exists($zoneFile)) { @unlink($zoneFile); logger("Removed DNS zone: $zoneFile"); + $domainTouched = true; // Remove from named.conf.local $namedConf = '/etc/bind/named.conf.local'; @@ -911,11 +920,13 @@ function deleteUser(array $params): array if (is_dir($mailDir)) { exec("rm -rf " . escapeshellarg($mailDir)); logger("Removed mail directory: $mailDir"); + $domainTouched = true; } $vmailDir = "/var/vmail/$domain"; if (is_dir($vmailDir)) { exec("rm -rf " . escapeshellarg($vmailDir)); logger("Removed vmail directory: $vmailDir"); + $domainTouched = true; } // Remove from Postfix virtual_mailbox_domains @@ -952,14 +963,22 @@ function deleteUser(array $params): array if (is_dir($certPath)) { exec("rm -rf " . escapeshellarg($certPath)); logger("Removed SSL certificate: $certPath"); + $domainTouched = true; } if (is_dir($certArchive)) { exec("rm -rf " . escapeshellarg($certArchive)); logger("Removed SSL archive: $certArchive"); + $domainTouched = true; } if (file_exists($certRenewal)) { @unlink($certRenewal); logger("Removed SSL renewal config: $certRenewal"); + $domainTouched = true; + } + + if ($domainTouched) { + $domainConfigRemoved = true; + $steps[] = "$domain config files removed"; } } @@ -967,6 +986,9 @@ function deleteUser(array $params): array $dbPrefix = $username . '_'; $mysqli = getMysqlConnection(); if ($mysqli) { + $dbDeletedCount = 0; + $dbUserDeletedCount = 0; + // Get all databases belonging to this user $result = $mysqli->query("SHOW DATABASES LIKE '{$mysqli->real_escape_string($dbPrefix)}%'"); if ($result) { @@ -976,6 +998,7 @@ function deleteUser(array $params): array if (strpos($dbName, $dbPrefix) === 0) { $mysqli->query("DROP DATABASE IF EXISTS `{$mysqli->real_escape_string($dbName)}`"); logger("Deleted MySQL database: $dbName"); + $dbDeletedCount++; } } $result->free(); @@ -991,18 +1014,33 @@ function deleteUser(array $params): array if (strpos($dbUser, $dbPrefix) === 0) { $mysqli->query("DROP USER IF EXISTS '{$mysqli->real_escape_string($dbUser)}'@'{$mysqli->real_escape_string($dbHost)}'"); logger("Deleted MySQL user: $dbUser@$dbHost"); + $dbUserDeletedCount++; } } $result->free(); } $mysqli->query("FLUSH PRIVILEGES"); $mysqli->close(); + + if ($dbDeletedCount > 0) { + $steps[] = "MySQL databases removed ({$dbDeletedCount})"; + } + if ($dbUserDeletedCount > 0) { + $steps[] = "MySQL users removed ({$dbUserDeletedCount})"; + } } // Remove PHP-FPM pool config + $fpmRemovedCount = 0; foreach (glob("/etc/php/*/fpm/pool.d/$username.conf") as $poolConf) { - @unlink($poolConf); - logger("Removed PHP-FPM pool: $poolConf"); + if (@unlink($poolConf)) { + logger("Removed PHP-FPM pool: $poolConf"); + $fpmRemovedCount++; + } + } + if ($fpmRemovedCount > 0) { + $steps[] = 'PHP-FPM pool removed'; + $domainConfigRemoved = true; } // Delete user with --force to ignore warnings about mail spool @@ -1018,12 +1056,16 @@ function deleteUser(array $params): array return ['success' => false, 'error' => 'Failed to delete user: ' . implode("\n", $userdelOutput)]; } + $steps[] = 'User removed from SSH'; + $steps[] = 'Unix user removed from the server'; + // Delete Redis ACL user (and all their cached keys) $redisResult = redisDeleteUser(['username' => $username]); if (!$redisResult['success']) { logger("Warning: Failed to delete Redis user for $username: " . ($redisResult['error'] ?? 'Unknown error')); } else { logger("Deleted Redis ACL user for $username"); + $steps[] = 'Redis ACL user removed'; } // Manually remove home directory if requested (since it's owned by root) @@ -1031,6 +1073,11 @@ function deleteUser(array $params): array exec(sprintf('rm -rf %s 2>&1', escapeshellarg($homeDir)), $rmOutput, $rmExit); if ($rmExit !== 0) { logger("Warning: Failed to remove home directory for $username"); + } else { + $steps[] = "User's data directory removed"; + if ($domainsDirExists) { + $steps[] = "User's domains directory removed"; + } } } @@ -1042,9 +1089,13 @@ function deleteUser(array $params): array exec('rndc reload 2>/dev/null'); exec('systemctl reload php*-fpm 2>/dev/null'); + if ($domainConfigRemoved) { + $steps[] = "User's config files deleted"; + } + logger("Deleted user $username" . ($removeHome ? " with home directory" : "") . " and cleaned up " . count($domains) . " domain(s)"); - return ['success' => true, 'message' => "User $username deleted successfully"]; + return ['success' => true, 'message' => "User $username deleted successfully", 'steps' => $steps]; } function setUserPassword(array $params): array @@ -2784,19 +2835,26 @@ function ensureJabaliNginxIncludeFiles(): void ensureWafUnicodeMapFile(); ensureWafMainConfig(); + $modSecurityAvailable = isModSecurityModuleAvailable(); $baseConfig = findWafBaseConfig(); - $shouldDisableWaf = $baseConfig === null; + $shouldDisableWaf = $baseConfig === null || !$modSecurityAvailable; if (!file_exists(JABALI_WAF_INCLUDE)) { $content = "# Managed by Jabali\n"; - if ($shouldDisableWaf) { + if (!$modSecurityAvailable) { + $content .= "# ModSecurity module not available in nginx.\n"; + } elseif ($shouldDisableWaf) { $content .= "modsecurity off;\n"; } file_put_contents(JABALI_WAF_INCLUDE, $content); } elseif ($shouldDisableWaf) { $current = file_get_contents(JABALI_WAF_INCLUDE); - if ($current === false || strpos($current, 'modsecurity_rules_file') !== false || strpos($current, 'modsecurity on;') !== false) { - file_put_contents(JABALI_WAF_INCLUDE, "# Managed by Jabali\nmodsecurity off;\n"); + if ($current === false || strpos($current, 'modsecurity_rules_file') !== false || strpos($current, 'modsecurity on;') !== false || strpos($current, 'modsecurity off;') !== false) { + if (!$modSecurityAvailable) { + file_put_contents(JABALI_WAF_INCLUDE, "# Managed by Jabali\n# ModSecurity module not available in nginx.\n"); + } else { + file_put_contents(JABALI_WAF_INCLUDE, "# Managed by Jabali\nmodsecurity off;\n"); + } } } @@ -2944,6 +3002,26 @@ function findWafBaseConfig(): ?string return null; } +function isModSecurityModuleAvailable(): bool +{ + $output = []; + exec('nginx -V 2>&1', $output); + $info = implode("\n", $output); + + if (stripos($info, 'modsecurity') !== false) { + return true; + } + + foreach (glob('/etc/nginx/modules-enabled/*.conf') ?: [] as $file) { + $content = file_get_contents($file); + if ($content !== false && stripos($content, 'modsecurity') !== false) { + return true; + } + } + + return false; +} + function isWafBaseConfigUsable(string $path): bool { if (!is_readable($path)) { @@ -3006,8 +3084,14 @@ function wafApplySettings(array $params): array $prevInclude = file_exists(JABALI_WAF_INCLUDE) ? file_get_contents(JABALI_WAF_INCLUDE) : null; $prevRules = file_exists(JABALI_WAF_RULES) ? file_get_contents(JABALI_WAF_RULES) : null; + $modSecurityAvailable = isModSecurityModuleAvailable(); if ($enabled) { + if (!$modSecurityAvailable) { + file_put_contents(JABALI_WAF_INCLUDE, "# Managed by Jabali\n# ModSecurity module not available in nginx.\n"); + return ['success' => false, 'error' => 'ModSecurity module not available in nginx']; + } + ensureWafUnicodeMapFile(); $baseConfig = findWafBaseConfig(); if (!$baseConfig) { @@ -3035,7 +3119,11 @@ function wafApplySettings(array $params): array file_put_contents(JABALI_WAF_INCLUDE, implode("\n", $include) . "\n"); } else { - file_put_contents(JABALI_WAF_INCLUDE, "# Managed by Jabali\nmodsecurity off;\n"); + if ($modSecurityAvailable) { + file_put_contents(JABALI_WAF_INCLUDE, "# Managed by Jabali\nmodsecurity off;\n"); + } else { + file_put_contents(JABALI_WAF_INCLUDE, "# Managed by Jabali\n# ModSecurity module not available in nginx.\n"); + } } ensureNginxServerIncludes([ diff --git a/docs/architecture/control-panel-blueprint.md b/docs/architecture/control-panel-blueprint.md index 49e2e0c..9569e44 100644 --- a/docs/architecture/control-panel-blueprint.md +++ b/docs/architecture/control-panel-blueprint.md @@ -16,6 +16,7 @@ This blueprint describes a modern web hosting control panel (cPanel/DirectAdmin- ### Control plane (panel) - UI + API, RBAC, tenant/package/quota management +- UI stack: Tailwind CSS + Filament components for panels, forms, tables, and widgets - Job runner + queue workers - Audit log + job logs/artifacts - Central configuration + templates diff --git a/install.sh b/install.sh index 5e9b902..6c1742b 100755 --- a/install.sh +++ b/install.sh @@ -16,7 +16,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [[ -f "$SCRIPT_DIR/VERSION" ]]; then JABALI_VERSION="$(sed -n 's/^VERSION=//p' "$SCRIPT_DIR/VERSION")" fi -JABALI_VERSION="${JABALI_VERSION:-0.9-rc26}" +JABALI_VERSION="${JABALI_VERSION:-0.9-rc34}" # Colors RED='\033[0;31m' @@ -487,6 +487,38 @@ install_packages() { # Add Security packages if enabled if [[ "$INSTALL_SECURITY" == "true" ]]; then info "Including Security packages..." + if apt-cache show libnginx-mod-http-modsecurity &>/dev/null; then + base_packages+=( + libnginx-mod-http-modsecurity + ) + elif apt-cache show libnginx-mod-http-modsecurity2 &>/dev/null; then + base_packages+=( + libnginx-mod-http-modsecurity2 + ) + elif apt-cache show nginx-extras &>/dev/null; then + base_packages+=( + nginx-extras + ) + else + warn "ModSecurity nginx module not available in apt repositories" + fi + + if apt-cache show libmodsecurity3t64 &>/dev/null; then + base_packages+=( + libmodsecurity3t64 + ) + elif apt-cache show libmodsecurity3 &>/dev/null; then + base_packages+=( + libmodsecurity3 + ) + fi + + if apt-cache show modsecurity-crs &>/dev/null; then + base_packages+=( + modsecurity-crs + ) + fi + base_packages+=( clamav clamav-daemon @@ -3225,6 +3257,12 @@ uninstall() { # Security fail2ban + libnginx-mod-http-modsecurity + libnginx-mod-http-modsecurity2 + libmodsecurity3t64 + libmodsecurity3 + modsecurity-crs + nginx-extras clamav clamav-daemon clamav-freshclam