query($sql); } self::ensureColumn('coolify_instances', 'default_destination_uuid', 'VARCHAR(128) NULL'); self::ensureColumn('coolify_targets', 'availability_state', "VARCHAR(32) NOT NULL DEFAULT 'degraded'"); self::ensureColumn('coolify_targets', 'desired_compose_hash', 'CHAR(64) NULL'); self::ensureColumn('coolify_targets', 'last_reconcile_json', 'LONGTEXT NULL'); self::ensureColumn('coolify_operations', 'guarded', 'TINYINT(1) NOT NULL DEFAULT 1'); self::ensureColumn('coolify_instance_gateways', 'last_reconciled_at', 'DATETIME NULL'); self::ensureModuleConfigDefault('Coolify', 'enabled', 'false', 'bool'); self::ensureModuleConfigDefault('Coolify', 'lb_automation_enabled', 'false', 'bool'); self::ensureModuleConfigDefault('Coolify', 'lb_automation_mode', 'report_only', 'string'); self::ensureModuleConfigDefault('Coolify', 'hetzner_load_balancer_id', '', 'string'); self::ensureModuleConfigDefault('Coolify', 'hetzner_cloud_api_token', '', 'string'); self::ensureModuleConfigDefault('Coolify', 'public_gateway_host', 'api-v2.truckwash.io', 'string'); self::ensureModuleConfigDefault('Coolify', 'public_gateway_probe_path', '', 'string'); self::ensureModuleConfigDefault('Coolify', 'github_runner_token', '', 'string'); self::ensureModuleConfigDefault('Coolify', 'github_runner_service_uuid', '', 'string'); self::ensureModuleConfigDefault('Coolify', 'github_runner_frontend_repository', 'copenhagentruckwash/pleno-vue', 'string'); self::ensureModuleConfigDefault('Coolify', 'github_runner_backend_repository', 'copenhagentruckwash/api', 'string'); self::ensureModuleConfigDefault('Coolify', 'github_runner_labels', 'self-hosted,Linux,X64,default', 'string'); self::ensureModuleConfigDefault('Coolify', 'github_runner_count_per_repo', '1', 'int'); self::ensureDefaultGateway('node1.truckwash.io', '94.130.142.41', 10); self::ensureDefaultGateway('node2.truckwash.io', '65.21.214.30', 20); self::ensureDefaultGateway('node3.truckwash.io', '23.88.23.183', 30); self::$initialized = true; self::$tablesExist = true; } public static function tablesExist(): bool { if (self::$tablesExist !== null) { return self::$tablesExist; } global $db; foreach (['coolify_instances', 'coolify_targets', 'coolify_operations', 'coolify_audit_logs', 'coolify_instance_gateways'] as $table) { $tableSql = $db->escape_string($table); $result = $db->query("SHOW TABLES LIKE '$tableSql'"); if ($result === false || $result->num_rows === 0) { self::$tablesExist = false; return false; } } self::$tablesExist = true; return self::$tablesExist; } private static function ensureColumn(string $table, string $column, string $definition): void { global $db; $table = preg_replace('/[^a-zA-Z0-9_]/', '', $table); $column = preg_replace('/[^a-zA-Z0-9_]/', '', $column); if ($table === '' || $column === '') { return; } $result = $db->query("SHOW COLUMNS FROM `$table` LIKE '$column'"); if ($result !== false && $result->num_rows > 0) { return; } $db->query("ALTER TABLE `$table` ADD COLUMN `$column` $definition"); } private static function ensureModuleConfigDefault(string $module, string $variable, string $value, string $type): void { global $db; $moduleSql = $db->escape_string($module); $variableSql = $db->escape_string($variable); $result = $db->query("SELECT value FROM module_config WHERE module = '$moduleSql' AND variable = '$variableSql' LIMIT 1"); if ($result !== false && $result->num_rows > 0) { return; } $valueSql = $db->escape_string($value); $typeSql = $db->escape_string($type); $db->query("INSERT INTO module_config (module, variable, value, type) VALUES ('$moduleSql', '$variableSql', '$valueSql', '$typeSql')"); } private static function ensureDefaultGateway(string $hostname, string $targetIp, int $priority): void { global $db; $targetIpSql = $db->escape_string($targetIp); $result = $db->query("SELECT id FROM coolify_instance_gateways WHERE target_ip = '$targetIpSql' LIMIT 1"); if ($result !== false && $result->num_rows > 0) { return; } $hostnameSql = $db->escape_string($hostname); $db->query( "INSERT INTO coolify_instance_gateways (hostname, target_ip, enabled, priority) VALUES ('$hostnameSql', '$targetIpSql', 1, " . (int)$priority . ")" ); } }