mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 75d5b73e2f | |||
| 25f7356a1a | |||
| 764d432453 | |||
| a180e12bdf | |||
| f3a4a1b1be | |||
| 6977a8ecbc | |||
| d00f64e6ad | |||
| d5b3da6ba3 | |||
| c245318339 | |||
| b3b0fec2e1 | |||
| 9d571a4c71 | |||
| f283858490 | |||
| 76fe203767 | |||
| b9a47f2f50 | |||
| 27cb17c640 | |||
| ef8fb89eb3 | |||
| f1b045f8a8 | |||
| 050066132b | |||
| 2a6e6a1e24 | |||
| 92689d084c | |||
| 00f314039d | |||
| fcb75e837d | |||
| 9fbea76b74 | |||
| b3ff120bcf | |||
| 9dea98f020 | |||
| c4701d4e7a | |||
| 48903ca3a1 | |||
| 0c9fd4aef4 | |||
| b2af2e46ac | |||
| efc76a0683 | |||
| c4a553c166 | |||
| 69a00b0252 | |||
| 4257c08b43 | |||
| c9e5b92f79 | |||
| 99818c2ad8 | |||
| 99e3afabad | |||
| d3339a7f3a | |||
| 678bfffe49 | |||
| 728b48044c | |||
| 7ccbfa48bc |
@@ -10,7 +10,7 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Build-Linux-Ubuntu:
|
Build-Linux-Ubuntu:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.6.2
|
QT_VERSION: 6.6.2
|
||||||
@@ -190,7 +190,7 @@ jobs:
|
|||||||
- name: 'Install go'
|
- name: 'Install go'
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.22.1'
|
go-version: '1.24'
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
- name: 'Setup gomobile'
|
- name: 'Setup gomobile'
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
|||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.8.4.0
|
project(${PROJECT} VERSION 4.8.6.0
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
|||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 2077)
|
set(APP_ANDROID_VERSION_CODE 2083)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|||||||
+10
-10
@@ -6,11 +6,11 @@
|
|||||||
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
||||||
|
|
||||||
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
|
### [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 source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
||||||
|
|
||||||
[](https://amnezia.org)
|
[](https://amnezia.org)
|
||||||
|
|
||||||
### [Сайт](https://amnezia.org) | [Зеркало на сайт](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
### [Сайт](https://amnezia.org) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
|
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
|
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
|
||||||
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
|
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
|
||||||
- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
|
- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
|
||||||
- Поддерживает платформы: Windows, MacOS, Linux, Android, iOS.
|
- Поддерживает платформы: Windows, macOS, Linux, Android, iOS.
|
||||||
- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
|
- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
|
||||||
|
|
||||||
## Ссылки
|
## Ссылки
|
||||||
@@ -38,10 +38,10 @@
|
|||||||
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
|
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||||
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
|
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
|
||||||
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||||
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддржки в Telegram (Английский)
|
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
|
||||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддржки в Telegram (Фарси)
|
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
|
||||||
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддржки в Telegram (Мьянма)
|
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
|
||||||
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддржки в Telegram (Русский)
|
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
|
||||||
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
|
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
|
||||||
|
|
||||||
## Технологии
|
## Технологии
|
||||||
@@ -80,8 +80,8 @@ git submodule update --init --recursive
|
|||||||
Проверьте папку deploy для скриптов сборки.
|
Проверьте папку deploy для скриптов сборки.
|
||||||
|
|
||||||
### Как собрать iOS-приложение из исходного кода на MacOS
|
### Как собрать iOS-приложение из исходного кода на MacOS
|
||||||
1. Убедитесь, что у вас установлен XCode версии 14 или выше.
|
1. Убедитесь, что у вас установлен Xcode версии 14 или выше.
|
||||||
2. Для генерации проекта XCode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
|
2. Для генерации проекта Xcode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
|
||||||
- MacOS
|
- MacOS
|
||||||
- iOS
|
- iOS
|
||||||
- Модуль совместимости с Qt 5
|
- Модуль совместимости с Qt 5
|
||||||
@@ -117,7 +117,7 @@ $QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
|
|||||||
export PATH=$(PATH):/path/to/GOPATH/bin
|
export PATH=$(PATH):/path/to/GOPATH/bin
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Откройте проект в XCode. Теперь вы можете тестировать, архивировать или публиковать приложение.
|
6. Откройте проект в Xcode. Теперь вы можете тестировать, архивировать или публиковать приложение.
|
||||||
|
|
||||||
Если сборка завершится с ошибкой:
|
Если сборка завершится с ошибкой:
|
||||||
```
|
```
|
||||||
|
|||||||
+1
-1
Submodule client/3rd-prebuilt updated: e555c78bcf...efad1a5b5c
@@ -31,10 +31,6 @@ add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
|
|||||||
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
|
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
|
||||||
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
|
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
|
||||||
|
|
||||||
if(IOS)
|
|
||||||
set(PACKAGES ${PACKAGES} Multimedia)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
set(PACKAGES ${PACKAGES} Widgets)
|
set(PACKAGES ${PACKAGES} Widgets)
|
||||||
endif()
|
endif()
|
||||||
@@ -48,10 +44,6 @@ set(LIBS ${LIBS}
|
|||||||
Qt6::Core5Compat Qt6::Concurrent
|
Qt6::Core5Compat Qt6::Concurrent
|
||||||
)
|
)
|
||||||
|
|
||||||
if(IOS)
|
|
||||||
set(LIBS ${LIBS} Qt6::Multimedia)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
set(LIBS ${LIBS} Qt6::Widgets)
|
set(LIBS ${LIBS} Qt6::Widgets)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
|
#include <QRegularExpression>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QTemporaryDir>
|
#include <QTemporaryDir>
|
||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
@@ -19,13 +20,17 @@
|
|||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings,
|
||||||
bool isAwg, QObject *parent)
|
const QSharedPointer<ServerController> &serverController, bool isAwg,
|
||||||
|
QObject *parent)
|
||||||
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
|
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
|
||||||
{
|
{
|
||||||
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
m_serverConfigPath =
|
||||||
m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
|
m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
||||||
m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
|
m_serverPublicKeyPath =
|
||||||
|
m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
|
||||||
|
m_serverPskKeyPath =
|
||||||
|
m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
|
||||||
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
|
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
|
||||||
|
|
||||||
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
||||||
@@ -63,9 +68,31 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
|
|||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<QHostAddress> WireguardConfigurator::getIpsFromConf(const QString &input)
|
||||||
|
{
|
||||||
|
QRegularExpression regex("AllowedIPs = (\\d+\\.\\d+\\.\\d+\\.\\d+)");
|
||||||
|
QRegularExpressionMatchIterator matchIterator = regex.globalMatch(input);
|
||||||
|
|
||||||
|
QList<QHostAddress> ips;
|
||||||
|
|
||||||
|
while (matchIterator.hasNext()) {
|
||||||
|
QRegularExpressionMatch match = matchIterator.next();
|
||||||
|
const QString address_string { match.captured(1) };
|
||||||
|
const QHostAddress address { address_string };
|
||||||
|
if (address.isNull()) {
|
||||||
|
qWarning() << "Couldn't recognize the ip address: " << address_string;
|
||||||
|
} else {
|
||||||
|
ips << address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips;
|
||||||
|
}
|
||||||
|
|
||||||
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
||||||
DockerContainer container,
|
DockerContainer container,
|
||||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
const QJsonObject &containerConfig,
|
||||||
|
ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
||||||
connData.host = credentials.hostName;
|
connData.host = credentials.hostName;
|
||||||
@@ -76,65 +103,45 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get list of already created clients (only IP addresses)
|
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
|
||||||
QString nextIpNumber;
|
QString stdOut;
|
||||||
{
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
QString script = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
|
stdOut += data + "\n";
|
||||||
QString stdOut;
|
return ErrorCode::NoError;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
};
|
||||||
stdOut += data + "\n";
|
|
||||||
return ErrorCode::NoError;
|
|
||||||
};
|
|
||||||
|
|
||||||
errorCode = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut);
|
errorCode = m_serverController->runContainerScript(credentials, container, getIpsScript, cbReadStdOut);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
auto ips = getIpsFromConf(stdOut);
|
||||||
|
|
||||||
stdOut.replace("AllowedIPs = ", "");
|
QHostAddress nextIp = [&] {
|
||||||
stdOut.replace("/32", "");
|
QHostAddress result;
|
||||||
QStringList ips = stdOut.split("\n", Qt::SkipEmptyParts);
|
QHostAddress lastIp;
|
||||||
|
if (ips.empty()) {
|
||||||
// remove extra IPs from each line for case when user manually edited the wg0.conf
|
lastIp.setAddress(containerConfig.value(m_protocolName)
|
||||||
// and added there more IPs for route his itnernal networks, like:
|
.toObject()
|
||||||
// ...
|
.value(config_key::subnet_address)
|
||||||
// AllowedIPs = 10.8.1.6/32, 192.168.1.0/24, 192.168.2.0/24, ...
|
.toString(protocols::wireguard::defaultSubnetAddress));
|
||||||
// ...
|
|
||||||
// without this code - next IP would be 1 if last item in 'ips' has format above
|
|
||||||
QStringList vpnIps;
|
|
||||||
for (const auto &ip : ips) {
|
|
||||||
vpnIps.append(ip.split(",", Qt::SkipEmptyParts).first().trimmed());
|
|
||||||
}
|
|
||||||
ips = vpnIps;
|
|
||||||
|
|
||||||
// Calc next IP address
|
|
||||||
if (ips.isEmpty()) {
|
|
||||||
nextIpNumber = "2";
|
|
||||||
} else {
|
} else {
|
||||||
int next = ips.last().split(".").last().toInt() + 1;
|
lastIp = ips.last();
|
||||||
if (next > 254) {
|
|
||||||
errorCode = ErrorCode::AddressPoolError;
|
|
||||||
return connData;
|
|
||||||
}
|
|
||||||
nextIpNumber = QString::number(next);
|
|
||||||
}
|
}
|
||||||
}
|
quint8 lastOctet = static_cast<quint8>(lastIp.toIPv4Address());
|
||||||
|
switch (lastOctet) {
|
||||||
QString subnetIp = containerConfig.value(m_protocolName).toObject().value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
case 254: result.setAddress(lastIp.toIPv4Address() + 3); break;
|
||||||
{
|
case 255: result.setAddress(lastIp.toIPv4Address() + 2); break;
|
||||||
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
|
default: result.setAddress(lastIp.toIPv4Address() + 1); break;
|
||||||
if (l.isEmpty()) {
|
|
||||||
errorCode = ErrorCode::AddressPoolError;
|
|
||||||
return connData;
|
|
||||||
}
|
}
|
||||||
l.removeLast();
|
|
||||||
l.append(nextIpNumber);
|
|
||||||
|
|
||||||
connData.clientIP = l.join(".");
|
return result;
|
||||||
}
|
}();
|
||||||
|
|
||||||
|
connData.clientIP = nextIp.toString();
|
||||||
|
|
||||||
// Get keys
|
// Get keys
|
||||||
connData.serverPubKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
|
connData.serverPubKey =
|
||||||
|
m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
|
||||||
connData.serverPubKey.replace("\n", "");
|
connData.serverPubKey.replace("\n", "");
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return connData;
|
return connData;
|
||||||
@@ -161,10 +168,12 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'").arg(m_serverConfigPath);
|
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
|
||||||
|
.arg(m_serverConfigPath);
|
||||||
|
|
||||||
errorCode = m_serverController->runScript(
|
errorCode = m_serverController->runScript(
|
||||||
credentials, m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
|
credentials,
|
||||||
|
m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
|
||||||
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
@@ -173,8 +182,8 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
|
|||||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
||||||
QString config =
|
QString config = m_serverController->replaceVars(
|
||||||
m_serverController->replaceVars(scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
|
scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||||
|
|
||||||
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
@@ -208,16 +217,16 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
|
|||||||
return QJsonDocument(jConfig).toJson();
|
return QJsonDocument(jConfig).toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns,
|
||||||
QString &protocolConfigString)
|
const bool isApiConfig, QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
|
||||||
return protocolConfigString;
|
return protocolConfigString;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns,
|
||||||
QString &protocolConfigString)
|
const bool isApiConfig, QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#ifndef WIREGUARD_CONFIGURATOR_H
|
#ifndef WIREGUARD_CONFIGURATOR_H
|
||||||
#define WIREGUARD_CONFIGURATOR_H
|
#define WIREGUARD_CONFIGURATOR_H
|
||||||
|
|
||||||
|
#include <QHostAddress>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
|
|
||||||
@@ -12,8 +13,8 @@ class WireguardConfigurator : public ConfiguratorBase
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, bool isAwg,
|
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||||
QObject *parent = nullptr);
|
bool isAwg, QObject *parent = nullptr);
|
||||||
|
|
||||||
struct ConnectionData
|
struct ConnectionData
|
||||||
{
|
{
|
||||||
@@ -26,15 +27,18 @@ public:
|
|||||||
QString port;
|
QString port;
|
||||||
};
|
};
|
||||||
|
|
||||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
ErrorCode &errorCode);
|
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||||
|
|
||||||
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
QString &protocolConfigString);
|
||||||
|
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString);
|
||||||
|
|
||||||
static ConnectionData genClientKeys();
|
static ConnectionData genClientKeys();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QList<QHostAddress> getIpsFromConf(const QString &input);
|
||||||
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
|||||||
return amnezia::ErrorCode::NoError;
|
return amnezia::ErrorCode::NoError;
|
||||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
|
qDebug() << reply->error();
|
||||||
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
||||||
} else {
|
} else {
|
||||||
QString err = reply->errorString();
|
QString err = reply->errorString();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "coreController.h"
|
#include "coreController.h"
|
||||||
|
|
||||||
|
#include <QDirIterator>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
@@ -20,6 +21,9 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
|
|||||||
initControllers();
|
initControllers();
|
||||||
initSignalHandlers();
|
initSignalHandlers();
|
||||||
|
|
||||||
|
initAndroidController();
|
||||||
|
initAppleController();
|
||||||
|
|
||||||
initNotificationHandler();
|
initNotificationHandler();
|
||||||
|
|
||||||
auto locale = m_settings->getAppLanguage();
|
auto locale = m_settings->getAppLanguage();
|
||||||
@@ -90,6 +94,9 @@ void CoreController::initModels()
|
|||||||
|
|
||||||
m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this));
|
m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get());
|
m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get());
|
||||||
|
|
||||||
|
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initControllers()
|
void CoreController::initControllers()
|
||||||
@@ -129,7 +136,8 @@ void CoreController::initControllers()
|
|||||||
m_systemController.reset(new SystemController(m_settings));
|
m_systemController.reset(new SystemController(m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
|
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
|
||||||
|
|
||||||
m_apiSettingsController.reset(new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_settings));
|
m_apiSettingsController.reset(
|
||||||
|
new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get());
|
m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get());
|
||||||
|
|
||||||
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
|
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
|
||||||
@@ -231,7 +239,23 @@ void CoreController::updateTranslator(const QLocale &locale)
|
|||||||
QCoreApplication::removeTranslator(m_translator.get());
|
QCoreApplication::removeTranslator(m_translator.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
|
QStringList availableTranslations;
|
||||||
|
QDirIterator it(":/translations", QStringList("amneziavpn_*.qm"), QDir::Files);
|
||||||
|
while (it.hasNext()) {
|
||||||
|
availableTranslations << it.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This code allow to load translation for the language only, without country code
|
||||||
|
const QString lang = locale.name().split("_").first();
|
||||||
|
const QString translationFilePrefix = QString(":/translations/amneziavpn_") + lang;
|
||||||
|
QString strFileName = QString(":/translations/amneziavpn_%1.qm").arg(locale.name());
|
||||||
|
for (const QString &translation : availableTranslations) {
|
||||||
|
if (translation.contains(translationFilePrefix)) {
|
||||||
|
strFileName = translation;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (m_translator->load(strFileName)) {
|
if (m_translator->load(strFileName)) {
|
||||||
if (QCoreApplication::installTranslator(m_translator.get())) {
|
if (QCoreApplication::installTranslator(m_translator.get())) {
|
||||||
m_settings->setAppLanguage(locale);
|
m_settings->setAppLanguage(locale);
|
||||||
|
|||||||
@@ -25,8 +25,9 @@
|
|||||||
#include "ui/models/protocols/ikev2ConfigModel.h"
|
#include "ui/models/protocols/ikev2ConfigModel.h"
|
||||||
#endif
|
#endif
|
||||||
#include "ui/models/api/apiAccountInfoModel.h"
|
#include "ui/models/api/apiAccountInfoModel.h"
|
||||||
#include "ui/models/api/apiServicesModel.h"
|
|
||||||
#include "ui/models/api/apiCountryModel.h"
|
#include "ui/models/api/apiCountryModel.h"
|
||||||
|
#include "ui/models/api/apiDevicesModel.h"
|
||||||
|
#include "ui/models/api/apiServicesModel.h"
|
||||||
#include "ui/models/appSplitTunnelingModel.h"
|
#include "ui/models/appSplitTunnelingModel.h"
|
||||||
#include "ui/models/clientManagementModel.h"
|
#include "ui/models/clientManagementModel.h"
|
||||||
#include "ui/models/protocols/awgConfigModel.h"
|
#include "ui/models/protocols/awgConfigModel.h"
|
||||||
@@ -117,6 +118,7 @@ private:
|
|||||||
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
||||||
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||||
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
||||||
|
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
|
||||||
|
|
||||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ namespace
|
|||||||
constexpr char apiPayload[] = "api_payload";
|
constexpr char apiPayload[] = "api_payload";
|
||||||
constexpr char keyPayload[] = "key_payload";
|
constexpr char keyPayload[] = "key_payload";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr QLatin1String errorResponsePattern1("No active configuration found for");
|
||||||
|
constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for");
|
||||||
|
constexpr QLatin1String errorResponsePattern3("Account not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
GatewayController::GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent)
|
GatewayController::GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent)
|
||||||
@@ -148,7 +152,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
|
|
||||||
QByteArray encryptedResponseBody = reply->readAll();
|
QByteArray encryptedResponseBody = reply->readAll();
|
||||||
|
|
||||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, false)) {
|
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
||||||
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
|
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
|
||||||
request.setUrl(url);
|
request.setUrl(url);
|
||||||
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||||
@@ -157,12 +161,12 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
|
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
|
||||||
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
|
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
|
||||||
encryptedResponseBody = nestedReply->readAll();
|
encryptedResponseBody = nestedReply->readAll();
|
||||||
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
|
reply = nestedReply;
|
||||||
|
if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
|
||||||
sslErrors = nestedSslErrors;
|
sslErrors = nestedSslErrors;
|
||||||
reply = nestedReply;
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
||||||
@@ -194,16 +198,16 @@ QStringList GatewayController::getProxyUrls()
|
|||||||
QList<QSslError> sslErrors;
|
QList<QSslError> sslErrors;
|
||||||
QNetworkReply *reply;
|
QNetworkReply *reply;
|
||||||
|
|
||||||
QStringList proxyStorageUrl;
|
QStringList proxyStorageUrls;
|
||||||
if (m_isDevEnvironment) {
|
if (m_isDevEnvironment) {
|
||||||
proxyStorageUrl = QStringList { DEV_S3_ENDPOINT };
|
proxyStorageUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
||||||
} else {
|
} else {
|
||||||
proxyStorageUrl = QStringList { PROD_S3_ENDPOINT };
|
proxyStorageUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||||
|
|
||||||
for (const auto &proxyStorageUrl : proxyStorageUrl) {
|
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||||
request.setUrl(proxyStorageUrl);
|
request.setUrl(proxyStorageUrl);
|
||||||
reply = amnApp->networkManager()->get(request);
|
reply = amnApp->networkManager()->get(request);
|
||||||
|
|
||||||
@@ -212,62 +216,77 @@ QStringList GatewayController::getProxyUrls()
|
|||||||
wait.exec();
|
wait.exec();
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||||
break;
|
auto encryptedResponseBody = reply->readAll();
|
||||||
}
|
reply->deleteLater();
|
||||||
reply->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto encryptedResponseBody = reply->readAll();
|
EVP_PKEY *privateKey = nullptr;
|
||||||
reply->deleteLater();
|
QByteArray responseBody;
|
||||||
|
try {
|
||||||
|
if (!m_isDevEnvironment) {
|
||||||
|
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||||
|
hash.addData(key);
|
||||||
|
QByteArray hashResult = hash.result().toHex();
|
||||||
|
|
||||||
EVP_PKEY *privateKey = nullptr;
|
QByteArray key = QByteArray::fromHex(hashResult.left(64));
|
||||||
QByteArray responseBody;
|
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
|
||||||
try {
|
|
||||||
if (!m_isDevEnvironment) {
|
|
||||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
|
||||||
hash.addData(key);
|
|
||||||
QByteArray hashResult = hash.result().toHex();
|
|
||||||
|
|
||||||
QByteArray key = QByteArray::fromHex(hashResult.left(64));
|
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
|
||||||
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
|
|
||||||
|
|
||||||
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
|
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
|
||||||
|
} else {
|
||||||
|
responseBody = encryptedResponseBody;
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
Utils::logException();
|
||||||
|
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
QSimpleCrypto::QBlockCipher blockCipher;
|
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||||
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
|
|
||||||
|
QStringList endpoints;
|
||||||
|
for (const auto &endpoint : endpointsArray) {
|
||||||
|
endpoints.push_back(endpoint.toString());
|
||||||
|
}
|
||||||
|
return endpoints;
|
||||||
} else {
|
} else {
|
||||||
responseBody = encryptedResponseBody;
|
apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||||
|
qDebug() << "go to the next storage endpoint";
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
}
|
}
|
||||||
} catch (...) {
|
|
||||||
Utils::logException();
|
|
||||||
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
return {};
|
||||||
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
|
||||||
|
|
||||||
QStringList endpoints;
|
|
||||||
for (const auto &endpoint : endpointsArray) {
|
|
||||||
endpoints.push_back(endpoint.toString());
|
|
||||||
}
|
|
||||||
return endpoints;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
|
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
|
||||||
const QByteArray &iv, const QByteArray &salt)
|
const QByteArray &iv, const QByteArray &salt)
|
||||||
{
|
{
|
||||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
qDebug() << "Timeout occurred";
|
qDebug() << "timeout occurred";
|
||||||
|
qDebug() << reply->error();
|
||||||
return true;
|
return true;
|
||||||
} else if (responseBody.contains("html")) {
|
} else if (responseBody.contains("html")) {
|
||||||
qDebug() << "The response contains an html tag";
|
qDebug() << "the response contains an html tag";
|
||||||
|
return true;
|
||||||
|
} else if (reply->error() == QNetworkReply::NetworkError::ContentNotFoundError) {
|
||||||
|
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
||||||
|
|| responseBody.contains(errorResponsePattern3)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
qDebug() << reply->error();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||||
|
qDebug() << reply->error();
|
||||||
return true;
|
return true;
|
||||||
} else if (checkEncryption) {
|
} else if (checkEncryption) {
|
||||||
try {
|
try {
|
||||||
QSimpleCrypto::QBlockCipher blockCipher;
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt));
|
static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt));
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
qDebug() << "Failed to decrypt the data";
|
qDebug() << "failed to decrypt the data";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -288,7 +307,7 @@ void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *repl
|
|||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
|
|
||||||
for (const QString &proxyUrl : proxyUrls) {
|
for (const QString &proxyUrl : proxyUrls) {
|
||||||
qDebug() << "Go to the next endpoint";
|
qDebug() << "go to the next proxy endpoint";
|
||||||
reply->deleteLater(); // delete the previous reply
|
reply->deleteLater(); // delete the previous reply
|
||||||
reply = requestFunction(endpoint.arg(proxyUrl));
|
reply = requestFunction(endpoint.arg(proxyUrl));
|
||||||
|
|
||||||
@@ -296,7 +315,7 @@ void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *repl
|
|||||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
wait.exec();
|
wait.exec();
|
||||||
|
|
||||||
if (!replyProcessingFunction(reply, sslErrors)) {
|
if (replyProcessingFunction(reply, sslErrors)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -709,7 +709,7 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
|||||||
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
|
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
|
||||||
|
|
||||||
// TODO reimplement with netstat
|
// TODO reimplement with netstat
|
||||||
QString script = QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
|
QString script = QString("which lsof > /dev/null 2>&1 || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
|
||||||
for (auto &port : fixedPorts) {
|
for (auto &port : fixedPorts) {
|
||||||
script = script.append("|:%1").arg(port);
|
script = script.append("|:%1").arg(port);
|
||||||
}
|
}
|
||||||
@@ -757,10 +757,6 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
|||||||
|
|
||||||
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
|
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
|
||||||
{
|
{
|
||||||
if (credentials.userName == "root") {
|
|
||||||
return ErrorCode::NoError;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
stdOut += data + "\n";
|
stdOut += data + "\n";
|
||||||
@@ -774,8 +770,16 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D
|
|||||||
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
||||||
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
if (!stdOut.contains("sudo"))
|
if (credentials.userName != "root" && stdOut.contains("sudo:") && !stdOut.contains("uname:") && stdOut.contains("not found"))
|
||||||
|
return ErrorCode::ServerSudoPackageIsNotPreinstalled;
|
||||||
|
if (credentials.userName != "root" && !stdOut.contains("sudo") && !stdOut.contains("wheel"))
|
||||||
return ErrorCode::ServerUserNotInSudo;
|
return ErrorCode::ServerUserNotInSudo;
|
||||||
|
if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory"))
|
||||||
|
return ErrorCode::ServerUserDirectoryNotAccessible;
|
||||||
|
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
|
||||||
|
return ErrorCode::ServerUserNotAllowedInSudoers;
|
||||||
|
if (stdOut.contains("password is required"))
|
||||||
|
return ErrorCode::ServerUserPasswordRequired;
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ namespace amnezia
|
|||||||
ServerCancelInstallation = 204,
|
ServerCancelInstallation = 204,
|
||||||
ServerUserNotInSudo = 205,
|
ServerUserNotInSudo = 205,
|
||||||
ServerPacketManagerError = 206,
|
ServerPacketManagerError = 206,
|
||||||
|
ServerSudoPackageIsNotPreinstalled = 207,
|
||||||
|
ServerUserDirectoryNotAccessible = 208,
|
||||||
|
ServerUserNotAllowedInSudoers = 209,
|
||||||
|
ServerUserPasswordRequired = 210,
|
||||||
|
|
||||||
// Ssh connection errors
|
// Ssh connection errors
|
||||||
SshRequestDeniedError = 300,
|
SshRequestDeniedError = 300,
|
||||||
|
|||||||
@@ -20,8 +20,12 @@ QString errorString(ErrorCode code) {
|
|||||||
case(ErrorCode::ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break;
|
case(ErrorCode::ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break;
|
||||||
case(ErrorCode::ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
|
case(ErrorCode::ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
|
||||||
case(ErrorCode::ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
|
case(ErrorCode::ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
|
||||||
case(ErrorCode::ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break;
|
case(ErrorCode::ServerUserNotInSudo): errorMessage = QObject::tr("The user is not a member of the sudo group"); break;
|
||||||
case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break;
|
case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Package manager error"); break;
|
||||||
|
case(ErrorCode::ServerSudoPackageIsNotPreinstalled): errorMessage = QObject::tr("The sudo package is not pre-installed on the server"); break;
|
||||||
|
case(ErrorCode::ServerUserDirectoryNotAccessible): errorMessage = QObject::tr("The server user's home directory is not accessible"); break;
|
||||||
|
case(ErrorCode::ServerUserNotAllowedInSudoers): errorMessage = QObject::tr("Action not allowed in sudoers"); break;
|
||||||
|
case(ErrorCode::ServerUserPasswordRequired): errorMessage = QObject::tr("The user's password is required"); break;
|
||||||
|
|
||||||
// Libssh errors
|
// Libssh errors
|
||||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20 3H4C2.89543 3 2 3.89543 2 5V15C2 16.1046 2.89543 17 4 17H20C21.1046 17 22 16.1046 22 15V5C22 3.89543 21.1046 3 20 3Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M8 21H16" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 17V21" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 522 B |
@@ -14,10 +14,15 @@ extension UIApplication {
|
|||||||
var keyWindows: [UIWindow] {
|
var keyWindows: [UIWindow] {
|
||||||
connectedScenes
|
connectedScenes
|
||||||
.compactMap {
|
.compactMap {
|
||||||
|
guard let windowScene = $0 as? UIWindowScene else { return nil }
|
||||||
if #available(iOS 15.0, *) {
|
if #available(iOS 15.0, *) {
|
||||||
($0 as? UIWindowScene)?.keyWindow
|
guard let keywindow = windowScene.keyWindow else {
|
||||||
|
windowScene.windows.first?.makeKey()
|
||||||
|
return windowScene.windows.first
|
||||||
|
}
|
||||||
|
return keywindow
|
||||||
} else {
|
} else {
|
||||||
($0 as? UIWindowScene)?.windows.first { $0.isKeyWindow }
|
return windowScene.windows.first { $0.isKeyWindow }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ IPUtilsLinux::~IPUtilsLinux() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IPUtilsLinux::addInterfaceIPs(const InterfaceConfig& config) {
|
bool IPUtilsLinux::addInterfaceIPs(const InterfaceConfig& config) {
|
||||||
return addIP4AddressToDevice(config) && addIP6AddressToDevice(config);
|
bool ret = addIP4AddressToDevice(config);
|
||||||
|
addIP6AddressToDevice(config);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
|
bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
|
||||||
|
|||||||
@@ -14,8 +14,6 @@
|
|||||||
|
|
||||||
#include "leakdetector.h"
|
#include "leakdetector.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "platforms/windows/windowscommons.h"
|
|
||||||
#include "windowsdaemon.h"
|
|
||||||
#include "windowsfirewall.h"
|
#include "windowsfirewall.h"
|
||||||
|
|
||||||
#pragma comment(lib, "iphlpapi.lib")
|
#pragma comment(lib, "iphlpapi.lib")
|
||||||
@@ -269,6 +267,13 @@ bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) {
|
|||||||
if (result == ERROR_OBJECT_ALREADY_EXISTS) {
|
if (result == ERROR_OBJECT_ALREADY_EXISTS) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Case for ipv6 route with disabled ipv6
|
||||||
|
if (prefix.address().protocol() == QAbstractSocket::IPv6Protocol
|
||||||
|
&& result == ERROR_NOT_FOUND) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (result != NO_ERROR) {
|
if (result != NO_ERROR) {
|
||||||
logger.error() << "Failed to create route to"
|
logger.error() << "Failed to create route to"
|
||||||
<< prefix.toString()
|
<< prefix.toString()
|
||||||
|
|||||||
@@ -229,6 +229,8 @@
|
|||||||
<file>ui/qml/Pages2/PageSettingsApiSupport.qml</file>
|
<file>ui/qml/Pages2/PageSettingsApiSupport.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsApiInstructions.qml</file>
|
<file>ui/qml/Pages2/PageSettingsApiInstructions.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
|
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
|
||||||
|
<file>ui/qml/Pages2/PageSettingsApiDevices.qml</file>
|
||||||
|
<file>images/controls/monitor.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/countriesFlags">
|
<qresource prefix="/countriesFlags">
|
||||||
<file>images/flagKit/ZW.svg</file>
|
<file>images/flagKit/ZW.svg</file>
|
||||||
|
|||||||
@@ -1,2 +1,13 @@
|
|||||||
CUR_USER=$(whoami);\
|
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\
|
||||||
groups $CUR_USER
|
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); opt="--version";\
|
||||||
|
elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\
|
||||||
|
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\
|
||||||
|
else pm="uname"; opt="-a";\
|
||||||
|
fi;\
|
||||||
|
CUR_USER=$(whoami 2>/dev/null || echo $HOME | sed 's/.*\///');\
|
||||||
|
echo $LANG | grep -qE '^(en_US.UTF-8|C.UTF-8|C)$' || export LC_ALL=C;\
|
||||||
|
sudo -K;\
|
||||||
|
cd ~;\
|
||||||
|
if [ "$CUR_USER" = "root" ] || ( groups "$CUR_USER" | grep -E '\<(sudo|wheel)\>' ); then \
|
||||||
|
sudo -nu $CUR_USER $pm $opt > /dev/null; sudo -n $pm $opt > /dev/null;\
|
||||||
|
fi
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
CUR_USER=$(whoami);\
|
CUR_USER=$(whoami 2>/dev/null || echo $HOME | sed 's/.*\///');\
|
||||||
sudo mkdir -p $DOCKERFILE_FOLDER;\
|
sudo mkdir -p $DOCKERFILE_FOLDER;\
|
||||||
sudo chown $CUR_USER $DOCKERFILE_FOLDER;\
|
sudo chown $CUR_USER $DOCKERFILE_FOLDER;\
|
||||||
if ! sudo docker network ls | grep -q amnezia-dns-net; then sudo docker network create \
|
if ! sudo docker network ls | grep -q amnezia-dns-net; then sudo docker network create \
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1074
-403
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
#include "apiConfigsController.h"
|
#include "apiConfigsController.h"
|
||||||
|
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
|
#include <QEventLoop>
|
||||||
|
|
||||||
#include "amnezia_application.h"
|
#include "amnezia_application.h"
|
||||||
#include "configurators/wireguard_configurator.h"
|
#include "configurators/wireguard_configurator.h"
|
||||||
@@ -19,7 +19,7 @@ namespace
|
|||||||
constexpr char cloak[] = "cloak";
|
constexpr char cloak[] = "cloak";
|
||||||
constexpr char awg[] = "awg";
|
constexpr char awg[] = "awg";
|
||||||
|
|
||||||
constexpr char apiEdnpoint[] = "api_endpoint";
|
constexpr char apiEndpoint[] = "api_endpoint";
|
||||||
constexpr char accessToken[] = "api_key";
|
constexpr char accessToken[] = "api_key";
|
||||||
constexpr char certificate[] = "certificate";
|
constexpr char certificate[] = "certificate";
|
||||||
constexpr char publicKey[] = "public_key";
|
constexpr char publicKey[] = "public_key";
|
||||||
@@ -269,54 +269,37 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
|||||||
|
|
||||||
bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
|
bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
|
||||||
{
|
{
|
||||||
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
|
||||||
auto installationUuid = m_settings->getInstallationUuid(true);
|
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
IosController::Instance()->requestInetAccess();
|
IosController::Instance()->requestInetAccess();
|
||||||
QThread::msleep(10);
|
QThread::msleep(10);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (serverConfig.value(config_key::configVersion).toInt()) {
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
|
||||||
QNetworkRequest request;
|
|
||||||
request.setTransferTimeout(apiDefs::requestTimeoutMsecs);
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
request.setRawHeader("Authorization", "Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
|
|
||||||
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
|
|
||||||
request.setUrl(endpoint);
|
|
||||||
|
|
||||||
QString protocol = serverConfig.value(configKey::protocol).toString();
|
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
||||||
|
auto installationUuid = m_settings->getInstallationUuid(true);
|
||||||
|
|
||||||
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
QString serviceProtocol = serverConfig.value(configKey::protocol).toString();
|
||||||
|
ApiPayloadData apiPayloadData = generateApiPayloadData(serviceProtocol);
|
||||||
|
|
||||||
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
QJsonObject apiPayload = fillApiPayload(serviceProtocol, apiPayloadData);
|
||||||
apiPayload[configKey::uuid] = installationUuid;
|
apiPayload[configKey::uuid] = installationUuid;
|
||||||
|
apiPayload[configKey::accessToken] = serverConfig.value(configKey::accessToken).toString();
|
||||||
|
apiPayload[configKey::apiEndpoint] = serverConfig.value(configKey::apiEndpoint).toString();
|
||||||
|
|
||||||
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
QByteArray responseBody;
|
||||||
|
ErrorCode errorCode = gatewayController.post(QString("%1v1/proxy_config"), apiPayload, responseBody);
|
||||||
|
|
||||||
QNetworkReply *reply = amnApp->networkManager()->post(request, requestBody);
|
if (errorCode == ErrorCode::NoError) {
|
||||||
|
fillServerConfig(serviceProtocol, apiPayloadData, responseBody, serverConfig);
|
||||||
|
|
||||||
QEventLoop wait;
|
|
||||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
|
||||||
|
|
||||||
QList<QSslError> sslErrors;
|
|
||||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
|
||||||
wait.exec();
|
|
||||||
|
|
||||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
reply->deleteLater();
|
|
||||||
emit errorOccurred(errorCode);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto apiResponseBody = reply->readAll();
|
|
||||||
reply->deleteLater();
|
|
||||||
fillServerConfig(protocol, apiPayloadData, apiResponseBody, serverConfig);
|
|
||||||
m_serversModel->editServer(serverConfig, serverIndex);
|
m_serversModel->editServer(serverConfig, serverIndex);
|
||||||
emit updateServerFromApiFinished();
|
emit updateServerFromApiFinished();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
emit errorOccurred(errorCode);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ApiConfigsController::deactivateDevice()
|
bool ApiConfigsController::deactivateDevice()
|
||||||
@@ -354,6 +337,43 @@ bool ApiConfigsController::deactivateDevice()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode)
|
||||||
|
{
|
||||||
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
|
||||||
|
|
||||||
|
auto serverIndex = m_serversModel->getProcessedServerIndex();
|
||||||
|
auto serverConfigObject = m_serversModel->getServerConfig(serverIndex);
|
||||||
|
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
||||||
|
|
||||||
|
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString();
|
||||||
|
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
||||||
|
|
||||||
|
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||||
|
apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode);
|
||||||
|
apiPayload[configKey::serverCountryCode] = serverCountryCode;
|
||||||
|
apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType);
|
||||||
|
apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData);
|
||||||
|
apiPayload[configKey::uuid] = uuid;
|
||||||
|
|
||||||
|
QByteArray responseBody;
|
||||||
|
ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody);
|
||||||
|
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
|
||||||
|
emit errorOccurred(errorCode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uuid == m_settings->getInstallationUuid(true)) {
|
||||||
|
serverConfigObject.remove(config_key::containers);
|
||||||
|
m_serversModel->editServer(serverConfigObject, serverIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool ApiConfigsController::isConfigValid()
|
bool ApiConfigsController::isConfigValid()
|
||||||
{
|
{
|
||||||
int serverIndex = m_serversModel->getDefaultServerIndex();
|
int serverIndex = m_serversModel->getDefaultServerIndex();
|
||||||
@@ -369,7 +389,7 @@ bool ApiConfigsController::isConfigValid()
|
|||||||
return updateServiceFromGateway(serverIndex, "", "");
|
return updateServiceFromGateway(serverIndex, "", "");
|
||||||
} else if (configSource && m_serversModel->isApiKeyExpired(serverIndex)) {
|
} else if (configSource && m_serversModel->isApiKeyExpired(serverIndex)) {
|
||||||
qDebug() << "attempt to update api config by expires_at event";
|
qDebug() << "attempt to update api config by expires_at event";
|
||||||
if (configSource == apiDefs::ConfigSource::Telegram) {
|
if (configSource == apiDefs::ConfigSource::AmneziaGateway) {
|
||||||
return updateServiceFromGateway(serverIndex, "", "");
|
return updateServiceFromGateway(serverIndex, "", "");
|
||||||
} else {
|
} else {
|
||||||
m_serversModel->removeApiConfig(serverIndex);
|
m_serversModel->removeApiConfig(serverIndex);
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public slots:
|
|||||||
bool reloadServiceConfig = false);
|
bool reloadServiceConfig = false);
|
||||||
bool updateServiceFromTelegram(const int serverIndex);
|
bool updateServiceFromTelegram(const int serverIndex);
|
||||||
bool deactivateDevice();
|
bool deactivateDevice();
|
||||||
|
bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode);
|
||||||
|
|
||||||
bool isConfigValid();
|
bool isConfigValid();
|
||||||
|
|
||||||
|
|||||||
@@ -25,11 +25,13 @@ namespace
|
|||||||
ApiSettingsController::ApiSettingsController(const QSharedPointer<ServersModel> &serversModel,
|
ApiSettingsController::ApiSettingsController(const QSharedPointer<ServersModel> &serversModel,
|
||||||
const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
|
const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
|
||||||
const QSharedPointer<ApiCountryModel> &apiCountryModel,
|
const QSharedPointer<ApiCountryModel> &apiCountryModel,
|
||||||
|
const QSharedPointer<ApiDevicesModel> &apiDevicesModel,
|
||||||
const std::shared_ptr<Settings> &settings, QObject *parent)
|
const std::shared_ptr<Settings> &settings, QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
m_serversModel(serversModel),
|
m_serversModel(serversModel),
|
||||||
m_apiAccountInfoModel(apiAccountInfoModel),
|
m_apiAccountInfoModel(apiAccountInfoModel),
|
||||||
m_apiCountryModel(apiCountryModel),
|
m_apiCountryModel(apiCountryModel),
|
||||||
|
m_apiDevicesModel(apiDevicesModel),
|
||||||
m_settings(settings)
|
m_settings(settings)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -73,6 +75,7 @@ bool ApiSettingsController::getAccountInfo(bool reload)
|
|||||||
|
|
||||||
if (reload) {
|
if (reload) {
|
||||||
updateApiCountryModel();
|
updateApiCountryModel();
|
||||||
|
updateApiDevicesModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -83,3 +86,8 @@ void ApiSettingsController::updateApiCountryModel()
|
|||||||
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");
|
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");
|
||||||
m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo());
|
m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApiSettingsController::updateApiDevicesModel()
|
||||||
|
{
|
||||||
|
m_apiDevicesModel->updateModel(m_apiAccountInfoModel->getIssuedConfigsInfo());
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include "ui/models/api/apiAccountInfoModel.h"
|
#include "ui/models/api/apiAccountInfoModel.h"
|
||||||
#include "ui/models/api/apiCountryModel.h"
|
#include "ui/models/api/apiCountryModel.h"
|
||||||
|
#include "ui/models/api/apiDevicesModel.h"
|
||||||
#include "ui/models/servers_model.h"
|
#include "ui/models/servers_model.h"
|
||||||
|
|
||||||
class ApiSettingsController : public QObject
|
class ApiSettingsController : public QObject
|
||||||
@@ -12,13 +13,14 @@ class ApiSettingsController : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ApiSettingsController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
|
ApiSettingsController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
|
||||||
const QSharedPointer<ApiCountryModel> &apiCountryModel, const std::shared_ptr<Settings> &settings,
|
const QSharedPointer<ApiCountryModel> &apiCountryModel, const QSharedPointer<ApiDevicesModel> &apiDevicesModel,
|
||||||
QObject *parent = nullptr);
|
const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
|
||||||
~ApiSettingsController();
|
~ApiSettingsController();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
bool getAccountInfo(bool reload);
|
bool getAccountInfo(bool reload);
|
||||||
void updateApiCountryModel();
|
void updateApiCountryModel();
|
||||||
|
void updateApiDevicesModel();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void errorOccurred(ErrorCode errorCode);
|
void errorOccurred(ErrorCode errorCode);
|
||||||
@@ -27,6 +29,7 @@ private:
|
|||||||
QSharedPointer<ServersModel> m_serversModel;
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
||||||
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||||
|
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
|
||||||
|
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ namespace
|
|||||||
ConfigTypes checkConfigFormat(const QString &config)
|
ConfigTypes checkConfigFormat(const QString &config)
|
||||||
{
|
{
|
||||||
const QString openVpnConfigPatternCli = "client";
|
const QString openVpnConfigPatternCli = "client";
|
||||||
const QString openVpnConfigPatternProto1 = "proto tcp";
|
|
||||||
const QString openVpnConfigPatternProto2 = "proto udp";
|
|
||||||
const QString openVpnConfigPatternDriver1 = "dev tun";
|
const QString openVpnConfigPatternDriver1 = "dev tun";
|
||||||
const QString openVpnConfigPatternDriver2 = "dev tap";
|
const QString openVpnConfigPatternDriver2 = "dev tap";
|
||||||
|
|
||||||
@@ -53,14 +51,13 @@ namespace
|
|||||||
|| (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
|
|| (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
|
||||||
&& config.contains(amneziaConfigPatternPassword))) {
|
&& config.contains(amneziaConfigPatternPassword))) {
|
||||||
return ConfigTypes::Amnezia;
|
return ConfigTypes::Amnezia;
|
||||||
} else if (config.contains(openVpnConfigPatternCli)
|
|
||||||
&& (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2))
|
|
||||||
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
|
|
||||||
return ConfigTypes::OpenVpn;
|
|
||||||
} else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) {
|
} else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) {
|
||||||
return ConfigTypes::WireGuard;
|
return ConfigTypes::WireGuard;
|
||||||
} else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) {
|
} else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) {
|
||||||
return ConfigTypes::Xray;
|
return ConfigTypes::Xray;
|
||||||
|
} else if (config.contains(openVpnConfigPatternCli)
|
||||||
|
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
|
||||||
|
return ConfigTypes::OpenVpn;
|
||||||
}
|
}
|
||||||
return ConfigTypes::Invalid;
|
return ConfigTypes::Invalid;
|
||||||
}
|
}
|
||||||
@@ -97,6 +94,8 @@ bool ImportController::extractConfigFromFile(const QString &fileName)
|
|||||||
|
|
||||||
bool ImportController::extractConfigFromData(QString data)
|
bool ImportController::extractConfigFromData(QString data)
|
||||||
{
|
{
|
||||||
|
m_maliciousWarningText.clear();
|
||||||
|
|
||||||
QString config = data;
|
QString config = data;
|
||||||
QString prefix;
|
QString prefix;
|
||||||
QString errormsg;
|
QString errormsg;
|
||||||
@@ -345,7 +344,7 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data)
|
|||||||
arr.push_back(containers);
|
arr.push_back(containers);
|
||||||
|
|
||||||
QString hostName;
|
QString hostName;
|
||||||
const static QRegularExpression hostNameRegExp("remote (.*) [0-9]*");
|
const static QRegularExpression hostNameRegExp("remote\\s+([^\\s]+)");
|
||||||
QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data);
|
QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data);
|
||||||
if (hostNameMatch.hasMatch()) {
|
if (hostNameMatch.hasMatch()) {
|
||||||
hostName = hostNameMatch.captured(1);
|
hostName = hostNameMatch.captured(1);
|
||||||
@@ -661,6 +660,7 @@ void ImportController::checkForMaliciousStrings(const QJsonObject &serverConfig)
|
|||||||
if ((containerName == ContainerProps::containerToString(DockerContainer::OpenVpn))
|
if ((containerName == ContainerProps::containerToString(DockerContainer::OpenVpn))
|
||||||
|| (containerName == ContainerProps::containerToString(DockerContainer::Cloak))
|
|| (containerName == ContainerProps::containerToString(DockerContainer::Cloak))
|
||||||
|| (containerName == ContainerProps::containerToString(DockerContainer::ShadowSocks))) {
|
|| (containerName == ContainerProps::containerToString(DockerContainer::ShadowSocks))) {
|
||||||
|
|
||||||
QString protocolConfig =
|
QString protocolConfig =
|
||||||
containerConfig[ProtocolProps::protoToString(Proto::OpenVpn)].toObject()[config_key::last_config].toString();
|
containerConfig[ProtocolProps::protoToString(Proto::OpenVpn)].toObject()[config_key::last_config].toString();
|
||||||
QString protocolConfigJson = QJsonDocument::fromJson(protocolConfig.toUtf8()).object()[config_key::config].toString();
|
QString protocolConfigJson = QJsonDocument::fromJson(protocolConfig.toUtf8()).object()[config_key::config].toString();
|
||||||
@@ -682,8 +682,11 @@ void ImportController::checkForMaliciousStrings(const QJsonObject &serverConfig)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_maliciousWarningText = tr("This configuration contains an OpenVPN setup. OpenVPN configurations can include malicious "
|
||||||
|
"scripts, so only add it if you fully trust the provider of this config. ");
|
||||||
|
|
||||||
if (maliciousStrings.size() >= dangerousTagsMaxCount) {
|
if (maliciousStrings.size() >= dangerousTagsMaxCount) {
|
||||||
m_maliciousWarningText = tr("In the imported configuration, potentially dangerous lines were found:");
|
m_maliciousWarningText.push_back(tr("<br>In the imported configuration, potentially dangerous lines were found:"));
|
||||||
for (const auto &string : maliciousStrings) {
|
for (const auto &string : maliciousStrings) {
|
||||||
m_maliciousWarningText.push_back(QString("<br><i>%1</i>").arg(string));
|
m_maliciousWarningText.push_back(QString("<br><i>%1</i>").arg(string));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ namespace PageLoader
|
|||||||
PageSettingsApiSupport,
|
PageSettingsApiSupport,
|
||||||
PageSettingsApiInstructions,
|
PageSettingsApiInstructions,
|
||||||
PageSettingsApiNativeConfigs,
|
PageSettingsApiNativeConfigs,
|
||||||
|
PageSettingsApiDevices,
|
||||||
|
|
||||||
PageServiceSftpSettings,
|
PageServiceSftpSettings,
|
||||||
PageServiceTorWebsiteSettings,
|
PageServiceTorWebsiteSettings,
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ void SitesController::addSite(QString hostname)
|
|||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
|
||||||
Q_ARG(QStringList, QStringList() << hostname));
|
Q_ARG(QStringList, QStringList() << hostname));
|
||||||
}
|
}
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) {
|
const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) {
|
||||||
@@ -75,7 +74,6 @@ void SitesController::removeSite(int index)
|
|||||||
|
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection,
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection,
|
||||||
Q_ARG(QStringList, QStringList() << hostname));
|
Q_ARG(QStringList, QStringList() << hostname));
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection);
|
|
||||||
|
|
||||||
emit finished(tr("Site removed: %1").arg(hostname));
|
emit finished(tr("Site removed: %1").arg(hostname));
|
||||||
}
|
}
|
||||||
@@ -124,7 +122,6 @@ void SitesController::importSites(const QString &fileName, bool replaceExisting)
|
|||||||
m_sitesModel->addSites(sites, replaceExisting);
|
m_sitesModel->addSites(sites, replaceExisting);
|
||||||
|
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips));
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips));
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection);
|
|
||||||
|
|
||||||
emit finished(tr("Import completed"));
|
emit finished(tr("Import completed"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
case ServiceDescriptionRole: {
|
case ServiceDescriptionRole: {
|
||||||
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) {
|
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) {
|
||||||
return tr("Classic VPN for comfortable work, downloading large files and watching videos. Works for any sites. Speed up to 200 "
|
return tr("Classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online resources. "
|
||||||
"Mb/s");
|
"Speeds up to 200 Mbps");
|
||||||
} else if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
} else if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
||||||
return tr("Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and "
|
return tr("Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and "
|
||||||
"more. YouTube is not included in the free plan.");
|
"more. YouTube is not included in the free plan.");
|
||||||
@@ -58,6 +58,19 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
|||||||
case IsComponentVisibleRole: {
|
case IsComponentVisibleRole: {
|
||||||
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2;
|
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2;
|
||||||
}
|
}
|
||||||
|
case HasExpiredWorkerRole: {
|
||||||
|
for (int i = 0; i < m_issuedConfigsInfo.size(); i++) {
|
||||||
|
QJsonObject issuedConfigObject = m_issuedConfigsInfo.at(i).toObject();
|
||||||
|
|
||||||
|
auto lastDownloaded = QDateTime::fromString(issuedConfigObject.value(apiDefs::key::lastDownloaded).toString());
|
||||||
|
auto workerLastUpdated = QDateTime::fromString(issuedConfigObject.value(apiDefs::key::workerLastUpdated).toString());
|
||||||
|
|
||||||
|
if (lastDownloaded < workerLastUpdated) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
@@ -124,6 +137,7 @@ QHash<int, QByteArray> ApiAccountInfoModel::roleNames() const
|
|||||||
roles[ConnectedDevicesRole] = "connectedDevices";
|
roles[ConnectedDevicesRole] = "connectedDevices";
|
||||||
roles[ServiceDescriptionRole] = "serviceDescription";
|
roles[ServiceDescriptionRole] = "serviceDescription";
|
||||||
roles[IsComponentVisibleRole] = "isComponentVisible";
|
roles[IsComponentVisibleRole] = "isComponentVisible";
|
||||||
|
roles[HasExpiredWorkerRole] = "hasExpiredWorker";
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ public:
|
|||||||
ConnectedDevicesRole,
|
ConnectedDevicesRole,
|
||||||
ServiceDescriptionRole,
|
ServiceDescriptionRole,
|
||||||
EndDateRole,
|
EndDateRole,
|
||||||
IsComponentVisibleRole
|
IsComponentVisibleRole,
|
||||||
|
HasExpiredWorkerRole
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ApiAccountInfoModel(QObject *parent = nullptr);
|
explicit ApiAccountInfoModel(QObject *parent = nullptr);
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const
|
|||||||
case IsIssuedRole: {
|
case IsIssuedRole: {
|
||||||
return isIssued;
|
return isIssued;
|
||||||
}
|
}
|
||||||
|
case IsWorkerExpiredRole: {
|
||||||
|
return issuedConfigInfo.lastDownloaded < issuedConfigInfo.workerLastUpdated;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
@@ -114,5 +117,6 @@ QHash<int, QByteArray> ApiCountryModel::roleNames() const
|
|||||||
roles[CountryCodeRole] = "countryCode";
|
roles[CountryCodeRole] = "countryCode";
|
||||||
roles[CountryImageCodeRole] = "countryImageCode";
|
roles[CountryImageCodeRole] = "countryImageCode";
|
||||||
roles[IsIssuedRole] = "isIssued";
|
roles[IsIssuedRole] = "isIssued";
|
||||||
|
roles[IsWorkerExpiredRole] = "isWorkerExpired";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ public:
|
|||||||
CountryNameRole = Qt::UserRole + 1,
|
CountryNameRole = Qt::UserRole + 1,
|
||||||
CountryCodeRole,
|
CountryCodeRole,
|
||||||
CountryImageCodeRole,
|
CountryImageCodeRole,
|
||||||
IsIssuedRole
|
IsIssuedRole,
|
||||||
|
IsWorkerExpiredRole
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ApiCountryModel(QObject *parent = nullptr);
|
explicit ApiCountryModel(QObject *parent = nullptr);
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#include "apiDevicesModel.h"
|
||||||
|
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
#include "core/api/apiDefs.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
Logger logger("ApiDevicesModel");
|
||||||
|
|
||||||
|
constexpr QLatin1String gatewayAccount("gateway_account");
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiDevicesModel::ApiDevicesModel(std::shared_ptr<Settings> settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int ApiDevicesModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
|
return m_issuedConfigs.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ApiDevicesModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount()))
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
IssuedConfigInfo issuedConfigInfo = m_issuedConfigs.at(index.row());
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case OsVersionRole: {
|
||||||
|
return issuedConfigInfo.osVersion;
|
||||||
|
}
|
||||||
|
case SupportTagRole: {
|
||||||
|
return issuedConfigInfo.installationUuid;
|
||||||
|
}
|
||||||
|
case CountryCodeRole: {
|
||||||
|
return issuedConfigInfo.countryCode;
|
||||||
|
}
|
||||||
|
case LastUpdateRole: {
|
||||||
|
return QDateTime::fromString(issuedConfigInfo.lastDownloaded, Qt::ISODate).toLocalTime().toString("d MMM yyyy");
|
||||||
|
}
|
||||||
|
case IsCurrentDeviceRole: {
|
||||||
|
return issuedConfigInfo.installationUuid == m_settings->getInstallationUuid(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiDevicesModel::updateModel(const QJsonArray &issuedConfigs)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
|
||||||
|
m_issuedConfigs.clear();
|
||||||
|
for (int i = 0; i < issuedConfigs.size(); i++) {
|
||||||
|
IssuedConfigInfo issuedConfigInfo;
|
||||||
|
QJsonObject issuedConfigObject = issuedConfigs.at(i).toObject();
|
||||||
|
|
||||||
|
if (issuedConfigObject.value(apiDefs::key::sourceType).toString() != gatewayAccount) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
issuedConfigInfo.installationUuid = issuedConfigObject.value(apiDefs::key::installationUuid).toString();
|
||||||
|
issuedConfigInfo.workerLastUpdated = issuedConfigObject.value(apiDefs::key::workerLastUpdated).toString();
|
||||||
|
issuedConfigInfo.lastDownloaded = issuedConfigObject.value(apiDefs::key::lastDownloaded).toString();
|
||||||
|
issuedConfigInfo.sourceType = issuedConfigObject.value(apiDefs::key::sourceType).toString();
|
||||||
|
issuedConfigInfo.osVersion = issuedConfigObject.value(apiDefs::key::osVersion).toString();
|
||||||
|
|
||||||
|
issuedConfigInfo.countryName = issuedConfigObject.value(apiDefs::key::serverCountryName).toString();
|
||||||
|
issuedConfigInfo.countryCode = issuedConfigObject.value(apiDefs::key::serverCountryCode).toString();
|
||||||
|
|
||||||
|
m_issuedConfigs.push_back(issuedConfigInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> ApiDevicesModel::roleNames() const
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> roles;
|
||||||
|
roles[OsVersionRole] = "osVersion";
|
||||||
|
roles[SupportTagRole] = "supportTag";
|
||||||
|
roles[CountryCodeRole] = "countryCode";
|
||||||
|
roles[LastUpdateRole] = "lastUpdate";
|
||||||
|
roles[IsCurrentDeviceRole] = "isCurrentDevice";
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#ifndef APIDEVICESMODEL_H
|
||||||
|
#define APIDEVICESMODEL_H
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
class ApiDevicesModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Roles {
|
||||||
|
OsVersionRole = Qt::UserRole + 1,
|
||||||
|
SupportTagRole,
|
||||||
|
CountryCodeRole,
|
||||||
|
LastUpdateRole,
|
||||||
|
IsCurrentDeviceRole
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit ApiDevicesModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void updateModel(const QJsonArray &issuedConfigs);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct IssuedConfigInfo
|
||||||
|
{
|
||||||
|
QString installationUuid;
|
||||||
|
QString workerLastUpdated;
|
||||||
|
QString lastDownloaded;
|
||||||
|
QString sourceType;
|
||||||
|
QString osVersion;
|
||||||
|
|
||||||
|
QString countryName;
|
||||||
|
QString countryCode;
|
||||||
|
};
|
||||||
|
|
||||||
|
QVector<IssuedConfigInfo> m_issuedConfigs;
|
||||||
|
|
||||||
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
};
|
||||||
|
#endif // APIDEVICESMODEL_H
|
||||||
@@ -65,8 +65,8 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
|||||||
case CardDescriptionRole: {
|
case CardDescriptionRole: {
|
||||||
auto speed = apiServiceData.serviceInfo.speed;
|
auto speed = apiServiceData.serviceInfo.speed;
|
||||||
if (serviceType == serviceType::amneziaPremium) {
|
if (serviceType == serviceType::amneziaPremium) {
|
||||||
return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. "
|
return tr("Amnezia Premium is classic VPN for seamless work, downloading large files, and watching videos. "
|
||||||
"Works for any sites with no restrictions. Speed up to %1 MBit/s. Unlimited traffic.")
|
"Access all websites and online resources. Speeds up to %1 Mbps.")
|
||||||
.arg(speed);
|
.arg(speed);
|
||||||
} else if (serviceType == serviceType::amneziaFree) {
|
} else if (serviceType == serviceType::amneziaFree) {
|
||||||
QString description = tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
|
QString description = tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
|
||||||
@@ -79,8 +79,8 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
case ServiceDescriptionRole: {
|
case ServiceDescriptionRole: {
|
||||||
if (serviceType == serviceType::amneziaPremium) {
|
if (serviceType == serviceType::amneziaPremium) {
|
||||||
return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. "
|
return tr("Amnezia Premium is classic VPN for for seamless work, downloading large files, and watching videos. "
|
||||||
"Works for any sites with no restrictions.");
|
"Access all websites and online resources.");
|
||||||
} else {
|
} else {
|
||||||
return tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
|
return tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
#include "languageModel.h"
|
#include "languageModel.h"
|
||||||
|
|
||||||
LanguageModel::LanguageModel(std::shared_ptr<Settings> settings, QObject *parent)
|
LanguageModel::LanguageModel(std::shared_ptr<Settings> settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent)
|
||||||
: m_settings(settings), QAbstractListModel(parent)
|
|
||||||
{
|
{
|
||||||
QMetaEnum metaEnum = QMetaEnum::fromType<LanguageSettings::AvailableLanguageEnum>();
|
QMetaEnum metaEnum = QMetaEnum::fromType<LanguageSettings::AvailableLanguageEnum>();
|
||||||
for (int i = 0; i < metaEnum.keyCount(); i++) {
|
for (int i = 0; i < metaEnum.keyCount(); i++) {
|
||||||
m_availableLanguages.push_back(
|
m_availableLanguages.push_back(LanguageModelData { getLocalLanguageName(static_cast<LanguageSettings::AvailableLanguageEnum>(i)),
|
||||||
LanguageModelData {getLocalLanguageName(static_cast<LanguageSettings::AvailableLanguageEnum>(i)),
|
static_cast<LanguageSettings::AvailableLanguageEnum>(i) });
|
||||||
static_cast<LanguageSettings::AvailableLanguageEnum>(i) });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +48,7 @@ QString LanguageModel::getLocalLanguageName(const LanguageSettings::AvailableLan
|
|||||||
case LanguageSettings::AvailableLanguageEnum::Burmese: strLanguage = "မြန်မာဘာသာ"; break;
|
case LanguageSettings::AvailableLanguageEnum::Burmese: strLanguage = "မြန်မာဘာသာ"; break;
|
||||||
case LanguageSettings::AvailableLanguageEnum::Urdu: strLanguage = "اُرْدُوْ"; break;
|
case LanguageSettings::AvailableLanguageEnum::Urdu: strLanguage = "اُرْدُوْ"; break;
|
||||||
case LanguageSettings::AvailableLanguageEnum::Hindi: strLanguage = "हिन्दी"; break;
|
case LanguageSettings::AvailableLanguageEnum::Hindi: strLanguage = "हिन्दी"; break;
|
||||||
default:
|
default: break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return strLanguage;
|
return strLanguage;
|
||||||
@@ -104,11 +101,12 @@ QString LanguageModel::getCurrentLanguageName()
|
|||||||
return m_availableLanguages[getCurrentLanguageIndex()].name;
|
return m_availableLanguages[getCurrentLanguageIndex()].name;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString LanguageModel::getCurrentSiteUrl()
|
QString LanguageModel::getCurrentSiteUrl(const QString &path)
|
||||||
{
|
{
|
||||||
auto language = static_cast<LanguageSettings::AvailableLanguageEnum>(getCurrentLanguageIndex());
|
auto language = static_cast<LanguageSettings::AvailableLanguageEnum>(getCurrentLanguageIndex());
|
||||||
switch (language) {
|
switch (language) {
|
||||||
case LanguageSettings::AvailableLanguageEnum::Russian: return "https://storage.googleapis.com/amnezia/amnezia.org";
|
case LanguageSettings::AvailableLanguageEnum::Russian:
|
||||||
default: return "https://amnezia.org";
|
return "https://storage.googleapis.com/amnezia/amnezia.org" + (path.isEmpty() ? "" : (QString("?m-path=/%1").arg(path)));
|
||||||
|
default: return QString("https://amnezia.org") + (path.isEmpty() ? "" : (QString("/%1").arg(path)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ public slots:
|
|||||||
int getCurrentLanguageIndex();
|
int getCurrentLanguageIndex();
|
||||||
int getLineHeightAppend();
|
int getLineHeightAppend();
|
||||||
QString getCurrentLanguageName();
|
QString getCurrentLanguageName();
|
||||||
QString getCurrentSiteUrl();
|
QString getCurrentSiteUrl(const QString &path = "");
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void updateTranslations(const QLocale &locale);
|
void updateTranslations(const QLocale &locale);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ Rectangle {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
onClicked: function() {
|
onClicked: function() {
|
||||||
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/premium")
|
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl("premium"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ Rectangle {
|
|||||||
Layout.rightMargin: 10
|
Layout.rightMargin: 10
|
||||||
Layout.leftMargin: 10
|
Layout.leftMargin: 10
|
||||||
|
|
||||||
text: qsTr("Amnezia Premium - for access to any website")
|
text: qsTr("Amnezia Premium - for access to all websites and online resources")
|
||||||
color: AmneziaStyle.color.pearlGray
|
color: AmneziaStyle.color.pearlGray
|
||||||
|
|
||||||
lineHeight: 18
|
lineHeight: 18
|
||||||
|
|||||||
@@ -27,5 +27,6 @@ QtObject {
|
|||||||
readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8)
|
readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8)
|
||||||
readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
|
readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
|
||||||
readonly property color pearlGray: '#EAEAEC'
|
readonly property color pearlGray: '#EAEAEC'
|
||||||
|
readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ PageType {
|
|||||||
text: qsTr("Privacy Policy")
|
text: qsTr("Privacy Policy")
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/policy")
|
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl("policy"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ PageType {
|
|||||||
actionButtonImage: "qrc:/images/controls/settings.svg"
|
actionButtonImage: "qrc:/images/controls/settings.svg"
|
||||||
|
|
||||||
headerText: root.processedServer.name
|
headerText: root.processedServer.name
|
||||||
descriptionText: qsTr("Locations for connection")
|
descriptionText: qsTr("Location for connection")
|
||||||
|
|
||||||
actionButtonFunction: function() {
|
actionButtonFunction: function() {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Dialogs
|
||||||
|
|
||||||
|
import QtCore
|
||||||
|
|
||||||
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
|
import PageEnum 1.0
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "./"
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
import "../Config"
|
||||||
|
import "../Components"
|
||||||
|
|
||||||
|
PageType {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
ListViewType {
|
||||||
|
id: listView
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: 20
|
||||||
|
anchors.bottomMargin: 24
|
||||||
|
|
||||||
|
model: ApiDevicesModel
|
||||||
|
|
||||||
|
header: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
|
BackButtonType {
|
||||||
|
id: backButton
|
||||||
|
}
|
||||||
|
|
||||||
|
HeaderType {
|
||||||
|
id: header
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
|
headerText: qsTr("Active Devices")
|
||||||
|
descriptionText: qsTr("Manage currently connected devices")
|
||||||
|
}
|
||||||
|
|
||||||
|
WarningType {
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
textString: qsTr("You can find the identifier on the Support tab or, for older versions of the app, "
|
||||||
|
+ "by tapping '+' and then the three dots at the top of the page.")
|
||||||
|
|
||||||
|
iconPath: "qrc:/images/controls/alert-circle.svg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 6
|
||||||
|
|
||||||
|
text: osVersion + (isCurrentDevice ? qsTr(" (current device)") : "")
|
||||||
|
descriptionText: qsTr("Support tag: ") + "\n" + supportTag + "\n" + qsTr("Last updated: ") + lastUpdate
|
||||||
|
rightImageSource: "qrc:/images/controls/trash.svg"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
if (isCurrentDevice && ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
||||||
|
PageController.showNotificationMessage(qsTr("Cannot unlink device during active connection"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var headerText = qsTr("Are you sure you want to unlink this device?")
|
||||||
|
var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect.")
|
||||||
|
var yesButtonText = qsTr("Continue")
|
||||||
|
var noButtonText = qsTr("Cancel")
|
||||||
|
|
||||||
|
var yesButtonFunction = function() {
|
||||||
|
Qt.callLater(deactivateExternalDevice, supportTag, countryCode)
|
||||||
|
}
|
||||||
|
var noButtonFunction = function() {
|
||||||
|
}
|
||||||
|
|
||||||
|
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deactivateExternalDevice(supportTag, countryCode) {
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
if (ApiConfigsController.deactivateExternalDevice(supportTag, countryCode)) {
|
||||||
|
ApiSettingsController.getAccountInfo(true)
|
||||||
|
}
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,7 +99,7 @@ PageType {
|
|||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("How to connect on another device")
|
headerText: qsTr("How to connect on another device")
|
||||||
descriptionText: qsTr("Instructions on the Amnezia website")
|
descriptionText: qsTr("Setup guides on the Amnezia website")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +107,6 @@ PageType {
|
|||||||
width: listView.width
|
width: listView.width
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
id: telegramButton
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 6
|
Layout.topMargin: 6
|
||||||
|
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Configuration files")
|
headerText: qsTr("Configuration Files")
|
||||||
descriptionText: qsTr("To connect a router or AmneziaWG application")
|
descriptionText: qsTr("For router setup or the AmneziaWG app")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +58,9 @@ PageType {
|
|||||||
Layout.topMargin: 6
|
Layout.topMargin: 6
|
||||||
|
|
||||||
text: countryName
|
text: countryName
|
||||||
|
descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : ""
|
||||||
|
descriptionColor: AmneziaStyle.color.vibrantRed
|
||||||
|
|
||||||
leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
|
leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
|
||||||
rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg"
|
rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg"
|
||||||
|
|
||||||
@@ -120,13 +123,13 @@ PageType {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: 16
|
Layout.margins: 16
|
||||||
|
|
||||||
headerText: qsTr("Configuration file ") + moreOptionsDrawer.countryName
|
headerText: moreOptionsDrawer.countryName + qsTr(" configuration file")
|
||||||
}
|
}
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
text: qsTr("Create a new")
|
text: qsTr("Generate a new configuration file")
|
||||||
descriptionText: qsTr("The previously created one will stop working")
|
descriptionText: qsTr("The previously created one will stop working")
|
||||||
|
|
||||||
clickedFunction: function() {
|
clickedFunction: function() {
|
||||||
@@ -190,9 +193,15 @@ PageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showQuestion(isConfigIssue, countryCode, countryName) {
|
function showQuestion(isConfigIssue, countryCode, countryName) {
|
||||||
var headerText = qsTr("Revoke the actual %1 configuration file?").arg(countryName)
|
var headerText
|
||||||
var descriptionText = qsTr("The previously created file will no longer be valid. It will not be possible to connect using it.")
|
if (isConfigIssue) {
|
||||||
var yesButtonText = qsTr("Continue")
|
headerText = qsTr("Generate a new %1 configuration file?").arg(countryName)
|
||||||
|
} else {
|
||||||
|
headerText = qsTr("Revoke the current %1 configuration file?").arg(countryName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var descriptionText = qsTr("Your previous configuration file will no longer work, and it will not be possible to connect using it")
|
||||||
|
var yesButtonText = isConfigIssue ? qsTr("Download") : qsTr("Continue")
|
||||||
var noButtonText = qsTr("Cancel")
|
var noButtonText = qsTr("Cancel")
|
||||||
|
|
||||||
var yesButtonFunction = function() {
|
var yesButtonFunction = function() {
|
||||||
|
|||||||
@@ -26,15 +26,15 @@ PageType {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: statusObject
|
id: statusObject
|
||||||
|
|
||||||
readonly property string title: qsTr("Subscription status")
|
readonly property string title: qsTr("Subscription Status")
|
||||||
readonly property string contentKey: "subscriptionStatus"
|
readonly property string contentKey: "subscriptionStatus"
|
||||||
readonly property string objectImageSource: "qrc:/images/controls/map-pin.svg"
|
readonly property string objectImageSource: "qrc:/images/controls/info.svg"
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: endDateObject
|
id: endDateObject
|
||||||
|
|
||||||
readonly property string title: qsTr("Valid until")
|
readonly property string title: qsTr("Valid Until")
|
||||||
readonly property string contentKey: "endDate"
|
readonly property string contentKey: "endDate"
|
||||||
readonly property string objectImageSource: "qrc:/images/controls/history.svg"
|
readonly property string objectImageSource: "qrc:/images/controls/history.svg"
|
||||||
}
|
}
|
||||||
@@ -42,9 +42,9 @@ PageType {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: deviceCountObject
|
id: deviceCountObject
|
||||||
|
|
||||||
readonly property string title: qsTr("Connected devices")
|
readonly property string title: qsTr("Active Connections")
|
||||||
readonly property string contentKey: "connectedDevices"
|
readonly property string contentKey: "connectedDevices"
|
||||||
readonly property string objectImageSource: "qrc:/images/controls/gauge.svg"
|
readonly property string objectImageSource: "qrc:/images/controls/monitor.svg"
|
||||||
}
|
}
|
||||||
|
|
||||||
property var processedServer
|
property var processedServer
|
||||||
@@ -158,15 +158,32 @@ PageType {
|
|||||||
|
|
||||||
readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible")
|
readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible")
|
||||||
|
|
||||||
|
WarningType {
|
||||||
|
id: warning
|
||||||
|
|
||||||
|
Layout.topMargin: 32
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
backGroundColor: AmneziaStyle.color.translucentRichBrown
|
||||||
|
|
||||||
|
textString: qsTr("Configurations have been updated for some countries. Download and install the updated configuration files")
|
||||||
|
|
||||||
|
iconPath: "qrc:/images/controls/alert-circle.svg"
|
||||||
|
|
||||||
|
visible: ApiAccountInfoModel.data("hasExpiredWorker")
|
||||||
|
}
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
id: vpnKey
|
id: vpnKey
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 32
|
Layout.topMargin: warning.visible ? 16 : 32
|
||||||
|
|
||||||
visible: false //footer.isVisibleForAmneziaFree
|
visible: false //footer.isVisibleForAmneziaFree
|
||||||
|
|
||||||
text: qsTr("Subscription key")
|
text: qsTr("Subscription Key")
|
||||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||||
|
|
||||||
clickedFunction: function() {
|
clickedFunction: function() {
|
||||||
@@ -174,7 +191,7 @@ PageType {
|
|||||||
|
|
||||||
shareConnectionDrawer.openTriggered()
|
shareConnectionDrawer.openTriggered()
|
||||||
shareConnectionDrawer.isSelfHostedConfig = false;
|
shareConnectionDrawer.isSelfHostedConfig = false;
|
||||||
shareConnectionDrawer.shareButtonText = qsTr("Save VPN key to file")
|
shareConnectionDrawer.shareButtonText = qsTr("Save VPN key as a file")
|
||||||
shareConnectionDrawer.copyButtonText = qsTr("Copy VPN key")
|
shareConnectionDrawer.copyButtonText = qsTr("Copy VPN key")
|
||||||
|
|
||||||
|
|
||||||
@@ -192,13 +209,13 @@ PageType {
|
|||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 32
|
Layout.topMargin: warning.visible ? 16 : 32
|
||||||
|
|
||||||
visible: footer.isVisibleForAmneziaFree
|
visible: footer.isVisibleForAmneziaFree
|
||||||
|
|
||||||
text: qsTr("Configuration files")
|
text: qsTr("Configuration Files")
|
||||||
|
|
||||||
descriptionText: qsTr("To connect a router or AmneziaWG application")
|
descriptionText: qsTr("Manage configuration files")
|
||||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||||
|
|
||||||
clickedFunction: function() {
|
clickedFunction: function() {
|
||||||
@@ -211,6 +228,26 @@ PageType {
|
|||||||
visible: footer.isVisibleForAmneziaFree
|
visible: footer.isVisibleForAmneziaFree
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
visible: footer.isVisibleForAmneziaFree
|
||||||
|
|
||||||
|
text: qsTr("Active Devices")
|
||||||
|
|
||||||
|
descriptionText: qsTr("Manage currently connected devices")
|
||||||
|
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
ApiSettingsController.updateApiDevicesModel()
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsApiDevices)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {
|
||||||
|
visible: footer.isVisibleForAmneziaFree
|
||||||
|
}
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: footer.isVisibleForAmneziaFree ? 0 : 32
|
Layout.topMargin: footer.isVisibleForAmneziaFree ? 0 : 32
|
||||||
@@ -228,6 +265,8 @@ PageType {
|
|||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
visible: footer.isVisibleForAmneziaFree
|
||||||
|
|
||||||
text: qsTr("How to connect on another device")
|
text: qsTr("How to connect on another device")
|
||||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||||
|
|
||||||
@@ -236,7 +275,9 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DividerType {}
|
DividerType {
|
||||||
|
visible: footer.isVisibleForAmneziaFree
|
||||||
|
}
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
id: resetButton
|
id: resetButton
|
||||||
@@ -288,16 +329,17 @@ PageType {
|
|||||||
pressedColor: AmneziaStyle.color.sheerWhite
|
pressedColor: AmneziaStyle.color.sheerWhite
|
||||||
textColor: AmneziaStyle.color.vibrantRed
|
textColor: AmneziaStyle.color.vibrantRed
|
||||||
|
|
||||||
text: qsTr("Deactivate the subscription on this device")
|
text: qsTr("Unlink this device")
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
var headerText = qsTr("Deactivate the subscription on this device?")
|
var headerText = qsTr("Are you sure you want to unlink this device?")
|
||||||
|
var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect.")
|
||||||
var yesButtonText = qsTr("Continue")
|
var yesButtonText = qsTr("Continue")
|
||||||
var noButtonText = qsTr("Cancel")
|
var noButtonText = qsTr("Cancel")
|
||||||
|
|
||||||
var yesButtonFunction = function() {
|
var yesButtonFunction = function() {
|
||||||
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
||||||
PageController.showNotificationMessage(qsTr("The next time the “Connect” button is pressed, the device will be activated again"))
|
PageController.showNotificationMessage(qsTr("Cannot unlink device during active connection"))
|
||||||
} else {
|
} else {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
if (ApiConfigsController.deactivateDevice()) {
|
if (ApiConfigsController.deactivateDevice()) {
|
||||||
@@ -309,7 +351,7 @@ PageType {
|
|||||||
var noButtonFunction = function() {
|
var noButtonFunction = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,92 +16,110 @@ import "../Components"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
ColumnLayout {
|
QtObject {
|
||||||
id: backButtonLayout
|
id: telegram
|
||||||
|
|
||||||
anchors.top: parent.top
|
readonly property string title: qsTr("Telegram")
|
||||||
anchors.left: parent.left
|
readonly property string description: "@" + ApiAccountInfoModel.getTelegramBotLink()
|
||||||
anchors.right: parent.right
|
readonly property string link: "https://t.me/" + ApiAccountInfoModel.getTelegramBotLink()
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: techSupport
|
||||||
|
|
||||||
|
readonly property string title: qsTr("Email")
|
||||||
|
readonly property string description: qsTr("support@amnezia.org")
|
||||||
|
readonly property string link: "mailto:support@amnezia.org"
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: paymentSupport
|
||||||
|
|
||||||
|
readonly property string title: qsTr("Email Billing & Orders")
|
||||||
|
readonly property string description: qsTr("help@vpnpay.io")
|
||||||
|
readonly property string link: "mailto:help@vpnpay.io"
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: site
|
||||||
|
|
||||||
|
readonly property string title: qsTr("Website")
|
||||||
|
readonly property string description: qsTr("amnezia.org")
|
||||||
|
readonly property string link: LanguageModel.getCurrentSiteUrl()
|
||||||
|
}
|
||||||
|
|
||||||
|
property list<QtObject> supportModel: [
|
||||||
|
telegram,
|
||||||
|
techSupport,
|
||||||
|
paymentSupport,
|
||||||
|
site
|
||||||
|
]
|
||||||
|
|
||||||
|
ListViewType {
|
||||||
|
id: listView
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20
|
||||||
|
anchors.bottomMargin: 24
|
||||||
|
|
||||||
BackButtonType {
|
model: supportModel
|
||||||
id: backButton
|
|
||||||
}
|
|
||||||
|
|
||||||
|
header: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
HeaderType {
|
BackButtonType {
|
||||||
id: header
|
id: backButton
|
||||||
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
HeaderType {
|
||||||
Layout.rightMargin: 16
|
id: header
|
||||||
Layout.leftMargin: 16
|
|
||||||
|
|
||||||
headerText: qsTr("Support")
|
Layout.fillWidth: true
|
||||||
descriptionText: qsTr("Our technical support specialists are ready to help you at any time")
|
Layout.rightMargin: 16
|
||||||
}
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
LabelWithButtonType {
|
headerText: qsTr("Support")
|
||||||
Layout.fillWidth: true
|
descriptionText: qsTr("Our technical support specialists are available to assist you at any time")
|
||||||
|
|
||||||
readonly property string telegramBotLink: ApiAccountInfoModel.getTelegramBotLink()
|
|
||||||
|
|
||||||
text: qsTr("Telegram")
|
|
||||||
descriptionText: "@" + telegramBotLink
|
|
||||||
rightImageSource: "qrc:/images/controls/external-link.svg"
|
|
||||||
|
|
||||||
clickedFunction: function() {
|
|
||||||
Qt.openUrlExternally("https://t.me/" + telegramBotLink)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DividerType {}
|
delegate: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
text: title
|
||||||
text: qsTr("Mail")
|
descriptionText: description
|
||||||
descriptionText: qsTr("support@amnezia.org")
|
rightImageSource: "qrc:/images/controls/external-link.svg"
|
||||||
rightImageSource: "qrc:/images/controls/external-link.svg"
|
clickedFunction: function() {
|
||||||
|
Qt.openUrlExternally(link)
|
||||||
clickedFunction: function() {
|
}
|
||||||
Qt.openUrlExternally(qsTr("mailto:support@amnezia.org"))
|
|
||||||
}
|
}
|
||||||
|
DividerType {}
|
||||||
}
|
}
|
||||||
|
|
||||||
DividerType {}
|
|
||||||
|
|
||||||
LabelWithButtonType {
|
footer: ColumnLayout {
|
||||||
Layout.fillWidth: true
|
width: listView.width
|
||||||
|
|
||||||
text: qsTr("Site")
|
LabelWithButtonType {
|
||||||
descriptionText: qsTr("amnezia.org")
|
id: supportUuid
|
||||||
rightImageSource: "qrc:/images/controls/external-link.svg"
|
Layout.fillWidth: true
|
||||||
|
|
||||||
clickedFunction: function() {
|
text: qsTr("Support tag")
|
||||||
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl())
|
descriptionText: SettingsController.getInstallationUuid()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DividerType {}
|
descriptionOnTop: true
|
||||||
|
|
||||||
LabelWithButtonType {
|
rightImageSource: "qrc:/images/controls/copy.svg"
|
||||||
id: supportUuid
|
rightImageColor: AmneziaStyle.color.paleGray
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: qsTr("Support tag")
|
clickedFunction: function() {
|
||||||
descriptionText: SettingsController.getInstallationUuid()
|
GC.copyToClipBoard(descriptionText)
|
||||||
|
PageController.showNotificationMessage(qsTr("Copied"))
|
||||||
descriptionOnTop: true
|
if (!GC.isMobile()) {
|
||||||
|
this.rightButton.forceActiveFocus()
|
||||||
rightImageSource: "qrc:/images/controls/copy.svg"
|
}
|
||||||
rightImageColor: AmneziaStyle.color.paleGray
|
|
||||||
|
|
||||||
clickedFunction: function() {
|
|
||||||
GC.copyToClipBoard(descriptionText)
|
|
||||||
PageController.showNotificationMessage(qsTr("Copied"))
|
|
||||||
if (!GC.isMobile()) {
|
|
||||||
this.rightButton.forceActiveFocus()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ PageType {
|
|||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!checkable) {
|
if (!checkable) {
|
||||||
PageController.showNotificationMessage(qsTr("Cannot change killSwitch settings during active connection"))
|
PageController.showNotificationMessage(qsTr("Cannot change KillSwitch settings during active connection"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import QtQuick.Controls
|
|||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Dialogs
|
import QtQuick.Dialogs
|
||||||
|
|
||||||
|
import QtCore
|
||||||
|
|
||||||
import PageEnum 1.0
|
import PageEnum 1.0
|
||||||
import Style 1.0
|
import Style 1.0
|
||||||
|
|
||||||
@@ -101,6 +103,34 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("Export client logs")
|
||||||
|
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||||
|
|
||||||
|
visible: PageController.isStartPageVisible()
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
var fileName = ""
|
||||||
|
if (GC.isMobile()) {
|
||||||
|
fileName = "AmneziaVPN.log"
|
||||||
|
} else {
|
||||||
|
fileName = SystemController.getFileName(qsTr("Save"),
|
||||||
|
qsTr("Logs files (*.log)"),
|
||||||
|
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN",
|
||||||
|
true,
|
||||||
|
".log")
|
||||||
|
}
|
||||||
|
if (fileName !== "") {
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
SettingsController.exportLogsFile(fileName)
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
PageController.showNotificationMessage(qsTr("Logs file saved"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
id: supportUuid
|
id: supportUuid
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ PageType {
|
|||||||
leftImageSource: "qrc:/images/controls/help-circle.svg"
|
leftImageSource: "qrc:/images/controls/help-circle.svg"
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/starter-guide")
|
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl("starter-guide"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ PageType {
|
|||||||
height: containers.contentItem.height
|
height: containers.contentItem.height
|
||||||
spacing: 16
|
spacing: 16
|
||||||
|
|
||||||
currentIndex: 1
|
currentIndex: 0
|
||||||
clip: true
|
clip: true
|
||||||
interactive: false
|
interactive: false
|
||||||
model: proxyContainersModel
|
model: proxyContainersModel
|
||||||
|
|||||||
@@ -351,8 +351,10 @@ void VpnConnection::appendSplitTunnelingConfig()
|
|||||||
sitesJsonArray.append(site);
|
sitesJsonArray.append(site);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow traffic to Amnezia DNS
|
if (sitesJsonArray.isEmpty()) {
|
||||||
if (sitesRouteMode == Settings::VpnOnlyForwardSites) {
|
sitesRouteMode = Settings::RouteMode::VpnAllSites;
|
||||||
|
} else if (sitesRouteMode == Settings::VpnOnlyForwardSites) {
|
||||||
|
// Allow traffic to Amnezia DNS
|
||||||
sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns1).toString());
|
sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns1).toString());
|
||||||
sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns2).toString());
|
sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns2).toString());
|
||||||
}
|
}
|
||||||
@@ -371,6 +373,10 @@ void VpnConnection::appendSplitTunnelingConfig()
|
|||||||
for (const auto &app : apps) {
|
for (const auto &app : apps) {
|
||||||
appsJsonArray.append(app.appPath.isEmpty() ? app.packageName : app.appPath);
|
appsJsonArray.append(app.appPath.isEmpty() ? app.packageName : app.appPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appsJsonArray.isEmpty()) {
|
||||||
|
appsRouteMode = Settings::AppsRouteMode::VpnAllApps;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_vpnConfiguration.insert(config_key::appSplitTunnelType, appsRouteMode);
|
m_vpnConfiguration.insert(config_key::appSplitTunnelType, appsRouteMode);
|
||||||
|
|||||||
@@ -2,13 +2,33 @@
|
|||||||
|
|
||||||
APP_NAME=AmneziaVPN
|
APP_NAME=AmneziaVPN
|
||||||
PLIST_NAME=$APP_NAME.plist
|
PLIST_NAME=$APP_NAME.plist
|
||||||
LAUNCH_DAEMONS_PLIST_NAME=/Library/LaunchDaemons/$PLIST_NAME
|
LAUNCH_DAEMONS_PLIST_NAME="/Library/LaunchDaemons/$PLIST_NAME"
|
||||||
|
APP_PATH="/Applications/$APP_NAME.app"
|
||||||
|
USER_APP_SUPPORT="$HOME/Library/Application Support/$APP_NAME"
|
||||||
|
SYSTEM_APP_SUPPORT="/Library/Application Support/$APP_NAME"
|
||||||
|
LOG_FOLDER="/var/log/$APP_NAME"
|
||||||
|
CACHES_FOLDER="$HOME/Library/Caches/$APP_NAME"
|
||||||
|
|
||||||
if launchctl list "$APP_NAME-service" &> /dev/null; then
|
# Stop the running service if it exists
|
||||||
launchctl unload $LAUNCH_DAEMONS_PLIST_NAME
|
if pgrep -x "${APP_NAME}-service" > /dev/null; then
|
||||||
rm -f $LAUNCH_DAEMONS_PLIST_NAME
|
sudo killall -9 "${APP_NAME}-service"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -rf "$HOME/Library/Application Support/$APP_NAME"
|
# Unload the service if loaded and remove its plist file regardless
|
||||||
rm -rf /var/log/$APP_NAME
|
if launchctl list "${APP_NAME}-service" &> /dev/null; then
|
||||||
rm -rf /Applications/$APP_NAME.app/Contents
|
sudo launchctl unload "$LAUNCH_DAEMONS_PLIST_NAME"
|
||||||
|
fi
|
||||||
|
sudo rm -f "$LAUNCH_DAEMONS_PLIST_NAME"
|
||||||
|
|
||||||
|
# Remove the entire application bundle
|
||||||
|
sudo rm -rf "$APP_PATH"
|
||||||
|
|
||||||
|
# Remove Application Support folders (user and system, if they exist)
|
||||||
|
rm -rf "$USER_APP_SUPPORT"
|
||||||
|
sudo rm -rf "$SYSTEM_APP_SUPPORT"
|
||||||
|
|
||||||
|
# Remove the log folder
|
||||||
|
sudo rm -rf "$LOG_FOLDER"
|
||||||
|
|
||||||
|
# Remove any caches left behind
|
||||||
|
rm -rf "$CACHES_FOLDER"
|
||||||
|
|||||||
Reference in New Issue
Block a user