liueic revised this gist . Go to revision
1 file changed, 158 insertions
health_check.php(file created)
| @@ -0,0 +1,158 @@ | |||
| 1 | + | <?php | |
| 2 | + | ||
| 3 | + | // 禁用错误报告,避免在健康检查中暴露敏感信息 | |
| 4 | + | error_reporting(0); | |
| 5 | + | ini_set('display_errors', 0); | |
| 6 | + | ||
| 7 | + | header('Content-Type: application/json'); | |
| 8 | + | ||
| 9 | + | $status = [ | |
| 10 | + | 'overall' => 'healthy', | |
| 11 | + | 'checks' => [] | |
| 12 | + | ]; | |
| 13 | + | ||
| 14 | + | // --- 数据库连接检查 (修改为适应Typecho现代化配置) --- | |
| 15 | + | function checkDatabaseConnection() { | |
| 16 | + | // 假设 Typecho 的配置文件是 config.inc.php | |
| 17 | + | $configFilePath = __DIR__ . '/config.inc.php'; | |
| 18 | + | if (!file_exists($configFilePath)) { | |
| 19 | + | return ['status' => 'critical', 'message' => 'Typecho configuration file (config.inc.php) not found.']; | |
| 20 | + | } | |
| 21 | + | ||
| 22 | + | // 尝试包含 config.inc.php 文件 | |
| 23 | + | // 这将初始化 Typecho 的数据库连接对象 | |
| 24 | + | try { | |
| 25 | + | require_once $configFilePath; | |
| 26 | + | ||
| 27 | + | // 检查 Typecho 的 Db 类是否已初始化并可用 | |
| 28 | + | if (!class_exists('\Typecho\Db') || !\Typecho\Db::get()) { | |
| 29 | + | return ['status' => 'critical', 'message' => 'Typecho database object not initialized or failed to get instance.']; | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | // 获取数据库实例 | |
| 33 | + | $db = \Typecho\Db::get(); | |
| 34 | + | ||
| 35 | + | // 尝试执行一个简单的查询来验证连接和权限 | |
| 36 | + | // Typecho 的 Db 对象没有直接的 `query` 方法用于简单SQL, | |
| 37 | + | // 可以通过 `fetchRow` 或 `fetchObject` 来执行 select 语句 | |
| 38 | + | // 或者直接通过 Typecho_Db_Adapter::test() 方法 (如果存在且公开的话) | |
| 39 | + | // 这里我们尝试查询一个 Typecho 常用表,例如 'typecho_options' | |
| 40 | + | ||
| 41 | + | // 注意: Typecho 的 Db::query 方法返回的是结果集对象,不是直接布尔值 | |
| 42 | + | // 我们需要确保查询没有抛出异常,并能获取到结果。 | |
| 43 | + | ||
| 44 | + | // 修正:直接从 $db 对象获取前缀 | |
| 45 | + | $optionsTableName = $db->getPrefix() . 'options'; | |
| 46 | + | ||
| 47 | + | // 尝试查询一条数据,确认连接活跃且表可访问 | |
| 48 | + | $db->fetchRow($db->select()->from($optionsTableName)->limit(1)); | |
| 49 | + | ||
| 50 | + | return ['status' => 'healthy', 'message' => 'Database connection successful and query executed.']; | |
| 51 | + | ||
| 52 | + | } catch (\Typecho\Db\Exception $e) { | |
| 53 | + | return ['status' => 'critical', 'message' => 'Database connection failed: ' . $e->getMessage()]; | |
| 54 | + | } catch (Exception $e) { | |
| 55 | + | return ['status' => 'critical', 'message' => 'An unexpected error occurred during database check: ' . $e->getMessage()]; | |
| 56 | + | } | |
| 57 | + | } | |
| 58 | + | $status['checks']['database'] = checkDatabaseConnection(); | |
| 59 | + | if ($status['checks']['database']['status'] === 'critical') { | |
| 60 | + | $status['overall'] = 'unhealthy'; | |
| 61 | + | } | |
| 62 | + | ||
| 63 | + | // --- 文件系统权限检查 --- | |
| 64 | + | function checkFilePermissions() { | |
| 65 | + | $paths = [ | |
| 66 | + | 'usr/uploads' => 'write', // 用户上传目录 | |
| 67 | + | 'config.inc.php' => 'read', // 配置文件 | |
| 68 | + | 'var/Widget/Contents/Post/Edit.php' => 'read' // 核心文件示例 | |
| 69 | + | ]; | |
| 70 | + | ||
| 71 | + | $results = []; | |
| 72 | + | foreach ($paths as $path => $permissionType) { | |
| 73 | + | $fullPath = __DIR__ . DIRECTORY_SEPARATOR . $path; | |
| 74 | + | if (!file_exists($fullPath)) { | |
| 75 | + | $results[$path] = ['status' => 'warning', 'message' => "Path not found: {$path}"]; | |
| 76 | + | continue; | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | $canAccess = false; | |
| 80 | + | if ($permissionType === 'read') { | |
| 81 | + | $canAccess = is_readable($fullPath); | |
| 82 | + | } elseif ($permissionType === 'write') { | |
| 83 | + | // 对于目录,检查是否可写入 | |
| 84 | + | if (is_dir($fullPath)) { | |
| 85 | + | $testFile = $fullPath . DIRECTORY_SEPARATOR . uniqid('health_check_') . '.tmp'; | |
| 86 | + | if (file_put_contents($testFile, 'test') !== false) { | |
| 87 | + | unlink($testFile); // 删除测试文件 | |
| 88 | + | $canAccess = true; | |
| 89 | + | } | |
| 90 | + | } else { | |
| 91 | + | $canAccess = is_writable($fullPath); | |
| 92 | + | } | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | if ($canAccess) { | |
| 96 | + | $results[$path] = ['status' => 'healthy', 'message' => "{$permissionType} access OK."]; | |
| 97 | + | } else { | |
| 98 | + | $results[$path] = ['status' => 'critical', 'message' => "{$permissionType} access FAILED."]; | |
| 99 | + | } | |
| 100 | + | } | |
| 101 | + | return $results; | |
| 102 | + | } | |
| 103 | + | $status['checks']['file_permissions'] = checkFilePermissions(); | |
| 104 | + | foreach ($status['checks']['file_permissions'] as $pathStatus) { | |
| 105 | + | if ($pathStatus['status'] === 'critical') { | |
| 106 | + | $status['overall'] = 'unhealthy'; | |
| 107 | + | break; | |
| 108 | + | } | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | // --- 核心文件可访问性检查 (HTTP请求) --- | |
| 112 | + | // 注意:这个检查需要服务器环境支持CURL或file_get_contents对外部URL的访问 | |
| 113 | + | // 并且检查的是Web服务器是否能正常提供这些文件,而不是PHP脚本本身是否能访问 | |
| 114 | + | function checkCoreFileAccess() { | |
| 115 | + | $baseUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://{$_SERVER['HTTP_HOST']}"; | |
| 116 | + | // 假设这些文件是可以通过HTTP访问的Typecho核心文件 | |
| 117 | + | // 这里需要根据你的Typecho安装路径和实际可访问的文件来调整 | |
| 118 | + | $coreFiles = [ | |
| 119 | + | '/index.php', // 首页 | |
| 120 | + | '/admin/index.php' // 后台登录页 | |
| 121 | + | ]; | |
| 122 | + | ||
| 123 | + | $results = []; | |
| 124 | + | foreach ($coreFiles as $file) { | |
| 125 | + | $url = $baseUrl . $file; | |
| 126 | + | $ch = curl_init($url); | |
| 127 | + | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
| 128 | + | curl_setopt($ch, CURLOPT_TIMEOUT, 5); // 5秒超时 | |
| 129 | + | curl_setopt($ch, CURLOPT_NOBODY, true); // 只获取头部信息,不下载内容 | |
| 130 | + | curl_exec($ch); | |
| 131 | + | $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); | |
| 132 | + | curl_close($ch); | |
| 133 | + | ||
| 134 | + | if ($httpCode >= 200 && $httpCode < 400) { | |
| 135 | + | $results[$file] = ['status' => 'healthy', 'message' => "Accessible (HTTP {$httpCode})."]; | |
| 136 | + | } else { | |
| 137 | + | $results[$file] = ['status' => 'critical', 'message' => "Not accessible (HTTP {$httpCode})."]; | |
| 138 | + | } | |
| 139 | + | } | |
| 140 | + | return $results; | |
| 141 | + | } | |
| 142 | + | // 仅在服务器支持CURL的情况下执行此检查 | |
| 143 | + | if (function_exists('curl_init')) { | |
| 144 | + | $status['checks']['core_file_access'] = checkCoreFileAccess(); | |
| 145 | + | foreach ($status['checks']['core_file_access'] as $fileStatus) { | |
| 146 | + | if ($fileStatus['status'] === 'critical') { | |
| 147 | + | $status['overall'] = 'unhealthy'; | |
| 148 | + | break; | |
| 149 | + | } | |
| 150 | + | } | |
| 151 | + | } else { | |
| 152 | + | $status['checks']['core_file_access'] = ['status' => 'warning', 'message' => 'cURL extension is not enabled, skipping core file HTTP access check.']; | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | // 输出 JSON 结果 | |
| 156 | + | echo json_encode($status, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); | |
| 157 | + | ||
| 158 | + | ?> | |
Newer
Older