From 49e147c5b5e70110b6563f30a14637f2820c2837 Mon Sep 17 00:00:00 2001 From: Yasen Pramatarov Date: Thu, 25 Sep 2025 11:50:29 +0300 Subject: [PATCH] Moves imgration flag to DB with fallback to file --- app/core/Maintenance.php | 51 ++++++++++++++++++++++++++++++++- app/core/Settings.php | 54 +++++++++++++++++++++++++++++++++++ app/templates/maintenance.php | 5 ++++ doc/database/README.md | 7 +++++ 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 app/core/Settings.php diff --git a/app/core/Maintenance.php b/app/core/Maintenance.php index 253f9db..f63752c 100644 --- a/app/core/Maintenance.php +++ b/app/core/Maintenance.php @@ -4,15 +4,40 @@ namespace App\Core; class Maintenance { - public const FLAG_PATH = __DIR__ . '/../../storage/maintenance.flag'; + // Keep it simple: store the flag within the app directory + public const FLAG_PATH = __DIR__ . '/../../app/.maintenance.flag'; public static function isEnabled(): bool { + if (getenv('JILO_MAINTENANCE') === '1') { + return true; + } + // Prefer DB settings if available in the current request + if (isset($GLOBALS['db'])) { + try { + require_once __DIR__ . '/Settings.php'; + $settings = new Settings($GLOBALS['db']); + return $settings->get('maintenance_enabled', '0') === '1'; + } catch (\Throwable $e) { + // fall back to file flag + } + } return file_exists(self::FLAG_PATH); } public static function enable(string $message = ''): bool { + if (isset($GLOBALS['db'])) { + try { + require_once __DIR__ . '/Settings.php'; + $settings = new Settings($GLOBALS['db']); + $ok1 = $settings->set('maintenance_enabled', '1'); + $ok2 = $settings->set('maintenance_message', $message); + return $ok1 && $ok2; + } catch (\Throwable $e) { + // fall back to file flag + } + } $dir = dirname(self::FLAG_PATH); if (!is_dir($dir)) { mkdir($dir, 0755, true); @@ -23,6 +48,17 @@ class Maintenance public static function disable(): bool { + if (isset($GLOBALS['db'])) { + try { + require_once __DIR__ . '/Settings.php'; + $settings = new Settings($GLOBALS['db']); + $ok1 = $settings->set('maintenance_enabled', '0'); + // keep last message for reference, optional to clear + return $ok1; + } catch (\Throwable $e) { + // fall back to file flag + } + } if (file_exists(self::FLAG_PATH)) { return unlink(self::FLAG_PATH); } @@ -34,6 +70,19 @@ class Maintenance if (!self::isEnabled()) { return ''; } + $envMsg = getenv('JILO_MAINTENANCE_MESSAGE'); + if ($envMsg) { + return trim($envMsg); + } + if (isset($GLOBALS['db'])) { + try { + require_once __DIR__ . '/Settings.php'; + $settings = new Settings($GLOBALS['db']); + return (string)$settings->get('maintenance_message', ''); + } catch (\Throwable $e) { + // ignore and fall back to file flag + } + } $msg = @file_get_contents(self::FLAG_PATH); return is_string($msg) ? trim($msg) : ''; } diff --git a/app/core/Settings.php b/app/core/Settings.php new file mode 100644 index 0000000..3cdabab --- /dev/null +++ b/app/core/Settings.php @@ -0,0 +1,54 @@ +pdo = $db; + } elseif (is_object($db) && method_exists($db, 'getConnection')) { + $pdo = $db->getConnection(); + if (!$pdo instanceof PDO) { + throw new Exception('Settings: database wrapper did not return PDO'); + } + $this->pdo = $pdo; + } else { + $type = is_object($db) ? get_class($db) : gettype($db); + throw new Exception("Settings: unsupported database type: {$type}"); + } + $this->ensureTable(); + } + + private function ensureTable(): void + { + $driver = $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + if ($driver === 'sqlite') { + $sql = "CREATE TABLE IF NOT EXISTS settings (\n `key` TEXT PRIMARY KEY,\n `value` TEXT,\n `updated_at` TEXT NOT NULL\n )"; + } else { + $sql = "CREATE TABLE IF NOT EXISTS settings (\n `key` VARCHAR(191) NOT NULL PRIMARY KEY,\n `value` TEXT NULL,\n `updated_at` DATETIME NOT NULL\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"; + } + $this->pdo->exec($sql); + } + + public function get(string $key, $default = null) + { + $stmt = $this->pdo->prepare('SELECT `value` FROM settings WHERE `key` = :k'); + $stmt->execute([':k' => $key]); + $val = $stmt->fetchColumn(); + if ($val === false) return $default; + return $val; + } + + public function set(string $key, $value): bool + { + $stmt = $this->pdo->prepare('REPLACE INTO settings (`key`, `value`, `updated_at`) VALUES (:k, :v, NOW())'); + return (bool)$stmt->execute([':k' => $key, ':v' => $value]); + } +} diff --git a/app/templates/maintenance.php b/app/templates/maintenance.php index 9828a24..a4c4ad7 100644 --- a/app/templates/maintenance.php +++ b/app/templates/maintenance.php @@ -12,7 +12,12 @@

The site is temporarily unavailable due to maintenance.

+ + +

+

Please try again later.

+
diff --git a/doc/database/README.md b/doc/database/README.md index 2bbc03a..c341c40 100644 --- a/doc/database/README.md +++ b/doc/database/README.md @@ -49,6 +49,13 @@ php scripts/maintenance.php off php scripts/maintenance.php status ``` +Notes: + +- The maintenance flag is stored at `app/.maintenance.flag`. +- You can also control maintenance via environment variables (useful when the filesystem is read-only): + - `JILO_MAINTENANCE=1` enables maintenance mode + - `JILO_MAINTENANCE_MESSAGE="Your message"` sets the banner message + ## Authoring new migrations 1. Create a new SQL file in `doc/database/migrations/`, e.g.: