# Техническая документация

### Описание

Система автоматического обновления MmoWeb Client реализует механизм безопасного развёртывания новых версий приложения через GitHub Releases API. Система поддерживает автоматическое резервное копирование, поэтапное применение изменений и отслеживание прогресса обновления в реальном времени.

***

### Архитектура системы

#### Основные компоненты

1. **UpdateService** (`Class/Services/UpdateService.php`)
   * Центральный сервис управления обновлениями
   * Отвечает за скачивание, распаковку и применение релизов
2. **Repository** (`Class/GitHub/Repository.php`)
   * Абстракция работы с GitHub API
   * Обработка запросов к репозиторию и релизам
3. **ApiController** (`Controllers/ApiController.php`)
   * HTTP endpoints для удалённого управления обновлениями
   * Методы: `updater_check_connection`, `install_update`, `get_version`, `get_updater_process_progress`
4. **Cache система**
   * Хранилище состояния процесса обновления
   * Кеширование прогресса выполнения

***

### Процесс обновления

#### 1. Инициализация

**Вызов API endpoint**

```http
POST /api/install_update
Content-Type: application/x-www-form-urlencoded

hash={HMAC_SHA256_SIGNATURE}&tag=v1.2.3
```

**Валидация**

* Проверка версии PHP (только 7.4.33)
* Проверка соединения с GitHub API
* Проверка конфигурации версии
* Проверка отсутствия активного процесса обновления

**Генерация процесса**

* Создаётся уникальный `process_key` (SHA256 хеш)
* Устанавливается статус `CREATED`
* Возвращается `process_key` клиенту

**Файл**: `UpdateService.php:82-104`

***

#### 2. Создание директории процесса

**Расположение**: `Files/update/{process_key}/`

Структура директории:

```
Files/update/{process_key}/
├── backup.zip          # Полный бекап текущей версии
└── {tag}.zip          # Скачанный релиз с GitHub
```

**Файл**: `UpdateService.php:106-127`

***

#### 3. Создание резервной копии

**Параметры бекапа**

* **Путь**: `Files/update/{process_key}/backup.zip`
* **Метод**: Рекурсивная архивация через `ZipArchive`
* **Исключения**:
  * Директория `Files/update/` (любые процессы обновления)
  * Архивы: `.zip`, `.rar`, `.7z`, `.tar`, `.gz`, `.bz2`, `.xz`, `.tgz`
  * Символические ссылки
  * Нечитаемые файлы

**Алгоритм**

1. Сканирование всех файлов от `ROOT_DIR`
2. Фильтрация по критериям исключения
3. Добавление файлов и директорий в ZIP с сохранением структуры
4. Проверка целостности (размер > 0 байт)

**Время выполнения**: зависит от размера проекта (обычно 5-30 секунд)

**Файл**: `UpdateService.php:233-312`

***

#### 4. Загрузка релиза

**Источник**

* **URL**: `{release.zipball_url}` из GitHub API
* **Таймаут**: 2100 секунд (35 минут)

**Механизм загрузки**

```php
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 2100);
curl_setopt($ch, CURLOPT_FAILONERROR, true);
```

**Путь сохранения**

`Files/update/{process_key}/{tag}.zip`

**Файл**: `UpdateService.php:129-179`

***

#### 5. Анализ изменений коммита

**Получение списка файлов**

Система запрашивает все файлы из коммита релиза через GitHub API:

```
GET /repos/{owner}/{repo}/commits/{sha}?page={N}
```

**Пагинация**

* Обрабатываются все страницы (max 50)
* Паузы между запросами: 250ms (`usleep(250000)`)

**Типы изменений файлов**

* **added** - новый файл
* **modified** - изменённый файл
* **renamed** - переименованный файл (обрабатывается как копирование)
* **removed** - удалённый файл (логируется, но НЕ удаляется локально)

**Файл**: `UpdateService.php:345-390`

***

#### 6. Применение изменений

**Процесс обработки файлов**

```
Для каждого файла из коммита:
1. Определить путь в архиве: {releaseFolder}/{filename}
2. Проверка ZIP Slip атак (путь не должен содержать `..` или абсолютные пути)
3. Применить операцию согласно статусу
4. Обновить progress
5. Отправить прогресс в Global API (каждые 3 секунды)
```

**Метод извлечения файла**

**Атомарная операция**:

1. Создание временного файла `{targetPath}.tmp`
2. Копирование потока из ZIP в временный файл
3. Установка прав `0644`
4. Атомарная замена через `rename()`

**Безопасность**:

* Автоматическое создание директорий с правами `0755`
* Откат при ошибке (удаление `.tmp`)

**Файл**: `UpdateService.php:604-631`

***

#### 7. Завершение процесса

**При успешном обновлении**

1. Статус устанавливается в `COMPLETED`
2. Обновляется конфигурация `version.json`:

   ```json
   {
       "tag": "v1.2.3",
       "timestamp": "2026-01-15T12:34:56Z",
       "release_id": 123456
   }
   ```
3. Очистка кеша (если `DEBUG = false`):
   * `update_progress_{process_key}`
   * `update`

**Файл**: `UpdateService.php:548-563`

**При ошибке**

1. Статус устанавливается в `FAILED`
2. Запись в лог с полным трейсом ошибки
3. Отправка ошибки в Global API
4. Бекап остаётся нетронутым в `Files/update/{process_key}/backup.zip`

**Файл**: `UpdateService.php:565-574`

***

### Логирование

#### Расположение логов

**Директория**: `Files/logs/`

**Формат имени**:

```
update_{Y-m-d_H-i}_{first_8_chars_of_process_key}.log
```

**Пример**:

```
update_2026-02-14_15-30_115abb9f.log
```

#### Содержимое логов

**Стандартная информация**

* Начало установки релиза
* Создание директории процесса
* Загрузка ZIP файла
* SHA коммита релиза
* Создание бекапа
* Обработка каждого файла (статус и путь)
* Прогресс обновления (% выполнения)
* Окончание процесса

**Debug информация (если `DEBUG = true`)**

**Системная информация**:

```
=== System information ===
[PHP]
Version: 8.1.2
SAPI: fpm-fcgi

[Environment]
OS: Linux
Architecture: 64-bit

[Limits]
Memory limit: 256M
Max execution time: 1800
Upload max filesize: 2M
Post max size: 8M

[Extensions]
- Core: unknown
- date: 8.1.2
- zip: 1.20.0
...
==========================
```

**Файл**: `UpdateService.php:633-639, 314-343`

#### Критические ошибки

**Обработчик Shutdown**

Регистрируется на старте обновления и отлавливает фатальные ошибки:

* `E_ERROR`
* `E_PARSE`
* `E_CORE_ERROR`
* `E_COMPILE_ERROR`
* `E_USER_ERROR`

При фатальной ошибке:

1. Пишется в лог с префиксом `FATAL ERROR:`
2. Кеш устанавливается в `FAILED`
3. Уведомление отправляется в Global API

**Файл**: `UpdateService.php:182-231`

***

### Управление состоянием

#### Кеширование статуса

**Ключ кеша: `update`**

**Структура**:

```php
[
    'status' => 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'CREATED',
    'process_key' => '{SHA256_HASH}'
]
```

**TTL**: 1800 секунд (30 минут)

**Файл**: `UpdateService.php:92-104`

***

#### Кеширование прогресса

**Ключ кеша: `update_progress_{process_key}`**

**Значение**: Float (0.00 - 100.00)

**TTL**: 600 секунд (10 минут)

**Обновление**: При обработке каждого файла

**Пример файла**: `cache/update_progress_{hash}.txt`

```php
a:3:{
    s:4:"time";i:1770805760;
    s:3:"ttl";i:600;
    s:4:"data";d:100;  // progress = 100.0
}
```

**Файл**: `UpdateService.php:532-533`

***

### API Endpoints

#### 1. Проверка соединения

**Endpoint**: `POST /api/updater_check_connection`

**Проверки**:

* Валидность конфигурации версии
* PHP = 7.4.33
* Соединение с GitHub API

**Ответы**:

```json
// Успех
{
    "result_code": "CHECK_SUCCESS",
    "metadata": {}
}

// Ошибка версии PHP
{
    "result_code": "PHP_VERSION_ERROR",
    "metadata": {
        "current_version": "7.3.0",
        "minimum_version": "7.4.33"
    }
}
```

**Файл**: `ApiController.php:432-475`

***

#### 2. Установка обновления

**Endpoint**: `POST /api/install_update`

**Параметры**:

* `tag` (required) - тег релиза (например, `v1.2.3`)
* `process_key` (optional) - ключ процесса при продолжении

**Двухэтапный процесс**:

**Этап 1: Создание процесса**

```http
POST /api/install_update
tag=v1.2.3
```

Ответ:

```json
{
    "result_code": "PROCESS_CREATED",
    "metadata": {
        "process_key": "115abb9f3a36cfbb5d8988ed6ac7bb74d930af6572e26bbdb23c964f8f5b453e"
    }
}
```

**Этап 2: Выполнение обновления**

```http
POST /api/install_update
tag=v1.2.3&process_key=115abb9f...
```

Ответ при успехе:

```json
{
    "result_code": "INSTALL_SUCCESS",
    "metadata": {}
}
```

Ответ при ошибке:

```json
{
    "result_code": "INSTALL_FAILED",
    "metadata": {
        "error": "Failed to open ZIP file..."
    }
}
```

**Файл**: `ApiController.php:477-572`

***

#### 3. Получить текущую версию

**Endpoint**: `POST /api/get_version`

**Ответ**:

```json
{
    "result_code": "CURRENT_VERSION",
    "metadata": {
        "version": "v1.2.3"
    }
}
```

**Файл**: `ApiController.php:574-590`

***

#### 4. Получить прогресс обновления

**Endpoint**: `GET /api/get_updater_process_progress?process_key={key}`

**Ответ**:

```json
{
    "result_code": "PROGRESS_RETRIEVED",
    "metadata": {
        "progress": 45.67
    }
}
```

**Файл**: `ApiController.php:592-630`

***

### Обработка ошибок и recovery

#### Типовые ошибки

**1. Обновление уже запущено**

**Статус**: `423 Locked`

```json
{
    "result_code": "UPDATE_IN_PROGRESS",
    "metadata": {}
}
```

**Решение**: Дождаться завершения текущего процесса или проверить статус в кеше

***

**2. Тег уже установлен**

**Exception**: `The specified tag is already installed.`

**Решение**: Проверить текущую версию через `/api/get_version`

***

**3. Проблемы с GitHub API**

**Возможные причины**:

* Неверный токен авторизации
* Rate limiting
* Таймаут соединения
* Несуществующий релиз/тег

**Диагностика**:

* Проверить логи `Debug/github.log`
* Проверить конфигурацию `Library/configs/github.json`
* Тест соединения: `/api/updater_check_connection`

**Файл**: `Repository.php:53-115`

***

**4. Ошибка создания бекапа**

**Возможные причины**:

* Недостаточно места на диске
* Нет прав на запись в `Files/update/`
* Исчерпана память PHP (`memory_limit`)

**Решение**:

1. Проверить свободное место: `df -h`
2. Проверить права: `ls -la Files/`
3. Увеличить `memory_limit` в php.ini (по умолчанию устанавливается 256M)

**Файл**: `UpdateService.php:233-312`

***

**5. Битый ZIP файл**

**Exception**: `Failed to open ZIP file... The file may be corrupted.`

**Решение**:

1. Проверить файл вручную: `unzip -t Files/update/{process_key}/{tag}.zip`
2. Удалить процесс и повторить обновление
3. Проверить стабильность сетевого соединения

**Файл**: `UpdateService.php:449-451`

***

**6. ZIP Slip атака**

**Exception**: `ZIP Slip detected: invalid path: ...`

**Причина**: Попытка записи файла за пределы `ROOT_DIR`

**Защита**: Проверка на наличие `..` или абсолютных путей в именах файлов

**Файл**: `UpdateService.php:492-497`

***

#### Восстановление из бекапа

**Автоматическое восстановление**

При ошибке процесс останавливается, но **откат не производится автоматически.**

**Ручное восстановление**

1. Найти директорию последнего обновления:

   ```bash
   ls -lt Files/update/ | head -n 2
   ```
2. Проверить наличие бекапа:

   ```bash
   ls -lh Files/update/{process_key}/backup.zip
   ```
3. Распаковать бекап:

   ```bash
   # НЕ ИСПОЛЬЗУЙТЕ unzip напрямую в продакшене!
   # Создайте скрипт восстановления:

   cd /path/to/project
   unzip -o Files/update/{process_key}/backup.zip
   ```
4. Проверить права доступа:

   ```bash
   find . -type f -exec chmod 644 {} \;
   find . -type d -exec chmod 755 {} \;
   ```
5. Очистить кеш обновления:

   ```bash
   rm -f Files/cache/update*
   ```

***

### Безопасность

#### Аутентификация API (клиентская часть)

Все API endpoints используют HMAC-SHA256 подпись:

```php
// Генерация подписи
ksort($_POST);
$signature = hash_hmac('sha256', json_encode($_POST), API_KEY);
$_POST['hash'] = $signature;
```

**Константа**: `API_KEY` из Config.php

**Файл**: `ApiController.php:14-44`

***

#### Защита от атак

**1. ZIP Slip Protection**

Проверка всех путей файлов на наличие:

* `..` (выход за пределы директории)
* Абсолютных путей (`/`, `\`, `C:`)

**2. Игнорирование пользовательского прерывания**

```php
ignore_user_abort(true);
```

**3. Ограничения выполнения**

* **Таймаут**: 1800 секунд (30 минут)
* **Память**: 256MB
* **Umask**: 0022 (права по умолчанию)

**Файл**: `UpdateService.php:395-400`

***

#### Конфигурация GitHub

**Файл**: `Library/configs/github.json`

```json
{
    "api_base_uri": "https://api.github.com",
    "repository_path": "PlaygridDev/playgrid-client",
    "token": "",
    "timeout": 15
}
```

**Файл**: `Repository.php:21-36`

***

### Производительность

#### Таймауты

| Операция                       | Таймаут           | Конфигурация              |
| ------------------------------ | ----------------- | ------------------------- |
| Процесс обновления             | 1800 сек (30 мин) | `PROCESS_EXECUTE_TIMEOUT` |
| Загрузка ZIP с GitHub          | 2100 сек (35 мин) | `CURLOPT_TIMEOUT`         |
| GitHub API запрос              | 15 сек            | `Repository->timeout`     |
| Пауза между запросами коммитов | 250 мс            | `usleep(250000)`          |

***

#### Оптимизация

**1. Прогресс-репортинг с дебаунсом**

Отправка прогресса в Global API происходит не чаще 1 раза в 3 секунды:

```php
if ($sendedProgressTime === 0 || (time() - $sendedProgressTime) >= 3) {
    $this->globalApi->sendUpdaterProcessProgress($this->processKey, $progress);
    $sendedProgressTime = time();
}
```

**Файл**: `UpdateService.php:536-539`

***

**2. Потоковая обработка файлов**

Использование `stream_copy_to_stream()` вместо загрузки в память:

```php
$stream = $zip->getStream($archivePath);
stream_copy_to_stream($stream, $out);
```

**Файл**: `UpdateService.php:607-617`

***

### Мониторинг

#### Проверка статуса обновления

```php
// Проверить активность процесса
$status = Cache::get('update');
if ($status['status'] === 'IN_PROGRESS') {
    $processKey = $status['process_key'];
    $progress = Cache::get('update_progress_' . $processKey);
    echo "Прогресс: {$progress}%";
}
```

***

#### Проверка логов

```bash
# Найти последние логи обновлений
ls -lt Files/logs/update_* | head -n 5

# Просмотр активного процесса
tail -f Files/logs/update_2026-02-14_15-30_115abb9f.log

# Поиск ошибок
grep -i "error\|exception\|fatal" Files/logs/update_*.log
```

***

### Конфигурационные файлы

#### version.json

```json
{
    "tag": "v1.2.3",
    "timestamp": "2026-01-15T12:34:56Z",
    "release_id": 123456
}
```

**Расположение**: `Library/configs/version.json`

***

#### github.json

```json
{
    "api_base_uri": "https://api.github.com",
    "repository_path": "PlaygridDev/playgrid-client",
    "token": "ghp_xxxxxxxxxxxxxx",
    "timeout": 15
}
```

**Расположение**: `Library/configs/github.json`

***

### Troubleshooting

#### Проблема: Обновление зависло

**Диагностика**:

1. Проверить статус процесса:

   ```bash
   php -r "var_dump(Cache::get('update'));"
   ```
2. Проверить PHP процессы:

   ```bash
   ps aux | grep php
   ```
3. Проверить последний лог:

   ```bash
   tail -50 Files/logs/update_*.log | tail -n 50
   ```

**Решение**:

* Если процесс завис > 30 минут - очистить кеш вручную
* Проверить доступность GitHub API
* Повторить обновление

***

#### Проблема: Недостаточно прав доступа

**Симптомы**:

```
Failed to create update process directory
Failed to add file to backup
Failed to replace file
```

**Решение**:

```bash
# Установить правильного владельца
chown -R www-data:www-data /path/to/project

# Установить права
find . -type f -exec chmod 644 {} \;
find . -type d -exec chmod 755 {} \;
chmod 775 Files/
chmod 775 Files/update/
chmod 775 Debug/
chmod 775 cache/
```

***

#### Проблема: GitHub API rate limit

**Симптомы**:

```
GitHub API returned error code 403: API rate limit exceeded
```

**Решение**:

* Используйте [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)

* Authenticated requests: 5000 req/hour

* Unauthenticated: 60 req/hour

***

### Рекомендации для специалистов

#### Pre-deployment checklist

* [ ] Убедиться в наличии свободного места (минимум 2x размер проекта)
* [ ] Проверить версию PHP = 7.4.33
* [ ] Убедиться, что extension `zip` установлен
* [ ] Проверить права на запись в `Files/`
* [ ] Убедиться, что нет активных процессов обновления
* [ ] Создать ручной бекап критичных данных (БД, конфиги)

***

#### Post-deployment checklist

* [ ] Проверить успешность обновления: `/api/get_version`
* [ ] Проверить логи на наличие ошибок
* [ ] Протестировать основной функционал приложения
* [ ] Убедиться в корректности прав доступа к файлам
* [ ] При необходимости удалить старые бекапы из `Files/update/`
