mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-17 16:26:08 +03:00
Compare commits
38 Commits
bugfix/mob
...
update_ser
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d61e2bc8f6 | ||
|
|
9e1298550f | ||
|
|
805bc5fb61 | ||
|
|
050066132b | ||
|
|
2a6e6a1e24 | ||
|
|
92689d084c | ||
|
|
00f314039d | ||
|
|
fcb75e837d | ||
|
|
9fbea76b74 | ||
|
|
b3ff120bcf | ||
|
|
9dea98f020 | ||
|
|
c4701d4e7a | ||
|
|
48903ca3a1 | ||
|
|
0c9fd4aef4 | ||
|
|
b2af2e46ac | ||
|
|
efc76a0683 | ||
|
|
c4a553c166 | ||
|
|
69a00b0252 | ||
|
|
4257c08b43 | ||
|
|
c9e5b92f79 | ||
|
|
99818c2ad8 | ||
|
|
99e3afabad | ||
|
|
d3339a7f3a | ||
|
|
678bfffe49 | ||
|
|
728b48044c | ||
|
|
7ccbfa48bc | ||
|
|
3241782098 | ||
|
|
b19009b669 | ||
|
|
26218b22ee | ||
|
|
3eeeb5094e | ||
|
|
f309a358c3 | ||
|
|
be0ec37738 | ||
|
|
179c6093ce | ||
|
|
10933ce466 | ||
|
|
cd9cdd24ec | ||
|
|
144ed3c988 | ||
|
|
e046b6df04 | ||
|
|
548959752c |
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
|
||||
project(${PROJECT} VERSION 4.8.4.0
|
||||
project(${PROJECT} VERSION 4.8.5.0
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
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 2082)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
20
README_RU.md
20
README_RU.md
@@ -6,11 +6,11 @@
|
||||
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
||||
|
||||
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
|
||||
[AmneziaVPN](https://amnezia.org) — это open sourse VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
||||
[AmneziaVPN](https://amnezia.org) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
||||
|
||||
[](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]
|
||||
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
|
||||
@@ -30,7 +30,7 @@
|
||||
- Классические 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.
|
||||
- Поддержка 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).
|
||||
|
||||
## Ссылки
|
||||
@@ -38,10 +38,10 @@
|
||||
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
|
||||
- [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_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](https://t.me/amnezia_vpn) - Канал поддржки в 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_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в 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\)
|
||||
|
||||
## Технологии
|
||||
@@ -80,8 +80,8 @@ git submodule update --init --recursive
|
||||
Проверьте папку deploy для скриптов сборки.
|
||||
|
||||
### Как собрать iOS-приложение из исходного кода на MacOS
|
||||
1. Убедитесь, что у вас установлен XCode версии 14 или выше.
|
||||
2. Для генерации проекта XCode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
|
||||
1. Убедитесь, что у вас установлен Xcode версии 14 или выше.
|
||||
2. Для генерации проекта Xcode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
|
||||
- MacOS
|
||||
- iOS
|
||||
- Модуль совместимости с 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
|
||||
```
|
||||
|
||||
6. Откройте проект в XCode. Теперь вы можете тестировать, архивировать или публиковать приложение.
|
||||
6. Откройте проект в Xcode. Теперь вы можете тестировать, архивировать или публиковать приложение.
|
||||
|
||||
Если сборка завершится с ошибкой:
|
||||
```
|
||||
|
||||
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_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))
|
||||
set(PACKAGES ${PACKAGES} Widgets)
|
||||
endif()
|
||||
@@ -48,10 +44,6 @@ set(LIBS ${LIBS}
|
||||
Qt6::Core5Compat Qt6::Concurrent
|
||||
)
|
||||
|
||||
if(IOS)
|
||||
set(LIBS ${LIBS} Qt6::Multimedia)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(LIBS ${LIBS} Qt6::Widgets)
|
||||
endif()
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QProcess>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QTemporaryDir>
|
||||
#include <QTemporaryFile>
|
||||
@@ -19,17 +20,25 @@
|
||||
#include "settings.h"
|
||||
#include "utilities.h"
|
||||
|
||||
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||
bool isAwg, QObject *parent)
|
||||
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings,
|
||||
const QSharedPointer<ServerController> &serverController, bool isAwg,
|
||||
QObject *parent)
|
||||
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
|
||||
{
|
||||
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
||||
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_serverConfigPath =
|
||||
m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
||||
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_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
||||
m_defaultPort = m_isAwg ? protocols::wireguard::defaultPort : protocols::awg::defaultPort;
|
||||
|
||||
m_interfaceName = m_isAwg ? protocols::awg::interfaceName : protocols::wireguard::interfaceName;
|
||||
m_wgBinaryName = m_isAwg ? protocols::awg::wgBinaryName : protocols::wireguard::wgBinaryName;
|
||||
m_wgQuickBinaryName = m_isAwg ? protocols::awg::wgQuickBinaryName : protocols::wireguard::wgQuickBinaryName;
|
||||
}
|
||||
|
||||
WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
|
||||
@@ -63,9 +72,31 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
|
||||
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,
|
||||
DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
||||
connData.host = credentials.hostName;
|
||||
@@ -76,65 +107,59 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
||||
return connData;
|
||||
}
|
||||
|
||||
// Get list of already created clients (only IP addresses)
|
||||
QString nextIpNumber;
|
||||
{
|
||||
QString script = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
errorCode = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
|
||||
stdOut.replace("AllowedIPs = ", "");
|
||||
stdOut.replace("/32", "");
|
||||
QStringList ips = stdOut.split("\n", Qt::SkipEmptyParts);
|
||||
|
||||
// remove extra IPs from each line for case when user manually edited the wg0.conf
|
||||
// and added there more IPs for route his itnernal networks, like:
|
||||
// ...
|
||||
// AllowedIPs = 10.8.1.6/32, 192.168.1.0/24, 192.168.2.0/24, ...
|
||||
// ...
|
||||
// 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";
|
||||
if (container == DockerContainer::Awg) {
|
||||
if (m_serverController->isNewAwgContainer(credentials)) {
|
||||
m_serverConfigPath = amnezia::protocols::awg::serverConfigPath;
|
||||
m_interfaceName = protocols::awg::interfaceName;
|
||||
m_wgBinaryName = protocols::awg::wgBinaryName;
|
||||
m_wgQuickBinaryName = protocols::awg::wgQuickBinaryName;
|
||||
} else {
|
||||
int next = ips.last().split(".").last().toInt() + 1;
|
||||
if (next > 254) {
|
||||
errorCode = ErrorCode::AddressPoolError;
|
||||
return connData;
|
||||
}
|
||||
nextIpNumber = QString::number(next);
|
||||
m_serverConfigPath = "/opt/amnezia/awg/wg0.conf";
|
||||
m_interfaceName = protocols::wireguard::interfaceName;
|
||||
m_wgBinaryName = protocols::wireguard::wgBinaryName;
|
||||
m_wgQuickBinaryName = protocols::wireguard::wgQuickBinaryName;
|
||||
}
|
||||
}
|
||||
|
||||
QString subnetIp = containerConfig.value(m_protocolName).toObject().value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
||||
{
|
||||
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
|
||||
if (l.isEmpty()) {
|
||||
errorCode = ErrorCode::AddressPoolError;
|
||||
return connData;
|
||||
}
|
||||
l.removeLast();
|
||||
l.append(nextIpNumber);
|
||||
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
connData.clientIP = l.join(".");
|
||||
errorCode = m_serverController->runContainerScript(credentials, container, getIpsScript, cbReadStdOut);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
auto ips = getIpsFromConf(stdOut);
|
||||
|
||||
QHostAddress nextIp = [&] {
|
||||
QHostAddress result;
|
||||
QHostAddress lastIp;
|
||||
if (ips.empty()) {
|
||||
lastIp.setAddress(containerConfig.value(m_protocolName)
|
||||
.toObject()
|
||||
.value(config_key::subnet_address)
|
||||
.toString(protocols::wireguard::defaultSubnetAddress));
|
||||
} else {
|
||||
lastIp = ips.last();
|
||||
}
|
||||
quint8 lastOctet = static_cast<quint8>(lastIp.toIPv4Address());
|
||||
switch (lastOctet) {
|
||||
case 254: result.setAddress(lastIp.toIPv4Address() + 3); break;
|
||||
case 255: result.setAddress(lastIp.toIPv4Address() + 2); break;
|
||||
default: result.setAddress(lastIp.toIPv4Address() + 1); break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}();
|
||||
|
||||
connData.clientIP = nextIp.toString();
|
||||
|
||||
// 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", "");
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
@@ -161,10 +186,12 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
||||
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 '%4 syncconf %2 <(%3 strip %1)'")
|
||||
.arg(m_serverConfigPath, m_interfaceName, m_wgQuickBinaryName, m_wgBinaryName);
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -173,8 +200,8 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
{
|
||||
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
||||
QString config =
|
||||
m_serverController->replaceVars(scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||
QString config = m_serverController->replaceVars(
|
||||
scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
@@ -208,16 +235,16 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
|
||||
return QJsonDocument(jConfig).toJson();
|
||||
}
|
||||
|
||||
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString)
|
||||
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns,
|
||||
const bool isApiConfig, QString &protocolConfigString)
|
||||
{
|
||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||
|
||||
return protocolConfigString;
|
||||
}
|
||||
|
||||
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString)
|
||||
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns,
|
||||
const bool isApiConfig, QString &protocolConfigString)
|
||||
{
|
||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef WIREGUARD_CONFIGURATOR_H
|
||||
#define WIREGUARD_CONFIGURATOR_H
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QObject>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
@@ -12,8 +13,8 @@ class WireguardConfigurator : public ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, bool isAwg,
|
||||
QObject *parent = nullptr);
|
||||
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||
bool isAwg, QObject *parent = nullptr);
|
||||
|
||||
struct ConnectionData
|
||||
{
|
||||
@@ -26,15 +27,18 @@ public:
|
||||
QString port;
|
||||
};
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode);
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
|
||||
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
||||
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
||||
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString);
|
||||
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString);
|
||||
|
||||
static ConnectionData genClientKeys();
|
||||
|
||||
private:
|
||||
QList<QHostAddress> getIpsFromConf(const QString &input);
|
||||
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
|
||||
@@ -45,6 +49,9 @@ private:
|
||||
amnezia::ProtocolScriptType m_configTemplate;
|
||||
QString m_protocolName;
|
||||
QString m_defaultPort;
|
||||
QString m_interfaceName;
|
||||
QString m_wgBinaryName;
|
||||
QString m_wgQuickBinaryName;
|
||||
};
|
||||
|
||||
#endif // WIREGUARD_CONFIGURATOR_H
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "coreController.h"
|
||||
|
||||
#include <QDirIterator>
|
||||
#include <QTranslator>
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
@@ -20,6 +21,9 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
|
||||
initControllers();
|
||||
initSignalHandlers();
|
||||
|
||||
initAndroidController();
|
||||
initAppleController();
|
||||
|
||||
initNotificationHandler();
|
||||
|
||||
auto locale = m_settings->getAppLanguage();
|
||||
@@ -90,6 +94,9 @@ void CoreController::initModels()
|
||||
|
||||
m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this));
|
||||
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()
|
||||
@@ -129,7 +136,8 @@ void CoreController::initControllers()
|
||||
m_systemController.reset(new SystemController(m_settings));
|
||||
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_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());
|
||||
}
|
||||
|
||||
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 (QCoreApplication::installTranslator(m_translator.get())) {
|
||||
m_settings->setAppLanguage(locale);
|
||||
|
||||
@@ -25,8 +25,9 @@
|
||||
#include "ui/models/protocols/ikev2ConfigModel.h"
|
||||
#endif
|
||||
#include "ui/models/api/apiAccountInfoModel.h"
|
||||
#include "ui/models/api/apiServicesModel.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/clientManagementModel.h"
|
||||
#include "ui/models/protocols/awgConfigModel.h"
|
||||
@@ -117,6 +118,7 @@ private:
|
||||
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
||||
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
||||
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
|
||||
|
||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||
|
||||
@@ -26,6 +26,10 @@ namespace
|
||||
constexpr char apiPayload[] = "api_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)
|
||||
@@ -148,7 +152,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
|
||||
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) {
|
||||
request.setUrl(url);
|
||||
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,
|
||||
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
|
||||
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;
|
||||
reply = nestedReply;
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
||||
@@ -194,16 +198,16 @@ QStringList GatewayController::getProxyUrls()
|
||||
QList<QSslError> sslErrors;
|
||||
QNetworkReply *reply;
|
||||
|
||||
QStringList proxyStorageUrl;
|
||||
QStringList proxyStorageUrls;
|
||||
if (m_isDevEnvironment) {
|
||||
proxyStorageUrl = QStringList { DEV_S3_ENDPOINT };
|
||||
proxyStorageUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
||||
} else {
|
||||
proxyStorageUrl = QStringList { PROD_S3_ENDPOINT };
|
||||
proxyStorageUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
||||
}
|
||||
|
||||
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);
|
||||
reply = amnApp->networkManager()->get(request);
|
||||
|
||||
@@ -212,45 +216,45 @@ QStringList GatewayController::getProxyUrls()
|
||||
wait.exec();
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||
break;
|
||||
}
|
||||
reply->deleteLater();
|
||||
}
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
reply->deleteLater();
|
||||
EVP_PKEY *privateKey = nullptr;
|
||||
QByteArray responseBody;
|
||||
try {
|
||||
if (!m_isDevEnvironment) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||
hash.addData(key);
|
||||
QByteArray hashResult = hash.result().toHex();
|
||||
|
||||
EVP_PKEY *privateKey = nullptr;
|
||||
QByteArray responseBody;
|
||||
try {
|
||||
if (!m_isDevEnvironment) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||
hash.addData(key);
|
||||
QByteArray hashResult = hash.result().toHex();
|
||||
QByteArray key = QByteArray::fromHex(hashResult.left(64));
|
||||
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
|
||||
|
||||
QByteArray key = QByteArray::fromHex(hashResult.left(64));
|
||||
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
|
||||
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
|
||||
|
||||
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;
|
||||
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
|
||||
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||
|
||||
QStringList endpoints;
|
||||
for (const auto &endpoint : endpointsArray) {
|
||||
endpoints.push_back(endpoint.toString());
|
||||
}
|
||||
return endpoints;
|
||||
} else {
|
||||
responseBody = encryptedResponseBody;
|
||||
reply->deleteLater();
|
||||
}
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||
|
||||
QStringList endpoints;
|
||||
for (const auto &endpoint : endpointsArray) {
|
||||
endpoints.push_back(endpoint.toString());
|
||||
}
|
||||
return endpoints;
|
||||
return {};
|
||||
}
|
||||
|
||||
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
|
||||
@@ -262,6 +266,15 @@ bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray
|
||||
} else if (responseBody.contains("html")) {
|
||||
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 {
|
||||
return true;
|
||||
}
|
||||
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||
return true;
|
||||
} else if (checkEncryption) {
|
||||
try {
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
@@ -296,7 +309,7 @@ void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *repl
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
if (!replyProcessingFunction(reply, sslErrors)) {
|
||||
if (replyProcessingFunction(reply, sslErrors)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,7 +709,7 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
||||
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
|
||||
|
||||
// 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) {
|
||||
script = script.append("|:%1").arg(port);
|
||||
}
|
||||
@@ -757,10 +757,6 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
||||
|
||||
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
|
||||
{
|
||||
if (credentials.userName == "root") {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
@@ -774,8 +770,16 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D
|
||||
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
||||
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;
|
||||
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;
|
||||
}
|
||||
@@ -839,3 +843,24 @@ ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &cred
|
||||
auto error = m_sshClient.getDecryptedPrivateKey(credentials, decryptedPrivateKey, callback);
|
||||
return error;
|
||||
}
|
||||
|
||||
bool ServerController::isNewAwgContainer(const ServerCredentials &credentials)
|
||||
{
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'type awg'");
|
||||
|
||||
runScript(credentials, replaceVars(script, genVarsForScript(credentials, DockerContainer::Awg)), cbReadStdOut, cbReadStdErr);
|
||||
|
||||
return stdOut.contains("/usr/bin/awg");
|
||||
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ public:
|
||||
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey,
|
||||
const std::function<QString()> &callback);
|
||||
|
||||
bool isNewAwgContainer(const ServerCredentials &credentials);
|
||||
|
||||
private:
|
||||
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container);
|
||||
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||
|
||||
@@ -54,6 +54,10 @@ namespace amnezia
|
||||
ServerCancelInstallation = 204,
|
||||
ServerUserNotInSudo = 205,
|
||||
ServerPacketManagerError = 206,
|
||||
ServerSudoPackageIsNotPreinstalled = 207,
|
||||
ServerUserDirectoryNotAccessible = 208,
|
||||
ServerUserNotAllowedInSudoers = 209,
|
||||
ServerUserPasswordRequired = 210,
|
||||
|
||||
// Ssh connection errors
|
||||
SshRequestDeniedError = 300,
|
||||
|
||||
@@ -20,8 +20,12 @@ QString errorString(ErrorCode code) {
|
||||
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::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::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); 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: 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
|
||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
||||
|
||||
5
client/images/controls/monitor.svg
Normal file
5
client/images/controls/monitor.svg
Normal file
@@ -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] {
|
||||
connectedScenes
|
||||
.compactMap {
|
||||
guard let windowScene = $0 as? UIWindowScene else { return nil }
|
||||
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 {
|
||||
($0 as? UIWindowScene)?.windows.first { $0.isKeyWindow }
|
||||
return windowScene.windows.first { $0.isKeyWindow }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +186,9 @@ namespace amnezia
|
||||
constexpr char serverPublicKeyPath[] = "/opt/amnezia/wireguard/wireguard_server_public_key.key";
|
||||
constexpr char serverPskKeyPath[] = "/opt/amnezia/wireguard/wireguard_psk.key";
|
||||
|
||||
constexpr char interfaceName[] = "wg0";
|
||||
constexpr char wgBinaryName[] = "wg";
|
||||
constexpr char wgQuickBinaryName[] = "wg-quick";
|
||||
}
|
||||
|
||||
namespace sftp
|
||||
@@ -203,7 +206,7 @@ namespace amnezia
|
||||
constexpr char defaultMtu[] = "1376";
|
||||
#endif
|
||||
|
||||
constexpr char serverConfigPath[] = "/opt/amnezia/awg/wg0.conf";
|
||||
constexpr char serverConfigPath[] = "/opt/amnezia/awg/awg0.conf";
|
||||
constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key";
|
||||
constexpr char serverPskKeyPath[] = "/opt/amnezia/awg/wireguard_psk.key";
|
||||
|
||||
@@ -216,6 +219,10 @@ namespace amnezia
|
||||
constexpr char defaultResponsePacketMagicHeader[] = "3288052141";
|
||||
constexpr char defaultTransportPacketMagicHeader[] = "2528465083";
|
||||
constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858";
|
||||
|
||||
constexpr char interfaceName[] = "awg0";
|
||||
constexpr char wgBinaryName[] = "awg";
|
||||
constexpr char wgQuickBinaryName[] = "awg-quick";
|
||||
}
|
||||
|
||||
namespace socks5Proxy
|
||||
|
||||
@@ -229,6 +229,8 @@
|
||||
<file>ui/qml/Pages2/PageSettingsApiSupport.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApiInstructions.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApiDevices.qml</file>
|
||||
<file>images/controls/monitor.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/countriesFlags">
|
||||
<file>images/flagKit/ZW.svg</file>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM amneziavpn/amnezia-wg:latest
|
||||
FROM amneziavpn/amneziawg-go:latest
|
||||
|
||||
LABEL maintainer="AmneziaVPN"
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
mkdir -p /opt/amnezia/awg
|
||||
cd /opt/amnezia/awg
|
||||
WIREGUARD_SERVER_PRIVATE_KEY=$(wg genkey)
|
||||
WIREGUARD_SERVER_PRIVATE_KEY=$(awg genkey)
|
||||
echo $WIREGUARD_SERVER_PRIVATE_KEY > /opt/amnezia/awg/wireguard_server_private_key.key
|
||||
|
||||
WIREGUARD_SERVER_PUBLIC_KEY=$(echo $WIREGUARD_SERVER_PRIVATE_KEY | wg pubkey)
|
||||
WIREGUARD_SERVER_PUBLIC_KEY=$(echo $WIREGUARD_SERVER_PRIVATE_KEY | awg pubkey)
|
||||
echo $WIREGUARD_SERVER_PUBLIC_KEY > /opt/amnezia/awg/wireguard_server_public_key.key
|
||||
|
||||
WIREGUARD_PSK=$(wg genpsk)
|
||||
WIREGUARD_PSK=$(awg genpsk)
|
||||
echo $WIREGUARD_PSK > /opt/amnezia/awg/wireguard_psk.key
|
||||
|
||||
cat > /opt/amnezia/awg/wg0.conf <<EOF
|
||||
cat > /opt/amnezia/awg/awg0.conf <<EOF
|
||||
[Interface]
|
||||
PrivateKey = $WIREGUARD_SERVER_PRIVATE_KEY
|
||||
Address = $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR
|
||||
|
||||
@@ -6,19 +6,19 @@ echo "Container startup"
|
||||
#ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up
|
||||
|
||||
# kill daemons in case of restart
|
||||
wg-quick down /opt/amnezia/awg/wg0.conf
|
||||
awg-quick down /opt/amnezia/awg/awg0.conf
|
||||
|
||||
# start daemons if configured
|
||||
if [ -f /opt/amnezia/awg/wg0.conf ]; then (wg-quick up /opt/amnezia/awg/wg0.conf); fi
|
||||
if [ -f /opt/amnezia/awg/awg0.conf ]; then (awg-quick up /opt/amnezia/awg/awg0.conf); fi
|
||||
|
||||
# Allow traffic on the TUN interface.
|
||||
iptables -A INPUT -i wg0 -j ACCEPT
|
||||
iptables -A FORWARD -i wg0 -j ACCEPT
|
||||
iptables -A OUTPUT -o wg0 -j ACCEPT
|
||||
iptables -A INPUT -i awg0 -j ACCEPT
|
||||
iptables -A FORWARD -i awg0 -j ACCEPT
|
||||
iptables -A OUTPUT -o awg0 -j ACCEPT
|
||||
|
||||
# Allow forwarding traffic only from the VPN.
|
||||
iptables -A FORWARD -i wg0 -o eth0 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
|
||||
iptables -A FORWARD -i wg0 -o eth1 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
|
||||
iptables -A FORWARD -i awg0 -o eth0 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
|
||||
iptables -A FORWARD -i awg0 -o eth1 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
|
||||
|
||||
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
|
||||
@@ -1,2 +1,13 @@
|
||||
CUR_USER=$(whoami);\
|
||||
groups $CUR_USER
|
||||
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\
|
||||
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 chown $CUR_USER $DOCKERFILE_FOLDER;\
|
||||
if ! sudo docker network ls | grep -q amnezia-dns-net; then sudo docker network create \
|
||||
|
||||
@@ -3334,8 +3334,8 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="22"/>
|
||||
<source>The user does not have permission to use sudo</source>
|
||||
<translation>ليس لدي المستخدم الصلحيات لأستخدام sudo</translation>
|
||||
<source>The user is not a member of the sudo group</source>
|
||||
<translation>المستخدم ليس عضوًا في مجموعة sudo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="26"/>
|
||||
@@ -3399,7 +3399,7 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="23"/>
|
||||
<source>Server error: Packet manager error</source>
|
||||
<source>Server error: Package manager error</source>
|
||||
<translation>خطأ في الخادم: خطأ في مدير الحزم</translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -3468,8 +3468,8 @@ It's okay as long as it's from someone you trust.</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="22"/>
|
||||
<source>The user does not have permission to use sudo</source>
|
||||
<translation>The user does not have permission to use sudo</translation>
|
||||
<source>The user is not a member of the sudo group</source>
|
||||
<translation>کاربر عضو گروه sudo نیست</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="26"/>
|
||||
@@ -3590,8 +3590,8 @@ It's okay as long as it's from someone you trust.</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="23"/>
|
||||
<source>Server error: Packet manager error</source>
|
||||
<translation>Server error: Packet manager error</translation>
|
||||
<source>Server error: Package manager error</source>
|
||||
<translation>خطای سرور: خطای مدیر بسته</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="34"/>
|
||||
|
||||
@@ -3434,13 +3434,13 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="22"/>
|
||||
<source>The user does not have permission to use sudo</source>
|
||||
<translation>उपयोगकर्ता के पास sudo का उपयोग करने की अनुमति नहीं है</translation>
|
||||
<source>The user is not a member of the sudo group</source>
|
||||
<translation>उपयोगकर्ता sudo समूह का सदस्य नहीं है</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="23"/>
|
||||
<source>Server error: Packet manager error</source>
|
||||
<translation>सर्वर त्रुटि: पैकेट प्रबंधक त्रुटि</translation>
|
||||
<source>Server error: Package manager error</source>
|
||||
<translation>सर्वर त्रुटि: पैकेज प्रबंधक त्रुटि</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="26"/>
|
||||
|
||||
@@ -3330,8 +3330,8 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="22"/>
|
||||
<source>The user does not have permission to use sudo</source>
|
||||
<translation>ဤအသုံးပြုသူသည် sudo ကိုအသုံးပြုရန်ခွင့်ပြုချက်မရှိပါ</translation>
|
||||
<source>The user is not a member of the sudo group</source>
|
||||
<translation>ဤအသုံးပြုသူသည် sudo အုပ်စု၏အဖွဲ့ဝင်မဟုတ်ပါ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="26"/>
|
||||
@@ -3395,8 +3395,8 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="23"/>
|
||||
<source>Server error: Packet manager error</source>
|
||||
<translation>ဆာဗာ မှားယွင်းမှု: Packet Manager မှားယွင်းမှု</translation>
|
||||
<source>Server error: Package manager error</source>
|
||||
<translation>ဆာဗာ အမှား- Package manager အမှား</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="34"/>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3700,13 +3700,13 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="22"/>
|
||||
<source>The user does not have permission to use sudo</source>
|
||||
<translation>The user does not have permission to use sudo</translation>
|
||||
<source>The user is not a member of the sudo group</source>
|
||||
<translation>Користувач не входить до групи sudo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="23"/>
|
||||
<source>Server error: Packet manager error</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<source>Server error: Package manager error</source>
|
||||
<translation>Помилка сервера: Помилка менеджера пакетів</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="26"/>
|
||||
|
||||
@@ -3433,8 +3433,8 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="22"/>
|
||||
<source>The user does not have permission to use sudo</source>
|
||||
<translation>صارف کو sudo استعمال کرنے کی اجازت نہیں ہے</translation>
|
||||
<source>The user is not a member of the sudo group</source>
|
||||
<translation>صارف sudo گروپ کا رکن نہیں ہے</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="26"/>
|
||||
@@ -3498,7 +3498,7 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="23"/>
|
||||
<source>Server error: Packet manager error</source>
|
||||
<source>Server error: Package manager error</source>
|
||||
<translation>سرور خطا: پیکیج منیجر خطا</translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -3675,13 +3675,13 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="22"/>
|
||||
<source>The user does not have permission to use sudo</source>
|
||||
<translation>用户没有root权限</translation>
|
||||
<source>The user is not a member of the sudo group</source>
|
||||
<translation>用户不是 sudo 组的成员</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="23"/>
|
||||
<source>Server error: Packet manager error</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<source>Server error: Package manager error</source>
|
||||
<translation>服务器错误:包管理器错误</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="26"/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "apiConfigsController.h"
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QClipboard>
|
||||
#include <QEventLoop>
|
||||
|
||||
#include "amnezia_application.h"
|
||||
#include "configurators/wireguard_configurator.h"
|
||||
@@ -19,7 +19,7 @@ namespace
|
||||
constexpr char cloak[] = "cloak";
|
||||
constexpr char awg[] = "awg";
|
||||
|
||||
constexpr char apiEdnpoint[] = "api_endpoint";
|
||||
constexpr char apiEndpoint[] = "api_endpoint";
|
||||
constexpr char accessToken[] = "api_key";
|
||||
constexpr char certificate[] = "certificate";
|
||||
constexpr char publicKey[] = "public_key";
|
||||
@@ -269,54 +269,37 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
||||
|
||||
bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
|
||||
{
|
||||
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
||||
auto installationUuid = m_settings->getInstallationUuid(true);
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
if (serverConfig.value(config_key::configVersion).toInt()) {
|
||||
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);
|
||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
|
||||
|
||||
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);
|
||||
apiPayload[configKey::uuid] = installationUuid;
|
||||
QJsonObject apiPayload = fillApiPayload(serviceProtocol, apiPayloadData);
|
||||
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);
|
||||
emit updateServerFromApiFinished();
|
||||
return true;
|
||||
} else {
|
||||
emit errorOccurred(errorCode);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApiConfigsController::deactivateDevice()
|
||||
@@ -354,6 +337,43 @@ bool ApiConfigsController::deactivateDevice()
|
||||
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()
|
||||
{
|
||||
int serverIndex = m_serversModel->getDefaultServerIndex();
|
||||
@@ -369,7 +389,7 @@ bool ApiConfigsController::isConfigValid()
|
||||
return updateServiceFromGateway(serverIndex, "", "");
|
||||
} else if (configSource && m_serversModel->isApiKeyExpired(serverIndex)) {
|
||||
qDebug() << "attempt to update api config by expires_at event";
|
||||
if (configSource == apiDefs::ConfigSource::Telegram) {
|
||||
if (configSource == apiDefs::ConfigSource::AmneziaGateway) {
|
||||
return updateServiceFromGateway(serverIndex, "", "");
|
||||
} else {
|
||||
m_serversModel->removeApiConfig(serverIndex);
|
||||
|
||||
@@ -31,6 +31,7 @@ public slots:
|
||||
bool reloadServiceConfig = false);
|
||||
bool updateServiceFromTelegram(const int serverIndex);
|
||||
bool deactivateDevice();
|
||||
bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode);
|
||||
|
||||
bool isConfigValid();
|
||||
|
||||
|
||||
@@ -25,11 +25,13 @@ namespace
|
||||
ApiSettingsController::ApiSettingsController(const QSharedPointer<ServersModel> &serversModel,
|
||||
const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
|
||||
const QSharedPointer<ApiCountryModel> &apiCountryModel,
|
||||
const QSharedPointer<ApiDevicesModel> &apiDevicesModel,
|
||||
const std::shared_ptr<Settings> &settings, QObject *parent)
|
||||
: QObject(parent),
|
||||
m_serversModel(serversModel),
|
||||
m_apiAccountInfoModel(apiAccountInfoModel),
|
||||
m_apiCountryModel(apiCountryModel),
|
||||
m_apiDevicesModel(apiDevicesModel),
|
||||
m_settings(settings)
|
||||
{
|
||||
}
|
||||
@@ -73,6 +75,7 @@ bool ApiSettingsController::getAccountInfo(bool reload)
|
||||
|
||||
if (reload) {
|
||||
updateApiCountryModel();
|
||||
updateApiDevicesModel();
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -83,3 +86,8 @@ void ApiSettingsController::updateApiCountryModel()
|
||||
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");
|
||||
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/apiCountryModel.h"
|
||||
#include "ui/models/api/apiDevicesModel.h"
|
||||
#include "ui/models/servers_model.h"
|
||||
|
||||
class ApiSettingsController : public QObject
|
||||
@@ -12,13 +13,14 @@ class ApiSettingsController : public QObject
|
||||
Q_OBJECT
|
||||
public:
|
||||
ApiSettingsController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
|
||||
const QSharedPointer<ApiCountryModel> &apiCountryModel, const std::shared_ptr<Settings> &settings,
|
||||
QObject *parent = nullptr);
|
||||
const QSharedPointer<ApiCountryModel> &apiCountryModel, const QSharedPointer<ApiDevicesModel> &apiDevicesModel,
|
||||
const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
|
||||
~ApiSettingsController();
|
||||
|
||||
public slots:
|
||||
bool getAccountInfo(bool reload);
|
||||
void updateApiCountryModel();
|
||||
void updateApiDevicesModel();
|
||||
|
||||
signals:
|
||||
void errorOccurred(ErrorCode errorCode);
|
||||
@@ -27,6 +29,7 @@ private:
|
||||
QSharedPointer<ServersModel> m_serversModel;
|
||||
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
||||
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
|
||||
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
};
|
||||
|
||||
@@ -27,8 +27,6 @@ namespace
|
||||
ConfigTypes checkConfigFormat(const QString &config)
|
||||
{
|
||||
const QString openVpnConfigPatternCli = "client";
|
||||
const QString openVpnConfigPatternProto1 = "proto tcp";
|
||||
const QString openVpnConfigPatternProto2 = "proto udp";
|
||||
const QString openVpnConfigPatternDriver1 = "dev tun";
|
||||
const QString openVpnConfigPatternDriver2 = "dev tap";
|
||||
|
||||
@@ -53,14 +51,13 @@ namespace
|
||||
|| (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
|
||||
&& config.contains(amneziaConfigPatternPassword))) {
|
||||
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)) {
|
||||
return ConfigTypes::WireGuard;
|
||||
} else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) {
|
||||
return ConfigTypes::Xray;
|
||||
} else if (config.contains(openVpnConfigPatternCli)
|
||||
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
|
||||
return ConfigTypes::OpenVpn;
|
||||
}
|
||||
return ConfigTypes::Invalid;
|
||||
}
|
||||
@@ -345,7 +342,7 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data)
|
||||
arr.push_back(containers);
|
||||
|
||||
QString hostName;
|
||||
const static QRegularExpression hostNameRegExp("remote (.*) [0-9]*");
|
||||
const static QRegularExpression hostNameRegExp("remote\\s+([^\\s]+)");
|
||||
QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data);
|
||||
if (hostNameMatch.hasMatch()) {
|
||||
hostName = hostNameMatch.captured(1);
|
||||
|
||||
@@ -370,8 +370,17 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
containerConfig.insert(config_key::transport_proto, transportProto);
|
||||
|
||||
if (protocol == Proto::Awg) {
|
||||
QString serverConfigPath;
|
||||
if (container == DockerContainer::Awg) {
|
||||
if (serverController->isNewAwgContainer(credentials)) {
|
||||
serverConfigPath = amnezia::protocols::awg::serverConfigPath;
|
||||
} else {
|
||||
serverConfigPath = "/opt/amnezia/awg/wg0.conf";
|
||||
}
|
||||
}
|
||||
|
||||
QString serverConfig = serverController->getTextFileFromContainer(container, credentials,
|
||||
protocols::awg::serverConfigPath, errorCode);
|
||||
serverConfigPath, errorCode);
|
||||
|
||||
QMap<QString, QString> serverConfigMap;
|
||||
auto serverConfigLines = serverConfig.split("\n");
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace PageLoader
|
||||
PageSettingsApiSupport,
|
||||
PageSettingsApiInstructions,
|
||||
PageSettingsApiNativeConfigs,
|
||||
PageSettingsApiDevices,
|
||||
|
||||
PageServiceSftpSettings,
|
||||
PageServiceTorWebsiteSettings,
|
||||
|
||||
@@ -44,7 +44,6 @@ void SitesController::addSite(QString hostname)
|
||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
|
||||
Q_ARG(QStringList, QStringList() << hostname));
|
||||
}
|
||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection);
|
||||
};
|
||||
|
||||
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,
|
||||
Q_ARG(QStringList, QStringList() << hostname));
|
||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection);
|
||||
|
||||
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);
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
case ServiceDescriptionRole: {
|
||||
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 "
|
||||
"Mb/s");
|
||||
return tr("Classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online resources. "
|
||||
"Speeds up to 200 Mbps");
|
||||
} 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 "
|
||||
"more. YouTube is not included in the free plan.");
|
||||
@@ -58,6 +58,19 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
||||
case IsComponentVisibleRole: {
|
||||
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();
|
||||
@@ -124,6 +137,7 @@ QHash<int, QByteArray> ApiAccountInfoModel::roleNames() const
|
||||
roles[ConnectedDevicesRole] = "connectedDevices";
|
||||
roles[ServiceDescriptionRole] = "serviceDescription";
|
||||
roles[IsComponentVisibleRole] = "isComponentVisible";
|
||||
roles[HasExpiredWorkerRole] = "hasExpiredWorker";
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ public:
|
||||
ConnectedDevicesRole,
|
||||
ServiceDescriptionRole,
|
||||
EndDateRole,
|
||||
IsComponentVisibleRole
|
||||
IsComponentVisibleRole,
|
||||
HasExpiredWorkerRole
|
||||
};
|
||||
|
||||
explicit ApiAccountInfoModel(QObject *parent = nullptr);
|
||||
|
||||
@@ -44,6 +44,9 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const
|
||||
case IsIssuedRole: {
|
||||
return isIssued;
|
||||
}
|
||||
case IsWorkerExpiredRole: {
|
||||
return issuedConfigInfo.lastDownloaded < issuedConfigInfo.workerLastUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
@@ -114,5 +117,6 @@ QHash<int, QByteArray> ApiCountryModel::roleNames() const
|
||||
roles[CountryCodeRole] = "countryCode";
|
||||
roles[CountryImageCodeRole] = "countryImageCode";
|
||||
roles[IsIssuedRole] = "isIssued";
|
||||
roles[IsWorkerExpiredRole] = "isWorkerExpired";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ public:
|
||||
CountryNameRole = Qt::UserRole + 1,
|
||||
CountryCodeRole,
|
||||
CountryImageCodeRole,
|
||||
IsIssuedRole
|
||||
IsIssuedRole,
|
||||
IsWorkerExpiredRole
|
||||
};
|
||||
|
||||
explicit ApiCountryModel(QObject *parent = nullptr);
|
||||
|
||||
90
client/ui/models/api/apiDevicesModel.cpp
Normal file
90
client/ui/models/api/apiDevicesModel.cpp
Normal file
@@ -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;
|
||||
}
|
||||
52
client/ui/models/api/apiDevicesModel.h
Normal file
52
client/ui/models/api/apiDevicesModel.h
Normal file
@@ -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: {
|
||||
auto speed = apiServiceData.serviceInfo.speed;
|
||||
if (serviceType == serviceType::amneziaPremium) {
|
||||
return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. "
|
||||
"Works for any sites with no restrictions. Speed up to %1 MBit/s. Unlimited traffic.")
|
||||
return tr("Amnezia Premium is classic VPN for seamless work, downloading large files, and watching videos. "
|
||||
"Access all websites and online resources. Speeds up to %1 Mbps.")
|
||||
.arg(speed);
|
||||
} 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.");
|
||||
@@ -79,8 +79,8 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
case ServiceDescriptionRole: {
|
||||
if (serviceType == serviceType::amneziaPremium) {
|
||||
return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. "
|
||||
"Works for any sites with no restrictions.");
|
||||
return tr("Amnezia Premium is classic VPN for for seamless work, downloading large files, and watching videos. "
|
||||
"Access all websites and online resources.");
|
||||
} 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.");
|
||||
}
|
||||
|
||||
@@ -209,7 +209,8 @@ ErrorCode ClientManagementModel::getWireGuardClients(const DockerContainer conta
|
||||
{
|
||||
ErrorCode error = ErrorCode::NoError;
|
||||
|
||||
const QString wireGuardConfigFile = QString("opt/amnezia/%1/wg0.conf").arg(container == DockerContainer::WireGuard ? "wireguard" : "awg");
|
||||
const QString wireGuardConfigFile =
|
||||
DockerContainer::WireGuard ? amnezia::protocols::wireguard::serverConfigPath : amnezia::protocols::awg::serverConfigPath;
|
||||
const QString wireguardConfigString = serverController->getTextFileFromContainer(container, credentials, wireGuardConfigFile, error);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to get the wg conf file from the server";
|
||||
@@ -736,8 +737,17 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont
|
||||
{
|
||||
ErrorCode error = ErrorCode::NoError;
|
||||
|
||||
const QString wireGuardConfigFile =
|
||||
QString("/opt/amnezia/%1/wg0.conf").arg(container == DockerContainer::WireGuard ? "wireguard" : "awg");
|
||||
QString wireGuardConfigFile;
|
||||
if (container == DockerContainer::Awg) {
|
||||
if (serverController->isNewAwgContainer(credentials)) {
|
||||
wireGuardConfigFile = amnezia::protocols::awg::serverConfigPath;
|
||||
} else {
|
||||
wireGuardConfigFile = "/opt/amnezia/awg/wg0.conf";
|
||||
}
|
||||
} else {
|
||||
wireGuardConfigFile = amnezia::protocols::wireguard::serverConfigPath;
|
||||
}
|
||||
|
||||
const QString wireguardConfigString = serverController->getTextFileFromContainer(container, credentials, wireGuardConfigFile, error);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to get the wg conf file from the server";
|
||||
@@ -780,7 +790,11 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont
|
||||
return error;
|
||||
}
|
||||
|
||||
const QString script = "sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'";
|
||||
QString interfaceName = DockerContainer::WireGuard ? protocols::wireguard::interfaceName : protocols::awg::interfaceName;
|
||||
QString wgBinaryName = DockerContainer::WireGuard ? protocols::wireguard::wgBinaryName : protocols::awg::wgBinaryName;
|
||||
QString wgQuickBinaryName = DockerContainer::WireGuard ? protocols::wireguard::wgQuickBinaryName : protocols::awg::wgQuickBinaryName;
|
||||
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c '%4 syncconf %2 <(%3 strip %1)'")
|
||||
.arg(wireGuardConfigFile, interfaceName, wgQuickBinaryName, wgBinaryName);
|
||||
error = serverController->runScript(
|
||||
credentials,
|
||||
serverController->replaceVars(script.arg(wireGuardConfigFile), serverController->genVarsForScript(credentials, container)));
|
||||
|
||||
@@ -54,7 +54,7 @@ Rectangle {
|
||||
Layout.rightMargin: 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
|
||||
|
||||
lineHeight: 18
|
||||
|
||||
@@ -27,5 +27,6 @@ QtObject {
|
||||
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 pearlGray: '#EAEAEC'
|
||||
readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ PageType {
|
||||
actionButtonImage: "qrc:/images/controls/settings.svg"
|
||||
|
||||
headerText: root.processedServer.name
|
||||
descriptionText: qsTr("Locations for connection")
|
||||
descriptionText: qsTr("Location for connection")
|
||||
|
||||
actionButtonFunction: function() {
|
||||
PageController.showBusyIndicator(true)
|
||||
|
||||
105
client/ui/qml/Pages2/PageSettingsApiDevices.qml
Normal file
105
client/ui/qml/Pages2/PageSettingsApiDevices.qml
Normal file
@@ -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
|
||||
|
||||
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
|
||||
|
||||
LabelWithButtonType {
|
||||
id: telegramButton
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 6
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
|
||||
headerText: qsTr("Configuration files")
|
||||
descriptionText: qsTr("To connect a router or AmneziaWG application")
|
||||
headerText: qsTr("Configuration Files")
|
||||
descriptionText: qsTr("For router setup or the AmneziaWG app")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,9 @@ PageType {
|
||||
Layout.topMargin: 6
|
||||
|
||||
text: countryName
|
||||
descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : ""
|
||||
descriptionColor: AmneziaStyle.color.vibrantRed
|
||||
|
||||
leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
|
||||
rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg"
|
||||
|
||||
@@ -120,13 +123,13 @@ PageType {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 16
|
||||
|
||||
headerText: qsTr("Configuration file ") + moreOptionsDrawer.countryName
|
||||
headerText: moreOptionsDrawer.countryName + qsTr(" configuration file")
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Create a new")
|
||||
text: qsTr("Generate a new configuration file")
|
||||
descriptionText: qsTr("The previously created one will stop working")
|
||||
|
||||
clickedFunction: function() {
|
||||
@@ -190,9 +193,15 @@ PageType {
|
||||
}
|
||||
|
||||
function showQuestion(isConfigIssue, countryCode, countryName) {
|
||||
var headerText = qsTr("Revoke the actual %1 configuration file?").arg(countryName)
|
||||
var descriptionText = qsTr("The previously created file will no longer be valid. It will not be possible to connect using it.")
|
||||
var yesButtonText = qsTr("Continue")
|
||||
var headerText
|
||||
if (isConfigIssue) {
|
||||
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 yesButtonFunction = function() {
|
||||
|
||||
@@ -26,15 +26,15 @@ PageType {
|
||||
QtObject {
|
||||
id: statusObject
|
||||
|
||||
readonly property string title: qsTr("Subscription status")
|
||||
readonly property string title: qsTr("Subscription Status")
|
||||
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 {
|
||||
id: endDateObject
|
||||
|
||||
readonly property string title: qsTr("Valid until")
|
||||
readonly property string title: qsTr("Valid Until")
|
||||
readonly property string contentKey: "endDate"
|
||||
readonly property string objectImageSource: "qrc:/images/controls/history.svg"
|
||||
}
|
||||
@@ -42,9 +42,9 @@ PageType {
|
||||
QtObject {
|
||||
id: deviceCountObject
|
||||
|
||||
readonly property string title: qsTr("Connected devices")
|
||||
readonly property string title: qsTr("Active Connections")
|
||||
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
|
||||
@@ -158,15 +158,32 @@ PageType {
|
||||
|
||||
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 {
|
||||
id: vpnKey
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
Layout.topMargin: warning.visible ? 16 : 32
|
||||
|
||||
visible: false //footer.isVisibleForAmneziaFree
|
||||
|
||||
text: qsTr("Subscription key")
|
||||
text: qsTr("Subscription Key")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
@@ -174,7 +191,7 @@ PageType {
|
||||
|
||||
shareConnectionDrawer.openTriggered()
|
||||
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")
|
||||
|
||||
|
||||
@@ -192,13 +209,13 @@ PageType {
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
Layout.topMargin: warning.visible ? 16 : 32
|
||||
|
||||
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"
|
||||
|
||||
clickedFunction: function() {
|
||||
@@ -211,6 +228,26 @@ PageType {
|
||||
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 {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: footer.isVisibleForAmneziaFree ? 0 : 32
|
||||
@@ -228,6 +265,8 @@ PageType {
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
visible: footer.isVisibleForAmneziaFree
|
||||
|
||||
text: qsTr("How to connect on another device")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
@@ -236,7 +275,9 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
DividerType {
|
||||
visible: footer.isVisibleForAmneziaFree
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: resetButton
|
||||
@@ -288,16 +329,17 @@ PageType {
|
||||
pressedColor: AmneziaStyle.color.sheerWhite
|
||||
textColor: AmneziaStyle.color.vibrantRed
|
||||
|
||||
text: qsTr("Deactivate the subscription on this device")
|
||||
text: qsTr("Unlink this device")
|
||||
|
||||
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 noButtonText = qsTr("Cancel")
|
||||
|
||||
var yesButtonFunction = function() {
|
||||
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 {
|
||||
PageController.showBusyIndicator(true)
|
||||
if (ApiConfigsController.deactivateDevice()) {
|
||||
@@ -309,7 +351,7 @@ PageType {
|
||||
var noButtonFunction = function() {
|
||||
}
|
||||
|
||||
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,92 +16,110 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
ColumnLayout {
|
||||
id: backButtonLayout
|
||||
QtObject {
|
||||
id: telegram
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
readonly property string title: qsTr("Telegram")
|
||||
readonly property string description: "@" + ApiAccountInfoModel.getTelegramBotLink()
|
||||
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.bottomMargin: 24
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
}
|
||||
model: supportModel
|
||||
|
||||
header: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
HeaderType {
|
||||
id: header
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
HeaderType {
|
||||
id: header
|
||||
|
||||
headerText: qsTr("Support")
|
||||
descriptionText: qsTr("Our technical support specialists are ready to help you at any time")
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
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)
|
||||
headerText: qsTr("Support")
|
||||
descriptionText: qsTr("Our technical support specialists are available to assist you at any time")
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Mail")
|
||||
descriptionText: qsTr("support@amnezia.org")
|
||||
rightImageSource: "qrc:/images/controls/external-link.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
Qt.openUrlExternally(qsTr("mailto:support@amnezia.org"))
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
text: title
|
||||
descriptionText: description
|
||||
rightImageSource: "qrc:/images/controls/external-link.svg"
|
||||
clickedFunction: function() {
|
||||
Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
DividerType {}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
footer: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
text: qsTr("Site")
|
||||
descriptionText: qsTr("amnezia.org")
|
||||
rightImageSource: "qrc:/images/controls/external-link.svg"
|
||||
LabelWithButtonType {
|
||||
id: supportUuid
|
||||
Layout.fillWidth: true
|
||||
|
||||
clickedFunction: function() {
|
||||
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl())
|
||||
}
|
||||
}
|
||||
text: qsTr("Support tag")
|
||||
descriptionText: SettingsController.getInstallationUuid()
|
||||
|
||||
DividerType {}
|
||||
descriptionOnTop: true
|
||||
|
||||
LabelWithButtonType {
|
||||
id: supportUuid
|
||||
Layout.fillWidth: true
|
||||
rightImageSource: "qrc:/images/controls/copy.svg"
|
||||
rightImageColor: AmneziaStyle.color.paleGray
|
||||
|
||||
text: qsTr("Support tag")
|
||||
descriptionText: SettingsController.getInstallationUuid()
|
||||
|
||||
descriptionOnTop: true
|
||||
|
||||
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()
|
||||
clickedFunction: function() {
|
||||
GC.copyToClipBoard(descriptionText)
|
||||
PageController.showNotificationMessage(qsTr("Copied"))
|
||||
if (!GC.isMobile()) {
|
||||
this.rightButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ PageType {
|
||||
}
|
||||
onClicked: {
|
||||
if (!checkable) {
|
||||
PageController.showNotificationMessage(qsTr("Cannot change killSwitch settings during active connection"))
|
||||
PageController.showNotificationMessage(qsTr("Cannot change KillSwitch settings during active connection"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user