Refine ACP system health/update checks and CLI PHP validation
This commit is contained in:
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class SettingController extends Controller
|
||||
{
|
||||
@@ -38,6 +39,12 @@ class SettingController extends Controller
|
||||
]);
|
||||
|
||||
$value = $data['value'] ?? '';
|
||||
if ($data['key'] === 'system.php_binary') {
|
||||
$validationError = $this->validatePhpBinarySetting($value);
|
||||
if ($validationError !== null) {
|
||||
return response()->json(['message' => $validationError], 422);
|
||||
}
|
||||
}
|
||||
|
||||
$setting = Setting::updateOrCreate(
|
||||
['key' => $data['key']],
|
||||
@@ -67,6 +74,12 @@ class SettingController extends Controller
|
||||
$updated = [];
|
||||
|
||||
foreach ($data['settings'] as $entry) {
|
||||
if (($entry['key'] ?? '') === 'system.php_binary') {
|
||||
$validationError = $this->validatePhpBinarySetting($entry['value'] ?? '');
|
||||
if ($validationError !== null) {
|
||||
return response()->json(['message' => $validationError], 422);
|
||||
}
|
||||
}
|
||||
$setting = Setting::updateOrCreate(
|
||||
['key' => $entry['key']],
|
||||
['value' => $entry['value'] ?? '']
|
||||
@@ -80,4 +93,66 @@ class SettingController extends Controller
|
||||
|
||||
return response()->json($updated);
|
||||
}
|
||||
|
||||
public function validateSystemPhpBinary(Request $request): JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
if (!$user || !$user->roles()->where('name', 'ROLE_ADMIN')->exists()) {
|
||||
return response()->json(['message' => 'Forbidden'], 403);
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'value' => ['required', 'string'],
|
||||
]);
|
||||
|
||||
$validationError = $this->validatePhpBinarySetting($data['value']);
|
||||
if ($validationError !== null) {
|
||||
return response()->json(['message' => $validationError], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'PHP interpreter is valid.',
|
||||
]);
|
||||
}
|
||||
|
||||
private function validatePhpBinarySetting(string $value): ?string
|
||||
{
|
||||
$binary = trim($value);
|
||||
if ($binary === '' || $binary === 'php') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($binary === 'keyhelp-php-domain') {
|
||||
return '`keyhelp-php-domain` is disabled. Use a concrete binary (e.g. keyhelp-php84).';
|
||||
}
|
||||
|
||||
$resolved = null;
|
||||
if (str_contains($binary, '/')) {
|
||||
if (!is_executable($binary)) {
|
||||
return "Configured PHP binary '{$binary}' is not executable.";
|
||||
}
|
||||
$resolved = $binary;
|
||||
} else {
|
||||
$escapedBinary = escapeshellarg($binary);
|
||||
$process = new Process(['sh', '-lc', "command -v {$escapedBinary}"]);
|
||||
$process->setTimeout(5);
|
||||
$process->run();
|
||||
if (!$process->isSuccessful()) {
|
||||
return "Configured PHP binary '{$binary}' was not found in PATH.";
|
||||
}
|
||||
$resolved = trim($process->getOutput());
|
||||
if ($resolved === '') {
|
||||
return "Configured PHP binary '{$binary}' was not found in PATH.";
|
||||
}
|
||||
}
|
||||
|
||||
$phpCheck = new Process([$resolved, '-r', 'echo PHP_VERSION;']);
|
||||
$phpCheck->setTimeout(5);
|
||||
$phpCheck->run();
|
||||
if (!$phpCheck->isSuccessful() || trim($phpCheck->getOutput()) === '') {
|
||||
return "Configured binary '{$binary}' is not a working PHP CLI executable.";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,22 @@ class SystemStatusController extends Controller
|
||||
$phpDefaultPath = $this->resolveBinary('php');
|
||||
$phpDefaultVersion = $phpDefaultPath ? $this->resolvePhpVersion($phpDefaultPath) : null;
|
||||
$phpConfiguredPath = trim((string) Setting::where('key', 'system.php_binary')->value('value'));
|
||||
$phpSelectedPath = $phpConfiguredPath ?: (PHP_BINARY ?: $phpDefaultPath);
|
||||
$phpSelectedOk = (bool) $phpSelectedPath;
|
||||
$phpSelectedVersion = $phpSelectedPath
|
||||
? ($this->resolvePhpVersion($phpSelectedPath) ?? PHP_VERSION)
|
||||
: PHP_VERSION;
|
||||
$phpSelectedPath = null;
|
||||
$phpSelectedVersion = null;
|
||||
$phpSelectedOk = false;
|
||||
|
||||
if ($phpConfiguredPath !== '') {
|
||||
$resolvedConfiguredPhpPath = $this->resolveConfiguredPhpBinaryPath($phpConfiguredPath);
|
||||
$phpSelectedPath = $resolvedConfiguredPhpPath ?: $phpConfiguredPath;
|
||||
$phpSelectedVersion = $resolvedConfiguredPhpPath ? $this->resolvePhpVersion($resolvedConfiguredPhpPath) : null;
|
||||
$phpSelectedOk = $resolvedConfiguredPhpPath !== null && $phpSelectedVersion !== null;
|
||||
} else {
|
||||
$phpSelectedPath = PHP_BINARY ?: $phpDefaultPath;
|
||||
$phpSelectedVersion = $phpSelectedPath
|
||||
? ($this->resolvePhpVersion($phpSelectedPath) ?? $phpDefaultVersion ?? PHP_VERSION)
|
||||
: null;
|
||||
$phpSelectedOk = $phpSelectedPath !== null && $phpSelectedVersion !== null;
|
||||
}
|
||||
$minVersions = $this->resolveMinVersions();
|
||||
$composerPath = $this->resolveBinary('composer');
|
||||
$nodePath = $this->resolveBinary('node');
|
||||
@@ -44,6 +55,8 @@ class SystemStatusController extends Controller
|
||||
|
||||
return response()->json([
|
||||
'php' => PHP_VERSION,
|
||||
'php_web_path' => PHP_BINARY ?: null,
|
||||
'php_web_version' => PHP_VERSION ?: null,
|
||||
'php_default' => $phpDefaultPath,
|
||||
'php_default_version' => $phpDefaultVersion,
|
||||
'php_configured' => $phpConfiguredPath ?: null,
|
||||
@@ -82,7 +95,12 @@ class SystemStatusController extends Controller
|
||||
return false;
|
||||
}
|
||||
|
||||
$resolvedTarget = realpath(dirname($publicStorage) . DIRECTORY_SEPARATOR . $target);
|
||||
$targetPath = $target;
|
||||
if (!str_starts_with($target, DIRECTORY_SEPARATOR) && !preg_match('/^[A-Za-z]:[\\\\\\/]/', $target)) {
|
||||
$targetPath = dirname($publicStorage) . DIRECTORY_SEPARATOR . $target;
|
||||
}
|
||||
|
||||
$resolvedTarget = realpath($targetPath);
|
||||
$expectedTarget = realpath($storagePublic);
|
||||
|
||||
return $resolvedTarget !== false && $expectedTarget !== false && $resolvedTarget === $expectedTarget;
|
||||
@@ -116,6 +134,20 @@ class SystemStatusController extends Controller
|
||||
return $output !== '' ? $output : null;
|
||||
}
|
||||
|
||||
private function resolveConfiguredPhpBinaryPath(string $binary): ?string
|
||||
{
|
||||
$value = trim($binary);
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (str_contains($value, '/')) {
|
||||
return is_executable($value) ? $value : null;
|
||||
}
|
||||
|
||||
return $this->resolveBinary($value);
|
||||
}
|
||||
|
||||
private function resolveBinaryVersion(?string $path, array $args): ?string
|
||||
{
|
||||
if (!$path) {
|
||||
|
||||
Reference in New Issue
Block a user