From 17eaaf2347ddeaeef4087568a65edf4490bee8aa Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Fri, 22 May 2026 22:43:41 -0500 Subject: [PATCH] feat: add Akeeba Backup and Admin Tools checks to health endpoint New health checks: - backup: last backup date/status/size, days since, total count, 7d count - security: Admin Tools WAF status, blocked requests 24h/7d Degraded reasons: - No backups found - Last backup older than 7 days - Last backup failed/incomplete Dashboard updated with Backup and Security rows. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/Extension/MokoWaaS.php | 223 ++++++++++++++++++++++++++++++++++++- 1 file changed, 222 insertions(+), 1 deletion(-) diff --git a/src/Extension/MokoWaaS.php b/src/Extension/MokoWaaS.php index afd7d5b7..40207542 100644 --- a/src/Extension/MokoWaaS.php +++ b/src/Extension/MokoWaaS.php @@ -1126,6 +1126,29 @@ class MokoWaaS extends CMSPlugin $reasons[] = 'Low disk space: ' . $check['free_disk_mb'] . ' MB free'; } + elseif ($name === 'backup') + { + if (!empty($check['message'])) + { + $reasons[] = $check['message']; + } + elseif (isset($check['days_since']) + && $check['days_since'] > 7) + { + $reasons[] = 'Last backup ' + . $check['days_since'] . ' days ago'; + } + elseif (isset($check['last_status']) + && $check['last_status'] !== 'complete') + { + $reasons[] = 'Last backup status: ' + . $check['last_status']; + } + else + { + $reasons[] = 'Backup: degraded'; + } + } else { $reasons[] = $name . ': degraded'; @@ -1156,12 +1179,16 @@ class MokoWaaS extends CMSPlugin */ protected function collectHealthChecks() { - return [ + $checks = [ 'database' => $this->checkDatabase(), 'filesystem' => $this->checkFilesystem(), 'cache' => $this->checkCache(), 'extensions' => $this->checkExtensions(), + 'backup' => $this->checkAkeebaBackup(), + 'security' => $this->checkAdminTools(), ]; + + return $checks; } /** @@ -1347,6 +1374,200 @@ class MokoWaaS extends CMSPlugin } } + /** + * Check Akeeba Backup status — last backup date, status, and profile. + * + * Queries the #__ak_stats table (Akeeba Backup) for the most recent + * backup record. Returns 'not_installed' if the table doesn't exist. + * + * @return array Check result with backup info + * + * @since 02.01.39 + */ + protected function checkAkeebaBackup() + { + try + { + $db = Factory::getDbo(); + + // Check if Akeeba Backup is installed + $tables = $db->getTableList(); + $prefix = $db->getPrefix(); + $akTable = $prefix . 'ak_stats'; + + if (!in_array($akTable, $tables)) + { + return [ + 'status' => 'ok', + 'installed' => false, + ]; + } + + // Get the most recent backup + $query = $db->getQuery(true) + ->select([ + $db->quoteName('id'), + $db->quoteName('description'), + $db->quoteName('status'), + $db->quoteName('backupstart'), + $db->quoteName('backupend'), + $db->quoteName('profile_id'), + $db->quoteName('total_size'), + ]) + ->from($db->quoteName('#__ak_stats')) + ->order($db->quoteName('id') . ' DESC'); + + $db->setQuery($query, 0, 1); + $latest = $db->loadObject(); + + if (!$latest) + { + return [ + 'status' => 'degraded', + 'installed' => true, + 'message' => 'No backups found', + ]; + } + + // Count total backups and recent (last 7 days) + $db->setQuery( + $db->getQuery(true) + ->select('COUNT(*)') + ->from($db->quoteName('#__ak_stats')) + ); + $totalBackups = (int) $db->loadResult(); + + $db->setQuery( + $db->getQuery(true) + ->select('COUNT(*)') + ->from($db->quoteName('#__ak_stats')) + ->where($db->quoteName('backupstart') + . ' >= DATE_SUB(NOW(), INTERVAL 7 DAY)') + ); + $recentBackups = (int) $db->loadResult(); + + // Check if last backup is older than 7 days + $lastDate = $latest->backupstart; + $daysSince = (int) ((time() - strtotime($lastDate)) / 86400); + $backupSize = $latest->total_size + ? round($latest->total_size / 1048576) + : null; + + $status = 'ok'; + + if ($latest->status !== 'complete') + { + $status = 'degraded'; + } + elseif ($daysSince > 7) + { + $status = 'degraded'; + } + + return [ + 'status' => $status, + 'installed' => true, + 'last_backup' => $lastDate, + 'last_status' => $latest->status, + 'last_size_mb' => $backupSize, + 'days_since' => $daysSince, + 'profile_id' => (int) $latest->profile_id, + 'total_backups' => $totalBackups, + 'recent_7d' => $recentBackups, + 'description' => $latest->description, + ]; + } + catch (\Exception $e) + { + return [ + 'status' => 'ok', + 'installed' => false, + ]; + } + } + + /** + * Check Admin Tools status — WAF status, security exceptions. + * + * Queries Admin Tools tables for firewall status and recent blocks. + * Returns 'not_installed' if tables don't exist. + * + * @return array Check result with security info + * + * @since 02.01.39 + */ + protected function checkAdminTools() + { + try + { + $db = Factory::getDbo(); + $tables = $db->getTableList(); + $prefix = $db->getPrefix(); + + // Check if Admin Tools is installed + $atTable = $prefix . 'admintools_log'; + + if (!in_array($atTable, $tables)) + { + return [ + 'status' => 'ok', + 'installed' => false, + ]; + } + + // Count blocked requests in last 24h + $db->setQuery( + $db->getQuery(true) + ->select('COUNT(*)') + ->from($db->quoteName('#__admintools_log')) + ->where($db->quoteName('logdate') + . ' >= DATE_SUB(NOW(), INTERVAL 1 DAY)') + ); + $blocked24h = (int) $db->loadResult(); + + // Count blocked in last 7 days + $db->setQuery( + $db->getQuery(true) + ->select('COUNT(*)') + ->from($db->quoteName('#__admintools_log')) + ->where($db->quoteName('logdate') + . ' >= DATE_SUB(NOW(), INTERVAL 7 DAY)') + ); + $blocked7d = (int) $db->loadResult(); + + // Check WAF config if available + $wafEnabled = null; + $wafTable = $prefix . 'admintools_wafconfig'; + + if (in_array($wafTable, $tables)) + { + $db->setQuery( + $db->getQuery(true) + ->select($db->quoteName('value')) + ->from($db->quoteName('#__admintools_wafconfig')) + ->where($db->quoteName('key') . ' = ' + . $db->quote('ipworkarounds')) + ); + $wafEnabled = $db->loadResult() !== null; + } + + return [ + 'status' => 'ok', + 'installed' => true, + 'blocked_24h' => $blocked24h, + 'blocked_7d' => $blocked7d, + 'waf_active' => $wafEnabled, + ]; + } + catch (\Exception $e) + { + return [ + 'status' => 'ok', + 'installed' => false, + ]; + } + } + /** * Send a JSON health response and terminate execution. *