final stage
This commit is contained in:
@@ -0,0 +1,505 @@
|
||||
/* =============================================================================
|
||||
Glastree Installer - Styles
|
||||
============================================================================= */
|
||||
|
||||
:root {
|
||||
--primary: #007bff;
|
||||
--primary-dark: #0056b3;
|
||||
--success: #28a745;
|
||||
--danger: #dc3545;
|
||||
--warning: #ffc107;
|
||||
--info: #17a2b8;
|
||||
--dark: #343a40;
|
||||
--light: #f8f9fa;
|
||||
--border: #dee2e6;
|
||||
--text: #212529;
|
||||
--text-muted: #6c757d;
|
||||
--radius: 8px;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
color: var(--text);
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.installer-header {
|
||||
background: linear-gradient(135deg, #343a40 0%, #212529 100%);
|
||||
color: #fff;
|
||||
padding: 30px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.installer-header h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.installer-header .subtitle {
|
||||
font-size: 14px;
|
||||
opacity: 0.75;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Step indicator */
|
||||
.step-indicator {
|
||||
text-align: center;
|
||||
padding: 12px 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.card {
|
||||
background: #fff;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.card-body h2 {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.card-body h4 {
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
color: var(--dark);
|
||||
}
|
||||
|
||||
.card-body h5 {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.card-body p {
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border);
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
/* Form elements */
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
color: var(--dark);
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
font-size: 15px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px rgba(0,123,255,0.15);
|
||||
}
|
||||
|
||||
.form-text {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 10px 22px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 14px 32px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
/* Option cards */
|
||||
.option-group {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.option-card {
|
||||
display: block;
|
||||
position: relative;
|
||||
border: 2px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.option-card:hover {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 2px 8px rgba(0,123,255,0.1);
|
||||
}
|
||||
|
||||
.option-card.selected {
|
||||
border-color: var(--primary);
|
||||
background: rgba(0,123,255,0.03);
|
||||
}
|
||||
|
||||
.option-card .option-radio {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.option-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.option-icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 8px;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.option-card h3 {
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.option-card p {
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.option-card ul {
|
||||
margin-top: 8px;
|
||||
padding-left: 18px;
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.option-card ul li {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* Requirements */
|
||||
.requirements-list {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.requirement-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.requirement-item.passed {
|
||||
background: #f0fff4;
|
||||
border-color: #b8e6c8;
|
||||
}
|
||||
|
||||
.requirement-item.failed {
|
||||
background: #fff5f5;
|
||||
border-color: #f5c6cb;
|
||||
}
|
||||
|
||||
.req-icon {
|
||||
font-size: 18px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.requirement-item.passed .req-icon { color: var(--success); }
|
||||
.requirement-item.failed .req-icon { color: var(--danger); }
|
||||
|
||||
.req-name {
|
||||
flex: 1;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.req-status {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.requirement-item.passed .req-status {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.requirement-item.failed .req-status {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.req-msg {
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
/* Alerts */
|
||||
.alert {
|
||||
padding: 14px 18px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffeeba;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background: #d1ecf1;
|
||||
border: 1px solid #bee5eb;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.alert ul {
|
||||
margin-top: 8px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
/* Log list */
|
||||
.log-list {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
background: var(--light);
|
||||
border-left: 3px solid var(--success);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* Results list */
|
||||
.results-list {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.result-item.success {
|
||||
background: #f0fff4;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.result-item.error {
|
||||
background: #fff5f5;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* Final box */
|
||||
.final-box {
|
||||
background: var(--light);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.final-box h3 {
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.final-box ol,
|
||||
.final-box ul {
|
||||
padding-left: 20px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-size: 13px;
|
||||
background: #f4f4f4;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
code.d-block {
|
||||
display: block;
|
||||
background: #f4f4f4;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.bg-light {
|
||||
background: var(--light);
|
||||
}
|
||||
|
||||
.p-2 {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.p-3 {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.mt-2 { margin-top: 8px; }
|
||||
.mt-3 { margin-top: 16px; }
|
||||
.mb-0 { margin-bottom: 0; }
|
||||
.mb-2 { margin-bottom: 8px; }
|
||||
|
||||
.border {
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.badge-admin {
|
||||
background: #007bff;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Icons via unicode/emoji fallback since no icon font */
|
||||
.icon-ok::before { content: "\2714 "; }
|
||||
.icon-error::before { content: "\2718 "; }
|
||||
.icon-warning::before { content: "\26A0 "; }
|
||||
.icon-rocket::before { content: "\1F680 "; }
|
||||
.icon-upload::before { content: "\2191 "; }
|
||||
.icon-tree::before { content: "\2328 "; }
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 600px) {
|
||||
.option-group {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.form-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
.form-actions .btn {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.installer-footer {
|
||||
text-align: center;
|
||||
padding: 16px 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -0,0 +1,533 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// =============================================================================
|
||||
// Glastree Installer - Helper Functions
|
||||
// =============================================================================
|
||||
|
||||
function getBaseUrl(): string
|
||||
{
|
||||
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https' : 'http';
|
||||
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||||
$scriptDir = dirname($_SERVER['SCRIPT_NAME']);
|
||||
$scriptDir = str_replace('/installer', '', $scriptDir);
|
||||
return $scheme . '://' . $host . $scriptDir;
|
||||
}
|
||||
|
||||
function siteUrl(string $path = ''): string
|
||||
{
|
||||
return rtrim(getBaseUrl(), '/') . '/' . ltrim($path, '/');
|
||||
}
|
||||
|
||||
function redirect(string $url): never
|
||||
{
|
||||
header('Location: ' . $url);
|
||||
exit;
|
||||
}
|
||||
|
||||
function isEnvFileExists(): bool
|
||||
{
|
||||
return file_exists(dirname(__DIR__) . '/.env');
|
||||
}
|
||||
|
||||
function e(mixed $value): string
|
||||
{
|
||||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
function old(string $key, mixed $default = ''): string
|
||||
{
|
||||
return e($_POST[$key] ?? $_SESSION['form_data'][$key] ?? $default);
|
||||
}
|
||||
|
||||
function checkRequirements(): array
|
||||
{
|
||||
$checks = [];
|
||||
|
||||
// PHP version
|
||||
$checks[] = [
|
||||
'name' => 'PHP 8.2+',
|
||||
'passed' => PHP_VERSION_ID >= 80200,
|
||||
'message' => PHP_VERSION_ID >= 80200 ? 'PHP ' . PHP_VERSION : 'Versione attuale: PHP ' . PHP_VERSION . '. Necessaria 8.2+',
|
||||
'critical' => true,
|
||||
];
|
||||
|
||||
// Extensions
|
||||
$extensions = [
|
||||
'pdo_mysql' => 'PDO MySQL',
|
||||
'mbstring' => 'MBString',
|
||||
'xml' => 'XML',
|
||||
'curl' => 'cURL',
|
||||
'zip' => 'Zip',
|
||||
'gd' => 'GD (immagini)',
|
||||
'fileinfo' => 'Fileinfo',
|
||||
];
|
||||
|
||||
foreach ($extensions as $ext => $label) {
|
||||
$checks[] = [
|
||||
'name' => $label,
|
||||
'passed' => extension_loaded($ext),
|
||||
'message' => extension_loaded($ext) ? 'Caricata' : 'Estensione "' . $ext . '" non trovata',
|
||||
'critical' => true,
|
||||
];
|
||||
}
|
||||
|
||||
// Directory permissions
|
||||
$dirs = [
|
||||
'storage' => dirname(__DIR__) . '/storage',
|
||||
'bootstrap/cache' => dirname(__DIR__) . '/bootstrap/cache',
|
||||
];
|
||||
|
||||
foreach ($dirs as $label => $dir) {
|
||||
$writable = is_dir($dir) && is_writable($dir);
|
||||
$checks[] = [
|
||||
'name' => $label . '/ scrivibile',
|
||||
'passed' => $writable,
|
||||
'message' => $writable ? 'Permessi OK' : 'Cartella non scrivibile. Esegui: chmod -R 775 ' . $dir,
|
||||
'critical' => true,
|
||||
];
|
||||
}
|
||||
|
||||
// mod_rewrite (suggested)
|
||||
$checks[] = [
|
||||
'name' => 'mod_rewrite (Apache)',
|
||||
'passed' => true,
|
||||
'message' => 'Raccomandato per URL puliti. Verifica configurazione Apache.',
|
||||
'critical' => false,
|
||||
];
|
||||
|
||||
// HTTPS
|
||||
$isHttps = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on';
|
||||
$checks[] = [
|
||||
'name' => 'HTTPS attivo',
|
||||
'passed' => $isHttps,
|
||||
'message' => $isHttps ? 'Connessione sicura' : 'Connessione non crittata. Configura SSL in produzione.',
|
||||
'critical' => false,
|
||||
];
|
||||
|
||||
return $checks;
|
||||
}
|
||||
|
||||
function allRequirementsMet(array $checks): bool
|
||||
{
|
||||
foreach ($checks as $check) {
|
||||
if (!empty($check['critical']) && !$check['passed']) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function testDatabaseConnection(string $host, int $port, string $database, string $user, string $pass): array
|
||||
{
|
||||
try {
|
||||
$dsn = "mysql:host={$host};port={$port};dbname={$database};charset=utf8mb4";
|
||||
$pdo = new PDO($dsn, $user, $pass, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_TIMEOUT => 5,
|
||||
]);
|
||||
return ['success' => true, 'message' => 'Connessione riuscita.'];
|
||||
} catch (PDOException $e) {
|
||||
return ['success' => false, 'message' => 'Errore: ' . $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
function testRootConnection(string $host, int $port, string $user, string $pass): array
|
||||
{
|
||||
try {
|
||||
$dsn = "mysql:host={$host};port={$port};charset=utf8mb4";
|
||||
$pdo = new PDO($dsn, $user, $pass, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_TIMEOUT => 5,
|
||||
]);
|
||||
return ['success' => true, 'message' => 'Connessione root riuscita.', 'pdo' => $pdo];
|
||||
} catch (PDOException $e) {
|
||||
return ['success' => false, 'message' => 'Errore: ' . $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
function createDatabaseAndUser(string $rootUser, string $rootPass, string $host, int $port, string $dbName, string $dbUser, string $dbPass): array
|
||||
{
|
||||
$result = testRootConnection($host, $port, $rootUser, $rootPass);
|
||||
if (!$result['success']) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = $result['pdo'];
|
||||
$pdo->exec("CREATE DATABASE IF NOT EXISTS `{$dbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||
$pdo->exec("CREATE USER IF NOT EXISTS '{$dbUser}'@'localhost' IDENTIFIED BY '{$dbPass}'");
|
||||
$pdo->exec("GRANT ALL ON `{$dbName}`.* TO '{$dbUser}'@'localhost'");
|
||||
$pdo->exec("FLUSH PRIVILEGES");
|
||||
return ['success' => true, 'message' => 'Database e utente creati con successo.'];
|
||||
} catch (PDOException $e) {
|
||||
return ['success' => false, 'message' => 'Errore: ' . $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
function writeEnvFile(array $params): bool
|
||||
{
|
||||
$rootDir = dirname(__DIR__);
|
||||
$examplePath = $rootDir . '/.env.example';
|
||||
$envPath = $rootDir . '/.env';
|
||||
|
||||
if (!file_exists($examplePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$content = file_get_contents($examplePath);
|
||||
|
||||
$replacements = [
|
||||
'APP_NAME=' => 'APP_NAME=' . ($params['app_name'] ?? 'Glastree'),
|
||||
'APP_ENV=' => 'APP_ENV=production',
|
||||
'APP_KEY=' => 'APP_KEY=',
|
||||
'APP_DEBUG=' => 'APP_DEBUG=false',
|
||||
'APP_URL=' => 'APP_URL=' . ($params['app_url'] ?? 'http://localhost'),
|
||||
'DB_CONNECTION=' => 'DB_CONNECTION=mysql',
|
||||
'# DB_HOST=' => 'DB_HOST=',
|
||||
'# DB_PORT=' => 'DB_PORT=',
|
||||
'# DB_DATABASE=' => 'DB_DATABASE=',
|
||||
'# DB_USERNAME=' => 'DB_USERNAME=',
|
||||
'# DB_PASSWORD=' => 'DB_PASSWORD=',
|
||||
];
|
||||
|
||||
foreach ($replacements as $search => $replace) {
|
||||
if (str_starts_with($content, $search) || preg_match('/^' . preg_quote($search, '/') . '/m', $content)) {
|
||||
$content = preg_replace(
|
||||
'/^' . preg_quote($search, '/') . '.*$/m',
|
||||
$replace . ($params['db_host'] ?? '127.0.0.1'),
|
||||
$content
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Specific DB replacements (handle commented lines)
|
||||
$content = preg_replace(
|
||||
'/^# DB_HOST=.*$/m',
|
||||
'DB_HOST=' . ($params['db_host'] ?? '127.0.0.1'),
|
||||
$content
|
||||
);
|
||||
$content = preg_replace(
|
||||
'/^# DB_PORT=.*$/m',
|
||||
'DB_PORT=' . ($params['db_port'] ?? '3306'),
|
||||
$content
|
||||
);
|
||||
$content = preg_replace(
|
||||
'/^# DB_DATABASE=.*$/m',
|
||||
'DB_DATABASE=' . ($params['db_database'] ?? 'glastree'),
|
||||
$content
|
||||
);
|
||||
$content = preg_replace(
|
||||
'/^# DB_USERNAME=.*$/m',
|
||||
'DB_USERNAME=' . ($params['db_username'] ?? 'glastree'),
|
||||
$content
|
||||
);
|
||||
$content = preg_replace(
|
||||
'/^# DB_PASSWORD=.*$/m',
|
||||
'DB_PASSWORD=' . ($params['db_password'] ?? ''),
|
||||
$content
|
||||
);
|
||||
|
||||
// Ensure APP_KEY= line exists
|
||||
if (!preg_match('/^APP_KEY=/m', $content)) {
|
||||
$content .= "\nAPP_KEY=\n";
|
||||
}
|
||||
|
||||
return file_put_contents($envPath, $content) !== false;
|
||||
}
|
||||
|
||||
function bootstrapLaravel(): object
|
||||
{
|
||||
$rootDir = dirname(__DIR__);
|
||||
require_once $rootDir . '/vendor/autoload.php';
|
||||
|
||||
$app = require $rootDir . '/bootstrap/app.php';
|
||||
$kernel = $app->make(\Illuminate\Contracts\Console\Kernel::class);
|
||||
|
||||
return $kernel;
|
||||
}
|
||||
|
||||
function runArtisanCommands(object $kernel, array $commands): array
|
||||
{
|
||||
$results = [];
|
||||
foreach ($commands as $command => $options) {
|
||||
try {
|
||||
$exitCode = $kernel->call($command, $options ?? []);
|
||||
$results[] = [
|
||||
'command' => $command,
|
||||
'success' => $exitCode === 0,
|
||||
'output' => $kernel->output(),
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
$results[] = [
|
||||
'command' => $command,
|
||||
'success' => false,
|
||||
'output' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
function restoreFromBackup(string $zipPath, string $dbUser, string $dbPass, string $dbName): array
|
||||
{
|
||||
$rootDir = dirname(__DIR__);
|
||||
$restoreDir = sys_get_temp_dir() . '/glastree-restore-' . uniqid();
|
||||
|
||||
if (!file_exists($zipPath)) {
|
||||
return ['success' => false, 'message' => 'File ZIP non trovato: ' . $zipPath];
|
||||
}
|
||||
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($zipPath) !== true) {
|
||||
return ['success' => false, 'message' => 'Impossibile aprire il file ZIP.'];
|
||||
}
|
||||
|
||||
$zip->extractTo($restoreDir);
|
||||
$zip->close();
|
||||
|
||||
$log = [];
|
||||
|
||||
// 1. Import database.sql
|
||||
$sqlFile = $restoreDir . '/database.sql';
|
||||
if (file_exists($sqlFile)) {
|
||||
$cmd = sprintf(
|
||||
'mysql --host=%s --port=%s --user=%s --password=%s %s < %s 2>&1',
|
||||
escapeshellarg($_SESSION['db_host'] ?? '127.0.0.1'),
|
||||
escapeshellarg((string)($_SESSION['db_port'] ?? '3306')),
|
||||
escapeshellarg($dbUser),
|
||||
escapeshellarg($dbPass),
|
||||
escapeshellarg($dbName),
|
||||
escapeshellarg($sqlFile)
|
||||
);
|
||||
exec($cmd, $output, $exitCode);
|
||||
if ($exitCode === 0) {
|
||||
$log[] = 'Database importato con successo.';
|
||||
} else {
|
||||
$log[] = 'Errore import database: ' . implode("\n", $output);
|
||||
return ['success' => false, 'message' => 'Import database fallito.', 'log' => $log];
|
||||
}
|
||||
} else {
|
||||
$log[] = 'database.sql non trovato nel backup.';
|
||||
}
|
||||
|
||||
// 2. Copy files to storage
|
||||
$filesDir = $restoreDir . '/files';
|
||||
$targetDir = $rootDir . '/storage/app/documenti';
|
||||
if (is_dir($filesDir)) {
|
||||
if (!is_dir($targetDir)) {
|
||||
mkdir($targetDir, 0755, true);
|
||||
}
|
||||
$files = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($filesDir, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::SELF_FIRST
|
||||
);
|
||||
foreach ($files as $file) {
|
||||
$relative = substr($file->getPathname(), strlen($filesDir) + 1);
|
||||
$target = $targetDir . '/' . $relative;
|
||||
if ($file->isDir()) {
|
||||
if (!is_dir($target)) {
|
||||
mkdir($target, 0755, true);
|
||||
}
|
||||
} else {
|
||||
copy($file->getPathname(), $target);
|
||||
}
|
||||
}
|
||||
$log[] = 'File documenti ripristinati.';
|
||||
} else {
|
||||
$log[] = 'Cartella files non trovata nel backup.';
|
||||
}
|
||||
|
||||
// 3. Merge .env if present (preserving current DB credentials)
|
||||
$envBackup = $restoreDir . '/.env';
|
||||
if (file_exists($envBackup)) {
|
||||
$currentEnv = file_get_contents($envBackup);
|
||||
$currentEnv = preg_replace('/^DB_HOST=.*$/m', 'DB_HOST=' . ($_SESSION['db_host'] ?? '127.0.0.1'), $currentEnv);
|
||||
$currentEnv = preg_replace('/^DB_PORT=.*$/m', 'DB_PORT=' . ($_SESSION['db_port'] ?? '3306'), $currentEnv);
|
||||
$currentEnv = preg_replace('/^DB_DATABASE=.*$/m', 'DB_DATABASE=' . $dbName, $currentEnv);
|
||||
$currentEnv = preg_replace('/^DB_USERNAME=.*$/m', 'DB_USERNAME=' . $dbUser, $currentEnv);
|
||||
$currentEnv = preg_replace('/^DB_PASSWORD=.*$/m', 'DB_PASSWORD=' . $dbPass, $currentEnv);
|
||||
|
||||
// Only merge if APP_URL differs from localhost
|
||||
if (str_contains($currentEnv, 'APP_URL=http://localhost') || str_contains($currentEnv, 'APP_URL=')) {
|
||||
$currentEnv = preg_replace(
|
||||
'/^APP_URL=.*$/m',
|
||||
'APP_URL=' . getBaseUrl(),
|
||||
$currentEnv
|
||||
);
|
||||
}
|
||||
|
||||
file_put_contents($rootDir . '/.env', $currentEnv);
|
||||
$log[] = '.env ripristinato dal backup (credenziali DB preservate).';
|
||||
}
|
||||
|
||||
// 4. Copy manifest if exists
|
||||
if (file_exists($restoreDir . '/manifest.json')) {
|
||||
$log[] = 'Manifest: ' . file_get_contents($restoreDir . '/manifest.json');
|
||||
}
|
||||
|
||||
// Cleanup temp
|
||||
$it = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($restoreDir, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
foreach ($it as $file) {
|
||||
$file->isDir() ? rmdir($file->getPathname()) : unlink($file->getPathname());
|
||||
}
|
||||
rmdir($restoreDir);
|
||||
|
||||
return ['success' => true, 'message' => 'Backup ripristinato con successo.', 'log' => $log];
|
||||
}
|
||||
|
||||
function createSuperAdmin(string $name, string $email, string $password): array
|
||||
{
|
||||
try {
|
||||
$kernel = bootstrapLaravel();
|
||||
$kernel->call('db:seed', ['--force' => true, '--class' => 'Database\\Seeders\\DiocesiSeeder']);
|
||||
$kernel->call('db:seed', ['--force' => true, '--class' => 'Database\\Seeders\\ComuniSeeder']);
|
||||
$kernel->call('db:seed', ['--force' => true, '--class' => 'Database\\Seeders\\RoleSeeder']);
|
||||
$kernel->call('db:seed', ['--force' => true, '--class' => 'Database\\Seeders\\RolePresetSeeder']);
|
||||
} catch (\Exception $e) {
|
||||
// Seeder might fail if already seeded, that's OK
|
||||
}
|
||||
|
||||
try {
|
||||
$user = new \App\Models\User();
|
||||
$user->name = $name;
|
||||
$user->email = $email;
|
||||
$user->password = \Illuminate\Support\Facades\Hash::make($password);
|
||||
$user->is_admin = true;
|
||||
$user->status = 'active';
|
||||
$user->role_preset_id = 1;
|
||||
$user->permissions = [
|
||||
'individui' => 2,
|
||||
'gruppi' => 2,
|
||||
'eventi' => 2,
|
||||
'documenti' => 2,
|
||||
'mailing' => 2,
|
||||
'viste' => 2,
|
||||
'report' => 2,
|
||||
'settings' => 2,
|
||||
];
|
||||
$user->save();
|
||||
|
||||
return ['success' => true, 'message' => 'Amministratore creato con successo.'];
|
||||
} catch (\Exception $e) {
|
||||
return ['success' => false, 'message' => 'Errore creazione admin: ' . $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
function getExistingUsers(): array
|
||||
{
|
||||
try {
|
||||
$kernel = bootstrapLaravel();
|
||||
$users = \App\Models\User::all(['id', 'name', 'email', 'is_admin'])->toArray();
|
||||
return $users;
|
||||
} catch (\Exception $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function selfDestruct(): bool
|
||||
{
|
||||
$installerDir = __DIR__;
|
||||
|
||||
if (!is_dir($installerDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$it = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($installerDir, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
foreach ($it as $file) {
|
||||
if ($file->isDir()) {
|
||||
rmdir($file->getPathname());
|
||||
} else {
|
||||
unlink($file->getPathname());
|
||||
}
|
||||
}
|
||||
|
||||
rmdir($installerDir);
|
||||
|
||||
return !is_dir($installerDir);
|
||||
}
|
||||
|
||||
function renderHeader(string $title, string $step = ''): void
|
||||
{
|
||||
$stepLabel = $step ? " — Step {$step}" : '';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= e($title) ?> — Glastree Installer</title>
|
||||
<link rel="stylesheet" href="assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="installer-wrapper">
|
||||
<div class="installer-header">
|
||||
<div class="container">
|
||||
<h1><i class="icon-tree"></i> Glastree</h1>
|
||||
<p class="subtitle">Installazione Wizard</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<?php if ($step): ?>
|
||||
<div class="step-indicator">Passo <?= e($step) ?> di 6</div>
|
||||
<?php endif; ?>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<?php
|
||||
}
|
||||
|
||||
function renderFooter(): void
|
||||
{
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="installer-footer">
|
||||
<div class="container">
|
||||
<p>Glastree © <?= date('Y') ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
|
||||
function renderErrors(array $errors): void
|
||||
{
|
||||
if (empty($errors)) return;
|
||||
?>
|
||||
<div class="alert alert-danger">
|
||||
<strong><i class="icon-error"></i> Errori:</strong>
|
||||
<ul class="mb-0">
|
||||
<?php foreach ($errors as $error): ?>
|
||||
<li><?= e($error) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
function renderSuccess(string $message, array $log = []): void
|
||||
{
|
||||
?>
|
||||
<div class="alert alert-success">
|
||||
<strong><i class="icon-ok"></i> <?= e($message) ?></strong>
|
||||
<?php if (!empty($log)): ?>
|
||||
<ul class="mb-0 mt-2">
|
||||
<?php foreach ($log as $item): ?>
|
||||
<li><?= e($item) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// =============================================================================
|
||||
// Glastree - Installer Entry Point
|
||||
// =============================================================================
|
||||
// Se .env esiste già, l'app è installata → redirect a login.
|
||||
// Altrimenti avvia il wizard di installazione.
|
||||
// =============================================================================
|
||||
|
||||
$rootDir = dirname(__DIR__);
|
||||
|
||||
if (file_exists($rootDir . '/.env')) {
|
||||
// App gia' installata
|
||||
$baseUrl = rtrim((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost') . dirname($_SERVER['SCRIPT_NAME']), '/');
|
||||
$baseUrl = str_replace('/installer', '', $baseUrl);
|
||||
header('Location: ' . $baseUrl . '/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Avvia il wizard
|
||||
session_start();
|
||||
$_SESSION['install_mode'] = $_SESSION['install_mode'] ?? '';
|
||||
$_SESSION['install_step'] = 1;
|
||||
|
||||
header('Location: setup.php?step=1');
|
||||
exit;
|
||||
@@ -0,0 +1,711 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// =============================================================================
|
||||
// Glastree Installer - Wizard Setup
|
||||
// =============================================================================
|
||||
|
||||
session_start();
|
||||
|
||||
require_once __DIR__ . '/functions.php';
|
||||
|
||||
// If already installed (and not in the middle of install), block
|
||||
if (isEnvFileExists() && empty($_SESSION['install_in_progress'])) {
|
||||
redirect('/login');
|
||||
}
|
||||
|
||||
$step = max(1, min(6, (int) ($_GET['step'] ?? 1)));
|
||||
$errors = [];
|
||||
$success = '';
|
||||
|
||||
// ─── Step 1: Welcome + Mode Selection ──────────────────────────────────────
|
||||
|
||||
if ($step === 1) {
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$mode = $_POST['mode'] ?? '';
|
||||
if (!in_array($mode, ['fresh', 'backup'], true)) {
|
||||
$errors[] = 'Seleziona una modalita di installazione.';
|
||||
} else {
|
||||
$_SESSION['install_mode'] = $mode;
|
||||
$_SESSION['install_in_progress'] = true;
|
||||
redirect('setup.php?step=2');
|
||||
}
|
||||
}
|
||||
|
||||
renderHeader('Benvenuto', '1');
|
||||
?>
|
||||
<h2>Benvenuto nell'installazione di Glastree</h2>
|
||||
<p>Questo wizard ti guidera nella configurazione dell'applicazione su questo server.</p>
|
||||
|
||||
<?php renderErrors($errors); ?>
|
||||
|
||||
<form method="POST">
|
||||
<div class="option-group">
|
||||
<label class="option-card <?= ($_SESSION['install_mode'] ?? '') === 'fresh' ? 'selected' : '' ?>">
|
||||
<input type="radio" name="mode" value="fresh" class="option-radio"
|
||||
<?= ($_SESSION['install_mode'] ?? '') === 'fresh' ? 'checked' : '' ?>>
|
||||
<div class="option-content">
|
||||
<div class="option-icon"><i class="icon-rocket"></i></div>
|
||||
<h3>Fresh Install</h3>
|
||||
<p>Crea un database nuovo, esegue migration e seed, e configura l'applicazione da zero.</p>
|
||||
<ul>
|
||||
<li>Database nuovo</li>
|
||||
<li>Migration + dati di base</li>
|
||||
<li>Crea amministratore</li>
|
||||
</ul>
|
||||
</div>
|
||||
</label>
|
||||
<label class="option-card <?= ($_SESSION['install_mode'] ?? '') === 'backup' ? 'selected' : '' ?>">
|
||||
<input type="radio" name="mode" value="backup" class="option-radio"
|
||||
<?= ($_SESSION['install_mode'] ?? '') === 'backup' ? 'checked' : '' ?>>
|
||||
<div class="option-content">
|
||||
<div class="option-icon"><i class="icon-upload"></i></div>
|
||||
<h3>Restore da Backup</h3>
|
||||
<p>Carica un file ZIP di backup per ripristinare un'installazione esistente su questo server.</p>
|
||||
<ul>
|
||||
<li>Upload file ZIP</li>
|
||||
<li>Importa database</li>
|
||||
<li>Ripristina documenti</li>
|
||||
</ul>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Avanti →</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php
|
||||
renderFooter();
|
||||
}
|
||||
|
||||
// ─── Step 2: Requirements Check ─────────────────────────────────────────────
|
||||
|
||||
if ($step === 2) {
|
||||
$checks = checkRequirements();
|
||||
$allMet = allRequirementsMet($checks);
|
||||
$nonCriticalFailed = false;
|
||||
|
||||
foreach ($checks as $check) {
|
||||
if (!$check['passed'] && empty($check['critical'])) {
|
||||
$nonCriticalFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $allMet) {
|
||||
redirect('setup.php?step=3');
|
||||
}
|
||||
|
||||
renderHeader('Verifica Requisiti', '2');
|
||||
?>
|
||||
<h2>Verifica Requisiti di Sistema</h2>
|
||||
<p>Il sistema controlla che il server soddisfi i requisiti minimi per eseguire Glastree.</p>
|
||||
|
||||
<?php renderErrors($errors); ?>
|
||||
|
||||
<div class="requirements-list">
|
||||
<?php foreach ($checks as $check): ?>
|
||||
<div class="requirement-item <?= $check['passed'] ? 'passed' : 'failed' ?>">
|
||||
<span class="req-icon">
|
||||
<?php if ($check['passed']): ?>
|
||||
<i class="icon-ok"></i>
|
||||
<?php else: ?>
|
||||
<i class="icon-error"></i>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<span class="req-name"><?= e($check['name']) ?></span>
|
||||
<span class="req-status"><?= $check['passed'] ? 'OK' : 'ERRORE' ?></span>
|
||||
<?php if (!$check['passed']): ?>
|
||||
<span class="req-msg"><?= e($check['message']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!$allMet): ?>
|
||||
<div class="alert alert-danger">
|
||||
<i class="icon-error"></i>
|
||||
Requisiti critici non soddisfatti. Risolvi gli errori sopra e ricarica la pagina.
|
||||
</div>
|
||||
<?php elseif ($nonCriticalFailed): ?>
|
||||
<div class="alert alert-warning">
|
||||
<i class="icon-warning"></i>
|
||||
Alcuni requisiti non critici non sono soddisfatti. Puoi proseguire, ma alcune funzionalita potrebbero essere limitate.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST">
|
||||
<div class="form-actions">
|
||||
<a href="setup.php?step=1" class="btn btn-secondary">← Indietro</a>
|
||||
<?php if ($allMet): ?>
|
||||
<button type="submit" class="btn btn-primary">Avanti →</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</form>
|
||||
<?php
|
||||
renderFooter();
|
||||
}
|
||||
|
||||
// ─── Step 3: Database Configuration ─────────────────────────────────────────
|
||||
|
||||
if ($step === 3) {
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? '';
|
||||
$dbHost = $_POST['db_host'] ?? '127.0.0.1';
|
||||
$dbPort = (int) ($_POST['db_port'] ?? 3306);
|
||||
|
||||
if ($action === 'test_root') {
|
||||
// Test root credentials
|
||||
$rootUser = $_POST['root_user'] ?? 'root';
|
||||
$rootPass = $_POST['root_pass'] ?? '';
|
||||
$result = testRootConnection($dbHost, $dbPort, $rootUser, $rootPass);
|
||||
if ($result['success']) {
|
||||
$_SESSION['root_user'] = $rootUser;
|
||||
$_SESSION['root_pass'] = $rootPass;
|
||||
$success = 'Connessione root riuscita. Ora puoi creare il database.';
|
||||
} else {
|
||||
$errors[] = $result['message'];
|
||||
}
|
||||
} elseif ($action === 'create_db') {
|
||||
// Create database + user
|
||||
$rootUser = $_SESSION['root_user'] ?? 'root';
|
||||
$rootPass = $_SESSION['root_pass'] ?? '';
|
||||
$dbName = $_POST['db_name'] ?? 'glastree';
|
||||
$dbUser = $_POST['db_user'] ?? 'glastree';
|
||||
$dbPass = $_POST['db_pass'] ?? '';
|
||||
$appUrl = $_POST['app_url'] ?? getBaseUrl();
|
||||
|
||||
if (strlen($dbPass) < 6) {
|
||||
$errors[] = 'La password del database deve essere almeno 6 caratteri.';
|
||||
} else {
|
||||
$result = createDatabaseAndUser($rootUser, $rootPass, $dbHost, $dbPort, $dbName, $dbUser, $dbPass);
|
||||
if ($result['success']) {
|
||||
$_SESSION['db_host'] = $dbHost;
|
||||
$_SESSION['db_port'] = $dbPort;
|
||||
$_SESSION['db_name'] = $dbName;
|
||||
$_SESSION['db_user'] = $dbUser;
|
||||
$_SESSION['db_pass'] = $dbPass;
|
||||
$_SESSION['app_url'] = $appUrl;
|
||||
|
||||
// Write .env
|
||||
$envWritten = writeEnvFile([
|
||||
'app_url' => $appUrl,
|
||||
'db_host' => $dbHost,
|
||||
'db_port' => (string) $dbPort,
|
||||
'db_database' => $dbName,
|
||||
'db_username' => $dbUser,
|
||||
'db_password' => $dbPass,
|
||||
]);
|
||||
|
||||
if ($envWritten) {
|
||||
redirect('setup.php?step=4');
|
||||
} else {
|
||||
$errors[] = 'Impossibile scrivere il file .env.';
|
||||
}
|
||||
} else {
|
||||
$errors[] = $result['message'];
|
||||
}
|
||||
}
|
||||
} elseif ($action === 'test_existing') {
|
||||
// Test existing database connection
|
||||
$dbName = $_POST['db_name'] ?? '';
|
||||
$dbUser = $_POST['db_user'] ?? '';
|
||||
$dbPass = $_POST['db_pass'] ?? '';
|
||||
$appUrl = $_POST['app_url'] ?? getBaseUrl();
|
||||
|
||||
if (empty($dbName) || empty($dbUser)) {
|
||||
$errors[] = 'Compila tutti i campi obbligatori.';
|
||||
} else {
|
||||
$result = testDatabaseConnection($dbHost, $dbPort, $dbName, $dbUser, $dbPass);
|
||||
if ($result['success']) {
|
||||
$_SESSION['db_host'] = $dbHost;
|
||||
$_SESSION['db_port'] = $dbPort;
|
||||
$_SESSION['db_name'] = $dbName;
|
||||
$_SESSION['db_user'] = $dbUser;
|
||||
$_SESSION['db_pass'] = $dbPass;
|
||||
$_SESSION['app_url'] = $appUrl;
|
||||
|
||||
$envWritten = writeEnvFile([
|
||||
'app_url' => $appUrl,
|
||||
'db_host' => $dbHost,
|
||||
'db_port' => (string) $dbPort,
|
||||
'db_database' => $dbName,
|
||||
'db_username' => $dbUser,
|
||||
'db_password' => $dbPass,
|
||||
]);
|
||||
|
||||
if ($envWritten) {
|
||||
redirect('setup.php?step=4');
|
||||
} else {
|
||||
$errors[] = 'Impossibile scrivere il file .env.';
|
||||
}
|
||||
} else {
|
||||
$errors[] = $result['message'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$errors[] = 'Azione non valida.';
|
||||
}
|
||||
}
|
||||
|
||||
renderHeader('Configurazione Database', '3');
|
||||
?>
|
||||
<h2>Configurazione Database</h2>
|
||||
<p>Configura la connessione al database MySQL.</p>
|
||||
|
||||
<?php renderErrors($errors); ?>
|
||||
<?php if ($success): ?>
|
||||
<div class="alert alert-success"><i class="icon-ok"></i> <?= e($success) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST" id="db-form">
|
||||
<div class="form-group">
|
||||
<label for="db_host">Host MySQL</label>
|
||||
<input type="text" name="db_host" id="db_host" class="form-control" value="<?= old('db_host', '127.0.0.1') ?>" placeholder="127.0.0.1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="db_port">Porta</label>
|
||||
<input type="number" name="db_port" id="db_port" class="form-control" value="<?= old('db_port', '3306') ?>" placeholder="3306">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="app_url">URL Applicazione</label>
|
||||
<input type="text" name="app_url" id="app_url" class="form-control" value="<?= old('app_url', getBaseUrl()) ?>" placeholder="https://glastree.esempio.it">
|
||||
<small class="form-text text-muted">L'indirizzo pubblico con cui si accedera a Glastree</small>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<h4>Modalita A: Crea Nuovo Database</h4>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="root_user">Utente Root MySQL</label>
|
||||
<input type="text" name="root_user" id="root_user" class="form-control" value="<?= old('root_user', 'root') ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="root_pass">Password Root</label>
|
||||
<input type="password" name="root_pass" id="root_pass" class="form-control" value="">
|
||||
</div>
|
||||
<button type="submit" name="action" value="test_root" class="btn btn-secondary">
|
||||
<i class="icon-ok"></i> Test Connessione Root
|
||||
</button>
|
||||
|
||||
<div class="mt-3 p-3 bg-light border rounded">
|
||||
<h5>Nuovo Database da Creare</h5>
|
||||
<div class="form-group">
|
||||
<label for="db_name">Nome Database</label>
|
||||
<input type="text" name="db_name" class="form-control" value="<?= old('db_name', 'glastree') ?>" placeholder="glastree">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="db_user">Utente Database</label>
|
||||
<input type="text" name="db_user" class="form-control" value="<?= old('db_user', 'glastree') ?>" placeholder="glastree">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="db_pass">Password Utente</label>
|
||||
<input type="password" name="db_pass" class="form-control" value="">
|
||||
</div>
|
||||
<button type="submit" name="action" value="create_db" class="btn btn-primary">
|
||||
<i class="icon-rocket"></i> Crea Database e Continua
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<h4>Modalita B: Usa Database Esistente</h4>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="db_name_ex">Nome Database</label>
|
||||
<input type="text" name="db_name" class="form-control" value="<?= old('db_name', 'glastree') ?>" placeholder="glastree">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="db_user_ex">Utente</label>
|
||||
<input type="text" name="db_user" class="form-control" value="<?= old('db_user', 'glastree') ?>" placeholder="glastree">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="db_pass_ex">Password</label>
|
||||
<input type="password" name="db_pass" class="form-control" value="">
|
||||
</div>
|
||||
<button type="submit" name="action" value="test_existing" class="btn btn-primary">
|
||||
<i class="icon-ok"></i> Test e Continua
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="form-actions mt-3">
|
||||
<a href="setup.php?step=2" class="btn btn-secondary">← Indietro</a>
|
||||
</div>
|
||||
<?php
|
||||
renderFooter();
|
||||
}
|
||||
|
||||
// ─── Step 4: Install (Fresh: migrate+seed / Backup: upload+restore) ────────
|
||||
|
||||
if ($step === 4) {
|
||||
$mode = $_SESSION['install_mode'] ?? 'fresh';
|
||||
$installDone = $_SESSION['install_done'] ?? false;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($mode === 'fresh') {
|
||||
// Run Artisan commands
|
||||
try {
|
||||
$kernel = bootstrapLaravel();
|
||||
|
||||
$commands = [
|
||||
'key:generate' => ['--force' => true],
|
||||
'migrate' => ['--force' => true],
|
||||
'db:seed' => ['--force' => true],
|
||||
'storage:link' => [],
|
||||
];
|
||||
|
||||
$results = runArtisanCommands($kernel, $commands);
|
||||
$allOk = true;
|
||||
$log = [];
|
||||
foreach ($results as $r) {
|
||||
$log[] = ($r['success'] ? 'OK' : 'ERRORE') . ': ' . $r['command'];
|
||||
if (!$r['success']) $allOk = false;
|
||||
}
|
||||
|
||||
if ($allOk) {
|
||||
$_SESSION['install_done'] = true;
|
||||
$success = 'Installazione completata con successo!';
|
||||
$log[] = 'Ora puoi creare l\'amministratore.';
|
||||
} else {
|
||||
$errors[] = 'Alcuni comandi sono falliti. Verifica i log.';
|
||||
foreach ($log as $l) $errors[] = $l;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$errors[] = 'Errore: ' . $e->getMessage();
|
||||
}
|
||||
} elseif ($mode === 'backup') {
|
||||
// Handle backup upload
|
||||
if (!empty($_FILES['backup_file']) && $_FILES['backup_file']['error'] === UPLOAD_ERR_OK) {
|
||||
$uploadedFile = $_FILES['backup_file']['tmp_name'];
|
||||
$originalName = $_FILES['backup_file']['name'];
|
||||
|
||||
$result = restoreFromBackup(
|
||||
$uploadedFile,
|
||||
$_SESSION['db_user'] ?? '',
|
||||
$_SESSION['db_pass'] ?? '',
|
||||
$_SESSION['db_name'] ?? ''
|
||||
);
|
||||
|
||||
if ($result['success']) {
|
||||
$_SESSION['install_done'] = true;
|
||||
$success = 'Backup ripristinato con successo!';
|
||||
$log = $result['log'] ?? [];
|
||||
} else {
|
||||
$errors[] = $result['message'];
|
||||
if (!empty($result['log'])) {
|
||||
foreach ($result['log'] as $l) $errors[] = $l;
|
||||
}
|
||||
}
|
||||
} elseif (!empty($_POST['backup_path'])) {
|
||||
// Backup file already on server
|
||||
$serverPath = $_POST['backup_path'];
|
||||
|
||||
if (!file_exists($serverPath)) {
|
||||
$errors[] = 'File non trovato: ' . $serverPath;
|
||||
} else {
|
||||
$result = restoreFromBackup(
|
||||
$serverPath,
|
||||
$_SESSION['db_user'] ?? '',
|
||||
$_SESSION['db_pass'] ?? '',
|
||||
$_SESSION['db_name'] ?? ''
|
||||
);
|
||||
|
||||
if ($result['success']) {
|
||||
$_SESSION['install_done'] = true;
|
||||
$success = 'Backup ripristinato con successo!';
|
||||
} else {
|
||||
$errors[] = $result['message'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$errors[] = 'Seleziona un file di backup o fornisci il percorso.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderHeader($mode === 'fresh' ? 'Installazione' : 'Ripristino Backup', '4');
|
||||
?>
|
||||
<h2><?= $mode === 'fresh' ? 'Installazione Database' : 'Ripristino Backup' ?></h2>
|
||||
|
||||
<?php renderErrors($errors); ?>
|
||||
<?php if ($success): ?>
|
||||
<div class="alert alert-success"><i class="icon-ok"></i> <?= e($success) ?></div>
|
||||
<?php if (!empty($log)): ?>
|
||||
<div class="log-list">
|
||||
<?php foreach ($log as $l): ?>
|
||||
<div class="log-item"><?= e($l) ?></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($installDone): ?>
|
||||
<div class="alert alert-info">
|
||||
<i class="icon-ok"></i> Installazione completata. Procedi al passo successivo.
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<a href="setup.php?step=5" class="btn btn-primary">Avanti →</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
|
||||
<?php if ($mode === 'fresh'): ?>
|
||||
<form method="POST">
|
||||
<p>Clicca "Installa" per eseguire migration, seed e generare la chiave dell'applicazione.</p>
|
||||
<div class="form-actions">
|
||||
<a href="setup.php?step=3" class="btn btn-secondary">← Indietro</a>
|
||||
<button type="submit" class="btn btn-primary"><i class="icon-rocket"></i> Installa</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="backup_file">Carica file ZIP di backup</label>
|
||||
<input type="file" name="backup_file" id="backup_file" class="form-control" accept=".zip">
|
||||
</div>
|
||||
<hr>
|
||||
<p class="text-muted">oppure specifica il percorso sul server:</p>
|
||||
<div class="form-group">
|
||||
<label for="backup_path">Percorso server del file ZIP</label>
|
||||
<input type="text" name="backup_path" id="backup_path" class="form-control" placeholder="/tmp/backup.zip">
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<a href="setup.php?step=3" class="btn btn-secondary">← Indietro</a>
|
||||
<button type="submit" class="btn btn-primary"><i class="icon-upload"></i> Ripristina Backup</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
renderFooter();
|
||||
}
|
||||
|
||||
// ─── Step 5: Admin User Creation ────────────────────────────────────────────
|
||||
|
||||
if ($step === 5) {
|
||||
$mode = $_SESSION['install_mode'] ?? 'fresh';
|
||||
$adminCreated = false;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($mode === 'fresh') {
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
$passwordConfirm = $_POST['password_confirm'] ?? '';
|
||||
|
||||
if (empty($name)) $errors[] = 'Il nome e obbligatorio.';
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = 'Email non valida.';
|
||||
if (strlen($password) < 8) $errors[] = 'La password deve essere almeno 8 caratteri.';
|
||||
if ($password !== $passwordConfirm) $errors[] = 'Le password non coincidono.';
|
||||
|
||||
if (empty($errors)) {
|
||||
$result = createSuperAdmin($name, $email, $password);
|
||||
if ($result['success']) {
|
||||
$adminCreated = true;
|
||||
$_SESSION['admin_created'] = true;
|
||||
} else {
|
||||
$errors[] = $result['message'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Backup mode: skip or create additional admin
|
||||
if (!empty($_POST['name']) && !empty($_POST['email']) && !empty($_POST['password'])) {
|
||||
$result = createSuperAdmin($_POST['name'], $_POST['email'], $_POST['password']);
|
||||
if ($result['success']) {
|
||||
$adminCreated = true;
|
||||
} else {
|
||||
$errors[] = $result['message'];
|
||||
}
|
||||
} else {
|
||||
$_SESSION['admin_created'] = true;
|
||||
$adminCreated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check existing users (backup mode)
|
||||
$existingUsers = [];
|
||||
if ($mode === 'backup' && file_exists(dirname(__DIR__) . '/.env')) {
|
||||
try {
|
||||
$existingUsers = getExistingUsers();
|
||||
} catch (\Exception $e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
renderHeader('Amministratore', '5');
|
||||
?>
|
||||
<h2><?= $mode === 'fresh' ? 'Crea Amministratore' : 'Verifica Utenti' ?></h2>
|
||||
|
||||
<?php renderErrors($errors); ?>
|
||||
<?php if ($success): ?>
|
||||
<div class="alert alert-success"><?= e($success) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($adminCreated || !empty($_SESSION['admin_created'])): ?>
|
||||
<div class="alert alert-success">
|
||||
<i class="icon-ok"></i>
|
||||
<?php if ($mode === 'fresh'): ?>
|
||||
Amministratore creato con successo!
|
||||
<?php else: ?>
|
||||
Utenti configurati.
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<a href="setup.php?step=6" class="btn btn-primary">Avanti →</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
|
||||
<?php if (!empty($existingUsers)): ?>
|
||||
<div class="alert alert-info">
|
||||
<strong>Utenti trovati nel database ripristinato:</strong>
|
||||
<ul>
|
||||
<?php foreach ($existingUsers as $u): ?>
|
||||
<li><?= e($u['name']) ?> <<?= e($u['email']) ?>>
|
||||
<?= $u['is_admin'] ? '<span class="badge badge-admin">Superadmin</span>' : '' ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<p>Puoi proseguire senza creare nuovi utenti.</p>
|
||||
</div>
|
||||
<form method="POST" class="mt-3">
|
||||
<button type="submit" class="btn btn-secondary">Salta (usa utenti esistenti)</button>
|
||||
</form>
|
||||
<hr>
|
||||
<p class="text-muted">oppure crea un nuovo amministratore:</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
<label for="name">Nome Amministratore</label>
|
||||
<input type="text" name="name" id="name" class="form-control" value="<?= old('name', 'Amministratore') ?>" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" name="email" id="email" class="form-control" value="<?= old('email', 'admin@glastree.local') ?>" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password (min. 8 caratteri)</label>
|
||||
<input type="password" name="password" id="password" class="form-control" required minlength="8">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password_confirm">Conferma Password</label>
|
||||
<input type="password" name="password_confirm" id="password_confirm" class="form-control" required minlength="8">
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<a href="setup.php?step=4" class="btn btn-secondary">← Indietro</a>
|
||||
<button type="submit" class="btn btn-primary"><i class="icon-ok"></i> Crea Amministratore</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
renderFooter();
|
||||
}
|
||||
|
||||
// ─── Step 6: Finalize ───────────────────────────────────────────────────────
|
||||
|
||||
if ($step === 6) {
|
||||
$finalized = false;
|
||||
$results = [];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
try {
|
||||
if (!file_exists(dirname(__DIR__) . '/.env')) {
|
||||
$errors[] = 'File .env non trovato. Qualcosa e andato storto.';
|
||||
} else {
|
||||
$kernel = bootstrapLaravel();
|
||||
$commands = [
|
||||
'config:cache' => [],
|
||||
'route:cache' => [],
|
||||
'view:cache' => [],
|
||||
];
|
||||
|
||||
$results = runArtisanCommands($kernel, $commands);
|
||||
$allOk = true;
|
||||
foreach ($results as $r) {
|
||||
if (!$r['success']) $allOk = false;
|
||||
}
|
||||
|
||||
if ($allOk) {
|
||||
// Self-destruct
|
||||
selfDestruct();
|
||||
$finalized = true;
|
||||
} else {
|
||||
$errors[] = 'Alcune operazioni sono fallite.';
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$errors[] = 'Errore: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$finalized && empty($errors)) {
|
||||
// Initial display
|
||||
renderHeader('Finalizzazione', '6');
|
||||
?>
|
||||
<h2>Finalizzazione</h2>
|
||||
<p>Completa l'installazione generando la cache e rimuovendo i file di setup.</p>
|
||||
|
||||
<?php renderErrors($errors); ?>
|
||||
|
||||
<form method="POST">
|
||||
<div class="form-actions">
|
||||
<a href="setup.php?step=5" class="btn btn-secondary">← Indietro</a>
|
||||
<button type="submit" class="btn btn-primary"><i class="icon-ok"></i> Completa Installazione</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php
|
||||
renderFooter();
|
||||
} else {
|
||||
// Show results
|
||||
renderHeader('Installazione Completata', '6');
|
||||
?>
|
||||
<h2>Installazione Completata!</h2>
|
||||
|
||||
<?php renderErrors($errors); ?>
|
||||
|
||||
<?php if (!empty($results)): ?>
|
||||
<div class="results-list">
|
||||
<?php foreach ($results as $r): ?>
|
||||
<div class="result-item <?= $r['success'] ? 'success' : 'error' ?>">
|
||||
<span class="result-icon"><?= $r['success'] ? '<i class="icon-ok"></i>' : '<i class="icon-error"></i>' ?></span>
|
||||
<span class="result-name"><?= e($r['command']) ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($finalized): ?>
|
||||
<div class="alert alert-success">
|
||||
<i class="icon-ok"></i>
|
||||
<strong>Glastree e stato installato con successo!</strong>
|
||||
<p class="mb-0 mt-2">La cartella <code>installer/</code> e stata eliminata per sicurezza.</p>
|
||||
</div>
|
||||
|
||||
<div class="final-box">
|
||||
<h3>Prossimi passi:</h3>
|
||||
<ol>
|
||||
<li><strong>Configura SSL</strong> con Let's Encrypt per connessioni sicure</li>
|
||||
<li>Accedi con le credenziali amministratore</li>
|
||||
<li>Vai su <strong>Impostazioni</strong> per configurare:
|
||||
<ul>
|
||||
<li>Nome applicazione e logo</li>
|
||||
<li>Email (SMTP e IMAP)</li>
|
||||
<li>Repository remoti (Google Drive, Nextcloud)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Vai su <strong>Admin</strong> per creare altri utenti</li>
|
||||
<li>Configura il cron per backup automatico:
|
||||
<code class="d-block mt-2 p-2 bg-light">* * * * * cd <?= e(dirname(__DIR__)) ?> && php artisan schedule:run >> /dev/null 2>&1</code>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="<?= e(getBaseUrl()) ?>/login" class="btn btn-primary btn-lg">
|
||||
<i class="icon-ok"></i> Vai al Login →
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
renderFooter();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user