This commit is contained in:
2026-06-19 09:09:52 +02:00
parent baea181159
commit c5127da750
6 changed files with 859 additions and 4 deletions
+8 -2
View File
@@ -1,8 +1,12 @@
APP_NAME="Glastree"
APP_ENV=local
APP_ENV=production
APP_KEY=
APP_DEBUG=true
APP_DEBUG=false
APP_URL=http://localhost
APP_PROTOCOL=https
APP_SUBFOLDER=
FORCE_HTTPS=false
TRUSTED_PROXIES="10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1"
APP_LOCALE=it
APP_FALLBACK_LOCALE=en
@@ -27,6 +31,8 @@ DB_PASSWORD=
SESSION_DRIVER=file
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_SECURE_COOKIE=false
SESSION_SAME_SITE=lax
SESSION_PATH=/
SESSION_DOMAIN=null
+46
View File
@@ -26,6 +26,7 @@ App gestionale Laravel 13 con AdminLTE 4 per gestione Persone e Gruppi.
- **2026-06-17**: Google OAuth 2.0 unificato (revert Socialite, unified OAuth per Email/Drive/Calendar, XOAUTH2 SMTP+IMAP, UI impostazioni)
- **2026-06-17**: Fix CalendarioConnessione — `encryptAndSetConfig()` perdeva campi sensibili in edit, `is_active` checkbox senza hidden fallback, query duplicata nella view
- **2026-06-18**: Fix import CSV — `declare(strict_types=1)` mancante in IndividuoController, header check assente, Log::warning() su errori; fix freeze server (set_time_limit, session_write_close, DB::transaction, fgetcsv length illimitato) in GruppoController e IndividuoController
- **2026-06-18**: Diagnose script (`diagnose.php`), .env.example aggiornato (SESSION_SECURE_COOKIE, FORCE_HTTPS, TRUSTED_PROXIES), build-dist.sh aggiornato con passo diagnose
## Funzionalità Implementate
@@ -705,3 +706,48 @@ DB::commit();
### Verifica
- `php -l` su entrambi: nessun errore di sintassi
## 2026-06-18 — Diagnose script + .env.example + build-dist.sh
### `diagnose.php` — Script standalone di diagnostica
Creato script standalone per verificare installazione su server remoto. Non richiede Laravel.
**Cosa verifica**:
- PHP version (≥ 8.2), 15 estensioni required, 4 opzionali
- INI settings (execution_time, memory, upload/post max)
- .env: APP_KEY decodifica (32 bytes), APP_URL, SESSION_SECURE_COOKIE, DB, FORCE_HTTPS
- 13 directory in `storage/` e `bootstrap/cache`: esistenza, permessi, scrivibilità
- Symlink `public/storage` (supporto sia link assoluti che relativi)
- Test scrittura/lettura file sessione
- Connessione DB via PDO, tabelle principali, conteggio records, engine InnoDB
- Middleware bootstrap/app.php: ForceHttps registrato, TrustProxies, duplicazioni web group
- Vendor: autoload, composer, node_modules, public/build, artisan
- CSRF: HTTP GET login page, estrazione token, POST con token, verifica 419
- Spazio disco: totale, libero, percentuale
**Uso**:
```bash
php diagnose.php
php diagnose.php --verbose # output dettagliato
```
Exit code: 0 = OK, 1 = errori critici.
### `.env.example` aggiornato
Aggiunte variabili mancanti:
- `APP_PROTOCOL`, `APP_SUBFOLDER` — supporto subfolder
- `FORCE_HTTPS`, `TRUSTED_PROXIES` — proxy/HTTPS configurabile
- `SESSION_SECURE_COOKIE`, `SESSION_SAME_SITE` — sessione cross-protocol
- `APP_ENV=production`, `APP_DEBUG=false` — safe defaults per produzione
### `build-dist.sh`
- Aggiunto `php diagnose.php` come passo #0 (pre-installazione) e #7 (verifica finale)
- Istruzioni post-estrazione complete
### File modificati
- `diagnose.php` (nuovo) — standalone diagnostica
- `.env.example` — variabili mancanti aggiunte
- `build-dist.sh` — passo diagnose
### Verifica
- `php -l diagnose.php`: OK
- `php diagnose.php`: eseguito con 58/68 check superati su server locale
+6
View File
@@ -70,6 +70,9 @@ echo "Per estrarre sul server di destinazione:"
echo " tar xzf ${ARCHIVE}"
echo ""
echo "Poi esegui:"
echo " # 0. Diagnostica preliminare"
echo " php diagnose.php"
echo ""
echo " # 1. Crea directory necessarie (se non esistono già)"
echo ' mkdir -p storage/framework/cache/data storage/framework/sessions storage/framework/views storage/logs bootstrap/cache storage/app/public storage/app/public/logos storage/app/public/documenti/eventi storage/app/backups storage/app/documenti storage/app/private/avatars/gruppi'
echo ""
@@ -94,3 +97,6 @@ echo " php artisan view:clear"
echo ""
echo " # 6. Avvia installazione MySQL"
echo " php install.php"
echo ""
echo " # 7. Verifica finale"
echo " php diagnose.php"
+797
View File
@@ -0,0 +1,797 @@
<?php
/**
* diagnose.php — Standalone verificare installazione Glastree (Laravel)
*
* Esecuzione:
* php diagnose.php
* php diagnose.php --verbose
*
* Non richiede Laravel. Legge .env direttamente e testa DB via PDO.
*/
declare(strict_types=1);
// ─────────────────────────────────────────────────────────────────────────────
// 1. Setup
// ─────────────────────────────────────────────────────────────────────────────
define('BASE_DIR', __DIR__);
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";
$verbose = in_array('--verbose', $argv ?? []);
$results = [
'passed' => 0,
'failed' => 0,
'warnings' => 0,
];
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}";
if ($detail !== null) {
echo COLOR_RED . "{$detail}" . COLOR_RESET;
}
echo "\n";
}
function warn(string $msg, ?string $detail = null): void
{
global $results;
$results['warnings']++;
echo " " . COLOR_YELLOW . "⚠️ " . COLOR_RESET . " {$msg}";
if ($detail !== null) {
echo COLOR_YELLOW . "{$detail}" . COLOR_RESET;
}
echo "\n";
}
function heading(string $title): void
{
echo "\n" . COLOR_BOLD . COLOR_CYAN . "═══ {$title} ═══" . COLOR_RESET . "\n\n";
}
function v(string $msg): void
{
global $verbose;
if ($verbose) {
echo " " . COLOR_YELLOW . "[verbose]" . COLOR_RESET . " {$msg}\n";
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 2. Parse .env (standalone, no framework)
// ─────────────────────────────────────────────────────────────────────────────
function parseEnv(string $path): array
{
if (!file_exists($path)) {
return [];
}
$env = [];
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$line = trim($line);
if ($line === '' || str_starts_with($line, '#')) {
continue;
}
if (str_contains($line, '=')) {
$parts = explode('=', $line, 2);
$key = trim($parts[0]);
$value = trim($parts[1] ?? '');
// Rimuovi eventuali apici
if ((str_starts_with($value, '"') && str_ends_with($value, '"'))
|| (str_starts_with($value, "'") && str_ends_with($value, "'"))) {
$value = substr($value, 1, -1);
}
// Risolvi variabili ${...}
$value = preg_replace_callback('/\$\{(\w+)\}/', function ($m) use ($env) {
return $env[$m[1]] ?? $m[0];
}, $value);
$env[$key] = $value;
}
}
return $env;
}
// ─────────────────────────────────────────────────────────────────────────────
// 3. Check funzioni
// ─────────────────────────────────────────────────────────────────────────────
echo COLOR_BOLD . "\n"
. " ╔════════════════════════════════════════════╗\n"
. " ║ Glastree — Diagnosi Installazione ║\n"
. " ╚════════════════════════════════════════════╝\n"
. COLOR_RESET;
echo "\n Data: " . date('Y-m-d H:i:s') . " | PID: " . getmypid() . "\n";
// ─────────────────────────────────────────────────────────────────────────────
// 3.1 PHP Version & Extensions
// ─────────────────────────────────────────────────────────────────────────────
heading('PHP Version & Extensions');
$phpVersion = PHP_VERSION;
$phpMajorMinor = PHP_MAJOR_VERSION . '.' . PHP_MINOR_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) {
if (extension_loaded($ext)) {
ok("Estensione {$ext}: OK");
} else {
fail("Estensione {$ext}: NON TROVATA", "Richiesta da Laravel");
}
}
foreach ($optionalExts as $ext => $label) {
if (extension_loaded($ext)) {
ok("Estensione {$ext}: OK ({$label})");
} else {
warn("Estensione {$ext}: NON TROVATA ({$label})");
}
}
// INI settings
$iniChecks = [
'max_execution_time' => [30, '60+ consigliato per import CSV'],
'memory_limit' => ['128M', '128M minimo, 256M+ consigliato'],
'upload_max_filesize' => ['20M', '20M minimo per CSV/ICS'],
'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'],
];
heading('PHP INI Settings');
foreach ($iniChecks as $iniKey => [$minVal, $note]) {
$actual = ini_get($iniKey);
$ok = false;
if (is_numeric($minVal) && is_numeric($actual)) {
$ok = (int) $actual >= (int) $minVal;
} elseif (is_string($minVal)) {
// Comparazione bytes
$actualBytes = returnBytes($actual);
$minBytes = returnBytes($minVal);
$ok = $actualBytes >= $minBytes;
}
if ($ok) {
ok("{$iniKey} = {$actual}");
} else {
warn("{$iniKey} = {$actual} (minimo: {$minVal})", $note);
}
}
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,
};
}
// ─────────────────────────────────────────────────────────────────────────────
// 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);
if ($decoded !== false && strlen($decoded) === 32) {
ok("APP_KEY: valido (32 bytes)");
} else {
fail('APP_KEY: decodifica fallita o lunghezza errata', "Deve essere 32 bytes dopo decodifica base64");
}
}
// APP_URL
if (empty($env['APP_URL'])) {
fail('APP_URL non impostato');
} elseif (!str_starts_with($env['APP_URL'], 'http://') && !str_starts_with($env['APP_URL'], 'https://')) {
fail('APP_URL non valido', $env['APP_URL']);
} else {
ok("APP_URL: {$env['APP_URL']}");
// Verifica reachability via curl
$ch = curl_init($env['APP_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_NOBODY => true,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($response === false) {
fail("APP_URL non raggiungibile via HTTP", $curlError);
} else {
ok("APP_URL raggiungibile (HTTP {$httpCode})");
}
}
// 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',
'Il cookie di sessione potrebbe non essere inviato su HTTP');
} 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';
if (in_array($sessionDriver, ['file', 'database', 'redis', 'memcached', 'cookie', 'dynamodb'])) {
ok("SESSION_DRIVER: {$sessionDriver}");
} else {
fail("SESSION_DRIVER: {$sessionDriver} sconosciuto");
}
// SESSION_DOMAIN check
if (!empty($env['SESSION_DOMAIN']) && $env['SESSION_DOMAIN'] !== 'null') {
$parsedUrl = parse_url($env['APP_URL'] ?? 'http://localhost');
$urlHost = $parsedUrl['host'] ?? 'localhost';
if (!str_contains($env['SESSION_DOMAIN'], $urlHost)) {
warn("SESSION_DOMAIN ({$env['SESSION_DOMAIN']}) non corrisponde a APP_URL ({$urlHost})",
'Il cookie di sessione potrebbe non essere inviato');
} else {
ok("SESSION_DOMAIN: {$env['SESSION_DOMAIN']}");
}
} else {
ok("SESSION_DOMAIN: null (usa dominio richiesta)");
}
// DB_CONNECTION
$dbConnection = $env['DB_CONNECTION'] ?? 'mysql';
if (in_array($dbConnection, ['mysql', 'mariadb', 'sqlite', 'pgsql', 'sqlsrv'])) {
ok("DB_CONNECTION: {$dbConnection}");
} else {
fail("DB_CONNECTION: {$dbConnection} sconosciuto");
}
// APP_DEBUG in produzione
$appEnv = $env['APP_ENV'] ?? 'production';
$appDebug = ($env['APP_DEBUG'] ?? 'false') === 'true';
if ($appEnv === 'production' && $appDebug) {
warn('APP_DEBUG=true in ambiente production',
'Disabilita in produzione: espone errori sensibili');
} else {
ok("APP_DEBUG: " . ($appDebug ? 'true' : 'false') . " in ambiente {$appEnv}");
}
// ─────────────────────────────────────────────────────────────────────────────
// 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',
];
foreach ($writeDirs as $label => $dir) {
if (!file_exists($dir)) {
warn("{$label}: directory NON ESISTE");
continue;
}
if (!is_dir($dir)) {
fail("{$label}: NON è una directory");
continue;
}
$perms = decoct(fileperms($dir) & 0777);
$owner = function_exists('posix_getpwuid') ? posix_getpwuid(fileowner($dir))['name'] ?? fileowner($dir) : fileowner($dir);
if (is_writable($dir)) {
ok("{$label}: {$perms} proprietario: {$owner} — SCRIVIBILE");
} else {
fail("{$label}: {$perms} proprietario: {$owner} — NON SCRIVIBILE",
"Chmod: chmod -R 775 " . str_replace(BASE_DIR . '/', '', $dir));
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 3.5 Symlink Check
// ─────────────────────────────────────────────────────────────────────────────
heading('Symlink');
$publicStorage = BASE_DIR . '/public/storage';
$target = $publicStorage;
if (!file_exists($publicStorage)) {
fail("public/storage: NON ESISTE", "Esegui: php artisan storage:link");
v("Path: {$publicStorage}");
} elseif (!is_link($publicStorage)) {
fail("public/storage: NON è un symlink (è una directory/file)");
} else {
$readTarget = readlink($publicStorage);
$expected = realpath(BASE_DIR . '/storage/app/public');
// Se readlink restituisce percorso assoluto, usalo direttamente
$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));
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 3.6 Session Test
// ─────────────────────────────────────────────────────────────────────────────
heading('Sessione — Test scrittura/lettura');
$sessionDir = BASE_DIR . '/storage/framework/sessions';
if (!is_writable($sessionDir)) {
fail("Directory session NON scrivibile — test saltato");
} else {
$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);
if ($read === $testVal) {
ok("Scrittura/lettura file sessione: OK");
} else {
fail("Scrittura/lettura file sessione: DATI NON CORRISPONDONO",
"Letto: {$read}, atteso: {$testVal}");
}
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 3.7 Database Connectivity (standalone via PDO)
// ─────────────────────────────────────────────────────────────────────────────
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)) {
$perms = decoct(fileperms($absPath) & 0777);
ok("SQLite database trovato: {$sqlitePath} ({$perms})");
} 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})");
// Verifica esistenza tabelle principali
$stmt = $pdo->query("SHOW TABLES");
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
$expectedTables = ['individui', 'gruppi', 'eventi', 'users', 'tags', 'mailing_lists', 'documenti'];
$foundTables = [];
foreach ($expectedTables as $table) {
if (in_array($table, $tables, true)) {
$foundTables[] = $table;
}
}
if (count($foundTables) === count($expectedTables)) {
ok("Tabelle principali: tutte presenti (" . implode(', ', $foundTables) . ")");
} else {
$missing = array_diff($expectedTables, $foundTables);
warn("Tabelle mancanti: " . implode(', ', $missing),
"Esegui: php artisan migrate");
}
// Verifica conteggio records
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");
}
}
// Verifica engine InnoDB
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') . ')';
}
}
if (empty($nonInnoDb)) {
ok("Engine: tutte le tabelle usano InnoDB");
} else {
warn("Engine non-InnoDB: " . implode(', ', $nonInnoDb),
"InnoDB raccomandato per integrità referenziale");
}
} catch (PDOException $e) {
v("SHOW TABLE STATUS non supportato: " . $e->getMessage());
}
} catch (PDOException $e) {
$errorMsg = $e->getMessage();
// Maschera password in output
$errorSafe = str_replace($dbPass, '****', $errorMsg);
fail("Connessione DB FALLITA", $errorSafe);
// Diagnostica
if ($e->getCode() === 2002) {
v("Host '{$dbHost}:{$dbPort}' non raggiungibile. Verifica DB_HOST e DB_PORT.");
} elseif ($e->getCode() === 1045) {
v("Accesso negato per utente '{$dbUser}'.");
} elseif ($e->getCode() === 1049) {
v("Database '{$dbName}' non esiste.");
} elseif ($e->getCode() === 1044) {
v("Utente '{$dbUser}' non ha accesso al database '{$dbName}'.");
}
}
// Verifica estensione PDO nel caso la connessione non sia stata testata
if (!extension_loaded('pdo_mysql') && $dbDriver === 'mysql') {
fail("PDO MySQL non installato", "Installa: apt install php-mysql");
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 3.8 Bootstrap/app.php middleware check
// ─────────────────────────────────────────────────────────────────────────────
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);
$warnedDoubles = false;
// Check ForceHttps registration
if (str_contains($appContent, 'ForceHttps')) {
ok("ForceHttps middleware: registrato");
} else {
warn("ForceHttps middleware: NON registrato",
"FORCE_HTTPS=true nel .env ma middleware non aggiunto a bootstrap/app.php");
}
// Check TrustProxies
if (str_contains($appContent, 'trustProxies')) {
ok("TrustProxies: configurato");
} else {
warn("TrustProxies: NON configurato",
"Può causare problemi con HTTPS dietro proxy");
}
// Check for duplicated web middleware
$webAppend = [];
if (preg_match('/\$middleware->web\(append:\s*\[([^\]]*)\]/s', $appContent, $m)) {
$block = $m[1];
preg_match_all('/class\s*:\s*[a-z\\\]*\\\([a-zA-Z]+)/i', $block, $classMatches);
$webAppend = $classMatches[1] ?? [];
}
$defaultWeb = ['EncryptCookies', 'AddQueuedCookiesToResponse', 'StartSession', 'ShareErrorsFromSession'];
foreach ($defaultWeb as $mw) {
if (in_array($mw, $webAppend)) {
warn("Middleware duplicato in web(append): {$mw}",
"Già presente nel default stack di Laravel 11. Può causare 419 su login.");
$warnedDoubles = true;
}
}
if (!$warnedDoubles) {
ok("Middleware web group: nessuna duplicazione");
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 3.9 Vendor & Autoload
// ─────────────────────────────────────────────────────────────────────────────
heading('Vendor & Autoload');
$vendorAutoload = BASE_DIR . '/vendor/autoload.php';
if (file_exists($vendorAutoload)) {
ok("vendor/autoload.php: presente");
$vendorSize = filesize(BASE_DIR . '/vendor/');
if (is_dir(BASE_DIR . '/vendor/composer')) {
ok("composer: installato");
} else {
fail("vendor/composer/ mancante", "Esegui: composer install");
}
} else {
fail("vendor/autoload.php NON TROVATO", "Esegui: composer install --no-dev");
}
// Package.json / node_modules
if (is_dir(BASE_DIR . '/node_modules')) {
ok("node_modules: presente");
} else {
warn("node_modules: assente", "Esegui: npm install (non necessario in produzione se public/build/ è presente)");
}
// Check public/build
if (is_dir(BASE_DIR . '/public/build')) {
ok("public/build: presente (asset Vite compilati)");
} else {
warn("public/build: assente", "Esegui: npm run build (gli asset AdminLTE potrebbero non funzionare)");
}
// Check artisan
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");
}
// ─────────────────────────────────────────────────────────────────────────────
// 3.10 CSRF Test (HTTP)
// ─────────────────────────────────────────────────────────────────────────────
heading('Test CSRF (via HTTP)');
$baseUrl = $env['APP_URL'] ?? 'http://localhost';
$loginUrl = rtrim($baseUrl, '/') . '/login';
$loginUrlHttp = str_replace('https://', 'http://', $loginUrl);
v("Test login page: {$loginUrl}");
v("Test HTTP variant: {$loginUrlHttp}");
$ch = curl_init($loginUrl);
curl_setopt_array($ch, [
CURLOPT_TIMEOUT => 15,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_HEADER => true,
CURLOPT_COOKIEJAR => '/tmp/glastree_diagnose_cookies_' . getmypid() . '.txt',
CURLOPT_COOKIEFILE => '/tmp/glastree_diagnose_cookies_' . getmypid() . '.txt',
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = is_string($response) ? substr($response, 0, $headerSize) : '';
$body = is_string($response) ? substr($response, $headerSize) : '';
curl_close($ch);
if ($response === false || $body === false || $body === '') {
fail("Login page non raggiungibile", $curlError);
warn("Test CSRF saltato — APP_URL non raggiungibile da questo server");
} else {
ok("Login page raggiungibile (HTTP {$httpCode})");
// Extract CSRF token
$token = null;
if (preg_match('/<input[^>]*name=["\']_token["\'][^>]*value=["\']([^"\']+)["\']/i', $body, $m)) {
$token = $m[1];
ok("CSRF token estratto dalla pagina login");
v("Token (primi 20 char): " . substr($token, 0, 20) . '...');
} else {
fail("CSRF token NON TROVATO nella pagina login",
"@csrf potrebbe non funzionare correttamente");
}
// Try POST to login with token
if ($token !== null) {
$ch2 = curl_init($loginUrl);
curl_setopt_array($ch2, [
CURLOPT_TIMEOUT => 15,
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@example.com',
'password' => 'wrongpassword',
]),
CURLOPT_COOKIEJAR => '/tmp/glastree_diagnose_cookies_' . getmypid() . '.txt',
CURLOPT_COOKIEFILE => '/tmp/glastree_diagnose_cookies_' . getmypid() . '.txt',
]);
$postResponse = curl_exec($ch2);
$postHttpCode = curl_getinfo($ch2, CURLINFO_HTTP_CODE);
$postHeaders = substr($postResponse, 0, curl_getinfo($ch2, CURLINFO_HEADER_SIZE));
curl_close($ch2);
if ($postHttpCode === 419) {
fail("POST login → 419 Page Expired",
"CSRO token non riconosciuto. Possibili cause: sessione non persistente,"
. " SESSION_SECURE_COOKIE errato, middleware duplicati, permessi session.");
} elseif ($postHttpCode === 302 || $postHttpCode === 200) {
ok("POST login → HTTP {$postHttpCode} (419 non verificato)");
v("Se 200: la pagina restituita probabilmente contiene errori di validazione (normale)");
} else {
warn("POST login → HTTP {$postHttpCode} (inaspettato)");
}
}
}
// Cleanup cookies
$cookieFile = '/tmp/glastree_diagnose_cookies_' . getmypid() . '.txt';
if (file_exists($cookieFile)) {
unlink($cookieFile);
}
// ─────────────────────────────────────────────────────────────────────────────
// 3.11 Disk Space
// ─────────────────────────────────────────────────────────────────────────────
heading('Spazio Disco');
$total = disk_total_space(BASE_DIR);
$free = disk_free_space(BASE_DIR);
$used = $total - $free;
$pctUsed = round(($used / $total) * 100, 1);
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];
}
ok("Spazio totale: " . formatBytes($total));
ok("Spazio libero: " . formatBytes($free));
if ($pctUsed > 90) {
fail("Spazio utilizzato: {$pctUsed}% — CRITICO");
} elseif ($pctUsed > 80) {
warn("Spazio utilizzato: {$pctUsed}%");
} else {
ok("Spazio utilizzato: {$pctUsed}%");
}
// ─────────────────────────────────────────────────────────────────────────────
// 4. Summary
// ─────────────────────────────────────────────────────────────────────────────
heading('Riepilogo');
echo "\n";
echo " " . COLOR_GREEN . "✅ Passed: {$results['passed']}" . COLOR_RESET . "\n";
echo " " . COLOR_YELLOW . "⚠️ Warnings: {$results['warnings']}" . COLOR_RESET . "\n";
echo " " . COLOR_RED . "❌ Failed: {$results['failed']}" . COLOR_RESET . "\n";
echo "\n";
if ($results['failed'] > 0) {
echo COLOR_BOLD . COLOR_RED . " ⛔ PROBLEMI CRITICI TROVATI — Risolvere prima di proseguire." . COLOR_RESET . "\n\n";
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);
}
Binary file not shown.
+2 -2
View File
@@ -3,7 +3,7 @@
'name' => 'laravel/laravel',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'd2084b9d07cbeccb7e289e6f31ca767f6cce07c8',
'reference' => 'baea18115984a1c2506c8af908424d45498b0b11',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -427,7 +427,7 @@
'laravel/laravel' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'd2084b9d07cbeccb7e289e6f31ca767f6cce07c8',
'reference' => 'baea18115984a1c2506c8af908424d45498b0b11',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),