Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/xray user management #972

Merged
merged 17 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 139 additions & 8 deletions client/configurators/xray_configurator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,169 @@
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#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> settings, const QSharedPointer<ServerController> &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)
{
// Generate new UUID for client
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);

// Get current server config
QString currentConfig = m_serverController->getTextFileFromContainer(
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);

if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to get server config file";
return "";
}

// Parse current config as JSON
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
if (doc.isNull() || !doc.isObject()) {
logger.error() << "Failed to parse server config JSON";
errorCode = ErrorCode::InternalError;
return "";
}

QJsonObject serverConfig = doc.object();

// Validate server config structure
if (!serverConfig.contains("inbounds")) {
logger.error() << "Server config missing 'inbounds' field";
errorCode = ErrorCode::InternalError;
return "";
}

QJsonArray inbounds = serverConfig["inbounds"].toArray();
if (inbounds.isEmpty()) {
logger.error() << "Server config has empty 'inbounds' array";
errorCode = ErrorCode::InternalError;
return "";
}

QJsonObject inbound = inbounds[0].toObject();
if (!inbound.contains("settings")) {
logger.error() << "Inbound missing 'settings' field";
errorCode = ErrorCode::InternalError;
return "";
}

QJsonObject settings = inbound["settings"].toObject();
if (!settings.contains("clients")) {
logger.error() << "Settings missing 'clients' field";
errorCode = ErrorCode::InternalError;
return "";
}

QJsonArray clients = settings["clients"].toArray();

// Create configuration for new client
QJsonObject clientConfig {
{"id", clientId},
{"flow", "xtls-rprx-vision"}
};

clients.append(clientConfig);

// Update config
settings["clients"] = clients;
inbound["settings"] = settings;
inbounds[0] = inbound;
serverConfig["inbounds"] = inbounds;

// Save updated config to server
QString updatedConfig = QJsonDocument(serverConfig).toJson();
errorCode = m_serverController->uploadTextFileToContainer(
container,
credentials,
updatedConfig,
amnezia::protocols::xray::serverConfigPath,
libssh::ScpOverwriteMode::ScpOverwriteExisting
);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to upload updated config";
return "";
}

// Restart container
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
errorCode = m_serverController->runScript(
credentials,
m_serverController->replaceVars(restartScript, m_serverController->genVarsForScript(credentials, container))
);

if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to restart container";
return "";
}

return clientId;
}

QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
{
// Get client ID from prepareServerConfig
QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, errorCode);
if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) {
logger.error() << "Failed to prepare server config";
errorCode = ErrorCode::InternalError;
return "";
}

QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
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 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);
if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
logger.error() << "Failed to get short ID";
errorCode = ErrorCode::InternalError;
return "";
}
xrayShortId.replace("\n", "");

if (errorCode != ErrorCode::NoError) {
// 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", xrayUuid);
config.replace("$XRAY_CLIENT_ID", xrayClientId);
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
config.replace("$XRAY_SHORT_ID", xrayShortId);

Expand Down
4 changes: 4 additions & 0 deletions client/configurators/xray_configurator.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class XrayConfigurator : public ConfiguratorBase

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
9 changes: 4 additions & 5 deletions client/ui/controllers/exportController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion client/ui/controllers/exportController.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading
Loading