mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
fixed vless
This commit is contained in:
@@ -137,117 +137,284 @@ 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,
|
||||||
// Get flow value from settings (default xtls-rprx-vision)
|
libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||||
QString flowValue = "xtls-rprx-vision";
|
|
||||||
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
|
|
||||||
if (!xrayCfg->serverConfig.flow.isEmpty()) {
|
|
||||||
flowValue = xrayCfg->serverConfig.flow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current server config
|
|
||||||
QString currentConfig = m_sshSession->getTextFileFromContainer(
|
|
||||||
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() << "Failed to upload updated config";
|
||||||
return "";
|
return errorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse current config as JSON
|
const QString restartScript = QStringLiteral("sudo docker restart $CONTAINER_NAME");
|
||||||
|
errorCode = m_sshSession->runScript(
|
||||||
|
credentials,
|
||||||
|
m_sshSession->replaceVars(restartScript,
|
||||||
|
amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns,
|
||||||
|
dnsSettings.secondaryDns)));
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
logger.error() << "Failed to restart container";
|
||||||
|
}
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
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 currentConfig = m_sshSession->getTextFileFromContainer(
|
||||||
|
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
logger.error() << "Xray applyServerSettings: getTextFileFromContainer failed, error="
|
||||||
|
<< static_cast<int>(errorCode) << "path=" << amnezia::protocols::xray::serverConfigPath;
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
logger.info() << "Xray applyServerSettings: read server config, bytes=" << currentConfig.size();
|
||||||
|
|
||||||
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::InternalError;
|
||||||
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::InternalError;
|
||||||
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::InternalError;
|
||||||
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::InternalError;
|
||||||
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::InternalError;
|
||||||
|
}
|
||||||
|
clientId = clients[0].toObject()[amnezia::protocols::xray::id].toString();
|
||||||
|
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 = buildClientProtocolConfig(credentials, container, srv, clientId, errorCode);
|
||||||
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
|
|
||||||
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 xrayPublicKey;
|
||||||
|
QString xrayShortId;
|
||||||
|
|
||||||
|
if (srv.security == QLatin1String("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 {};
|
||||||
|
}
|
||||||
|
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 {};
|
||||||
|
}
|
||||||
|
xrayShortId.replace("\n", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
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() ? amnezia::protocols::xray::defaultPort : 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;
|
||||||
@@ -441,93 +608,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,11 +23,32 @@ public:
|
|||||||
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
|
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
|
||||||
amnezia::ProtocolConfig protocolConfig) override;
|
amnezia::ProtocolConfig protocolConfig) override;
|
||||||
|
|
||||||
|
/// Uploads server.json inbound (port, streamSettings, client flows). Optionally appends a new VLESS client.
|
||||||
|
/// When appendNewClient is false, rebuilds admin client config in containerConfig from the first server client.
|
||||||
|
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);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
QJsonObject mergeStreamSettingsForServerInbound(const amnezia::XrayServerConfig &srv,
|
||||||
|
const QJsonObject &existingStreamSettings) const;
|
||||||
|
|
||||||
// Builds the native xray "streamSettings" JSON object from XrayServerConfig
|
// Builds the native xray "streamSettings" JSON object from XrayServerConfig
|
||||||
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"
|
||||||
@@ -181,6 +182,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);
|
||||||
@@ -191,6 +202,18 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged) {
|
||||||
|
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);
|
||||||
@@ -638,12 +661,13 @@ 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)
|
if (!oldXrayConfig->serverConfig.hasEqualServerSettings(newXrayConfig->serverConfig)) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -251,7 +251,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 +354,150 @@ XrayProtocolConfig XrayProtocolConfig::fromJson(const QJsonObject &json)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.needsClientHydration = !json.contains(configKey::xrayTransport) && c.hasClientConfig();
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.security = streamSettings.value(protocols::xray::security).toString(protocols::xray::defaultSecurity);
|
||||||
|
|
||||||
|
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();
|
||||||
|
|||||||
@@ -139,6 +139,12 @@ struct XrayProtocolConfig {
|
|||||||
bool hasClientConfig() const;
|
bool hasClientConfig() const;
|
||||||
void setClientConfig(const XrayClientConfig &config);
|
void setClientConfig(const XrayClientConfig &config);
|
||||||
void clearClientConfig();
|
void clearClientConfig();
|
||||||
|
|
||||||
|
/// Set in fromJson when persisted server fields are missing (typical shared profile).
|
||||||
|
bool needsClientHydration = false;
|
||||||
|
|
||||||
|
/// Fills serverConfig from client nativeConfig (outbound streamSettings). For read-only UI.
|
||||||
|
bool hydrateServerConfigFromClientNative();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace amnezia
|
} // namespace amnezia
|
||||||
|
|||||||
@@ -263,11 +263,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);
|
||||||
@@ -276,6 +280,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));
|
||||||
|
|
||||||
const auto defaultContainer =
|
const auto defaultContainer =
|
||||||
m_serversController->getDefaultContainer(serverId);
|
m_serversController->getDefaultContainer(serverId);
|
||||||
@@ -295,7 +300,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);
|
||||||
@@ -307,6 +312,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));
|
||||||
|
|
||||||
const auto defaultContainer = m_serversController->getDefaultContainer(serverId);
|
const auto defaultContainer = m_serversController->getDefaultContainer(serverId);
|
||||||
if ((serverId == m_serversController->getDefaultServerId()) && (container == defaultContainer)) {
|
if ((serverId == m_serversController->getDefaultServerId()) && (container == defaultContainer)) {
|
||||||
@@ -427,6 +433,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,6 +267,9 @@ 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);
|
applyDefaultsToServerConfig(m_protocolConfig.serverConfig);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -174,7 +186,7 @@ PageType {
|
|||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
// Show Save immediately while user edits port, even before focus loss.
|
// Show Save immediately while user edits port, even before focus loss.
|
||||||
visible: listView.enabled && (XrayConfigModel.hasUnsavedChanges || textFieldWithHeaderType.textField.text !== port)
|
visible: listView.enabled && XrayConfigModel.hasUnsavedChanges
|
||||||
enabled: visible && textFieldWithHeaderType.errorText === ""
|
enabled: visible && textFieldWithHeaderType.errorText === ""
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
onClicked: function() {
|
onClicked: function() {
|
||||||
@@ -189,6 +201,10 @@ PageType {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (textFieldWithHeaderType.textField.text !== port) {
|
||||||
|
port = textFieldWithHeaderType.textField.text
|
||||||
|
}
|
||||||
|
|
||||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||||
InstallController.updateContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
InstallController.updateContainer(ServersUiController.getServerId(ServersUiController.processedServerIndex), ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||||
}
|
}
|
||||||
@@ -209,6 +225,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