2026-06-19 09:09:52 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
/**
|
2026-06-23 08:05:51 +02:00
|
|
|
* diagnose.php — Verifica e ripara installazione Glastree (Laravel 13)
|
2026-06-19 09:09:52 +02:00
|
|
|
*
|
|
|
|
|
* Esecuzione:
|
2026-06-23 08:05:51 +02:00
|
|
|
* php diagnose.php # solo diagnostica
|
|
|
|
|
* php diagnose.php --verbose # output dettagliato
|
|
|
|
|
* php diagnose.php --fix # diagnostica + tenta riparazione automatica
|
2026-06-19 09:09:52 +02:00
|
|
|
*
|
|
|
|
|
* Non richiede Laravel. Legge .env direttamente e testa DB via PDO.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
define('BASE_DIR', __DIR__);
|
2026-06-23 08:05:51 +02:00
|
|
|
define('WEB_USER', 'www-data');
|
|
|
|
|
|
2026-06-19 09:09:52 +02:00
|
|
|
const COLOR_GREEN = "\033[32m";
|
|
|
|
|
const COLOR_RED = "\033[31m";
|
|
|
|
|
const COLOR_YELLOW= "\033[33m";
|
|
|
|
|
const COLOR_CYAN = "\033[36m";
|
|
|
|
|
const COLOR_BOLD = "\033[1m";
|
|
|
|
|
const COLOR_RESET = "\033[0m";
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
$verbose = false;
|
|
|
|
|
$fixMode = false;
|
2026-06-19 09:09:52 +02:00
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
foreach ($argv ?? [] as $arg) {
|
|
|
|
|
if ($arg === '--verbose') { $verbose = true; }
|
|
|
|
|
if ($arg === '--fix') { $fixMode = true; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$results = ['passed' => 0, 'failed' => 0, 'warnings' => 0, 'fixed' => 0];
|
|
|
|
|
$groupsToFix = [];
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
// Helper functions
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
2026-06-19 09:09:52 +02:00
|
|
|
|
|
|
|
|
function ok(string $msg): void
|
|
|
|
|
{
|
|
|
|
|
global $results;
|
|
|
|
|
$results['passed']++;
|
|
|
|
|
echo " " . COLOR_GREEN . "✅" . COLOR_RESET . " {$msg}\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function fail(string $msg, ?string $detail = null): void
|
|
|
|
|
{
|
|
|
|
|
global $results;
|
|
|
|
|
$results['failed']++;
|
|
|
|
|
echo " " . COLOR_RED . "❌" . COLOR_RESET . " {$msg}";
|
2026-06-23 08:05:51 +02:00
|
|
|
if ($detail !== null) { echo COLOR_RED . " — {$detail}" . COLOR_RESET; }
|
2026-06-19 09:09:52 +02:00
|
|
|
echo "\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function warn(string $msg, ?string $detail = null): void
|
|
|
|
|
{
|
|
|
|
|
global $results;
|
|
|
|
|
$results['warnings']++;
|
|
|
|
|
echo " " . COLOR_YELLOW . "⚠️ " . COLOR_RESET . " {$msg}";
|
2026-06-23 08:05:51 +02:00
|
|
|
if ($detail !== null) { echo COLOR_YELLOW . " — {$detail}" . COLOR_RESET; }
|
2026-06-19 09:09:52 +02:00
|
|
|
echo "\n";
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
function fixed(string $msg): void
|
|
|
|
|
{
|
|
|
|
|
global $results;
|
|
|
|
|
$results['fixed']++;
|
|
|
|
|
echo " " . COLOR_GREEN . "🔧" . COLOR_RESET . " {$msg}\n";
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-19 09:09:52 +02:00
|
|
|
function heading(string $title): void
|
|
|
|
|
{
|
|
|
|
|
echo "\n" . COLOR_BOLD . COLOR_CYAN . "═══ {$title} ═══" . COLOR_RESET . "\n\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function v(string $msg): void
|
|
|
|
|
{
|
|
|
|
|
global $verbose;
|
2026-06-23 08:05:51 +02:00
|
|
|
if ($verbose) { echo " " . COLOR_YELLOW . "[verbose]" . COLOR_RESET . " {$msg}\n"; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function subheading(string $title): void
|
|
|
|
|
{
|
|
|
|
|
echo " " . COLOR_BOLD . "→ {$title}" . COLOR_RESET . "\n";
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
2026-06-23 08:05:51 +02:00
|
|
|
// Parse .env
|
2026-06-19 09:09:52 +02:00
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
function parseEnv(string $path): array
|
|
|
|
|
{
|
2026-06-23 08:05:51 +02:00
|
|
|
if (!file_exists($path)) { return []; }
|
2026-06-19 09:09:52 +02:00
|
|
|
$env = [];
|
|
|
|
|
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
|
|
|
|
foreach ($lines as $line) {
|
|
|
|
|
$line = trim($line);
|
2026-06-23 08:05:51 +02:00
|
|
|
if ($line === '' || str_starts_with($line, '#')) { continue; }
|
2026-06-19 09:09:52 +02:00
|
|
|
if (str_contains($line, '=')) {
|
|
|
|
|
$parts = explode('=', $line, 2);
|
|
|
|
|
$key = trim($parts[0]);
|
|
|
|
|
$value = trim($parts[1] ?? '');
|
|
|
|
|
if ((str_starts_with($value, '"') && str_ends_with($value, '"'))
|
|
|
|
|
|| (str_starts_with($value, "'") && str_ends_with($value, "'"))) {
|
|
|
|
|
$value = substr($value, 1, -1);
|
|
|
|
|
}
|
|
|
|
|
$value = preg_replace_callback('/\$\{(\w+)\}/', function ($m) use ($env) {
|
|
|
|
|
return $env[$m[1]] ?? $m[0];
|
|
|
|
|
}, $value);
|
|
|
|
|
$env[$key] = $value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $env;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
function saveEnv(string $path, array $env): bool
|
|
|
|
|
{
|
|
|
|
|
$out = '';
|
|
|
|
|
foreach ($env as $key => $value) {
|
|
|
|
|
$out .= "{$key}={$value}\n";
|
|
|
|
|
}
|
|
|
|
|
return file_put_contents($path, $out) !== false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function returnBytes(string $val): int
|
|
|
|
|
{
|
|
|
|
|
$val = trim($val);
|
|
|
|
|
$last = strtolower($val[-1]);
|
|
|
|
|
$num = (int) $val;
|
|
|
|
|
return match ($last) {
|
|
|
|
|
'g' => $num * 1024 * 1024 * 1024,
|
|
|
|
|
'm' => $num * 1024 * 1024,
|
|
|
|
|
'k' => $num * 1024,
|
|
|
|
|
default => $num,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatBytes(int|float $bytes): string
|
|
|
|
|
{
|
|
|
|
|
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
|
|
|
$i = 0;
|
|
|
|
|
while ($bytes >= 1024 && $i < count($units) - 1) { $bytes /= 1024; $i++; }
|
|
|
|
|
return round($bytes, 1) . ' ' . $units[$i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function sudoExec(string $cmd): array
|
|
|
|
|
{
|
|
|
|
|
$output = [];
|
|
|
|
|
$exitCode = 0;
|
|
|
|
|
exec($cmd, $output, $exitCode);
|
|
|
|
|
return ['output' => $output, 'exit' => $exitCode];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isRoot(): bool
|
|
|
|
|
{
|
|
|
|
|
return (function_exists('posix_getuid') && posix_getuid() === 0)
|
|
|
|
|
|| trim(shell_exec('whoami') ?? '') === 'root';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
// Fix runner
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
function runFixes(array $groupsToFix): void
|
|
|
|
|
{
|
|
|
|
|
if (empty($groupsToFix)) { return; }
|
|
|
|
|
|
|
|
|
|
echo "\n" . COLOR_BOLD . COLOR_GREEN . "═══ APPLICAZIONE FIX ═══" . COLOR_RESET . "\n";
|
|
|
|
|
|
|
|
|
|
$needsSudo = false;
|
|
|
|
|
|
|
|
|
|
// 1. Create missing directories
|
|
|
|
|
$dirsToCreate = $groupsToFix['mkdir'] ?? [];
|
|
|
|
|
if (!empty($dirsToCreate)) {
|
|
|
|
|
subheading("Creazione directory mancanti");
|
|
|
|
|
foreach ($dirsToCreate as $dir) {
|
|
|
|
|
if (mkdir($dir, 0775, true)) {
|
|
|
|
|
fixed("Creata directory: " . str_replace(BASE_DIR . '/', '', $dir));
|
|
|
|
|
} else {
|
|
|
|
|
fail("Impossibile creare directory: " . str_replace(BASE_DIR . '/', '', $dir));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Fix permissions
|
|
|
|
|
$dirsToPerm = $groupsToFix['chmod'] ?? [];
|
|
|
|
|
if (!empty($dirsToPerm)) {
|
|
|
|
|
subheading("Permessi directory");
|
|
|
|
|
foreach ($dirsToPerm as $dir) {
|
|
|
|
|
if (chmod($dir, 0775)) {
|
|
|
|
|
fixed("chmod 775: " . str_replace(BASE_DIR . '/', '', $dir));
|
|
|
|
|
} else {
|
|
|
|
|
fail("chmod fallito: " . str_replace(BASE_DIR . '/', '', $dir));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. Chown
|
|
|
|
|
$dirsToChown = $groupsToFix['chown'] ?? [];
|
|
|
|
|
if (!empty($dirsToChown)) {
|
|
|
|
|
subheading("Proprietà directory");
|
|
|
|
|
$chownCmd = 'chown -R ' . WEB_USER . ':' . WEB_USER . ' ' . implode(' ', array_map('escapeshellarg', $dirsToChown));
|
|
|
|
|
v("Esecuzione: {$chownCmd}");
|
|
|
|
|
$result = sudoExec($chownCmd);
|
|
|
|
|
if ($result['exit'] === 0) {
|
|
|
|
|
fixed("Proprietà impostata a " . WEB_USER . ":" . WEB_USER);
|
|
|
|
|
} else {
|
|
|
|
|
$needsSudo = true;
|
|
|
|
|
warn("chown fallito (forse serve sudo): " . implode(', ', $result['output']));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. Symlink
|
|
|
|
|
if (($groupsToFix['symlink'] ?? false) === true) {
|
|
|
|
|
$target = BASE_DIR . '/public/storage';
|
|
|
|
|
if (file_exists($target) || is_link($target)) {
|
|
|
|
|
unlink($target);
|
|
|
|
|
}
|
|
|
|
|
$result = sudoExec('ln -sf ../storage/app/public ' . escapeshellarg($target));
|
|
|
|
|
if ($result['exit'] === 0) {
|
|
|
|
|
fixed("Symlink ricreato: public/storage → ../storage/app/public");
|
|
|
|
|
} else {
|
|
|
|
|
$needsSudo = true;
|
|
|
|
|
warn("Symlink fallito (forse serve sudo)");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. .gitignore in storage/app/public
|
|
|
|
|
if (($groupsToFix['gitignore'] ?? false) === true) {
|
|
|
|
|
$gitignore = BASE_DIR . '/storage/app/public/.gitignore';
|
|
|
|
|
if (file_put_contents($gitignore, "*\n!.gitignore\n") !== false) {
|
|
|
|
|
fixed("Creato .gitignore in storage/app/public");
|
|
|
|
|
} else {
|
|
|
|
|
fail("Impossibile creare .gitignore in storage/app/public");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 6. .env variables
|
|
|
|
|
$envToFix = $groupsToFix['env'] ?? [];
|
|
|
|
|
if (!empty($envToFix)) {
|
|
|
|
|
$envPath = BASE_DIR . '/.env';
|
|
|
|
|
$env = parseEnv($envPath);
|
|
|
|
|
subheading("Variabili .env");
|
|
|
|
|
$changed = false;
|
|
|
|
|
foreach ($envToFix as $key => $value) {
|
|
|
|
|
if (!isset($env[$key])) {
|
|
|
|
|
$env[$key] = $value;
|
|
|
|
|
$changed = true;
|
|
|
|
|
fixed("Aggiunta {$key}={$value}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ($changed) {
|
|
|
|
|
$out = '';
|
|
|
|
|
foreach ($env as $k => $v) { $out .= "{$k}={$v}\n"; }
|
|
|
|
|
file_put_contents($envPath, $out);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 7. Clear cache dirs
|
|
|
|
|
$cacheDirs = $groupsToFix['clearcache'] ?? [];
|
|
|
|
|
if (!empty($cacheDirs)) {
|
|
|
|
|
subheading("Pulizia cache");
|
|
|
|
|
foreach ($cacheDirs as $dir) {
|
|
|
|
|
array_map('unlink', glob($dir . '/*') ?: []);
|
|
|
|
|
fixed("Svuotata: " . str_replace(BASE_DIR . '/', '', $dir));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($needsSudo) {
|
|
|
|
|
echo "\n " . COLOR_YELLOW . "⚠️" . COLOR_RESET . " Alcune operazioni richiedono sudo. Esegui:\n";
|
|
|
|
|
echo " sudo php " . $_SERVER['PHP_SELF'] . " --fix\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
echo "\n" . COLOR_GREEN . "═══ FIX COMPLETATI ═══" . COLOR_RESET . "\n";
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-19 09:09:52 +02:00
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
2026-06-23 08:05:51 +02:00
|
|
|
// Banner
|
2026-06-19 09:09:52 +02:00
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
echo COLOR_BOLD . "\n"
|
|
|
|
|
. " ╔════════════════════════════════════════════╗\n"
|
|
|
|
|
. " ║ Glastree — Diagnosi Installazione ║\n"
|
|
|
|
|
. " ╚════════════════════════════════════════════╝\n"
|
|
|
|
|
. COLOR_RESET;
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
echo "\n Data: " . date('Y-m-d H:i:s') . " | PID: " . getmypid();
|
|
|
|
|
if ($fixMode) { echo " | " . COLOR_GREEN . "MODALITÀ FIX ATTIVA" . COLOR_RESET; }
|
|
|
|
|
echo "\n\n";
|
2026-06-19 09:09:52 +02:00
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
// 3.1 PHP Version & Extensions
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
heading('PHP Version & Extensions');
|
|
|
|
|
|
|
|
|
|
$phpVersion = PHP_VERSION;
|
|
|
|
|
if (version_compare($phpVersion, '8.2', '>=')) {
|
|
|
|
|
ok("PHP version: {$phpVersion}");
|
|
|
|
|
} else {
|
|
|
|
|
fail("PHP version: {$phpVersion} — richiesta ≥ 8.2");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$requiredExts = [
|
|
|
|
|
'json', 'mbstring', 'openssl', 'PDO', 'pdo_mysql', 'fileinfo',
|
|
|
|
|
'xml', 'ctype', 'bcmath', 'zip', 'gd', 'intl', 'curl', 'session',
|
|
|
|
|
];
|
|
|
|
|
$optionalExts = [
|
|
|
|
|
'imap' => 'Sync email IMAP',
|
|
|
|
|
'imagick' => 'Gestione immagini avanzata',
|
|
|
|
|
'redis' => 'Cache/sessione Redis',
|
|
|
|
|
'sodium' => 'Crittografia (Laravel 11+)',
|
|
|
|
|
'sockets' => 'Connessioni socket',
|
|
|
|
|
'tokenizer' => 'Laravel optimization',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
foreach ($requiredExts as $ext) {
|
2026-06-23 08:05:51 +02:00
|
|
|
extension_loaded($ext) ? ok("Estensione {$ext}: OK") : fail("Estensione {$ext}: NON TROVATA", "Richiesta da Laravel");
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
foreach ($optionalExts as $ext => $label) {
|
2026-06-23 08:05:51 +02:00
|
|
|
extension_loaded($ext) ? ok("Estensione {$ext}: OK ({$label})") : warn("Estensione {$ext}: NON TROVATA ({$label})");
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
// INI settings to check
|
|
|
|
|
$iniSettings = [
|
2026-06-19 09:09:52 +02:00
|
|
|
'max_execution_time' => [30, '60+ consigliato per import CSV'],
|
2026-06-23 08:05:51 +02:00
|
|
|
'memory_limit' => ['128M', '128M minimo, 256M+ consigliato'],
|
2026-06-19 09:09:52 +02:00
|
|
|
'upload_max_filesize' => ['20M', '20M minimo per CSV/ICS'],
|
2026-06-23 08:05:51 +02:00
|
|
|
'post_max_size' => ['20M', '20M minimo per upload'],
|
|
|
|
|
'max_input_vars' => [1000, '1000+ per form con molti campi'],
|
|
|
|
|
'max_input_time' => [60, '60+ per upload file'],
|
2026-06-19 09:09:52 +02:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
heading('PHP INI Settings');
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
$phpIniPath = php_ini_loaded_file();
|
|
|
|
|
$phpIniDir = PHP_CONFIG_FILE_SCAN_DIR;
|
|
|
|
|
$sapi = PHP_SAPI;
|
2026-06-19 09:09:52 +02:00
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
if ($phpIniPath) {
|
|
|
|
|
ok("php.ini caricato (SAPI: {$sapi}): {$phpIniPath}");
|
|
|
|
|
} else {
|
|
|
|
|
warn("php.ini: nessun file caricato (SAPI: {$sapi})");
|
|
|
|
|
}
|
|
|
|
|
v("Directory scan config: " . ($phpIniDir ?: 'nessuna'));
|
|
|
|
|
|
|
|
|
|
// Detect web server php.ini (common paths)
|
|
|
|
|
$webIniPath = null;
|
|
|
|
|
if ($sapi === 'cli') {
|
|
|
|
|
$phpMajorMinor = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
|
|
|
|
|
foreach (['apache2', 'fpm', 'cgi'] as $sapiDir) {
|
|
|
|
|
$p = "/etc/php/{$phpMajorMinor}/{$sapiDir}/php.ini";
|
|
|
|
|
if (file_exists($p)) { $webIniPath = $p; break; }
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
2026-06-23 08:05:51 +02:00
|
|
|
if ($webIniPath) {
|
|
|
|
|
ok("WEB php.ini rilevato: {$webIniPath}");
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-19 09:09:52 +02:00
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
echo "\n " . COLOR_BOLD . "Valori SAPI corrente ({$sapi}):" . COLOR_RESET . "\n";
|
|
|
|
|
|
|
|
|
|
foreach ($iniSettings as $iniKey => [$minVal, $note]) {
|
|
|
|
|
$actual = ini_get($iniKey);
|
|
|
|
|
$isOk = false;
|
|
|
|
|
if (is_numeric($minVal) && is_numeric($actual)) {
|
|
|
|
|
$isOk = (int) $actual >= (int) $minVal;
|
|
|
|
|
} elseif (is_string($minVal)) {
|
|
|
|
|
$isOk = returnBytes($actual) >= returnBytes($minVal);
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
2026-06-23 08:05:51 +02:00
|
|
|
$isOk ? ok("{$iniKey} = {$actual}") : warn("{$iniKey} = {$actual} (minimo: {$minVal})", $note);
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
// Show web server values if different php.ini exists
|
|
|
|
|
if ($webIniPath && $sapi === 'cli') {
|
|
|
|
|
echo "\n " . COLOR_BOLD . "Valori WEB server (da {$webIniPath}):" . COLOR_RESET . "\n";
|
|
|
|
|
$webRaw = file_get_contents($webIniPath);
|
|
|
|
|
foreach ($iniSettings as $iniKey => [$minVal, $note]) {
|
|
|
|
|
$cliVal = ini_get($iniKey);
|
|
|
|
|
$webVal = null;
|
|
|
|
|
if (preg_match('/^' . preg_quote($iniKey, '/') . '\s*=\s*(.+)$/m', $webRaw, $m)) {
|
|
|
|
|
$webVal = trim($m[1]);
|
|
|
|
|
}
|
|
|
|
|
if ($webVal !== null && $webVal !== $cliVal) {
|
|
|
|
|
warn("{$iniKey} = {$webVal} (CLI: {$cliVal})", "DIVERGENTE da CLI");
|
|
|
|
|
} elseif ($webVal !== null) {
|
|
|
|
|
ok("{$iniKey} = {$webVal}");
|
|
|
|
|
} else {
|
|
|
|
|
ok("{$iniKey} = [non impostato, usa default]");
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
// 3.2 .env File
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
heading('File .env');
|
|
|
|
|
|
|
|
|
|
$envPath = BASE_DIR . '/.env';
|
|
|
|
|
if (!file_exists($envPath)) {
|
|
|
|
|
fail('.env NON TROVATO', "Cercato in: {$envPath}");
|
|
|
|
|
$env = [];
|
|
|
|
|
} elseif (!is_readable($envPath)) {
|
|
|
|
|
fail('.env NON LEGGIBILE', "Permessi: " . decoct(fileperms($envPath) & 0777));
|
|
|
|
|
$env = [];
|
|
|
|
|
} else {
|
|
|
|
|
ok(".env trovato e leggibile");
|
|
|
|
|
$env = parseEnv($envPath);
|
|
|
|
|
v("Variabili parsate: " . count($env));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
// 3.3 Configurazione .env
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
heading('Configurazione .env');
|
|
|
|
|
|
|
|
|
|
// APP_KEY
|
|
|
|
|
if (empty($env['APP_KEY'])) {
|
|
|
|
|
fail('APP_KEY non impostato');
|
|
|
|
|
} elseif (!str_starts_with($env['APP_KEY'], 'base64:')) {
|
|
|
|
|
fail('APP_KEY formato errato', "Deve iniziare con 'base64:'");
|
|
|
|
|
} else {
|
|
|
|
|
$decoded = base64_decode(substr($env['APP_KEY'], 7), true);
|
2026-06-23 08:05:51 +02:00
|
|
|
($decoded !== false && strlen($decoded) === 32)
|
|
|
|
|
? ok("APP_KEY: valido (32 bytes)")
|
|
|
|
|
: fail('APP_KEY: decodifica fallita o lunghezza errata', "Deve essere 32 bytes dopo decodifica base64");
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
// APP_URL — test duale HTTP/HTTPS
|
|
|
|
|
heading('Test HTTP/HTTPS (APP_URL)');
|
|
|
|
|
|
|
|
|
|
$baseUrl = $env['APP_URL'] ?? '';
|
|
|
|
|
if (empty($baseUrl)) {
|
2026-06-19 09:09:52 +02:00
|
|
|
fail('APP_URL non impostato');
|
2026-06-23 08:05:51 +02:00
|
|
|
} elseif (!str_starts_with($baseUrl, 'http://') && !str_starts_with($baseUrl, 'https://')) {
|
|
|
|
|
fail('APP_URL non valido', $baseUrl);
|
2026-06-19 09:09:52 +02:00
|
|
|
} else {
|
2026-06-23 08:05:51 +02:00
|
|
|
$httpsUrl = $baseUrl;
|
|
|
|
|
$httpUrl = str_replace('https://', 'http://', $baseUrl);
|
|
|
|
|
$hasHttps = str_starts_with($baseUrl, 'https://');
|
|
|
|
|
|
|
|
|
|
ok("APP_URL (HTTPS): {$httpsUrl}");
|
|
|
|
|
if ($hasHttps) {
|
|
|
|
|
ok("APP_URL (HTTP): {$httpUrl}");
|
|
|
|
|
}
|
2026-06-19 09:09:52 +02:00
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
$ch = curl_init();
|
2026-06-19 09:09:52 +02:00
|
|
|
curl_setopt_array($ch, [
|
|
|
|
|
CURLOPT_TIMEOUT => 10,
|
|
|
|
|
CURLOPT_CONNECTTIMEOUT => 5,
|
|
|
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
|
|
|
CURLOPT_FOLLOWLOCATION => true,
|
|
|
|
|
CURLOPT_SSL_VERIFYPEER => false,
|
|
|
|
|
CURLOPT_SSL_VERIFYHOST => 0,
|
|
|
|
|
CURLOPT_NOBODY => true,
|
|
|
|
|
]);
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
$httpsWorks = false;
|
|
|
|
|
$httpWorks = false;
|
|
|
|
|
$httpsCode = 0;
|
|
|
|
|
$httpCode = 0;
|
|
|
|
|
|
|
|
|
|
// Test HTTPS
|
|
|
|
|
if ($hasHttps) {
|
|
|
|
|
$chH = curl_copy_handle($ch);
|
|
|
|
|
curl_setopt($chH, CURLOPT_URL, $httpsUrl);
|
|
|
|
|
$resp = curl_exec($chH);
|
|
|
|
|
$httpsWorks = $resp !== false;
|
|
|
|
|
$httpsCode = curl_getinfo($chH, CURLINFO_HTTP_CODE);
|
|
|
|
|
$httpsErr = curl_error($chH);
|
|
|
|
|
curl_close($chH);
|
|
|
|
|
$httpsWorks ? ok("HTTPS raggiungibile (HTTP {$httpsCode})") : fail("HTTPS non raggiungibile", $httpsErr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test HTTP
|
|
|
|
|
$chH2 = curl_copy_handle($ch);
|
|
|
|
|
curl_setopt($chH2, CURLOPT_URL, $httpUrl);
|
|
|
|
|
$resp2 = curl_exec($chH2);
|
|
|
|
|
$httpWorks = $resp2 !== false;
|
|
|
|
|
$httpCode = curl_getinfo($chH2, CURLINFO_HTTP_CODE);
|
|
|
|
|
$httpErr = curl_error($chH2);
|
|
|
|
|
curl_close($chH2);
|
|
|
|
|
$httpWorks ? ok("HTTP raggiungibile (HTTP {$httpCode})") : fail("HTTP non raggiungibile", $httpErr);
|
|
|
|
|
|
|
|
|
|
if ($hasHttps && !$httpsWorks && !$httpWorks) {
|
|
|
|
|
fail("Nessun protocollo raggiungibile", "Verifica DNS, firewall o configurazione server");
|
|
|
|
|
} elseif ($hasHttps && !$httpsWorks && $httpWorks) {
|
|
|
|
|
warn("Solo HTTP funziona — APP_URL punta a HTTPS. Imposta APP_URL=http://...");
|
|
|
|
|
} elseif ($hasHttps && $httpsWorks && !$httpWorks) {
|
|
|
|
|
ok("Solo HTTPS disponibile (configurazione normale)");
|
|
|
|
|
} elseif (!$hasHttps && $httpWorks) {
|
|
|
|
|
ok("HTTP disponibile (nessun HTTPS configurato)");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// APP_URL host mismatch check
|
|
|
|
|
$urlHost = parse_url($baseUrl, PHP_URL_HOST);
|
|
|
|
|
$serverHost = gethostname();
|
|
|
|
|
if ($urlHost && $serverHost && $urlHost !== $serverHost) {
|
|
|
|
|
warn("APP_URL host ({$urlHost}) ≠ server hostname ({$serverHost})",
|
|
|
|
|
"Se accedi con un dominio/IP diverso da APP_URL, il token CSRF può fallire");
|
|
|
|
|
}
|
|
|
|
|
// Also check if APP_URL host resolves to this server
|
|
|
|
|
if ($urlHost) {
|
|
|
|
|
$resolvedIps = gethostbynamel($urlHost);
|
|
|
|
|
if ($resolvedIps !== false) {
|
|
|
|
|
ok("APP_URL host ({$urlHost}) → IP: " . implode(', ', $resolvedIps));
|
2026-06-19 09:09:52 +02:00
|
|
|
} else {
|
2026-06-23 08:05:51 +02:00
|
|
|
warn("APP_URL host ({$urlHost}) NON risolvibile via DNS");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check cached config
|
|
|
|
|
$cachedConfig = BASE_DIR . '/bootstrap/cache/config.php';
|
|
|
|
|
if (file_exists($cachedConfig)) {
|
|
|
|
|
warn("Config CACHED in bootstrap/cache/config.php",
|
|
|
|
|
"Le modifiche a .env NON sono attive finché non esegui: php artisan config:clear");
|
|
|
|
|
// Peek at cached app.url
|
|
|
|
|
$cachedContent = file_get_contents($cachedConfig);
|
|
|
|
|
if (preg_match("/'url' => '([^']+)'/", $cachedContent, $m)) {
|
|
|
|
|
$cachedUrl = $m[1];
|
|
|
|
|
if ($cachedUrl !== $baseUrl) {
|
|
|
|
|
fail("APP_URL nel config cache differisce da .env",
|
|
|
|
|
"Cache: {$cachedUrl} — .env: {$baseUrl}. Esegui php artisan config:clear");
|
|
|
|
|
} else {
|
|
|
|
|
ok("APP_URL in config cache coincide con .env: {$cachedUrl}");
|
|
|
|
|
}
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
2026-06-23 08:05:51 +02:00
|
|
|
} else {
|
|
|
|
|
ok("Config cache: assente (config non cached, valori da .env live)");
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FORCE_HTTPS vs SESSION_SECURE_COOKIE
|
|
|
|
|
$forceHttps = ($env['FORCE_HTTPS'] ?? 'false') === 'true';
|
|
|
|
|
$sessionSecure = ($env['SESSION_SECURE_COOKIE'] ?? 'false') === 'true';
|
|
|
|
|
|
|
|
|
|
if ($forceHttps && !$sessionSecure) {
|
|
|
|
|
warn('FORCE_HTTPS=true ma SESSION_SECURE_COOKIE non impostato',
|
2026-06-23 08:05:51 +02:00
|
|
|
'Il cookie di sessione non verrà inviato su HTTP');
|
|
|
|
|
if ($fixMode) {
|
|
|
|
|
$groupsToFix['env']['SESSION_SECURE_COOKIE'] = 'true';
|
|
|
|
|
}
|
2026-06-19 09:09:52 +02:00
|
|
|
} elseif (!$forceHttps && $sessionSecure) {
|
|
|
|
|
warn('SESSION_SECURE_COOKIE=true ma FORCE_HTTPS non attivo',
|
|
|
|
|
'Il cookie di sessione non verrà inviato su HTTP');
|
|
|
|
|
} else {
|
|
|
|
|
ok("FORCE_HTTPS / SESSION_SECURE_COOKIE: coerente");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SESSION_DRIVER
|
|
|
|
|
$sessionDriver = $env['SESSION_DRIVER'] ?? 'file';
|
2026-06-23 08:05:51 +02:00
|
|
|
in_array($sessionDriver, ['file', 'database', 'redis', 'memcached', 'cookie', 'dynamodb'])
|
|
|
|
|
? ok("SESSION_DRIVER: {$sessionDriver}")
|
|
|
|
|
: fail("SESSION_DRIVER: {$sessionDriver} sconosciuto");
|
2026-06-19 09:09:52 +02:00
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
// SESSION_DOMAIN
|
2026-06-19 09:09:52 +02:00
|
|
|
if (!empty($env['SESSION_DOMAIN']) && $env['SESSION_DOMAIN'] !== 'null') {
|
|
|
|
|
$parsedUrl = parse_url($env['APP_URL'] ?? 'http://localhost');
|
|
|
|
|
$urlHost = $parsedUrl['host'] ?? 'localhost';
|
2026-06-23 08:05:51 +02:00
|
|
|
str_contains($env['SESSION_DOMAIN'], $urlHost)
|
|
|
|
|
? ok("SESSION_DOMAIN: {$env['SESSION_DOMAIN']}")
|
|
|
|
|
: warn("SESSION_DOMAIN ({$env['SESSION_DOMAIN']}) non corrisponde a APP_URL ({$urlHost})");
|
2026-06-19 09:09:52 +02:00
|
|
|
} else {
|
|
|
|
|
ok("SESSION_DOMAIN: null (usa dominio richiesta)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DB_CONNECTION
|
|
|
|
|
$dbConnection = $env['DB_CONNECTION'] ?? 'mysql';
|
2026-06-23 08:05:51 +02:00
|
|
|
in_array($dbConnection, ['mysql', 'mariadb', 'sqlite', 'pgsql', 'sqlsrv'])
|
|
|
|
|
? ok("DB_CONNECTION: {$dbConnection}")
|
|
|
|
|
: fail("DB_CONNECTION: {$dbConnection} sconosciuto");
|
2026-06-19 09:09:52 +02:00
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
// APP_DEBUG
|
2026-06-19 09:09:52 +02:00
|
|
|
$appEnv = $env['APP_ENV'] ?? 'production';
|
|
|
|
|
$appDebug = ($env['APP_DEBUG'] ?? 'false') === 'true';
|
|
|
|
|
if ($appEnv === 'production' && $appDebug) {
|
2026-06-23 08:05:51 +02:00
|
|
|
warn('APP_DEBUG=true in ambiente production', 'Disabilita in produzione: espone errori sensibili');
|
|
|
|
|
if ($fixMode) {
|
|
|
|
|
$groupsToFix['env']['APP_DEBUG'] = 'false';
|
|
|
|
|
}
|
2026-06-19 09:09:52 +02:00
|
|
|
} else {
|
|
|
|
|
ok("APP_DEBUG: " . ($appDebug ? 'true' : 'false') . " in ambiente {$appEnv}");
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
// Ensure SESSION_SAME_SITE exists
|
|
|
|
|
if (!isset($env['SESSION_SAME_SITE'])) {
|
|
|
|
|
warn('SESSION_SAME_SITE non impostato (default: lax)');
|
|
|
|
|
if ($fixMode) {
|
|
|
|
|
$groupsToFix['env']['SESSION_SAME_SITE'] = 'lax';
|
|
|
|
|
v("Aggiunto SESSION_SAME_SITE=lax in attesa fix");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!isset($env['SESSION_SECURE_COOKIE'])) {
|
|
|
|
|
warn('SESSION_SECURE_COOKIE non impostato');
|
|
|
|
|
if ($fixMode && !isset($groupsToFix['env']['SESSION_SECURE_COOKIE'])) {
|
|
|
|
|
$groupsToFix['env']['SESSION_SECURE_COOKIE'] = 'false';
|
|
|
|
|
v("Aggiunto SESSION_SECURE_COOKIE=false in attesa fix");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!isset($env['FORCE_HTTPS'])) {
|
|
|
|
|
warn('FORCE_HTTPS non impostato');
|
|
|
|
|
if ($fixMode) {
|
|
|
|
|
$groupsToFix['env']['FORCE_HTTPS'] = 'false';
|
|
|
|
|
v("Aggiunto FORCE_HTTPS=false in attesa fix");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!isset($env['TRUSTED_PROXIES'])) {
|
|
|
|
|
warn('TRUSTED_PROXIES non impostato');
|
|
|
|
|
if ($fixMode) {
|
|
|
|
|
$groupsToFix['env']['TRUSTED_PROXIES'] = '"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1"';
|
|
|
|
|
v("Aggiunto TRUSTED_PROXIES in attesa fix");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-19 09:09:52 +02:00
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
// 3.4 Directory Permissions
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
heading('Directory & Permessi');
|
|
|
|
|
|
|
|
|
|
$writeDirs = [
|
|
|
|
|
'storage' => BASE_DIR . '/storage',
|
|
|
|
|
'storage/logs' => BASE_DIR . '/storage/logs',
|
|
|
|
|
'storage/framework' => BASE_DIR . '/storage/framework',
|
|
|
|
|
'storage/framework/sessions' => BASE_DIR . '/storage/framework/sessions',
|
|
|
|
|
'storage/framework/views' => BASE_DIR . '/storage/framework/views',
|
|
|
|
|
'storage/framework/cache' => BASE_DIR . '/storage/framework/cache',
|
|
|
|
|
'storage/framework/cache/data' => BASE_DIR . '/storage/framework/cache/data',
|
|
|
|
|
'storage/app/public' => BASE_DIR . '/storage/app/public',
|
|
|
|
|
'storage/app/public/logos' => BASE_DIR . '/storage/app/public/logos',
|
|
|
|
|
'storage/app/public/documenti/eventi' => BASE_DIR . '/storage/app/public/documenti/eventi',
|
|
|
|
|
'storage/app/private' => BASE_DIR . '/storage/app/private',
|
|
|
|
|
'bootstrap/cache' => BASE_DIR . '/bootstrap/cache',
|
|
|
|
|
];
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
$missingDirs = [];
|
|
|
|
|
$unwritableDirs = [];
|
|
|
|
|
$allDirsOk = true;
|
|
|
|
|
|
2026-06-19 09:09:52 +02:00
|
|
|
foreach ($writeDirs as $label => $dir) {
|
|
|
|
|
if (!file_exists($dir)) {
|
|
|
|
|
warn("{$label}: directory NON ESISTE");
|
2026-06-23 08:05:51 +02:00
|
|
|
$missingDirs[] = $dir;
|
|
|
|
|
$allDirsOk = false;
|
2026-06-19 09:09:52 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!is_dir($dir)) {
|
|
|
|
|
fail("{$label}: NON è una directory");
|
2026-06-23 08:05:51 +02:00
|
|
|
$allDirsOk = false;
|
2026-06-19 09:09:52 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$perms = decoct(fileperms($dir) & 0777);
|
2026-06-23 08:05:51 +02:00
|
|
|
$owner = function_exists('posix_getpwuid') ? (posix_getpwuid(fileowner($dir))['name'] ?? (string) fileowner($dir)) : (string) fileowner($dir);
|
2026-06-19 09:09:52 +02:00
|
|
|
if (is_writable($dir)) {
|
|
|
|
|
ok("{$label}: {$perms} proprietario: {$owner} — SCRIVIBILE");
|
2026-06-23 08:05:51 +02:00
|
|
|
} elseif ($owner === WEB_USER && in_array($perms, ['775', '777', '755', '770'])) {
|
|
|
|
|
warn("{$label}: {$perms} proprietario: {$owner} — NON scrivibile da CLI ma WEB server sì");
|
2026-06-19 09:09:52 +02:00
|
|
|
} else {
|
2026-06-23 08:05:51 +02:00
|
|
|
fail("{$label}: {$perms} proprietario: {$owner} — NON SCRIVIBILE");
|
|
|
|
|
$unwritableDirs[] = $dir;
|
|
|
|
|
$allDirsOk = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$allDirsOk) {
|
|
|
|
|
if ($fixMode) {
|
|
|
|
|
if (!empty($missingDirs)) {
|
|
|
|
|
$groupsToFix['mkdir'] = ($groupsToFix['mkdir'] ?? []);
|
|
|
|
|
foreach ($missingDirs as $d) {
|
|
|
|
|
if (!in_array($d, $groupsToFix['mkdir'])) { $groupsToFix['mkdir'][] = $d; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!empty($unwritableDirs)) {
|
|
|
|
|
$groupsToFix['chmod'] = ($groupsToFix['chmod'] ?? []);
|
|
|
|
|
foreach ($unwritableDirs as $d) {
|
|
|
|
|
if (!in_array($d, $groupsToFix['chmod'])) { $groupsToFix['chmod'][] = $d; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$chownDirs = array_unique(array_merge(
|
|
|
|
|
[BASE_DIR . '/storage', BASE_DIR . '/bootstrap/cache'],
|
|
|
|
|
$missingDirs,
|
|
|
|
|
$unwritableDirs
|
|
|
|
|
));
|
|
|
|
|
$groupsToFix['chown'] = $chownDirs;
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
// 3.5 Symlink Check
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
heading('Symlink');
|
|
|
|
|
|
|
|
|
|
$publicStorage = BASE_DIR . '/public/storage';
|
2026-06-23 08:05:51 +02:00
|
|
|
$symlinkOk = true;
|
2026-06-19 09:09:52 +02:00
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
if (!file_exists($publicStorage) && !is_link($publicStorage)) {
|
2026-06-19 09:09:52 +02:00
|
|
|
fail("public/storage: NON ESISTE", "Esegui: php artisan storage:link");
|
2026-06-23 08:05:51 +02:00
|
|
|
$symlinkOk = false;
|
2026-06-19 09:09:52 +02:00
|
|
|
} elseif (!is_link($publicStorage)) {
|
|
|
|
|
fail("public/storage: NON è un symlink (è una directory/file)");
|
2026-06-23 08:05:51 +02:00
|
|
|
$symlinkOk = false;
|
2026-06-19 09:09:52 +02:00
|
|
|
} else {
|
|
|
|
|
$readTarget = readlink($publicStorage);
|
|
|
|
|
$expected = realpath(BASE_DIR . '/storage/app/public');
|
|
|
|
|
$resolved = str_starts_with($readTarget, '/')
|
|
|
|
|
? realpath($readTarget)
|
|
|
|
|
: realpath(dirname($publicStorage) . '/' . $readTarget);
|
|
|
|
|
|
|
|
|
|
if ($resolved === $expected) {
|
|
|
|
|
$displayTarget = str_starts_with($readTarget, '/')
|
|
|
|
|
? str_replace(BASE_DIR . '/', '', $readTarget)
|
|
|
|
|
: $readTarget;
|
|
|
|
|
ok("public/storage → {$displayTarget} (puntamento corretto)");
|
|
|
|
|
} else {
|
|
|
|
|
fail("public/storage → {$readTarget}", "Dovrebbe puntare a: " . str_replace(BASE_DIR . '/', '', $expected));
|
2026-06-23 08:05:51 +02:00
|
|
|
$symlinkOk = false;
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
if (!$symlinkOk && $fixMode) {
|
|
|
|
|
$groupsToFix['symlink'] = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// .gitignore in storage/app/public
|
|
|
|
|
$gitignorePath = BASE_DIR . '/storage/app/public/.gitignore';
|
|
|
|
|
if (!file_exists($gitignorePath)) {
|
|
|
|
|
warn('.gitignore mancante in storage/app/public (serve a Laravel)');
|
|
|
|
|
if ($fixMode) { $groupsToFix['gitignore'] = true; }
|
|
|
|
|
} else {
|
|
|
|
|
ok(".gitignore presente in storage/app/public");
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-19 09:09:52 +02:00
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
// 3.6 Session Test
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
heading('Sessione — Test scrittura/lettura');
|
|
|
|
|
|
|
|
|
|
$sessionDir = BASE_DIR . '/storage/framework/sessions';
|
2026-06-23 08:05:51 +02:00
|
|
|
if (!is_dir($sessionDir)) {
|
|
|
|
|
fail("Directory session NON ESISTE: {$sessionDir}");
|
|
|
|
|
} elseif (!is_writable($sessionDir)) {
|
2026-06-19 09:09:52 +02:00
|
|
|
fail("Directory session NON scrivibile — test saltato");
|
|
|
|
|
} else {
|
2026-06-23 08:05:51 +02:00
|
|
|
// Show session files count and first files with permissions
|
|
|
|
|
$sessionFiles = glob($sessionDir . '/*');
|
|
|
|
|
$fileCount = count($sessionFiles);
|
|
|
|
|
if ($fileCount > 0) {
|
|
|
|
|
$exampleFiles = array_slice($sessionFiles, 0, 3);
|
|
|
|
|
$fileInfo = [];
|
|
|
|
|
foreach ($exampleFiles as $f) {
|
|
|
|
|
$fileInfo[] = basename($f) . ' (' . decoct(fileperms($f) & 0777) . ')';
|
|
|
|
|
}
|
|
|
|
|
ok("Session files presenti: {$fileCount} (esempi: " . implode(', ', $fileInfo) . "...)");
|
|
|
|
|
} else {
|
|
|
|
|
ok("Session files: nessuno (normale su installazione pulita)");
|
|
|
|
|
}
|
2026-06-19 09:09:52 +02:00
|
|
|
$testFile = $sessionDir . '/.diagnose_test_' . getmypid();
|
|
|
|
|
$testVal = 'diagnose_' . bin2hex(random_bytes(8));
|
|
|
|
|
$written = file_put_contents($testFile, $testVal);
|
|
|
|
|
if ($written === false) {
|
|
|
|
|
fail("Scrittura file sessione: FALLITA", $testFile);
|
|
|
|
|
} else {
|
|
|
|
|
$read = file_get_contents($testFile);
|
|
|
|
|
unlink($testFile);
|
2026-06-23 08:05:51 +02:00
|
|
|
($read === $testVal)
|
|
|
|
|
? ok("Scrittura/lettura file sessione: OK")
|
|
|
|
|
: fail("Scrittura/lettura file sessione: DATI NON CORRISPONDONO", "Letto: {$read}, atteso: {$testVal}");
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
2026-06-23 08:05:51 +02:00
|
|
|
// 3.7 Database Connectivity
|
2026-06-19 09:09:52 +02:00
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
heading('Database');
|
|
|
|
|
|
|
|
|
|
$dbDriver = $env['DB_CONNECTION'] ?? 'mysql';
|
|
|
|
|
$dbHost = $env['DB_HOST'] ?? '127.0.0.1';
|
|
|
|
|
$dbPort = $env['DB_PORT'] ?? '3306';
|
|
|
|
|
$dbName = $env['DB_DATABASE'] ?? '';
|
|
|
|
|
$dbUser = $env['DB_USERNAME'] ?? '';
|
|
|
|
|
$dbPass = $env['DB_PASSWORD'] ?? '';
|
|
|
|
|
|
|
|
|
|
if (empty($dbName)) {
|
|
|
|
|
fail("DB_DATABASE non impostato nel .env");
|
|
|
|
|
} elseif ($dbDriver === 'sqlite') {
|
|
|
|
|
$sqlitePath = $env['DB_DATABASE_SQLITE'] ?? 'database/database.sqlite';
|
|
|
|
|
$absPath = str_starts_with($sqlitePath, '/') ? $sqlitePath : BASE_DIR . '/' . $sqlitePath;
|
|
|
|
|
if (file_exists($absPath)) {
|
2026-06-23 08:05:51 +02:00
|
|
|
ok("SQLite database trovato: {$sqlitePath} (" . decoct(fileperms($absPath) & 0777) . ")");
|
2026-06-19 09:09:52 +02:00
|
|
|
} else {
|
|
|
|
|
fail("SQLite database NON TROVATO: {$sqlitePath}", "Cercato in: {$absPath}");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$dsn = "{$dbDriver}:host={$dbHost};port={$dbPort};dbname={$dbName};charset=utf8mb4";
|
|
|
|
|
v("Tentativo connessione: {$dsn} user={$dbUser}");
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$pdo = new PDO($dsn, $dbUser, $dbPass, [
|
|
|
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
|
|
|
PDO::ATTR_TIMEOUT => 5,
|
|
|
|
|
]);
|
|
|
|
|
$serverInfo = $pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
|
|
|
|
|
$driverInfo = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
|
|
|
|
|
ok("Connessione DB: OK (driver: {$driverInfo}, versione: {$serverInfo})");
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
// Tabelle
|
2026-06-19 09:09:52 +02:00
|
|
|
$stmt = $pdo->query("SHOW TABLES");
|
|
|
|
|
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
|
|
|
|
$expectedTables = ['individui', 'gruppi', 'eventi', 'users', 'tags', 'mailing_lists', 'documenti'];
|
2026-06-23 08:05:51 +02:00
|
|
|
$foundTables = array_intersect($expectedTables, $tables);
|
|
|
|
|
$missingTables = array_diff($expectedTables, $tables);
|
2026-06-19 09:09:52 +02:00
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
if (empty($missingTables)) {
|
2026-06-19 09:09:52 +02:00
|
|
|
ok("Tabelle principali: tutte presenti (" . implode(', ', $foundTables) . ")");
|
|
|
|
|
} else {
|
2026-06-23 08:05:51 +02:00
|
|
|
warn("Tabelle mancanti: " . implode(', ', $missingTables), "Esegui: php artisan migrate");
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
// Record counts
|
2026-06-19 09:09:52 +02:00
|
|
|
foreach (['users', 'individui', 'gruppi', 'eventi', 'tags'] as $table) {
|
|
|
|
|
if (in_array($table, $tables, true)) {
|
|
|
|
|
$count = $pdo->query("SELECT COUNT(*) FROM `{$table}`")->fetchColumn();
|
|
|
|
|
ok("Tabella {$table}: {$count} record");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
// Engine check
|
2026-06-19 09:09:52 +02:00
|
|
|
try {
|
|
|
|
|
$engines = $pdo->query("SHOW TABLE STATUS WHERE Engine IS NOT NULL")->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
|
$nonInnoDb = [];
|
|
|
|
|
foreach ($engines as $row) {
|
|
|
|
|
if (($row['Engine'] ?? '') !== 'InnoDB' && ($row['Engine'] ?? '') !== '') {
|
|
|
|
|
$nonInnoDb[] = $row['Name'] . ' (' . ($row['Engine'] ?? 'unknown') . ')';
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-23 08:05:51 +02:00
|
|
|
empty($nonInnoDb)
|
|
|
|
|
? ok("Engine: tutte le tabelle usano InnoDB")
|
|
|
|
|
: warn("Engine non-InnoDB: " . implode(', ', $nonInnoDb));
|
2026-06-19 09:09:52 +02:00
|
|
|
} catch (PDOException $e) {
|
2026-06-23 08:05:51 +02:00
|
|
|
v("SHOW TABLE STATUS non supportato");
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
} catch (PDOException $e) {
|
2026-06-23 08:05:51 +02:00
|
|
|
$errorSafe = str_replace($dbPass, '****', $e->getMessage());
|
2026-06-19 09:09:52 +02:00
|
|
|
fail("Connessione DB FALLITA", $errorSafe);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!extension_loaded('pdo_mysql') && $dbDriver === 'mysql') {
|
|
|
|
|
fail("PDO MySQL non installato", "Installa: apt install php-mysql");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
2026-06-23 08:05:51 +02:00
|
|
|
// 3.8 Bootstrap/app.php middleware
|
2026-06-19 09:09:52 +02:00
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
heading('Configurazione Middleware (bootstrap/app.php)');
|
|
|
|
|
|
|
|
|
|
$appPath = BASE_DIR . '/bootstrap/app.php';
|
|
|
|
|
if (!file_exists($appPath)) {
|
|
|
|
|
fail("bootstrap/app.php NON TROVATO");
|
|
|
|
|
} else {
|
|
|
|
|
$appContent = file_get_contents($appPath);
|
2026-06-23 08:05:51 +02:00
|
|
|
$warned = false;
|
|
|
|
|
|
|
|
|
|
$forceHttpsRegistered = preg_match('/\$middleware\s*->\s*\w+\s*\(.*?ForceHttps::class/s', $appContent) === 1;
|
|
|
|
|
if ($forceHttpsRegistered) {
|
|
|
|
|
ok("ForceHttps middleware: registrato correttamente");
|
|
|
|
|
} elseif (str_contains($appContent, 'ForceHttps')) {
|
|
|
|
|
warn("ForceHttps middleware: IMPORTATO ma NON registrato in alcun gruppo middleware",
|
|
|
|
|
"Aggiungi \$middleware->append(ForceHttps::class) se necessario");
|
2026-06-19 09:09:52 +02:00
|
|
|
} else {
|
2026-06-23 08:05:51 +02:00
|
|
|
warn("ForceHttps middleware: NON presente");
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (str_contains($appContent, 'trustProxies')) {
|
|
|
|
|
ok("TrustProxies: configurato");
|
|
|
|
|
} else {
|
2026-06-23 08:05:51 +02:00
|
|
|
warn("TrustProxies: NON configurato", "Può causare problemi con HTTPS dietro proxy");
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
// Duplicated web middleware
|
2026-06-19 09:09:52 +02:00
|
|
|
$webAppend = [];
|
2026-06-23 08:05:51 +02:00
|
|
|
if (preg_match('/\$middleware->web\(append:\s*\[(.+?)\]/s', $appContent, $m)) {
|
|
|
|
|
preg_match_all('/([a-zA-Z]+)::class/', $m[1], $classMatches);
|
2026-06-19 09:09:52 +02:00
|
|
|
$webAppend = $classMatches[1] ?? [];
|
|
|
|
|
}
|
|
|
|
|
$defaultWeb = ['EncryptCookies', 'AddQueuedCookiesToResponse', 'StartSession', 'ShareErrorsFromSession'];
|
|
|
|
|
foreach ($defaultWeb as $mw) {
|
|
|
|
|
if (in_array($mw, $webAppend)) {
|
2026-06-23 08:05:51 +02:00
|
|
|
warn("Middleware duplicato in web(append): {$mw}", "Già nel default stack. Può causare 419.");
|
|
|
|
|
$warned = true;
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-06-23 08:05:51 +02:00
|
|
|
if (!$warned) { ok("Middleware web group: nessuna duplicazione"); }
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
// 3.9 Vendor & Autoload
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
heading('Vendor & Autoload');
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
if (file_exists(BASE_DIR . '/vendor/autoload.php')) {
|
2026-06-19 09:09:52 +02:00
|
|
|
ok("vendor/autoload.php: presente");
|
2026-06-23 08:05:51 +02:00
|
|
|
is_dir(BASE_DIR . '/vendor/composer')
|
|
|
|
|
? ok("composer: installato")
|
|
|
|
|
: fail("vendor/composer/ mancante", "Esegui: composer install");
|
2026-06-19 09:09:52 +02:00
|
|
|
} else {
|
|
|
|
|
fail("vendor/autoload.php NON TROVATO", "Esegui: composer install --no-dev");
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
is_dir(BASE_DIR . '/node_modules')
|
|
|
|
|
? ok("node_modules: presente")
|
|
|
|
|
: warn("node_modules: assente", "npm install (non serve in prod se public/build/ è presente)");
|
2026-06-19 09:09:52 +02:00
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
is_dir(BASE_DIR . '/public/build')
|
|
|
|
|
? ok("public/build: presente (asset Vite compilati)")
|
|
|
|
|
: warn("public/build: assente", "Esegui: npm run build");
|
2026-06-19 09:09:52 +02:00
|
|
|
|
|
|
|
|
if (file_exists(BASE_DIR . '/artisan')) {
|
|
|
|
|
ok("artisan: presente");
|
|
|
|
|
if (!is_executable(BASE_DIR . '/artisan')) {
|
|
|
|
|
warn("artisan: NON eseguibile", "chmod +x artisan");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
fail("artisan NON TROVATO");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
2026-06-23 08:05:51 +02:00
|
|
|
// 3.10 CSRF Test — Duale HTTP + HTTPS
|
2026-06-19 09:09:52 +02:00
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
heading('Test CSRF (login HTTP + HTTPS)');
|
2026-06-19 09:09:52 +02:00
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
function testCsrfLogin(string $url, string $label): ?int
|
|
|
|
|
{
|
|
|
|
|
global $verbose;
|
2026-06-19 09:09:52 +02:00
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
// Note: CURLOPT_COOKIEJAR is broken in this environment (curl 8.14.1 / PHP 8.4).
|
|
|
|
|
// We manually parse Set-Cookie headers and send them back.
|
2026-06-19 09:09:52 +02:00
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
// GET login page
|
|
|
|
|
$ch = curl_init($url);
|
|
|
|
|
curl_setopt_array($ch, [
|
|
|
|
|
CURLOPT_TIMEOUT => 10,
|
|
|
|
|
CURLOPT_CONNECTTIMEOUT => 5,
|
|
|
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
|
|
|
CURLOPT_FOLLOWLOCATION => true,
|
|
|
|
|
CURLOPT_SSL_VERIFYPEER => false,
|
|
|
|
|
CURLOPT_SSL_VERIFYHOST => 0,
|
|
|
|
|
CURLOPT_HEADER => true,
|
|
|
|
|
]);
|
|
|
|
|
$resp = curl_exec($ch);
|
|
|
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
|
$curlErr = curl_error($ch);
|
|
|
|
|
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
|
|
|
|
$body = is_string($resp) ? substr($resp, $headerSize) : '';
|
|
|
|
|
$headersRaw = is_string($resp) ? substr($resp, 0, $headerSize) : '';
|
|
|
|
|
curl_close($ch);
|
2026-06-19 09:09:52 +02:00
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
if ($resp === false || $body === '') {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2026-06-19 09:09:52 +02:00
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
// Extract CSRF token from HTML
|
2026-06-19 09:09:52 +02:00
|
|
|
$token = null;
|
|
|
|
|
if (preg_match('/<input[^>]*name=["\']_token["\'][^>]*value=["\']([^"\']+)["\']/i', $body, $m)) {
|
|
|
|
|
$token = $m[1];
|
|
|
|
|
} else {
|
2026-06-23 08:05:51 +02:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
v("{$label}: token: " . substr($token, 0, 20) . '...');
|
|
|
|
|
|
|
|
|
|
// Parse all Set-Cookie headers to build a cookie string
|
|
|
|
|
$cookies = [];
|
|
|
|
|
if (preg_match_all('/^Set-Cookie:\s*([^;]+)/mi', $headersRaw, $cookieMatches)) {
|
|
|
|
|
foreach ($cookieMatches[1] as $cookieStr) {
|
|
|
|
|
$cookies[] = trim($cookieStr);
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-06-23 08:05:51 +02:00
|
|
|
$cookieHeader = !empty($cookies) ? implode('; ', $cookies) : '';
|
|
|
|
|
v("{$label}: cookies: " . ($cookieHeader ? substr($cookieHeader, 0, 80) . '...' : 'none'));
|
|
|
|
|
|
|
|
|
|
// POST login with manually extracted cookies
|
|
|
|
|
$ch2 = curl_init($url);
|
|
|
|
|
$curlOpts = [
|
|
|
|
|
CURLOPT_TIMEOUT => 10,
|
|
|
|
|
CURLOPT_CONNECTTIMEOUT => 5,
|
|
|
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
|
|
|
CURLOPT_FOLLOWLOCATION => false,
|
|
|
|
|
CURLOPT_SSL_VERIFYPEER => false,
|
|
|
|
|
CURLOPT_SSL_VERIFYHOST => 0,
|
|
|
|
|
CURLOPT_HEADER => true,
|
|
|
|
|
CURLOPT_POST => true,
|
|
|
|
|
CURLOPT_POSTFIELDS => http_build_query([
|
|
|
|
|
'_token' => $token,
|
|
|
|
|
'email' => 'test@diagnose.local',
|
|
|
|
|
'password' => 'wrong',
|
|
|
|
|
]),
|
|
|
|
|
];
|
|
|
|
|
if ($cookieHeader !== '') {
|
|
|
|
|
$curlOpts[CURLOPT_COOKIE] = $cookieHeader;
|
|
|
|
|
}
|
|
|
|
|
curl_setopt_array($ch2, $curlOpts);
|
|
|
|
|
|
|
|
|
|
$resp2 = curl_exec($ch2);
|
|
|
|
|
$postCode = curl_getinfo($ch2, CURLINFO_HTTP_CODE);
|
|
|
|
|
curl_close($ch2);
|
|
|
|
|
|
|
|
|
|
if ($postCode !== 302 && $postCode !== 200) {
|
|
|
|
|
v("{$label}: POST returned HTTP {$postCode}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $postCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$baseLoginUrl = rtrim($baseUrl ?: 'http://localhost', '/') . '/login';
|
|
|
|
|
$httpsLoginUrl = $baseLoginUrl;
|
|
|
|
|
$httpLoginUrl = str_replace('https://', 'http://', $baseLoginUrl);
|
|
|
|
|
|
|
|
|
|
$hasHttps = str_starts_with($baseLoginUrl, 'https://');
|
|
|
|
|
|
|
|
|
|
$httpsResult = null;
|
|
|
|
|
$httpResult = null;
|
|
|
|
|
|
|
|
|
|
if ($hasHttps) {
|
|
|
|
|
echo " " . COLOR_BOLD . "Test HTTPS:" . COLOR_RESET . " {$httpsLoginUrl}\n";
|
|
|
|
|
$httpsResult = testCsrfLogin($httpsLoginUrl, 'HTTPS');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
echo " " . COLOR_BOLD . "Test HTTP:" . COLOR_RESET . " {$httpLoginUrl}\n";
|
|
|
|
|
$httpResult = testCsrfLogin($httpLoginUrl, 'HTTP');
|
|
|
|
|
|
|
|
|
|
echo "\n";
|
|
|
|
|
|
|
|
|
|
if ($hasHttps) {
|
|
|
|
|
if ($httpsResult === null) {
|
|
|
|
|
fail("HTTPS: Login page non raggiungibile");
|
|
|
|
|
} elseif ($httpsResult === -1) {
|
|
|
|
|
fail("HTTPS: CSRF token non trovato nella pagina");
|
|
|
|
|
} elseif ($httpsResult === 419) {
|
|
|
|
|
fail("HTTPS: POST login → 419 Page Expired",
|
|
|
|
|
"Token non riconosciuto. Sessione non persistente o SESSION_SECURE_COOKIE errato.");
|
|
|
|
|
} elseif ($httpsResult === 302 || $httpsResult === 200) {
|
|
|
|
|
ok("HTTPS: POST login → HTTP {$httpsResult} (nessun 419)");
|
|
|
|
|
} else {
|
|
|
|
|
warn("HTTPS: POST login → HTTP {$httpsResult} (inaspettato)");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($httpResult === null) {
|
|
|
|
|
fail("HTTP: Login page non raggiungibile");
|
|
|
|
|
} elseif ($httpResult === -1) {
|
|
|
|
|
fail("HTTP: CSRF token non trovato nella pagina");
|
|
|
|
|
} elseif ($httpResult === 419) {
|
|
|
|
|
fail("HTTP: POST login → 419 Page Expired",
|
|
|
|
|
"Token non riconosciuto. Sessione non persistente o SESSION_SECURE_COOKIE errato.");
|
|
|
|
|
} elseif ($httpResult === 302 || $httpResult === 200) {
|
|
|
|
|
ok("HTTP: POST login → HTTP {$httpResult} (nessun 419)");
|
|
|
|
|
} else {
|
|
|
|
|
warn("HTTP: POST login → HTTP {$httpResult} (inaspettato)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($hasHttps && $httpsResult !== null && $httpsResult !== 419 && $httpResult === 419) {
|
|
|
|
|
warn("HTTPS funziona ma HTTP dà 419",
|
|
|
|
|
"SESSION_SECURE_COOKIE=true blocca il cookie su HTTP. Imposta SESSION_SECURE_COOKIE=false se serve HTTP.");
|
|
|
|
|
if ($fixMode) {
|
|
|
|
|
$groupsToFix['env']['SESSION_SECURE_COOKIE'] = 'false';
|
|
|
|
|
}
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-23 08:05:51 +02:00
|
|
|
// Local IP CSRF test — verifica accesso via IP di rete locale (diverso da APP_URL hostname)
|
|
|
|
|
$localIp = null;
|
|
|
|
|
$hostnameI = trim((string) exec('hostname -I 2>/dev/null'));
|
|
|
|
|
if ($hostnameI !== '') {
|
|
|
|
|
$parts = explode(' ', $hostnameI);
|
|
|
|
|
$localIp = $parts[0];
|
|
|
|
|
}
|
|
|
|
|
if ($localIp && $localIp !== parse_url($baseUrl, PHP_URL_HOST)) {
|
|
|
|
|
$localLoginUrl = "http://{$localIp}/login";
|
|
|
|
|
echo " " . COLOR_BOLD . "Test Locale IP:" . COLOR_RESET . " {$localLoginUrl}\n";
|
|
|
|
|
$localResult = testCsrfLogin($localLoginUrl, 'LOCAL IP');
|
|
|
|
|
echo "\n";
|
|
|
|
|
if ($localResult === null) {
|
|
|
|
|
fail("LOCAL IP: Login page non raggiungibile su {$localIp}");
|
|
|
|
|
} elseif ($localResult === -1) {
|
|
|
|
|
fail("LOCAL IP: CSRF token non trovato nella pagina su {$localIp}");
|
|
|
|
|
} elseif ($localResult === 419) {
|
|
|
|
|
fail("LOCAL IP: POST login → 419 Page Expired",
|
|
|
|
|
"Sessione non funzionante su IP locale. Verifica SESSION_DRIVER, permessi storage o cookie domain.");
|
|
|
|
|
} elseif ($localResult === 302 || $localResult === 200) {
|
|
|
|
|
ok("LOCAL IP: POST login → HTTP {$localResult} (nessun 419)");
|
|
|
|
|
} else {
|
|
|
|
|
warn("LOCAL IP: POST login → HTTP {$localResult} (inaspettato)");
|
|
|
|
|
}
|
2026-06-19 09:09:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
// 3.11 Disk Space
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
heading('Spazio Disco');
|
|
|
|
|
|
|
|
|
|
$total = disk_total_space(BASE_DIR);
|
2026-06-23 08:05:51 +02:00
|
|
|
$free = disk_free_space(BASE_DIR);
|
|
|
|
|
$used = $total - $free;
|
2026-06-19 09:09:52 +02:00
|
|
|
$pctUsed = round(($used / $total) * 100, 1);
|
|
|
|
|
|
|
|
|
|
ok("Spazio totale: " . formatBytes($total));
|
|
|
|
|
ok("Spazio libero: " . formatBytes($free));
|
2026-06-23 08:05:51 +02:00
|
|
|
|
2026-06-19 09:09:52 +02:00
|
|
|
if ($pctUsed > 90) {
|
|
|
|
|
fail("Spazio utilizzato: {$pctUsed}% — CRITICO");
|
|
|
|
|
} elseif ($pctUsed > 80) {
|
|
|
|
|
warn("Spazio utilizzato: {$pctUsed}%");
|
|
|
|
|
} else {
|
|
|
|
|
ok("Spazio utilizzato: {$pctUsed}%");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
2026-06-23 08:05:51 +02:00
|
|
|
// 4. Apply fixes
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
if ($fixMode) {
|
|
|
|
|
runFixes($groupsToFix);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
// 5. Summary
|
2026-06-19 09:09:52 +02:00
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
heading('Riepilogo');
|
|
|
|
|
echo "\n";
|
2026-06-23 08:05:51 +02:00
|
|
|
echo " " . COLOR_GREEN . "✅ Passed: {$results['passed']}" . COLOR_RESET . "\n";
|
2026-06-19 09:09:52 +02:00
|
|
|
echo " " . COLOR_YELLOW . "⚠️ Warnings: {$results['warnings']}" . COLOR_RESET . "\n";
|
2026-06-23 08:05:51 +02:00
|
|
|
echo " " . COLOR_RED . "❌ Failed: {$results['failed']}" . COLOR_RESET . "\n";
|
|
|
|
|
if ($results['fixed'] > 0) {
|
|
|
|
|
echo " " . COLOR_GREEN . "🔧 Fixed: {$results['fixed']}" . COLOR_RESET . "\n";
|
|
|
|
|
}
|
2026-06-19 09:09:52 +02:00
|
|
|
echo "\n";
|
|
|
|
|
|
|
|
|
|
if ($results['failed'] > 0) {
|
2026-06-23 08:05:51 +02:00
|
|
|
echo COLOR_BOLD . COLOR_RED . " ⛔ PROBLEMI CRITICI TROVATI." . COLOR_RESET . "\n";
|
|
|
|
|
if (!$fixMode) {
|
|
|
|
|
echo " Per tentare la riparazione automatica: " . COLOR_BOLD . "php diagnose.php --fix" . COLOR_RESET . "\n";
|
|
|
|
|
}
|
|
|
|
|
echo "\n";
|
2026-06-19 09:09:52 +02:00
|
|
|
exit(1);
|
|
|
|
|
} elseif ($results['warnings'] > 0) {
|
|
|
|
|
echo COLOR_BOLD . COLOR_YELLOW . " ⚠️ Warning presenti — Verificare i punti segnalati." . COLOR_RESET . "\n\n";
|
|
|
|
|
exit(0);
|
|
|
|
|
} else {
|
|
|
|
|
echo COLOR_BOLD . COLOR_GREEN . " ✅ Tutti i controlli superati. Installazione OK." . COLOR_RESET . "\n\n";
|
|
|
|
|
exit(0);
|
|
|
|
|
}
|