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