Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04eccff819 | ||
|
|
fec6fa166c | ||
|
|
dec7ed2509 | ||
|
|
b628bfcd82 | ||
|
|
6c31f8eeb2 | ||
|
|
4615b1da51 | ||
|
|
597d9f59ae | ||
|
|
cd5b3493a2 | ||
|
|
036faf58c9 | ||
|
|
28fed0c8a0 | ||
|
|
c27dfbfba7 | ||
|
|
8d55a2629c | ||
|
|
67292b2697 | ||
|
|
491feb04ac |
36
GIT_SAFETY.txt
Normal file
36
GIT_SAFETY.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
🔒 БЕЗОПАСНОСТЬ GIT - ВАЖНО!
|
||||
|
||||
❌ НИКОГДА НЕ ПУШИТЬ В GITHUB HIDDIFY!
|
||||
|
||||
✅ ПРАВИЛЬНЫЕ КОМАНДЫ:
|
||||
|
||||
Основной репозиторий:
|
||||
git push gitea main ← ТОЛЬКО ТАК!
|
||||
|
||||
Libcore:
|
||||
cd libcore
|
||||
git push gitea main ← ТОЛЬКО ТАК!
|
||||
git push origin main ← Тоже можно (origin = gitea)
|
||||
|
||||
❌ ОПАСНЫЕ КОМАНДЫ (НЕ ИСПОЛЬЗОВАТЬ):
|
||||
git push origin main ← В основном репо это GitHub!
|
||||
git push hiddify-upstream ← Заблокировано
|
||||
|
||||
📋 ПРОВЕРКА ПЕРЕД PUSH:
|
||||
git remote -v ← Проверить куда пушим
|
||||
git log --oneline -5 ← Проверить что пушим
|
||||
|
||||
🔍 ТЕКУЩИЕ НАСТРОЙКИ:
|
||||
|
||||
Основной репозиторий:
|
||||
origin = GitHub Hiddify (fetch only) ← READ ONLY!
|
||||
gitea = Наш Gitea ✅
|
||||
|
||||
libcore/:
|
||||
origin = Наш Gitea ✅
|
||||
gitea = Наш Gitea ✅
|
||||
hiddify-upstream = GitHub Hiddify (fetch only) ← READ ONLY!
|
||||
|
||||
✅ ЕСЛИ НУЖНО ОБНОВИТЬСЯ ОТ HIDDIFY:
|
||||
git fetch origin ← Безопасно (только чтение)
|
||||
git merge origin/main ← Потом мержим если нужно
|
||||
685
WINDOWS_SPLIT_TUNNELING.md
Normal file
685
WINDOWS_SPLIT_TUNNELING.md
Normal file
@@ -0,0 +1,685 @@
|
||||
# 🔀 Split Tunneling для Windows (Per-App Proxy)
|
||||
|
||||
## 📋 Что такое Split Tunneling?
|
||||
|
||||
**Split Tunneling** (раздельное туннелирование) - функция которая позволяет:
|
||||
- ✅ **Исключать** определённые приложения из VPN (они идут напрямую)
|
||||
- ✅ **Включать** только выбранные приложения в VPN (остальные напрямую)
|
||||
|
||||
### Примеры использования:
|
||||
- Исключить банковские приложения из VPN
|
||||
- Исключить локальные приложения (Steam, торренты)
|
||||
- Пускать через VPN только браузер
|
||||
- Пускать через VPN только мессенджеры
|
||||
|
||||
---
|
||||
|
||||
## ✅ Текущее состояние
|
||||
|
||||
### Android - ✅ ПОЛНОСТЬЮ РАБОТАЕТ
|
||||
|
||||
```kotlin
|
||||
// android/app/src/main/kotlin/com/umbrix/app/bg/VPNService.kt
|
||||
|
||||
fun addIncludePackage(builder: Builder, packageName: String) {
|
||||
builder.addAllowedApplication(packageName) // Android VPNService API
|
||||
}
|
||||
|
||||
fun addExcludePackage(builder: Builder, packageName: String) {
|
||||
builder.addDisallowedApplication(packageName) // Android VPNService API
|
||||
}
|
||||
```
|
||||
|
||||
**UI готов:** `/settings/per-app-proxy`
|
||||
- Список установленных приложений
|
||||
- Переключатель режимов: Все / Включить / Исключить
|
||||
- Фильтр системных приложений
|
||||
- Поиск по названию
|
||||
|
||||
### Windows - ⚠️ НЕ РЕАЛИЗОВАНО
|
||||
|
||||
**Проблема:** На Windows нет аналога `addAllowedApplication()` из Android API.
|
||||
|
||||
**Что есть:**
|
||||
- ✅ UI уже готов (та же страница `/settings/per-app-proxy`)
|
||||
- ✅ Настройки сохраняются (`per_app_proxy_mode`, `per_app_proxy_include_list`, `per_app_proxy_exclude_list`)
|
||||
- ✅ sing-box поддерживает фильтрацию по процессам через `process_name`
|
||||
- ❌ НЕТ функции получения списка установленных приложений
|
||||
- ❌ НЕТ передачи списка процессов в libcore
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Решение для Windows
|
||||
|
||||
### Архитектура
|
||||
|
||||
```
|
||||
┌─────────────────────────┐
|
||||
│ Flutter UI │
|
||||
│ PerAppProxyPage │ ← Уже готов!
|
||||
└──────────┬──────────────┘
|
||||
│
|
||||
↓
|
||||
┌─────────────────────────┐
|
||||
│ Platform Channel │
|
||||
│ get_installed_programs │ ← Нужно создать
|
||||
└──────────┬──────────────┘
|
||||
│
|
||||
↓
|
||||
┌─────────────────────────┐
|
||||
│ C++ Windows Plugin │
|
||||
│ Enumerate processes │ ← Нужно создать
|
||||
└──────────┬──────────────┘
|
||||
│
|
||||
↓
|
||||
┌─────────────────────────┐
|
||||
│ libcore config │
|
||||
│ process_name: │ ← Нужно передавать
|
||||
│ - chrome.exe │
|
||||
│ - firefox.exe │
|
||||
└─────────────────────────┘
|
||||
│
|
||||
↓
|
||||
┌─────────────────────────┐
|
||||
│ sing-box routing │
|
||||
│ По имени процесса │ ← Уже работает!
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
### Что нужно реализовать
|
||||
|
||||
#### 1. Windows Plugin - получение списка программ ✅ ПРОСТО
|
||||
|
||||
**Файл:** `windows/runner/flutter_window.cpp`
|
||||
|
||||
```cpp
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <shlobj.h>
|
||||
#include <atlbase.h>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
// Структура для хранения информации о программе
|
||||
struct ProgramInfo {
|
||||
std::wstring name; // Название (из описания .exe)
|
||||
std::wstring exePath; // Полный путь к .exe
|
||||
std::wstring exeName; // Только имя файла (chrome.exe)
|
||||
};
|
||||
|
||||
// Получить описание .exe файла (название программы)
|
||||
std::wstring GetFileDescription(const std::wstring& filePath) {
|
||||
DWORD dummy;
|
||||
DWORD size = GetFileVersionInfoSizeW(filePath.c_str(), &dummy);
|
||||
if (size == 0) return L"";
|
||||
|
||||
std::vector<BYTE> data(size);
|
||||
if (!GetFileVersionInfoW(filePath.c_str(), 0, size, data.data())) {
|
||||
return L"";
|
||||
}
|
||||
|
||||
struct LANGANDCODEPAGE {
|
||||
WORD language;
|
||||
WORD codePage;
|
||||
} *lpTranslate;
|
||||
|
||||
UINT cbTranslate;
|
||||
if (!VerQueryValueW(data.data(), L"\\VarFileInfo\\Translation",
|
||||
(LPVOID*)&lpTranslate, &cbTranslate)) {
|
||||
return L"";
|
||||
}
|
||||
|
||||
wchar_t subBlock[50];
|
||||
swprintf(subBlock, 50, L"\\StringFileInfo\\%04x%04x\\FileDescription",
|
||||
lpTranslate[0].language, lpTranslate[0].codePage);
|
||||
|
||||
LPWSTR lpBuffer;
|
||||
UINT dwBytes;
|
||||
if (VerQueryValueW(data.data(), subBlock, (LPVOID*)&lpBuffer, &dwBytes)) {
|
||||
return std::wstring(lpBuffer);
|
||||
}
|
||||
|
||||
return L"";
|
||||
}
|
||||
|
||||
// Получить список всех запущенных процессов
|
||||
std::set<std::wstring> GetRunningProcesses() {
|
||||
std::set<std::wstring> processes;
|
||||
|
||||
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (hSnapshot == INVALID_HANDLE_VALUE) return processes;
|
||||
|
||||
PROCESSENTRY32W pe32;
|
||||
pe32.dwSize = sizeof(PROCESSENTRY32W);
|
||||
|
||||
if (Process32FirstW(hSnapshot, &pe32)) {
|
||||
do {
|
||||
processes.insert(pe32.szExeFile);
|
||||
} while (Process32NextW(hSnapshot, &pe32));
|
||||
}
|
||||
|
||||
CloseHandle(hSnapshot);
|
||||
return processes;
|
||||
}
|
||||
|
||||
// Сканировать папки с программами
|
||||
std::vector<ProgramInfo> ScanInstalledPrograms() {
|
||||
std::vector<ProgramInfo> programs;
|
||||
std::set<std::wstring> runningProcs = GetRunningProcesses();
|
||||
|
||||
// Папки для сканирования
|
||||
std::vector<std::wstring> folders = {
|
||||
L"C:\\Program Files",
|
||||
L"C:\\Program Files (x86)",
|
||||
};
|
||||
|
||||
// Добавить AppData\\Local\\Programs
|
||||
wchar_t appDataPath[MAX_PATH];
|
||||
if (SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, appDataPath) == S_OK) {
|
||||
std::wstring localPrograms = std::wstring(appDataPath) + L"\\Programs";
|
||||
folders.push_back(localPrograms);
|
||||
}
|
||||
|
||||
for (const auto& folder : folders) {
|
||||
WIN32_FIND_DATAW findData;
|
||||
HANDLE hFind = FindFirstFileW((folder + L"\\*").c_str(), &findData);
|
||||
|
||||
if (hFind != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
if (wcscmp(findData.cFileName, L".") != 0 &&
|
||||
wcscmp(findData.cFileName, L"..") != 0) {
|
||||
|
||||
// Искать .exe в подпапке
|
||||
std::wstring subfolder = folder + L"\\" + findData.cFileName;
|
||||
WIN32_FIND_DATAW exeFind;
|
||||
HANDLE hExe = FindFirstFileW((subfolder + L"\\*.exe").c_str(), &exeFind);
|
||||
|
||||
if (hExe != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
std::wstring exePath = subfolder + L"\\" + exeFind.cFileName;
|
||||
std::wstring description = GetFileDescription(exePath);
|
||||
|
||||
if (!description.empty()) {
|
||||
programs.push_back({
|
||||
description,
|
||||
exePath,
|
||||
exeFind.cFileName
|
||||
});
|
||||
}
|
||||
} while (FindNextFileW(hExe, &exeFind));
|
||||
FindClose(hExe);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (FindNextFileW(hFind, &findData));
|
||||
FindClose(hFind);
|
||||
}
|
||||
}
|
||||
|
||||
return programs;
|
||||
}
|
||||
|
||||
// Flutter Method Channel Handler
|
||||
void HandleGetInstalledPrograms(
|
||||
const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
|
||||
|
||||
auto programs = ScanInstalledPrograms();
|
||||
|
||||
flutter::EncodableList programList;
|
||||
for (const auto& prog : programs) {
|
||||
flutter::EncodableMap programMap;
|
||||
|
||||
// Конвертировать wstring в string (UTF-8)
|
||||
int nameLen = WideCharToMultiByte(CP_UTF8, 0, prog.name.c_str(), -1, NULL, 0, NULL, NULL);
|
||||
std::string name(nameLen - 1, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, prog.name.c_str(), -1, &name[0], nameLen, NULL, NULL);
|
||||
|
||||
int exeNameLen = WideCharToMultiByte(CP_UTF8, 0, prog.exeName.c_str(), -1, NULL, 0, NULL, NULL);
|
||||
std::string exeName(exeNameLen - 1, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, prog.exeName.c_str(), -1, &exeName[0], exeNameLen, NULL, NULL);
|
||||
|
||||
programMap[flutter::EncodableValue("name")] = flutter::EncodableValue(name);
|
||||
programMap[flutter::EncodableValue("packageName")] = flutter::EncodableValue(exeName);
|
||||
programMap[flutter::EncodableValue("isSystemApp")] = flutter::EncodableValue(false);
|
||||
|
||||
programList.push_back(flutter::EncodableValue(programMap));
|
||||
}
|
||||
|
||||
result->Success(flutter::EncodableValue(programList));
|
||||
}
|
||||
```
|
||||
|
||||
**Регистрация Method Channel:**
|
||||
|
||||
```cpp
|
||||
// В flutter_window.cpp, метод OnCreate()
|
||||
|
||||
#include <flutter/method_channel.h>
|
||||
#include <flutter/standard_method_codec.h>
|
||||
|
||||
// В FlutterWindow::OnCreate() после создания flutter_controller_:
|
||||
|
||||
auto channel = std::make_unique<flutter::MethodChannel<>>(
|
||||
flutter_controller_->engine()->messenger(),
|
||||
"com.umbrix.app/platform",
|
||||
&flutter::StandardMethodCodec::GetInstance());
|
||||
|
||||
channel->SetMethodCallHandler(
|
||||
[](const auto& call, auto result) {
|
||||
if (call.method_name() == "get_installed_packages") {
|
||||
HandleGetInstalledPrograms(call, std::move(result));
|
||||
} else {
|
||||
result->NotImplemented();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### 2. Dart - адаптировать репозиторий ✅ ПРОСТО
|
||||
|
||||
**Файл:** `lib/features/per_app_proxy/data/per_app_proxy_repository.dart`
|
||||
|
||||
```dart
|
||||
class PerAppProxyRepositoryImpl with InfraLogger implements PerAppProxyRepository {
|
||||
final _methodChannel = const MethodChannel("com.umbrix.app/platform");
|
||||
|
||||
@override
|
||||
TaskEither<String, List<InstalledPackageInfo>> getInstalledPackages() {
|
||||
return TaskEither(
|
||||
() async {
|
||||
loggy.debug("getting installed packages info");
|
||||
|
||||
// ✅ УЖЕ РАБОТАЕТ на Android
|
||||
// ✅ БУДЕТ РАБОТАТЬ на Windows после добавления C++ кода
|
||||
final result = await _methodChannel.invokeMethod<String>("get_installed_packages");
|
||||
|
||||
if (result == null) return left("null response");
|
||||
return right(
|
||||
(jsonDecode(result) as List).map((e) {
|
||||
return InstalledPackageInfo.fromJson(e as Map<String, dynamic>);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Uint8List> getPackageIcon(String packageName) {
|
||||
// Для Windows можно извлекать иконку из .exe файла
|
||||
// Или использовать placeholder иконку
|
||||
return TaskEither(
|
||||
() async {
|
||||
if (PlatformUtils.isDesktop) {
|
||||
// Временно возвращать пустую иконку
|
||||
return right(Uint8List(0));
|
||||
}
|
||||
|
||||
// Android код остаётся без изменений
|
||||
loggy.debug("getting package [$packageName] icon");
|
||||
final result = await _methodChannel.invokeMethod<String>(
|
||||
"get_package_icon",
|
||||
{"packageName": packageName},
|
||||
);
|
||||
if (result == null) return left("null response");
|
||||
final Uint8List decoded;
|
||||
try {
|
||||
decoded = base64.decode(result);
|
||||
} catch (e) {
|
||||
return left("error parsing base64 response");
|
||||
}
|
||||
return right(decoded);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. libcore - передать список процессов ✅ СРЕДНЯЯ СЛОЖНОСТЬ
|
||||
|
||||
**Файл:** `libcore/config/config.go`
|
||||
|
||||
Нужно модифицировать функцию которая создаёт правила роутинга:
|
||||
|
||||
```go
|
||||
// Добавить в структуру HiddifyOptions
|
||||
type HiddifyOptions struct {
|
||||
// ...существующие поля...
|
||||
|
||||
// Новые поля для Per-App Proxy
|
||||
PerAppProxyMode string `json:"per_app_proxy_mode"` // "off", "include", "exclude"
|
||||
IncludedApplications []string `json:"included_applications"` // ["chrome.exe", "firefox.exe"]
|
||||
ExcludedApplications []string `json:"excluded_applications"` // ["steam.exe", "uTorrent.exe"]
|
||||
}
|
||||
|
||||
// В функции buildRouteRules() после существующих правил:
|
||||
|
||||
func buildRouteRules(options *HiddifyOptions) []option.Rule {
|
||||
var routeRules []option.Rule
|
||||
|
||||
// ...существующие правила...
|
||||
|
||||
// ====== PER-APP PROXY ДЛЯ WINDOWS/LINUX/MACOS ======
|
||||
if runtime.GOOS != "android" { // Не Android
|
||||
if options.PerAppProxyMode == "include" && len(options.IncludedApplications) > 0 {
|
||||
// РЕЖИМ: Только выбранные приложения идут через VPN
|
||||
|
||||
// 1. Выбранные приложения → VPN
|
||||
routeRules = append(routeRules, option.Rule{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultRule{
|
||||
ProcessName: options.IncludedApplications, // ["chrome.exe", "firefox.exe"]
|
||||
Outbound: OutboundSelectTag, // Через VPN
|
||||
},
|
||||
})
|
||||
|
||||
// 2. Все остальные → Direct
|
||||
routeRules = append(routeRules, option.Rule{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultRule{
|
||||
Outbound: OutboundDirectTag, // Напрямую
|
||||
},
|
||||
})
|
||||
|
||||
} else if options.PerAppProxyMode == "exclude" && len(options.ExcludedApplications) > 0 {
|
||||
// РЕЖИМ: Исключённые приложения НЕ идут через VPN
|
||||
|
||||
// 1. Исключённые приложения → Direct
|
||||
routeRules = append(routeRules, option.Rule{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultRule{
|
||||
ProcessName: options.ExcludedApplications, // ["steam.exe", "uTorrent.exe"]
|
||||
Outbound: OutboundDirectTag, // Напрямую
|
||||
},
|
||||
})
|
||||
|
||||
// 2. Все остальные → VPN (это уже есть в default правилах)
|
||||
}
|
||||
}
|
||||
|
||||
// Убедиться что Umbrix сам не идёт через VPN (чтобы избежать циклов)
|
||||
routeRules = append(routeRules, option.Rule{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultRule{
|
||||
ProcessName: []string{
|
||||
"umbrix.exe",
|
||||
"Umbrix.exe",
|
||||
"UmbrixCli.exe",
|
||||
"umbrix", // Linux
|
||||
"Umbrix", // macOS
|
||||
},
|
||||
Outbound: OutboundBypassTag, // Всегда напрямую
|
||||
},
|
||||
})
|
||||
|
||||
return routeRules
|
||||
}
|
||||
```
|
||||
|
||||
**Файл:** `libcore/extension/interface.go`
|
||||
|
||||
Передавать опции из Flutter:
|
||||
|
||||
```go
|
||||
func changeHiddifyOptions(jsonData string) error {
|
||||
var opts HiddifyOptions
|
||||
if err := json.Unmarshal([]byte(jsonData), &opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Логирование для отладки
|
||||
if opts.PerAppProxyMode != "off" {
|
||||
log.Printf("[Per-App] Режим: %s", opts.PerAppProxyMode)
|
||||
if opts.PerAppProxyMode == "include" {
|
||||
log.Printf("[Per-App] Включены приложения: %v", opts.IncludedApplications)
|
||||
} else if opts.PerAppProxyMode == "exclude" {
|
||||
log.Printf("[Per-App] Исключены приложения: %v", opts.ExcludedApplications)
|
||||
}
|
||||
}
|
||||
|
||||
// Пересоздать конфигурацию с новыми правилами
|
||||
return recreateConfigWithOptions(opts)
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. Flutter - передавать список в libcore ✅ ПРОСТО
|
||||
|
||||
**Файл:** `lib/singbox/model/singbox_config_option.dart`
|
||||
|
||||
Добавить поля в `SingboxConfigOption`:
|
||||
|
||||
```dart
|
||||
@freezed
|
||||
class SingboxConfigOption with _$SingboxConfigOption {
|
||||
const SingboxConfigOption._();
|
||||
|
||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
||||
const factory SingboxConfigOption({
|
||||
// ...существующие поля...
|
||||
required List<SingboxRule> rules,
|
||||
required SingboxMuxOption mux,
|
||||
required SingboxTlsTricks tlsTricks,
|
||||
required SingboxWarpOption warp,
|
||||
required SingboxWarpOption warp2,
|
||||
|
||||
// ✨ НОВЫЕ ПОЛЯ
|
||||
@Default("off") String perAppProxyMode, // "off", "include", "exclude"
|
||||
@Default([]) List<String> includedApplications, // ["chrome.exe"]
|
||||
@Default([]) List<String> excludedApplications, // ["steam.exe"]
|
||||
}) = _SingboxConfigOption;
|
||||
|
||||
factory SingboxConfigOption.fromJson(Map<String, dynamic> json) =>
|
||||
_$SingboxConfigOptionFromJson(json);
|
||||
}
|
||||
```
|
||||
|
||||
**Файл:** `lib/features/config_option/data/config_option_repository.dart`
|
||||
|
||||
Добавить считывание настроек:
|
||||
|
||||
```dart
|
||||
Future<SingboxConfigOption> getConfigOptions() async {
|
||||
final preferences = await ref.read(sharedPreferencesProvider.future);
|
||||
|
||||
// ...существующие настройки...
|
||||
|
||||
// ✨ Читать Per-App Proxy настройки
|
||||
final perAppProxyMode = preferences.getString("per_app_proxy_mode") ?? "off";
|
||||
final List<String> perAppList;
|
||||
|
||||
if (perAppProxyMode == "include") {
|
||||
perAppList = preferences.getStringList("per_app_proxy_include_list") ?? [];
|
||||
} else if (perAppProxyMode == "exclude") {
|
||||
perAppList = preferences.getStringList("per_app_proxy_exclude_list") ?? [];
|
||||
} else {
|
||||
perAppList = [];
|
||||
}
|
||||
|
||||
return SingboxConfigOption(
|
||||
// ...существующие параметры...
|
||||
|
||||
// ✨ Передать в libcore
|
||||
perAppProxyMode: perAppProxyMode,
|
||||
includedApplications: perAppProxyMode == "include" ? perAppList : [],
|
||||
excludedApplications: perAppProxyMode == "exclude" ? perAppList : [],
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 План реализации
|
||||
|
||||
### Этап 1: Windows C++ Plugin (1-2 часа)
|
||||
- [x] Создать функцию `GetRunningProcesses()`
|
||||
- [x] Создать функцию `ScanInstalledPrograms()`
|
||||
- [x] Создать функцию `GetFileDescription()`
|
||||
- [x] Добавить Method Channel handler
|
||||
- [x] Тестирование: список программ отображается
|
||||
|
||||
### Этап 2: Dart адаптация (30 минут)
|
||||
- [ ] Обновить `per_app_proxy_repository.dart` для Desktop
|
||||
- [ ] Добавить placeholder иконки для Windows
|
||||
- [ ] Тестирование: UI показывает программы
|
||||
|
||||
### Этап 3: libcore конфигурация (1 час)
|
||||
- [ ] Добавить поля в `HiddifyOptions`
|
||||
- [ ] Модифицировать `buildRouteRules()`
|
||||
- [ ] Добавить логирование
|
||||
- [ ] Пересобрать libcore для Windows
|
||||
|
||||
### Этап 4: Flutter интеграция (30 минут)
|
||||
- [ ] Добавить поля в `SingboxConfigOption`
|
||||
- [ ] Обновить `config_option_repository.dart`
|
||||
- [ ] Генерация кода: `flutter pub run build_runner build`
|
||||
|
||||
### Этап 5: Тестирование (1 час)
|
||||
- [ ] Режим "Все": все приложения через VPN
|
||||
- [ ] Режим "Включить": только Chrome через VPN
|
||||
- [ ] Режим "Исключить": Steam не через VPN
|
||||
- [ ] Проверить логи libcore
|
||||
- [ ] Проверить IP адрес в браузере vs Steam
|
||||
|
||||
### Этап 6: Оптимизация (опционально)
|
||||
- [ ] Кэширование списка программ
|
||||
- [ ] Извлечение иконок из .exe файлов
|
||||
- [ ] Фоновое сканирование при запуске
|
||||
- [ ] Фильтр по папкам (не показывать system32)
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Примеры использования
|
||||
|
||||
### Пример 1: Только браузер через VPN
|
||||
|
||||
```
|
||||
Настройки → Сеть → Исключения → вкладка "Приложения"
|
||||
|
||||
Режим: Включить [Proxy]
|
||||
Выбрать:
|
||||
✅ Google Chrome
|
||||
✅ Mozilla Firefox
|
||||
✅ Microsoft Edge
|
||||
|
||||
Результат:
|
||||
- Браузеры идут через VPN ✅
|
||||
- Все остальные программы напрямую ❌
|
||||
```
|
||||
|
||||
### Пример 2: Исключить торренты и игры
|
||||
|
||||
```
|
||||
Настройки → Сеть → Исключения → вкладка "Приложения"
|
||||
|
||||
Режим: Исключить [Exclude]
|
||||
Выбрать:
|
||||
✅ uTorrent
|
||||
✅ Steam
|
||||
✅ Epic Games Launcher
|
||||
|
||||
Результат:
|
||||
- Торренты и игры идут напрямую ❌
|
||||
- Все остальные программы через VPN ✅
|
||||
```
|
||||
|
||||
### Пример 3: Исключить банковские приложения
|
||||
|
||||
```
|
||||
Режим: Исключить [Exclude]
|
||||
Выбрать:
|
||||
✅ Sberbank Online
|
||||
✅ Тинькофф
|
||||
|
||||
Результат:
|
||||
- Банки работают со своими серверами ❌
|
||||
- Остальное через VPN ✅
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Сравнение платформ
|
||||
|
||||
| Функция | Android | Windows | Linux | macOS |
|
||||
|---------|---------|---------|-------|-------|
|
||||
| Получить список приложений | ✅ | 🔨 | 🔨 | 🔨 |
|
||||
| Иконки приложений | ✅ | ⏳ | ⏳ | ⏳ |
|
||||
| Режим "Включить" | ✅ | 🔨 | 🔨 | 🔨 |
|
||||
| Режим "Исключить" | ✅ | 🔨 | 🔨 | 🔨 |
|
||||
| Фильтр по процессам | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
✅ = Работает
|
||||
🔨 = Нужно реализовать
|
||||
⏳ = Опционально
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Известные ограничения
|
||||
|
||||
### Windows
|
||||
- ❌ Не все программы могут быть обнаружены (портативные версии)
|
||||
- ❌ UWP приложения из Microsoft Store могут требовать специального подхода
|
||||
- ⚠️ Фильтрация работает только по имени .exe (не по пути)
|
||||
- ⚠️ Если программа меняет имя процесса, фильтр не сработает
|
||||
|
||||
### Sing-box
|
||||
- ⚠️ `process_name` работает только в режиме TUN
|
||||
- ❌ Не работает в режиме System Proxy
|
||||
- ⚠️ На Linux требуется `/proc` доступ
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Полезные ссылки
|
||||
|
||||
- **Sing-box routing rules:** https://sing-box.sagernet.org/configuration/route/rule/
|
||||
- **Android VPNService:** https://developer.android.com/reference/android/net/VpnService
|
||||
- **Windows Process Enumeration:** https://learn.microsoft.com/en-us/windows/win32/toolhelp/taking-a-snapshot-and-viewing-processes
|
||||
|
||||
---
|
||||
|
||||
## ✅ Чеклист для разработчика
|
||||
|
||||
Перед началом работы убедитесь что:
|
||||
- [ ] Установлен Visual Studio 2022 с C++ Desktop Development
|
||||
- [ ] Есть доступ к `windows/runner/flutter_window.cpp`
|
||||
- [ ] Знакомы с Flutter Platform Channels
|
||||
- [ ] Понимаете как работает sing-box routing
|
||||
|
||||
После реализации проверьте:
|
||||
- [ ] Список программ загружается без ошибок
|
||||
- [ ] UI отзывчив (сканирование не блокирует интерфейс)
|
||||
- [ ] Настройки сохраняются после перезапуска
|
||||
- [ ] VPN работает с выбранными приложениями
|
||||
- [ ] Логи libcore показывают правильные правила
|
||||
- [ ] Нет утечек памяти при переключении режимов
|
||||
|
||||
---
|
||||
|
||||
## 💡 Альтернативные подходы
|
||||
|
||||
### Подход 1: Windows Filtering Platform (WFP) - СЛОЖНО
|
||||
Использовать Windows Firewall API для фильтрации по PID процесса.
|
||||
- ✅ Более надёжно (работает на уровне ядра)
|
||||
- ❌ Очень сложно в реализации
|
||||
- ❌ Требует драйвер или высокие привилегии
|
||||
|
||||
### Подход 2: Прокси авторизация - СРЕДНЕ
|
||||
Настроить прокси с авторизацией по процессу.
|
||||
- ✅ Не требует TUN режим
|
||||
- ❌ Не все приложения поддерживают прокси
|
||||
- ❌ Нужен специальный прокси сервер
|
||||
|
||||
### Подход 3: Текущий (process_name в sing-box) - ПРОСТО ✅
|
||||
Использовать встроенную поддержку sing-box.
|
||||
- ✅ Уже реализовано в sing-box
|
||||
- ✅ Работает надёжно
|
||||
- ✅ Не требует дополнительных драйверов
|
||||
- ⚠️ Только в TUN режиме
|
||||
|
||||
---
|
||||
|
||||
**Рекомендуемый подход:** #3 (текущий через sing-box)
|
||||
|
||||
Время на полную реализацию: **3-4 часа**
|
||||
Сложность: **Средняя** (C++ + Dart + Go)
|
||||
Приоритет: **Высокий** (очень востребованная функция)
|
||||
106
WINDOWS_SPLIT_TUNNELING_QUICK.md
Normal file
106
WINDOWS_SPLIT_TUNNELING_QUICK.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# 🚀 Быстрый старт: Split Tunneling для Windows
|
||||
|
||||
## Что это?
|
||||
|
||||
Выборочный VPN - некоторые приложения через VPN, другие напрямую.
|
||||
|
||||
**Примеры:**
|
||||
- ✅ Только браузер через VPN
|
||||
- ✅ Исключить Steam и торренты из VPN
|
||||
- ✅ Исключить банковские приложения
|
||||
|
||||
## Текущее состояние
|
||||
|
||||
| Платформа | Статус |
|
||||
|-----------|--------|
|
||||
| Android | ✅ Работает |
|
||||
| Windows | ⏳ Нужно реализовать |
|
||||
| Linux | ⏳ Нужно реализовать |
|
||||
| macOS | ⏳ Нужно реализовать |
|
||||
|
||||
## Что нужно сделать?
|
||||
|
||||
### 1. Windows C++ код (1-2 часа)
|
||||
|
||||
**Файл:** `windows/runner/flutter_window.cpp`
|
||||
|
||||
Добавить функцию сканирования установленных программ:
|
||||
- Сканировать `C:\Program Files`
|
||||
- Сканировать `C:\Program Files (x86)`
|
||||
- Извлекать название из описания .exe
|
||||
- Возвращать JSON список через Method Channel
|
||||
|
||||
**API:** `com.umbrix.app/platform` → метод `get_installed_packages`
|
||||
|
||||
### 2. libcore routing (1 час)
|
||||
|
||||
**Файл:** `libcore/config/config.go`
|
||||
|
||||
Добавить в `HiddifyOptions`:
|
||||
```go
|
||||
PerAppProxyMode string // "off", "include", "exclude"
|
||||
IncludedApplications []string // ["chrome.exe", "firefox.exe"]
|
||||
ExcludedApplications []string // ["steam.exe"]
|
||||
```
|
||||
|
||||
Добавить правила:
|
||||
```go
|
||||
if mode == "include" {
|
||||
// Выбранные → VPN
|
||||
// Остальные → Direct
|
||||
} else if mode == "exclude" {
|
||||
// Выбранные → Direct
|
||||
// Остальные → VPN
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Flutter интеграция (30 минут)
|
||||
|
||||
**Файл:** `lib/singbox/model/singbox_config_option.dart`
|
||||
|
||||
```dart
|
||||
@Default("off") String perAppProxyMode,
|
||||
@Default([]) List<String> includedApplications,
|
||||
@Default([]) List<String> excludedApplications,
|
||||
```
|
||||
|
||||
Передавать в `changeHiddifyOptions()`.
|
||||
|
||||
## Тестирование
|
||||
|
||||
```powershell
|
||||
# 1. Собрать
|
||||
flutter build windows --release
|
||||
|
||||
# 2. Запустить
|
||||
.\build\windows\x64\runner\Release\umbrix.exe
|
||||
|
||||
# 3. Открыть
|
||||
Настройки → Сеть → Исключения → вкладка "Приложения"
|
||||
|
||||
# 4. Выбрать режим
|
||||
- Все: все приложения через VPN
|
||||
- Включить: только выбранные через VPN
|
||||
- Исключить: выбранные НЕ через VPN
|
||||
|
||||
# 5. Проверить
|
||||
- Открыть 2ip.ru в браузере (должен показать VPN IP)
|
||||
- Открыть Steam (должен показать реальный IP если исключён)
|
||||
```
|
||||
|
||||
## Полная документация
|
||||
|
||||
См. [WINDOWS_SPLIT_TUNNELING.md](./WINDOWS_SPLIT_TUNNELING.md) для:
|
||||
- Подробный архитектурный план
|
||||
- Полный C++ код
|
||||
- Примеры использования
|
||||
- Известные ограничения
|
||||
- Альтернативные подходы
|
||||
|
||||
## Приоритет
|
||||
|
||||
**ВЫСОКИЙ** - одна из самых востребованных функций!
|
||||
|
||||
Время: **3-4 часа**
|
||||
Сложность: **Средняя**
|
||||
Ожидаемый результат: ⭐⭐⭐⭐⭐
|
||||
26
appcast.xml
26
appcast.xml
@@ -3,11 +3,31 @@
|
||||
<channel>
|
||||
<title>Umbrix Updates</title>
|
||||
<description>Umbrix VPN автообновления</description>
|
||||
<link>http://localhost:8000</link>
|
||||
<link>https://update.umbrix.net</link>
|
||||
<language>ru</language>
|
||||
|
||||
<!-- Пример структуры для обновления версии 2.5.8 -->
|
||||
<!-- Раскомментируйте и измените, когда будет новая версия -->
|
||||
<!-- Версия 1.7.5 - тестовое обновление -->
|
||||
<item>
|
||||
<title>Umbrix 1.7.5</title>
|
||||
<description>🎨 Брендинг обновлён:
|
||||
- Заменена иконка CLI на Umbrix
|
||||
- Переименован HiddifyCli.exe → UmbrixCli.exe
|
||||
- Улучшена стабильность</description>
|
||||
<pubDate>Mon, 20 Jan 2026 09:30:00 +0000</pubDate>
|
||||
<sparkle:version>1.7.5</sparkle:version>
|
||||
<sparkle:shortVersionString>1.7.5</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>1.0.0</sparkle:minimumSystemVersion>
|
||||
|
||||
<!-- Windows Portable ZIP (no UAC, no SmartScreen!) -->
|
||||
<enclosure
|
||||
url="https://update.umbrix.net/vodorod/umbrix/releases/download/v1.7.5/umbrix-1.7.5-portable-windows-x64.zip"
|
||||
sparkle:version="1.7.5"
|
||||
sparkle:os="windows"
|
||||
type="application/zip"
|
||||
length="38653952" />
|
||||
</item>
|
||||
|
||||
<!-- Пример структуры для будущих обновлений -->
|
||||
<!--
|
||||
<item>
|
||||
<title>Umbrix 2.5.8</title>
|
||||
|
||||
@@ -163,6 +163,15 @@ Future<void> _performBootstrap(
|
||||
);
|
||||
}
|
||||
|
||||
// Автопроверка обновлений для Android
|
||||
if (Platform.isAndroid) {
|
||||
_safeInit(
|
||||
"auto check updates",
|
||||
() => container.read(appUpdateNotifierProvider.notifier).checkSilently(),
|
||||
timeout: 5000,
|
||||
);
|
||||
}
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
await _safeInit(
|
||||
"android display mode",
|
||||
|
||||
@@ -9,7 +9,10 @@ import 'package:umbrix/core/model/constants.dart';
|
||||
import 'package:umbrix/core/router/router.dart';
|
||||
import 'package:umbrix/core/theme/app_theme.dart';
|
||||
import 'package:umbrix/core/theme/theme_preferences.dart';
|
||||
import 'package:umbrix/core/notification/in_app_notification_controller.dart';
|
||||
import 'package:umbrix/features/app_update/notifier/app_update_notifier.dart';
|
||||
import 'package:umbrix/features/app_update/notifier/app_update_state.dart';
|
||||
import 'package:umbrix/features/app_update/widget/new_version_dialog.dart';
|
||||
import 'package:umbrix/features/connection/widget/connection_wrapper.dart';
|
||||
import 'package:umbrix/features/profile/notifier/profiles_update_notifier.dart';
|
||||
import 'package:umbrix/features/shortcut/shortcut_wrapper.dart';
|
||||
@@ -34,6 +37,31 @@ class App extends HookConsumerWidget with PresLogger {
|
||||
|
||||
ref.listen(foregroundProfilesUpdateNotifierProvider, (_, __) {});
|
||||
|
||||
// Слушаем состояние обновлений и показываем диалог
|
||||
ref.listen(appUpdateNotifierProvider, (previous, next) {
|
||||
if (next is AppUpdateStateAvailable) {
|
||||
// Получаем BuildContext через router
|
||||
final context = router.routerDelegate.navigatorKey.currentContext;
|
||||
if (context != null && context.mounted) {
|
||||
// Показываем диалог обновления
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (context.mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => NewVersionDialog(
|
||||
newVersion: next.versionInfo,
|
||||
onIgnore: () {
|
||||
ref.read(appUpdateNotifierProvider.notifier).ignoreRelease(next.versionInfo);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return WindowWrapper(
|
||||
TrayWrapper(
|
||||
ShortcutWrapper(
|
||||
|
||||
@@ -38,23 +38,41 @@ class RemoteVersionEntity with _$RemoteVersionEntity {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Для Windows - ищем .exe с приоритетом x64
|
||||
if (extension == '.exe') {
|
||||
// Сначала ищем x64 setup/installer
|
||||
for (final pattern in ['x64', 'amd64', 'win64', 'setup', 'installer']) {
|
||||
try {
|
||||
final asset = assets.firstWhere(
|
||||
(asset) => asset.name.toLowerCase().contains(pattern) && asset.name.endsWith('.exe'),
|
||||
);
|
||||
return asset.downloadUrl;
|
||||
} catch (_) {
|
||||
continue;
|
||||
// Для Windows - ищем .exe или .zip
|
||||
if (extension == '.exe' || extension == '.zip') {
|
||||
final targetExt = extension;
|
||||
|
||||
// Приоритет для zip: portable -> windows -> любой .zip
|
||||
if (targetExt == '.zip') {
|
||||
for (final pattern in ['portable', 'windows', 'win']) {
|
||||
try {
|
||||
final asset = assets.firstWhere(
|
||||
(asset) => asset.name.toLowerCase().contains(pattern) && asset.name.endsWith('.zip'),
|
||||
);
|
||||
return asset.downloadUrl;
|
||||
} catch (_) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Если не нашли специфичный - берём любой .exe
|
||||
|
||||
// Приоритет для exe: x64 setup/installer
|
||||
if (targetExt == '.exe') {
|
||||
for (final pattern in ['x64', 'amd64', 'win64', 'setup', 'installer']) {
|
||||
try {
|
||||
final asset = assets.firstWhere(
|
||||
(asset) => asset.name.toLowerCase().contains(pattern) && asset.name.endsWith('.exe'),
|
||||
);
|
||||
return asset.downloadUrl;
|
||||
} catch (_) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Если не нашли специфичный - берём любой с нужным расширением
|
||||
try {
|
||||
final asset = assets.firstWhere((asset) => asset.name.endsWith('.exe'));
|
||||
final asset = assets.firstWhere((asset) => asset.name.endsWith(targetExt));
|
||||
return asset.downloadUrl;
|
||||
} catch (_) {
|
||||
return null;
|
||||
@@ -74,7 +92,7 @@ class RemoteVersionEntity with _$RemoteVersionEntity {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Если не нашли - берём любой .dmg
|
||||
try {
|
||||
final asset = assets.firstWhere((asset) => asset.name.endsWith('.dmg'));
|
||||
|
||||
@@ -73,9 +73,15 @@ class NewVersionDialog extends HookConsumerWidget with PresLogger {
|
||||
|
||||
// Определяем нужное расширение файла
|
||||
String fileExt = '';
|
||||
if (Platform.isWindows)
|
||||
fileExt = '.exe';
|
||||
else if (Platform.isMacOS)
|
||||
if (Platform.isWindows) {
|
||||
// Для Windows приоритет: .zip (portable) → .exe (installer)
|
||||
// ZIP не требует UAC и подписи кода!
|
||||
fileExt = '.zip';
|
||||
final zipUrl = newVersion.findAssetByExtension('.zip');
|
||||
if (zipUrl == null) {
|
||||
fileExt = '.exe'; // Fallback на .exe если нет .zip
|
||||
}
|
||||
} else if (Platform.isMacOS)
|
||||
fileExt = '.dmg';
|
||||
else if (Platform.isLinux) fileExt = '.deb';
|
||||
|
||||
@@ -129,23 +135,13 @@ class NewVersionDialog extends HookConsumerWidget with PresLogger {
|
||||
if (context.mounted) {
|
||||
CustomToast('Установка обновления...', type: AlertType.info).show(context);
|
||||
}
|
||||
|
||||
|
||||
// Запускаем установщик в тихом режиме с правами администратора
|
||||
// /VERYSILENT - без UI, /SUPPRESSMSGBOXES - без диалогов
|
||||
// /NORESTART - не перезагружать систему
|
||||
final result = await Process.run(
|
||||
'powershell',
|
||||
[
|
||||
'-Command',
|
||||
'Start-Process',
|
||||
'-FilePath',
|
||||
'"$savePath"',
|
||||
'-ArgumentList',
|
||||
'"/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART"',
|
||||
'-Verb',
|
||||
'RunAs',
|
||||
'-Wait'
|
||||
],
|
||||
['-Command', 'Start-Process', '-FilePath', '"$savePath"', '-ArgumentList', '"/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART"', '-Verb', 'RunAs', '-Wait'],
|
||||
);
|
||||
|
||||
if (result.exitCode == 0) {
|
||||
@@ -166,6 +162,76 @@ class NewVersionDialog extends HookConsumerWidget with PresLogger {
|
||||
}
|
||||
}
|
||||
|
||||
// Windows portable ZIP update
|
||||
if (Platform.isWindows && fileExt == '.zip') {
|
||||
try {
|
||||
if (context.mounted) {
|
||||
CustomToast('Установка обновления из ZIP...', type: AlertType.info).show(context);
|
||||
}
|
||||
|
||||
// Получить путь к исполняемому файлу приложения
|
||||
final exePath = Platform.resolvedExecutable;
|
||||
final appDir = Directory(exePath).parent.path;
|
||||
|
||||
// Распаковать во временную папку
|
||||
final tempDir = Directory('${Directory.systemTemp.path}\\umbrix_update_${DateTime.now().millisecondsSinceEpoch}');
|
||||
await tempDir.create(recursive: true);
|
||||
|
||||
loggy.info('Extracting ZIP to: ${tempDir.path}');
|
||||
|
||||
// Распаковка через PowerShell
|
||||
final extractResult = await Process.run(
|
||||
'powershell',
|
||||
['-Command', 'Expand-Archive', '-Path', '"$savePath"', '-DestinationPath', '"${tempDir.path}"', '-Force'],
|
||||
);
|
||||
|
||||
if (extractResult.exitCode != 0) {
|
||||
throw Exception('Failed to extract ZIP: ${extractResult.stderr}');
|
||||
}
|
||||
|
||||
loggy.info('ZIP extracted successfully');
|
||||
|
||||
// Скрипт для замены файлов после закрытия приложения
|
||||
final updateScript = '''
|
||||
@echo off
|
||||
echo Waiting for application to close...
|
||||
timeout /t 3 /nobreak > nul
|
||||
|
||||
echo Updating files...
|
||||
xcopy /E /Y "${tempDir.path}\\*" "$appDir\\"
|
||||
|
||||
echo Cleanup...
|
||||
rmdir /S /Q "${tempDir.path}"
|
||||
|
||||
echo Starting application...
|
||||
start "" "$exePath"
|
||||
|
||||
echo Update complete!
|
||||
del "%~f0"
|
||||
''';
|
||||
|
||||
final scriptPath = '${Directory.systemTemp.path}\\umbrix_update.bat';
|
||||
await File(scriptPath).writeAsString(updateScript);
|
||||
|
||||
if (context.mounted) {
|
||||
CustomToast.success('Обновление установлено! Приложение перезагрузится...').show(context);
|
||||
context.pop();
|
||||
}
|
||||
|
||||
// Запустить скрипт и закрыть приложение
|
||||
await Process.start('cmd', ['/c', scriptPath], mode: ProcessStartMode.detached);
|
||||
|
||||
// Задержка перед выходом
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
exit(0);
|
||||
});
|
||||
|
||||
return;
|
||||
} catch (e) {
|
||||
loggy.warning('Failed to install from ZIP: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Для других платформ или если автоустановка не сработала - просто открываем файл
|
||||
final result = await OpenFile.open(savePath);
|
||||
|
||||
|
||||
@@ -334,6 +334,32 @@ abstract class ConfigOptions {
|
||||
"",
|
||||
);
|
||||
|
||||
// Per-App Proxy Settings (Desktop platforms)
|
||||
static final perAppProxyMode = PreferencesNotifier.create<String, String>(
|
||||
"per-app-proxy-mode",
|
||||
"off",
|
||||
);
|
||||
|
||||
static final perAppProxyIncludeList = PreferencesNotifier.create<List<String>, String>(
|
||||
"per-app-proxy-include-list",
|
||||
[],
|
||||
mapFrom: (value) {
|
||||
if (value.isEmpty) return [];
|
||||
return value.split(',').map((e) => e.trim()).where((e) => e.isNotEmpty).toList();
|
||||
},
|
||||
mapTo: (value) => value.join(','),
|
||||
);
|
||||
|
||||
static final perAppProxyExcludeList = PreferencesNotifier.create<List<String>, String>(
|
||||
"per-app-proxy-exclude-list",
|
||||
[],
|
||||
mapFrom: (value) {
|
||||
if (value.isEmpty) return [];
|
||||
return value.split(',').map((e) => e.trim()).where((e) => e.isNotEmpty).toList();
|
||||
},
|
||||
mapTo: (value) => value.join(','),
|
||||
);
|
||||
|
||||
static final hasExperimentalFeatures = Provider.autoDispose<bool>(
|
||||
(ref) {
|
||||
final mode = ref.watch(serviceMode);
|
||||
@@ -557,6 +583,10 @@ abstract class ConfigOptions {
|
||||
noiseSize: ref.watch(warpNoiseSize),
|
||||
noiseDelay: ref.watch(warpNoiseDelay),
|
||||
),
|
||||
// Per-App Proxy
|
||||
perAppProxyMode: ref.watch(perAppProxyMode),
|
||||
includedApplications: ref.watch(perAppProxyMode) == 'include' ? ref.watch(perAppProxyIncludeList) : [],
|
||||
excludedApplications: ref.watch(perAppProxyMode) == 'exclude' ? ref.watch(perAppProxyExcludeList) : [],
|
||||
// geoipPath: ref.watch(geoAssetPathResolverProvider).relativePath(
|
||||
// geoAssets.geoip.providerName,
|
||||
// geoAssets.geoip.fileName,
|
||||
|
||||
@@ -52,6 +52,9 @@ class SingboxConfigOption with _$SingboxConfigOption {
|
||||
required SingboxTlsTricks tlsTricks,
|
||||
required SingboxWarpOption warp,
|
||||
required SingboxWarpOption warp2,
|
||||
@JsonKey(name: 'per-app-proxy-mode') @Default('off') String perAppProxyMode,
|
||||
@JsonKey(name: 'included-applications') @Default([]) List<String> includedApplications,
|
||||
@JsonKey(name: 'excluded-applications') @Default([]) List<String> excludedApplications,
|
||||
}) = _SingboxConfigOption;
|
||||
|
||||
String format() {
|
||||
|
||||
2
libcore
2
libcore
Submodule libcore updated: 8b6f4d6f20...85db0efc59
@@ -1,7 +1,7 @@
|
||||
name: umbrix
|
||||
description: Cross Platform Multi Protocol Proxy Frontend.
|
||||
publish_to: "none"
|
||||
version: 1.7.3+173
|
||||
version: 1.7.6+176
|
||||
|
||||
environment:
|
||||
sdk: ">=3.3.0 <4.0.0"
|
||||
|
||||
@@ -1,18 +1,46 @@
|
||||
New-Item -ItemType Directory -Force -Name "dist\tmp"
|
||||
New-Item -ItemType Directory -Force -Name "out"
|
||||
# Umbrix Windows Packaging Script (based on Hiddify)
|
||||
# Creates: Setup.exe + Portable.zip
|
||||
|
||||
# windows setup
|
||||
# Get-ChildItem -Recurse -File -Path "dist" -Filter "*windows-setup.exe" | Copy-Item -Destination "dist\tmp\hiddify-next-setup.exe" -ErrorAction SilentlyContinue
|
||||
# Compress-Archive -Force -Path "dist\tmp\hiddify-next-setup.exe",".github\help\mac-windows\*.url" -DestinationPath "out\hiddify-windows-x64-setup.zip"
|
||||
Get-ChildItem -Recurse -File -Path "dist" -Filter "*windows-setup.exe" | Copy-Item -Destination "out\Hiddify-Windows-Setup-x64.exe" -ErrorAction SilentlyContinue
|
||||
Get-ChildItem -Recurse -File -Path "dist" -Filter "*windows.msix" | Copy-Item -Destination "out\Hiddify-Windows-Setup-x64.msix" -ErrorAction SilentlyContinue
|
||||
$Version = "1.7.5"
|
||||
|
||||
Write-Host "===============================================" -ForegroundColor Cyan
|
||||
Write-Host " Umbrix Windows Packaging v$Version" -ForegroundColor Cyan
|
||||
Write-Host "===============================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# windows portable
|
||||
xcopy "build\windows\x64\runner\Release" "dist\tmp\hiddify-next" /E/H/C/I/Y
|
||||
xcopy ".github\help\mac-windows\*.url" "dist\tmp\hiddify-next" /E/H/C/I/Y
|
||||
Compress-Archive -Force -Path "dist\tmp\hiddify-next" -DestinationPath "out\Hiddify-Windows-Portable-x64.zip" -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Name "dist\tmp" | Out-Null
|
||||
New-Item -ItemType Directory -Force -Name "out" | Out-Null
|
||||
|
||||
Remove-Item -Path "$HOME\.pub-cache\git\cache\flutter_circle_flags*" -Force -Recurse -ErrorAction SilentlyContinue
|
||||
# Windows Setup.exe (if exists in dist/)
|
||||
Write-Host "[1/2] Checking for Setup.exe..." -ForegroundColor Yellow
|
||||
$SetupExe = Get-ChildItem -Recurse -File -Path "dist" -Filter "*windows-setup.exe" -ErrorAction SilentlyContinue
|
||||
if ($SetupExe) {
|
||||
Copy-Item $SetupExe.FullName -Destination "out\Umbrix-Windows-Setup-x64.exe"
|
||||
$size = [math]::Round((Get-Item "out\Umbrix-Windows-Setup-x64.exe").Length / 1MB, 2)
|
||||
Write-Host "SUCCESS: Setup.exe copied: $size MB" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "WARNING: Setup.exe not found (will create via Inno Setup)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
echo "Done"
|
||||
# Windows Portable ZIP
|
||||
Write-Host ""
|
||||
Write-Host "[2/2] Creating Portable.zip..." -ForegroundColor Yellow
|
||||
xcopy "build\windows\x64\runner\Release" "dist\tmp\umbrix-portable" /E/H/C/I/Y | Out-Null
|
||||
Compress-Archive -Force -Path "dist\tmp\umbrix-portable" -DestinationPath "out\Umbrix-$Version-Windows-x64-Portable.zip"
|
||||
$size = [math]::Round((Get-Item "out\Umbrix-$Version-Windows-x64-Portable.zip").Length / 1MB, 2)
|
||||
Write-Host "SUCCESS: Portable.zip created: $size MB" -ForegroundColor Green
|
||||
|
||||
# Cleanup
|
||||
Remove-Item -Path "dist\tmp" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "===============================================" -ForegroundColor Cyan
|
||||
Write-Host " PACKAGING COMPLETE!" -ForegroundColor Green
|
||||
Write-Host "===============================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Files in out/:" -ForegroundColor White
|
||||
Get-ChildItem "out" -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
$filesize = [math]::Round($_.Length / 1MB, 2)
|
||||
Write-Host " * $($_.Name) - $filesize MB" -ForegroundColor Gray
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
284
update-server/CODE_SIGNING_WINDOWS.md
Normal file
284
update-server/CODE_SIGNING_WINDOWS.md
Normal file
@@ -0,0 +1,284 @@
|
||||
# Подпись кода для Windows (Code Signing)
|
||||
|
||||
## Зачем нужен сертификат?
|
||||
|
||||
При установке `.exe` файла без цифровой подписи Windows показывает предупреждения:
|
||||
- ⚠️ "Неизвестный издатель"
|
||||
- ⚠️ "Windows защитила ваш компьютер"
|
||||
- ⚠️ Windows Defender может блокировать файл
|
||||
|
||||
**С сертификатом:**
|
||||
- ✅ "Проверенный издатель: Umbrix Team"
|
||||
- ✅ Нет предупреждений SmartScreen
|
||||
- ✅ Пользователи доверяют приложению
|
||||
|
||||
## Текущее состояние (БЕЗ сертификата)
|
||||
|
||||
Приложение **РАБОТАЕТ**, но:
|
||||
1. При установке Windows показывает "Неизвестный издатель"
|
||||
2. Пользователь должен нажать "Все равно запустить"
|
||||
3. Антивирус может блокировать (ложное срабатывание)
|
||||
|
||||
**Для внутреннего использования это НОРМАЛЬНО** ✅
|
||||
|
||||
## Где получить сертификат?
|
||||
|
||||
### 1. Коммерческие сертификаты (EV Code Signing)
|
||||
|
||||
**Recommended:** Extended Validation (EV) - сразу доверие Windows
|
||||
|
||||
| Провайдер | Цена/год | Особенности |
|
||||
|-----------|----------|-------------|
|
||||
| **DigiCert** | $469-799 | Лучшая репутация, EV на USB токене |
|
||||
| **Sectigo (Comodo)** | $399-599 | Популярный, хорошая поддержка |
|
||||
| **GlobalSign** | $349-599 | Быстрое получение (1-2 дня) |
|
||||
| **SSL.com** | $299-499 | Дешевле, но дольше проверка |
|
||||
| **Certum** | €199-399 | Европейский, принимают криптовалюту |
|
||||
|
||||
**EV сертификат (Extended Validation):**
|
||||
- ✅ Сразу доверие Windows SmartScreen
|
||||
- ✅ Зелёная подпись "Проверенный издатель"
|
||||
- ✅ Никаких предупреждений
|
||||
- 📦 Выдается на USB токене (нельзя скопировать)
|
||||
- ⏱️ Проверка документов 3-7 дней
|
||||
|
||||
**Обычный Code Signing (не EV):**
|
||||
- ⚠️ Доверие нарастает со временем (нужно минимум 100-1000 установок)
|
||||
- ⚠️ Первые месяцы будут предупреждения SmartScreen
|
||||
- 💾 Файл .pfx/.p12 (можно хранить локально)
|
||||
- ⏱️ Получение 1-2 дня
|
||||
|
||||
### 2. Требования для получения
|
||||
|
||||
**Документы:**
|
||||
- Регистрация компании (ИНН, ОГРН, выписка из ЕГРЮЛ)
|
||||
- ИЛИ ИП (ОГРНИП)
|
||||
- ИЛИ личность физлица (паспорт, утилиты)
|
||||
|
||||
**Проверка:**
|
||||
- Подтверждение номера телефона
|
||||
- Email компании (домен должен совпадать)
|
||||
- Банковские реквизиты
|
||||
- Адрес офиса (могут позвонить или прислать письмо с кодом)
|
||||
|
||||
### 3. Процесс получения (DigiCert EV пример)
|
||||
|
||||
```
|
||||
1. Заказ → 2-3 минуты (онлайн форма)
|
||||
↓
|
||||
2. Оплата → $799/год
|
||||
↓
|
||||
3. Проверка документов → 3-7 дней
|
||||
- Загрузка ЕГРЮЛ/паспорта
|
||||
- Подтверждение телефона
|
||||
- Проверка адреса
|
||||
↓
|
||||
4. Отправка USB токена → 1-5 дней (DHL/FedEx)
|
||||
↓
|
||||
5. Установка драйверов → 10 минут
|
||||
↓
|
||||
6. Готово! Можно подписывать код
|
||||
```
|
||||
|
||||
## Как подписать файл после получения сертификата
|
||||
|
||||
### С EV сертификатом (USB токен)
|
||||
|
||||
```powershell
|
||||
# 1. Установить Windows SDK (включает signtool.exe)
|
||||
# Скачать: https://developer.microsoft.com/windows/downloads/windows-sdk/
|
||||
|
||||
# 2. Подключить USB токен
|
||||
|
||||
# 3. Подписать EXE
|
||||
$signtool = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe"
|
||||
& $signtool sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /a "umbrix-1.7.3-windows-setup.exe"
|
||||
|
||||
# Результат:
|
||||
# Successfully signed: umbrix-1.7.3-windows-setup.exe
|
||||
```
|
||||
|
||||
### С обычным Code Signing (.pfx файл)
|
||||
|
||||
```powershell
|
||||
# 1. Экспортировать сертификат в .pfx с паролем
|
||||
|
||||
# 2. Подписать EXE
|
||||
$signtool = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe"
|
||||
& $signtool sign /f "umbrix-cert.pfx" /p "PASSWORD" /tr http://timestamp.digicert.com /td sha256 /fd sha256 "umbrix-1.7.3-windows-setup.exe"
|
||||
```
|
||||
|
||||
### Проверка подписи
|
||||
|
||||
```powershell
|
||||
# Посмотреть информацию о подписи
|
||||
$signtool verify /pa /v "umbrix-1.7.3-windows-setup.exe"
|
||||
|
||||
# Или через GUI: ПКМ → Свойства → Цифровые подписи
|
||||
```
|
||||
|
||||
## Автоматизация с GitHub Actions
|
||||
|
||||
```yaml
|
||||
name: Build & Sign Windows
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.24.0'
|
||||
|
||||
- name: Build Windows
|
||||
run: flutter build windows --release
|
||||
|
||||
- name: Build Installer
|
||||
run: iscc windows/installer.iss
|
||||
|
||||
# Для EV сертификата - использовать self-hosted runner с USB токеном
|
||||
# Для обычного - загрузить .pfx в GitHub Secrets
|
||||
|
||||
- name: Sign EXE
|
||||
env:
|
||||
CERT_PASSWORD: ${{ secrets.CERT_PASSWORD }}
|
||||
run: |
|
||||
# Импорт .pfx из секрета
|
||||
$cert = [Convert]::FromBase64String("${{ secrets.CERT_BASE64 }}")
|
||||
[IO.File]::WriteAllBytes("cert.pfx", $cert)
|
||||
|
||||
# Подпись
|
||||
$signtool = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe"
|
||||
& $signtool sign /f cert.pfx /p $env:CERT_PASSWORD /tr http://timestamp.digicert.com /td sha256 /fd sha256 dist/umbrix-1.7.3-windows-setup.exe
|
||||
|
||||
# Удалить временный .pfx
|
||||
Remove-Item cert.pfx
|
||||
|
||||
- name: Upload to Gitea
|
||||
run: |
|
||||
curl -X POST "https://update.umbrix.net/api/v1/repos/vodorod/umbrix/releases/assets" `
|
||||
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" `
|
||||
--data-binary "@dist/umbrix-1.7.3-windows-setup.exe"
|
||||
```
|
||||
|
||||
## Альтернативы (НЕ рекомендуется)
|
||||
|
||||
### 1. Self-Signed сертификат (для тестирования)
|
||||
|
||||
```powershell
|
||||
# Создать self-signed сертификат
|
||||
$cert = New-SelfSignedCertificate -Type CodeSigningCert -Subject "CN=Umbrix Team" -CertStoreLocation Cert:\CurrentUser\My
|
||||
|
||||
# Экспортировать
|
||||
$pwd = ConvertTo-SecureString -String "password123" -Force -AsPlainText
|
||||
Export-PfxCertificate -Cert $cert -FilePath umbrix-selfsigned.pfx -Password $pwd
|
||||
|
||||
# Подписать
|
||||
signtool sign /f umbrix-selfsigned.pfx /p password123 /fd sha256 umbrix-1.7.3.exe
|
||||
```
|
||||
|
||||
**⚠️ Проблемы:**
|
||||
- Windows все равно показывает предупреждение
|
||||
- Нужно вручную добавлять сертификат в доверенные
|
||||
- НЕ работает на других компьютерах
|
||||
- Только для внутреннего тестирования
|
||||
|
||||
### 2. Open Source Certificate (StartSSL, Let's Encrypt)
|
||||
|
||||
❌ **Не работает для Code Signing!**
|
||||
- Let's Encrypt выдает только SSL/TLS (для HTTPS)
|
||||
- Code Signing требует проверку юрлица
|
||||
- Нет бесплатных Code Signing сертификатов
|
||||
|
||||
### 3. Использование osslsigncode (Linux подпись Windows файлов)
|
||||
|
||||
```bash
|
||||
# Установка на Linux
|
||||
sudo apt install osslsigncode
|
||||
|
||||
# Подпись (с .pfx сертификатом)
|
||||
osslsigncode sign \
|
||||
-pkcs12 umbrix-cert.pfx \
|
||||
-pass "PASSWORD" \
|
||||
-ts http://timestamp.digicert.com \
|
||||
-in umbrix-1.7.3-windows-setup.exe \
|
||||
-out umbrix-1.7.3-windows-setup-signed.exe
|
||||
```
|
||||
|
||||
## Рекомендации для Umbrix
|
||||
|
||||
### Для разработки и тестирования (СЕЙЧАС):
|
||||
✅ **Без сертификата** - просто предупреждение Windows
|
||||
- Пользователи могут нажать "Все равно запустить"
|
||||
- Работает для внутреннего использования
|
||||
- Бесплатно
|
||||
|
||||
### Для публичного релиза (БУДУЩЕЕ):
|
||||
✅ **DigiCert EV Code Signing** ($799/год)
|
||||
- Сразу доверие Windows
|
||||
- Нет предупреждений
|
||||
- Профессиональный вид
|
||||
- Рекомендуется для коммерческого продукта
|
||||
|
||||
### Для малого бюджета:
|
||||
✅ **Certum Open Source Code Signing** (€199/год)
|
||||
- Дешевле других
|
||||
- Принимают криптовалюту
|
||||
- Специальная цена для Open Source проектов
|
||||
- Требуют ссылку на GitHub/GitLab
|
||||
|
||||
## Ссылки
|
||||
|
||||
**Покупка сертификатов:**
|
||||
- DigiCert: https://www.digicert.com/signing/code-signing-certificates
|
||||
- Sectigo: https://sectigo.com/ssl-certificates-tls/code-signing
|
||||
- GlobalSign: https://www.globalsign.com/en/code-signing-certificate
|
||||
- SSL.com: https://www.ssl.com/certificates/code-signing/
|
||||
- Certum: https://en.sklep.certum.pl/data-safety/code-signing-certificates/
|
||||
|
||||
**Документация:**
|
||||
- Microsoft Code Signing: https://learn.microsoft.com/windows/win32/seccrypto/cryptography-tools
|
||||
- signtool.exe: https://learn.microsoft.com/windows-hardware/drivers/devtest/signtool
|
||||
|
||||
**Проверка репутации SmartScreen:**
|
||||
- https://www.microsoft.com/en-us/wdsi/filesubmission
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Можно ли работать без сертификата?**
|
||||
A: Да! Приложение будет работать, но Windows покажет предупреждение "Неизвестный издатель". Для личного/внутреннего использования это OK.
|
||||
|
||||
**Q: Как быстро получить доверие SmartScreen с обычным сертификатом?**
|
||||
A: Нужно минимум 100-1000 установок за 2-3 месяца. EV сертификат дает доверие сразу.
|
||||
|
||||
**Q: Можно ли использовать сертификат на Linux для подписи Windows файлов?**
|
||||
A: Да, через `osslsigncode`. Но EV сертификаты на USB токене работают только на Windows.
|
||||
|
||||
**Q: Что дешевле - EV или обычный Code Signing?**
|
||||
A: Обычный дешевле ($299 vs $799), но нужно ждать наработки репутации. EV дороже, но доверие сразу.
|
||||
|
||||
**Q: Сертификат для Umbrix - обязателен?**
|
||||
A: Для закрытого тестирования - НЕТ. Для публичного релиза - ОЧЕНЬ ЖЕЛАТЕЛЬНО.
|
||||
|
||||
## Итого
|
||||
|
||||
**Текущая конфигурация обновлений:**
|
||||
- ✅ Файл: `.exe` установщик (Inno Setup)
|
||||
- ✅ Приоритет поиска: x64 → amd64 → win64 → setup → installer → любой .exe
|
||||
- ✅ Установка: PowerShell с `/VERYSILENT /SUPPRESSMSGBOXES /NORESTART`
|
||||
- ✅ Автоперезагрузка: через 2 секунды
|
||||
- ⚠️ Сертификат: НЕТ (показывает предупреждение, но работает)
|
||||
|
||||
**Следующие шаги:**
|
||||
1. Собрать Windows версию на машине с Windows
|
||||
2. Создать Inno Setup установщик
|
||||
3. Загрузить в Gitea релиз
|
||||
4. Протестировать автообновление
|
||||
5. (Опционально) Купить сертификат для публичного релиза
|
||||
110
update-server/WINDOWS_BUILD_CHEATSHEET.md
Normal file
110
update-server/WINDOWS_BUILD_CHEATSHEET.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Windows Build Cheat Sheet
|
||||
|
||||
## 🚀 Быстрая сборка (на Windows машине)
|
||||
|
||||
```powershell
|
||||
# 1. Клонировать репозиторий
|
||||
git clone --recursive https://update.umbrix.net/vodorod/umbrix.git
|
||||
cd umbrix
|
||||
|
||||
# 2. Скачать и заменить libcore.dll
|
||||
curl -L -o libcore.tar.gz "https://update.umbrix.net/vodorod/umbrix-libcore/releases/download/v1.7.0-umbrix/libcore-windows-amd64-umbrix.tar.gz"
|
||||
tar -xzf libcore.tar.gz
|
||||
Copy-Item libcore.dll -Destination libcore\bin\libcore.dll -Force
|
||||
|
||||
# 3. Установить flutter_distributor (один раз)
|
||||
dart pub global activate flutter_distributor
|
||||
|
||||
# 4. СОБРАТЬ УСТАНОВЩИК
|
||||
dart run flutter_distributor:main package --platform windows --targets exe --skip-clean
|
||||
|
||||
# ✅ Результат: dist\1.7.3+173\umbrix-setup-x64.exe (~60MB)
|
||||
```
|
||||
|
||||
## 📦 Что получается?
|
||||
|
||||
**Один .exe файл** который содержит:
|
||||
- umbrix.exe (основное приложение)
|
||||
- Flutter runtime
|
||||
- libcore.dll (45MB VPN ядро)
|
||||
- WebUI
|
||||
- Все зависимости
|
||||
- Установщик Inno Setup
|
||||
|
||||
## 📤 Загрузка в Gitea
|
||||
|
||||
```powershell
|
||||
# Переименовать для релиза
|
||||
$version = "1.7.3"
|
||||
$file = "dist\$version+173\umbrix-setup-x64.exe"
|
||||
$newName = "umbrix-$version-windows-setup-x64.exe"
|
||||
Copy-Item $file $newName
|
||||
|
||||
# Загрузить
|
||||
$token = "YOUR_GITEA_TOKEN"
|
||||
$releaseId = 1
|
||||
|
||||
curl -X POST "https://update.umbrix.net/api/v1/repos/vodorod/umbrix/releases/$releaseId/assets?name=$newName" `
|
||||
-H "Authorization: token $token" `
|
||||
-H "Content-Type: application/octet-stream" `
|
||||
--data-binary "@$newName"
|
||||
```
|
||||
|
||||
## ✅ Тестирование автообновлений
|
||||
|
||||
```powershell
|
||||
# 1. Установить старую версию (например 1.7.0)
|
||||
.\umbrix-1.7.0-windows-setup-x64.exe
|
||||
|
||||
# 2. Запустить приложение
|
||||
# Через 5 секунд появится уведомление об обновлении
|
||||
|
||||
# 3. Нажать "Обновить"
|
||||
# Автоматически скачается, установится, перезапустится
|
||||
|
||||
# 4. Проверить версию
|
||||
# Должна быть 1.7.3
|
||||
```
|
||||
|
||||
## 🔧 Требования
|
||||
|
||||
- Windows 10/11
|
||||
- Flutter 3.24.0+
|
||||
- Visual Studio 2022 с C++
|
||||
- Inno Setup 6.x+ (https://jrsoftware.org/isdl.php)
|
||||
|
||||
## 📚 Документация
|
||||
|
||||
- **Полная инструкция:** [WINDOWS_DISTRIBUTOR_BUILD.md](WINDOWS_DISTRIBUTOR_BUILD.md)
|
||||
- **Подпись кода:** [CODE_SIGNING_WINDOWS.md](CODE_SIGNING_WINDOWS.md)
|
||||
- **Ручная сборка:** [WINDOWS_BUILD_INSTRUCTIONS.md](WINDOWS_BUILD_INSTRUCTIONS.md)
|
||||
|
||||
## ⚠️ Частые ошибки
|
||||
|
||||
**"Failed to lookup symbol 'changeHiddifyOptions'"**
|
||||
→ Используйте libcore.dll из umbrix-libcore, НЕ из оригинального Hiddify
|
||||
|
||||
**"Inno Setup not found"**
|
||||
→ Установите Inno Setup и добавьте в PATH
|
||||
|
||||
**"flutter_distributor command not found"**
|
||||
→ `dart pub global activate flutter_distributor`
|
||||
→ Добавьте `C:\Users\YOUR_NAME\AppData\Local\Pub\Cache\bin` в PATH
|
||||
|
||||
## 💡 Сертификат кода
|
||||
|
||||
**Не обязателен для работы!**
|
||||
- Без сертификата: Windows показывает предупреждение, но приложение работает
|
||||
- С сертификатом: Нет предупреждений, "Проверенный издатель"
|
||||
- Цена: $299-799/год (DigiCert, Sectigo, Certum)
|
||||
- Подробности: [CODE_SIGNING_WINDOWS.md](CODE_SIGNING_WINDOWS.md)
|
||||
|
||||
## 🎯 Итого
|
||||
|
||||
**Одна команда → Один .exe файл → Готово к релизу**
|
||||
|
||||
```powershell
|
||||
dart run flutter_distributor package --platform windows --targets exe
|
||||
```
|
||||
|
||||
Всё остальное делается автоматически! 🚀
|
||||
@@ -1,5 +1,28 @@
|
||||
# Windows Build & Update Instructions
|
||||
|
||||
## ⚡ РЕКОМЕНДУЕТСЯ: flutter_distributor (автоматическая упаковка)
|
||||
|
||||
**Используйте [WINDOWS_DISTRIBUTOR_BUILD.md](WINDOWS_DISTRIBUTOR_BUILD.md)** для автоматической сборки через flutter_distributor.
|
||||
|
||||
Преимущества:
|
||||
- ✅ **Один .exe файл** вместо папки с множеством файлов
|
||||
- ✅ Автоматическая упаковка через Inno Setup
|
||||
- ✅ Та же команда, что для Linux (deb/rpm/appimage)
|
||||
- ✅ Готовый конфиг уже настроен в `windows/packaging/exe/`
|
||||
|
||||
**Команда:**
|
||||
```powershell
|
||||
dart run flutter_distributor:main package --platform windows --targets exe --skip-clean
|
||||
```
|
||||
|
||||
**Результат:** `dist/1.7.3+173/umbrix-setup-x64.exe` (~60MB) - готов к загрузке!
|
||||
|
||||
---
|
||||
|
||||
## Альтернатива: Ручная сборка через Inno Setup
|
||||
|
||||
Используйте этот метод только если flutter_distributor не работает.
|
||||
|
||||
## Система автообновлений для Windows
|
||||
|
||||
### Требования для Windows EXE сборки
|
||||
|
||||
271
update-server/WINDOWS_DISTRIBUTOR_BUILD.md
Normal file
271
update-server/WINDOWS_DISTRIBUTOR_BUILD.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# Сборка Windows установщика через flutter_distributor
|
||||
|
||||
## ✅ РЕШЕНИЕ: Один .exe файл со всеми зависимостями
|
||||
|
||||
Hiddify использует **flutter_distributor** с **target: exe**, который:
|
||||
1. Собирает Flutter приложение
|
||||
2. Упаковывает ВСЕ файлы (exe + dll + data + libcore.dll)
|
||||
3. Создает **Inno Setup** установщик
|
||||
4. Результат: **ОДИН .exe файл** (~50-60MB)
|
||||
|
||||
## Требования
|
||||
|
||||
1. **Windows машина** (или VM)
|
||||
2. **Flutter SDK** 3.24.0+
|
||||
3. **Visual Studio 2022** с C++ компонентами
|
||||
4. **Inno Setup** 6.x+ (скачать: https://jrsoftware.org/isdl.php)
|
||||
|
||||
## Шаг 1: Клонирование репозитория на Windows
|
||||
|
||||
```powershell
|
||||
# Клонировать с Gitea
|
||||
git clone --recursive https://update.umbrix.net/vodorod/umbrix.git
|
||||
cd umbrix
|
||||
|
||||
# Или обновить submodules если уже клонировано
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
## Шаг 2: Замена libcore.dll
|
||||
|
||||
```powershell
|
||||
# Скачать libcore с Gitea
|
||||
curl -L -o libcore-windows.tar.gz "https://update.umbrix.net/vodorod/umbrix-libcore/releases/download/v1.7.0-umbrix/libcore-windows-amd64-umbrix.tar.gz"
|
||||
|
||||
# Распаковать
|
||||
tar -xzf libcore-windows.tar.gz
|
||||
|
||||
# Заменить в проекте
|
||||
Copy-Item libcore.dll -Destination libcore\bin\libcore.dll -Force
|
||||
Copy-Item HiddifyCli.exe -Destination libcore\bin\HiddifyCli.exe -Force
|
||||
Copy-Item -Path webui -Destination libcore\bin\ -Recurse -Force
|
||||
```
|
||||
|
||||
## Шаг 3: Установка flutter_distributor
|
||||
|
||||
```powershell
|
||||
# Активировать плагин
|
||||
dart pub global activate flutter_distributor
|
||||
|
||||
# Добавить в PATH (если еще нет)
|
||||
# C:\Users\YOUR_USERNAME\AppData\Local\Pub\Cache\bin
|
||||
```
|
||||
|
||||
## Шаг 4: Сборка установщика
|
||||
|
||||
```powershell
|
||||
# Собрать Windows .exe установщик
|
||||
dart run flutter_distributor:main package --platform windows --targets exe --skip-clean
|
||||
|
||||
# Процесс:
|
||||
# 1. flutter build windows --release (5-10 минут)
|
||||
# 2. Копирование файлов в temp директорию
|
||||
# 3. Запуск Inno Setup для создания установщика (1-2 минуты)
|
||||
# 4. Результат: dist\1.7.3+173\umbrix-setup-x64.exe (~50-60MB)
|
||||
```
|
||||
|
||||
## Что происходит внутри?
|
||||
|
||||
### 1. Flutter Build
|
||||
```
|
||||
build/windows/x64/runner/Release/
|
||||
├── umbrix.exe (основной исполняемый файл)
|
||||
├── data/ (ресурсы Flutter)
|
||||
│ ├── app.so
|
||||
│ ├── icudtl.dat
|
||||
│ └── flutter_assets/
|
||||
├── flutter_windows.dll (Flutter engine)
|
||||
├── libcore.dll (VPN ядро, 45MB)
|
||||
└── другие .dll
|
||||
```
|
||||
|
||||
### 2. Inno Setup упаковывает
|
||||
- Все файлы из `build/windows/x64/runner/Release/`
|
||||
- Создает установщик с GUI на русском/английском
|
||||
- Добавляет ярлыки (рабочий стол + меню Пуск)
|
||||
- Регистрирует в "Установка и удаление программ"
|
||||
- Поддерживает тихую установку `/VERYSILENT`
|
||||
|
||||
### 3. Результат
|
||||
```
|
||||
dist/1.7.3+173/umbrix-setup-x64.exe (~50-60MB)
|
||||
```
|
||||
|
||||
**Этот файл содержит ВСЁ:**
|
||||
- umbrix.exe
|
||||
- Flutter runtime
|
||||
- libcore.dll (45MB)
|
||||
- WebUI
|
||||
- Все зависимости
|
||||
|
||||
## Шаг 5: Тестирование
|
||||
|
||||
```powershell
|
||||
# Запустить установщик
|
||||
.\dist\1.7.3+173\umbrix-setup-x64.exe
|
||||
|
||||
# Или тихая установка (для тестирования автообновлений)
|
||||
.\umbrix-setup-x64.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART
|
||||
|
||||
# Проверить установку
|
||||
dir "C:\Program Files\Umbrix"
|
||||
```
|
||||
|
||||
## Шаг 6: Загрузка в Gitea
|
||||
|
||||
```powershell
|
||||
# Переименовать для релиза
|
||||
$version = "1.7.3"
|
||||
$file = "dist\$version+173\umbrix-setup-x64.exe"
|
||||
$newName = "umbrix-$version-windows-setup-x64.exe"
|
||||
Copy-Item $file $newName
|
||||
|
||||
# Загрузить через API
|
||||
$token = "YOUR_GITEA_TOKEN"
|
||||
$releaseId = 1 # ID релиза v1.7.3
|
||||
|
||||
curl -X POST "https://update.umbrix.net/api/v1/repos/vodorod/umbrix/releases/$releaseId/assets?name=$newName" `
|
||||
-H "Authorization: token $token" `
|
||||
-H "Content-Type: application/octet-stream" `
|
||||
--data-binary "@$newName"
|
||||
```
|
||||
|
||||
## Автоматизация (GitHub Actions)
|
||||
|
||||
Создайте `.github/workflows/build-windows.yml`:
|
||||
|
||||
```yaml
|
||||
name: Build Windows
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.24.0'
|
||||
|
||||
- name: Download libcore
|
||||
run: |
|
||||
curl -L -o libcore.tar.gz "https://update.umbrix.net/vodorod/umbrix-libcore/releases/download/v1.7.0-umbrix/libcore-windows-amd64-umbrix.tar.gz"
|
||||
tar -xzf libcore.tar.gz
|
||||
Copy-Item libcore.dll -Destination libcore\bin\libcore.dll -Force
|
||||
|
||||
- name: Install flutter_distributor
|
||||
run: dart pub global activate flutter_distributor
|
||||
|
||||
- name: Build Windows EXE
|
||||
run: dart run flutter_distributor:main package --platform windows --targets exe --skip-clean
|
||||
|
||||
- name: Upload to Gitea
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: |
|
||||
$file = Get-ChildItem dist\*\umbrix-setup-x64.exe | Select-Object -First 1
|
||||
curl -X POST "https://update.umbrix.net/api/v1/repos/vodorod/umbrix/releases/assets" `
|
||||
-H "Authorization: token $env:GITEA_TOKEN" `
|
||||
--data-binary "@$($file.FullName)"
|
||||
```
|
||||
|
||||
## Конфигурация (уже настроено)
|
||||
|
||||
**windows/packaging/exe/make_config.yaml:**
|
||||
```yaml
|
||||
app_id: 7U904649-52C2-5607-H590-CK880G32B76E
|
||||
publisher: Umbrix Team
|
||||
publisher_url: https://umbrix.net
|
||||
display_name: Umbrix
|
||||
executable_name: umbrix.exe
|
||||
output_base_file_name: umbrix-setup-x64.exe
|
||||
create_desktop_icon: true
|
||||
install_dir_name: "{autopf64}\\Umbrix"
|
||||
setup_icon_file: ..\..\windows\runner\resources\app_icon.ico
|
||||
locales:
|
||||
- en
|
||||
- ru
|
||||
```
|
||||
|
||||
**windows/packaging/exe/inno_setup.sas:**
|
||||
- Шаблон Inno Setup с поддержкой тихой установки
|
||||
- Автоматически закрывает старую версию перед установкой
|
||||
- Создает ярлыки на рабочем столе и в меню Пуск
|
||||
|
||||
## Альтернатива: Ручная сборка через Inno Setup
|
||||
|
||||
Если flutter_distributor не работает:
|
||||
|
||||
```powershell
|
||||
# 1. Собрать Flutter
|
||||
flutter build windows --release
|
||||
|
||||
# 2. Создать installer.iss (см. WINDOWS_BUILD_INSTRUCTIONS.md)
|
||||
|
||||
# 3. Скомпилировать через Inno Setup GUI
|
||||
# Или через командную строку:
|
||||
"C:\Program Files (x86)\Inno Setup 6\ISCC.exe" installer.iss
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Ошибка: "Inno Setup not found"**
|
||||
```powershell
|
||||
# Установить Inno Setup
|
||||
# Добавить в PATH: C:\Program Files (x86)\Inno Setup 6
|
||||
```
|
||||
|
||||
**Ошибка: "flutter_distributor command not found"**
|
||||
```powershell
|
||||
# Переустановить
|
||||
dart pub global activate flutter_distributor
|
||||
|
||||
# Проверить PATH
|
||||
echo $env:PATH | Select-String "Pub\\Cache\\bin"
|
||||
```
|
||||
|
||||
**Ошибка: "Failed to lookup symbol 'changeHiddifyOptions'"**
|
||||
```powershell
|
||||
# Убедитесь, что используете libcore.dll из umbrix-libcore репозитория
|
||||
# НЕ используйте оригинальный Hiddify libcore!
|
||||
```
|
||||
|
||||
**Большой размер файла (~60MB)**
|
||||
- Это нормально! Включает:
|
||||
- Flutter runtime (~10MB)
|
||||
- libcore.dll (45MB)
|
||||
- WebUI (~600KB)
|
||||
- Инсталлятор Inno Setup (~5MB)
|
||||
|
||||
## Сравнение с Linux сборкой
|
||||
|
||||
| Платформа | Команда | Результат | Размер |
|
||||
|-----------|---------|-----------|--------|
|
||||
| **Linux** | `dart run flutter_distributor package --platform linux --targets deb` | umbrix-1.7.3-linux.deb | 29MB |
|
||||
| **Windows** | `dart run flutter_distributor package --platform windows --targets exe` | umbrix-setup-x64.exe | 60MB |
|
||||
|
||||
**Почему Windows больше?**
|
||||
- libcore.dll (45MB) vs libcore.so (45MB) - одинаково
|
||||
- Inno Setup упаковщик добавляет ~5MB
|
||||
- Windows DLL зависимости больше чем Linux .so
|
||||
|
||||
## Итого
|
||||
|
||||
✅ **flutter_distributor решает все проблемы:**
|
||||
- Один .exe файл вместо папки с множеством файлов
|
||||
- Автоматическая упаковка через Inno Setup
|
||||
- Поддержка тихой установки `/VERYSILENT`
|
||||
- Совместимость с системой автообновлений
|
||||
- Та же команда, что и для Linux (deb/rpm/appimage)
|
||||
|
||||
🎯 **На Windows машине выполнить одну команду:**
|
||||
```powershell
|
||||
dart run flutter_distributor:main package --platform windows --targets exe --skip-clean
|
||||
```
|
||||
|
||||
**Результат:** `dist/1.7.3+173/umbrix-setup-x64.exe` - готов к загрузке в Gitea!
|
||||
87
update-server/WINDOWS_PORTABLE_QUICK_START.md
Normal file
87
update-server/WINDOWS_PORTABLE_QUICK_START.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# 🚀 Быстрая сборка Windows Portable ZIP
|
||||
|
||||
## Проблема решена! ✅
|
||||
|
||||
Теперь в ZIP будут файлы которые помогут пользователям не запутаться:
|
||||
|
||||
```
|
||||
umbrix-portable/
|
||||
├── 📄 README.txt ⭐ ПЕРВЫЙ в списке - ясная инструкция!
|
||||
├── 🚀 Запустить Umbrix.bat ⭐ Удобный запуск одним кликом
|
||||
├── 📱 Umbrix.exe ⭐ Основное приложение
|
||||
├── ⚙️ UmbrixCli.exe ⚠️ Служебная утилита
|
||||
└── ...DLL и папки...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Команда для сборки на Windows
|
||||
|
||||
```powershell
|
||||
# 1. Собрать Release
|
||||
flutter build windows --release
|
||||
|
||||
# 2. Скопировать файлы для пользователей + создать ZIP
|
||||
$buildPath = "build\windows\x64\runner\Release"
|
||||
Copy-Item "windows\packaging\portable\README.txt" -Destination "$buildPath\" -Force
|
||||
Copy-Item "windows\packaging\portable\Запустить Umbrix.bat" -Destination "$buildPath\" -Force
|
||||
|
||||
$zipName = "umbrix-1.7.5-portable-windows-x64.zip"
|
||||
Compress-Archive -Path "$buildPath\*" -DestinationPath $zipName -Force
|
||||
|
||||
# 3. Проверить
|
||||
Get-Item $zipName | Select-Object Name, @{N="MB";E={[math]::Round($_.Length/1MB,2)}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Что увидит пользователь после распаковки
|
||||
|
||||
1. **README.txt** - первый файл (по алфавиту с символом '═')
|
||||
- Большой заголовок с инструкцией
|
||||
- Указывает запускать **Umbrix.exe**
|
||||
- Предупреждает не запускать UmbrixCli.exe
|
||||
|
||||
2. **Запустить Umbrix.bat** - второй файл
|
||||
- Русское название понятное всем
|
||||
- Запускает Umbrix.exe автоматически
|
||||
|
||||
3. **Umbrix.exe** - основное приложение
|
||||
- Яркая цветная иконка
|
||||
- Выделяется визуально
|
||||
|
||||
4. **UmbrixCli.exe** - служебная утилита
|
||||
- Название "Cli" намекает что это командная строка
|
||||
- Большинство не будет трогать
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Альтернативы (если не понравится)
|
||||
|
||||
### Вариант 1: Скрыть CLI в подпапку
|
||||
```powershell
|
||||
# При создании ZIP переместить CLI в tools/
|
||||
New-Item -Path "$buildPath\tools" -ItemType Directory -Force
|
||||
Move-Item "$buildPath\UmbrixCli.exe" "$buildPath\tools\" -Force
|
||||
Compress-Archive -Path "$buildPath\*" -DestinationPath $zipName -Force
|
||||
```
|
||||
|
||||
### Вариант 2: Переименовать основное приложение
|
||||
Изменить в `windows/packaging/exe/make_config.yaml`:
|
||||
```yaml
|
||||
executable_name: Start-Umbrix.exe # Без пробела!
|
||||
```
|
||||
⚠️ Но это сломает update скрипты которые ищут `umbrix.exe`
|
||||
|
||||
### Вариант 3: Только батник
|
||||
Удалить README.txt, оставить только `Запустить Umbrix.bat` как единственный понятный способ запуска.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Полная документация
|
||||
|
||||
См. [WINDOWS_PORTABLE_ZIP.md](./WINDOWS_PORTABLE_ZIP.md) для:
|
||||
- Создания релиза в Gitea
|
||||
- Загрузки ZIP через API
|
||||
- Тестирования auto-update
|
||||
- Сравнения ZIP vs EXE
|
||||
284
update-server/WINDOWS_PORTABLE_ZIP.md
Normal file
284
update-server/WINDOWS_PORTABLE_ZIP.md
Normal file
@@ -0,0 +1,284 @@
|
||||
# Creating Portable ZIP Update for Windows
|
||||
|
||||
## ✅ Преимущества Portable ZIP
|
||||
|
||||
- **Без UAC** - не нужны права администратора
|
||||
- **Без SmartScreen** - Windows не показывает предупреждения для ZIP файлов
|
||||
- **Быстрое обновление** - просто заменить файлы
|
||||
- **Идеально для тестирования** без подписи кода
|
||||
- **Fallback на .exe** если .zip не найден
|
||||
|
||||
---
|
||||
|
||||
## 📦 Создание Portable ZIP (на Windows)
|
||||
|
||||
### Шаг 1: Собрать Release версию
|
||||
|
||||
```powershell
|
||||
cd "C:\Umbrix\project.exe.umbrix 1.7.3\umbrix"
|
||||
|
||||
# Собрать Flutter app
|
||||
flutter build windows --release
|
||||
|
||||
# Результат: build\windows\x64\runner\Release\
|
||||
```
|
||||
|
||||
### Шаг 2: Упаковать в ZIP
|
||||
|
||||
```powershell
|
||||
# Путь к build папке
|
||||
$buildPath = "build\windows\x64\runner\Release"
|
||||
|
||||
# ✨ НОВОЕ: Добавить файлы для пользователей
|
||||
Copy-Item "windows\packaging\portable\README.txt" -Destination "$buildPath\" -Force
|
||||
Copy-Item "windows\packaging\portable\Запустить Umbrix.bat" -Destination "$buildPath\" -Force
|
||||
|
||||
# Создать ZIP архив
|
||||
$zipName = "umbrix-1.7.5-portable-windows-x64.zip"
|
||||
Compress-Archive -Path "$buildPath\*" -DestinationPath $zipName -Force
|
||||
|
||||
# Проверить размер (~50-60MB)
|
||||
Get-Item $zipName | Select-Object Name, Length
|
||||
|
||||
Write-Host "`n✅ ZIP содержит:" -ForegroundColor Green
|
||||
Write-Host " • README.txt (инструкция - ПЕРВЫЙ файл в списке)" -ForegroundColor Cyan
|
||||
Write-Host " • Запустить Umbrix.bat (удобный запуск)" -ForegroundColor Cyan
|
||||
Write-Host " • Umbrix.exe (основное приложение)" -ForegroundColor Yellow
|
||||
Write-Host " • UmbrixCli.exe (служебная утилита)" -ForegroundColor Gray
|
||||
Write-Host " • Все DLL и папки" -ForegroundColor Gray
|
||||
```
|
||||
|
||||
### Шаг 3: Создать релиз в Gitea
|
||||
|
||||
```powershell
|
||||
# Создать релиз v1.7.5
|
||||
$token = "bfe5806ebda0adb22815154c81f25057f02a3dde"
|
||||
$body = @{
|
||||
tag_name = "v1.7.5"
|
||||
name = "Umbrix v1.7.5 (Portable Test)"
|
||||
body = @"
|
||||
**Test release with portable ZIP update**
|
||||
|
||||
✅ Windows Portable ZIP (no UAC, no SmartScreen!)
|
||||
✅ Auto-update test: 1.7.4 → 1.7.5
|
||||
✅ EXE installer also available
|
||||
|
||||
**Download:**
|
||||
- **umbrix-1.7.5-portable-windows-x64.zip** - Recommended for testing (no warnings!)
|
||||
- umbrix-1.7.5-windows-setup-x64.exe - Full installer (requires UAC)
|
||||
"@
|
||||
draft = $false
|
||||
prerelease = $false
|
||||
} | ConvertTo-Json
|
||||
|
||||
$response = Invoke-RestMethod -Uri "https://update.umbrix.net/api/v1/repos/vodorod/umbrix/releases" `
|
||||
-Method Post `
|
||||
-Headers @{"Authorization"="token $token"; "Content-Type"="application/json"} `
|
||||
-Body $body
|
||||
|
||||
$releaseId = $response.id
|
||||
Write-Host "Release created with ID: $releaseId"
|
||||
```
|
||||
|
||||
### Шаг 4: Загрузить ZIP
|
||||
|
||||
```powershell
|
||||
$releaseId = 5 # From previous step
|
||||
$zipFile = "umbrix-1.7.5-portable-windows-x64.zip"
|
||||
$token = "bfe5806ebda0adb22815154c81f25057f02a3dde"
|
||||
|
||||
# Upload
|
||||
curl -X POST "https://update.umbrix.net/api/v1/repos/vodorod/umbrix/releases/$releaseId/assets?name=$zipFile" `
|
||||
-H "Authorization: token $token" `
|
||||
-H "Content-Type: application/zip" `
|
||||
--data-binary "@$zipFile"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Тестирование Auto-Update
|
||||
|
||||
### 1. Установить старую версию (1.7.4)
|
||||
|
||||
```powershell
|
||||
# Распаковать 1.7.4 в папку
|
||||
$oldVersion = "C:\Umbrix\Test\umbrix-1.7.4"
|
||||
Expand-Archive -Path "umbrix-1.7.4-portable-windows-x64.zip" -DestinationPath $oldVersion
|
||||
|
||||
# Запустить
|
||||
cd $oldVersion
|
||||
.\umbrix.exe
|
||||
```
|
||||
|
||||
### 2. Проверить автообновление
|
||||
|
||||
**Что происходит:**
|
||||
|
||||
1. Приложение запускается (версия 1.7.4)
|
||||
2. Через 5 секунд: уведомление "Доступна новая версия 1.7.5"
|
||||
3. Нажать кнопку **"Обновить"**
|
||||
4. Начинается загрузка `umbrix-1.7.5-portable-windows-x64.zip`
|
||||
5. ✅ **БЕЗ UAC запроса!**
|
||||
6. ✅ **БЕЗ SmartScreen предупреждений!**
|
||||
7. Показывается toast: "Установка обновления из ZIP..."
|
||||
8. ZIP распаковывается в `%TEMP%\umbrix_update_xxxxx`
|
||||
9. Создается batch скрипт для замены файлов
|
||||
10. Приложение закрывается
|
||||
11. Скрипт ждет 3 секунды, заменяет файлы, запускает приложение
|
||||
12. ✅ Готово! Версия 1.7.5 запущена
|
||||
|
||||
### 3. Проверить версию
|
||||
|
||||
```
|
||||
Настройки → О программе → Версия: 1.7.5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Логика выбора обновления
|
||||
|
||||
**Приоритет (в коде):**
|
||||
|
||||
1. **Ищем .zip файл** с patterns: portable, windows, win
|
||||
2. Если .zip найден → **используем ZIP** (без UAC!)
|
||||
3. Если .zip НЕ найден → **fallback на .exe** (с UAC)
|
||||
|
||||
**Пример файлов в релизе:**
|
||||
|
||||
```
|
||||
✅ umbrix-1.7.5-portable-windows-x64.zip ← Будет использован (приоритет!)
|
||||
umbrix-1.7.5-windows-setup-x64.exe ← Запасной вариант
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📂 Структура ZIP архива
|
||||
|
||||
```
|
||||
umbrix-1.7.5-portable-windows-x64.zip
|
||||
├── umbrix.exe (главный EXE)
|
||||
├── data/ (Flutter ресурсы)
|
||||
│ ├── app.so
|
||||
│ ├── icudtl.dat
|
||||
│ └── flutter_assets/
|
||||
├── flutter_windows.dll (Flutter engine)
|
||||
├── libcore.dll (VPN core, 45MB)
|
||||
└── другие .dll файлы
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Быстрая команда (все в одном)
|
||||
|
||||
```powershell
|
||||
cd "C:\Umbrix\project.exe.umbrix 1.7.3\umbrix"
|
||||
|
||||
# Собрать
|
||||
flutter build windows --release
|
||||
|
||||
# Упаковать
|
||||
$version = "1.7.5"
|
||||
$zipName = "umbrix-$version-portable-windows-x64.zip"
|
||||
Compress-Archive -Path "build\windows\x64\runner\Release\*" -DestinationPath $zipName -Force
|
||||
|
||||
# Проверить
|
||||
Get-Item $zipName | Select-Object Name, @{N="Size (MB)";E={[math]::Round($_.Length/1MB, 2)}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## <20> Структура Portable ZIP (что видят пользователи)
|
||||
|
||||
Когда пользователь распакует ZIP, он увидит:
|
||||
|
||||
```
|
||||
umbrix-portable/
|
||||
├── 📄 README.txt ⭐ ПЕРВЫЙ в списке - инструкция!
|
||||
├── 🚀 Запустить Umbrix.bat ⭐ Удобный способ запуска
|
||||
├── 📱 Umbrix.exe ⭐ Основное приложение (267 KB)
|
||||
├── ⚙️ UmbrixCli.exe ⚠️ Служебная утилита (1.8 MB)
|
||||
├── 1.7.4/ 📂 Папка с данными
|
||||
├── data/ 📂 Ресурсы Flutter
|
||||
├── dynamic_color_plugin.dll 🔧 DLL плагины
|
||||
├── flutter_windows.dll 🔧 Flutter (17 MB)
|
||||
├── libcore.dll 🔧 Либкор (45 MB)
|
||||
└── ...другие DLL...
|
||||
```
|
||||
|
||||
**Почему пользователи не запутаются:**
|
||||
1. ✅ **README.txt** - первый файл (по алфавиту), ясная инструкция
|
||||
2. ✅ **Запустить Umbrix.bat** - второй файл, понятное название
|
||||
3. ✅ **Umbrix.exe** с красивой иконкой - визуально выделяется
|
||||
4. ⚠️ **UmbrixCli.exe** - название намекает что это утилита командной строки
|
||||
|
||||
---
|
||||
|
||||
## <20>💡 Советы
|
||||
|
||||
**Для разработки:**
|
||||
- ✅ Используйте Portable ZIP - быстрее тестировать
|
||||
- ✅ Нет задержек от UAC и SmartScreen
|
||||
- ✅ Можно создавать релизы каждые 5 минут
|
||||
|
||||
**Для production:**
|
||||
- Portable ZIP для пользователей без админ прав
|
||||
- EXE installer для "правильной" установки в Program Files
|
||||
- **Рекомендуется оба варианта в релизе!**
|
||||
|
||||
**Размеры:**
|
||||
- Portable ZIP: ~50MB (сжатый)
|
||||
- EXE installer: ~60MB (Inno Setup добавляет ~10MB)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Преимущества двух вариантов
|
||||
|
||||
| Аспект | Portable ZIP | EXE Installer |
|
||||
|--------|--------------|---------------|
|
||||
| **UAC запрос** | ❌ Нет | ✅ Требуется |
|
||||
| **SmartScreen** | ❌ Не показывается | ⚠️ Показывается (без подписи) |
|
||||
| **Права админа** | ❌ Не нужны | ✅ Нужны |
|
||||
| **Скорость обновления** | ✅ Очень быстро | ⏱️ Медленнее |
|
||||
| **Установка в Program Files** | ❌ Нет | ✅ Да |
|
||||
| **Ярлыки в меню Пуск** | ❌ Нет | ✅ Да |
|
||||
| **Удаление через "Программы и компоненты"** | ❌ Нет | ✅ Да |
|
||||
| **Портативность** | ✅ Да (можно на флешке) | ❌ Нет |
|
||||
|
||||
---
|
||||
|
||||
## 📝 Рекомендации
|
||||
|
||||
**Для тестирования (БЕЗ подписи кода):**
|
||||
```
|
||||
✅ Используйте Portable ZIP
|
||||
✅ Быстрое тестирование
|
||||
✅ Нет предупреждений Windows
|
||||
```
|
||||
|
||||
**Для production (С подписью кода):**
|
||||
```
|
||||
✅ Оба варианта в релизе
|
||||
✅ EXE для установки
|
||||
✅ ZIP для обновлений
|
||||
```
|
||||
|
||||
**Пример релиза:**
|
||||
```
|
||||
v1.7.5 - Umbrix Release
|
||||
├── umbrix-1.7.5-portable-windows-x64.zip (для обновлений)
|
||||
└── umbrix-1.7.5-windows-setup-x64.exe (для новых установок)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Итого
|
||||
|
||||
**Добавлена поддержка Portable ZIP:**
|
||||
- Код проверяет .zip → fallback на .exe
|
||||
- ZIP распаковывается и заменяет файлы
|
||||
- Без UAC, без SmartScreen
|
||||
- Идеально для тестирования!
|
||||
|
||||
**Теперь 2 способа обновления на Windows:**
|
||||
1. Portable ZIP (рекомендуется для тестирования)
|
||||
2. EXE Installer (для production)
|
||||
@@ -68,8 +68,8 @@ function InitializeSetup(): Boolean;
|
||||
var
|
||||
ResultCode: Integer;
|
||||
begin
|
||||
Exec('taskkill', '/F /IM hiddify.exe', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
|
||||
Exec('net', 'stop "HiddifyTunnelService"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
|
||||
Exec('sc.exe', 'delete "HiddifyTunnelService"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
|
||||
Exec('taskkill', '/F /IM umbrix.exe', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
|
||||
Exec('net', 'stop "UmbrixTunnelService"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
|
||||
Exec('sc.exe', 'delete "UmbrixTunnelService"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
|
||||
Result := True;
|
||||
end;
|
||||
@@ -1,17 +1,13 @@
|
||||
app_id: 6L903538-42B1-4596-G479-BJ779F21A65D
|
||||
publisher: Hiddify
|
||||
publisher_url: https://github.com/hiddify/hiddify-next
|
||||
display_name: Hiddify
|
||||
executable_name: Hiddify.exe
|
||||
output_base_file_name: Hiddify.exe
|
||||
app_id: 7U904649-52C2-5607-H590-CK880G32B76E
|
||||
publisher: Umbrix Team
|
||||
publisher_url: https://umbrix.net
|
||||
display_name: Umbrix
|
||||
executable_name: umbrix.exe
|
||||
output_base_file_name: umbrix-setup-x64.exe
|
||||
create_desktop_icon: true
|
||||
install_dir_name: "{autopf64}\\Hiddify"
|
||||
install_dir_name: "{autopf64}\\Umbrix"
|
||||
setup_icon_file: ..\..\windows\runner\resources\app_icon.ico
|
||||
locales:
|
||||
- ar
|
||||
- en
|
||||
- fa
|
||||
- ru
|
||||
- pt
|
||||
- tr
|
||||
script_template: inno_setup.sas
|
||||
|
||||
19
windows/packaging/portable/README.txt
Normal file
19
windows/packaging/portable/README.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
═══════════════════════════════════════════════════════════
|
||||
UMBRIX - Portable Version
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
🚀 ДЛЯ ЗАПУСКА ПРИЛОЖЕНИЯ:
|
||||
► Откройте файл: Umbrix.exe
|
||||
|
||||
📦 Portable версия:
|
||||
• Не требует установки
|
||||
• Не требует прав администратора
|
||||
• Все настройки сохраняются в папке приложения
|
||||
|
||||
⚠️ НЕ ЗАПУСКАЙТЕ:
|
||||
• UmbrixCli.exe - это служебная утилита командной строки
|
||||
• Файлы .dll - это системные библиотеки
|
||||
|
||||
═══════════════════════════════════════════════════════════
|
||||
https://umbrix.net
|
||||
═══════════════════════════════════════════════════════════
|
||||
8
windows/packaging/portable/Запустить Umbrix.bat
Normal file
8
windows/packaging/portable/Запустить Umbrix.bat
Normal file
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
chcp 65001 >nul 2>&1
|
||||
echo.
|
||||
echo ═══════════════════════════════════════════════
|
||||
echo Запуск Umbrix...
|
||||
echo ═══════════════════════════════════════════════
|
||||
echo.
|
||||
start "" "%~dp0Umbrix.exe"
|
||||
Reference in New Issue
Block a user