mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ce06e451bc | |||
| e8cad3afe1 | |||
| b7d378ecb7 | |||
| 6e167e998b | |||
| dec410016e | |||
| ba2382924d |
@@ -4,6 +4,7 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
|
#include <QThread>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
@@ -137,117 +138,326 @@ amnezia::ProtocolConfig XrayConfigurator::processConfigWithLocalSettings(const a
|
|||||||
return protocolConfig;
|
return protocolConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode XrayConfigurator::uploadServerConfigJson(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const ContainerConfig &containerConfig,
|
const DnsSettings &dnsSettings, const QJsonObject &serverConfig) const
|
||||||
const DnsSettings &dnsSettings,
|
|
||||||
ErrorCode &errorCode)
|
|
||||||
{
|
{
|
||||||
// Generate new UUID for client
|
const QString updatedConfig = QJsonDocument(serverConfig).toJson();
|
||||||
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
ErrorCode errorCode = m_sshSession->uploadTextFileToContainer(
|
||||||
|
container, credentials, updatedConfig, amnezia::protocols::xray::serverConfigPath,
|
||||||
|
libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
logger.error() << "Failed to upload updated config";
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
// Get flow value from settings (default xtls-rprx-vision)
|
const QString restartScript = QStringLiteral("sudo docker restart $CONTAINER_NAME");
|
||||||
QString flowValue = "xtls-rprx-vision";
|
errorCode = m_sshSession->runScript(
|
||||||
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
|
credentials,
|
||||||
if (!xrayCfg->serverConfig.flow.isEmpty()) {
|
m_sshSession->replaceVars(restartScript,
|
||||||
flowValue = xrayCfg->serverConfig.flow;
|
amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns,
|
||||||
|
dnsSettings.secondaryDns)));
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
logger.error() << "Failed to restart container";
|
||||||
|
}
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode XrayConfigurator::readRealityKeyFiles(const DockerContainer container, const ServerCredentials &credentials,
|
||||||
|
QString &outPublicKey, QString &outShortId) const
|
||||||
|
{
|
||||||
|
outPublicKey.clear();
|
||||||
|
outShortId.clear();
|
||||||
|
|
||||||
|
auto readKeyFile = [&](const QString &path, QString &out) -> ErrorCode {
|
||||||
|
for (int attempt = 0; attempt < 3; ++attempt) {
|
||||||
|
ErrorCode fileError = ErrorCode::NoError;
|
||||||
|
out = QString::fromUtf8(m_sshSession->getTextFileFromContainer(container, credentials, path, fileError));
|
||||||
|
out.replace(QLatin1Char('\n'), QString());
|
||||||
|
out.replace(QLatin1Char('\r'), QString());
|
||||||
|
if (fileError == ErrorCode::NoError && !out.isEmpty()) {
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
}
|
||||||
|
if (attempt < 2) {
|
||||||
|
QThread::msleep(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.error() << "Xray readRealityKeyFiles: failed path=" << path;
|
||||||
|
return ErrorCode::XrayRealityKeysReadFailed;
|
||||||
|
};
|
||||||
|
|
||||||
|
ErrorCode errorCode = readKeyFile(QString::fromLatin1(amnezia::protocols::xray::PublicKeyPath), outPublicKey);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
return readKeyFile(QString::fromLatin1(amnezia::protocols::xray::shortidPath), outShortId);
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject XrayConfigurator::mergeStreamSettingsForServerInbound(const XrayServerConfig &srv,
|
||||||
|
const QJsonObject &existingStreamSettings) const
|
||||||
|
{
|
||||||
|
QJsonObject streamSettings = buildStreamSettings(srv, QString());
|
||||||
|
|
||||||
|
if (srv.security != QLatin1String("reality")) {
|
||||||
|
return streamSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonObject newRs = streamSettings[amnezia::protocols::xray::realitySettings].toObject();
|
||||||
|
QJsonObject oldRs = existingStreamSettings[amnezia::protocols::xray::realitySettings].toObject();
|
||||||
|
QJsonObject merged = oldRs.isEmpty() ? newRs : oldRs;
|
||||||
|
|
||||||
|
const QString siteEff = srv.site.isEmpty() ? QString::fromLatin1(amnezia::protocols::xray::defaultSite) : srv.site;
|
||||||
|
const QString sniEff = srv.sni.isEmpty() ? siteEff : srv.sni;
|
||||||
|
|
||||||
|
if (newRs.contains(amnezia::protocols::xray::fingerprint)) {
|
||||||
|
merged[amnezia::protocols::xray::fingerprint] = newRs[amnezia::protocols::xray::fingerprint];
|
||||||
|
}
|
||||||
|
merged[amnezia::protocols::xray::serverNames] = QJsonArray { sniEff };
|
||||||
|
if (!merged.contains(QStringLiteral("dest"))) {
|
||||||
|
merged[QStringLiteral("dest")] = siteEff + QStringLiteral(":443");
|
||||||
|
}
|
||||||
|
|
||||||
|
streamSettings[amnezia::protocols::xray::realitySettings] = merged;
|
||||||
|
return streamSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode XrayConfigurator::applyServerSettingsToRemote(const ServerCredentials &credentials, DockerContainer container,
|
||||||
|
ContainerConfig &containerConfig, const DnsSettings &dnsSettings,
|
||||||
|
bool appendNewClient, QString *outClientId)
|
||||||
|
{
|
||||||
|
ErrorCode errorCode = ErrorCode::NoError;
|
||||||
|
const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>();
|
||||||
|
if (!xrayCfg) {
|
||||||
|
logger.error() << "Xray applyServerSettings: missing XrayProtocolConfig";
|
||||||
|
return ErrorCode::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const XrayServerConfig &srv = xrayCfg->serverConfig;
|
||||||
|
if (srv.isThirdPartyConfig) {
|
||||||
|
logger.info() << "Xray applyServerSettings: skipped (third-party/native profile)";
|
||||||
|
if (outClientId && xrayCfg->hasClientConfig()) {
|
||||||
|
*outClientId = xrayCfg->clientConfig->id;
|
||||||
|
}
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info() << "Xray applyServerSettings: start"
|
||||||
|
<< "container=" << static_cast<int>(container) << "host=" << credentials.hostName
|
||||||
|
<< "transport=" << srv.transport << "security=" << srv.security << "port=" << srv.port
|
||||||
|
<< "appendClient=" << appendNewClient;
|
||||||
|
QString flowValue = srv.flow;
|
||||||
|
if (flowValue.isEmpty() && srv.security == QLatin1String("reality")) {
|
||||||
|
flowValue = QStringLiteral("xtls-rprx-vision");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString realityPublicKey;
|
||||||
|
QString realityShortId;
|
||||||
|
if (srv.security == QLatin1String("reality")) {
|
||||||
|
errorCode = readRealityKeyFiles(container, credentials, realityPublicKey, realityShortId);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
logger.error() << "Xray applyServerSettings: readRealityKeyFiles failed, error="
|
||||||
|
<< static_cast<int>(errorCode);
|
||||||
|
return errorCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current server config
|
|
||||||
QString currentConfig = m_sshSession->getTextFileFromContainer(
|
QString currentConfig = m_sshSession->getTextFileFromContainer(
|
||||||
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
|
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
|
||||||
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
logger.error() << "Failed to get server config file";
|
logger.error() << "Xray applyServerSettings: getTextFileFromContainer failed, error="
|
||||||
return "";
|
<< static_cast<int>(errorCode) << "path=" << amnezia::protocols::xray::serverConfigPath;
|
||||||
|
return errorCode;
|
||||||
}
|
}
|
||||||
|
logger.info() << "Xray applyServerSettings: read server config, bytes=" << currentConfig.size();
|
||||||
|
|
||||||
// Parse current config as JSON
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
|
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
|
||||||
if (doc.isNull() || !doc.isObject()) {
|
if (doc.isNull() || !doc.isObject()) {
|
||||||
logger.error() << "Failed to parse server config JSON";
|
logger.error() << "Failed to parse server config JSON";
|
||||||
errorCode = ErrorCode::InternalError;
|
return ErrorCode::XrayServerConfigInvalid;
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject serverConfig = doc.object();
|
QJsonObject serverConfig = doc.object();
|
||||||
|
|
||||||
// Validate server config structure
|
|
||||||
if (!serverConfig.contains(amnezia::protocols::xray::inbounds)) {
|
if (!serverConfig.contains(amnezia::protocols::xray::inbounds)) {
|
||||||
logger.error() << "Server config missing 'inbounds' field";
|
logger.error() << "Server config missing 'inbounds' field";
|
||||||
errorCode = ErrorCode::InternalError;
|
return ErrorCode::XrayServerConfigInvalid;
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonArray inbounds = serverConfig[amnezia::protocols::xray::inbounds].toArray();
|
QJsonArray inbounds = serverConfig[amnezia::protocols::xray::inbounds].toArray();
|
||||||
if (inbounds.isEmpty()) {
|
if (inbounds.isEmpty()) {
|
||||||
logger.error() << "Server config has empty 'inbounds' array";
|
logger.error() << "Server config has empty 'inbounds' array";
|
||||||
errorCode = ErrorCode::InternalError;
|
return ErrorCode::XrayServerConfigInvalid;
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject inbound = inbounds[0].toObject();
|
QJsonObject inbound = inbounds[0].toObject();
|
||||||
if (!inbound.contains(amnezia::protocols::xray::settings)) {
|
if (!inbound.contains(amnezia::protocols::xray::settings)) {
|
||||||
logger.error() << "Inbound missing 'settings' field";
|
logger.error() << "Inbound missing 'settings' field";
|
||||||
errorCode = ErrorCode::InternalError;
|
return ErrorCode::XrayServerConfigInvalid;
|
||||||
return "";
|
}
|
||||||
|
|
||||||
|
const QJsonObject existingStream = inbound[amnezia::protocols::xray::streamSettings].toObject();
|
||||||
|
inbound[amnezia::protocols::xray::streamSettings] = mergeStreamSettingsForServerInbound(srv, existingStream);
|
||||||
|
|
||||||
|
if (!srv.port.isEmpty()) {
|
||||||
|
inbound[amnezia::protocols::xray::port] = srv.port.toInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject settings = inbound[amnezia::protocols::xray::settings].toObject();
|
QJsonObject settings = inbound[amnezia::protocols::xray::settings].toObject();
|
||||||
if (!settings.contains(amnezia::protocols::xray::clients)) {
|
if (!settings.contains(amnezia::protocols::xray::clients)) {
|
||||||
logger.error() << "Settings missing 'clients' field";
|
settings[amnezia::protocols::xray::clients] = QJsonArray {};
|
||||||
errorCode = ErrorCode::InternalError;
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonArray clients = settings[amnezia::protocols::xray::clients].toArray();
|
QJsonArray clients = settings[amnezia::protocols::xray::clients].toArray();
|
||||||
|
QString clientId;
|
||||||
|
|
||||||
// Create configuration for new client
|
if (appendNewClient) {
|
||||||
QJsonObject clientConfig {
|
clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||||
{amnezia::protocols::xray::id, clientId},
|
QJsonObject clientEntry;
|
||||||
};
|
clientEntry[amnezia::protocols::xray::id] = clientId;
|
||||||
clientConfig[amnezia::protocols::xray::id] = clientId;
|
if (!flowValue.isEmpty()) {
|
||||||
if (!flowValue.isEmpty()) {
|
clientEntry[amnezia::protocols::xray::flow] = flowValue;
|
||||||
clientConfig[amnezia::protocols::xray::flow] = flowValue;
|
}
|
||||||
|
clients.append(clientEntry);
|
||||||
|
} else {
|
||||||
|
if (clients.isEmpty()) {
|
||||||
|
logger.error() << "Server config has no VLESS clients";
|
||||||
|
return ErrorCode::XrayServerNoVlessClients;
|
||||||
|
}
|
||||||
|
clientId = clients[0].toObject()[amnezia::protocols::xray::id].toString();
|
||||||
|
if (clientId.isEmpty()) {
|
||||||
|
logger.error() << "Server config VLESS client has empty id";
|
||||||
|
return ErrorCode::XrayServerNoVlessClients;
|
||||||
|
}
|
||||||
|
QJsonArray updatedClients;
|
||||||
|
for (const QJsonValue &v : clients) {
|
||||||
|
QJsonObject c = v.toObject();
|
||||||
|
if (flowValue.isEmpty()) {
|
||||||
|
c.remove(amnezia::protocols::xray::flow);
|
||||||
|
} else {
|
||||||
|
c[amnezia::protocols::xray::flow] = flowValue;
|
||||||
|
}
|
||||||
|
updatedClients.append(c);
|
||||||
|
}
|
||||||
|
clients = updatedClients;
|
||||||
}
|
}
|
||||||
|
|
||||||
clients.append(clientConfig);
|
|
||||||
|
|
||||||
// Update config
|
|
||||||
settings[amnezia::protocols::xray::clients] = clients;
|
settings[amnezia::protocols::xray::clients] = clients;
|
||||||
inbound[amnezia::protocols::xray::settings] = settings;
|
inbound[amnezia::protocols::xray::settings] = settings;
|
||||||
inbounds[0] = inbound;
|
inbounds[0] = inbound;
|
||||||
serverConfig[amnezia::protocols::xray::inbounds] = inbounds;
|
serverConfig[amnezia::protocols::xray::inbounds] = inbounds;
|
||||||
|
|
||||||
// Save updated config to server
|
errorCode = uploadServerConfigJson(credentials, container, dnsSettings, serverConfig);
|
||||||
QString updatedConfig = QJsonDocument(serverConfig).toJson();
|
|
||||||
errorCode = m_sshSession->uploadTextFileToContainer(
|
|
||||||
container,
|
|
||||||
credentials,
|
|
||||||
updatedConfig,
|
|
||||||
amnezia::protocols::xray::serverConfigPath,
|
|
||||||
libssh::ScpOverwriteMode::ScpOverwriteExisting
|
|
||||||
);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
logger.error() << "Failed to upload updated config";
|
logger.error() << "Xray applyServerSettings: upload/restart failed, error=" << static_cast<int>(errorCode);
|
||||||
return "";
|
return errorCode;
|
||||||
|
}
|
||||||
|
logger.info() << "Xray applyServerSettings: server config uploaded and container restarted";
|
||||||
|
|
||||||
|
if (outClientId) {
|
||||||
|
*outClientId = clientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart container
|
XrayProtocolConfig updated =
|
||||||
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
|
buildClientProtocolConfig(credentials, container, srv, clientId, errorCode, realityPublicKey, realityShortId);
|
||||||
errorCode = m_sshSession->runScript(
|
|
||||||
credentials,
|
|
||||||
m_sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
logger.error() << "Failed to restart container";
|
logger.error() << "Xray applyServerSettings: buildClientProtocolConfig failed, error="
|
||||||
return "";
|
<< static_cast<int>(errorCode);
|
||||||
|
return errorCode;
|
||||||
}
|
}
|
||||||
|
containerConfig.protocolConfig = updated;
|
||||||
|
logger.info() << "Xray applyServerSettings: done, clientId=" << clientId;
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
|
const ContainerConfig &containerConfig,
|
||||||
|
const DnsSettings &dnsSettings,
|
||||||
|
ErrorCode &errorCode)
|
||||||
|
{
|
||||||
|
ContainerConfig mutableConfig = containerConfig;
|
||||||
|
QString clientId;
|
||||||
|
const ErrorCode applyError =
|
||||||
|
applyServerSettingsToRemote(credentials, container, mutableConfig, dnsSettings, true, &clientId);
|
||||||
|
errorCode = applyError;
|
||||||
|
if (applyError != ErrorCode::NoError || clientId.isEmpty()) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
return clientId;
|
return clientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XrayProtocolConfig XrayConfigurator::buildClientProtocolConfig(const ServerCredentials &credentials,
|
||||||
|
DockerContainer container,
|
||||||
|
const XrayServerConfig &srv, const QString &clientId,
|
||||||
|
ErrorCode &errorCode,
|
||||||
|
const QString &prefetchedRealityPublicKey,
|
||||||
|
const QString &prefetchedRealityShortId) const
|
||||||
|
{
|
||||||
|
QString xrayPublicKey = prefetchedRealityPublicKey;
|
||||||
|
QString xrayShortId = prefetchedRealityShortId;
|
||||||
|
|
||||||
|
if (srv.security == QLatin1String("reality")) {
|
||||||
|
if (xrayPublicKey.isEmpty() || xrayShortId.isEmpty()) {
|
||||||
|
errorCode = readRealityKeyFiles(container, credentials, xrayPublicKey, xrayShortId);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject userObj;
|
||||||
|
userObj[amnezia::protocols::xray::id] = clientId;
|
||||||
|
userObj[amnezia::protocols::xray::encryption] = QStringLiteral("none");
|
||||||
|
if (!srv.flow.isEmpty()) {
|
||||||
|
userObj[amnezia::protocols::xray::flow] = srv.flow;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject vnextEntry;
|
||||||
|
vnextEntry[amnezia::protocols::xray::address] = credentials.hostName;
|
||||||
|
vnextEntry[amnezia::protocols::xray::port] =
|
||||||
|
srv.port.isEmpty() ? QString(amnezia::protocols::xray::defaultPort).toInt() : srv.port.toInt();
|
||||||
|
vnextEntry[amnezia::protocols::xray::users] = QJsonArray { userObj };
|
||||||
|
|
||||||
|
QJsonObject outboundSettings;
|
||||||
|
outboundSettings[amnezia::protocols::xray::vnext] = QJsonArray { vnextEntry };
|
||||||
|
|
||||||
|
QJsonObject outbound;
|
||||||
|
outbound[QStringLiteral("protocol")] = QStringLiteral("vless");
|
||||||
|
outbound[amnezia::protocols::xray::settings] = outboundSettings;
|
||||||
|
|
||||||
|
QJsonObject streamObj = buildStreamSettings(srv, clientId);
|
||||||
|
if (srv.security == QLatin1String("reality")) {
|
||||||
|
QJsonObject rs = streamObj[amnezia::protocols::xray::realitySettings].toObject();
|
||||||
|
rs[amnezia::protocols::xray::publicKey] = xrayPublicKey;
|
||||||
|
rs[amnezia::protocols::xray::shortId] = xrayShortId;
|
||||||
|
rs[amnezia::protocols::xray::spiderX] = QString();
|
||||||
|
streamObj[amnezia::protocols::xray::realitySettings] = rs;
|
||||||
|
}
|
||||||
|
|
||||||
|
outbound[amnezia::protocols::xray::streamSettings] = streamObj;
|
||||||
|
|
||||||
|
QJsonObject inboundObj;
|
||||||
|
inboundObj[QStringLiteral("listen")] = amnezia::protocols::xray::defaultLocalListenAddr;
|
||||||
|
inboundObj[amnezia::protocols::xray::port] = amnezia::protocols::xray::defaultLocalProxyPort;
|
||||||
|
inboundObj[QStringLiteral("protocol")] = QStringLiteral("socks");
|
||||||
|
inboundObj[amnezia::protocols::xray::settings] = QJsonObject { { QStringLiteral("udp"), true } };
|
||||||
|
|
||||||
|
QJsonObject clientJson;
|
||||||
|
clientJson[QStringLiteral("log")] = QJsonObject { { QStringLiteral("loglevel"), QStringLiteral("error") } };
|
||||||
|
clientJson[amnezia::protocols::xray::inbounds] = QJsonArray { inboundObj };
|
||||||
|
clientJson[amnezia::protocols::xray::outbounds] = QJsonArray { outbound };
|
||||||
|
|
||||||
|
const QString config = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact));
|
||||||
|
|
||||||
|
XrayProtocolConfig protocolConfig;
|
||||||
|
protocolConfig.serverConfig = srv;
|
||||||
|
|
||||||
|
XrayClientConfig clientConfig;
|
||||||
|
clientConfig.nativeConfig = config;
|
||||||
|
clientConfig.localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort);
|
||||||
|
clientConfig.id = clientId;
|
||||||
|
protocolConfig.setClientConfig(clientConfig);
|
||||||
|
|
||||||
|
return protocolConfig;
|
||||||
|
}
|
||||||
|
|
||||||
QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, const QString &clientId) const
|
QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, const QString &clientId) const
|
||||||
{
|
{
|
||||||
QJsonObject streamSettings;
|
QJsonObject streamSettings;
|
||||||
@@ -419,6 +629,13 @@ ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentia
|
|||||||
const DnsSettings &dnsSettings,
|
const DnsSettings &dnsSettings,
|
||||||
ErrorCode &errorCode)
|
ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
|
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
|
||||||
|
if (xrayCfg->serverConfig.isThirdPartyConfig && xrayCfg->hasClientConfig()) {
|
||||||
|
logger.info() << "Xray createConfig: returning existing third-party client config without server SSH";
|
||||||
|
return *xrayCfg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const XrayServerConfig *serverConfig = nullptr;
|
const XrayServerConfig *serverConfig = nullptr;
|
||||||
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
|
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
|
||||||
serverConfig = &xrayCfg->serverConfig;
|
serverConfig = &xrayCfg->serverConfig;
|
||||||
@@ -441,93 +658,5 @@ ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentia
|
|||||||
return XrayProtocolConfig{};
|
return XrayProtocolConfig{};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch server keys (Reality only)
|
return buildClientProtocolConfig(credentials, container, srv, xrayClientId, errorCode);
|
||||||
QString xrayPublicKey;
|
|
||||||
QString xrayShortId;
|
|
||||||
|
|
||||||
if (srv.security == "reality") {
|
|
||||||
xrayPublicKey = m_sshSession->getTextFileFromContainer(container, credentials,
|
|
||||||
amnezia::protocols::xray::PublicKeyPath, errorCode);
|
|
||||||
if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
|
|
||||||
logger.error() << "Failed to get public key";
|
|
||||||
if (errorCode == ErrorCode::NoError) {
|
|
||||||
errorCode = ErrorCode::InternalError;
|
|
||||||
}
|
|
||||||
return XrayProtocolConfig{};
|
|
||||||
}
|
|
||||||
xrayPublicKey.replace("\n", "");
|
|
||||||
|
|
||||||
xrayShortId = m_sshSession->getTextFileFromContainer(container, credentials,
|
|
||||||
amnezia::protocols::xray::shortidPath, errorCode);
|
|
||||||
if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
|
|
||||||
logger.error() << "Failed to get short ID";
|
|
||||||
if (errorCode == ErrorCode::NoError) {
|
|
||||||
errorCode = ErrorCode::InternalError;
|
|
||||||
}
|
|
||||||
return XrayProtocolConfig{};
|
|
||||||
}
|
|
||||||
xrayShortId.replace("\n", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build outbound
|
|
||||||
QJsonObject userObj;
|
|
||||||
userObj[amnezia::protocols::xray::id] = xrayClientId;
|
|
||||||
userObj[amnezia::protocols::xray::encryption] = "none";
|
|
||||||
if (!srv.flow.isEmpty()) {
|
|
||||||
userObj[amnezia::protocols::xray::flow] = srv.flow;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject vnextEntry;
|
|
||||||
vnextEntry[amnezia::protocols::xray::address] = credentials.hostName;
|
|
||||||
vnextEntry[amnezia::protocols::xray::port] = srv.port.toInt();
|
|
||||||
vnextEntry[amnezia::protocols::xray::users] = QJsonArray { userObj };
|
|
||||||
|
|
||||||
QJsonObject outboundSettings;
|
|
||||||
outboundSettings[amnezia::protocols::xray::vnext] = QJsonArray { vnextEntry };
|
|
||||||
|
|
||||||
QJsonObject outbound;
|
|
||||||
outbound["protocol"] = "vless";
|
|
||||||
outbound[amnezia::protocols::xray::settings] = outboundSettings;
|
|
||||||
|
|
||||||
// Build streamSettings
|
|
||||||
QJsonObject streamObj = buildStreamSettings(srv, xrayClientId);
|
|
||||||
|
|
||||||
// Inject Reality keys
|
|
||||||
if (srv.security == "reality") {
|
|
||||||
QJsonObject rs = streamObj[amnezia::protocols::xray::realitySettings].toObject();
|
|
||||||
rs[amnezia::protocols::xray::publicKey] = xrayPublicKey;
|
|
||||||
rs[amnezia::protocols::xray::shortId] = xrayShortId;
|
|
||||||
rs[amnezia::protocols::xray::spiderX] = "";
|
|
||||||
streamObj[amnezia::protocols::xray::realitySettings] = rs;
|
|
||||||
}
|
|
||||||
|
|
||||||
outbound[amnezia::protocols::xray::streamSettings] = streamObj;
|
|
||||||
|
|
||||||
// Build full client config
|
|
||||||
QJsonObject inboundObj;
|
|
||||||
inboundObj["listen"] = amnezia::protocols::xray::defaultLocalListenAddr;
|
|
||||||
inboundObj[amnezia::protocols::xray::port] = amnezia::protocols::xray::defaultLocalProxyPort;
|
|
||||||
inboundObj["protocol"] = "socks";
|
|
||||||
inboundObj[amnezia::protocols::xray::settings] = QJsonObject { { "udp", true } };
|
|
||||||
|
|
||||||
QJsonObject clientJson;
|
|
||||||
clientJson["log"] = QJsonObject { { "loglevel", "error" } };
|
|
||||||
clientJson[amnezia::protocols::xray::inbounds] = QJsonArray { inboundObj };
|
|
||||||
clientJson[amnezia::protocols::xray::outbounds] = QJsonArray { outbound };
|
|
||||||
|
|
||||||
QString config = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact));
|
|
||||||
|
|
||||||
// Return
|
|
||||||
XrayProtocolConfig protocolConfig;
|
|
||||||
protocolConfig.serverConfig = srv;
|
|
||||||
|
|
||||||
XrayClientConfig clientConfig;
|
|
||||||
clientConfig.nativeConfig = config;
|
|
||||||
qDebug() << "config:" << config;
|
|
||||||
clientConfig.localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort);
|
|
||||||
clientConfig.id = xrayClientId;
|
|
||||||
|
|
||||||
protocolConfig.setClientConfig(clientConfig);
|
|
||||||
|
|
||||||
return protocolConfig;
|
|
||||||
}
|
}
|
||||||
@@ -23,12 +23,37 @@ public:
|
|||||||
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
|
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
|
||||||
amnezia::ProtocolConfig protocolConfig) override;
|
amnezia::ProtocolConfig protocolConfig) override;
|
||||||
|
|
||||||
|
amnezia::ErrorCode applyServerSettingsToRemote(const amnezia::ServerCredentials &credentials,
|
||||||
|
amnezia::DockerContainer container,
|
||||||
|
amnezia::ContainerConfig &containerConfig,
|
||||||
|
const amnezia::DnsSettings &dnsSettings,
|
||||||
|
bool appendNewClient,
|
||||||
|
QString *outClientId = nullptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString prepareServerConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
|
QString prepareServerConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
|
||||||
const amnezia::DnsSettings &dnsSettings,
|
const amnezia::DnsSettings &dnsSettings,
|
||||||
amnezia::ErrorCode &errorCode);
|
amnezia::ErrorCode &errorCode);
|
||||||
|
|
||||||
// Builds the native xray "streamSettings" JSON object from XrayServerConfig
|
amnezia::ErrorCode uploadServerConfigJson(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
|
||||||
|
const amnezia::DnsSettings &dnsSettings, const QJsonObject &serverConfig) const;
|
||||||
|
|
||||||
|
amnezia::XrayProtocolConfig buildClientProtocolConfig(const amnezia::ServerCredentials &credentials,
|
||||||
|
amnezia::DockerContainer container,
|
||||||
|
const amnezia::XrayServerConfig &srv,
|
||||||
|
const QString &clientId,
|
||||||
|
amnezia::ErrorCode &errorCode,
|
||||||
|
const QString &prefetchedRealityPublicKey = {},
|
||||||
|
const QString &prefetchedRealityShortId = {}) const;
|
||||||
|
|
||||||
|
amnezia::ErrorCode readRealityKeyFiles(amnezia::DockerContainer container,
|
||||||
|
const amnezia::ServerCredentials &credentials,
|
||||||
|
QString &outPublicKey,
|
||||||
|
QString &outShortId) const;
|
||||||
|
|
||||||
|
QJsonObject mergeStreamSettingsForServerInbound(const amnezia::XrayServerConfig &srv,
|
||||||
|
const QJsonObject &existingStreamSettings) const;
|
||||||
|
|
||||||
QJsonObject buildStreamSettings(const amnezia::XrayServerConfig &srv,
|
QJsonObject buildStreamSettings(const amnezia::XrayServerConfig &srv,
|
||||||
const QString &clientId) const;
|
const QString &clientId) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "core/installers/sftpInstaller.h"
|
#include "core/installers/sftpInstaller.h"
|
||||||
#include "core/installers/socks5Installer.h"
|
#include "core/installers/socks5Installer.h"
|
||||||
#include "core/installers/mtProxyInstaller.h"
|
#include "core/installers/mtProxyInstaller.h"
|
||||||
|
#include "core/configurators/xrayConfigurator.h"
|
||||||
#include "core/installers/telemtInstaller.h"
|
#include "core/installers/telemtInstaller.h"
|
||||||
#include "core/installers/torInstaller.h"
|
#include "core/installers/torInstaller.h"
|
||||||
#include "core/installers/wireguardInstaller.h"
|
#include "core/installers/wireguardInstaller.h"
|
||||||
@@ -186,6 +187,16 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
|||||||
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||||
qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
|
qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
|
||||||
|
|
||||||
|
bool xrayServerSettingsChanged = false;
|
||||||
|
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
|
||||||
|
const auto *oldXrayConfig = oldConfig.getXrayProtocolConfig();
|
||||||
|
const auto *newXrayConfig = newConfig.getXrayProtocolConfig();
|
||||||
|
if (oldXrayConfig && newXrayConfig) {
|
||||||
|
xrayServerSettingsChanged =
|
||||||
|
!oldXrayConfig->serverConfig.hasEqualServerSettings(newXrayConfig->serverConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ErrorCode errorCode = ErrorCode::NoError;
|
ErrorCode errorCode = ErrorCode::NoError;
|
||||||
if (reinstallRequired) {
|
if (reinstallRequired) {
|
||||||
errorCode = setupContainer(credentials, container, newConfig, true);
|
errorCode = setupContainer(credentials, container, newConfig, true);
|
||||||
@@ -196,6 +207,21 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bool skipXrayInboundSync =
|
||||||
|
newConfig.getXrayProtocolConfig() && newConfig.getXrayProtocolConfig()->serverConfig.isThirdPartyConfig;
|
||||||
|
|
||||||
|
if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged && !skipXrayInboundSync) {
|
||||||
|
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
|
||||||
|
XrayConfigurator xrayConfigurator(&sshSession);
|
||||||
|
qDebug() << "InstallController::updateContainer applying Xray server inbound sync, reinstall="
|
||||||
|
<< reinstallRequired;
|
||||||
|
errorCode = xrayConfigurator.applyServerSettingsToRemote(credentials, container, newConfig, dnsSettings, false);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
qDebug() << "InstallController::updateContainer Xray inbound sync failed, error="
|
||||||
|
<< static_cast<int>(errorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (errorCode == ErrorCode::NoError) {
|
if (errorCode == ErrorCode::NoError) {
|
||||||
if (container == DockerContainer::MtProxy) {
|
if (container == DockerContainer::MtProxy) {
|
||||||
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||||
@@ -643,12 +669,19 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
|
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
|
||||||
const auto* oldXrayConfig = oldConfig.getXrayProtocolConfig();
|
const auto *oldXrayConfig = oldConfig.getXrayProtocolConfig();
|
||||||
const auto* newXrayConfig = newConfig.getXrayProtocolConfig();
|
const auto *newXrayConfig = newConfig.getXrayProtocolConfig();
|
||||||
|
|
||||||
if (oldXrayConfig && newXrayConfig) {
|
if (oldXrayConfig && newXrayConfig) {
|
||||||
if (oldXrayConfig->serverConfig.port != newXrayConfig->serverConfig.port)
|
const QString oldPort = oldXrayConfig->serverConfig.port.isEmpty()
|
||||||
|
? QString(protocols::xray::defaultPort)
|
||||||
|
: oldXrayConfig->serverConfig.port;
|
||||||
|
const QString newPort = newXrayConfig->serverConfig.port.isEmpty()
|
||||||
|
? QString(protocols::xray::defaultPort)
|
||||||
|
: newXrayConfig->serverConfig.port;
|
||||||
|
if (oldPort != newPort) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,35 +108,114 @@ QJsonObject XrayXhttpConfig::toJson() const
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
XrayXhttpConfig clearedXhttpConfig()
|
||||||
|
{
|
||||||
|
XrayXhttpConfig c;
|
||||||
|
c.mode = QString();
|
||||||
|
c.host = QString();
|
||||||
|
c.path = QString();
|
||||||
|
c.headersTemplate = QString();
|
||||||
|
c.uplinkMethod = QString();
|
||||||
|
c.disableGrpc = false;
|
||||||
|
c.disableSse = false;
|
||||||
|
c.sessionPlacement = QString();
|
||||||
|
c.sessionKey = QString();
|
||||||
|
c.seqPlacement = QString();
|
||||||
|
c.seqKey = QString();
|
||||||
|
c.uplinkDataPlacement = QString();
|
||||||
|
c.uplinkDataKey = QString();
|
||||||
|
c.uplinkChunkSize = QString();
|
||||||
|
c.scMaxBufferedPosts = QString();
|
||||||
|
c.scMaxEachPostBytesMin = QString();
|
||||||
|
c.scMaxEachPostBytesMax = QString();
|
||||||
|
c.scMinPostsIntervalMsMin = QString();
|
||||||
|
c.scMinPostsIntervalMsMax = QString();
|
||||||
|
c.scStreamUpServerSecsMin = QString();
|
||||||
|
c.scStreamUpServerSecsMax = QString();
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
XrayXhttpConfig XrayXhttpConfig::fromJson(const QJsonObject &json)
|
XrayXhttpConfig XrayXhttpConfig::fromJson(const QJsonObject &json)
|
||||||
{
|
{
|
||||||
XrayXhttpConfig c;
|
if (json.isEmpty()) {
|
||||||
c.mode = json.value(configKey::xhttpMode).toString(protocols::xray::defaultXhttpMode);
|
return clearedXhttpConfig();
|
||||||
c.host = json.value(configKey::xhttpHost).toString(protocols::xray::defaultSite);
|
}
|
||||||
c.path = json.value(configKey::xhttpPath).toString();
|
|
||||||
c.headersTemplate = json.value(configKey::xhttpHeadersTemplate).toString(protocols::xray::defaultXhttpHeadersTemplate);
|
|
||||||
c.uplinkMethod = json.value(configKey::xhttpUplinkMethod).toString(protocols::xray::defaultXhttpUplinkMethod);
|
|
||||||
c.disableGrpc = json.value(configKey::xhttpDisableGrpc).toBool(true);
|
|
||||||
c.disableSse = json.value(configKey::xhttpDisableSse).toBool(true);
|
|
||||||
|
|
||||||
c.sessionPlacement = json.value(configKey::xhttpSessionPlacement).toString(protocols::xray::defaultXhttpSessionPlacement);
|
XrayXhttpConfig c = clearedXhttpConfig();
|
||||||
c.sessionKey = json.value(configKey::xhttpSessionKey).toString();
|
|
||||||
c.seqPlacement = json.value(configKey::xhttpSeqPlacement).toString(protocols::xray::defaultXhttpSessionPlacement);
|
|
||||||
c.seqKey = json.value(configKey::xhttpSeqKey).toString();
|
|
||||||
c.uplinkDataPlacement = json.value(configKey::xhttpUplinkDataPlacement).toString(protocols::xray::defaultXhttpUplinkDataPlacement);
|
|
||||||
c.uplinkDataKey = json.value(configKey::xhttpUplinkDataKey).toString();
|
|
||||||
|
|
||||||
c.uplinkChunkSize = json.value(configKey::xhttpUplinkChunkSize).toString("0");
|
if (json.contains(configKey::xhttpMode)) {
|
||||||
c.scMaxBufferedPosts = json.value(configKey::xhttpScMaxBufferedPosts).toString();
|
c.mode = json.value(configKey::xhttpMode).toString();
|
||||||
c.scMaxEachPostBytesMin = json.value(configKey::xhttpScMaxEachPostBytesMin).toString("1");
|
}
|
||||||
c.scMaxEachPostBytesMax = json.value(configKey::xhttpScMaxEachPostBytesMax).toString("100");
|
if (json.contains(configKey::xhttpHost)) {
|
||||||
c.scMinPostsIntervalMsMin = json.value(configKey::xhttpScMinPostsIntervalMsMin).toString("100");
|
c.host = json.value(configKey::xhttpHost).toString();
|
||||||
c.scMinPostsIntervalMsMax = json.value(configKey::xhttpScMinPostsIntervalMsMax).toString("800");
|
}
|
||||||
c.scStreamUpServerSecsMin = json.value(configKey::xhttpScStreamUpServerSecsMin).toString("1");
|
if (json.contains(configKey::xhttpPath)) {
|
||||||
c.scStreamUpServerSecsMax = json.value(configKey::xhttpScStreamUpServerSecsMax).toString("100");
|
c.path = json.value(configKey::xhttpPath).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpHeadersTemplate)) {
|
||||||
|
c.headersTemplate = json.value(configKey::xhttpHeadersTemplate).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpUplinkMethod)) {
|
||||||
|
c.uplinkMethod = json.value(configKey::xhttpUplinkMethod).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpDisableGrpc)) {
|
||||||
|
c.disableGrpc = json.value(configKey::xhttpDisableGrpc).toBool();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpDisableSse)) {
|
||||||
|
c.disableSse = json.value(configKey::xhttpDisableSse).toBool();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpSessionPlacement)) {
|
||||||
|
c.sessionPlacement = json.value(configKey::xhttpSessionPlacement).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpSessionKey)) {
|
||||||
|
c.sessionKey = json.value(configKey::xhttpSessionKey).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpSeqPlacement)) {
|
||||||
|
c.seqPlacement = json.value(configKey::xhttpSeqPlacement).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpSeqKey)) {
|
||||||
|
c.seqKey = json.value(configKey::xhttpSeqKey).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpUplinkDataPlacement)) {
|
||||||
|
c.uplinkDataPlacement = json.value(configKey::xhttpUplinkDataPlacement).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpUplinkDataKey)) {
|
||||||
|
c.uplinkDataKey = json.value(configKey::xhttpUplinkDataKey).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpUplinkChunkSize)) {
|
||||||
|
c.uplinkChunkSize = json.value(configKey::xhttpUplinkChunkSize).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpScMaxBufferedPosts)) {
|
||||||
|
c.scMaxBufferedPosts = json.value(configKey::xhttpScMaxBufferedPosts).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpScMaxEachPostBytesMin)) {
|
||||||
|
c.scMaxEachPostBytesMin = json.value(configKey::xhttpScMaxEachPostBytesMin).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpScMaxEachPostBytesMax)) {
|
||||||
|
c.scMaxEachPostBytesMax = json.value(configKey::xhttpScMaxEachPostBytesMax).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpScMinPostsIntervalMsMin)) {
|
||||||
|
c.scMinPostsIntervalMsMin = json.value(configKey::xhttpScMinPostsIntervalMsMin).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpScMinPostsIntervalMsMax)) {
|
||||||
|
c.scMinPostsIntervalMsMax = json.value(configKey::xhttpScMinPostsIntervalMsMax).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpScStreamUpServerSecsMin)) {
|
||||||
|
c.scStreamUpServerSecsMin = json.value(configKey::xhttpScStreamUpServerSecsMin).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xhttpScStreamUpServerSecsMax)) {
|
||||||
|
c.scStreamUpServerSecsMax = json.value(configKey::xhttpScStreamUpServerSecsMax).toString();
|
||||||
|
}
|
||||||
|
|
||||||
c.xPadding = XrayXPaddingConfig::fromJson(json.value("xPadding").toObject());
|
if (json.contains(QLatin1String("xPadding"))) {
|
||||||
c.xmux = XrayXmuxConfig::fromJson(json.value("xmux").toObject());
|
c.xPadding = XrayXPaddingConfig::fromJson(json.value(QLatin1String("xPadding")).toObject());
|
||||||
|
}
|
||||||
|
if (json.contains(QLatin1String("xmux"))) {
|
||||||
|
c.xmux = XrayXmuxConfig::fromJson(json.value(QLatin1String("xmux")).toObject());
|
||||||
|
}
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
@@ -156,12 +235,27 @@ QJsonObject XrayMkcpConfig::toJson() const
|
|||||||
XrayMkcpConfig XrayMkcpConfig::fromJson(const QJsonObject &json)
|
XrayMkcpConfig XrayMkcpConfig::fromJson(const QJsonObject &json)
|
||||||
{
|
{
|
||||||
XrayMkcpConfig c;
|
XrayMkcpConfig c;
|
||||||
c.tti = json.value(configKey::mkcpTti).toString();
|
if (json.isEmpty()) {
|
||||||
c.uplinkCapacity = json.value(configKey::mkcpUplinkCapacity).toString();
|
return c;
|
||||||
c.downlinkCapacity = json.value(configKey::mkcpDownlinkCapacity).toString();
|
}
|
||||||
c.readBufferSize = json.value(configKey::mkcpReadBufferSize).toString();
|
if (json.contains(configKey::mkcpTti)) {
|
||||||
c.writeBufferSize = json.value(configKey::mkcpWriteBufferSize).toString();
|
c.tti = json.value(configKey::mkcpTti).toString();
|
||||||
c.congestion = json.value(configKey::mkcpCongestion).toBool(true);
|
}
|
||||||
|
if (json.contains(configKey::mkcpUplinkCapacity)) {
|
||||||
|
c.uplinkCapacity = json.value(configKey::mkcpUplinkCapacity).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::mkcpDownlinkCapacity)) {
|
||||||
|
c.downlinkCapacity = json.value(configKey::mkcpDownlinkCapacity).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::mkcpReadBufferSize)) {
|
||||||
|
c.readBufferSize = json.value(configKey::mkcpReadBufferSize).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::mkcpWriteBufferSize)) {
|
||||||
|
c.writeBufferSize = json.value(configKey::mkcpWriteBufferSize).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::mkcpCongestion)) {
|
||||||
|
c.congestion = json.value(configKey::mkcpCongestion).toBool();
|
||||||
|
}
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,8 +302,14 @@ QJsonObject XrayServerConfig::toJson() const
|
|||||||
if (!transport.isEmpty()) {
|
if (!transport.isEmpty()) {
|
||||||
obj[configKey::xrayTransport] = transport;
|
obj[configKey::xrayTransport] = transport;
|
||||||
}
|
}
|
||||||
obj["xhttp"] = xhttp.toJson();
|
const QJsonObject xhttpObj = xhttp.toJson();
|
||||||
obj["mkcp"] = mkcp.toJson();
|
if (!xhttpObj.isEmpty()) {
|
||||||
|
obj[QStringLiteral("xhttp")] = xhttpObj;
|
||||||
|
}
|
||||||
|
const QJsonObject mkcpObj = mkcp.toJson();
|
||||||
|
if (!mkcpObj.isEmpty()) {
|
||||||
|
obj[QStringLiteral("mkcp")] = mkcpObj;
|
||||||
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@@ -225,20 +325,39 @@ XrayServerConfig XrayServerConfig::fromJson(const QJsonObject &json)
|
|||||||
c.site = json.value(configKey::site).toString();
|
c.site = json.value(configKey::site).toString();
|
||||||
c.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false);
|
c.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false);
|
||||||
|
|
||||||
// New: Security
|
if (json.contains(configKey::xraySecurity)) {
|
||||||
c.security = json.value(configKey::xraySecurity).toString(protocols::xray::defaultSecurity);
|
c.security = json.value(configKey::xraySecurity).toString();
|
||||||
c.flow = json.value(configKey::xrayFlow).toString(protocols::xray::defaultFlow);
|
}
|
||||||
c.fingerprint = json.value(configKey::xrayFingerprint).toString(protocols::xray::defaultFingerprint);
|
if (json.contains(configKey::xrayFlow)) {
|
||||||
if (c.fingerprint.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
|
c.flow = json.value(configKey::xrayFlow).toString();
|
||||||
c.fingerprint = QString::fromLatin1(protocols::xray::defaultFingerprint);
|
}
|
||||||
|
if (json.contains(configKey::xrayFingerprint)) {
|
||||||
|
c.fingerprint = json.value(configKey::xrayFingerprint).toString();
|
||||||
|
if (c.fingerprint.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
|
||||||
|
c.fingerprint = QString::fromLatin1(protocols::xray::defaultFingerprint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xraySni)) {
|
||||||
|
c.sni = json.value(configKey::xraySni).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xrayAlpn)) {
|
||||||
|
c.alpn = json.value(configKey::xrayAlpn).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(configKey::xrayTransport)) {
|
||||||
|
c.transport = json.value(configKey::xrayTransport).toString();
|
||||||
|
}
|
||||||
|
if (json.contains(QLatin1String("xhttp"))) {
|
||||||
|
const QJsonObject xhttpJson = json.value(QLatin1String("xhttp")).toObject();
|
||||||
|
if (!xhttpJson.isEmpty()) {
|
||||||
|
c.xhttp = XrayXhttpConfig::fromJson(xhttpJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (json.contains(QLatin1String("mkcp"))) {
|
||||||
|
const QJsonObject mkcpJson = json.value(QLatin1String("mkcp")).toObject();
|
||||||
|
if (!mkcpJson.isEmpty()) {
|
||||||
|
c.mkcp = XrayMkcpConfig::fromJson(mkcpJson);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
c.sni = json.value(configKey::xraySni).toString(protocols::xray::defaultSni);
|
|
||||||
c.alpn = json.value(configKey::xrayAlpn).toString(protocols::xray::defaultAlpn);
|
|
||||||
|
|
||||||
// New: Transport
|
|
||||||
c.transport = json.value(configKey::xrayTransport).toString(protocols::xray::defaultTransport);
|
|
||||||
c.xhttp = XrayXhttpConfig::fromJson(json.value("xhttp").toObject());
|
|
||||||
c.mkcp = XrayMkcpConfig::fromJson(json.value("mkcp").toObject());
|
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
@@ -251,7 +370,10 @@ bool XrayServerConfig::hasEqualServerSettings(const XrayServerConfig &other) con
|
|||||||
&& flow == other.flow
|
&& flow == other.flow
|
||||||
&& transport == other.transport
|
&& transport == other.transport
|
||||||
&& fingerprint == other.fingerprint
|
&& fingerprint == other.fingerprint
|
||||||
&& sni == other.sni;
|
&& sni == other.sni
|
||||||
|
&& alpn == other.alpn
|
||||||
|
&& xhttp.toJson() == other.xhttp.toJson()
|
||||||
|
&& mkcp.toJson() == other.mkcp.toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject XrayClientConfig::toJson() const
|
QJsonObject XrayClientConfig::toJson() const
|
||||||
@@ -351,9 +473,154 @@ XrayProtocolConfig XrayProtocolConfig::fromJson(const QJsonObject &json)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.needsClientHydration =
|
||||||
|
c.hasClientConfig()
|
||||||
|
&& (!json.contains(configKey::xrayTransport) || c.serverConfig.isThirdPartyConfig);
|
||||||
|
if (c.needsClientHydration) {
|
||||||
|
c.hydrateServerConfigFromClientNative();
|
||||||
|
}
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool XrayProtocolConfig::hydrateServerConfigFromClientNative()
|
||||||
|
{
|
||||||
|
if (!clientConfig.has_value() || clientConfig->nativeConfig.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(clientConfig->nativeConfig.toUtf8());
|
||||||
|
if (doc.isNull() || !doc.isObject()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonObject root = doc.object();
|
||||||
|
const QJsonArray outbounds = root.value(protocols::xray::outbounds).toArray();
|
||||||
|
if (outbounds.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonObject outbound = outbounds[0].toObject();
|
||||||
|
const QJsonObject streamSettings = outbound.value(protocols::xray::streamSettings).toObject();
|
||||||
|
if (streamSettings.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
XrayServerConfig &srv = serverConfig;
|
||||||
|
|
||||||
|
const QJsonObject settings = outbound.value(protocols::xray::settings).toObject();
|
||||||
|
const QJsonArray vnext = settings.value(protocols::xray::vnext).toArray();
|
||||||
|
if (!vnext.isEmpty()) {
|
||||||
|
const QJsonObject vnextEntry = vnext[0].toObject();
|
||||||
|
if (vnextEntry.contains(protocols::xray::port)) {
|
||||||
|
srv.port = QString::number(vnextEntry.value(protocols::xray::port).toInt());
|
||||||
|
}
|
||||||
|
const QJsonArray users = vnextEntry.value(protocols::xray::users).toArray();
|
||||||
|
if (!users.isEmpty()) {
|
||||||
|
srv.flow = users[0].toObject().value(protocols::xray::flow).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString networkVal = streamSettings.value(protocols::xray::network).toString(QStringLiteral("tcp"));
|
||||||
|
if (networkVal == QLatin1String("xhttp")) {
|
||||||
|
srv.transport = QStringLiteral("xhttp");
|
||||||
|
} else if (networkVal == QLatin1String("kcp")) {
|
||||||
|
srv.transport = QStringLiteral("mkcp");
|
||||||
|
} else {
|
||||||
|
srv.transport = QStringLiteral("raw");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (streamSettings.contains(protocols::xray::security)) {
|
||||||
|
srv.security = streamSettings.value(protocols::xray::security).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srv.security == QLatin1String("reality")) {
|
||||||
|
const QJsonObject rs = streamSettings.value(protocols::xray::realitySettings).toObject();
|
||||||
|
srv.sni = rs.value(protocols::xray::serverName).toString();
|
||||||
|
srv.site = srv.sni.isEmpty() ? srv.site : srv.sni;
|
||||||
|
const QString fp = rs.value(protocols::xray::fingerprint).toString();
|
||||||
|
if (!fp.isEmpty()) {
|
||||||
|
srv.fingerprint = fp.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)
|
||||||
|
? QString::fromLatin1(protocols::xray::defaultFingerprint)
|
||||||
|
: fp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srv.security == QLatin1String("tls")) {
|
||||||
|
const QJsonObject tls = streamSettings.value(QStringLiteral("tlsSettings")).toObject();
|
||||||
|
srv.sni = tls.value(protocols::xray::serverName).toString();
|
||||||
|
const QString fp = tls.value(protocols::xray::fingerprint).toString();
|
||||||
|
if (!fp.isEmpty()) {
|
||||||
|
srv.fingerprint = fp;
|
||||||
|
}
|
||||||
|
QStringList alpnList;
|
||||||
|
for (const QJsonValue &v : tls.value(QStringLiteral("alpn")).toArray()) {
|
||||||
|
alpnList << v.toString();
|
||||||
|
}
|
||||||
|
if (!alpnList.isEmpty()) {
|
||||||
|
srv.alpn = alpnList.join(QLatin1Char(','));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srv.transport == QLatin1String("xhttp")) {
|
||||||
|
const QJsonObject xhttpObj = streamSettings.value(QStringLiteral("xhttpSettings")).toObject();
|
||||||
|
QJsonObject xhttpJson;
|
||||||
|
const QString mode = xhttpObj.value(QStringLiteral("mode")).toString();
|
||||||
|
if (!mode.isEmpty()) {
|
||||||
|
if (mode == QLatin1String("auto")) {
|
||||||
|
xhttpJson[configKey::xhttpMode] = QStringLiteral("Auto");
|
||||||
|
} else if (mode == QLatin1String("packet-up")) {
|
||||||
|
xhttpJson[configKey::xhttpMode] = QStringLiteral("Packet-up");
|
||||||
|
} else if (mode == QLatin1String("stream-up")) {
|
||||||
|
xhttpJson[configKey::xhttpMode] = QStringLiteral("Stream-up");
|
||||||
|
} else if (mode == QLatin1String("stream-one")) {
|
||||||
|
xhttpJson[configKey::xhttpMode] = QStringLiteral("Stream-one");
|
||||||
|
} else {
|
||||||
|
xhttpJson[configKey::xhttpMode] = mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (xhttpObj.contains(QStringLiteral("host"))) {
|
||||||
|
xhttpJson[configKey::xhttpHost] = xhttpObj.value(QStringLiteral("host")).toString();
|
||||||
|
}
|
||||||
|
if (xhttpObj.contains(QStringLiteral("path"))) {
|
||||||
|
xhttpJson[configKey::xhttpPath] = xhttpObj.value(QStringLiteral("path")).toString();
|
||||||
|
}
|
||||||
|
if (xhttpObj.contains(QStringLiteral("uplinkHTTPMethod"))) {
|
||||||
|
xhttpJson[configKey::xhttpUplinkMethod] = xhttpObj.value(QStringLiteral("uplinkHTTPMethod")).toString();
|
||||||
|
}
|
||||||
|
xhttpJson[configKey::xhttpDisableGrpc] = xhttpObj.value(QStringLiteral("noGRPCHeader")).toBool(true);
|
||||||
|
xhttpJson[configKey::xhttpDisableSse] = xhttpObj.value(QStringLiteral("noSSEHeader")).toBool(true);
|
||||||
|
srv.xhttp = XrayXhttpConfig::fromJson(xhttpJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srv.transport == QLatin1String("mkcp")) {
|
||||||
|
const QJsonObject kcpObj = streamSettings.value(QStringLiteral("kcpSettings")).toObject();
|
||||||
|
XrayMkcpConfig mk;
|
||||||
|
if (kcpObj.contains(QStringLiteral("tti"))) {
|
||||||
|
mk.tti = QString::number(kcpObj.value(QStringLiteral("tti")).toInt());
|
||||||
|
}
|
||||||
|
if (kcpObj.contains(QStringLiteral("uplinkCapacity"))) {
|
||||||
|
mk.uplinkCapacity = QString::number(kcpObj.value(QStringLiteral("uplinkCapacity")).toInt());
|
||||||
|
}
|
||||||
|
if (kcpObj.contains(QStringLiteral("downlinkCapacity"))) {
|
||||||
|
mk.downlinkCapacity = QString::number(kcpObj.value(QStringLiteral("downlinkCapacity")).toInt());
|
||||||
|
}
|
||||||
|
if (kcpObj.contains(QStringLiteral("readBufferSize"))) {
|
||||||
|
mk.readBufferSize = QString::number(kcpObj.value(QStringLiteral("readBufferSize")).toInt());
|
||||||
|
}
|
||||||
|
if (kcpObj.contains(QStringLiteral("writeBufferSize"))) {
|
||||||
|
mk.writeBufferSize = QString::number(kcpObj.value(QStringLiteral("writeBufferSize")).toInt());
|
||||||
|
}
|
||||||
|
if (kcpObj.contains(QStringLiteral("congestion"))) {
|
||||||
|
mk.congestion = kcpObj.value(QStringLiteral("congestion")).toBool(true);
|
||||||
|
}
|
||||||
|
srv.mkcp = mk;
|
||||||
|
}
|
||||||
|
|
||||||
|
needsClientHydration = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool XrayProtocolConfig::hasClientConfig() const
|
bool XrayProtocolConfig::hasClientConfig() const
|
||||||
{
|
{
|
||||||
return clientConfig.has_value();
|
return clientConfig.has_value();
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ struct XrayXhttpConfig {
|
|||||||
XrayXmuxConfig xmux;
|
XrayXmuxConfig xmux;
|
||||||
|
|
||||||
QJsonObject toJson() const;
|
QJsonObject toJson() const;
|
||||||
|
/// Reads only keys present in JSON (no Amnezia UI defaults). Use XrayConfigModel::applyDefaultsToServerConfig for UI.
|
||||||
static XrayXhttpConfig fromJson(const QJsonObject &json);
|
static XrayXhttpConfig fromJson(const QJsonObject &json);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -99,15 +100,13 @@ struct XrayServerConfig {
|
|||||||
QString site;
|
QString site;
|
||||||
bool isThirdPartyConfig = false;
|
bool isThirdPartyConfig = false;
|
||||||
|
|
||||||
// New: Security
|
QString security;
|
||||||
QString security = protocols::xray::defaultSecurity;
|
QString flow;
|
||||||
QString flow = protocols::xray::defaultFlow;
|
QString fingerprint;
|
||||||
QString fingerprint = protocols::xray::defaultFingerprint;
|
QString sni;
|
||||||
QString sni = protocols::xray::defaultSni;
|
QString alpn;
|
||||||
QString alpn = protocols::xray::defaultAlpn;
|
|
||||||
|
|
||||||
// New: Transport
|
QString transport;
|
||||||
QString transport = protocols::xray::defaultTransport;
|
|
||||||
XrayXhttpConfig xhttp;
|
XrayXhttpConfig xhttp;
|
||||||
XrayMkcpConfig mkcp;
|
XrayMkcpConfig mkcp;
|
||||||
|
|
||||||
@@ -139,6 +138,10 @@ struct XrayProtocolConfig {
|
|||||||
bool hasClientConfig() const;
|
bool hasClientConfig() const;
|
||||||
void setClientConfig(const XrayClientConfig &config);
|
void setClientConfig(const XrayClientConfig &config);
|
||||||
void clearClientConfig();
|
void clearClientConfig();
|
||||||
|
|
||||||
|
bool needsClientHydration = false;
|
||||||
|
|
||||||
|
bool hydrateServerConfigFromClientNative();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace amnezia
|
} // namespace amnezia
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ namespace amnezia
|
|||||||
ServerCgroupMountpoint = 212,
|
ServerCgroupMountpoint = 212,
|
||||||
DockerPullRateLimit = 213,
|
DockerPullRateLimit = 213,
|
||||||
ServerLinuxKernelTooOld = 214,
|
ServerLinuxKernelTooOld = 214,
|
||||||
|
XrayServerConfigInvalid = 215,
|
||||||
|
XrayServerNoVlessClients = 216,
|
||||||
|
XrayRealityKeysReadFailed = 217,
|
||||||
|
|
||||||
// Ssh connection errors
|
// Ssh connection errors
|
||||||
SshRequestDeniedError = 300,
|
SshRequestDeniedError = 300,
|
||||||
|
|||||||
@@ -30,6 +30,15 @@ QString errorString(ErrorCode code) {
|
|||||||
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
|
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
|
||||||
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
|
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
|
||||||
case(ErrorCode::ServerLinuxKernelTooOld): errorMessage = QObject::tr("Server error: Linux kernel is too old"); break;
|
case(ErrorCode::ServerLinuxKernelTooOld): errorMessage = QObject::tr("Server error: Linux kernel is too old"); break;
|
||||||
|
case(ErrorCode::XrayServerConfigInvalid):
|
||||||
|
errorMessage = QObject::tr("Server error: invalid or unreadable XRay server configuration");
|
||||||
|
break;
|
||||||
|
case(ErrorCode::XrayServerNoVlessClients):
|
||||||
|
errorMessage = QObject::tr("Server error: XRay server has no VLESS clients");
|
||||||
|
break;
|
||||||
|
case(ErrorCode::XrayRealityKeysReadFailed):
|
||||||
|
errorMessage = QObject::tr("Server error: failed to read XRay Reality keys from the server");
|
||||||
|
break;
|
||||||
|
|
||||||
// Libssh errors
|
// Libssh errors
|
||||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
||||||
|
|||||||
@@ -275,11 +275,15 @@ void InstallUiController::updateContainer(const QString &serverId, int container
|
|||||||
}
|
}
|
||||||
ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverId, container);
|
ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverId, container);
|
||||||
|
|
||||||
if (container == DockerContainer::MtProxy || container == DockerContainer::Telemt) {
|
const bool asyncUpdate = container == DockerContainer::MtProxy || container == DockerContainer::Telemt
|
||||||
|
|| container == DockerContainer::Xray || container == DockerContainer::SSXray;
|
||||||
|
|
||||||
|
if (asyncUpdate) {
|
||||||
emit serverIsBusy(true);
|
emit serverIsBusy(true);
|
||||||
auto *watcher = new QFutureWatcher<ErrorCode>(this);
|
auto *watcher = new QFutureWatcher<ErrorCode>(this);
|
||||||
|
const Proto protocolTypeCopy = protocolType;
|
||||||
QObject::connect(watcher, &QFutureWatcher<ErrorCode>::finished, this,
|
QObject::connect(watcher, &QFutureWatcher<ErrorCode>::finished, this,
|
||||||
[this, watcher, serverId, container, closePage]() {
|
[this, watcher, serverId, container, closePage, protocolTypeCopy]() {
|
||||||
const ErrorCode errorCode = watcher->result();
|
const ErrorCode errorCode = watcher->result();
|
||||||
watcher->deleteLater();
|
watcher->deleteLater();
|
||||||
emit serverIsBusy(false);
|
emit serverIsBusy(false);
|
||||||
@@ -288,6 +292,7 @@ void InstallUiController::updateContainer(const QString &serverId, int container
|
|||||||
const ContainerConfig updatedConfig =
|
const ContainerConfig updatedConfig =
|
||||||
m_serversController->getContainerConfig(serverId, container);
|
m_serversController->getContainerConfig(serverId, container);
|
||||||
m_protocolModel->updateModel(updatedConfig);
|
m_protocolModel->updateModel(updatedConfig);
|
||||||
|
updateProtocolConfigModel(serverId, static_cast<int>(container), static_cast<int>(protocolTypeCopy));
|
||||||
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
|
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
|
||||||
} else {
|
} else {
|
||||||
emit installationErrorOccurred(errorCode);
|
emit installationErrorOccurred(errorCode);
|
||||||
@@ -299,7 +304,7 @@ void InstallUiController::updateContainer(const QString &serverId, int container
|
|||||||
InstallController *installController = m_installController;
|
InstallController *installController = m_installController;
|
||||||
QFuture<ErrorCode> future =
|
QFuture<ErrorCode> future =
|
||||||
QtConcurrent::run([installController, serverId, container, oldConfigCopy,
|
QtConcurrent::run([installController, serverId, container, oldConfigCopy,
|
||||||
newConfigCopy]() mutable -> ErrorCode {
|
newConfigCopy]() mutable -> ErrorCode {
|
||||||
return installController->updateContainer(serverId, container, oldConfigCopy, newConfigCopy);
|
return installController->updateContainer(serverId, container, oldConfigCopy, newConfigCopy);
|
||||||
});
|
});
|
||||||
watcher->setFuture(future);
|
watcher->setFuture(future);
|
||||||
@@ -311,6 +316,7 @@ void InstallUiController::updateContainer(const QString &serverId, int container
|
|||||||
if (errorCode == ErrorCode::NoError) {
|
if (errorCode == ErrorCode::NoError) {
|
||||||
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
|
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
|
||||||
m_protocolModel->updateModel(updatedConfig);
|
m_protocolModel->updateModel(updatedConfig);
|
||||||
|
updateProtocolConfigModel(serverId, static_cast<int>(container), static_cast<int>(protocolType));
|
||||||
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
|
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -425,6 +431,34 @@ void InstallUiController::removeContainer(const QString &serverId, int container
|
|||||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||||
QString containerName = ContainerUtils::containerHumanNames().value(container);
|
QString containerName = ContainerUtils::containerHumanNames().value(container);
|
||||||
|
|
||||||
|
const bool asyncRemove = container == DockerContainer::Xray || container == DockerContainer::SSXray;
|
||||||
|
|
||||||
|
if (asyncRemove) {
|
||||||
|
emit serverIsBusy(true);
|
||||||
|
auto *watcher = new QFutureWatcher<ErrorCode>(this);
|
||||||
|
QObject::connect(watcher, &QFutureWatcher<ErrorCode>::finished, this,
|
||||||
|
[this, watcher, serverId, container, containerName, serverName]() {
|
||||||
|
const ErrorCode errorCode = watcher->result();
|
||||||
|
watcher->deleteLater();
|
||||||
|
emit serverIsBusy(false);
|
||||||
|
|
||||||
|
if (errorCode == ErrorCode::NoError) {
|
||||||
|
emit removeContainerFinished(
|
||||||
|
tr("%1 has been removed from the server '%2'").arg(containerName, serverName));
|
||||||
|
} else {
|
||||||
|
emit installationErrorOccurred(errorCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
InstallController *installController = m_installController;
|
||||||
|
QFuture<ErrorCode> future = QtConcurrent::run(
|
||||||
|
[installController, serverId, container]() -> ErrorCode {
|
||||||
|
return installController->removeContainer(serverId, container);
|
||||||
|
});
|
||||||
|
watcher->setFuture(future);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ErrorCode errorCode = m_installController->removeContainer(serverId, container);
|
ErrorCode errorCode = m_installController->removeContainer(serverId, container);
|
||||||
if (errorCode == ErrorCode::NoError) {
|
if (errorCode == ErrorCode::NoError) {
|
||||||
|
|
||||||
|
|||||||
@@ -267,8 +267,13 @@ void XrayConfigModel::updateModel(amnezia::DockerContainer container, const amne
|
|||||||
m_container = container;
|
m_container = container;
|
||||||
|
|
||||||
m_protocolConfig = protocolConfig;
|
m_protocolConfig = protocolConfig;
|
||||||
|
if (m_protocolConfig.needsClientHydration) {
|
||||||
|
m_protocolConfig.hydrateServerConfigFromClientNative();
|
||||||
|
}
|
||||||
|
|
||||||
applyDefaultsToServerConfig(m_protocolConfig.serverConfig);
|
if (!m_protocolConfig.serverConfig.isThirdPartyConfig) {
|
||||||
|
applyDefaultsToServerConfig(m_protocolConfig.serverConfig);
|
||||||
|
}
|
||||||
|
|
||||||
m_originalProtocolConfig = m_protocolConfig;
|
m_originalProtocolConfig = m_protocolConfig;
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,18 @@ PageType {
|
|||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
|
Text {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.topMargin: 8
|
||||||
|
visible: !listView.enabled
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
color: AmneziaStyle.color.paleGray
|
||||||
|
font.pixelSize: 14
|
||||||
|
text: qsTr("You have read-only access to this server. XRay settings cannot be edited.")
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
@@ -73,6 +85,8 @@ PageType {
|
|||||||
BaseHeaderType {
|
BaseHeaderType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
headerText: qsTr("XRay VLESS settings")
|
headerText: qsTr("XRay VLESS settings")
|
||||||
|
descriptionLinkText: qsTr("More about settings")
|
||||||
|
descriptionLinkUrl: "https://docs.amnezia.org"
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButtonType {
|
ImageButtonType {
|
||||||
@@ -85,22 +99,6 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LabelTextType {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.leftMargin: 16
|
|
||||||
Layout.rightMargin: 16
|
|
||||||
Layout.topMargin: 4
|
|
||||||
text: qsTr("More about settings")
|
|
||||||
color: AmneziaStyle.color.goldenApricot
|
|
||||||
font.pixelSize: 16
|
|
||||||
lineHeight: 24 + LanguageUiController.getLineHeightAppend()
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: Qt.openUrlExternally("https://docs.amnezia.org")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
id: textFieldWithHeaderType
|
id: textFieldWithHeaderType
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -173,8 +171,9 @@ PageType {
|
|||||||
Layout.bottomMargin: 8
|
Layout.bottomMargin: 8
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
// Show Save immediately while user edits port, even before focus loss.
|
visible: listView.enabled
|
||||||
visible: listView.enabled && (XrayConfigModel.hasUnsavedChanges || textFieldWithHeaderType.textField.text !== port)
|
&& (XrayConfigModel.hasUnsavedChanges
|
||||||
|
|| textFieldWithHeaderType.textField.text !== port)
|
||||||
enabled: visible && textFieldWithHeaderType.errorText === ""
|
enabled: visible && textFieldWithHeaderType.errorText === ""
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
onClicked: function() {
|
onClicked: function() {
|
||||||
@@ -189,6 +188,10 @@ PageType {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (textFieldWithHeaderType.textField.text !== port) {
|
||||||
|
port = textFieldWithHeaderType.textField.text
|
||||||
|
}
|
||||||
|
|
||||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||||
}
|
}
|
||||||
@@ -209,6 +212,8 @@ PageType {
|
|||||||
clickedFunction: function() {
|
clickedFunction: function() {
|
||||||
var yesButtonFunction = function() {
|
var yesButtonFunction = function() {
|
||||||
XrayConfigModel.resetToDefaults()
|
XrayConfigModel.resetToDefaults()
|
||||||
|
PageController.showNotificationMessage(
|
||||||
|
qsTr("Settings were reset to defaults. Tap Save to apply them on the server."))
|
||||||
}
|
}
|
||||||
showQuestionDrawer(qsTr("Reset settings?"), qsTr("All XRay settings will be restored to defaults."),
|
showQuestionDrawer(qsTr("Reset settings?"), qsTr("All XRay settings will be restored to defaults."),
|
||||||
qsTr("Reset"), qsTr("Cancel"), yesButtonFunction, function() {
|
qsTr("Reset"), qsTr("Cancel"), yesButtonFunction, function() {
|
||||||
|
|||||||
Reference in New Issue
Block a user