- Changed window size to mobile phone format (400x800) - Removed width condition for ActiveProxyFooter - now always visible - Added run-umbrix.sh launch script with icon copying - Stats cards now display on all screen sizes
194 lines
6.0 KiB
PHP
194 lines
6.0 KiB
PHP
<?php
|
||
/**
|
||
* Umbrix Update Server API для Desktop Платформ
|
||
*
|
||
* Сервер обновлений для Windows, macOS и Linux версий приложения.
|
||
* Android использует Google Play Store для обновлений.
|
||
*
|
||
* Версия: 1.1
|
||
*
|
||
* Использование:
|
||
* GET /api/latest - получить последнюю версию для Desktop
|
||
* GET /api/latest?include_prerelease=true - включая бета-версии
|
||
*/
|
||
|
||
// === НАСТРОЙКИ ===
|
||
|
||
// Разрешенные домены для CORS (замените на свой домен)
|
||
$allowed_origins = [
|
||
'https://umbrix.net',
|
||
'https://www.umbrix.net',
|
||
'https://api.umbrix.net'
|
||
];
|
||
|
||
// Путь к файлу с информацией о версии
|
||
$version_file = __DIR__ . '/latest.json';
|
||
|
||
// Включить логирование запросов
|
||
$enable_logging = true;
|
||
$log_file = __DIR__ . '/logs/access.log';
|
||
|
||
// Ограничение запросов (Rate Limiting)
|
||
$rate_limit_enabled = true;
|
||
$max_requests_per_minute = 10;
|
||
|
||
// === КОНЕЦ НАСТРОЕК ===
|
||
|
||
// Установка заголовков
|
||
header('Content-Type: application/json; charset=utf-8');
|
||
header('X-Powered-By: Umbrix Update Server');
|
||
|
||
// CORS - разрешаем запросы с приложения
|
||
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||
if (in_array($origin, $allowed_origins)) {
|
||
header("Access-Control-Allow-Origin: $origin");
|
||
} else {
|
||
header('Access-Control-Allow-Origin: *'); // Разрешаем все для начала
|
||
}
|
||
header('Access-Control-Allow-Methods: GET, OPTIONS');
|
||
header('Access-Control-Allow-Headers: Content-Type');
|
||
|
||
// Обработка preflight запроса
|
||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||
http_response_code(200);
|
||
exit();
|
||
}
|
||
|
||
// Только GET запросы
|
||
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
|
||
sendError(405, 'Method Not Allowed');
|
||
}
|
||
|
||
// Rate Limiting
|
||
if ($rate_limit_enabled && !checkRateLimit($max_requests_per_minute)) {
|
||
sendError(429, 'Too Many Requests', 'Превышен лимит запросов. Попробуйте позже.');
|
||
}
|
||
|
||
// Логирование
|
||
if ($enable_logging) {
|
||
logRequest($log_file);
|
||
}
|
||
|
||
// Проверка существования файла с версией
|
||
if (!file_exists($version_file)) {
|
||
sendError(500, 'Internal Server Error', 'Файл с версией не найден');
|
||
}
|
||
|
||
// Чтение и парсинг JSON
|
||
$version_data = file_get_contents($version_file);
|
||
$version_info = json_decode($version_data, true);
|
||
|
||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||
sendError(500, 'Internal Server Error', 'Ошибка парсинга JSON: ' . json_last_error_msg());
|
||
}
|
||
|
||
// Проверка обязательных полей
|
||
$required_fields = ['version', 'build_number', 'download_url', 'published_at'];
|
||
foreach ($required_fields as $field) {
|
||
if (!isset($version_info[$field])) {
|
||
sendError(500, 'Internal Server Error', "Отсутствует обязательное поле: $field");
|
||
}
|
||
}
|
||
|
||
// Фильтр пре-релизов
|
||
$include_prerelease = isset($_GET['include_prerelease']) && $_GET['include_prerelease'] === 'true';
|
||
|
||
if (!$include_prerelease && isset($version_info['is_prerelease']) && $version_info['is_prerelease'] === true) {
|
||
// Вернуть пустой ответ, если пре-релиз не запрошен
|
||
sendError(404, 'Not Found', 'Стабильная версия не найдена');
|
||
}
|
||
|
||
// Добавление дополнительной информации
|
||
$version_info['server_time'] = date('c');
|
||
$version_info['api_version'] = '1.0';
|
||
|
||
// Успешный ответ
|
||
http_response_code(200);
|
||
echo json_encode($version_info, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||
exit();
|
||
|
||
// === ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ===
|
||
|
||
/**
|
||
* Отправка ошибки в формате JSON
|
||
*/
|
||
function sendError($code, $message, $details = null) {
|
||
http_response_code($code);
|
||
$error = [
|
||
'error' => $message,
|
||
'code' => $code,
|
||
'timestamp' => date('c')
|
||
];
|
||
if ($details) {
|
||
$error['details'] = $details;
|
||
}
|
||
echo json_encode($error, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||
exit();
|
||
}
|
||
|
||
/**
|
||
* Проверка лимита запросов
|
||
*/
|
||
function checkRateLimit($max_requests) {
|
||
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||
$cache_file = sys_get_temp_dir() . '/umbrix_ratelimit_' . md5($ip);
|
||
|
||
$current_time = time();
|
||
$window_start = $current_time - 60; // Окно в 1 минуту
|
||
|
||
$requests = [];
|
||
if (file_exists($cache_file)) {
|
||
$requests = json_decode(file_get_contents($cache_file), true) ?? [];
|
||
}
|
||
|
||
// Удаляем старые запросы
|
||
$requests = array_filter($requests, function($timestamp) use ($window_start) {
|
||
return $timestamp > $window_start;
|
||
});
|
||
|
||
// Проверяем лимит
|
||
if (count($requests) >= $max_requests) {
|
||
return false;
|
||
}
|
||
|
||
// Добавляем текущий запрос
|
||
$requests[] = $current_time;
|
||
file_put_contents($cache_file, json_encode($requests));
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Логирование запроса
|
||
*/
|
||
function logRequest($log_file) {
|
||
$log_dir = dirname($log_file);
|
||
if (!is_dir($log_dir)) {
|
||
mkdir($log_dir, 0755, true);
|
||
}
|
||
|
||
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
|
||
$request_uri = $_SERVER['REQUEST_URI'] ?? 'unknown';
|
||
$timestamp = date('Y-m-d H:i:s');
|
||
|
||
// Извлекаем версию приложения из User-Agent (если есть)
|
||
$app_version = 'unknown';
|
||
if (preg_match('/Umbrix\/([0-9.]+)/', $user_agent, $matches)) {
|
||
$app_version = $matches[1];
|
||
}
|
||
|
||
$log_entry = sprintf(
|
||
"[%s] IP: %s | App Version: %s | URI: %s | User-Agent: %s\n",
|
||
$timestamp,
|
||
$ip,
|
||
$app_version,
|
||
$request_uri,
|
||
$user_agent
|
||
);
|
||
|
||
file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX);
|
||
}
|
||
|
||
?>
|