Files
novacpx/panel/lib/DB.php
T
myron fbc445dad2 Migrate panel DB from MySQL to SQLite
Panel no longer depends on the user-managed MariaDB service.
SQLite at /var/lib/novacpx/panel.db runs independently so the
control panel stays up even when MariaDB is stopped.

- DB.php: switch to sqlite: DSN, add SQL translator (ON DUPLICATE KEY,
  DATE_ADD/DATE_SUB INTERVAL, NOW(), UNIX_TIMESTAMP(), IFNULL)
- Core.php: replace DB_HOST/NAME/USER/PASS with DB_PATH constant
- schema.sql: full SQLite syntax, add TOTP columns to users table
- _branding.php: use sqlite: PDO, datetime('now') for session check
- install.sh: apt install sqlite3, create SQLite DB instead of MySQL DB
- tools/migrate-to-sqlite.sh: one-shot migration script for existing installs
2026-06-09 14:52:02 +00:00

122 lines
4.6 KiB
PHP

<?php
class DB {
private static ?DB $instance = null;
private PDO $pdo;
private function __construct() {
$path = defined('DB_PATH') ? DB_PATH : '/var/lib/novacpx/panel.db';
$dir = dirname($path);
if (!is_dir($dir)) mkdir($dir, 0750, true);
$this->pdo = new PDO(
"sqlite:{$path}",
null, null,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
$this->pdo->exec("PRAGMA journal_mode = WAL");
$this->pdo->exec("PRAGMA foreign_keys = ON");
$this->pdo->exec("PRAGMA busy_timeout = 5000");
}
public static function getInstance(): self {
if (!self::$instance) self::$instance = new self();
return self::$instance;
}
// Translate MySQL-isms to SQLite equivalents
private function translate(string $sql): string {
// ON DUPLICATE KEY UPDATE col=VALUES(col) → ON CONFLICT DO UPDATE SET col=excluded.col
$sql = preg_replace_callback(
'/ON DUPLICATE KEY UPDATE\s+(.+?)(?=\s*(?:;|$))/is',
function (array $m): string {
$pairs = preg_split('/,\s*/', trim($m[1]));
$sets = array_map(function (string $pair): string {
if (preg_match('/(\w+)\s*=\s*VALUES\s*\(\s*(\w+)\s*\)/i', $pair, $pm)) {
return "{$pm[1]}=excluded.{$pm[2]}";
}
// col=? or col=expr — keep as-is
return $pair;
}, $pairs);
return 'ON CONFLICT DO UPDATE SET ' . implode(', ', $sets);
},
$sql
);
// NOW() → datetime('now')
$sql = preg_replace('/\bNOW\(\)/i', "datetime('now')", $sql);
// UNIX_TIMESTAMP() → strftime('%s','now')
$sql = preg_replace('/\bUNIX_TIMESTAMP\(\)/i', "strftime('%s','now')", $sql);
// DATE_ADD(expr, INTERVAL n UNIT) → datetime(expr, '+n unit')
$sql = preg_replace_callback(
"/DATE_ADD\s*\(\s*(NOW\(\)|datetime\('now'\))\s*,\s*INTERVAL\s+(\d+)\s+(\w+)\s*\)/i",
function (array $m): string {
$n = $m[2];
$unit = strtolower($m[3]);
// Map MySQL interval units to SQLite modifier strings
$map = [
'second' => 'second', 'seconds' => 'second',
'minute' => 'minute', 'minutes' => 'minute',
'hour' => 'hour', 'hours' => 'hour',
'day' => 'day', 'days' => 'day',
'month' => 'month', 'months' => 'month',
'year' => 'year', 'years' => 'year',
];
$mod = $map[$unit] ?? $unit;
return "datetime('now', '+{$n} {$mod}')";
},
$sql
);
// DATE_SUB(expr, INTERVAL n UNIT) → datetime(expr, '-n unit')
$sql = preg_replace_callback(
"/DATE_SUB\s*\(\s*(NOW\(\)|datetime\('now'\))\s*,\s*INTERVAL\s+(\d+)\s+(\w+)\s*\)/i",
function (array $m): string {
$n = $m[2];
$unit = strtolower($m[3]);
$map = [
'second' => 'second', 'seconds' => 'second',
'minute' => 'minute', 'minutes' => 'minute',
'hour' => 'hour', 'hours' => 'hour',
'day' => 'day', 'days' => 'day',
'month' => 'month', 'months' => 'month',
'year' => 'year', 'years' => 'year',
];
$mod = $map[$unit] ?? $unit;
return "datetime('now', '-{$n} {$mod}')";
},
$sql
);
// IFNULL → COALESCE (SQLite supports both but be safe)
$sql = preg_replace('/\bIFNULL\s*\(/i', 'COALESCE(', $sql);
return $sql;
}
public function execute(string $sql, array $params = []): PDOStatement {
$stmt = $this->pdo->prepare($this->translate($sql));
$stmt->execute($params);
return $stmt;
}
public function fetchOne(string $sql, array $params = []): ?array {
return $this->execute($sql, $params)->fetch() ?: null;
}
public function fetchAll(string $sql, array $params = []): array {
return $this->execute($sql, $params)->fetchAll();
}
public function insert(string $sql, array $params = []): string {
$this->execute($sql, $params);
return $this->pdo->lastInsertId();
}
public function pdo(): PDO { return $this->pdo; }
}