From 3d7a46d058b1b4af9876e29ea24fb0fce2a7288c Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Tue, 13 Aug 2024 14:56:22 +0200 Subject: [PATCH 001/172] add xray client id to the clients table --- client/configurators/xray_configurator.cpp | 12 +- client/ui/models/clientManagementModel.cpp | 124 +++++++++++++++++++-- client/ui/models/clientManagementModel.h | 4 + 3 files changed, 127 insertions(+), 13 deletions(-) diff --git a/client/configurators/xray_configurator.cpp b/client/configurators/xray_configurator.cpp index 786da47c..19692db8 100644 --- a/client/configurators/xray_configurator.cpp +++ b/client/configurators/xray_configurator.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include "containers/containers_defs.h" #include "core/controllers/serverController.h" @@ -13,8 +15,8 @@ XrayConfigurator::XrayConfigurator(std::shared_ptr settings, const QSh { } -QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, - ErrorCode &errorCode) +QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode &errorCode) { QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), m_serverController->genVarsForScript(credentials, container, containerConfig)); @@ -25,11 +27,13 @@ QString XrayConfigurator::createConfig(const ServerCredentials &credentials, Doc QString xrayUuid = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, errorCode); xrayUuid.replace("\n", ""); - + qDebug() << "===>> xrayUuid: " << xrayUuid; QString xrayShortId = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode); xrayShortId.replace("\n", ""); + QString xrayUserId = QUuid::createUuid().toString(QUuid::WithoutBraces); + if (errorCode != ErrorCode::NoError) { return ""; } @@ -37,6 +41,8 @@ QString XrayConfigurator::createConfig(const ServerCredentials &credentials, Doc config.replace("$XRAY_CLIENT_ID", xrayUuid); config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey); config.replace("$XRAY_SHORT_ID", xrayShortId); + config.replace("$XRAY_USER_ID", xrayUserId); + qDebug() << "===>> xrayUserId: " << xrayUserId; return config; } diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index f2117f75..1698d612 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -103,6 +103,8 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co error = getOpenVpnClients(container, credentials, serverController, count); } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) { error = getWireGuardClients(container, credentials, serverController, count); + } else if (container == DockerContainer::Xray) { + error = getXrayClients(container, credentials, serverController, count); } if (error != ErrorCode::NoError) { endResetModel(); @@ -232,6 +234,35 @@ ErrorCode ClientManagementModel::getWireGuardClients(const DockerContainer conta } return error; } +ErrorCode ClientManagementModel::getXrayClients(const DockerContainer container, const ServerCredentials& credentials, + const QSharedPointer &serverController, int &count) +{ + ErrorCode error = ErrorCode::NoError; + + const QString xrayClientIdFile = QStringLiteral("/opt/amnezia/xray/xray_uuid.key"); + const QString xrayClientId = serverController->getTextFileFromContainer(container, credentials, xrayClientIdFile, error); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to get the xray client id file from the server"; + return error; + } + QStringList xrayClientIds { xrayClientId }; + + for (auto &xrayClientId : xrayClientIds) { + if (!isClientExists(xrayClientId)) { + QJsonObject client; + client[configKey::clientId] = xrayClientId; + + QJsonObject userData; + userData[configKey::clientName] = QStringLiteral("Client %1").arg(count); + userData[configKey::userData] = userData; + + m_clientsTable.push_back(client); + + count++; + } + } + return error; +} ErrorCode ClientManagementModel::wgShow(const DockerContainer container, const ServerCredentials &credentials, const QSharedPointer &serverController, std::vector &data) @@ -413,10 +444,27 @@ ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContain auto client = m_clientsTable.at(row).toObject(); QString clientId = client.value(configKey::clientId).toString(); - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController); - } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) { - errorCode = revokeWireGuard(row, container, credentials, serverController); + switch(container) + { + case DockerContainer::OpenVpn: + case DockerContainer::ShadowSocks: + case DockerContainer::Cloak: { + errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController); + break; + } + case DockerContainer::WireGuard: + case DockerContainer::Awg: { + errorCode = revokeWireGuard(row, container, credentials, serverController); + break; + } + case DockerContainer::Xray: { + errorCode = revokeXray(row, container, credentials, serverController); + break; + } + default: { + logger.warning() << "Unknown container type was received"; + break; + } } if (errorCode == ErrorCode::NoError) { @@ -454,13 +502,26 @@ ErrorCode ClientManagementModel::revokeClient(const QJsonObject &containerConfig } Proto protocol; - if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { + + switch(container) + { + case DockerContainer::ShadowSocks: + case DockerContainer::Cloak: { protocol = Proto::OpenVpn; - } else if (container == DockerContainer::OpenVpn || container == DockerContainer::WireGuard || container == DockerContainer::Awg) { + break; + } + case DockerContainer::OpenVpn: + case DockerContainer::WireGuard: + case DockerContainer::Awg: + case DockerContainer::Xray: { protocol = ContainerProps::defaultProtocol(container); - } else { + break; + } + default: { + logger.warning() << "Unknown container type was received"; return ErrorCode::NoError; } + } auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig); @@ -478,11 +539,28 @@ ErrorCode ClientManagementModel::revokeClient(const QJsonObject &containerConfig return errorCode; } - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { + switch (container) + { + case DockerContainer::OpenVpn: + case DockerContainer::ShadowSocks: + case DockerContainer::Cloak: { errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController); - } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) { - errorCode = revokeWireGuard(row, container, credentials, serverController); + break; } + case DockerContainer::WireGuard: + case DockerContainer::Awg: { + errorCode = revokeWireGuard(row, container, credentials, serverController); + break; + } + case DockerContainer::Xray: { + errorCode = revokeXray(row, container, credentials, serverController); + break; + } + default: + logger.warning() << "Unknown container type was received"; + break; + } + return errorCode; } @@ -585,6 +663,32 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont return ErrorCode::NoError; } +ErrorCode ClientManagementModel::revokeXray(const int row, + const DockerContainer container, + const ServerCredentials &credentials, + const QSharedPointer &serverController) +{ + ErrorCode error = ErrorCode::NoError; + + const QString xrayClientIdFile = QStringLiteral("/opt/amnezia/xray/xray_uuid.key"); + const QString xrayClientId = serverController->getTextFileFromContainer(container, credentials, xrayClientIdFile, error); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to get the xray client id file from the server"; + return error; + } + + auto client = m_clientsTable.at(row).toObject(); + QString clientId = client.value(configKey::clientId).toString(); + + // remove from /opt/amnezia/xray/server.json + + beginRemoveRows(QModelIndex(), row, row); + m_clientsTable.removeAt(row); + endRemoveRows(); + + return error; +} + QHash ClientManagementModel::roleNames() const { QHash roles; diff --git a/client/ui/models/clientManagementModel.h b/client/ui/models/clientManagementModel.h index d64280a3..2df641b4 100644 --- a/client/ui/models/clientManagementModel.h +++ b/client/ui/models/clientManagementModel.h @@ -62,11 +62,15 @@ private: const QSharedPointer &serverController); ErrorCode revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials, const QSharedPointer &serverController); + ErrorCode revokeXray(const int row, const DockerContainer container, const ServerCredentials &credentials, + const QSharedPointer &serverController); ErrorCode getOpenVpnClients(const DockerContainer container, const ServerCredentials &credentials, const QSharedPointer &serverController, int &count); ErrorCode getWireGuardClients(const DockerContainer container, const ServerCredentials &credentials, const QSharedPointer &serverController, int &count); + ErrorCode getXrayClients(const DockerContainer container, const ServerCredentials& credentials, + const QSharedPointer &serverController, int &count); ErrorCode wgShow(const DockerContainer container, const ServerCredentials &credentials, const QSharedPointer &serverController, std::vector &data); From 76a439d16d19a449a66772ccf0a8ea1d0e8043ae Mon Sep 17 00:00:00 2001 From: aiamnezia Date: Sat, 26 Oct 2024 05:48:42 +0400 Subject: [PATCH 002/172] Implement client management functionality for Xray --- client/configurators/xray_configurator.cpp | 118 ++++++++++++++--- client/configurators/xray_configurator.h | 4 + client/protocols/protocols_defs.h | 1 - .../xray/configure_container.sh | 5 - client/ui/models/clientManagementModel.cpp | 122 +++++++++++++++--- 5 files changed, 205 insertions(+), 45 deletions(-) diff --git a/client/configurators/xray_configurator.cpp b/client/configurators/xray_configurator.cpp index 19692db8..094fbbb7 100644 --- a/client/configurators/xray_configurator.cpp +++ b/client/configurators/xray_configurator.cpp @@ -15,34 +15,116 @@ XrayConfigurator::XrayConfigurator(std::shared_ptr settings, const QSh { } -QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode &errorCode) +QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode &errorCode) { - QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), - m_serverController->genVarsForScript(credentials, container, containerConfig)); + // Generate new UUID for client + QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces); + + // Create configuration for new client + QString clientConfig = QString(R"( + { + "id": "%1", + "flow": "xtls-rprx-vision" + })") + .arg(clientId); - QString xrayPublicKey = - m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode); - xrayPublicKey.replace("\n", ""); + // Get current server config + QString currentConfig = m_serverController->getTextFileFromContainer( + container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode); + + if (errorCode != ErrorCode::NoError) { + return ""; + } - QString xrayUuid = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, errorCode); - xrayUuid.replace("\n", ""); - qDebug() << "===>> xrayUuid: " << xrayUuid; - QString xrayShortId = - m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode); - xrayShortId.replace("\n", ""); + // Parse current config as JSON + QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8()); + if (doc.isNull()) { + errorCode = ErrorCode::InternalError; + return ""; + } - QString xrayUserId = QUuid::createUuid().toString(QUuid::WithoutBraces); + QJsonObject serverConfig = doc.object(); + + if (!serverConfig.contains("inbounds") || serverConfig["inbounds"].toArray().isEmpty()) { + return ""; + } + + QJsonArray inbounds = serverConfig["inbounds"].toArray(); + QJsonObject inbound = inbounds[0].toObject(); + QJsonObject settings = inbound["settings"].toObject(); + QJsonArray clients = settings["clients"].toArray(); + + clients.append(QJsonDocument::fromJson(clientConfig.toUtf8()).object()); + + // Update config + settings["clients"] = clients; + inbound["settings"] = settings; + inbounds[0] = inbound; + serverConfig["inbounds"] = inbounds; + + // Save updated config to server + QString updatedConfig = QJsonDocument(serverConfig).toJson(); + errorCode = m_serverController->uploadTextFileToContainer( + container, + credentials, + updatedConfig, + amnezia::protocols::xray::serverConfigPath, + libssh::ScpOverwriteMode::ScpOverwriteExisting + ); + qDebug() << "Updated config:" << updatedConfig; + if (errorCode != ErrorCode::NoError) { + return ""; + } + + // Restart container + QString restartScript = QString("docker restart $CONTAINER_NAME"); + errorCode = m_serverController->runScript( + credentials, + m_serverController->replaceVars(restartScript, m_serverController->genVarsForScript(credentials, container)) + ); if (errorCode != ErrorCode::NoError) { return ""; } - config.replace("$XRAY_CLIENT_ID", xrayUuid); + return clientId; +} + +QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode &errorCode) +{ + // Get client ID from prepareServerConfig + QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, errorCode); + if (errorCode != ErrorCode::NoError) { + return ""; + } + + QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), + m_serverController->genVarsForScript(credentials, container, containerConfig)); + + QString xrayPublicKey = + m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode); + if (errorCode != ErrorCode::NoError) { + return ""; + } + xrayPublicKey.replace("\n", ""); + + QString xrayShortId = + m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode); + if (errorCode != ErrorCode::NoError) { + return ""; + } + xrayShortId.replace("\n", ""); + + config.replace("$XRAY_CLIENT_ID", xrayClientId); config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey); config.replace("$XRAY_SHORT_ID", xrayShortId); - config.replace("$XRAY_USER_ID", xrayUserId); - qDebug() << "===>> xrayUserId: " << xrayUserId; - return config; + QJsonObject jConfig; + jConfig[config_key::config] = config; + jConfig[config_key::clientId] = xrayClientId; + + qDebug() << "===>> xrayClientId: " << xrayClientId; + return QJsonDocument(jConfig).toJson(); } diff --git a/client/configurators/xray_configurator.h b/client/configurators/xray_configurator.h index 2acfdf71..8ed4e775 100644 --- a/client/configurators/xray_configurator.h +++ b/client/configurators/xray_configurator.h @@ -14,6 +14,10 @@ public: QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, ErrorCode &errorCode); + +private: + QString prepareServerConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, + ErrorCode &errorCode); }; #endif // XRAY_CONFIGURATOR_H diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 56be0d7d..c6863b90 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -147,7 +147,6 @@ namespace amnezia namespace xray { constexpr char serverConfigPath[] = "/opt/amnezia/xray/server.json"; - constexpr char uuidPath[] = "/opt/amnezia/xray/xray_uuid.key"; constexpr char PublicKeyPath[] = "/opt/amnezia/xray/xray_public.key"; constexpr char PrivateKeyPath[] = "/opt/amnezia/xray/xray_private.key"; constexpr char shortidPath[] = "/opt/amnezia/xray/xray_short_id.key"; diff --git a/client/server_scripts/xray/configure_container.sh b/client/server_scripts/xray/configure_container.sh index bf6f4a42..76195417 100644 --- a/client/server_scripts/xray/configure_container.sh +++ b/client/server_scripts/xray/configure_container.sh @@ -1,5 +1,4 @@ cd /opt/amnezia/xray -XRAY_CLIENT_ID=$(xray uuid) && echo $XRAY_CLIENT_ID > /opt/amnezia/xray/xray_uuid.key XRAY_SHORT_ID=$(openssl rand -hex 8) && echo $XRAY_SHORT_ID > /opt/amnezia/xray/xray_short_id.key KEYPAIR=$(xray x25519) @@ -33,10 +32,6 @@ cat > /opt/amnezia/xray/server.json <getTextFileFromContainer(container, credentials, xrayClientIdFile, error); + const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath; + const QString configString = serverController->getTextFileFromContainer(container, credentials, serverConfigPath, error); if (error != ErrorCode::NoError) { - logger.error() << "Failed to get the xray client id file from the server"; + logger.error() << "Failed to get the xray server config file from the server"; return error; } - QStringList xrayClientIds { xrayClientId }; - for (auto &xrayClientId : xrayClientIds) { - if (!isClientExists(xrayClientId)) { + QJsonDocument serverConfig = QJsonDocument::fromJson(configString.toUtf8()); + if (serverConfig.isNull()) { + logger.error() << "Failed to parse xray server config JSON"; + return ErrorCode::InternalError; + } + + if (!serverConfig.object().contains("inbounds") || serverConfig.object()["inbounds"].toArray().isEmpty()) { + logger.error() << "Invalid xray server config structure"; + return ErrorCode::InternalError; + } + + const QJsonArray clients = serverConfig.object()["inbounds"].toArray()[0].toObject()["settings"].toObject()["clients"].toArray(); + for (const auto &clientValue : clients) { + QString clientId = clientValue.toObject()["id"].toString(); + + if (!isClientExists(clientId)) { QJsonObject client; - client[configKey::clientId] = xrayClientId; + client[configKey::clientId] = clientId; QJsonObject userData; - userData[configKey::clientName] = QStringLiteral("Client %1").arg(count); - userData[configKey::userData] = userData; + userData[configKey::clientName] = QString("Client %1").arg(count); + client[configKey::userData] = userData; m_clientsTable.push_back(client); - count++; } } + return error; } @@ -348,12 +361,19 @@ ErrorCode ClientManagementModel::appendClient(const DockerContainer container, c const QSharedPointer &serverController) { Proto protocol; - if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - protocol = Proto::OpenVpn; - } else if (container == DockerContainer::OpenVpn || container == DockerContainer::WireGuard || container == DockerContainer::Awg) { - protocol = ContainerProps::defaultProtocol(container); - } else { - return ErrorCode::NoError; + switch (container) { + case DockerContainer::ShadowSocks: + case DockerContainer::Cloak: + protocol = Proto::OpenVpn; + break; + case DockerContainer::OpenVpn: + case DockerContainer::WireGuard: + case DockerContainer::Awg: + case DockerContainer::Xray: + protocol = ContainerProps::defaultProtocol(container); + break; + default: + return ErrorCode::NoError; } auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig); @@ -670,22 +690,82 @@ ErrorCode ClientManagementModel::revokeXray(const int row, { ErrorCode error = ErrorCode::NoError; - const QString xrayClientIdFile = QStringLiteral("/opt/amnezia/xray/xray_uuid.key"); - const QString xrayClientId = serverController->getTextFileFromContainer(container, credentials, xrayClientIdFile, error); + // Get server config + const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath; + const QString configString = serverController->getTextFileFromContainer(container, credentials, serverConfigPath, error); if (error != ErrorCode::NoError) { - logger.error() << "Failed to get the xray client id file from the server"; + logger.error() << "Failed to get the xray server config file"; return error; } + QJsonDocument serverConfig = QJsonDocument::fromJson(configString.toUtf8()); + if (serverConfig.isNull()) { + logger.error() << "Failed to parse xray server config JSON"; + return ErrorCode::InternalError; + } + + // Get client ID to remove auto client = m_clientsTable.at(row).toObject(); QString clientId = client.value(configKey::clientId).toString(); - // remove from /opt/amnezia/xray/server.json + // Remove client from server config + QJsonObject configObj = serverConfig.object(); + QJsonArray inbounds = configObj["inbounds"].toArray(); + QJsonObject inbound = inbounds[0].toObject(); + QJsonObject settings = inbound["settings"].toObject(); + QJsonArray clients = settings["clients"].toArray(); + for (int i = 0; i < clients.size(); ++i) { + if (clients[i].toObject()["id"].toString() == clientId) { + clients.removeAt(i); + break; + } + } + + // Update server config + settings["clients"] = clients; + inbound["settings"] = settings; + inbounds[0] = inbound; + configObj["inbounds"] = inbounds; + + // Upload updated config + error = serverController->uploadTextFileToContainer( + container, + credentials, + QJsonDocument(configObj).toJson(), + serverConfigPath + ); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to upload updated xray config"; + return error; + } + + // Remove from local table beginRemoveRows(QModelIndex(), row, row); m_clientsTable.removeAt(row); endRemoveRows(); + // Update clients table file on server + const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); + QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable") + .arg(ContainerProps::containerTypeToString(container)); + + error = serverController->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to upload the clientsTable file"; + } + + // Restart container + QString restartScript = QString("docker restart $CONTAINER_NAME"); + error = serverController->runScript( + credentials, + serverController->replaceVars(restartScript, serverController->genVarsForScript(credentials, container)) + ); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to restart xray container"; + return error; + } + return error; } @@ -698,4 +778,4 @@ QHash ClientManagementModel::roleNames() const roles[DataReceivedRole] = "dataReceived"; roles[DataSentRole] = "dataSent"; return roles; -} +} \ No newline at end of file From 08bbf8818e462bde852a3099471f1085c50588fc Mon Sep 17 00:00:00 2001 From: aiamnezia Date: Sat, 26 Oct 2024 07:39:51 +0400 Subject: [PATCH 003/172] Fix creation of Xray client config --- client/configurators/xray_configurator.cpp | 7 +------ client/ui/models/clientManagementModel.cpp | 10 ++++++++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/client/configurators/xray_configurator.cpp b/client/configurators/xray_configurator.cpp index 094fbbb7..60ef2b1d 100644 --- a/client/configurators/xray_configurator.cpp +++ b/client/configurators/xray_configurator.cpp @@ -72,7 +72,6 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia amnezia::protocols::xray::serverConfigPath, libssh::ScpOverwriteMode::ScpOverwriteExisting ); - qDebug() << "Updated config:" << updatedConfig; if (errorCode != ErrorCode::NoError) { return ""; } @@ -121,10 +120,6 @@ QString XrayConfigurator::createConfig(const ServerCredentials &credentials, Doc config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey); config.replace("$XRAY_SHORT_ID", xrayShortId); - QJsonObject jConfig; - jConfig[config_key::config] = config; - jConfig[config_key::clientId] = xrayClientId; - qDebug() << "===>> xrayClientId: " << xrayClientId; - return QJsonDocument(jConfig).toJson(); + return config; } diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index 5d8a6056..b54ef90d 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -386,8 +386,14 @@ ErrorCode ClientManagementModel::appendClient(const DockerContainer container, c } auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig); - - return appendClient(protocolConfig.value(config_key::clientId).toString(), clientName, container, credentials, serverController); + QString clientId; + if (container == DockerContainer::Xray) { + clientId = protocolConfig.value("outbounds").toArray()[0].toObject()["settings"].toObject()["vnext"] + .toArray()[0].toObject()["users"].toArray()[0].toObject()["id"].toString(); + } else { + clientId = protocolConfig.value(config_key::clientId).toString(); + } + return appendClient(clientId, clientName, container, credentials, serverController); } ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QString &clientName, const DockerContainer container, From 1077f009aa051cdbd9986bfda2e12803793c9fc9 Mon Sep 17 00:00:00 2001 From: aiamnezia Date: Tue, 29 Oct 2024 20:50:41 +0400 Subject: [PATCH 004/172] Add input data validation --- client/configurators/xray_configurator.cpp | 60 +++++++++++----- client/ui/models/clientManagementModel.cpp | 82 ++++++++++++++++++++-- 2 files changed, 121 insertions(+), 21 deletions(-) diff --git a/client/configurators/xray_configurator.cpp b/client/configurators/xray_configurator.cpp index 60ef2b1d..1260e890 100644 --- a/client/configurators/xray_configurator.cpp +++ b/client/configurators/xray_configurator.cpp @@ -21,14 +21,6 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia // Generate new UUID for client QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces); - // Create configuration for new client - QString clientConfig = QString(R"( - { - "id": "%1", - "flow": "xtls-rprx-vision" - })") - .arg(clientId); - // Get current server config QString currentConfig = m_serverController->getTextFileFromContainer( container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode); @@ -39,23 +31,46 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia // Parse current config as JSON QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8()); - if (doc.isNull()) { + if (doc.isNull() || !doc.isObject()) { errorCode = ErrorCode::InternalError; return ""; } QJsonObject serverConfig = doc.object(); - if (!serverConfig.contains("inbounds") || serverConfig["inbounds"].toArray().isEmpty()) { + // Validate server config structure + if (!serverConfig.contains("inbounds")) { + errorCode = ErrorCode::InternalError; + return ""; + } + + QJsonArray inbounds = serverConfig["inbounds"].toArray(); + if (inbounds.isEmpty()) { + errorCode = ErrorCode::InternalError; return ""; } - QJsonArray inbounds = serverConfig["inbounds"].toArray(); QJsonObject inbound = inbounds[0].toObject(); + if (!inbound.contains("settings")) { + errorCode = ErrorCode::InternalError; + return ""; + } + QJsonObject settings = inbound["settings"].toObject(); + if (!settings.contains("clients")) { + errorCode = ErrorCode::InternalError; + return ""; + } + QJsonArray clients = settings["clients"].toArray(); - clients.append(QJsonDocument::fromJson(clientConfig.toUtf8()).object()); + // Create configuration for new client + QJsonObject clientConfig { + {"id", clientId}, + {"flow", "xtls-rprx-vision"} + }; + + clients.append(clientConfig); // Update config settings["clients"] = clients; @@ -95,31 +110,44 @@ QString XrayConfigurator::createConfig(const ServerCredentials &credentials, Doc { // Get client ID from prepareServerConfig QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, errorCode); - if (errorCode != ErrorCode::NoError) { + if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) { + errorCode = ErrorCode::InternalError; return ""; } QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), m_serverController->genVarsForScript(credentials, container, containerConfig)); + + if (config.isEmpty()) { + errorCode = ErrorCode::InternalError; + return ""; + } QString xrayPublicKey = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode); - if (errorCode != ErrorCode::NoError) { + if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) { + errorCode = ErrorCode::InternalError; return ""; } xrayPublicKey.replace("\n", ""); QString xrayShortId = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode); - if (errorCode != ErrorCode::NoError) { + if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) { + errorCode = ErrorCode::InternalError; return ""; } xrayShortId.replace("\n", ""); + // Validate all required variables are present + if (!config.contains("$XRAY_CLIENT_ID") || !config.contains("$XRAY_PUBLIC_KEY") || !config.contains("$XRAY_SHORT_ID")) { + errorCode = ErrorCode::InternalError; + return ""; + } + config.replace("$XRAY_CLIENT_ID", xrayClientId); config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey); config.replace("$XRAY_SHORT_ID", xrayShortId); - qDebug() << "===>> xrayClientId: " << xrayClientId; return config; } diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index b54ef90d..675e29ff 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -264,9 +264,26 @@ ErrorCode ClientManagementModel::getXrayClients(const DockerContainer container, return ErrorCode::InternalError; } - const QJsonArray clients = serverConfig.object()["inbounds"].toArray()[0].toObject()["settings"].toObject()["clients"].toArray(); + const QJsonObject inbound = serverConfig.object()["inbounds"].toArray()[0].toObject(); + if (!inbound.contains("settings")) { + logger.error() << "Missing settings in xray inbound config"; + return ErrorCode::InternalError; + } + + const QJsonObject settings = inbound["settings"].toObject(); + if (!settings.contains("clients")) { + logger.error() << "Missing clients in xray settings config"; + return ErrorCode::InternalError; + } + + const QJsonArray clients = settings["clients"].toArray(); for (const auto &clientValue : clients) { - QString clientId = clientValue.toObject()["id"].toString(); + const QJsonObject clientObj = clientValue.toObject(); + if (!clientObj.contains("id")) { + logger.error() << "Missing id in xray client config"; + continue; + } + QString clientId = clientObj["id"].toString(); if (!isClientExists(clientId)) { QJsonObject client; @@ -388,8 +405,38 @@ ErrorCode ClientManagementModel::appendClient(const DockerContainer container, c auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig); QString clientId; if (container == DockerContainer::Xray) { - clientId = protocolConfig.value("outbounds").toArray()[0].toObject()["settings"].toObject()["vnext"] - .toArray()[0].toObject()["users"].toArray()[0].toObject()["id"].toString(); + if (!protocolConfig.contains("outbounds")) { + return ErrorCode::InternalError; + } + QJsonArray outbounds = protocolConfig.value("outbounds").toArray(); + if (outbounds.isEmpty()) { + return ErrorCode::InternalError; + } + QJsonObject outbound = outbounds[0].toObject(); + if (!outbound.contains("settings")) { + return ErrorCode::InternalError; + } + QJsonObject settings = outbound["settings"].toObject(); + if (!settings.contains("vnext")) { + return ErrorCode::InternalError; + } + QJsonArray vnext = settings["vnext"].toArray(); + if (vnext.isEmpty()) { + return ErrorCode::InternalError; + } + QJsonObject vnextObj = vnext[0].toObject(); + if (!vnextObj.contains("users")) { + return ErrorCode::InternalError; + } + QJsonArray users = vnextObj["users"].toArray(); + if (users.isEmpty()) { + return ErrorCode::InternalError; + } + QJsonObject user = users[0].toObject(); + if (!user.contains("id")) { + return ErrorCode::InternalError; + } + clientId = user["id"].toString(); } else { clientId = protocolConfig.value(config_key::clientId).toString(); } @@ -725,13 +772,38 @@ ErrorCode ClientManagementModel::revokeXray(const int row, // Remove client from server config QJsonObject configObj = serverConfig.object(); + if (!configObj.contains("inbounds")) { + logger.error() << "Missing inbounds in xray config"; + return ErrorCode::InternalError; + } + QJsonArray inbounds = configObj["inbounds"].toArray(); + if (inbounds.isEmpty()) { + logger.error() << "Empty inbounds array in xray config"; + return ErrorCode::InternalError; + } + QJsonObject inbound = inbounds[0].toObject(); + if (!inbound.contains("settings")) { + logger.error() << "Missing settings in xray inbound config"; + return ErrorCode::InternalError; + } + QJsonObject settings = inbound["settings"].toObject(); + if (!settings.contains("clients")) { + logger.error() << "Missing clients in xray settings"; + return ErrorCode::InternalError; + } + QJsonArray clients = settings["clients"].toArray(); + if (clients.isEmpty()) { + logger.error() << "Empty clients array in xray config"; + return ErrorCode::InternalError; + } for (int i = 0; i < clients.size(); ++i) { - if (clients[i].toObject()["id"].toString() == clientId) { + QJsonObject clientObj = clients[i].toObject(); + if (clientObj.contains("id") && clientObj["id"].toString() == clientId) { clients.removeAt(i); break; } From c90781831159d9be294d7f62456893c985dd94f7 Mon Sep 17 00:00:00 2001 From: aiamnezia Date: Thu, 7 Nov 2024 19:59:15 +0400 Subject: [PATCH 005/172] Add xray native config export --- client/ui/controllers/exportController.cpp | 9 ++++----- client/ui/controllers/exportController.h | 2 +- client/ui/models/clientManagementModel.cpp | 6 ++++++ client/ui/models/clientManagementModel.h | 2 ++ client/ui/qml/Pages2/PageShare.qml | 2 +- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 2690b5b1..8681406e 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -121,9 +121,8 @@ ErrorCode ExportController::generateNativeConfig(const DockerContainer container jsonNativeConfig = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); - if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg) { - auto clientId = jsonNativeConfig.value(config_key::clientId).toString(); - errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials, serverController); + if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg || protocol == Proto::Xray) { + errorCode = m_clientManagementModel->appendClient(jsonNativeConfig, clientName, container, credentials, serverController); } return errorCode; } @@ -248,10 +247,10 @@ void ExportController::generateCloakConfig() emit exportConfigChanged(); } -void ExportController::generateXrayConfig() +void ExportController::generateXrayConfig(const QString &clientName) { QJsonObject nativeConfig; - ErrorCode errorCode = generateNativeConfig(DockerContainer::Xray, "", Proto::Xray, nativeConfig); + ErrorCode errorCode = generateNativeConfig(DockerContainer::Xray, clientName, Proto::Xray, nativeConfig); if (errorCode) { emit exportErrorOccurred(errorCode); return; diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index b031ea39..a2c9fcfa 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -28,7 +28,7 @@ public slots: void generateAwgConfig(const QString &clientName); void generateShadowSocksConfig(); void generateCloakConfig(); - void generateXrayConfig(); + void generateXrayConfig(const QString &clientName); QString getConfig(); QString getNativeConfigString(); diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index 675e29ff..1a1ff201 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -403,6 +403,12 @@ ErrorCode ClientManagementModel::appendClient(const DockerContainer container, c } auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig); + return appendClient(protocolConfig, clientName, container, credentials, serverController); +} + +ErrorCode ClientManagementModel::appendClient(QJsonObject &protocolConfig, const QString &clientName, const DockerContainer container, + const ServerCredentials &credentials, const QSharedPointer &serverController) +{ QString clientId; if (container == DockerContainer::Xray) { if (!protocolConfig.contains("outbounds")) { diff --git a/client/ui/models/clientManagementModel.h b/client/ui/models/clientManagementModel.h index 9ac8b10b..989120a9 100644 --- a/client/ui/models/clientManagementModel.h +++ b/client/ui/models/clientManagementModel.h @@ -40,6 +40,8 @@ public slots: const QSharedPointer &serverController); ErrorCode appendClient(const DockerContainer container, const ServerCredentials &credentials, const QJsonObject &containerConfig, const QString &clientName, const QSharedPointer &serverController); + ErrorCode appendClient(QJsonObject &protocolConfig, const QString &clientName,const DockerContainer container, + const ServerCredentials &credentials, const QSharedPointer &serverController); ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container, const ServerCredentials &credentials, const QSharedPointer &serverController); ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container, const ServerCredentials &credentials, diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 6640df36..1f6a7089 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -92,7 +92,7 @@ PageType { break } case PageShare.ConfigType.Xray: { - ExportController.generateXrayConfig() + ExportController.generateXrayConfig(clientNameTextField.textFieldText) shareConnectionDrawer.configCaption = qsTr("Save XRay config") shareConnectionDrawer.configExtension = ".json" shareConnectionDrawer.configFileName = "amnezia_for_xray" From 83967d7a886031781ed17a3c049d79ff6e2b6578 Mon Sep 17 00:00:00 2001 From: aiamnezia Date: Mon, 11 Nov 2024 20:38:04 +0400 Subject: [PATCH 006/172] Fix revoking client when clear profile --- client/ui/models/clientManagementModel.cpp | 40 +++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index 1a1ff201..4a62391c 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -446,6 +446,7 @@ ErrorCode ClientManagementModel::appendClient(QJsonObject &protocolConfig, const } else { clientId = protocolConfig.value(config_key::clientId).toString(); } + return appendClient(clientId, clientName, container, credentials, serverController); } @@ -613,9 +614,46 @@ ErrorCode ClientManagementModel::revokeClient(const QJsonObject &containerConfig auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig); + QString clientId; + if (container == DockerContainer::Xray) { + if (!protocolConfig.contains("outbounds")) { + return ErrorCode::InternalError; + } + QJsonArray outbounds = protocolConfig.value("outbounds").toArray(); + if (outbounds.isEmpty()) { + return ErrorCode::InternalError; + } + QJsonObject outbound = outbounds[0].toObject(); + if (!outbound.contains("settings")) { + return ErrorCode::InternalError; + } + QJsonObject settings = outbound["settings"].toObject(); + if (!settings.contains("vnext")) { + return ErrorCode::InternalError; + } + QJsonArray vnext = settings["vnext"].toArray(); + if (vnext.isEmpty()) { + return ErrorCode::InternalError; + } + QJsonObject vnextObj = vnext[0].toObject(); + if (!vnextObj.contains("users")) { + return ErrorCode::InternalError; + } + QJsonArray users = vnextObj["users"].toArray(); + if (users.isEmpty()) { + return ErrorCode::InternalError; + } + QJsonObject user = users[0].toObject(); + if (!user.contains("id")) { + return ErrorCode::InternalError; + } + clientId = user["id"].toString(); + } else { + clientId = protocolConfig.value(config_key::clientId).toString(); + } + int row; bool clientExists = false; - QString clientId = protocolConfig.value(config_key::clientId).toString(); for (row = 0; row < rowCount(); row++) { auto client = m_clientsTable.at(row).toObject(); if (clientId == client.value(configKey::clientId).toString()) { From 5674a275459d517e2cb414ab92a1736fc489ad86 Mon Sep 17 00:00:00 2001 From: aiamnezia Date: Thu, 21 Nov 2024 04:41:47 +0400 Subject: [PATCH 007/172] Fix connection error for non-root server users --- client/configurators/xray_configurator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/configurators/xray_configurator.cpp b/client/configurators/xray_configurator.cpp index 1260e890..62f18066 100644 --- a/client/configurators/xray_configurator.cpp +++ b/client/configurators/xray_configurator.cpp @@ -92,7 +92,7 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia } // Restart container - QString restartScript = QString("docker restart $CONTAINER_NAME"); + QString restartScript = QString("sudo docker restart $CONTAINER_NAME"); errorCode = m_serverController->runScript( credentials, m_serverController->replaceVars(restartScript, m_serverController->genVarsForScript(credentials, container)) From 7a63ef79f4eb67c01bf830cbf74da85e0857a6d4 Mon Sep 17 00:00:00 2001 From: aiamnezia Date: Thu, 21 Nov 2024 05:25:00 +0400 Subject: [PATCH 008/172] Fix revoking clients with non-root server users --- client/ui/models/clientManagementModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index 4a62391c..c5d5e530 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -887,7 +887,7 @@ ErrorCode ClientManagementModel::revokeXray(const int row, } // Restart container - QString restartScript = QString("docker restart $CONTAINER_NAME"); + QString restartScript = QString("sudo docker restart $CONTAINER_NAME"); error = serverController->runScript( credentials, serverController->replaceVars(restartScript, serverController->genVarsForScript(credentials, container)) From 46a7f97e32cd7d9852262fb82f5edffbfc511887 Mon Sep 17 00:00:00 2001 From: aiamnezia Date: Wed, 27 Nov 2024 15:52:10 +0400 Subject: [PATCH 009/172] Fix connection to server from old versions by credentials --- client/protocols/protocols_defs.h | 1 + client/server_scripts/xray/configure_container.sh | 5 +++++ client/ui/models/clientManagementModel.cpp | 5 ++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 498c89a0..865edae4 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -148,6 +148,7 @@ namespace amnezia namespace xray { constexpr char serverConfigPath[] = "/opt/amnezia/xray/server.json"; + constexpr char uuidPath[] = "/opt/amnezia/xray/xray_uuid.key"; constexpr char PublicKeyPath[] = "/opt/amnezia/xray/xray_public.key"; constexpr char PrivateKeyPath[] = "/opt/amnezia/xray/xray_private.key"; constexpr char shortidPath[] = "/opt/amnezia/xray/xray_short_id.key"; diff --git a/client/server_scripts/xray/configure_container.sh b/client/server_scripts/xray/configure_container.sh index 4e27f734..a84751c7 100644 --- a/client/server_scripts/xray/configure_container.sh +++ b/client/server_scripts/xray/configure_container.sh @@ -1,4 +1,5 @@ cd /opt/amnezia/xray +XRAY_CLIENT_ID=$(xray uuid) && echo $XRAY_CLIENT_ID > /opt/amnezia/xray/xray_uuid.key XRAY_SHORT_ID=$(openssl rand -hex 8) && echo $XRAY_SHORT_ID > /opt/amnezia/xray/xray_short_id.key KEYPAIR=$(xray x25519) @@ -32,6 +33,10 @@ cat > /opt/amnezia/xray/server.json <getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, error); + xrayDefUuid.replace("\n", ""); + + if (!isClientExists(clientId) && clientId != xrayDefUuid) { QJsonObject client; client[configKey::clientId] = clientId; From 0291abede76861fc7d838bb2c9b89ef735515c21 Mon Sep 17 00:00:00 2001 From: aiamnezia Date: Thu, 28 Nov 2024 08:33:42 +0400 Subject: [PATCH 010/172] Small style fix --- client/ui/models/clientManagementModel.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index dccb75c7..04b4e18c 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -285,10 +285,10 @@ ErrorCode ClientManagementModel::getXrayClients(const DockerContainer container, } QString clientId = clientObj["id"].toString(); - QString xrayDefUuid = serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, error); - xrayDefUuid.replace("\n", ""); + QString xrayDefaultUuid = serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, error); + xrayDefaultUuid.replace("\n", ""); - if (!isClientExists(clientId) && clientId != xrayDefUuid) { + if (!isClientExists(clientId) && clientId != xrayDefaultUuid) { QJsonObject client; client[configKey::clientId] = clientId; From a86b0872c72f0db0cddd7cd87567d2ad2234277e Mon Sep 17 00:00:00 2001 From: aiamnezia Date: Mon, 9 Dec 2024 13:09:12 +0400 Subject: [PATCH 011/172] add logs and relevant errors --- client/configurators/xray_configurator.cpp | 22 +++++++++++- client/ui/models/clientManagementModel.cpp | 40 +++++++++++----------- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/client/configurators/xray_configurator.cpp b/client/configurators/xray_configurator.cpp index 62f18066..514aa821 100644 --- a/client/configurators/xray_configurator.cpp +++ b/client/configurators/xray_configurator.cpp @@ -4,12 +4,16 @@ #include #include #include -#include +#include "logger.h" #include "containers/containers_defs.h" #include "core/controllers/serverController.h" #include "core/scripts_registry.h" +namespace { +Logger logger("XrayConfigurator"); +} + XrayConfigurator::XrayConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent) : ConfiguratorBase(settings, serverController, parent) { @@ -26,12 +30,14 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode); if (errorCode != ErrorCode::NoError) { + logger.error() << "Failed to get server config file"; return ""; } // Parse current config as JSON QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8()); if (doc.isNull() || !doc.isObject()) { + logger.error() << "Failed to parse server config JSON"; errorCode = ErrorCode::InternalError; return ""; } @@ -40,24 +46,28 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia // Validate server config structure if (!serverConfig.contains("inbounds")) { + logger.error() << "Server config missing 'inbounds' field"; errorCode = ErrorCode::InternalError; return ""; } QJsonArray inbounds = serverConfig["inbounds"].toArray(); if (inbounds.isEmpty()) { + logger.error() << "Server config has empty 'inbounds' array"; errorCode = ErrorCode::InternalError; return ""; } QJsonObject inbound = inbounds[0].toObject(); if (!inbound.contains("settings")) { + logger.error() << "Inbound missing 'settings' field"; errorCode = ErrorCode::InternalError; return ""; } QJsonObject settings = inbound["settings"].toObject(); if (!settings.contains("clients")) { + logger.error() << "Settings missing 'clients' field"; errorCode = ErrorCode::InternalError; return ""; } @@ -88,6 +98,7 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia libssh::ScpOverwriteMode::ScpOverwriteExisting ); if (errorCode != ErrorCode::NoError) { + logger.error() << "Failed to upload updated config"; return ""; } @@ -99,6 +110,7 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia ); if (errorCode != ErrorCode::NoError) { + logger.error() << "Failed to restart container"; return ""; } @@ -111,6 +123,7 @@ QString XrayConfigurator::createConfig(const ServerCredentials &credentials, Doc // Get client ID from prepareServerConfig QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, errorCode); if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) { + logger.error() << "Failed to prepare server config"; errorCode = ErrorCode::InternalError; return ""; } @@ -119,6 +132,7 @@ QString XrayConfigurator::createConfig(const ServerCredentials &credentials, Doc m_serverController->genVarsForScript(credentials, container, containerConfig)); if (config.isEmpty()) { + logger.error() << "Failed to get config template"; errorCode = ErrorCode::InternalError; return ""; } @@ -126,6 +140,7 @@ QString XrayConfigurator::createConfig(const ServerCredentials &credentials, Doc QString xrayPublicKey = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode); if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) { + logger.error() << "Failed to get public key"; errorCode = ErrorCode::InternalError; return ""; } @@ -134,6 +149,7 @@ QString XrayConfigurator::createConfig(const ServerCredentials &credentials, Doc QString xrayShortId = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode); if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) { + logger.error() << "Failed to get short ID"; errorCode = ErrorCode::InternalError; return ""; } @@ -141,6 +157,10 @@ QString XrayConfigurator::createConfig(const ServerCredentials &credentials, Doc // Validate all required variables are present if (!config.contains("$XRAY_CLIENT_ID") || !config.contains("$XRAY_PUBLIC_KEY") || !config.contains("$XRAY_SHORT_ID")) { + logger.error() << "Config template missing required variables:" + << "XRAY_CLIENT_ID:" << !config.contains("$XRAY_CLIENT_ID") + << "XRAY_PUBLIC_KEY:" << !config.contains("$XRAY_PUBLIC_KEY") + << "XRAY_SHORT_ID:" << !config.contains("$XRAY_SHORT_ID"); errorCode = ErrorCode::InternalError; return ""; } diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index 04b4e18c..f07eae71 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -554,8 +554,8 @@ ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContain break; } default: { - logger.warning() << "Unknown container type was received"; - break; + logger.error() << "Internal error: received unexpected container type"; + return ErrorCode::InternalError; } } @@ -597,22 +597,22 @@ ErrorCode ClientManagementModel::revokeClient(const QJsonObject &containerConfig switch(container) { - case DockerContainer::ShadowSocks: - case DockerContainer::Cloak: { - protocol = Proto::OpenVpn; - break; - } - case DockerContainer::OpenVpn: - case DockerContainer::WireGuard: - case DockerContainer::Awg: - case DockerContainer::Xray: { - protocol = ContainerProps::defaultProtocol(container); - break; - } - default: { - logger.warning() << "Unknown container type was received"; - return ErrorCode::NoError; - } + case DockerContainer::ShadowSocks: + case DockerContainer::Cloak: { + protocol = Proto::OpenVpn; + break; + } + case DockerContainer::OpenVpn: + case DockerContainer::WireGuard: + case DockerContainer::Awg: + case DockerContainer::Xray: { + protocol = ContainerProps::defaultProtocol(container); + break; + } + default: { + logger.error() << "Internal error: received unexpected container type"; + return ErrorCode::InternalError; + } } auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig); @@ -686,8 +686,8 @@ ErrorCode ClientManagementModel::revokeClient(const QJsonObject &containerConfig break; } default: - logger.warning() << "Unknown container type was received"; - break; + logger.error() << "Internal error: received unexpected container type"; + return ErrorCode::InternalError; } return errorCode; From d06924c59dd8684c28b6257efe5d1a11db34b19b Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Tue, 10 Dec 2024 03:17:16 +0100 Subject: [PATCH 012/172] feature/xray user management (#972) * feature: implement client management functionality for Xray --------- Co-authored-by: aiamnezia Co-authored-by: vladimir.kuznetsov --- client/configurators/xray_configurator.cpp | 165 +++++++++- client/configurators/xray_configurator.h | 4 + client/ui/controllers/exportController.cpp | 9 +- client/ui/controllers/exportController.h | 2 +- client/ui/models/clientManagementModel.cpp | 353 +++++++++++++++++++-- client/ui/models/clientManagementModel.h | 6 + client/ui/qml/Pages2/PageShare.qml | 2 +- 7 files changed, 495 insertions(+), 46 deletions(-) diff --git a/client/configurators/xray_configurator.cpp b/client/configurators/xray_configurator.cpp index 786da47c..514aa821 100644 --- a/client/configurators/xray_configurator.cpp +++ b/client/configurators/xray_configurator.cpp @@ -3,38 +3,169 @@ #include #include #include +#include +#include "logger.h" #include "containers/containers_defs.h" #include "core/controllers/serverController.h" #include "core/scripts_registry.h" +namespace { +Logger logger("XrayConfigurator"); +} + XrayConfigurator::XrayConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent) : ConfiguratorBase(settings, serverController, parent) { } -QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, - ErrorCode &errorCode) +QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode &errorCode) { - QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), - m_serverController->genVarsForScript(credentials, container, containerConfig)); - - QString xrayPublicKey = - m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode); - xrayPublicKey.replace("\n", ""); - - QString xrayUuid = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, errorCode); - xrayUuid.replace("\n", ""); - - QString xrayShortId = - m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode); - xrayShortId.replace("\n", ""); - + // Generate new UUID for client + QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces); + + // Get current server config + QString currentConfig = m_serverController->getTextFileFromContainer( + container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode); + if (errorCode != ErrorCode::NoError) { + logger.error() << "Failed to get server config file"; return ""; } - config.replace("$XRAY_CLIENT_ID", xrayUuid); + // Parse current config as JSON + QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8()); + if (doc.isNull() || !doc.isObject()) { + logger.error() << "Failed to parse server config JSON"; + errorCode = ErrorCode::InternalError; + return ""; + } + + QJsonObject serverConfig = doc.object(); + + // Validate server config structure + if (!serverConfig.contains("inbounds")) { + logger.error() << "Server config missing 'inbounds' field"; + errorCode = ErrorCode::InternalError; + return ""; + } + + QJsonArray inbounds = serverConfig["inbounds"].toArray(); + if (inbounds.isEmpty()) { + logger.error() << "Server config has empty 'inbounds' array"; + errorCode = ErrorCode::InternalError; + return ""; + } + + QJsonObject inbound = inbounds[0].toObject(); + if (!inbound.contains("settings")) { + logger.error() << "Inbound missing 'settings' field"; + errorCode = ErrorCode::InternalError; + return ""; + } + + QJsonObject settings = inbound["settings"].toObject(); + if (!settings.contains("clients")) { + logger.error() << "Settings missing 'clients' field"; + errorCode = ErrorCode::InternalError; + return ""; + } + + QJsonArray clients = settings["clients"].toArray(); + + // Create configuration for new client + QJsonObject clientConfig { + {"id", clientId}, + {"flow", "xtls-rprx-vision"} + }; + + clients.append(clientConfig); + + // Update config + settings["clients"] = clients; + inbound["settings"] = settings; + inbounds[0] = inbound; + serverConfig["inbounds"] = inbounds; + + // Save updated config to server + QString updatedConfig = QJsonDocument(serverConfig).toJson(); + errorCode = m_serverController->uploadTextFileToContainer( + container, + credentials, + updatedConfig, + amnezia::protocols::xray::serverConfigPath, + libssh::ScpOverwriteMode::ScpOverwriteExisting + ); + if (errorCode != ErrorCode::NoError) { + logger.error() << "Failed to upload updated config"; + return ""; + } + + // Restart container + QString restartScript = QString("sudo docker restart $CONTAINER_NAME"); + errorCode = m_serverController->runScript( + credentials, + m_serverController->replaceVars(restartScript, m_serverController->genVarsForScript(credentials, container)) + ); + + if (errorCode != ErrorCode::NoError) { + logger.error() << "Failed to restart container"; + return ""; + } + + return clientId; +} + +QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode &errorCode) +{ + // Get client ID from prepareServerConfig + QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, errorCode); + if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) { + logger.error() << "Failed to prepare server config"; + errorCode = ErrorCode::InternalError; + return ""; + } + + QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), + m_serverController->genVarsForScript(credentials, container, containerConfig)); + + if (config.isEmpty()) { + logger.error() << "Failed to get config template"; + errorCode = ErrorCode::InternalError; + return ""; + } + + QString xrayPublicKey = + m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode); + if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) { + logger.error() << "Failed to get public key"; + errorCode = ErrorCode::InternalError; + return ""; + } + xrayPublicKey.replace("\n", ""); + + QString xrayShortId = + m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode); + if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) { + logger.error() << "Failed to get short ID"; + errorCode = ErrorCode::InternalError; + return ""; + } + xrayShortId.replace("\n", ""); + + // Validate all required variables are present + if (!config.contains("$XRAY_CLIENT_ID") || !config.contains("$XRAY_PUBLIC_KEY") || !config.contains("$XRAY_SHORT_ID")) { + logger.error() << "Config template missing required variables:" + << "XRAY_CLIENT_ID:" << !config.contains("$XRAY_CLIENT_ID") + << "XRAY_PUBLIC_KEY:" << !config.contains("$XRAY_PUBLIC_KEY") + << "XRAY_SHORT_ID:" << !config.contains("$XRAY_SHORT_ID"); + errorCode = ErrorCode::InternalError; + return ""; + } + + config.replace("$XRAY_CLIENT_ID", xrayClientId); config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey); config.replace("$XRAY_SHORT_ID", xrayShortId); diff --git a/client/configurators/xray_configurator.h b/client/configurators/xray_configurator.h index 2acfdf71..8ed4e775 100644 --- a/client/configurators/xray_configurator.h +++ b/client/configurators/xray_configurator.h @@ -14,6 +14,10 @@ public: QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, ErrorCode &errorCode); + +private: + QString prepareServerConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, + ErrorCode &errorCode); }; #endif // XRAY_CONFIGURATOR_H diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 2690b5b1..8681406e 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -121,9 +121,8 @@ ErrorCode ExportController::generateNativeConfig(const DockerContainer container jsonNativeConfig = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); - if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg) { - auto clientId = jsonNativeConfig.value(config_key::clientId).toString(); - errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials, serverController); + if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg || protocol == Proto::Xray) { + errorCode = m_clientManagementModel->appendClient(jsonNativeConfig, clientName, container, credentials, serverController); } return errorCode; } @@ -248,10 +247,10 @@ void ExportController::generateCloakConfig() emit exportConfigChanged(); } -void ExportController::generateXrayConfig() +void ExportController::generateXrayConfig(const QString &clientName) { QJsonObject nativeConfig; - ErrorCode errorCode = generateNativeConfig(DockerContainer::Xray, "", Proto::Xray, nativeConfig); + ErrorCode errorCode = generateNativeConfig(DockerContainer::Xray, clientName, Proto::Xray, nativeConfig); if (errorCode) { emit exportErrorOccurred(errorCode); return; diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index b031ea39..a2c9fcfa 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -28,7 +28,7 @@ public slots: void generateAwgConfig(const QString &clientName); void generateShadowSocksConfig(); void generateCloakConfig(); - void generateXrayConfig(); + void generateXrayConfig(const QString &clientName); QString getConfig(); QString getNativeConfigString(); diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index 7445d60f..f07eae71 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -106,6 +106,8 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co error = getOpenVpnClients(container, credentials, serverController, count); } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) { error = getWireGuardClients(container, credentials, serverController, count); + } else if (container == DockerContainer::Xray) { + error = getXrayClients(container, credentials, serverController, count); } if (error != ErrorCode::NoError) { endResetModel(); @@ -239,6 +241,68 @@ ErrorCode ClientManagementModel::getWireGuardClients(const DockerContainer conta } return error; } +ErrorCode ClientManagementModel::getXrayClients(const DockerContainer container, const ServerCredentials& credentials, + const QSharedPointer &serverController, int &count) +{ + ErrorCode error = ErrorCode::NoError; + + const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath; + const QString configString = serverController->getTextFileFromContainer(container, credentials, serverConfigPath, error); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to get the xray server config file from the server"; + return error; + } + + QJsonDocument serverConfig = QJsonDocument::fromJson(configString.toUtf8()); + if (serverConfig.isNull()) { + logger.error() << "Failed to parse xray server config JSON"; + return ErrorCode::InternalError; + } + + if (!serverConfig.object().contains("inbounds") || serverConfig.object()["inbounds"].toArray().isEmpty()) { + logger.error() << "Invalid xray server config structure"; + return ErrorCode::InternalError; + } + + const QJsonObject inbound = serverConfig.object()["inbounds"].toArray()[0].toObject(); + if (!inbound.contains("settings")) { + logger.error() << "Missing settings in xray inbound config"; + return ErrorCode::InternalError; + } + + const QJsonObject settings = inbound["settings"].toObject(); + if (!settings.contains("clients")) { + logger.error() << "Missing clients in xray settings config"; + return ErrorCode::InternalError; + } + + const QJsonArray clients = settings["clients"].toArray(); + for (const auto &clientValue : clients) { + const QJsonObject clientObj = clientValue.toObject(); + if (!clientObj.contains("id")) { + logger.error() << "Missing id in xray client config"; + continue; + } + QString clientId = clientObj["id"].toString(); + + QString xrayDefaultUuid = serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, error); + xrayDefaultUuid.replace("\n", ""); + + if (!isClientExists(clientId) && clientId != xrayDefaultUuid) { + QJsonObject client; + client[configKey::clientId] = clientId; + + QJsonObject userData; + userData[configKey::clientName] = QString("Client %1").arg(count); + client[configKey::userData] = userData; + + m_clientsTable.push_back(client); + count++; + } + } + + return error; +} ErrorCode ClientManagementModel::wgShow(const DockerContainer container, const ServerCredentials &credentials, const QSharedPointer &serverController, std::vector &data) @@ -326,17 +390,67 @@ ErrorCode ClientManagementModel::appendClient(const DockerContainer container, c const QSharedPointer &serverController) { Proto protocol; - if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - protocol = Proto::OpenVpn; - } else if (container == DockerContainer::OpenVpn || container == DockerContainer::WireGuard || container == DockerContainer::Awg) { - protocol = ContainerProps::defaultProtocol(container); - } else { - return ErrorCode::NoError; + switch (container) { + case DockerContainer::ShadowSocks: + case DockerContainer::Cloak: + protocol = Proto::OpenVpn; + break; + case DockerContainer::OpenVpn: + case DockerContainer::WireGuard: + case DockerContainer::Awg: + case DockerContainer::Xray: + protocol = ContainerProps::defaultProtocol(container); + break; + default: + return ErrorCode::NoError; } auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig); + return appendClient(protocolConfig, clientName, container, credentials, serverController); +} - return appendClient(protocolConfig.value(config_key::clientId).toString(), clientName, container, credentials, serverController); +ErrorCode ClientManagementModel::appendClient(QJsonObject &protocolConfig, const QString &clientName, const DockerContainer container, + const ServerCredentials &credentials, const QSharedPointer &serverController) +{ + QString clientId; + if (container == DockerContainer::Xray) { + if (!protocolConfig.contains("outbounds")) { + return ErrorCode::InternalError; + } + QJsonArray outbounds = protocolConfig.value("outbounds").toArray(); + if (outbounds.isEmpty()) { + return ErrorCode::InternalError; + } + QJsonObject outbound = outbounds[0].toObject(); + if (!outbound.contains("settings")) { + return ErrorCode::InternalError; + } + QJsonObject settings = outbound["settings"].toObject(); + if (!settings.contains("vnext")) { + return ErrorCode::InternalError; + } + QJsonArray vnext = settings["vnext"].toArray(); + if (vnext.isEmpty()) { + return ErrorCode::InternalError; + } + QJsonObject vnextObj = vnext[0].toObject(); + if (!vnextObj.contains("users")) { + return ErrorCode::InternalError; + } + QJsonArray users = vnextObj["users"].toArray(); + if (users.isEmpty()) { + return ErrorCode::InternalError; + } + QJsonObject user = users[0].toObject(); + if (!user.contains("id")) { + return ErrorCode::InternalError; + } + clientId = user["id"].toString(); + } else { + clientId = protocolConfig.value(config_key::clientId).toString(); + } + + return appendClient(clientId, clientName, container, credentials, serverController); } ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QString &clientName, const DockerContainer container, @@ -422,10 +536,27 @@ ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContain auto client = m_clientsTable.at(row).toObject(); QString clientId = client.value(configKey::clientId).toString(); - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController); - } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) { - errorCode = revokeWireGuard(row, container, credentials, serverController); + switch(container) + { + case DockerContainer::OpenVpn: + case DockerContainer::ShadowSocks: + case DockerContainer::Cloak: { + errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController); + break; + } + case DockerContainer::WireGuard: + case DockerContainer::Awg: { + errorCode = revokeWireGuard(row, container, credentials, serverController); + break; + } + case DockerContainer::Xray: { + errorCode = revokeXray(row, container, credentials, serverController); + break; + } + default: { + logger.error() << "Internal error: received unexpected container type"; + return ErrorCode::InternalError; + } } if (errorCode == ErrorCode::NoError) { @@ -463,19 +594,69 @@ ErrorCode ClientManagementModel::revokeClient(const QJsonObject &containerConfig } Proto protocol; - if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - protocol = Proto::OpenVpn; - } else if (container == DockerContainer::OpenVpn || container == DockerContainer::WireGuard || container == DockerContainer::Awg) { - protocol = ContainerProps::defaultProtocol(container); - } else { - return ErrorCode::NoError; + + switch(container) + { + case DockerContainer::ShadowSocks: + case DockerContainer::Cloak: { + protocol = Proto::OpenVpn; + break; + } + case DockerContainer::OpenVpn: + case DockerContainer::WireGuard: + case DockerContainer::Awg: + case DockerContainer::Xray: { + protocol = ContainerProps::defaultProtocol(container); + break; + } + default: { + logger.error() << "Internal error: received unexpected container type"; + return ErrorCode::InternalError; + } } auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig); + QString clientId; + if (container == DockerContainer::Xray) { + if (!protocolConfig.contains("outbounds")) { + return ErrorCode::InternalError; + } + QJsonArray outbounds = protocolConfig.value("outbounds").toArray(); + if (outbounds.isEmpty()) { + return ErrorCode::InternalError; + } + QJsonObject outbound = outbounds[0].toObject(); + if (!outbound.contains("settings")) { + return ErrorCode::InternalError; + } + QJsonObject settings = outbound["settings"].toObject(); + if (!settings.contains("vnext")) { + return ErrorCode::InternalError; + } + QJsonArray vnext = settings["vnext"].toArray(); + if (vnext.isEmpty()) { + return ErrorCode::InternalError; + } + QJsonObject vnextObj = vnext[0].toObject(); + if (!vnextObj.contains("users")) { + return ErrorCode::InternalError; + } + QJsonArray users = vnextObj["users"].toArray(); + if (users.isEmpty()) { + return ErrorCode::InternalError; + } + QJsonObject user = users[0].toObject(); + if (!user.contains("id")) { + return ErrorCode::InternalError; + } + clientId = user["id"].toString(); + } else { + clientId = protocolConfig.value(config_key::clientId).toString(); + } + int row; bool clientExists = false; - QString clientId = protocolConfig.value(config_key::clientId).toString(); for (row = 0; row < rowCount(); row++) { auto client = m_clientsTable.at(row).toObject(); if (clientId == client.value(configKey::clientId).toString()) { @@ -487,11 +668,28 @@ ErrorCode ClientManagementModel::revokeClient(const QJsonObject &containerConfig return errorCode; } - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { + switch (container) + { + case DockerContainer::OpenVpn: + case DockerContainer::ShadowSocks: + case DockerContainer::Cloak: { errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController); - } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) { - errorCode = revokeWireGuard(row, container, credentials, serverController); + break; } + case DockerContainer::WireGuard: + case DockerContainer::Awg: { + errorCode = revokeWireGuard(row, container, credentials, serverController); + break; + } + case DockerContainer::Xray: { + errorCode = revokeXray(row, container, credentials, serverController); + break; + } + default: + logger.error() << "Internal error: received unexpected container type"; + return ErrorCode::InternalError; + } + return errorCode; } @@ -594,6 +792,117 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont return ErrorCode::NoError; } +ErrorCode ClientManagementModel::revokeXray(const int row, + const DockerContainer container, + const ServerCredentials &credentials, + const QSharedPointer &serverController) +{ + ErrorCode error = ErrorCode::NoError; + + // Get server config + const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath; + const QString configString = serverController->getTextFileFromContainer(container, credentials, serverConfigPath, error); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to get the xray server config file"; + return error; + } + + QJsonDocument serverConfig = QJsonDocument::fromJson(configString.toUtf8()); + if (serverConfig.isNull()) { + logger.error() << "Failed to parse xray server config JSON"; + return ErrorCode::InternalError; + } + + // Get client ID to remove + auto client = m_clientsTable.at(row).toObject(); + QString clientId = client.value(configKey::clientId).toString(); + + // Remove client from server config + QJsonObject configObj = serverConfig.object(); + if (!configObj.contains("inbounds")) { + logger.error() << "Missing inbounds in xray config"; + return ErrorCode::InternalError; + } + + QJsonArray inbounds = configObj["inbounds"].toArray(); + if (inbounds.isEmpty()) { + logger.error() << "Empty inbounds array in xray config"; + return ErrorCode::InternalError; + } + + QJsonObject inbound = inbounds[0].toObject(); + if (!inbound.contains("settings")) { + logger.error() << "Missing settings in xray inbound config"; + return ErrorCode::InternalError; + } + + QJsonObject settings = inbound["settings"].toObject(); + if (!settings.contains("clients")) { + logger.error() << "Missing clients in xray settings"; + return ErrorCode::InternalError; + } + + QJsonArray clients = settings["clients"].toArray(); + if (clients.isEmpty()) { + logger.error() << "Empty clients array in xray config"; + return ErrorCode::InternalError; + } + + for (int i = 0; i < clients.size(); ++i) { + QJsonObject clientObj = clients[i].toObject(); + if (clientObj.contains("id") && clientObj["id"].toString() == clientId) { + clients.removeAt(i); + break; + } + } + + // Update server config + settings["clients"] = clients; + inbound["settings"] = settings; + inbounds[0] = inbound; + configObj["inbounds"] = inbounds; + + // Upload updated config + error = serverController->uploadTextFileToContainer( + container, + credentials, + QJsonDocument(configObj).toJson(), + serverConfigPath + ); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to upload updated xray config"; + return error; + } + + // Remove from local table + beginRemoveRows(QModelIndex(), row, row); + m_clientsTable.removeAt(row); + endRemoveRows(); + + // Update clients table file on server + const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); + QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable") + .arg(ContainerProps::containerTypeToString(container)); + + error = serverController->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to upload the clientsTable file"; + } + + // Restart container + QString restartScript = QString("sudo docker restart $CONTAINER_NAME"); + error = serverController->runScript( + credentials, + serverController->replaceVars(restartScript, serverController->genVarsForScript(credentials, container)) + ); + if (error != ErrorCode::NoError) { + logger.error() << "Failed to restart xray container"; + return error; + } + + return error; +} + QHash ClientManagementModel::roleNames() const { QHash roles; @@ -604,4 +913,4 @@ QHash ClientManagementModel::roleNames() const roles[DataSentRole] = "dataSent"; roles[AllowedIpsRole] = "allowedIps"; return roles; -} +} \ No newline at end of file diff --git a/client/ui/models/clientManagementModel.h b/client/ui/models/clientManagementModel.h index 60132abe..989120a9 100644 --- a/client/ui/models/clientManagementModel.h +++ b/client/ui/models/clientManagementModel.h @@ -40,6 +40,8 @@ public slots: const QSharedPointer &serverController); ErrorCode appendClient(const DockerContainer container, const ServerCredentials &credentials, const QJsonObject &containerConfig, const QString &clientName, const QSharedPointer &serverController); + ErrorCode appendClient(QJsonObject &protocolConfig, const QString &clientName,const DockerContainer container, + const ServerCredentials &credentials, const QSharedPointer &serverController); ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container, const ServerCredentials &credentials, const QSharedPointer &serverController); ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container, const ServerCredentials &credentials, @@ -64,11 +66,15 @@ private: const QSharedPointer &serverController); ErrorCode revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials, const QSharedPointer &serverController); + ErrorCode revokeXray(const int row, const DockerContainer container, const ServerCredentials &credentials, + const QSharedPointer &serverController); ErrorCode getOpenVpnClients(const DockerContainer container, const ServerCredentials &credentials, const QSharedPointer &serverController, int &count); ErrorCode getWireGuardClients(const DockerContainer container, const ServerCredentials &credentials, const QSharedPointer &serverController, int &count); + ErrorCode getXrayClients(const DockerContainer container, const ServerCredentials& credentials, + const QSharedPointer &serverController, int &count); ErrorCode wgShow(const DockerContainer container, const ServerCredentials &credentials, const QSharedPointer &serverController, std::vector &data); diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 995fa3e7..d6ce7848 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -92,7 +92,7 @@ PageType { break } case PageShare.ConfigType.Xray: { - ExportController.generateXrayConfig() + ExportController.generateXrayConfig(clientNameTextField.textFieldText) shareConnectionDrawer.configCaption = qsTr("Save XRay config") shareConnectionDrawer.configExtension = ".json" shareConnectionDrawer.configFileName = "amnezia_for_xray" From 367789bda28f33a11a72d61b222453c5b502299e Mon Sep 17 00:00:00 2001 From: KsZnak Date: Sat, 14 Dec 2024 14:29:33 +0200 Subject: [PATCH 013/172] Update README_RU.md (#1300) * Update README_RU.md --- README_RU.md | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/README_RU.md b/README_RU.md index fe9dd286..59518f4b 100644 --- a/README_RU.md +++ b/README_RU.md @@ -55,6 +55,112 @@ AmneziaVPN использует несколько проектов с откр - [LibSsh](https://libssh.org) - и другие... +## Проверка исходного кода +После клонирования репозитория обязательно загрузите все подмодули. + +```bash +git submodule update --init --recursive +``` + + +## Разработка +Хотите внести свой вклад? Добро пожаловать! + +### Помощь с переводами + +Загрузите самые актуальные файлы перевода. + +Перейдите на [вкладку "Actions"](https://github.com/amnezia-vpn/amnezia-client/actions?query=is%3Asuccess+branch%3Adev), нажмите на первую строку. Затем прокрутите вниз до раздела "Artifacts" и скачайте "AmneziaVPN_translations". + +Распакуйте этот файл. Каждый файл с расширением *.ts содержит строки для соответствующего языка. + +Переведите или исправьте строки в одном или нескольких файлах *.ts и загрузите их обратно в этот репозиторий в папку ``client/translations``. Это можно сделать через веб-интерфейс или любым другим знакомым вам способом. + +### Сборка исходного кода и деплой +Проверьте папку deploy для скриптов сборки. + +### Как собрать iOS-приложение из исходного кода на MacOS +1. Убедитесь, что у вас установлен XCode версии 14 или выше. +2. Для генерации проекта XCode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули: +- MacOS +- iOS +- Модуль совместимости с Qt 5 +- Qt Shader Tools +- Дополнительные библиотеки: + - Qt Image Formats + - Qt Multimedia + - Qt Remote Objects + + +3. Установите CMake, если это необходимо. Рекомендуемая версия — 3.25. Скачать CMake можно здесь. +4. Установите Go версии >= v1.16. Если Go ещё не установлен, скачайте его с [официального сайта](https://golang.org/dl/) или используйте Homebrew. Установите gomobile: + +```bash +export PATH=$PATH:~/go/bin +go install golang.org/x/mobile/cmd/gomobile@latest +gomobile init +``` + +5. Соберите проект: +```bash +export QT_BIN_DIR="/Qt//ios/bin" +export QT_MACOS_ROOT_DIR="/Qt//macos" +export QT_IOS_BIN=$QT_BIN_DIR +export PATH=$PATH:~/go/bin +mkdir build-ios +$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR +``` +Замените и на ваши значения. + +Если появляется ошибка gomobile: command not found, убедитесь, что PATH настроен на папку bin, где установлен gomobile: +```bash +export PATH=$(PATH):/path/to/GOPATH/bin +``` + +6. Откройте проект в XCode. Теперь вы можете тестировать, архивировать или публиковать приложение. + +Если сборка завершится с ошибкой: +``` +make: *** +[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared] +Error 1 +``` +Добавьте пользовательскую переменную PATH в настройки сборки для целей AmneziaVPN и WireGuardNetworkExtension с ключом `PATH` и значением `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`. + +Если ошибка повторяется на Mac с M1, установите версию CMake для архитектуры ARM: +``` +arch -arm64 brew install cmake +``` + + При первой попытке сборка может завершиться с ошибкой source files not found. Это происходит из-за параллельной компиляции зависимостей в XCode. Просто перезапустите сборку. + + +## Как собрать Android-приложение +Сборка тестировалась на MacOS. Требования: +- JDK 11 +- Android SDK 33 +- CMake 3.25.0 + +Установите QT, QT Creator и Android Studio. +Настройте QT Creator: + +- В меню QT Creator перейдите в `QT Creator` -> `Preferences` -> `Devices` ->`Android`. +- Укажите путь к JDK 11. +- Укажите путь к Android SDK (`$ANDROID_HOME`) + +Если вы сталкиваетесь с ошибками, связанными с отсутствием SDK или сообщением «SDK manager not running», их нельзя исправить просто корректировкой путей. Если у вас есть несколько свободных гигабайт на диске, вы можете позволить Qt Creator установить все необходимые компоненты, выбрав пустую папку для расположения Android SDK и нажав кнопку **Set Up SDK**. Учтите: это установит второй Android SDK и NDK на вашем компьютере! + +Убедитесь, что настроена правильная версия CMake: перейдите в **Qt Creator -> Preferences** и в боковом меню выберите пункт **Kits**. В центральной части окна, на вкладке **Kits**, найдите запись для инструмента **CMake Tool**. Если выбранная по умолчанию версия CMake ниже 3.25.0, установите на свою систему CMake версии 3.25.0 или выше, а затем выберите опцию **System CMake at <путь>** из выпадающего списка. Если этот пункт отсутствует, это может означать, что вы еще не установили CMake, или Qt Creator не смог найти путь к нему. В таком случае в окне **Preferences** перейдите в боковое меню **CMake**, затем во вкладку **Tools** в центральной части окна и нажмите кнопку **Add**, чтобы указать путь к установленному CMake. + +Убедитесь, что для вашего проекта выбрана Android Platform SDK 33: в главном окне на боковой панели выберите пункт **Projects**, и слева вы увидите раздел **Build & Run**, показывающий различные целевые Android-платформы. Вы можете выбрать любую из них, так как настройка проекта Amnezia VPN разработана таким образом, чтобы все Android-цели могли быть собраны. Перейдите в подраздел **Build** и прокрутите центральную часть окна до раздела **Build Steps**. Нажмите **Details** в заголовке **Build Android APK** (кнопка **Details** может быть скрыта, если окно Qt Creator не запущено в полноэкранном режиме!). Вот здесь выберите **android-33** в качестве Android Build Platform SDK. + +### Разработка Android-компонентов + +После сборки QT Creator копирует проект в отдельную папку, например, `build-amnezia-client-Android_Qt__Clang_-`. Для разработки Android-компонентов откройте сгенерированный проект в Android Studio, указав папку `build-amnezia-client-Android_Qt__Clang_-/client/android-build` в качестве корневой. +Изменения в сгенерированном проекте нужно вручную перенести в репозиторий. После этого можно коммитить изменения. +Если возникают проблемы со сборкой в QT Creator после работы в Android Studio, выполните команду `./gradlew clean` в корневой папке сгенерированного проекта (`/client/android-build/.`). + + ## Лицензия GPL v3.0 From 48f6cf904e7f41c002be3a6daf3b53535191456c Mon Sep 17 00:00:00 2001 From: Nethius Date: Thu, 19 Dec 2024 10:36:20 +0300 Subject: [PATCH 014/172] chore/minor UI fixes (#1308) * chore: corrected the translation error * bugfix: fixed basic button left iamge color --- client/translations/amneziavpn_ru_RU.ts | 2 +- client/ui/qml/Controls2/BasicButtonType.qml | 2 +- client/ui/qml/Pages2/PageHome.qml | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/translations/amneziavpn_ru_RU.ts b/client/translations/amneziavpn_ru_RU.ts index 2fb21259..c0d855b2 100644 --- a/client/translations/amneziavpn_ru_RU.ts +++ b/client/translations/amneziavpn_ru_RU.ts @@ -2679,7 +2679,7 @@ and will not be shared or disclosed to the Amnezia or any third parties Where to get connection data, step-by-step instructions for buying a VPS - Где взять данные для подключения, пошаговые инстуркции по покупке VPS + Где взять данные для подключения, пошаговые инструкции по покупке VPS diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index 828c32bc..ef66e0e2 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -24,7 +24,7 @@ Button { property string leftImageSource property string rightImageSource - property string leftImageColor + property string leftImageColor: textColor property bool changeLeftImageSize: true property bool squareLeftSide: false diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 8422a10f..e5112575 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -110,6 +110,7 @@ PageType { text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled") leftImageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : "" + leftImageColor: "" rightImageSource: "qrc:/images/controls/chevron-down.svg" Keys.onEnterPressed: splitTunnelingButton.clicked() From b88ab8e432fbd2ab5002856cd83ef57181a6d7fa Mon Sep 17 00:00:00 2001 From: albexk Date: Mon, 23 Dec 2024 04:27:09 +0300 Subject: [PATCH 015/172] fix(build): fix aqtinstall (#1312) --- .github/workflows/deploy.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a51c19b2..35e740b0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -335,7 +335,8 @@ jobs: arch: 'linux_gcc_64' modules: ${{ env.QT_MODULES }} dir: ${{ runner.temp }} - extra: '--external 7z --base ${{ env.QT_MIRROR }}' + py7zrversion: '==0.22.*' + extra: '--base ${{ env.QT_MIRROR }}' - name: 'Install android_x86_64 Qt' uses: jurplel/install-qt-action@v4 @@ -346,7 +347,8 @@ jobs: arch: 'android_x86_64' modules: ${{ env.QT_MODULES }} dir: ${{ runner.temp }} - extra: '--external 7z --base ${{ env.QT_MIRROR }}' + py7zrversion: '==0.22.*' + extra: '--base ${{ env.QT_MIRROR }}' - name: 'Install android_x86 Qt' uses: jurplel/install-qt-action@v4 @@ -357,7 +359,8 @@ jobs: arch: 'android_x86' modules: ${{ env.QT_MODULES }} dir: ${{ runner.temp }} - extra: '--external 7z --base ${{ env.QT_MIRROR }}' + py7zrversion: '==0.22.*' + extra: '--base ${{ env.QT_MIRROR }}' - name: 'Install android_armv7 Qt' uses: jurplel/install-qt-action@v4 @@ -368,7 +371,8 @@ jobs: arch: 'android_armv7' modules: ${{ env.QT_MODULES }} dir: ${{ runner.temp }} - extra: '--external 7z --base ${{ env.QT_MIRROR }}' + py7zrversion: '==0.22.*' + extra: '--base ${{ env.QT_MIRROR }}' - name: 'Install android_arm64_v8a Qt' uses: jurplel/install-qt-action@v4 @@ -379,7 +383,8 @@ jobs: arch: 'android_arm64_v8a' modules: ${{ env.QT_MODULES }} dir: ${{ runner.temp }} - extra: '--external 7z --base ${{ env.QT_MIRROR }}' + py7zrversion: '==0.22.*' + extra: '--base ${{ env.QT_MIRROR }}' - name: 'Grant execute permission for qt-cmake' shell: bash From 2bff37efae583a9606e5fae263dbbac907338458 Mon Sep 17 00:00:00 2001 From: Mikhail Kiselev <73298492+sund3RRR@users.noreply.github.com> Date: Sat, 28 Dec 2024 08:02:14 +0300 Subject: [PATCH 016/172] fix: segmentation violation due to missing return (#1321) --- client/utilities.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/utilities.cpp b/client/utilities.cpp index 1cc69aeb..4caa8f70 100755 --- a/client/utilities.cpp +++ b/client/utilities.cpp @@ -248,7 +248,7 @@ bool Utils::killProcessByName(const QString &name) #elif defined Q_OS_IOS || defined(Q_OS_ANDROID) return false; #else - QProcess::execute(QString("pkill %1").arg(name)); + return QProcess::execute(QString("pkill %1").arg(name)) == 0; #endif } From 212e9b3a9151b2793a5cc1ccb6dfb00de9155aca Mon Sep 17 00:00:00 2001 From: Andrey Alekseenko Date: Mon, 30 Dec 2024 05:45:26 +0000 Subject: [PATCH 017/172] fix: adding second new VMess links now works (#1325) --- client/core/serialization/vmess_new.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/core/serialization/vmess_new.cpp b/client/core/serialization/vmess_new.cpp index 6f3ec3e1..68d32203 100644 --- a/client/core/serialization/vmess_new.cpp +++ b/client/core/serialization/vmess_new.cpp @@ -104,7 +104,7 @@ QJsonObject Deserialize(const QString &vmessStr, QString *alias, QString *errMes server.users.first().security = "auto"; } - const static auto getQueryValue = [&query](const QString &key, const QString &defaultValue) { + const auto getQueryValue = [&query](const QString &key, const QString &defaultValue) { if (query.hasQueryItem(key)) return query.queryItemValue(key, QUrl::FullyDecoded); else From 6acaab0ffa4960acb0ee603db81037603814638b Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Tue, 31 Dec 2024 04:16:52 +0100 Subject: [PATCH 018/172] Improve navigation cpp (#1061) * add focusController class * add more key handlers * add focus navigation to qml * fixed language selector * add reverse focus change to FocusController * add default focus item * update transitions * update pages * add ListViewFocusController * fix ListView navigation * update CardType for using with focus navigation * remove useless key navigation * remove useless slots, logs, Drawer open and close * fix reverse focus move on listView * fix drawer radio buttons selection * fix drawer layout and focus move * fix PageSetupWizardProtocolSettings focus move * fix back navigation on default focus item * fix crashes after ListView navigation * fix protocol settings focus move * fix focus on users on page share * clean up page share * fix server rename * fix page share default server selection * refactor about page for correct focus move * fix focus move on list views with header and-or footer * minor fixes * fix server list back button handler * fix spawn signals on switch * fix share details drawer * fix drawer open close usage * refactor listViewFocusController * refactor focusController to make the logic more straightforward * fix focus on notification * update config page for scrolling with tab * fix crash on return with esc key * fix focus navigation in dynamic delegate of list view * fix focus move on qr code on share page * refactor page logging settings for focus navigation * update popup * Bump version * Add mandatory requirement for android.software.leanback. * Fix importing files on TVs * fix: add separate method for reading files to fix file reading on Android TV * fix(android): add CHANGE_NETWORK_STATE permission for all Android versions * Fix connection check for AWG/WG * chore: minor fixes (#1235) * fix: add a workaround to open files on Android TV due to lack of SAF * fix: change the banner format for TV * refactor: make TvFilePicker activity more sustainable * fix: add the touch emulation method for Android TV * fix: null uri processing * fix: add the touch emulation method for Android TV * fix: hide UI elements that use file saving * chore: bump version code * add `ScrollBarType` * update initial config page * refactor credentials setup page to handle the focus navigation * add `setDelegateIndex` method to `listViewFocusController` * fix focus behavior on new page/popup * make minor fixes and clean up * fix: get rid of the assign function call * Scrollbar is on if the content is larger than a screen * Fix selection in language change list * Update select language list * update logging settings page * fix checked item in lists * fix split tunneling settings * make unchangable properties readonly * refactor SwitcherType * fix hide/unhide password * `PageShare` readonly properties * Fix list view focus moving on `PageShare` * remove manual focus control on `PageShare` * format `ListViewFocusController` * format `FocusController` * add `focusControl` with utility functions for focus control * refactor `listViewFocusController` acoording to `focusControl` * refactor `focusConroller` according to `focusControl` * add `printSectionName` method to `listViewController` * remove arrow from `Close application` item * fix focus movement in `ServersListView` * `Restore from backup` is visible only on start screen * `I have nothing` is visible only on start screen * fix back button on `SelectLanguageDrawer` * rename `focusControl` to `qmlUtils` * fix `CMakeLists.txt` * fix `ScrollBarType` * fix `PageSetupWizardApiServicesList` * fix focus movement on dynamic delegates in listView * refactor `PageSetupWizardProtocols` * remove comments and clean up * fix `ListViewWithLabelsType` * fix `PageProtocolCloakSettings` * fix `PageSettingsAppSplitTunneling` * fix `PageDevMenu` * remove debug output from `FocusController` * remove debug output from `ListViewFocusController` * remove debug output from `focusControl` * `focusControl` => `FocusControl` --------- Co-authored-by: albexk Co-authored-by: Nethius --- CMakeLists.txt | 4 +- client/CMakeLists.txt | 2 + client/amnezia_application.cpp | 3 + client/amnezia_application.h | 2 + client/android/AndroidManifest.xml | 9 +- .../res/mipmap-anydpi-v26/ic_banner.xml | 5 - client/android/res/mipmap-hdpi/ic_banner.png | Bin 0 -> 15410 bytes client/android/res/mipmap-mdpi/ic_banner.png | Bin 0 -> 10138 bytes .../res/mipmap-xhdpi/ic_banner_foreground.png | Bin 12414 -> 0 bytes client/android/res/values-ru/strings.xml | 2 + .../res/values/ic_banner_background.xml | 4 - client/android/res/values/strings.xml | 2 + .../src/org/amnezia/vpn/AmneziaActivity.kt | 204 +++++- .../src/org/amnezia/vpn/TvFilePicker.kt | 45 ++ .../platforms/android/android_controller.cpp | 28 +- client/platforms/android/android_controller.h | 4 + client/resources.qrc | 384 +++++------ client/ui/controllers/focusController.cpp | 210 ++++++ client/ui/controllers/focusController.h | 57 ++ client/ui/controllers/importController.cpp | 22 +- .../controllers/listViewFocusController.cpp | 309 +++++++++ .../ui/controllers/listViewFocusController.h | 70 ++ client/ui/controllers/pageController.cpp | 18 +- client/ui/controllers/pageController.h | 7 +- client/ui/controllers/settingsController.cpp | 8 +- client/ui/controllers/sitesController.cpp | 6 +- client/ui/controllers/systemController.cpp | 34 +- client/ui/controllers/systemController.h | 6 +- client/ui/qml/Components/ConnectButton.qml | 26 + .../ConnectionTypeSelectionDrawer.qml | 23 +- .../qml/Components/HomeContainersListView.qml | 58 +- .../Components/HomeSplitTunnelingDrawer.qml | 31 +- .../ui/qml/Components/InstalledAppsDrawer.qml | 13 +- client/ui/qml/Components/QuestionDrawer.qml | 19 +- .../qml/Components/SelectLanguageDrawer.qml | 230 +++---- client/ui/qml/Components/ServersListView.qml | 126 ++++ .../Components/SettingsContainersListView.qml | 37 +- .../qml/Components/ShareConnectionDrawer.qml | 110 +-- .../qml/Components/TransportProtoSelector.qml | 2 - client/ui/qml/Controls2/BackButtonType.qml | 8 +- client/ui/qml/Controls2/BasicButtonType.qml | 29 +- client/ui/qml/Controls2/CardType.qml | 31 +- client/ui/qml/Controls2/CardWithIconsType.qml | 30 +- client/ui/qml/Controls2/DrawerType2.qml | 193 ++++-- client/ui/qml/Controls2/DropDownType.qml | 136 ++-- client/ui/qml/Controls2/FlickableType.qml | 5 +- client/ui/qml/Controls2/HeaderType.qml | 2 - .../qml/Controls2/HorizontalRadioButton.qml | 26 + client/ui/qml/Controls2/ImageButtonType.qml | 35 +- .../ui/qml/Controls2/LabelWithButtonType.qml | 26 + .../qml/Controls2/ListViewWithLabelsType.qml | 6 +- .../Controls2/ListViewWithRadioButtonType.qml | 193 +++--- client/ui/qml/Controls2/PageType.qml | 43 +- client/ui/qml/Controls2/PopupType.qml | 22 +- client/ui/qml/Controls2/ScrollBarType.qml | 11 + client/ui/qml/Controls2/SwitcherType.qml | 43 +- client/ui/qml/Controls2/TabButtonType.qml | 27 +- .../ui/qml/Controls2/TabImageButtonType.qml | 29 +- .../qml/Controls2/TextAreaWithFooterType.qml | 3 - .../qml/Controls2/TextFieldWithHeaderType.qml | 24 +- .../ui/qml/Controls2/VerticalRadioButton.qml | 27 +- client/ui/qml/Pages2/PageDevMenu.qml | 52 +- client/ui/qml/Pages2/PageHome.qml | 279 ++------ .../Pages2/PageProtocolAwgClientSettings.qml | 416 ++++++----- .../ui/qml/Pages2/PageProtocolAwgSettings.qml | 644 +++++++++--------- .../qml/Pages2/PageProtocolCloakSettings.qml | 22 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 45 +- client/ui/qml/Pages2/PageProtocolRaw.qml | 45 +- .../PageProtocolShadowSocksSettings.qml | 19 +- .../PageProtocolWireGuardClientSettings.qml | 2 - .../Pages2/PageProtocolWireGuardSettings.qml | 39 +- .../qml/Pages2/PageProtocolXraySettings.qml | 19 - .../ui/qml/Pages2/PageServiceDnsSettings.qml | 10 - .../ui/qml/Pages2/PageServiceSftpSettings.qml | 21 - .../Pages2/PageServiceSocksProxySettings.qml | 41 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 10 - client/ui/qml/Pages2/PageSettings.qml | 25 +- client/ui/qml/Pages2/PageSettingsAbout.qml | 227 +++--- .../Pages2/PageSettingsApiLanguageList.qml | 128 ++-- .../qml/Pages2/PageSettingsApiServerInfo.qml | 14 - .../Pages2/PageSettingsAppSplitTunneling.qml | 39 +- .../ui/qml/Pages2/PageSettingsApplication.qml | 38 +- client/ui/qml/Pages2/PageSettingsBackup.qml | 17 +- .../ui/qml/Pages2/PageSettingsConnection.qml | 41 +- client/ui/qml/Pages2/PageSettingsDns.qml | 21 +- client/ui/qml/Pages2/PageSettingsLogging.qml | 220 +++--- .../ui/qml/Pages2/PageSettingsServerData.qml | 20 - .../ui/qml/Pages2/PageSettingsServerInfo.qml | 105 +-- .../qml/Pages2/PageSettingsServerProtocol.qml | 219 +++--- .../Pages2/PageSettingsServerProtocols.qml | 76 +-- .../qml/Pages2/PageSettingsServerServices.qml | 72 +- .../ui/qml/Pages2/PageSettingsServersList.qml | 123 ++-- .../qml/Pages2/PageSettingsSplitTunneling.qml | 248 ++----- .../Pages2/PageSetupWizardApiServiceInfo.qml | 8 - .../Pages2/PageSetupWizardApiServicesList.qml | 23 +- .../Pages2/PageSetupWizardConfigSource.qml | 255 ++++--- .../qml/Pages2/PageSetupWizardCredentials.qml | 228 +++++-- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 15 +- .../PageSetupWizardProtocolSettings.qml | 78 +-- .../qml/Pages2/PageSetupWizardProtocols.qml | 147 ++-- client/ui/qml/Pages2/PageSetupWizardStart.qml | 9 - .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 12 - .../qml/Pages2/PageSetupWizardViewConfig.qml | 14 +- client/ui/qml/Pages2/PageShare.qml | 269 ++------ client/ui/qml/Pages2/PageShareFullAccess.qml | 22 +- client/ui/qml/Pages2/PageStart.qml | 73 +- client/ui/qml/main2.qml | 59 +- client/utils/qmlUtils.cpp | 128 ++++ client/utils/qmlUtils.h | 30 + 109 files changed, 4036 insertions(+), 3700 deletions(-) delete mode 100644 client/android/res/mipmap-anydpi-v26/ic_banner.xml create mode 100644 client/android/res/mipmap-hdpi/ic_banner.png create mode 100644 client/android/res/mipmap-mdpi/ic_banner.png delete mode 100644 client/android/res/mipmap-xhdpi/ic_banner_foreground.png delete mode 100644 client/android/res/values/ic_banner_background.xml create mode 100644 client/android/src/org/amnezia/vpn/TvFilePicker.kt create mode 100644 client/ui/controllers/focusController.cpp create mode 100644 client/ui/controllers/focusController.h create mode 100644 client/ui/controllers/listViewFocusController.cpp create mode 100644 client/ui/controllers/listViewFocusController.h create mode 100644 client/ui/qml/Components/ServersListView.qml create mode 100644 client/ui/qml/Controls2/ScrollBarType.qml create mode 100644 client/utils/qmlUtils.cpp create mode 100644 client/utils/qmlUtils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cb695631..98f3be14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.2.4 +project(${PROJECT} VERSION 4.8.3.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 2071) +set(APP_ANDROID_VERSION_CODE 2072) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 05f9f17c..3ef92385 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -146,6 +146,7 @@ set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h ${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h ${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h + ${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.h ) # Mozilla headres @@ -197,6 +198,7 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp ${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp + ${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.cpp ) # Mozilla sources diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 4e25097d..aeed439b 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -404,6 +404,9 @@ void AmneziaApplication::initControllers() m_pageController.reset(new PageController(m_serversModel, m_settings)); m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); + m_focusController.reset(new FocusController(m_engine, this)); + m_engine->rootContext()->setContextProperty("FocusController", m_focusController.get()); + m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_apiServicesModel, m_settings)); m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 64566216..cfeac0d1 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -19,6 +19,7 @@ #include "ui/controllers/exportController.h" #include "ui/controllers/importController.h" #include "ui/controllers/installController.h" +#include "ui/controllers/focusController.h" #include "ui/controllers/pageController.h" #include "ui/controllers/settingsController.h" #include "ui/controllers/sitesController.h" @@ -124,6 +125,7 @@ private: #endif QScopedPointer m_connectionController; + QScopedPointer m_focusController; QScopedPointer m_pageController; QScopedPointer m_installController; QScopedPointer m_importController; diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index 9e44e022..96f60f53 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -11,7 +11,7 @@ - + - +