375 lines
10 KiB
Markdown
375 lines
10 KiB
Markdown
|
|
# 🚀 Настройка собственного сервера обновлений для Umbrix
|
|||
|
|
|
|||
|
|
Это руководство поможет вам настроить систему обновлений для приватного распространения APK/IPA файлов до публикации в магазинах.
|
|||
|
|
|
|||
|
|
## 📋 Варианты реализации
|
|||
|
|
|
|||
|
|
### Вариант 1: Простой JSON файл на хостинге (самое простое)
|
|||
|
|
|
|||
|
|
Создайте файл `latest.json` на любом веб-сервере:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"version": "2.5.8",
|
|||
|
|
"build_number": "258",
|
|||
|
|
"is_prerelease": false,
|
|||
|
|
"download_url": "https://your-server.com/downloads/umbrix-2.5.8.apk",
|
|||
|
|
"release_notes": "Что нового:\n- Исправлены ошибки подключения\n- Улучшена стабильность\n- Новый дизайн главной страницы",
|
|||
|
|
"published_at": "2026-01-16T10:00:00Z"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Где разместить:**
|
|||
|
|
- ✅ Netlify / Vercel (бесплатно)
|
|||
|
|
- ✅ GitHub Pages (можно сделать приватный репозиторий)
|
|||
|
|
- ✅ Ваш VPS сервер
|
|||
|
|
- ✅ Firebase Hosting
|
|||
|
|
|
|||
|
|
**В constants.dart установите:**
|
|||
|
|
```dart
|
|||
|
|
static const customUpdateServerUrl = "https://your-site.netlify.app/latest.json";
|
|||
|
|
static const useCustomUpdateServer = true;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Вариант 2: Node.js API сервер (рекомендуемый)
|
|||
|
|
|
|||
|
|
#### Установка
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mkdir umbrix-update-server
|
|||
|
|
cd umbrix-update-server
|
|||
|
|
npm init -y
|
|||
|
|
npm install express cors
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### server.js
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
const express = require('express');
|
|||
|
|
const cors = require('cors');
|
|||
|
|
const app = express();
|
|||
|
|
|
|||
|
|
app.use(cors());
|
|||
|
|
app.use(express.json());
|
|||
|
|
|
|||
|
|
// Конфигурация версий
|
|||
|
|
const releases = {
|
|||
|
|
stable: {
|
|||
|
|
version: "2.5.8",
|
|||
|
|
build_number: "258",
|
|||
|
|
is_prerelease: false,
|
|||
|
|
download_url: "https://your-storage.com/umbrix-2.5.8.apk",
|
|||
|
|
release_notes: "Стабильная версия с исправлениями",
|
|||
|
|
published_at: new Date().toISOString()
|
|||
|
|
},
|
|||
|
|
beta: {
|
|||
|
|
version: "2.6.0-beta.1",
|
|||
|
|
build_number: "260",
|
|||
|
|
is_prerelease: true,
|
|||
|
|
download_url: "https://your-storage.com/umbrix-2.6.0-beta.1.apk",
|
|||
|
|
release_notes: "Бета-версия с новыми функциями",
|
|||
|
|
published_at: new Date().toISOString()
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Endpoint для получения последней версии
|
|||
|
|
app.get('/api/updates/latest', (req, res) => {
|
|||
|
|
const includePrerelease = req.query.include_prerelease === 'true';
|
|||
|
|
const release = includePrerelease ? releases.beta : releases.stable;
|
|||
|
|
|
|||
|
|
console.log(`Update check: prerelease=${includePrerelease}`);
|
|||
|
|
res.json(release);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Аналитика (опционально)
|
|||
|
|
app.post('/api/updates/analytics', (req, res) => {
|
|||
|
|
const { current_version, device_info } = req.body;
|
|||
|
|
console.log('Update analytics:', { current_version, device_info });
|
|||
|
|
res.json({ success: true });
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const PORT = process.env.PORT || 3000;
|
|||
|
|
app.listen(PORT, () => {
|
|||
|
|
console.log(`Update server running on port ${PORT}`);
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Запуск
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
node server.js
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Deploy на Render.com (бесплатно)
|
|||
|
|
|
|||
|
|
1. Создайте аккаунт на [Render.com](https://render.com)
|
|||
|
|
2. Подключите GitHub репозиторий
|
|||
|
|
3. Создайте Web Service
|
|||
|
|
4. Render автоматически задеплоит ваш сервер
|
|||
|
|
|
|||
|
|
**В constants.dart установите:**
|
|||
|
|
```dart
|
|||
|
|
static const customUpdateServerUrl = "https://your-app.onrender.com/api/updates/latest";
|
|||
|
|
static const useCustomUpdateServer = true;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Вариант 3: Firebase Cloud Functions (продвинутый)
|
|||
|
|
|
|||
|
|
#### functions/index.js
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
const functions = require('firebase-functions');
|
|||
|
|
const admin = require('firebase-admin');
|
|||
|
|
|
|||
|
|
admin.initializeApp();
|
|||
|
|
|
|||
|
|
exports.getLatestVersion = functions.https.onRequest(async (req, res) => {
|
|||
|
|
// CORS
|
|||
|
|
res.set('Access-Control-Allow-Origin', '*');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const db = admin.firestore();
|
|||
|
|
const doc = await db.collection('app_updates').doc('latest').get();
|
|||
|
|
|
|||
|
|
if (!doc.exists) {
|
|||
|
|
return res.status(404).json({ error: 'Version not found' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
res.json(doc.data());
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Error:', error);
|
|||
|
|
res.status(500).json({ error: 'Internal server error' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Плюсы Firebase:**
|
|||
|
|
- ✅ Автоматическое масштабирование
|
|||
|
|
- ✅ Бесплатный SSL
|
|||
|
|
- ✅ Встроенная аналитика
|
|||
|
|
- ✅ База данных Firestore для хранения версий
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📦 Где хранить APK файлы
|
|||
|
|
|
|||
|
|
### 1. Firebase Storage (рекомендуемый)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Установка Firebase CLI
|
|||
|
|
npm install -g firebase-tools
|
|||
|
|
firebase login
|
|||
|
|
|
|||
|
|
# Загрузка APK
|
|||
|
|
firebase storage:upload umbrix-2.5.8.apk /releases/umbrix-2.5.8.apk
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Получение публичной ссылки:**
|
|||
|
|
```javascript
|
|||
|
|
// Генерация signed URL (действителен 7 дней)
|
|||
|
|
const { getStorage } = require('firebase-admin/storage');
|
|||
|
|
const bucket = getStorage().bucket();
|
|||
|
|
const file = bucket.file('releases/umbrix-2.5.8.apk');
|
|||
|
|
const [url] = await file.getSignedUrl({
|
|||
|
|
action: 'read',
|
|||
|
|
expires: Date.now() + 7 * 24 * 60 * 60 * 1000
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. AWS S3 / DigitalOcean Spaces
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Пример с AWS CLI
|
|||
|
|
aws s3 cp umbrix-2.5.8.apk s3://your-bucket/releases/umbrix-2.5.8.apk --acl public-read
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. Собственный сервер
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Nginx конфигурация
|
|||
|
|
location /downloads/ {
|
|||
|
|
alias /var/www/downloads/;
|
|||
|
|
autoindex off;
|
|||
|
|
|
|||
|
|
# Защита паролем (опционально)
|
|||
|
|
auth_basic "Restricted";
|
|||
|
|
auth_basic_user_file /etc/nginx/.htpasswd;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔒 Безопасность
|
|||
|
|
|
|||
|
|
### 1. Базовая аутентификация (Basic Auth)
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
// В DioHttpClient добавьте заголовки
|
|||
|
|
final response = await httpClient.get<Map<String, dynamic>>(
|
|||
|
|
url,
|
|||
|
|
options: Options(
|
|||
|
|
headers: {
|
|||
|
|
'Authorization': 'Basic ${base64Encode(utf8.encode('username:password'))}',
|
|||
|
|
},
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. API ключ
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
// В constants.dart
|
|||
|
|
static const updateServerApiKey = "your-secret-api-key";
|
|||
|
|
|
|||
|
|
// В запросе
|
|||
|
|
headers: {
|
|||
|
|
'X-API-Key': Constants.updateServerApiKey,
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. JWT токен (самый безопасный)
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// На сервере
|
|||
|
|
const jwt = require('jsonwebtoken');
|
|||
|
|
|
|||
|
|
function verifyToken(req, res, next) {
|
|||
|
|
const token = req.headers['authorization']?.split(' ')[1];
|
|||
|
|
if (!token) return res.status(403).json({ error: 'No token' });
|
|||
|
|
|
|||
|
|
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
|
|||
|
|
if (err) return res.status(401).json({ error: 'Invalid token' });
|
|||
|
|
req.userId = decoded.userId;
|
|||
|
|
next();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
app.get('/api/updates/latest', verifyToken, (req, res) => {
|
|||
|
|
// ...
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 Пошаговая инструкция (быстрый старт)
|
|||
|
|
|
|||
|
|
### Шаг 1: Создайте JSON файл
|
|||
|
|
|
|||
|
|
Создайте файл `latest.json`:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"version": "2.5.8",
|
|||
|
|
"build_number": "258",
|
|||
|
|
"is_prerelease": false,
|
|||
|
|
"download_url": "https://github.com/your-org/your-repo/releases/download/v2.5.8/umbrix-2.5.8.apk",
|
|||
|
|
"release_notes": "Первая версия",
|
|||
|
|
"published_at": "2026-01-16T10:00:00Z"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Шаг 2: Разместите на GitHub Pages
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# В приватном репозитории создайте ветку gh-pages
|
|||
|
|
git checkout -b gh-pages
|
|||
|
|
git add latest.json
|
|||
|
|
git commit -m "Add update info"
|
|||
|
|
git push origin gh-pages
|
|||
|
|
|
|||
|
|
# В Settings → Pages включите GitHub Pages для ветки gh-pages
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Шаг 3: Обновите константы в приложении
|
|||
|
|
|
|||
|
|
В файле `lib/core/model/constants.dart`:
|
|||
|
|
```dart
|
|||
|
|
static const customUpdateServerUrl = "https://your-username.github.io/your-repo/latest.json";
|
|||
|
|
static const useCustomUpdateServer = true;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Шаг 4: Соберите и протестируйте
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
flutter build apk --release
|
|||
|
|
# Установите на устройство и проверьте обновления в разделе "О программе"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📱 Автоматизация через GitHub Actions
|
|||
|
|
|
|||
|
|
Создайте `.github/workflows/release.yml`:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
name: Build and Deploy APK
|
|||
|
|
|
|||
|
|
on:
|
|||
|
|
push:
|
|||
|
|
tags:
|
|||
|
|
- 'v*'
|
|||
|
|
|
|||
|
|
jobs:
|
|||
|
|
build:
|
|||
|
|
runs-on: ubuntu-latest
|
|||
|
|
steps:
|
|||
|
|
- uses: actions/checkout@v3
|
|||
|
|
|
|||
|
|
- name: Setup Flutter
|
|||
|
|
uses: subosito/flutter-action@v2
|
|||
|
|
|
|||
|
|
- name: Build APK
|
|||
|
|
run: flutter build apk --release
|
|||
|
|
|
|||
|
|
- name: Upload to storage
|
|||
|
|
run: |
|
|||
|
|
# Загрузка APK на ваш сервер
|
|||
|
|
curl -X POST -F "file=@build/app/outputs/flutter-apk/app-release.apk" \
|
|||
|
|
https://your-server.com/api/upload
|
|||
|
|
|
|||
|
|
- name: Update version info
|
|||
|
|
run: |
|
|||
|
|
# Обновление latest.json
|
|||
|
|
echo '{
|
|||
|
|
"version": "${{ github.ref_name }}",
|
|||
|
|
"build_number": "${{ github.run_number }}",
|
|||
|
|
"download_url": "https://your-server.com/downloads/${{ github.ref_name }}.apk",
|
|||
|
|
"published_at": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
|
|||
|
|
}' > latest.json
|
|||
|
|
|
|||
|
|
# Загрузка на сервер
|
|||
|
|
curl -X PUT -d @latest.json https://your-server.com/api/latest
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ❓ FAQ
|
|||
|
|
|
|||
|
|
**Q: Как переключиться обратно на GitHub?**
|
|||
|
|
A: В `constants.dart` установите `useCustomUpdateServer = false`
|
|||
|
|
|
|||
|
|
**Q: Можно ли использовать оба варианта?**
|
|||
|
|
A: Да, можно добавить fallback логику в `app_update_repository.dart`
|
|||
|
|
|
|||
|
|
**Q: Как защитить от несанкционированного доступа?**
|
|||
|
|
A: Используйте API ключи, JWT токены или базовую аутентификацию
|
|||
|
|
|
|||
|
|
**Q: Нужен ли HTTPS?**
|
|||
|
|
A: Да, обязательно! Иначе Android не разрешит загрузку
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎉 Готово!
|
|||
|
|
|
|||
|
|
Теперь у вас есть полностью функциональная система обновлений для приватного распространения APK файлов.
|
|||
|
|
|
|||
|
|
**Следующие шаги:**
|
|||
|
|
1. ✅ Выберите вариант размещения
|
|||
|
|
2. ✅ Обновите `Constants.customUpdateServerUrl`
|
|||
|
|
3. ✅ Загрузите APK на сервер
|
|||
|
|
4. ✅ Протестируйте обновление
|
|||
|
|
5. ✅ Настройте автоматизацию через CI/CD
|