Compare commits

..

2 Commits

Author SHA1 Message Date
vladimir.kuznetsov 1102554012 chore: removed clang-format step from deploy workflow 2024-12-08 13:18:12 +07:00
vladimir.kuznetsov 2df6a2fc3a chore: added clang-format step in deploy workflow 2024-12-08 13:13:17 +07:00
28 changed files with 209 additions and 910 deletions
+5 -10
View File
@@ -335,8 +335,7 @@ jobs:
arch: 'linux_gcc_64' arch: 'linux_gcc_64'
modules: ${{ env.QT_MODULES }} modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
py7zrversion: '==0.22.*' extra: '--external 7z --base ${{ env.QT_MIRROR }}'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_x86_64 Qt' - name: 'Install android_x86_64 Qt'
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
@@ -347,8 +346,7 @@ jobs:
arch: 'android_x86_64' arch: 'android_x86_64'
modules: ${{ env.QT_MODULES }} modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
py7zrversion: '==0.22.*' extra: '--external 7z --base ${{ env.QT_MIRROR }}'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_x86 Qt' - name: 'Install android_x86 Qt'
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
@@ -359,8 +357,7 @@ jobs:
arch: 'android_x86' arch: 'android_x86'
modules: ${{ env.QT_MODULES }} modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
py7zrversion: '==0.22.*' extra: '--external 7z --base ${{ env.QT_MIRROR }}'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_armv7 Qt' - name: 'Install android_armv7 Qt'
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
@@ -371,8 +368,7 @@ jobs:
arch: 'android_armv7' arch: 'android_armv7'
modules: ${{ env.QT_MODULES }} modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
py7zrversion: '==0.22.*' extra: '--external 7z --base ${{ env.QT_MIRROR }}'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_arm64_v8a Qt' - name: 'Install android_arm64_v8a Qt'
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
@@ -383,8 +379,7 @@ jobs:
arch: 'android_arm64_v8a' arch: 'android_arm64_v8a'
modules: ${{ env.QT_MODULES }} modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
py7zrversion: '==0.22.*' extra: '--external 7z --base ${{ env.QT_MIRROR }}'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Grant execute permission for qt-cmake' - name: 'Grant execute permission for qt-cmake'
shell: bash shell: bash
+2 -7
View File
@@ -1,14 +1,9 @@
# Amnezia VPN # Amnezia VPN
## _The best client for self-hosted VPN_
### _The best client for self-hosted VPN_
[![Build Status](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml/badge.svg?branch=dev)](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev) [![Build Status](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml/badge.svg?branch=dev)](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client) [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server. [Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org) [![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
@@ -55,7 +50,7 @@ AmneziaVPN uses several open-source projects to work:
- [OpenVPN](https://openvpn.net/) - [OpenVPN](https://openvpn.net/)
- [Shadowsocks](https://shadowsocks.org/) - [Shadowsocks](https://shadowsocks.org/)
- [Qt](https://www.qt.io/) - [Qt](https://www.qt.io/)
- [LibSsh](https://libssh.org) - forked from Qt Creator - [LibSsh](https://libssh.org)
- and more... - and more...
## Checking out the source code ## Checking out the source code
+3 -114
View File
@@ -1,11 +1,6 @@
# Amnezia VPN # Amnezia VPN
## _Лучший клиент для создания VPN на собственном сервере_
### _Лучший клиент для создания VPN на собственном сервере_
[![Build Status](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml/badge.svg?branch=dev)](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
[AmneziaVPN](https://amnezia.org) — это open sourse VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере. [AmneziaVPN](https://amnezia.org) — это open sourse VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org) [![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
@@ -15,8 +10,8 @@
> [!TIP] > [!TIP]
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/kldscp/amnezia.org). > Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/kldscp/amnezia.org).
<a href="https://storage.googleapis.com/kldscp/amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a> <a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/kldscp/amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases) [Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
@@ -55,112 +50,6 @@ AmneziaVPN использует несколько проектов с откр
- [LibSsh](https://libssh.org) - [LibSsh](https://libssh.org)
- и другие... - и другие...
## Проверка исходного кода
После клонирования репозитория обязательно загрузите все подмодули.
```bash
git submodule update --init --recursive
```
## Разработка
Хотите внести свой вклад? Добро пожаловать!
### Помощь с переводами
Загрузите самые актуальные файлы перевода.
Перейдите на [вкладку "Actions"](https://github.com/amnezia-vpn/amnezia-client/actions?query=is%3Asuccess+branch%3Adev), нажмите на первую строку. Затем прокрутите вниз до раздела "Artifacts" и скачайте "AmneziaVPN_translations".
Распакуйте этот файл. Каждый файл с расширением *.ts содержит строки для соответствующего языка.
Переведите или исправьте строки в одном или нескольких файлах *.ts и загрузите их обратно в этот репозиторий в папку ``client/translations``. Это можно сделать через веб-интерфейс или любым другим знакомым вам способом.
### Сборка исходного кода и деплой
Проверьте папку deploy для скриптов сборки.
### Как собрать iOS-приложение из исходного кода на MacOS
1. Убедитесь, что у вас установлен XCode версии 14 или выше.
2. Для генерации проекта XCode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
- MacOS
- iOS
- Модуль совместимости с Qt 5
- Qt Shader Tools
- Дополнительные библиотеки:
- Qt Image Formats
- Qt Multimedia
- Qt Remote Objects
3. Установите CMake, если это необходимо. Рекомендуемая версия — 3.25. Скачать CMake можно здесь.
4. Установите Go версии >= v1.16. Если Go ещё не установлен, скачайте его с [официального сайта](https://golang.org/dl/) или используйте Homebrew. Установите gomobile:
```bash
export PATH=$PATH:~/go/bin
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
```
5. Соберите проект:
```bash
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
export QT_IOS_BIN=$QT_BIN_DIR
export PATH=$PATH:~/go/bin
mkdir build-ios
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
```
Замените <PATH-TO-QT-FOLDER> и <QT-VERSION> на ваши значения.
Если появляется ошибка gomobile: command not found, убедитесь, что PATH настроен на папку bin, где установлен gomobile:
```bash
export PATH=$(PATH):/path/to/GOPATH/bin
```
6. Откройте проект в XCode. Теперь вы можете тестировать, архивировать или публиковать приложение.
Если сборка завершится с ошибкой:
```
make: ***
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
Error 1
```
Добавьте пользовательскую переменную PATH в настройки сборки для целей AmneziaVPN и WireGuardNetworkExtension с ключом `PATH` и значением `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
Если ошибка повторяется на Mac с M1, установите версию CMake для архитектуры ARM:
```
arch -arm64 brew install cmake
```
При первой попытке сборка может завершиться с ошибкой source files not found. Это происходит из-за параллельной компиляции зависимостей в XCode. Просто перезапустите сборку.
## Как собрать Android-приложение
Сборка тестировалась на MacOS. Требования:
- JDK 11
- Android SDK 33
- CMake 3.25.0
Установите QT, QT Creator и Android Studio.
Настройте QT Creator:
- В меню QT Creator перейдите в `QT Creator` -> `Preferences` -> `Devices` ->`Android`.
- Укажите путь к JDK 11.
- Укажите путь к Android SDK (`$ANDROID_HOME`)
Если вы сталкиваетесь с ошибками, связанными с отсутствием SDK или сообщением «SDK manager not running», их нельзя исправить просто корректировкой путей. Если у вас есть несколько свободных гигабайт на диске, вы можете позволить Qt Creator установить все необходимые компоненты, выбрав пустую папку для расположения Android SDK и нажав кнопку **Set Up SDK**. Учтите: это установит второй Android SDK и NDK на вашем компьютере!
Убедитесь, что настроена правильная версия CMake: перейдите в **Qt Creator -> Preferences** и в боковом меню выберите пункт **Kits**. В центральной части окна, на вкладке **Kits**, найдите запись для инструмента **CMake Tool**. Если выбранная по умолчанию версия CMake ниже 3.25.0, установите на свою систему CMake версии 3.25.0 или выше, а затем выберите опцию **System CMake at <путь>** из выпадающего списка. Если этот пункт отсутствует, это может означать, что вы еще не установили CMake, или Qt Creator не смог найти путь к нему. В таком случае в окне **Preferences** перейдите в боковое меню **CMake**, затем во вкладку **Tools** в центральной части окна и нажмите кнопку **Add**, чтобы указать путь к установленному CMake.
Убедитесь, что для вашего проекта выбрана Android Platform SDK 33: в главном окне на боковой панели выберите пункт **Projects**, и слева вы увидите раздел **Build & Run**, показывающий различные целевые Android-платформы. Вы можете выбрать любую из них, так как настройка проекта Amnezia VPN разработана таким образом, чтобы все Android-цели могли быть собраны. Перейдите в подраздел **Build** и прокрутите центральную часть окна до раздела **Build Steps**. Нажмите **Details** в заголовке **Build Android APK** (кнопка **Details** может быть скрыта, если окно Qt Creator не запущено в полноэкранном режиме!). Вот здесь выберите **android-33** в качестве Android Build Platform SDK.
### Разработка Android-компонентов
После сборки QT Creator копирует проект в отдельную папку, например, `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`. Для разработки Android-компонентов откройте сгенерированный проект в Android Studio, указав папку `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` в качестве корневой.
Изменения в сгенерированном проекте нужно вручную перенести в репозиторий. После этого можно коммитить изменения.
Если возникают проблемы со сборкой в QT Creator после работы в Android Studio, выполните команду `./gradlew clean` в корневой папке сгенерированного проекта (`<path>/client/android-build/.`).
## Лицензия ## Лицензия
GPL v3.0 GPL v3.0
+7 -138
View File
@@ -3,169 +3,38 @@
#include <QFile> #include <QFile>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QUuid>
#include "logger.h"
#include "containers/containers_defs.h" #include "containers/containers_defs.h"
#include "core/controllers/serverController.h" #include "core/controllers/serverController.h"
#include "core/scripts_registry.h" #include "core/scripts_registry.h"
namespace {
Logger logger("XrayConfigurator");
}
XrayConfigurator::XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent) XrayConfigurator::XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: ConfiguratorBase(settings, serverController, parent) : ConfiguratorBase(settings, serverController, parent)
{ {
} }
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container, QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
const QJsonObject &containerConfig, ErrorCode &errorCode) ErrorCode &errorCode)
{ {
// Generate new UUID for client
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
// Get current server config
QString currentConfig = m_serverController->getTextFileFromContainer(
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to get server config file";
return "";
}
// Parse current config as JSON
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
if (doc.isNull() || !doc.isObject()) {
logger.error() << "Failed to parse server config JSON";
errorCode = ErrorCode::InternalError;
return "";
}
QJsonObject serverConfig = doc.object();
// Validate server config structure
if (!serverConfig.contains("inbounds")) {
logger.error() << "Server config missing 'inbounds' field";
errorCode = ErrorCode::InternalError;
return "";
}
QJsonArray inbounds = serverConfig["inbounds"].toArray();
if (inbounds.isEmpty()) {
logger.error() << "Server config has empty 'inbounds' array";
errorCode = ErrorCode::InternalError;
return "";
}
QJsonObject inbound = inbounds[0].toObject();
if (!inbound.contains("settings")) {
logger.error() << "Inbound missing 'settings' field";
errorCode = ErrorCode::InternalError;
return "";
}
QJsonObject settings = inbound["settings"].toObject();
if (!settings.contains("clients")) {
logger.error() << "Settings missing 'clients' field";
errorCode = ErrorCode::InternalError;
return "";
}
QJsonArray clients = settings["clients"].toArray();
// Create configuration for new client
QJsonObject clientConfig {
{"id", clientId},
{"flow", "xtls-rprx-vision"}
};
clients.append(clientConfig);
// Update config
settings["clients"] = clients;
inbound["settings"] = settings;
inbounds[0] = inbound;
serverConfig["inbounds"] = inbounds;
// Save updated config to server
QString updatedConfig = QJsonDocument(serverConfig).toJson();
errorCode = m_serverController->uploadTextFileToContainer(
container,
credentials,
updatedConfig,
amnezia::protocols::xray::serverConfigPath,
libssh::ScpOverwriteMode::ScpOverwriteExisting
);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to upload updated config";
return "";
}
// Restart container
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
errorCode = m_serverController->runScript(
credentials,
m_serverController->replaceVars(restartScript, m_serverController->genVarsForScript(credentials, container))
);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to restart container";
return "";
}
return clientId;
}
QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
{
// Get client ID from prepareServerConfig
QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, errorCode);
if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) {
logger.error() << "Failed to prepare server config";
errorCode = ErrorCode::InternalError;
return "";
}
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
m_serverController->genVarsForScript(credentials, container, containerConfig)); m_serverController->genVarsForScript(credentials, container, containerConfig));
if (config.isEmpty()) {
logger.error() << "Failed to get config template";
errorCode = ErrorCode::InternalError;
return "";
}
QString xrayPublicKey = QString xrayPublicKey =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode); m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
logger.error() << "Failed to get public key";
errorCode = ErrorCode::InternalError;
return "";
}
xrayPublicKey.replace("\n", ""); xrayPublicKey.replace("\n", "");
QString xrayUuid = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, errorCode);
xrayUuid.replace("\n", "");
QString xrayShortId = QString xrayShortId =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode); m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
logger.error() << "Failed to get short ID";
errorCode = ErrorCode::InternalError;
return "";
}
xrayShortId.replace("\n", ""); xrayShortId.replace("\n", "");
// Validate all required variables are present if (errorCode != ErrorCode::NoError) {
if (!config.contains("$XRAY_CLIENT_ID") || !config.contains("$XRAY_PUBLIC_KEY") || !config.contains("$XRAY_SHORT_ID")) {
logger.error() << "Config template missing required variables:"
<< "XRAY_CLIENT_ID:" << !config.contains("$XRAY_CLIENT_ID")
<< "XRAY_PUBLIC_KEY:" << !config.contains("$XRAY_PUBLIC_KEY")
<< "XRAY_SHORT_ID:" << !config.contains("$XRAY_SHORT_ID");
errorCode = ErrorCode::InternalError;
return ""; return "";
} }
config.replace("$XRAY_CLIENT_ID", xrayClientId); config.replace("$XRAY_CLIENT_ID", xrayUuid);
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey); config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
config.replace("$XRAY_SHORT_ID", xrayShortId); config.replace("$XRAY_SHORT_ID", xrayShortId);
-4
View File
@@ -14,10 +14,6 @@ public:
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode); ErrorCode &errorCode);
private:
QString prepareServerConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode);
}; };
#endif // XRAY_CONFIGURATOR_H #endif // XRAY_CONFIGURATOR_H
@@ -379,13 +379,6 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody)
auto errorCode = checkErrors(sslErrors, reply); auto errorCode = checkErrors(sslErrors, reply);
reply->deleteLater(); reply->deleteLater();
if (errorCode == ErrorCode::NoError) {
if (!responseBody.contains("services")) {
return ErrorCode::ApiServicesMissingError;
}
}
return errorCode; return errorCode;
} }
-1
View File
@@ -109,7 +109,6 @@ namespace amnezia
ApiConfigSslError = 1104, ApiConfigSslError = 1104,
ApiMissingAgwPublicKey = 1105, ApiMissingAgwPublicKey = 1105,
ApiConfigDecryptionError = 1106, ApiConfigDecryptionError = 1106,
ApiServicesMissingError = 1107,
// QFile errors // QFile errors
OpenError = 1200, OpenError = 1200,
-1
View File
@@ -63,7 +63,6 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break; case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break;
case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break; case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break;
case (ErrorCode::ApiConfigDecryptionError): errorMessage = QObject::tr("Failed to decrypt response payload"); break; case (ErrorCode::ApiConfigDecryptionError): errorMessage = QObject::tr("Failed to decrypt response payload"); break;
case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); break;
// QFile errors // QFile errors
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break; case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
+1 -1
View File
@@ -104,7 +104,7 @@ QJsonObject Deserialize(const QString &vmessStr, QString *alias, QString *errMes
server.users.first().security = "auto"; server.users.first().security = "auto";
} }
const auto getQueryValue = [&query](const QString &key, const QString &defaultValue) { const static auto getQueryValue = [&query](const QString &key, const QString &defaultValue) {
if (query.hasQueryItem(key)) if (query.hasQueryItem(key))
return query.queryItemValue(key, QUrl::FullyDecoded); return query.queryItemValue(key, QUrl::FullyDecoded);
else else
@@ -196,8 +196,6 @@ QStringList LinuxFirewall::getDNSRules(const QStringList& servers)
result << QStringLiteral("-o amn0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server); result << QStringLiteral("-o amn0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server); result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server); result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun2+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun2+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
} }
return result; return result;
} }
@@ -279,7 +277,6 @@ void LinuxFirewall::install()
installAnchor(Both, QStringLiteral("200.allowVPN"), { installAnchor(Both, QStringLiteral("200.allowVPN"), {
QStringLiteral("-o amn0+ -j ACCEPT"), QStringLiteral("-o amn0+ -j ACCEPT"),
QStringLiteral("-o tun0+ -j ACCEPT"), QStringLiteral("-o tun0+ -j ACCEPT"),
QStringLiteral("-o tun2+ -j ACCEPT"),
}); });
installAnchor(IPv4, QStringLiteral("120.blockNets"), {}); installAnchor(IPv4, QStringLiteral("120.blockNets"), {});
+1 -1
View File
@@ -2679,7 +2679,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="154"/> <location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="154"/>
<source>Where to get connection data, step-by-step instructions for buying a VPS</source> <source>Where to get connection data, step-by-step instructions for buying a VPS</source>
<translation>Где взять данные для подключения, пошаговые инструкции по покупке VPS</translation> <translation>Где взять данные для подключения, пошаговые инстуркции по покупке VPS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="170"/> <location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="170"/>
@@ -55,7 +55,7 @@ void ConnectionController::openConnection()
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { && !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
emit updateApiConfigFromGateway(); emit updateApiConfigFromGateway();
} else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) { } else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) {
qDebug() << "attempt to update api config by expires_at event"; qDebug() << "attempt to update api config by end_date event";
if (configVersion == ApiConfigSources::Telegram) { if (configVersion == ApiConfigSources::Telegram) {
emit updateApiConfigFromTelegram(); emit updateApiConfigFromTelegram();
} else { } else {
+5 -4
View File
@@ -121,8 +121,9 @@ ErrorCode ExportController::generateNativeConfig(const DockerContainer container
jsonNativeConfig = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); jsonNativeConfig = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg || protocol == Proto::Xray) { if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg) {
errorCode = m_clientManagementModel->appendClient(jsonNativeConfig, clientName, container, credentials, serverController); auto clientId = jsonNativeConfig.value(config_key::clientId).toString();
errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials, serverController);
} }
return errorCode; return errorCode;
} }
@@ -247,10 +248,10 @@ void ExportController::generateCloakConfig()
emit exportConfigChanged(); emit exportConfigChanged();
} }
void ExportController::generateXrayConfig(const QString &clientName) void ExportController::generateXrayConfig()
{ {
QJsonObject nativeConfig; QJsonObject nativeConfig;
ErrorCode errorCode = generateNativeConfig(DockerContainer::Xray, clientName, Proto::Xray, nativeConfig); ErrorCode errorCode = generateNativeConfig(DockerContainer::Xray, "", Proto::Xray, nativeConfig);
if (errorCode) { if (errorCode) {
emit exportErrorOccurred(errorCode); emit exportErrorOccurred(errorCode);
return; return;
+1 -1
View File
@@ -28,7 +28,7 @@ public slots:
void generateAwgConfig(const QString &clientName); void generateAwgConfig(const QString &clientName);
void generateShadowSocksConfig(); void generateShadowSocksConfig();
void generateCloakConfig(); void generateCloakConfig();
void generateXrayConfig(const QString &clientName); void generateXrayConfig();
QString getConfig(); QString getConfig();
QString getNativeConfigString(); QString getNativeConfigString();
+34 -78
View File
@@ -27,9 +27,6 @@ namespace
constexpr char storeEndpoint[] = "store_endpoint"; constexpr char storeEndpoint[] = "store_endpoint";
constexpr char isAvailable[] = "is_available"; constexpr char isAvailable[] = "is_available";
constexpr char subscription[] = "subscription";
constexpr char endDate[] = "end_date";
} }
namespace serviceType namespace serviceType
@@ -54,23 +51,23 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount())) if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount()))
return QVariant(); return QVariant();
auto apiServiceData = m_services.at(index.row()); QJsonObject service = m_services.at(index.row()).toObject();
auto serviceType = apiServiceData.type; QJsonObject serviceInfo = service.value(configKey::serviceInfo).toObject();
auto isServiceAvailable = apiServiceData.isServiceAvailable; auto serviceType = service.value(configKey::serviceType).toString();
switch (role) { switch (role) {
case NameRole: { case NameRole: {
return apiServiceData.serviceInfo.name; return serviceInfo.value(configKey::name).toString();
} }
case CardDescriptionRole: { case CardDescriptionRole: {
auto speed = apiServiceData.serviceInfo.speed; auto speed = serviceInfo.value(configKey::speed).toString();
if (serviceType == serviceType::amneziaPremium) { if (serviceType == serviceType::amneziaPremium) {
return tr("Classic VPN for comfortable work, downloading large files and watching videos. " return tr("Classic VPN for comfortable work, downloading large files and watching videos. "
"Works for any sites. Speed up to %1 MBit/s") "Works for any sites. Speed up to %1 MBit/s")
.arg(speed); .arg(speed);
} else if (serviceType == serviceType::amneziaFree){ } else if (serviceType == serviceType::amneziaFree){
QString description = tr("VPN to access blocked sites in regions with high levels of Internet censorship. "); QString description = tr("VPN to access blocked sites in regions with high levels of Internet censorship. ");
if (isServiceAvailable) { if (service.value(configKey::isAvailable).isBool() && !service.value(configKey::isAvailable).toBool()) {
description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.</a>"); description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.</a>");
} }
return description; return description;
@@ -86,24 +83,25 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
} }
case IsServiceAvailableRole: { case IsServiceAvailableRole: {
if (serviceType == serviceType::amneziaFree) { if (serviceType == serviceType::amneziaFree) {
if (isServiceAvailable) { if (service.value(configKey::isAvailable).isBool() && !service.value(configKey::isAvailable).toBool()) {
return false; return false;
} }
} }
return true; return true;
} }
case SpeedRole: { case SpeedRole: {
return tr("%1 MBit/s").arg(apiServiceData.serviceInfo.speed); auto speed = serviceInfo.value(configKey::speed).toString();
return tr("%1 MBit/s").arg(speed);
} }
case TimeLimitRole: { case WorkPeriodRole: {
auto timeLimit = apiServiceData.serviceInfo.timeLimit; auto timelimit = serviceInfo.value(configKey::timelimit).toString();
if (timeLimit == "0") { if (timelimit == "0") {
return ""; return "";
} }
return tr("%1 days").arg(timeLimit); return tr("%1 days").arg(timelimit);
} }
case RegionRole: { case RegionRole: {
return apiServiceData.serviceInfo.region; return serviceInfo.value(configKey::region).toString();
} }
case FeaturesRole: { case FeaturesRole: {
if (serviceType == serviceType::amneziaPremium) { if (serviceType == serviceType::amneziaPremium) {
@@ -115,15 +113,12 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
} }
} }
case PriceRole: { case PriceRole: {
auto price = apiServiceData.serviceInfo.price; auto price = serviceInfo.value(configKey::price).toString();
if (price == "free") { if (price == "free") {
return tr("Free"); return tr("Free");
} }
return tr("%1 $/month").arg(price); return tr("%1 $/month").arg(price);
} }
case EndDateRole: {
return QDateTime::fromString(apiServiceData.subscription.endDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy");
}
} }
return QVariant(); return QVariant();
@@ -133,18 +128,15 @@ void ApiServicesModel::updateModel(const QJsonObject &data)
{ {
beginResetModel(); beginResetModel();
m_services.clear();
m_countryCode = data.value(configKey::userCountryCode).toString(); m_countryCode = data.value(configKey::userCountryCode).toString();
auto services = data.value(configKey::services).toArray(); m_services = data.value(configKey::services).toArray();
if (m_services.isEmpty()) {
QJsonObject service;
service.insert(configKey::serviceInfo, data.value(configKey::serviceInfo));
service.insert(configKey::serviceType, data.value(configKey::serviceType));
if (services.isEmpty()) { m_services.push_back(service);
m_services.push_back(getApiServicesData(data));
m_selectedServiceIndex = 0; m_selectedServiceIndex = 0;
} else {
for (const auto &service : services) {
m_services.push_back(getApiServicesData(service.toObject()));
}
} }
endResetModel(); endResetModel();
@@ -157,32 +149,32 @@ void ApiServicesModel::setServiceIndex(const int index)
QJsonObject ApiServicesModel::getSelectedServiceInfo() QJsonObject ApiServicesModel::getSelectedServiceInfo()
{ {
auto service = m_services.at(m_selectedServiceIndex); QJsonObject service = m_services.at(m_selectedServiceIndex).toObject();
return service.serviceInfo.object; return service.value(configKey::serviceInfo).toObject();
} }
QString ApiServicesModel::getSelectedServiceType() QString ApiServicesModel::getSelectedServiceType()
{ {
auto service = m_services.at(m_selectedServiceIndex); QJsonObject service = m_services.at(m_selectedServiceIndex).toObject();
return service.type; return service.value(configKey::serviceType).toString();
} }
QString ApiServicesModel::getSelectedServiceProtocol() QString ApiServicesModel::getSelectedServiceProtocol()
{ {
auto service = m_services.at(m_selectedServiceIndex); QJsonObject service = m_services.at(m_selectedServiceIndex).toObject();
return service.protocol; return service.value(configKey::serviceProtocol).toString();
} }
QString ApiServicesModel::getSelectedServiceName() QString ApiServicesModel::getSelectedServiceName()
{ {
auto service = m_services.at(m_selectedServiceIndex); auto modelIndex = index(m_selectedServiceIndex, 0);
return service.serviceInfo.name; return data(modelIndex, ApiServicesModel::Roles::NameRole).toString();
} }
QJsonArray ApiServicesModel::getSelectedServiceCountries() QJsonArray ApiServicesModel::getSelectedServiceCountries()
{ {
auto service = m_services.at(m_selectedServiceIndex); QJsonObject service = m_services.at(m_selectedServiceIndex).toObject();
return service.availableCountries; return service.value(configKey::availableCountries).toArray();
} }
QString ApiServicesModel::getCountryCode() QString ApiServicesModel::getCountryCode()
@@ -192,8 +184,8 @@ QString ApiServicesModel::getCountryCode()
QString ApiServicesModel::getStoreEndpoint() QString ApiServicesModel::getStoreEndpoint()
{ {
auto service = m_services.at(m_selectedServiceIndex); QJsonObject service = m_services.at(m_selectedServiceIndex).toObject();
return service.storeEndpoint; return service.value(configKey::storeEndpoint).toString();
} }
QVariant ApiServicesModel::getSelectedServiceData(const QString roleString) QVariant ApiServicesModel::getSelectedServiceData(const QString roleString)
@@ -217,46 +209,10 @@ QHash<int, QByteArray> ApiServicesModel::roleNames() const
roles[ServiceDescriptionRole] = "serviceDescription"; roles[ServiceDescriptionRole] = "serviceDescription";
roles[IsServiceAvailableRole] = "isServiceAvailable"; roles[IsServiceAvailableRole] = "isServiceAvailable";
roles[SpeedRole] = "speed"; roles[SpeedRole] = "speed";
roles[TimeLimitRole] = "timeLimit"; roles[WorkPeriodRole] = "workPeriod";
roles[RegionRole] = "region"; roles[RegionRole] = "region";
roles[FeaturesRole] = "features"; roles[FeaturesRole] = "features";
roles[PriceRole] = "price"; roles[PriceRole] = "price";
roles[EndDateRole] = "endDate";
return roles; return roles;
} }
ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJsonObject &data)
{
auto serviceInfo = data.value(configKey::serviceInfo).toObject();
auto serviceType = data.value(configKey::serviceType).toString();
auto serviceProtocol = data.value(configKey::serviceProtocol).toString();
auto availableCountries = data.value(configKey::availableCountries).toArray();
auto subscriptionObject = data.value(configKey::subscription).toObject();
ApiServicesData serviceData;
serviceData.serviceInfo.name = serviceInfo.value(configKey::name).toString();
serviceData.serviceInfo.price = serviceInfo.value(configKey::price).toString();
serviceData.serviceInfo.region = serviceInfo.value(configKey::region).toString();
serviceData.serviceInfo.speed = serviceInfo.value(configKey::speed).toString();
serviceData.serviceInfo.timeLimit = serviceInfo.value(configKey::timelimit).toString();
serviceData.type = serviceType;
serviceData.protocol = serviceProtocol;
serviceData.storeEndpoint = serviceInfo.value(configKey::storeEndpoint).toString();
if (serviceInfo.value(configKey::isAvailable).isBool()) {
serviceData.isServiceAvailable = data.value(configKey::isAvailable).toBool();
} else {
serviceData.isServiceAvailable = true;
}
serviceData.serviceInfo.object = serviceInfo;
serviceData.availableCountries = availableCountries;
serviceData.subscription.endDate = subscriptionObject.value(configKey::endDate).toString();
return serviceData;
}
+3 -37
View File
@@ -3,7 +3,6 @@
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject>
class ApiServicesModel : public QAbstractListModel class ApiServicesModel : public QAbstractListModel
{ {
@@ -16,11 +15,10 @@ public:
ServiceDescriptionRole, ServiceDescriptionRole,
IsServiceAvailableRole, IsServiceAvailableRole,
SpeedRole, SpeedRole,
TimeLimitRole, WorkPeriodRole,
RegionRole, RegionRole,
FeaturesRole, FeaturesRole,
PriceRole, PriceRole
EndDateRole
}; };
explicit ApiServicesModel(QObject *parent = nullptr); explicit ApiServicesModel(QObject *parent = nullptr);
@@ -50,40 +48,8 @@ protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
private: private:
struct ServiceInfo
{
QString name;
QString speed;
QString timeLimit;
QString region;
QString price;
QJsonObject object;
};
struct Subscription
{
QString endDate;
};
struct ApiServicesData
{
bool isServiceAvailable;
QString type;
QString protocol;
QString storeEndpoint;
ServiceInfo serviceInfo;
Subscription subscription;
QJsonArray availableCountries;
};
ApiServicesData getApiServicesData(const QJsonObject &data);
QString m_countryCode; QString m_countryCode;
QVector<ApiServicesData> m_services; QJsonArray m_services;
int m_selectedServiceIndex; int m_selectedServiceIndex;
}; };
+13 -322
View File
@@ -106,8 +106,6 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co
error = getOpenVpnClients(container, credentials, serverController, count); error = getOpenVpnClients(container, credentials, serverController, count);
} else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) { } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
error = getWireGuardClients(container, credentials, serverController, count); error = getWireGuardClients(container, credentials, serverController, count);
} else if (container == DockerContainer::Xray) {
error = getXrayClients(container, credentials, serverController, count);
} }
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
endResetModel(); endResetModel();
@@ -241,68 +239,6 @@ ErrorCode ClientManagementModel::getWireGuardClients(const DockerContainer conta
} }
return error; return error;
} }
ErrorCode ClientManagementModel::getXrayClients(const DockerContainer container, const ServerCredentials& credentials,
const QSharedPointer<ServerController> &serverController, int &count)
{
ErrorCode error = ErrorCode::NoError;
const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath;
const QString configString = serverController->getTextFileFromContainer(container, credentials, serverConfigPath, error);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to get the xray server config file from the server";
return error;
}
QJsonDocument serverConfig = QJsonDocument::fromJson(configString.toUtf8());
if (serverConfig.isNull()) {
logger.error() << "Failed to parse xray server config JSON";
return ErrorCode::InternalError;
}
if (!serverConfig.object().contains("inbounds") || serverConfig.object()["inbounds"].toArray().isEmpty()) {
logger.error() << "Invalid xray server config structure";
return ErrorCode::InternalError;
}
const QJsonObject inbound = serverConfig.object()["inbounds"].toArray()[0].toObject();
if (!inbound.contains("settings")) {
logger.error() << "Missing settings in xray inbound config";
return ErrorCode::InternalError;
}
const QJsonObject settings = inbound["settings"].toObject();
if (!settings.contains("clients")) {
logger.error() << "Missing clients in xray settings config";
return ErrorCode::InternalError;
}
const QJsonArray clients = settings["clients"].toArray();
for (const auto &clientValue : clients) {
const QJsonObject clientObj = clientValue.toObject();
if (!clientObj.contains("id")) {
logger.error() << "Missing id in xray client config";
continue;
}
QString clientId = clientObj["id"].toString();
QString xrayDefaultUuid = serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, error);
xrayDefaultUuid.replace("\n", "");
if (!isClientExists(clientId) && clientId != xrayDefaultUuid) {
QJsonObject client;
client[configKey::clientId] = clientId;
QJsonObject userData;
userData[configKey::clientName] = QString("Client %1").arg(count);
client[configKey::userData] = userData;
m_clientsTable.push_back(client);
count++;
}
}
return error;
}
ErrorCode ClientManagementModel::wgShow(const DockerContainer container, const ServerCredentials &credentials, ErrorCode ClientManagementModel::wgShow(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, std::vector<WgShowData> &data) const QSharedPointer<ServerController> &serverController, std::vector<WgShowData> &data)
@@ -390,67 +326,17 @@ ErrorCode ClientManagementModel::appendClient(const DockerContainer container, c
const QSharedPointer<ServerController> &serverController) const QSharedPointer<ServerController> &serverController)
{ {
Proto protocol; Proto protocol;
switch (container) { if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
case DockerContainer::ShadowSocks:
case DockerContainer::Cloak:
protocol = Proto::OpenVpn; protocol = Proto::OpenVpn;
break; } else if (container == DockerContainer::OpenVpn || container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
case DockerContainer::OpenVpn:
case DockerContainer::WireGuard:
case DockerContainer::Awg:
case DockerContainer::Xray:
protocol = ContainerProps::defaultProtocol(container); protocol = ContainerProps::defaultProtocol(container);
break; } else {
default:
return ErrorCode::NoError; return ErrorCode::NoError;
} }
auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig); auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig);
return appendClient(protocolConfig, clientName, container, credentials, serverController);
}
ErrorCode ClientManagementModel::appendClient(QJsonObject &protocolConfig, const QString &clientName, const DockerContainer container, return appendClient(protocolConfig.value(config_key::clientId).toString(), clientName, container, credentials, serverController);
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController)
{
QString clientId;
if (container == DockerContainer::Xray) {
if (!protocolConfig.contains("outbounds")) {
return ErrorCode::InternalError;
}
QJsonArray outbounds = protocolConfig.value("outbounds").toArray();
if (outbounds.isEmpty()) {
return ErrorCode::InternalError;
}
QJsonObject outbound = outbounds[0].toObject();
if (!outbound.contains("settings")) {
return ErrorCode::InternalError;
}
QJsonObject settings = outbound["settings"].toObject();
if (!settings.contains("vnext")) {
return ErrorCode::InternalError;
}
QJsonArray vnext = settings["vnext"].toArray();
if (vnext.isEmpty()) {
return ErrorCode::InternalError;
}
QJsonObject vnextObj = vnext[0].toObject();
if (!vnextObj.contains("users")) {
return ErrorCode::InternalError;
}
QJsonArray users = vnextObj["users"].toArray();
if (users.isEmpty()) {
return ErrorCode::InternalError;
}
QJsonObject user = users[0].toObject();
if (!user.contains("id")) {
return ErrorCode::InternalError;
}
clientId = user["id"].toString();
} else {
clientId = protocolConfig.value(config_key::clientId).toString();
}
return appendClient(clientId, clientName, container, credentials, serverController);
} }
ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QString &clientName, const DockerContainer container, ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QString &clientName, const DockerContainer container,
@@ -536,27 +422,10 @@ ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContain
auto client = m_clientsTable.at(row).toObject(); auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString(); QString clientId = client.value(configKey::clientId).toString();
switch(container) if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
{
case DockerContainer::OpenVpn:
case DockerContainer::ShadowSocks:
case DockerContainer::Cloak: {
errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController); errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController);
break; } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
}
case DockerContainer::WireGuard:
case DockerContainer::Awg: {
errorCode = revokeWireGuard(row, container, credentials, serverController); errorCode = revokeWireGuard(row, container, credentials, serverController);
break;
}
case DockerContainer::Xray: {
errorCode = revokeXray(row, container, credentials, serverController);
break;
}
default: {
logger.error() << "Internal error: received unexpected container type";
return ErrorCode::InternalError;
}
} }
if (errorCode == ErrorCode::NoError) { if (errorCode == ErrorCode::NoError) {
@@ -594,69 +463,19 @@ ErrorCode ClientManagementModel::revokeClient(const QJsonObject &containerConfig
} }
Proto protocol; Proto protocol;
if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
switch(container)
{
case DockerContainer::ShadowSocks:
case DockerContainer::Cloak: {
protocol = Proto::OpenVpn; protocol = Proto::OpenVpn;
break; } else if (container == DockerContainer::OpenVpn || container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
}
case DockerContainer::OpenVpn:
case DockerContainer::WireGuard:
case DockerContainer::Awg:
case DockerContainer::Xray: {
protocol = ContainerProps::defaultProtocol(container); protocol = ContainerProps::defaultProtocol(container);
break; } else {
} return ErrorCode::NoError;
default: {
logger.error() << "Internal error: received unexpected container type";
return ErrorCode::InternalError;
}
} }
auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig); auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig);
QString clientId;
if (container == DockerContainer::Xray) {
if (!protocolConfig.contains("outbounds")) {
return ErrorCode::InternalError;
}
QJsonArray outbounds = protocolConfig.value("outbounds").toArray();
if (outbounds.isEmpty()) {
return ErrorCode::InternalError;
}
QJsonObject outbound = outbounds[0].toObject();
if (!outbound.contains("settings")) {
return ErrorCode::InternalError;
}
QJsonObject settings = outbound["settings"].toObject();
if (!settings.contains("vnext")) {
return ErrorCode::InternalError;
}
QJsonArray vnext = settings["vnext"].toArray();
if (vnext.isEmpty()) {
return ErrorCode::InternalError;
}
QJsonObject vnextObj = vnext[0].toObject();
if (!vnextObj.contains("users")) {
return ErrorCode::InternalError;
}
QJsonArray users = vnextObj["users"].toArray();
if (users.isEmpty()) {
return ErrorCode::InternalError;
}
QJsonObject user = users[0].toObject();
if (!user.contains("id")) {
return ErrorCode::InternalError;
}
clientId = user["id"].toString();
} else {
clientId = protocolConfig.value(config_key::clientId).toString();
}
int row; int row;
bool clientExists = false; bool clientExists = false;
QString clientId = protocolConfig.value(config_key::clientId).toString();
for (row = 0; row < rowCount(); row++) { for (row = 0; row < rowCount(); row++) {
auto client = m_clientsTable.at(row).toObject(); auto client = m_clientsTable.at(row).toObject();
if (clientId == client.value(configKey::clientId).toString()) { if (clientId == client.value(configKey::clientId).toString()) {
@@ -668,28 +487,11 @@ ErrorCode ClientManagementModel::revokeClient(const QJsonObject &containerConfig
return errorCode; return errorCode;
} }
switch (container) if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
{
case DockerContainer::OpenVpn:
case DockerContainer::ShadowSocks:
case DockerContainer::Cloak: {
errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController); errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController);
break; } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
}
case DockerContainer::WireGuard:
case DockerContainer::Awg: {
errorCode = revokeWireGuard(row, container, credentials, serverController); errorCode = revokeWireGuard(row, container, credentials, serverController);
break;
} }
case DockerContainer::Xray: {
errorCode = revokeXray(row, container, credentials, serverController);
break;
}
default:
logger.error() << "Internal error: received unexpected container type";
return ErrorCode::InternalError;
}
return errorCode; return errorCode;
} }
@@ -792,117 +594,6 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont
return ErrorCode::NoError; return ErrorCode::NoError;
} }
ErrorCode ClientManagementModel::revokeXray(const int row,
const DockerContainer container,
const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController)
{
ErrorCode error = ErrorCode::NoError;
// Get server config
const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath;
const QString configString = serverController->getTextFileFromContainer(container, credentials, serverConfigPath, error);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to get the xray server config file";
return error;
}
QJsonDocument serverConfig = QJsonDocument::fromJson(configString.toUtf8());
if (serverConfig.isNull()) {
logger.error() << "Failed to parse xray server config JSON";
return ErrorCode::InternalError;
}
// Get client ID to remove
auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString();
// Remove client from server config
QJsonObject configObj = serverConfig.object();
if (!configObj.contains("inbounds")) {
logger.error() << "Missing inbounds in xray config";
return ErrorCode::InternalError;
}
QJsonArray inbounds = configObj["inbounds"].toArray();
if (inbounds.isEmpty()) {
logger.error() << "Empty inbounds array in xray config";
return ErrorCode::InternalError;
}
QJsonObject inbound = inbounds[0].toObject();
if (!inbound.contains("settings")) {
logger.error() << "Missing settings in xray inbound config";
return ErrorCode::InternalError;
}
QJsonObject settings = inbound["settings"].toObject();
if (!settings.contains("clients")) {
logger.error() << "Missing clients in xray settings";
return ErrorCode::InternalError;
}
QJsonArray clients = settings["clients"].toArray();
if (clients.isEmpty()) {
logger.error() << "Empty clients array in xray config";
return ErrorCode::InternalError;
}
for (int i = 0; i < clients.size(); ++i) {
QJsonObject clientObj = clients[i].toObject();
if (clientObj.contains("id") && clientObj["id"].toString() == clientId) {
clients.removeAt(i);
break;
}
}
// Update server config
settings["clients"] = clients;
inbound["settings"] = settings;
inbounds[0] = inbound;
configObj["inbounds"] = inbounds;
// Upload updated config
error = serverController->uploadTextFileToContainer(
container,
credentials,
QJsonDocument(configObj).toJson(),
serverConfigPath
);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload updated xray config";
return error;
}
// Remove from local table
beginRemoveRows(QModelIndex(), row, row);
m_clientsTable.removeAt(row);
endRemoveRows();
// Update clients table file on server
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable")
.arg(ContainerProps::containerTypeToString(container));
error = serverController->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file";
}
// Restart container
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
error = serverController->runScript(
credentials,
serverController->replaceVars(restartScript, serverController->genVarsForScript(credentials, container))
);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to restart xray container";
return error;
}
return error;
}
QHash<int, QByteArray> ClientManagementModel::roleNames() const QHash<int, QByteArray> ClientManagementModel::roleNames() const
{ {
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
-6
View File
@@ -40,8 +40,6 @@ public slots:
const QSharedPointer<ServerController> &serverController); const QSharedPointer<ServerController> &serverController);
ErrorCode appendClient(const DockerContainer container, const ServerCredentials &credentials, const QJsonObject &containerConfig, ErrorCode appendClient(const DockerContainer container, const ServerCredentials &credentials, const QJsonObject &containerConfig,
const QString &clientName, const QSharedPointer<ServerController> &serverController); const QString &clientName, const QSharedPointer<ServerController> &serverController);
ErrorCode appendClient(QJsonObject &protocolConfig, const QString &clientName,const DockerContainer container,
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController);
ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container, ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container,
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController); const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController);
ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container, const ServerCredentials &credentials, ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container, const ServerCredentials &credentials,
@@ -66,15 +64,11 @@ private:
const QSharedPointer<ServerController> &serverController); const QSharedPointer<ServerController> &serverController);
ErrorCode revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials, ErrorCode revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController); const QSharedPointer<ServerController> &serverController);
ErrorCode revokeXray(const int row, const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController);
ErrorCode getOpenVpnClients(const DockerContainer container, const ServerCredentials &credentials, ErrorCode getOpenVpnClients(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, int &count); const QSharedPointer<ServerController> &serverController, int &count);
ErrorCode getWireGuardClients(const DockerContainer container, const ServerCredentials &credentials, ErrorCode getWireGuardClients(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, int &count); const QSharedPointer<ServerController> &serverController, int &count);
ErrorCode getXrayClients(const DockerContainer container, const ServerCredentials& credentials,
const QSharedPointer<ServerController> &serverController, int &count);
ErrorCode wgShow(const DockerContainer container, const ServerCredentials &credentials, ErrorCode wgShow(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, std::vector<WgShowData> &data); const QSharedPointer<ServerController> &serverController, std::vector<WgShowData> &data);
+6 -27
View File
@@ -22,7 +22,7 @@ namespace
constexpr char serviceProtocol[] = "service_protocol"; constexpr char serviceProtocol[] = "service_protocol";
constexpr char publicKeyInfo[] = "public_key"; constexpr char publicKeyInfo[] = "public_key";
constexpr char expiresAt[] = "expires_at"; constexpr char endDate[] = "end_date";
} }
} }
@@ -39,9 +39,6 @@ ServersModel::ServersModel(std::shared_ptr<Settings> settings, QObject *parent)
emit ServersModel::defaultServerNameChanged(); emit ServersModel::defaultServerNameChanged();
updateDefaultServerContainersModel(); updateDefaultServerContainersModel();
}); });
connect(this, &ServersModel::processedServerIndexChanged, this, &ServersModel::processedServerChanged);
connect(this, &ServersModel::dataChanged, this, &ServersModel::processedServerChanged);
} }
int ServersModel::rowCount(const QModelIndex &parent) const int ServersModel::rowCount(const QModelIndex &parent) const
@@ -82,12 +79,6 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int
return true; return true;
} }
bool ServersModel::setData(const int index, const QVariant &value, int role)
{
QModelIndex modelIndex = this->index(index);
return setData(modelIndex, value, role);
}
QVariant ServersModel::data(const QModelIndex &index, int role) const QVariant ServersModel::data(const QModelIndex &index, int role) const
{ {
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(m_servers.size())) { if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(m_servers.size())) {
@@ -688,18 +679,6 @@ QVariant ServersModel::getProcessedServerData(const QString roleString)
return {}; return {};
} }
bool ServersModel::setProcessedServerData(const QString &roleString, const QVariant &value)
{
const auto roles = roleNames();
for (auto it = roles.begin(); it != roles.end(); it++) {
if (QString(it.value()) == roleString) {
return setData(m_processedServerIndex, value, it.key());
}
}
return false;
}
bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling() bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling()
{ {
auto server = m_servers.at(m_defaultServerIndex).toObject(); auto server = m_servers.at(m_defaultServerIndex).toObject();
@@ -739,9 +718,9 @@ bool ServersModel::isApiKeyExpired(const int serverIndex)
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
auto publicKeyInfo = apiConfig.value(configKey::publicKeyInfo).toObject(); auto publicKeyInfo = apiConfig.value(configKey::publicKeyInfo).toObject();
const QString expiresAt = publicKeyInfo.value(configKey::expiresAt).toString(); const QString endDate = publicKeyInfo.value(configKey::endDate).toString();
if (expiresAt.isEmpty()) { if (endDate.isEmpty()) {
publicKeyInfo.insert(configKey::expiresAt, QDateTime::currentDateTimeUtc().addDays(1).toString(Qt::ISODate)); publicKeyInfo.insert(configKey::endDate, QDateTime::currentDateTimeUtc().addDays(1).toString(Qt::ISODate));
apiConfig.insert(configKey::publicKeyInfo, publicKeyInfo); apiConfig.insert(configKey::publicKeyInfo, publicKeyInfo);
serverConfig.insert(configKey::apiConfig, apiConfig); serverConfig.insert(configKey::apiConfig, apiConfig);
editServer(serverConfig, serverIndex); editServer(serverConfig, serverIndex);
@@ -749,8 +728,8 @@ bool ServersModel::isApiKeyExpired(const int serverIndex)
return false; return false;
} }
auto expiresAtDateTime = QDateTime::fromString(expiresAt, Qt::ISODate).toUTC(); auto endDateDateTime = QDateTime::fromString(endDate, Qt::ISODate).toUTC();
if (expiresAtDateTime < QDateTime::currentDateTimeUtc()) { if (endDateDateTime < QDateTime::currentDateTimeUtc()) {
return true; return true;
} }
return false; return false;
-5
View File
@@ -46,7 +46,6 @@ public:
int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
bool setData(const int index, const QVariant &value, int role = Qt::EditRole);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant data(const int index, int role = Qt::DisplayRole) const; QVariant data(const int index, int role = Qt::DisplayRole) const;
@@ -116,7 +115,6 @@ public slots:
QVariant getDefaultServerData(const QString roleString); QVariant getDefaultServerData(const QString roleString);
QVariant getProcessedServerData(const QString roleString); QVariant getProcessedServerData(const QString roleString);
bool setProcessedServerData(const QString &roleString, const QVariant &value);
bool isDefaultServerDefaultContainerHasSplitTunneling(); bool isDefaultServerDefaultContainerHasSplitTunneling();
@@ -129,9 +127,6 @@ protected:
signals: signals:
void processedServerIndexChanged(const int index); void processedServerIndexChanged(const int index);
// emitted when the processed server index or processed server data is changed
void processedServerChanged();
void defaultServerIndexChanged(const int index); void defaultServerIndexChanged(const int index);
void defaultServerNameChanged(); void defaultServerNameChanged();
void defaultServerDescriptionChanged(); void defaultServerDescriptionChanged();
+1 -1
View File
@@ -24,7 +24,7 @@ Button {
property string leftImageSource property string leftImageSource
property string rightImageSource property string rightImageSource
property string leftImageColor: textColor property string leftImageColor
property bool changeLeftImageSize: true property bool changeLeftImageSize: true
property bool squareLeftSide: false property bool squareLeftSide: false
-1
View File
@@ -110,7 +110,6 @@ PageType {
text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled") text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled")
leftImageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : "" leftImageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : ""
leftImageColor: ""
rightImageSource: "qrc:/images/controls/chevron-down.svg" rightImageSource: "qrc:/images/controls/chevron-down.svg"
Keys.onEnterPressed: splitTunnelingButton.clicked() Keys.onEnterPressed: splitTunnelingButton.clicked()
@@ -54,14 +54,8 @@ PageType {
imageSource: "qrc:/images/controls/download.svg" imageSource: "qrc:/images/controls/download.svg"
checked: index === ApiCountryModel.currentIndex checked: index === ApiCountryModel.currentIndex
checkable: !ConnectionController.isConnected
onClicked: { onClicked: {
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Unable change server location while there is an active connection"))
return
}
if (index !== ApiCountryModel.currentIndex) { if (index !== ApiCountryModel.currentIndex) {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
var prevIndex = ApiCountryModel.currentIndex var prevIndex = ApiCountryModel.currentIndex
@@ -56,15 +56,12 @@ PageType {
} }
LabelWithImageType { LabelWithImageType {
property bool showSubscriptionEndDate: ServersModel.getProcessedServerData("isCountrySelectionAvailable")
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 16 Layout.margins: 16
imageSource: "qrc:/images/controls/history.svg" imageSource: "qrc:/images/controls/history.svg"
leftText: showSubscriptionEndDate ? qsTr("Valid until") : qsTr("Work period") leftText: qsTr("Work period")
rightText: showSubscriptionEndDate ? ApiServicesModel.getSelectedServiceData("endDate") rightText: ApiServicesModel.getSelectedServiceData("workPeriod")
: ApiServicesModel.getSelectedServiceData("workPeriod")
visible: rightText !== "" visible: rightText !== ""
} }
+35 -38
View File
@@ -25,8 +25,6 @@ PageType {
property int pageSettingsApiServerInfo: 3 property int pageSettingsApiServerInfo: 3
property int pageSettingsApiLanguageList: 4 property int pageSettingsApiLanguageList: 4
property var processedServer
defaultActiveFocusItem: focusItem defaultActiveFocusItem: focusItem
Connections { Connections {
@@ -37,18 +35,8 @@ PageType {
} }
} }
Connections {
target: ServersModel
function onProcessedServerChanged() {
root.processedServer = proxyServersModel.get(0)
}
}
SortFilterProxyModel { SortFilterProxyModel {
id: proxyServersModel id: proxyServersModel
objectName: "proxyServersModel"
sourceModel: ServersModel sourceModel: ServersModel
filters: [ filters: [
ValueFilter { ValueFilter {
@@ -56,31 +44,42 @@ PageType {
value: true value: true
} }
] ]
Component.onCompleted: {
root.processedServer = proxyServersModel.get(0)
}
} }
Item { Item {
id: focusItem id: focusItem
//KeyNavigation.tab: header KeyNavigation.tab: header
} }
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
spacing: 4 spacing: 16
Repeater {
id: header
model: proxyServersModel
activeFocusOnTab: true
onFocusChanged: {
header.itemAt(0).focusItem.forceActiveFocus()
}
delegate: ColumnLayout {
property alias focusItem: backButton
id: content
Layout.topMargin: 20
BackButtonType { BackButtonType {
id: backButton id: backButton
Layout.topMargin: 20
KeyNavigation.tab: headerContent.actionButton KeyNavigation.tab: headerContent.actionButton
backButtonFunction: function() { backButtonFunction: function() {
if (nestedStackView.currentIndex === root.pageSettingsApiServerInfo && if (nestedStackView.currentIndex === root.pageSettingsApiServerInfo &&
root.processedServer.isCountrySelectionAvailable) { ServersModel.getProcessedServerData("isCountrySelectionAvailable")) {
nestedStackView.currentIndex = root.pageSettingsApiLanguageList nestedStackView.currentIndex = root.pageSettingsApiLanguageList
} else { } else {
PageController.closePage() PageController.closePage()
@@ -94,23 +93,18 @@ PageType {
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
actionButtonImage: nestedStackView.currentIndex === root.pageSettingsApiLanguageList ? "qrc:/images/controls/settings.svg" actionButtonImage: nestedStackView.currentIndex === root.pageSettingsApiLanguageList ? "qrc:/images/controls/settings.svg" : "qrc:/images/controls/edit-3.svg"
: "qrc:/images/controls/edit-3.svg"
headerText: root.processedServer.name headerText: name
descriptionText: { descriptionText: {
if (root.processedServer.isServerFromGatewayApi) { if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) {
return qsTr("Subscription is valid until ") + ApiServicesModel.getSelectedServiceData("endDate")
} else {
return ApiServicesModel.getSelectedServiceData("serviceDescription") return ApiServicesModel.getSelectedServiceData("serviceDescription")
} } else if (ServersModel.getProcessedServerData("isServerFromTelegramApi")) {
} else if (root.processedServer.isServerFromTelegramApi) { return serverDescription
return root.processedServer.serverDescription } else if (ServersModel.isProcessedServerHasWriteAccess()) {
} else if (root.processedServer.hasWriteAccess) { return credentialsLogin + " · " + hostName
return root.processedServer.credentialsLogin + " · " + root.processedServer.hostName
} else { } else {
return root.processedServer.hostName return hostName
} }
} }
@@ -165,7 +159,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
headerText: qsTr("Server name") headerText: qsTr("Server name")
textFieldText: root.processedServer.name textFieldText: name
textField.maximumLength: 30 textField.maximumLength: 30
checkEmptyText: true checkEmptyText: true
@@ -185,14 +179,16 @@ PageType {
return return
} }
if (serverName.textFieldText !== root.processedServer.name) { if (serverName.textFieldText !== name) {
ServersModel.setProcessedServerData("name", serverName.textFieldText); name = serverName.textFieldText
} }
serverNameEditDrawer.close() serverNameEditDrawer.close()
} }
} }
} }
} }
}
}
TabBar { TabBar {
id: tabBar id: tabBar
@@ -261,7 +257,8 @@ PageType {
StackLayout { StackLayout {
id: nestedStackView id: nestedStackView
Layout.fillWidth: true Layout.preferredWidth: root.width
Layout.preferredHeight: root.height - tabBar.implicitHeight - header.implicitHeight
currentIndex: ServersModel.getProcessedServerData("isServerFromGatewayApi") ? currentIndex: ServersModel.getProcessedServerData("isServerFromGatewayApi") ?
(ServersModel.getProcessedServerData("isCountrySelectionAvailable") ? (ServersModel.getProcessedServerData("isCountrySelectionAvailable") ?
+1 -1
View File
@@ -92,7 +92,7 @@ PageType {
break break
} }
case PageShare.ConfigType.Xray: { case PageShare.ConfigType.Xray: {
ExportController.generateXrayConfig(clientNameTextField.textFieldText) ExportController.generateXrayConfig()
shareConnectionDrawer.configCaption = qsTr("Save XRay config") shareConnectionDrawer.configCaption = qsTr("Save XRay config")
shareConnectionDrawer.configExtension = ".json" shareConnectionDrawer.configExtension = ".json"
shareConnectionDrawer.configFileName = "amnezia_for_xray" shareConnectionDrawer.configFileName = "amnezia_for_xray"
+1 -1
View File
@@ -248,7 +248,7 @@ bool Utils::killProcessByName(const QString &name)
#elif defined Q_OS_IOS || defined(Q_OS_ANDROID) #elif defined Q_OS_IOS || defined(Q_OS_ANDROID)
return false; return false;
#else #else
return QProcess::execute(QString("pkill %1").arg(name)) == 0; QProcess::execute(QString("pkill %1").arg(name));
#endif #endif
} }
-2
View File
@@ -228,8 +228,6 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
// double-check + ensure our firewall is installed and enabled // double-check + ensure our firewall is installed and enabled
if (!LinuxFirewall::isInstalled())
LinuxFirewall::install();
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), blockAll); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), blockAll);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), allowNets); LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), allowNets);