Skip to content

Commit 89a78de

Browse files
feat: add requirements:health task for service health checks
1 parent 5170e8c commit 89a78de

4 files changed

Lines changed: 116 additions & 0 deletions

File tree

autoload.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
require_once(__DIR__ . '/deployer/requirements/task/check_env.php');
5252
require_once(__DIR__ . '/deployer/requirements/task/check_eol.php');
5353
require_once(__DIR__ . '/deployer/requirements/task/list.php');
54+
require_once(__DIR__ . '/deployer/requirements/task/health.php');
5455

5556
/*
5657
* dev

deployer/requirements/config/set.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@
107107
set('requirements_eol_warn_months', 6);
108108
set('requirements_eol_api_timeout', 5);
109109

110+
// Health check
111+
set('requirements_check_health_enabled', true);
112+
set('requirements_health_url', 'http://localhost');
113+
110114
// User / permissions
111115
set('requirements_user_group', 'www-data');
112116
set('requirements_deploy_path_permissions', '2770');

deployer/requirements/functions.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,34 @@ function checkEolForProduct(string $label, string $product, string $cycle, int $
331331
evaluateEolStatus("$label $cycle", $match, $warnMonths);
332332
}
333333

334+
/**
335+
* Check if a service is active via systemctl, with pgrep fallback.
336+
*
337+
* Returns the matched service/process name or null if none found.
338+
*/
339+
function isServiceActive(string ...$names): ?string
340+
{
341+
$hasSystemctl = test('command -v systemctl > /dev/null 2>&1');
342+
343+
foreach ($names as $name) {
344+
try {
345+
if ($hasSystemctl) {
346+
$status = trim(run("systemctl is-active $name 2>/dev/null || true"));
347+
348+
if ($status === 'active') {
349+
return $name;
350+
}
351+
} elseif (test("pgrep -x $name > /dev/null 2>&1")) {
352+
return $name;
353+
}
354+
} catch (RunException) {
355+
continue;
356+
}
357+
}
358+
359+
return null;
360+
}
361+
334362
/**
335363
* @return array<string, string>
336364
*/
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Deployer;
6+
7+
use Deployer\Exception\RunException;
8+
9+
task('requirements:health', function (): void {
10+
set('requirements_rows', []);
11+
12+
if (!get('requirements_check_health_enabled')) {
13+
return;
14+
}
15+
16+
// 1. PHP-FPM
17+
try {
18+
$phpVersion = trim(run('php -r "echo PHP_MAJOR_VERSION.\'.\'.PHP_MINOR_VERSION;" 2>/dev/null'));
19+
$fpmService = isServiceActive("php$phpVersion-fpm", 'php-fpm');
20+
21+
if ($fpmService !== null) {
22+
addRequirementRow('PHP-FPM', REQUIREMENT_OK, "Active ($fpmService)");
23+
} else {
24+
addRequirementRow('PHP-FPM', REQUIREMENT_FAIL, 'Process not found');
25+
}
26+
} catch (RunException) {
27+
addRequirementRow('PHP-FPM', REQUIREMENT_SKIP, 'Could not determine PHP version');
28+
}
29+
30+
// 2. Webserver
31+
$webserver = isServiceActive('nginx', 'apache2', 'httpd');
32+
33+
if ($webserver !== null) {
34+
addRequirementRow('Webserver', REQUIREMENT_OK, "Active ($webserver)");
35+
} else {
36+
addRequirementRow('Webserver', REQUIREMENT_FAIL, 'No nginx, apache2 or httpd process found');
37+
}
38+
39+
// 3. Database server
40+
$db = detectDatabaseProduct();
41+
$dbLabel = $db !== null ? $db['label'] : 'Database';
42+
$adminCmd = ($db !== null && $db['product'] === 'mariadb') ? 'mariadb-admin' : 'mysqladmin';
43+
$dbChecked = false;
44+
45+
try {
46+
run("$adminCmd ping --silent 2>&1 || true");
47+
addRequirementRow('Database server', REQUIREMENT_OK, "$dbLabel responding");
48+
$dbChecked = true;
49+
} catch (RunException) {
50+
// Admin tool not available — fall through to process check
51+
}
52+
53+
if (!$dbChecked) {
54+
$dbProcess = isServiceActive('mysqld', 'mariadbd');
55+
56+
if ($dbProcess !== null) {
57+
addRequirementRow('Database server', REQUIREMENT_OK, "$dbLabel process running ($dbProcess)");
58+
} else {
59+
addRequirementRow('Database server', REQUIREMENT_FAIL, 'No mysqld or mariadbd process found');
60+
}
61+
}
62+
63+
// 4. HTTP response
64+
$url = get('requirements_health_url');
65+
66+
try {
67+
$httpCode = (int) trim(run(
68+
sprintf("curl -s -o /dev/null -w '%%{http_code}' --max-time 5 %s 2>/dev/null", escapeshellarg($url))
69+
));
70+
71+
if ($httpCode >= 200 && $httpCode < 500) {
72+
addRequirementRow('HTTP response', REQUIREMENT_OK, "HTTP $httpCode from $url");
73+
} elseif ($httpCode === 0) {
74+
addRequirementRow('HTTP response', REQUIREMENT_FAIL, "No response from $url (connection refused or timeout)");
75+
} else {
76+
addRequirementRow('HTTP response', REQUIREMENT_FAIL, "HTTP $httpCode from $url");
77+
}
78+
} catch (RunException) {
79+
addRequirementRow('HTTP response', REQUIREMENT_SKIP, 'curl not available');
80+
}
81+
82+
renderRequirementsTable();
83+
})->desc('Check service health');

0 commit comments

Comments
 (0)