mirror of
https://github.com/myronblair/novacpx
synced 2026-06-30 17:50:41 -05:00
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
This commit is contained in:
+3
-4
@@ -13,10 +13,9 @@ if (!$_cfg) {
|
||||
die(json_encode(['error' => 'NovaCPX not configured. Run the installer.']));
|
||||
}
|
||||
|
||||
define('DB_HOST', $_cfg['database']['host'] ?? 'localhost');
|
||||
define('DB_NAME', $_cfg['database']['name'] ?? 'novacpx');
|
||||
define('DB_USER', $_cfg['database']['user'] ?? '');
|
||||
define('DB_PASS', $_cfg['database']['pass'] ?? '');
|
||||
define('DB_PATH', $_cfg['database']['path'] ?? '/var/lib/novacpx/panel.db');
|
||||
define('DB_WP_USER', $_cfg['database']['wp_user'] ?? '');
|
||||
define('DB_WP_PASS', $_cfg['database']['wp_pass'] ?? '');
|
||||
define('SECRET_KEY', $_cfg['panel']['secret'] ?? '');
|
||||
define('PANEL_VER', $_cfg['panel']['version'] ?? NOVACPX_VERSION);
|
||||
define('PORT_USER', (int)($_cfg['panel']['port_user'] ?? 8880));
|
||||
|
||||
+82
-4
@@ -4,15 +4,21 @@ class DB {
|
||||
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(
|
||||
"mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4",
|
||||
DB_USER, DB_PASS,
|
||||
"sqlite:{$path}",
|
||||
null, null,
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]
|
||||
);
|
||||
$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 {
|
||||
@@ -20,8 +26,80 @@ class DB {
|
||||
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($sql);
|
||||
$stmt = $this->pdo->prepare($this->translate($sql));
|
||||
$stmt->execute($params);
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user