Compare commits

...

17 Commits

Author SHA1 Message Date
vkamn
0658a8f565 revert: regional country codes UI (#2567) (#2621) 2026-05-20 13:55:28 +08:00
vkamn
482ec04b4a chore: bump version (#2620)
* chore: bump version

* chore: bump android qt version
2026-05-20 12:37:38 +08:00
vkamn
d40d24fcf9 fix: fixed validateAndPrepareConfig for non admin configs (#2617)
* fix: fixed validateAndPrepareConfig for non admin configs

* fix: fix fetchGatewayUrl lambda context
2026-05-20 12:37:22 +08:00
yp
fb5666057b feat: add extended vless configuration (#2566)
* update UI XRay, add new page PageProtocolXrayTransportSettings.qml PageProtocolXrayXmuxSettings.qml PageProtocolXrayXPaddingSettings.qml

* add UI PageProtocolXrayConfigsSettings, PageProtocolXrayFlowSettings, PageProtocolXraySecuritySettings

* add Xray-specific keys

* add vars xray model

* add new qml padding, update model

* update model and export

* rename file & update name class & update list xray

* fixed ui

* add save file in temp

* remove debug macros

* fixed build windows

* fix path Windows

* remove save config

* fixed changes

* fixed conf

* fixed UI

* fixed size & button save

* fixed build iOS

* fix: fixed headers base control

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-05-18 22:35:01 +08:00
yp
a49892c7e7 feat: add telemt container (#2435)
* Feat: Add MtProxy (Telegram)

* add path files

* Feat: Add Telemt (MtProxy)

* fixed secret & enum

* remove old path

* refactor: move logic from ui to core

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-05-18 20:01:09 +08:00
yp
277b295fd8 feat: add mtproxy(#2370)
* Feat: Add MtProxy (Telegram)

* add path files

* refactor: move logic from ui to core

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-05-18 19:52:58 +08:00
lunardunno
8c33779fc3 chore: Install recommends for apt (#2596) 2026-05-18 13:56:57 +08:00
lunardunno
f0299ca9fe chore: authentication prompt in Ubuntu 26.04 (#2603)
Handling the password prompt in Ubuntu 26.04
2026-05-18 11:55:07 +08:00
MrMirDan
c7b1c2809f fix: app buttons clicked instead of buttons in context menu (#2200)
* fix: app buttons clicked instead of buttons in context menu

* update: using MouseArea instead of changing popupType

* fix(cursor): fixed cursor type at opened context menu

---------

Co-authored-by: Mitternacht822 <sb@amnezia.org>
2026-05-15 21:02:09 +08:00
MrMirDan
c9ed0baf3b fix: app freezes when revoke awg/wg client during active connection (#2211)
* block configs revoke during connection

* update: check that current config is active

* update: notification text
2026-05-15 21:01:39 +08:00
yp
2a3e3126ac feat: regional country codes (#2567)
Co-authored-by: vkamn <vk@amnezia.org>
2026-05-15 15:44:58 +08:00
MrMirDan
98771027b7 fix: vless switch between dividers (#2600) 2026-05-15 14:58:23 +08:00
MrMirDan
0433e03bdc fix: amnezia free card button hovers when card enabled (#2602) 2026-05-15 14:58:11 +08:00
yp
cb48667b91 fix: bug when saving after canceling the save action (#2568)
Co-authored-by: vkamn <vk@amnezia.org>
2026-05-15 14:57:44 +08:00
yp
d0a1af0381 refactor: deactivate api config before remove (#2569)
Co-authored-by: vkamn <vk@amnezia.org>
2026-05-15 14:56:09 +08:00
Yaroslav Gurov
fd0c773918 fix: change artifact names (#2589) 2026-05-15 12:36:38 +08:00
vkamn
06372c8fd7 refactor: remove serverConfig struct (#2595)
* refactor: remove serverConfig struct

* refactor: add warnings for api v1 configs

* refactor: moved the server type definition to a separate namespace

* refactor: simplified gateway stacks

* fix: fixed server description

* fix: fixed postAsync reply usage

* fix: fixed validateConfig call

* fix: fixed server name in notifications

* fix: fixed initPrepareConfigHandler for lagacy configs
2026-05-15 12:33:36 +08:00
213 changed files with 14874 additions and 3390 deletions

View File

@@ -712,7 +712,7 @@ jobs:
env:
ANDROID_PLATFORM: android-28
NDK_VERSION: 27.0.11718014
QT_VERSION: 6.10.1
QT_VERSION: 6.10.3
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}

View File

@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.15.4)
set(AMNEZIAVPN_VERSION 4.8.9.0)
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
@@ -28,7 +28,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2120)
set(APP_ANDROID_VERSION_CODE 2122)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")

View File

@@ -1,6 +1,5 @@
<RCC>
<qresource prefix="/client_scripts">
<file>linux_installer.sh</file>
<file>mac_installer.sh</file>
</qresource>
</RCC>

View File

@@ -1,29 +0,0 @@
#!/bin/bash
EXTRACT_DIR="$1"
INSTALLER_PATH="$2"
# Create and clean extract directory
rm -rf "$EXTRACT_DIR"
mkdir -p "$EXTRACT_DIR"
# Extract TAR archive
tar -xf "$INSTALLER_PATH" -C "$EXTRACT_DIR"
if [ $? -ne 0 ]; then
echo 'Failed to extract TAR archive'
exit 1
fi
# Find and run installer
INSTALLER=$(find "$EXTRACT_DIR" -type f -executable)
if [ -z "$INSTALLER" ]; then
echo 'Installer not found'
exit 1
fi
"$INSTALLER"
EXIT_CODE=$?
# Cleanup
rm -rf "$EXTRACT_DIR"
exit $EXIT_CODE

View File

@@ -15,7 +15,6 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/utils/constants/protocolConstants.h
${CLIENT_ROOT_DIR}/core/utils/constants/apiKeys.h
${CLIENT_ROOT_DIR}/core/utils/constants/apiConstants.h
${CLIENT_ROOT_DIR}/core/utils/api/apiEnums.h
${CLIENT_ROOT_DIR}/core/utils/errorStrings.h
${CLIENT_ROOT_DIR}/core/utils/selfhosted/scriptsRegistry.h
${CLIENT_ROOT_DIR}/core/utils/qrCodeUtils.h
@@ -36,6 +35,8 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/installers/torInstaller.h
${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.h
${CLIENT_ROOT_DIR}/core/installers/socks5Installer.h
${CLIENT_ROOT_DIR}/core/installers/mtProxyInstaller.h
${CLIENT_ROOT_DIR}/core/installers/telemtInstaller.h
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.h
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.h
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.h
@@ -111,6 +112,8 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/installers/torInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/socks5Installer.cpp
${CLIENT_ROOT_DIR}/core/installers/mtProxyInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/telemtInstaller.cpp
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.cpp
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.cpp
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.cpp
@@ -138,6 +141,7 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/serverConfigUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.cpp
${CLIENT_ROOT_DIR}/core/utils/utilities.cpp
${CLIENT_ROOT_DIR}/core/utils/managementServer.cpp
@@ -201,12 +205,14 @@ file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
${CLIENT_ROOT_DIR}/ui/models/*.h
${CLIENT_ROOT_DIR}/ui/models/protocols/*.h
${CLIENT_ROOT_DIR}/ui/models/services/*.h
${CLIENT_ROOT_DIR}/ui/models/utils/*.h
${CLIENT_ROOT_DIR}/ui/models/api/*.h
)
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
${CLIENT_ROOT_DIR}/ui/models/*.cpp
${CLIENT_ROOT_DIR}/ui/models/protocols/*.cpp
${CLIENT_ROOT_DIR}/ui/models/services/*.cpp
${CLIENT_ROOT_DIR}/ui/models/utils/*.cpp
${CLIENT_ROOT_DIR}/ui/models/api/*.cpp
)

View File

@@ -20,14 +20,123 @@
#include "core/models/protocols/xrayProtocolConfig.h"
namespace {
Logger logger("XrayConfigurator");
}
Logger logger("XrayConfigurator");
QString normalizeXhttpMode(const QString &m) {
const QString t = m.trimmed();
if (t.isEmpty() || t.compare(QLatin1String("Auto"), Qt::CaseInsensitive) == 0) {
return QStringLiteral("auto");
}
if (t.compare(QLatin1String("Packet-up"), Qt::CaseInsensitive) == 0)
return QStringLiteral("packet-up");
if (t.compare(QLatin1String("Stream-up"), Qt::CaseInsensitive) == 0)
return QStringLiteral("stream-up");
if (t.compare(QLatin1String("Stream-one"), Qt::CaseInsensitive) == 0)
return QStringLiteral("stream-one");
return t.toLower();
}
// Xray-core: empty → path; "None" in UI → omit (core default path)
QString normalizeSessionSeqPlacement(const QString &p)
{
if (p.isEmpty() || p.compare(QLatin1String("None"), Qt::CaseInsensitive) == 0)
return {};
return p.toLower();
}
QString normalizeUplinkDataPlacement(const QString &p)
{
if (p.isEmpty() || p.compare(QLatin1String("Body"), Qt::CaseInsensitive) == 0)
return QStringLiteral("body");
if (p.compare(QLatin1String("Auto"), Qt::CaseInsensitive) == 0)
return QStringLiteral("auto");
if (p.compare(QLatin1String("Query"), Qt::CaseInsensitive) == 0)
// "Query" is not valid for uplink payload in splithttp; closest documented mode
return QStringLiteral("header");
return p.toLower();
}
// splithttp: cookie | header | query | queryInHeader (not "body")
QString normalizeXPaddingPlacement(const QString &p)
{
QString t = p.trimmed();
if (t.isEmpty())
return QString::fromLatin1(amnezia::protocols::xray::defaultXPaddingPlacement).toLower();
if (t.compare(QLatin1String("Body"), Qt::CaseInsensitive) == 0)
return QStringLiteral("queryInHeader");
if (t.contains(QLatin1String("queryInHeader"), Qt::CaseInsensitive)
|| t.compare(QLatin1String("Query in header"), Qt::CaseInsensitive) == 0)
return QStringLiteral("queryInHeader");
return t.toLower();
}
// splithttp: repeat-x | tokenish
QString normalizeXPaddingMethod(const QString &m)
{
QString t = m.trimmed();
if (t.isEmpty() || t.compare(QLatin1String("Repeat-x"), Qt::CaseInsensitive) == 0)
return QStringLiteral("repeat-x");
if (t.compare(QLatin1String("Tokenish"), Qt::CaseInsensitive) == 0)
return QStringLiteral("tokenish");
if (t.compare(QLatin1String("Random"), Qt::CaseInsensitive) == 0
|| t.compare(QLatin1String("Zero"), Qt::CaseInsensitive) == 0)
return QStringLiteral("repeat-x");
return t.toLower();
}
void putIntRangeIfAny(QJsonObject &obj, const char *key, QString minV, QString maxV, const char *fallbackMin,
const char *fallbackMax)
{
if (minV.isEmpty() && maxV.isEmpty())
return;
if (minV.isEmpty())
minV = QString::fromLatin1(fallbackMin);
if (maxV.isEmpty())
maxV = QString::fromLatin1(fallbackMax);
QJsonObject r;
r[QStringLiteral("from")] = minV.toInt();
r[QStringLiteral("to")] = maxV.toInt();
obj[QString::fromUtf8(key)] = r;
}
// Desktop applies this in XrayProtocol::start(); iOS/Android pass JSON straight to libxray — same fixes here.
void sanitizeXrayNativeConfig(amnezia::ProtocolConfig &pc)
{
QString c = pc.nativeConfig();
if (c.isEmpty()) {
return;
}
bool changed = false;
if (c.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
c.replace(QLatin1String("Mozilla/5.0"), QString::fromLatin1(amnezia::protocols::xray::defaultFingerprint),
Qt::CaseInsensitive);
changed = true;
}
const QString legacyListen = QString::fromLatin1(amnezia::protocols::xray::defaultLocalAddr);
const QString listenOk = QString::fromLatin1(amnezia::protocols::xray::defaultLocalListenAddr);
if (c.contains(legacyListen)) {
c.replace(legacyListen, listenOk);
changed = true;
}
if (changed) {
pc.setNativeConfig(c);
}
}
} // namespace
XrayConfigurator::XrayConfigurator(SshSession* sshSession, QObject *parent)
: ConfiguratorBase(sshSession, parent)
{
}
amnezia::ProtocolConfig XrayConfigurator::processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
amnezia::ProtocolConfig protocolConfig)
{
applyDnsToNativeConfig(settings.dns, protocolConfig);
sanitizeXrayNativeConfig(protocolConfig);
return protocolConfig;
}
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
@@ -35,11 +144,19 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
{
// Generate new UUID for client
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
// Get flow value from settings (default xtls-rprx-vision)
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) {
logger.error() << "Failed to get server config file";
return "";
@@ -54,7 +171,7 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
}
QJsonObject serverConfig = doc.object();
// Validate server config structure
if (!serverConfig.contains(amnezia::protocols::xray::inbounds)) {
logger.error() << "Server config missing 'inbounds' field";
@@ -68,7 +185,7 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
errorCode = ErrorCode::InternalError;
return "";
}
QJsonObject inbound = inbounds[0].toObject();
if (!inbound.contains(amnezia::protocols::xray::settings)) {
logger.error() << "Inbound missing 'settings' field";
@@ -84,26 +201,29 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
}
QJsonArray clients = settings[amnezia::protocols::xray::clients].toArray();
// Create configuration for new client
QJsonObject clientConfig {
{amnezia::protocols::xray::id, clientId},
{amnezia::protocols::xray::flow, "xtls-rprx-vision"}
};
clientConfig[amnezia::protocols::xray::id] = clientId;
if (!flowValue.isEmpty()) {
clientConfig[amnezia::protocols::xray::flow] = flowValue;
}
clients.append(clientConfig);
// Update config
settings[amnezia::protocols::xray::clients] = clients;
inbound[amnezia::protocols::xray::settings] = settings;
inbounds[0] = inbound;
serverConfig[amnezia::protocols::xray::inbounds] = inbounds;
// Save updated config to server
QString updatedConfig = QJsonDocument(serverConfig).toJson();
errorCode = m_sshSession->uploadTextFileToContainer(
container,
credentials,
container,
credentials,
updatedConfig,
amnezia::protocols::xray::serverConfigPath,
libssh::ScpOverwriteMode::ScpOverwriteExisting
@@ -116,7 +236,7 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
// Restart container
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
errorCode = m_sshSession->runScript(
credentials,
credentials,
m_sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns))
);
@@ -128,75 +248,286 @@ QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentia
return clientId;
}
ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, const QString &clientId) const
{
const XrayServerConfig* serverConfig = nullptr;
if (auto* xrayConfig = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
serverConfig = &xrayConfig->serverConfig;
QJsonObject streamSettings;
const auto &xhttp = srv.xhttp;
const auto &mkcp = srv.mkcp;
namespace px = amnezia::protocols::xray;
QString networkValue = QStringLiteral("tcp");
if (srv.transport == QLatin1String("xhttp"))
networkValue = QStringLiteral("xhttp");
else if (srv.transport == QLatin1String("mkcp"))
networkValue = QStringLiteral("kcp");
streamSettings[px::network] = networkValue;
streamSettings[px::security] = srv.security;
if (srv.security == QLatin1String("tls")) {
QJsonObject tlsSettings;
const QString sniEff = srv.sni.isEmpty() ? QString::fromLatin1(px::defaultSni) : srv.sni;
tlsSettings[px::serverName] = sniEff;
const QString alpnEff = srv.alpn.isEmpty() ? QString::fromLatin1(px::defaultAlpn) : srv.alpn;
QJsonArray alpnArray;
for (const QString &a : alpnEff.split(QLatin1Char(','))) {
const QString t = a.trimmed();
if (!t.isEmpty())
alpnArray.append(t);
}
if (!alpnArray.isEmpty())
tlsSettings[QStringLiteral("alpn")] = alpnArray;
const QString fpEff = srv.fingerprint.isEmpty() ? QString::fromLatin1(px::defaultFingerprint) : srv.fingerprint;
tlsSettings[px::fingerprint] = fpEff;
streamSettings[QStringLiteral("tlsSettings")] = tlsSettings;
}
if (srv.security == QLatin1String("reality")) {
QJsonObject realSettings;
const QString fpEff = srv.fingerprint.isEmpty() ? QString::fromLatin1(px::defaultFingerprint) : srv.fingerprint;
realSettings[px::fingerprint] = fpEff;
const QString sniEff = srv.sni.isEmpty() ? QString::fromLatin1(px::defaultSni) : srv.sni;
realSettings[px::serverName] = sniEff;
streamSettings[px::realitySettings] = realSettings;
}
// XHTTP — JSON must match Xray-core SplitHTTPConfig (flat xPadding fields, see transport_internet.go)
if (srv.transport == QLatin1String("xhttp")) {
QJsonObject xo;
const QString hostEff = xhttp.host.isEmpty() ? QString::fromLatin1(px::defaultXhttpHost) : xhttp.host;
xo[QStringLiteral("host")] = hostEff;
if (!xhttp.path.isEmpty())
xo[QStringLiteral("path")] = xhttp.path;
xo[QStringLiteral("mode")] = normalizeXhttpMode(xhttp.mode);
if (xhttp.headersTemplate.compare(QLatin1String("HTTP"), Qt::CaseInsensitive) == 0) {
QJsonObject headers;
headers[QStringLiteral("Host")] = hostEff;
xo[QStringLiteral("headers")] = headers;
}
const QString methodEff =
xhttp.uplinkMethod.isEmpty() ? QString::fromLatin1(px::defaultXhttpUplinkMethod) : xhttp.uplinkMethod;
xo[QStringLiteral("uplinkHTTPMethod")] = methodEff.toUpper();
xo[QStringLiteral("noGRPCHeader")] = xhttp.disableGrpc;
xo[QStringLiteral("noSSEHeader")] = xhttp.disableSse;
const QString sessPl = normalizeSessionSeqPlacement(xhttp.sessionPlacement);
if (!sessPl.isEmpty())
xo[QStringLiteral("sessionPlacement")] = sessPl;
const QString seqPl = normalizeSessionSeqPlacement(xhttp.seqPlacement);
if (!seqPl.isEmpty())
xo[QStringLiteral("seqPlacement")] = seqPl;
if (!xhttp.sessionKey.isEmpty())
xo[QStringLiteral("sessionKey")] = xhttp.sessionKey;
if (!xhttp.seqKey.isEmpty())
xo[QStringLiteral("seqKey")] = xhttp.seqKey;
xo[QStringLiteral("uplinkDataPlacement")] = normalizeUplinkDataPlacement(xhttp.uplinkDataPlacement);
if (!xhttp.uplinkDataKey.isEmpty())
xo[QStringLiteral("uplinkDataKey")] = xhttp.uplinkDataKey;
const QString ucs = xhttp.uplinkChunkSize.isEmpty() ? QString::fromLatin1(px::defaultXhttpUplinkChunkSize)
: xhttp.uplinkChunkSize;
if (!ucs.isEmpty() && ucs != QLatin1String("0")) {
const int v = ucs.toInt();
QJsonObject chunkR;
chunkR[QStringLiteral("from")] = v;
chunkR[QStringLiteral("to")] = v;
xo[QStringLiteral("uplinkChunkSize")] = chunkR;
}
if (!xhttp.scMaxBufferedPosts.isEmpty())
xo[QStringLiteral("scMaxBufferedPosts")] = xhttp.scMaxBufferedPosts.toLongLong();
putIntRangeIfAny(xo, "scMaxEachPostBytes", xhttp.scMaxEachPostBytesMin, xhttp.scMaxEachPostBytesMax,
px::defaultXhttpScMaxEachPostBytesMin, px::defaultXhttpScMaxEachPostBytesMax);
putIntRangeIfAny(xo, "scMinPostsIntervalMs", xhttp.scMinPostsIntervalMsMin, xhttp.scMinPostsIntervalMsMax,
px::defaultXhttpScMinPostsIntervalMsMin, px::defaultXhttpScMinPostsIntervalMsMax);
putIntRangeIfAny(xo, "scStreamUpServerSecs", xhttp.scStreamUpServerSecsMin, xhttp.scStreamUpServerSecsMax,
px::defaultXhttpScStreamUpServerSecsMin, px::defaultXhttpScStreamUpServerSecsMax);
const auto &pad = xhttp.xPadding;
xo[QStringLiteral("xPaddingObfsMode")] = pad.obfsMode;
if (pad.obfsMode) {
if (!pad.bytesMin.isEmpty() || !pad.bytesMax.isEmpty()) {
QJsonObject br;
br[QStringLiteral("from")] = pad.bytesMin.isEmpty() ? 1 : pad.bytesMin.toInt();
br[QStringLiteral("to")] = pad.bytesMax.isEmpty() ? (pad.bytesMin.isEmpty() ? 256 : pad.bytesMin.toInt())
: pad.bytesMax.toInt();
xo[QStringLiteral("xPaddingBytes")] = br;
}
xo[QStringLiteral("xPaddingKey")] = pad.key.isEmpty() ? QStringLiteral("x_padding") : pad.key;
xo[QStringLiteral("xPaddingHeader")] = pad.header.isEmpty() ? QStringLiteral("X-Padding") : pad.header;
xo[QStringLiteral("xPaddingPlacement")] = normalizeXPaddingPlacement(
pad.placement.isEmpty() ? QString::fromLatin1(px::defaultXPaddingPlacement) : pad.placement);
xo[QStringLiteral("xPaddingMethod")] = normalizeXPaddingMethod(
pad.method.isEmpty() ? QString::fromLatin1(px::defaultXPaddingMethod) : pad.method);
}
// xmux: Xray has no "enabled" flag; omit object when UI disables multiplex tuning.
if (xhttp.xmux.enabled) {
QJsonObject mux;
auto addMuxRange = [&](const char *key, const QString &a, const QString &b) {
if (a.isEmpty() && b.isEmpty())
return;
QJsonObject r;
r[QStringLiteral("from")] = a.isEmpty() ? 0 : a.toInt();
r[QStringLiteral("to")] = b.isEmpty() ? 0 : b.toInt();
mux[QString::fromUtf8(key)] = r;
};
addMuxRange("maxConcurrency", xhttp.xmux.maxConcurrencyMin, xhttp.xmux.maxConcurrencyMax);
addMuxRange("maxConnections", xhttp.xmux.maxConnectionsMin, xhttp.xmux.maxConnectionsMax);
addMuxRange("cMaxReuseTimes", xhttp.xmux.cMaxReuseTimesMin, xhttp.xmux.cMaxReuseTimesMax);
addMuxRange("hMaxRequestTimes", xhttp.xmux.hMaxRequestTimesMin, xhttp.xmux.hMaxRequestTimesMax);
addMuxRange("hMaxReusableSecs", xhttp.xmux.hMaxReusableSecsMin, xhttp.xmux.hMaxReusableSecsMax);
if (!xhttp.xmux.hKeepAlivePeriod.isEmpty())
mux[QStringLiteral("hKeepAlivePeriod")] = xhttp.xmux.hKeepAlivePeriod.toLongLong();
if (!mux.isEmpty())
xo[QStringLiteral("xmux")] = mux;
}
streamSettings[QStringLiteral("xhttpSettings")] = xo;
}
if (srv.transport == QLatin1String("mkcp")) {
QJsonObject kcpObj;
const QString ttiEff = mkcp.tti.isEmpty() ? QString::fromLatin1(px::defaultMkcpTti) : mkcp.tti;
const QString upEff = mkcp.uplinkCapacity.isEmpty() ? QString::fromLatin1(px::defaultMkcpUplinkCapacity)
: mkcp.uplinkCapacity;
const QString downEff = mkcp.downlinkCapacity.isEmpty() ? QString::fromLatin1(px::defaultMkcpDownlinkCapacity)
: mkcp.downlinkCapacity;
const QString rbufEff = mkcp.readBufferSize.isEmpty() ? QString::fromLatin1(px::defaultMkcpReadBufferSize)
: mkcp.readBufferSize;
const QString wbufEff = mkcp.writeBufferSize.isEmpty() ? QString::fromLatin1(px::defaultMkcpWriteBufferSize)
: mkcp.writeBufferSize;
kcpObj[QStringLiteral("tti")] = ttiEff.toInt();
kcpObj[QStringLiteral("uplinkCapacity")] = upEff.toInt();
kcpObj[QStringLiteral("downlinkCapacity")] = downEff.toInt();
kcpObj[QStringLiteral("readBufferSize")] = rbufEff.toInt();
kcpObj[QStringLiteral("writeBufferSize")] = wbufEff.toInt();
kcpObj[QStringLiteral("congestion")] = mkcp.congestion;
streamSettings[QStringLiteral("kcpSettings")] = kcpObj;
}
return streamSettings;
}
ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
const XrayServerConfig *serverConfig = nullptr;
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
serverConfig = &xrayCfg->serverConfig;
}
if (!serverConfig) {
logger.error() << "No XrayProtocolConfig found";
errorCode = ErrorCode::InternalError;
return XrayProtocolConfig{};
}
const XrayServerConfig &srv = *serverConfig;
QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, dnsSettings, errorCode);
if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) {
logger.error() << "Failed to prepare server config";
errorCode = ErrorCode::InternalError;
if (errorCode == ErrorCode::NoError) {
errorCode = ErrorCode::InternalError;
}
return XrayProtocolConfig{};
}
amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns);
vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig));
QString config = m_sshSession->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), vars);
if (config.isEmpty()) {
logger.error() << "Failed to get config template";
errorCode = ErrorCode::InternalError;
return XrayProtocolConfig{};
// Fetch server keys (Reality only)
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", "");
}
QString xrayPublicKey =
m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
logger.error() << "Failed to get public key";
errorCode = ErrorCode::InternalError;
return XrayProtocolConfig{};
}
xrayPublicKey.replace("\n", "");
QString xrayShortId =
m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
logger.error() << "Failed to get short ID";
errorCode = ErrorCode::InternalError;
return XrayProtocolConfig{};
}
xrayShortId.replace("\n", "");
if (!config.contains("$XRAY_CLIENT_ID") || !config.contains("$XRAY_PUBLIC_KEY") || !config.contains("$XRAY_SHORT_ID")) {
logger.error() << "Config template missing required variables:"
<< "XRAY_CLIENT_ID:" << !config.contains("$XRAY_CLIENT_ID")
<< "XRAY_PUBLIC_KEY:" << !config.contains("$XRAY_PUBLIC_KEY")
<< "XRAY_SHORT_ID:" << !config.contains("$XRAY_SHORT_ID");
errorCode = ErrorCode::InternalError;
return XrayProtocolConfig{};
// 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;
}
config.replace("$XRAY_CLIENT_ID", xrayClientId);
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
config.replace("$XRAY_SHORT_ID", xrayShortId);
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;
if (serverConfig) {
protocolConfig.serverConfig = *serverConfig;
}
protocolConfig.serverConfig = srv;
XrayClientConfig clientConfig;
clientConfig.nativeConfig = config;
clientConfig.localPort = "";
qDebug() << "config:" << config;
clientConfig.localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort);
clientConfig.id = xrayClientId;
protocolConfig.setClientConfig(clientConfig);
return protocolConfig;
}
}

View File

@@ -2,11 +2,13 @@
#define XRAY_CONFIGURATOR_H
#include <QObject>
#include <QJsonObject>
#include "configuratorBase.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/models/protocols/xrayProtocolConfig.h"
class XrayConfigurator : public ConfiguratorBase
{
@@ -18,10 +20,17 @@ public:
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode) override;
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
amnezia::ProtocolConfig protocolConfig) override;
private:
QString prepareServerConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode);
// Builds the native xray "streamSettings" JSON object from XrayServerConfig
QJsonObject buildStreamSettings(const amnezia::XrayServerConfig &srv,
const QString &clientId) const;
};
#endif // XRAY_CONFIGURATOR_H

View File

@@ -1,51 +1,93 @@
#include "newsController.h"
#include "core/controllers/gatewayController.h"
#include "core/utils/api/apiEnums.h"
#include "core/repositories/secureServersRepository.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "core/utils/constants/configKeys.h"
#include <QtConcurrent/QtConcurrent>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSet>
#include <QSharedPointer>
using namespace amnezia;
NewsController::NewsController(SecureAppSettingsRepository* appSettingsRepository,
ServersController* serversController)
: m_appSettingsRepository(appSettingsRepository), m_serversController(serversController)
NewsController::NewsController(SecureAppSettingsRepository *appSettingsRepository,
SecureServersRepository *serversRepository)
: m_appSettingsRepository(appSettingsRepository),
m_serversRepository(serversRepository)
{
}
QJsonObject NewsController::getServicesList() const
{
if (!m_serversRepository) {
return {};
}
QSet<QString> userCountryCodes;
QSet<QString> serviceTypes;
const QVector<QString> ids = m_serversRepository->orderedServerIds();
for (const QString &id : ids) {
const auto apiV2 = m_serversRepository->apiV2Config(id);
if (!apiV2.has_value()) {
continue;
}
if (!apiV2->apiConfig.userCountryCode.isEmpty()) {
userCountryCodes.insert(apiV2->apiConfig.userCountryCode);
}
const QString serviceType = apiV2->serviceType();
if (!serviceType.isEmpty()) {
serviceTypes.insert(serviceType);
}
}
if (userCountryCodes.isEmpty() && serviceTypes.isEmpty()) {
return {};
}
QJsonObject json;
QJsonArray userCountryCodesArray;
for (const QString &code : userCountryCodes) {
userCountryCodesArray.append(code);
}
json[apiDefs::key::userCountryCode] = userCountryCodesArray;
QJsonArray serviceTypesArray;
for (const QString &type : serviceTypes) {
serviceTypesArray.append(type);
}
json[apiDefs::key::serviceType] = serviceTypesArray;
return json;
}
QFuture<QPair<ErrorCode, QJsonArray>> NewsController::fetchNews()
{
if (!m_serversController) {
qWarning() << "ServersController is null, skip fetchNews";
if (!m_serversRepository) {
qWarning() << "SecureServersRepository is null, skip fetchNews";
return QtFuture::makeReadyFuture(qMakePair(ErrorCode::InternalError, QJsonArray()));
}
const auto stacks = m_serversController->gatewayStacks();
if (stacks.isEmpty()) {
const QJsonObject services = getServicesList();
if (services.isEmpty()) {
qDebug() << "No Gateway stacks, skip fetchNews";
return QtFuture::makeReadyFuture(qMakePair(ErrorCode::NoError, QJsonArray()));
}
auto gatewayController = QSharedPointer<GatewayController>::create(
m_appSettingsRepository->getGatewayEndpoint(),
m_appSettingsRepository->isDevGatewayEnv(),
apiDefs::requestTimeoutMsecs,
m_appSettingsRepository->isStrictKillSwitchEnabled());
m_appSettingsRepository->getGatewayEndpoint(),
m_appSettingsRepository->isDevGatewayEnv(),
apiDefs::requestTimeoutMsecs,
m_appSettingsRepository->isStrictKillSwitchEnabled());
QJsonObject payload;
payload.insert("locale", m_appSettingsRepository->getAppLanguage().name().split("_").first());
const QJsonObject stacksJson = stacks.toJson();
if (stacksJson.contains(apiDefs::key::userCountryCode)) {
payload.insert(apiDefs::key::userCountryCode, stacksJson.value(apiDefs::key::userCountryCode));
if (services.contains(apiDefs::key::userCountryCode)) {
payload.insert(apiDefs::key::userCountryCode, services.value(apiDefs::key::userCountryCode));
}
if (stacksJson.contains(apiDefs::key::serviceType)) {
payload.insert(apiDefs::key::serviceType, stacksJson.value(apiDefs::key::serviceType));
if (services.contains(apiDefs::key::serviceType)) {
payload.insert(apiDefs::key::serviceType, services.value(apiDefs::key::serviceType));
}
auto future = gatewayController->postAsync(QString("%1v1/news"), payload);
@@ -69,4 +111,3 @@ QFuture<QPair<ErrorCode, QJsonArray>> NewsController::fetchNews()
return qMakePair(ErrorCode::NoError, newsArray);
});
}

View File

@@ -3,26 +3,28 @@
#include <QFuture>
#include <QJsonArray>
#include <QJsonObject>
#include <QPair>
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/controllers/serversController.h"
#include "core/repositories/secureServersRepository.h"
class NewsController
{
public:
explicit NewsController(SecureAppSettingsRepository* appSettingsRepository,
ServersController* serversController);
SecureServersRepository* serversRepository);
QFuture<QPair<ErrorCode, QJsonArray>> fetchNews();
private:
QJsonObject getServicesList() const;
SecureAppSettingsRepository* m_appSettingsRepository;
ServersController* m_serversController;
SecureServersRepository* m_serversRepository;
};
#endif // NEWSCONTROLLER_H

View File

@@ -11,7 +11,7 @@
#include <limits>
#include "core/controllers/gatewayController.h"
#include "core/utils/api/apiEnums.h"
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "version.h"

View File

@@ -16,7 +16,7 @@
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/api/apiEnums.h"
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "core/utils/api/apiUtils.h"
@@ -26,7 +26,6 @@
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "version.h"
#include "core/models/serverConfig.h"
#include "core/models/containerConfig.h"
#include "core/models/api/apiConfig.h"
@@ -196,7 +195,7 @@ void SubscriptionController::updateApiConfigInJson(QJsonObject &serverConfigJson
apiConfig[apiDefs::key::serviceProtocol] = serviceProtocol;
apiConfig[apiDefs::key::userCountryCode] = userCountryCode;
if (serverConfigJson.value(configKey::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) {
if (serverConfigJson.value(configKey::configVersion).toInt() == serverConfigUtils::ConfigSource::AmneziaGateway) {
QJsonObject responseObj = QJsonDocument::fromJson(apiResponseBody).object();
if (responseObj.contains(apiDefs::key::supportedProtocols)) {
apiConfig.insert(apiDefs::key::supportedProtocols, responseObj.value(apiDefs::key::supportedProtocols).toArray());
@@ -217,8 +216,7 @@ ErrorCode SubscriptionController::executeRequest(const QString &endpoint, const
}
ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const ProtocolData &protocolData,
ServerConfig &serverConfig)
const QString &serviceProtocol, const ProtocolData &protocolData)
{
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
@@ -247,20 +245,18 @@ ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCo
updateApiConfigInJson(serverConfigJson, serviceType, serviceProtocol, userCountryCode, responseBody);
ServerConfig serverConfigModel = ServerConfig::fromJson(serverConfigJson);
if (!serverConfigModel.isApiV2()) {
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
return ErrorCode::InternalError;
}
m_serversRepository->addServer(serverConfigModel);
serverConfig = serverConfigModel;
ApiV2ServerConfig apiV2ServerConfig = ApiV2ServerConfig::fromJson(serverConfigJson);
m_serversRepository->addServer(QString(), apiV2ServerConfig.toJson(),
serverConfigUtils::configTypeFromJson(apiV2ServerConfig.toJson()));
return ErrorCode::NoError;
}
ErrorCode SubscriptionController::importTrialFromGateway(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const QString &email,
ServerConfig &serverConfig)
const QString &serviceProtocol, const QString &email)
{
const QString trimmedEmail = email.trimmed();
if (trimmedEmail.isEmpty()) {
@@ -306,16 +302,19 @@ ErrorCode SubscriptionController::importTrialFromGateway(const QString &userCoun
}
QJsonObject configObject = QJsonDocument::fromJson(configBytes).object();
ServerConfig serverConfigModel = ServerConfig::fromJson(configObject);
m_serversRepository->addServer(serverConfigModel);
serverConfig = serverConfigModel;
if (configObject.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
return ErrorCode::InternalError;
}
ApiV2ServerConfig apiV2ServerConfig = ApiV2ServerConfig::fromJson(configObject);
m_serversRepository->addServer(QString(), apiV2ServerConfig.toJson(),
serverConfigUtils::configTypeFromJson(apiV2ServerConfig.toJson()));
return ErrorCode::NoError;
}
ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const ProtocolData &protocolData,
const QString &transactionId, bool isTestPurchase,
ServerConfig &serverConfig,
int *duplicateServerIndex)
{
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
@@ -351,15 +350,8 @@ ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userC
// Check if server with this VPN key already exists
for (int i = 0; i < m_serversRepository->serversCount(); ++i) {
ServerConfig existingServerConfig = m_serversRepository->server(i);
QString existingVpnKey;
if (existingServerConfig.isApiV1()) {
const ApiV1ServerConfig* apiV1 = existingServerConfig.as<ApiV1ServerConfig>();
existingVpnKey = apiV1 ? apiV1->vpnKey() : QString();
} else if (existingServerConfig.isApiV2()) {
const ApiV2ServerConfig* apiV2 = existingServerConfig.as<ApiV2ServerConfig>();
existingVpnKey = apiV2 ? apiV2->vpnKey() : QString();
}
const auto apiV2 = m_serversRepository->apiV2Config(m_serversRepository->serverIdAt(i));
QString existingVpnKey = apiV2.has_value() ? apiV2->vpnKey() : QString();
existingVpnKey.replace(QStringLiteral("vpn://"), QString());
if (!existingVpnKey.isEmpty() && existingVpnKey == normalizedKey) {
if (duplicateServerIndex) {
@@ -385,38 +377,28 @@ ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userC
quint16 crc = qChecksum(QJsonDocument(configObject).toJson());
ServerConfig serverConfigModel = ServerConfig::fromJson(configObject);
if (!serverConfigModel.isApiV2()) {
if (configObject.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
return ErrorCode::InternalError;
}
ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
if (!apiV2) {
return ErrorCode::InternalError;
}
ApiV2ServerConfig apiV2ServerConfig = ApiV2ServerConfig::fromJson(configObject);
ApiV2ServerConfig* apiV2 = &apiV2ServerConfig;
apiV2->apiConfig.vpnKey = normalizedKey;
apiV2->apiConfig.isTestPurchase = isTestPurchase;
apiV2->apiConfig.isInAppPurchase = true;
apiV2->apiConfig.subscriptionExpiredByServer = false;
apiV2->crc = crc;
m_serversRepository->addServer(serverConfigModel);
serverConfig = serverConfigModel;
m_serversRepository->addServer(QString(), apiV2ServerConfig.toJson(),
serverConfigUtils::configTypeFromJson(apiV2ServerConfig.toJson()));
return ErrorCode::NoError;
}
ErrorCode SubscriptionController::updateServiceFromGateway(int serverIndex, const QString &newCountryCode, bool isConnectEvent)
ErrorCode SubscriptionController::updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent)
{
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
if (!serverConfigModel.isApiV2()) {
return ErrorCode::InternalError;
}
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
if (!apiV2) {
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (!apiV2.has_value()) {
return ErrorCode::InternalError;
}
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
@@ -445,12 +427,10 @@ ErrorCode SubscriptionController::updateServiceFromGateway(int serverIndex, cons
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
if (errorCode != ErrorCode::NoError) {
if (errorCode == ErrorCode::ApiSubscriptionExpiredError && !apiV2->apiConfig.isInAppPurchase) {
ServerConfig expiredServerConfig = serverConfigModel;
ApiV2ServerConfig *expiredApiV2 = expiredServerConfig.as<ApiV2ServerConfig>();
if (expiredApiV2) {
expiredApiV2->apiConfig.subscriptionExpiredByServer = true;
m_serversRepository->editServer(serverIndex, expiredServerConfig);
}
ApiV2ServerConfig expiredApiV2 = *apiV2;
expiredApiV2.apiConfig.subscriptionExpiredByServer = true;
m_serversRepository->editServer(serverId, expiredApiV2.toJson(),
serverConfigUtils::configTypeFromJson(expiredApiV2.toJson()));
}
return errorCode;
}
@@ -463,16 +443,12 @@ ErrorCode SubscriptionController::updateServiceFromGateway(int serverIndex, cons
updateApiConfigInJson(serverConfigJson, apiV2->apiConfig.serviceType, serviceProtocol, apiV2->apiConfig.userCountryCode, responseBody);
ServerConfig newServerConfigModel = ServerConfig::fromJson(serverConfigJson);
if (!newServerConfigModel.isApiV2()) {
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
return ErrorCode::InternalError;
}
ApiV2ServerConfig* newApiV2 = newServerConfigModel.as<ApiV2ServerConfig>();
if (!newApiV2) {
return ErrorCode::InternalError;
}
ApiV2ServerConfig newApiV2Config = ApiV2ServerConfig::fromJson(serverConfigJson);
ApiV2ServerConfig* newApiV2 = &newApiV2Config;
newApiV2->apiConfig.vpnKey = apiV2->apiConfig.vpnKey;
newApiV2->apiConfig.isTestPurchase = apiV2->apiConfig.isTestPurchase;
@@ -487,20 +463,15 @@ ErrorCode SubscriptionController::updateServiceFromGateway(int serverIndex, cons
newApiV2->nameOverriddenByUser = true;
}
m_serversRepository->editServer(serverIndex, newServerConfigModel);
m_serversRepository->editServer(serverId, newApiV2Config.toJson(),
serverConfigUtils::configTypeFromJson(newApiV2Config.toJson()));
return ErrorCode::NoError;
}
ErrorCode SubscriptionController::deactivateDevice(int serverIndex)
ErrorCode SubscriptionController::deactivateDevice(const QString &serverId)
{
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
if (!serverConfigModel.isApiV2()) {
return ErrorCode::NoError;
}
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
if (!apiV2) {
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (!apiV2.has_value()) {
return ErrorCode::NoError;
}
@@ -528,23 +499,16 @@ ErrorCode SubscriptionController::deactivateDevice(int serverIndex)
return errorCode;
}
serverConfigModel.visit([](auto& arg) {
arg.containers.clear();
});
m_serversRepository->editServer(serverIndex, serverConfigModel);
apiV2->containers.clear();
m_serversRepository->editServer(serverId, apiV2->toJson(),
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
return ErrorCode::NoError;
}
ErrorCode SubscriptionController::deactivateExternalDevice(int serverIndex, const QString &uuid, const QString &serverCountryCode)
ErrorCode SubscriptionController::deactivateExternalDevice(const QString &serverId, const QString &uuid, const QString &serverCountryCode)
{
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
if (!serverConfigModel.isApiV2()) {
return ErrorCode::NoError;
}
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
if (!apiV2) {
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (!apiV2.has_value()) {
return ErrorCode::NoError;
}
@@ -573,25 +537,18 @@ ErrorCode SubscriptionController::deactivateExternalDevice(int serverIndex, cons
}
if (uuid == m_appSettingsRepository->getInstallationUuid(true)) {
serverConfigModel.visit([](auto& arg) {
arg.containers.clear();
});
m_serversRepository->editServer(serverIndex, serverConfigModel);
apiV2->containers.clear();
m_serversRepository->editServer(serverId, apiV2->toJson(),
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
}
return ErrorCode::NoError;
}
ErrorCode SubscriptionController::exportNativeConfig(int serverIndex, const QString &serverCountryCode, QString &nativeConfig)
ErrorCode SubscriptionController::exportNativeConfig(const QString &serverId, const QString &serverCountryCode, QString &nativeConfig)
{
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
if (!serverConfigModel.isApiV2()) {
return ErrorCode::InternalError;
}
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
if (!apiV2) {
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (!apiV2.has_value()) {
return ErrorCode::InternalError;
}
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
@@ -624,16 +581,10 @@ ErrorCode SubscriptionController::exportNativeConfig(int serverIndex, const QStr
return ErrorCode::NoError;
}
ErrorCode SubscriptionController::revokeNativeConfig(int serverIndex, const QString &serverCountryCode)
ErrorCode SubscriptionController::revokeNativeConfig(const QString &serverId, const QString &serverCountryCode)
{
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
if (!serverConfigModel.isApiV2()) {
return ErrorCode::InternalError;
}
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
if (!apiV2) {
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (!apiV2.has_value()) {
return ErrorCode::InternalError;
}
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
@@ -661,126 +612,54 @@ ErrorCode SubscriptionController::revokeNativeConfig(int serverIndex, const QStr
return ErrorCode::NoError;
}
ErrorCode SubscriptionController::updateServiceFromTelegram(int serverIndex)
ErrorCode SubscriptionController::prepareVpnKeyExport(const QString &serverId, QString &vpnKey)
{
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
if (!serverConfigModel.isApiV1()) {
return ErrorCode::InternalError;
}
const ApiV1ServerConfig* apiV1 = serverConfigModel.as<ApiV1ServerConfig>();
if (!apiV1) {
return ErrorCode::InternalError;
}
QString serviceProtocol = apiV1->protocol;
ProtocolData protocolData = generateProtocolData(serviceProtocol);
QString installationUuid = m_appSettingsRepository->getInstallationUuid(true);
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(), m_appSettingsRepository->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_appSettingsRepository->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
apiPayload[apiDefs::key::uuid] = installationUuid;
apiPayload[apiDefs::key::osVersion] = QSysInfo::productType();
apiPayload[apiDefs::key::appVersion] = QString(APP_VERSION);
apiPayload[configKey::accessToken] = apiV1->apiKey;
apiPayload[apiDefs::key::apiEndpoint] = apiV1->apiEndpoint;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/proxy_config"), apiPayload, responseBody);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
QJsonObject serverConfigJson;
errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
ServerConfig newServerConfigModel = ServerConfig::fromJson(serverConfigJson);
if (!newServerConfigModel.isApiV1()) {
return ErrorCode::InternalError;
}
ApiV1ServerConfig* newApiV1 = newServerConfigModel.as<ApiV1ServerConfig>();
if (!newApiV1) {
return ErrorCode::InternalError;
}
newApiV1->apiKey = apiV1->apiKey;
newApiV1->apiEndpoint = apiV1->apiEndpoint;
newApiV1->crc = apiV1->crc;
m_serversRepository->editServer(serverIndex, newServerConfigModel);
return ErrorCode::NoError;
}
ErrorCode SubscriptionController::prepareVpnKeyExport(int serverIndex, QString &vpnKey)
{
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
if (serverConfigModel.isApiV1()) {
const ApiV1ServerConfig* apiV1 = serverConfigModel.as<ApiV1ServerConfig>();
vpnKey = apiV1 ? apiV1->vpnKey() : QString();
} else if (serverConfigModel.isApiV2()) {
ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
vpnKey = apiV2 ? apiV2->vpnKey() : QString();
if (vpnKey.isEmpty()) {
QJsonObject serverJson = serverConfigModel.toJson();
vpnKey = apiUtils::getPremiumV2VpnKey(serverJson);
if (vpnKey.isEmpty()) {
return ErrorCode::ApiConfigEmptyError;
}
apiV2->apiConfig.vpnKey = vpnKey;
m_serversRepository->editServer(serverIndex, serverConfigModel);
}
} else {
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (!apiV2.has_value()) {
return ErrorCode::ApiConfigEmptyError;
}
vpnKey = apiV2->vpnKey();
if (vpnKey.isEmpty()) {
vpnKey = apiUtils::getPremiumV2VpnKey(apiV2->toJson());
if (vpnKey.isEmpty()) {
return ErrorCode::ApiConfigEmptyError;
}
apiV2->apiConfig.vpnKey = vpnKey;
m_serversRepository->editServer(serverId, apiV2->toJson(),
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
}
return ErrorCode::NoError;
}
ErrorCode SubscriptionController::validateAndUpdateConfig(int serverIndex, bool hasInstalledContainers)
ErrorCode SubscriptionController::validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers)
{
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
apiDefs::ConfigSource configSource;
if (serverConfigModel.isApiV1()) {
configSource = apiDefs::ConfigSource::Telegram;
} else if (serverConfigModel.isApiV2()) {
configSource = apiDefs::ConfigSource::AmneziaGateway;
} else {
if (!m_serversRepository->apiV2Config(serverId).has_value()) {
return ErrorCode::NoError;
}
if (configSource == apiDefs::ConfigSource::Telegram && !hasInstalledContainers) {
removeApiConfig(serverIndex);
return updateServiceFromTelegram(serverIndex);
} else if (configSource == apiDefs::ConfigSource::AmneziaGateway && !hasInstalledContainers) {
return updateServiceFromGateway(serverIndex, "", true);
} else if (configSource && isApiKeyExpired(serverIndex)) {
qDebug() << "attempt to update api config by expires_at event";
if (configSource == apiDefs::ConfigSource::AmneziaGateway) {
return updateServiceFromGateway(serverIndex, "", true);
} else {
removeApiConfig(serverIndex);
return updateServiceFromTelegram(serverIndex);
}
if (!hasInstalledContainers) {
return updateServiceFromGateway(serverId, "", true);
}
if (isApiKeyExpired(serverId)) {
qDebug() << "attempt to update api config by expires_at event";
return updateServiceFromGateway(serverId, "", true);
}
return ErrorCode::NoError;
}
void SubscriptionController::removeApiConfig(int serverIndex)
void SubscriptionController::removeApiConfig(const QString &serverId)
{
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (!apiV2.has_value()) {
return;
}
#if defined(Q_OS_IOS) || defined(MACOS_NE)
QString description = serverConfigModel.description();
QString hostName = serverConfigModel.hostName();
QString description = apiV2->description;
QString hostName = apiV2->hostName;
QString vpncName = QString("%1 (%2) %3")
.arg(description)
.arg(hostName)
@@ -789,34 +668,42 @@ void SubscriptionController::removeApiConfig(int serverIndex)
AmneziaVPN::removeVPNC(vpncName.toStdString());
#endif
serverConfigModel.visit([](auto& arg) {
arg.dns1.clear();
arg.dns2.clear();
arg.containers.clear();
arg.hostName.clear();
arg.defaultContainer = DockerContainer::None;
});
apiV2->dns1.clear();
apiV2->dns2.clear();
apiV2->containers.clear();
apiV2->hostName.clear();
apiV2->defaultContainer = DockerContainer::None;
apiV2->apiConfig.publicKey = ApiConfig::PublicKeyInfo{};
if (serverConfigModel.isApiV2()) {
ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
if (apiV2) {
apiV2->apiConfig.publicKey = ApiConfig::PublicKeyInfo{};
}
}
m_serversRepository->editServer(serverIndex, serverConfigModel);
m_serversRepository->editServer(serverId, apiV2->toJson(),
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
}
bool SubscriptionController::isApiKeyExpired(int serverIndex) const
bool SubscriptionController::removeServer(const QString &serverId)
{
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
if (!serverConfigModel.isApiV2()) {
if (serverId.isEmpty()) {
return false;
}
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
if (!apiV2) {
if (!m_serversRepository->apiV2Config(serverId).has_value()) {
qWarning().noquote() << "SubscriptionController::removeServer: not an Api V2 server, id" << serverId;
return false;
}
const ErrorCode revokeError = deactivateDevice(serverId);
if (revokeError != ErrorCode::NoError && revokeError != ErrorCode::ApiNotFoundError) {
qWarning().noquote() << "SubscriptionController::removeServer: deactivateDevice failed (error"
<< static_cast<int>(revokeError) << "); removing locally anyway.";
}
m_serversRepository->removeServer(serverId);
return true;
}
bool SubscriptionController::isApiKeyExpired(const QString &serverId) const
{
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (!apiV2.has_value()) {
return false;
}
const QString expiresAt = apiV2->apiConfig.publicKey.expiresAt;
@@ -833,31 +720,24 @@ bool SubscriptionController::isApiKeyExpired(int serverIndex) const
return false;
}
void SubscriptionController::setCurrentProtocol(int serverIndex, const QString &protocolName)
void SubscriptionController::setCurrentProtocol(const QString &serverId, const QString &protocolName)
{
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
if (serverConfigModel.isApiV2()) {
ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
if (apiV2) {
apiV2->apiConfig.serviceProtocol = protocolName;
}
m_serversRepository->editServer(serverIndex, serverConfigModel);
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (apiV2.has_value()) {
apiV2->apiConfig.serviceProtocol = protocolName;
m_serversRepository->editServer(serverId, apiV2->toJson(),
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
}
}
bool SubscriptionController::isVlessProtocol(int serverIndex) const
bool SubscriptionController::isVlessProtocol(const QString &serverId) const
{
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
if (serverConfigModel.isApiV2()) {
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
return apiV2 && apiV2->serviceProtocol() == "vless";
}
return false;
auto apiV2 = m_serversRepository->apiV2Config(serverId);
return apiV2.has_value() && apiV2->serviceProtocol() == "vless";
}
ErrorCode SubscriptionController::processAppStorePurchase(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const QString &productId,
ServerConfig &serverConfig,
int *duplicateServerIndex)
{
#if defined(Q_OS_IOS) || defined(MACOS_NE)
@@ -891,13 +771,12 @@ ErrorCode SubscriptionController::processAppStorePurchase(const QString &userCou
ProtocolData protocolData = generateProtocolData(serviceProtocol);
return importServiceFromAppStore(userCountryCode, serviceType, serviceProtocol, protocolData,
originalTransactionId, isTestPurchase, serverConfig, duplicateServerIndex);
originalTransactionId, isTestPurchase, duplicateServerIndex);
#else
Q_UNUSED(userCountryCode);
Q_UNUSED(serviceType);
Q_UNUSED(serviceProtocol);
Q_UNUSED(productId);
Q_UNUSED(serverConfig);
return ErrorCode::ApiPurchaseError;
#endif
}
@@ -956,10 +835,9 @@ SubscriptionController::AppStoreRestoreResult SubscriptionController::processApp
<< "originalTransactionId =" << originalTransactionId << "productId =" << transactionProductId;
ProtocolData protocolData = generateProtocolData(serviceProtocol);
ServerConfig serverConfig;
int currentDuplicateServerIndex = -1;
ErrorCode errorCode = importServiceFromAppStore(userCountryCode, serviceType, serviceProtocol, protocolData,
originalTransactionId, isTestPurchase, serverConfig,
originalTransactionId, isTestPurchase,
&currentDuplicateServerIndex);
if (errorCode == ErrorCode::ApiConfigAlreadyAdded) {
@@ -991,16 +869,10 @@ SubscriptionController::AppStoreRestoreResult SubscriptionController::processApp
#endif
}
ErrorCode SubscriptionController::getAccountInfo(int serverIndex, QJsonObject &accountInfo)
ErrorCode SubscriptionController::getAccountInfo(const QString &serverId, QJsonObject &accountInfo)
{
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
if (!serverConfigModel.isApiV2()) {
return ErrorCode::InternalError;
}
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
if (!apiV2) {
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (!apiV2.has_value()) {
return ErrorCode::InternalError;
}
bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
@@ -1030,20 +902,13 @@ ErrorCode SubscriptionController::getAccountInfo(int serverIndex, QJsonObject &a
return ErrorCode::NoError;
}
QFuture<QPair<ErrorCode, QString>> SubscriptionController::getRenewalLink(int serverIndex)
QFuture<QPair<ErrorCode, QString>> SubscriptionController::getRenewalLink(const QString &serverId)
{
auto promise = QSharedPointer<QPromise<QPair<ErrorCode, QString>>>::create();
promise->start();
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
if (!serverConfigModel.isApiV2()) {
promise->addResult(qMakePair(ErrorCode::InternalError, QString()));
promise->finish();
return promise->future();
}
const ApiV2ServerConfig *apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
if (!apiV2) {
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (!apiV2.has_value()) {
promise->addResult(qMakePair(ErrorCode::InternalError, QString()));
promise->finish();
return promise->future();

View File

@@ -12,7 +12,6 @@
#include "core/utils/commonStructs.h"
#include "core/repositories/secureServersRepository.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/models/serverConfig.h"
class ServersController;
@@ -48,44 +47,40 @@ public:
ProtocolData generateProtocolData(const QString &protocol);
void appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload);
ErrorCode fillServerConfig(const QJsonObject &serverConfigJson, ServerConfig &serverConfig);
ErrorCode importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const ProtocolData &protocolData,
ServerConfig &serverConfig);
const QString &serviceProtocol, const ProtocolData &protocolData);
ErrorCode importTrialFromGateway(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const QString &email,
ServerConfig &serverConfig);
const QString &serviceProtocol, const QString &email);
ErrorCode importServiceFromAppStore(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const ProtocolData &protocolData,
const QString &transactionId, bool isTestPurchase,
ServerConfig &serverConfig,
int *duplicateServerIndex = nullptr);
ErrorCode updateServiceFromGateway(int serverIndex, const QString &newCountryCode, bool isConnectEvent);
ErrorCode updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent);
ErrorCode deactivateDevice(int serverIndex);
ErrorCode deactivateDevice(const QString &serverId);
ErrorCode deactivateExternalDevice(int serverIndex, const QString &uuid, const QString &serverCountryCode);
ErrorCode deactivateExternalDevice(const QString &serverId, const QString &uuid, const QString &serverCountryCode);
ErrorCode exportNativeConfig(int serverIndex, const QString &serverCountryCode, QString &nativeConfig);
ErrorCode exportNativeConfig(const QString &serverId, const QString &serverCountryCode, QString &nativeConfig);
ErrorCode revokeNativeConfig(int serverIndex, const QString &serverCountryCode);
ErrorCode revokeNativeConfig(const QString &serverId, const QString &serverCountryCode);
ErrorCode updateServiceFromTelegram(int serverIndex);
ErrorCode prepareVpnKeyExport(const QString &serverId, QString &vpnKey);
ErrorCode prepareVpnKeyExport(int serverIndex, QString &vpnKey);
ErrorCode validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers);
ErrorCode validateAndUpdateConfig(int serverIndex, bool hasInstalledContainers);
void removeApiConfig(const QString &serverId);
void removeApiConfig(int serverIndex);
bool removeServer(const QString &serverId);
void setCurrentProtocol(int serverIndex, const QString &protocolName);
bool isVlessProtocol(int serverIndex) const;
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
bool isVlessProtocol(const QString &serverId) const;
ErrorCode getAccountInfo(int serverIndex, QJsonObject &accountInfo);
QFuture<QPair<ErrorCode, QString>> getRenewalLink(int serverIndex);
ErrorCode getAccountInfo(const QString &serverId, QJsonObject &accountInfo);
QFuture<QPair<ErrorCode, QString>> getRenewalLink(const QString &serverId);
struct AppStoreRestoreResult
{
@@ -98,7 +93,6 @@ public:
ErrorCode processAppStorePurchase(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const QString &productId,
ServerConfig &serverConfig,
int *duplicateServerIndex = nullptr);
AppStoreRestoreResult processAppStoreRestore(const QString &userCountryCode, const QString &serviceType,
@@ -106,7 +100,7 @@ public:
private:
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false);
bool isApiKeyExpired(int serverIndex) const;
bool isApiKeyExpired(const QString &serverId) const;
ErrorCode extractServerConfigJsonFromResponse(const QByteArray &apiResponseBody, const QString &protocol,
const ProtocolData &protocolData, QJsonObject &serverConfigJson);

View File

@@ -9,11 +9,11 @@
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/utilities.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/serverConfigUtils.h"
#include "version.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/models/serverConfig.h"
#include "core/models/containerConfig.h"
#include "core/models/protocolConfig.h"
@@ -51,7 +51,7 @@ void ConnectionController::setConnectionState(Vpn::ConnectionState state)
}
}
ErrorCode ConnectionController::prepareConnection(int serverIndex,
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
QJsonObject& vpnConfiguration,
DockerContainer& container)
{
@@ -59,35 +59,98 @@ ErrorCode ConnectionController::prepareConnection(int serverIndex,
return ErrorCode::AmneziaServiceNotRunning;
}
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
container = serverConfigModel.defaultContainer();
ContainerConfig containerConfigModel;
QPair<QString, QString> dns;
QString hostName;
QString description;
int configVersion = 0;
bool isApiConfig = false;
const auto kind = m_serversRepository->serverKind(serverId);
switch (kind) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
if (!cfg.has_value()) return ErrorCode::InternalError;
container = cfg->defaultContainer;
containerConfigModel = cfg->containerConfig(container);
dns = { cfg->dns1, cfg->dns2 };
hostName = cfg->hostName;
description = cfg->description;
break;
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
if (!cfg.has_value()) return ErrorCode::InternalError;
container = cfg->defaultContainer;
containerConfigModel = cfg->containerConfig(container);
dns = { cfg->dns1, cfg->dns2 };
hostName = cfg->hostName;
description = cfg->description;
break;
}
case serverConfigUtils::ConfigType::Native: {
const auto cfg = m_serversRepository->nativeConfig(serverId);
if (!cfg.has_value()) return ErrorCode::InternalError;
container = cfg->defaultContainer;
containerConfigModel = cfg->containerConfig(container);
dns = { cfg->dns1, cfg->dns2 };
hostName = cfg->hostName;
description = cfg->description;
break;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
case serverConfigUtils::ConfigType::AmneziaFreeV3:
case serverConfigUtils::ConfigType::ExternalPremium: {
const auto cfg = m_serversRepository->apiV2Config(serverId);
if (!cfg.has_value()) return ErrorCode::InternalError;
container = cfg->defaultContainer;
containerConfigModel = cfg->containerConfig(container);
dns = { cfg->dns1, cfg->dns2 };
hostName = cfg->hostName;
description = cfg->description;
configVersion = serverConfigUtils::ConfigSource::AmneziaGateway;
isApiConfig = true;
break;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
case serverConfigUtils::ConfigType::AmneziaFreeV2:
return ErrorCode::InternalError;
case serverConfigUtils::ConfigType::Invalid:
default:
return ErrorCode::InternalError;
}
if (!isContainerSupported(container)) {
return ErrorCode::NotSupportedOnThisPlatform;
}
if (dns.first.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.first)) {
if (m_appSettingsRepository->useAmneziaDns()) {
dns.first = protocols::dns::amneziaDnsIp;
} else {
dns.first = m_appSettingsRepository->primaryDns();
}
}
if (dns.second.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.second)) {
dns.second = m_appSettingsRepository->secondaryDns();
}
ContainerConfig containerConfigModel = m_serversRepository->containerConfig(serverIndex, container);
auto dns = serverConfigModel.getDnsPair(m_appSettingsRepository->useAmneziaDns(),
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns());
vpnConfiguration = createConnectionConfiguration(dns, serverConfigModel, containerConfigModel, container);
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
containerConfigModel, container);
return ErrorCode::NoError;
}
ErrorCode ConnectionController::openConnection(int serverIndex)
ErrorCode ConnectionController::openConnection(const QString &serverId)
{
QJsonObject vpnConfiguration;
DockerContainer container;
ErrorCode errorCode = prepareConnection(serverIndex, vpnConfiguration, container);
ErrorCode errorCode = prepareConnection(serverId, vpnConfiguration, container);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
emit openConnectionRequested(serverIndex, container, vpnConfiguration);
emit openConnectionRequested(serverId, container, vpnConfiguration);
return ErrorCode::NoError;
}
@@ -120,7 +183,10 @@ ErrorCode ConnectionController::lastConnectionError() const
}
QJsonObject ConnectionController::createConnectionConfiguration(const QPair<QString, QString> &dns,
const ServerConfig &serverConfig,
bool isApiConfig,
const QString &hostName,
const QString &description,
int configVersion,
const ContainerConfig &containerConfig,
DockerContainer container)
{
@@ -134,7 +200,7 @@ QJsonObject ConnectionController::createConnectionConfiguration(const QPair<QStr
ConnectionSettings connectionSettings = {
{ dns.first, dns.second },
serverConfig.isApiConfig(),
isApiConfig,
{
m_appSettingsRepository->isSitesSplitTunnelingEnabled(),
m_appSettingsRepository->routeMode()
@@ -160,10 +226,9 @@ QJsonObject ConnectionController::createConnectionConfiguration(const QPair<QStr
vpnConfiguration[configKey::dns1] = dns.first;
vpnConfiguration[configKey::dns2] = dns.second;
vpnConfiguration[configKey::hostName] = serverConfig.hostName();
vpnConfiguration[configKey::description] = serverConfig.description();
vpnConfiguration[configKey::configVersion] = serverConfig.configVersion();
vpnConfiguration[configKey::hostName] = hostName;
vpnConfiguration[configKey::description] = description;
vpnConfiguration[configKey::configVersion] = configVersion;
return vpnConfiguration;
}

View File

@@ -30,11 +30,11 @@ public:
QObject* parent = nullptr);
~ConnectionController() = default;
ErrorCode prepareConnection(int serverIndex,
ErrorCode prepareConnection(const QString &serverId,
QJsonObject& vpnConfiguration,
DockerContainer& container);
ErrorCode openConnection(int serverIndex);
ErrorCode openConnection(const QString &serverId);
void closeConnection();
@@ -50,7 +50,10 @@ public:
void setConnectionState(Vpn::ConnectionState state);
QJsonObject createConnectionConfiguration(const QPair<QString, QString> &dns,
const ServerConfig &serverConfig,
bool isApiConfig,
const QString &hostName,
const QString &description,
int configVersion,
const ContainerConfig &containerConfig,
DockerContainer container);
@@ -60,7 +63,7 @@ public:
signals:
void connectionStateChanged(Vpn::ConnectionState state);
void openConnectionRequested(int serverIndex, DockerContainer container, const QJsonObject &vpnConfiguration);
void openConnectionRequested(const QString &serverId, DockerContainer container, const QJsonObject &vpnConfiguration);
void closeConnectionRequested();
void setConnectionStateRequested(Vpn::ConnectionState state);
void killSwitchModeChangedRequested(bool enabled);

View File

@@ -8,7 +8,6 @@
#include "core/controllers/selfhosted/installController.h"
#include "core/controllers/selfhosted/importController.h"
#include "core/controllers/coreSignalHandlers.h"
#include "core/models/serverConfig.h"
#include "logger.h"
#include "secureQSettings.h"
@@ -87,6 +86,9 @@ void CoreController::initModels()
m_xrayConfigModel = new XrayConfigModel(this);
setQmlContextProperty("XrayConfigModel", m_xrayConfigModel);
m_xrayConfigSnapshotsModel = new XrayConfigSnapshotsModel(m_appSettingsRepository, m_xrayConfigModel, this);
setQmlContextProperty("XrayConfigSnapshotsModel", m_xrayConfigSnapshotsModel);
m_torConfigModel = new TorConfigModel(this);
setQmlContextProperty("TorConfigModel", m_torConfigModel);
@@ -101,6 +103,12 @@ void CoreController::initModels()
m_socks5ConfigModel = new Socks5ProxyConfigModel(this);
setQmlContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel);
m_mtProxyConfigModel = new MtProxyConfigModel(this);
setQmlContextProperty("MtProxyConfigModel", m_mtProxyConfigModel);
m_telemtConfigModel = new TelemtConfigModel(this);
setQmlContextProperty("TelemtConfigModel", m_telemtConfigModel);
m_clientManagementModel = new ClientManagementModel(this);
setQmlContextProperty("ClientManagementModel", m_clientManagementModel);
@@ -145,7 +153,7 @@ void CoreController::initCoreControllers()
m_allowedDnsController = new AllowedDnsController(m_appSettingsRepository);
m_servicesCatalogController = new ServicesCatalogController(m_appSettingsRepository);
m_subscriptionController = new SubscriptionController(m_serversRepository, m_appSettingsRepository);
m_newsController = new NewsController(m_appSettingsRepository, m_serversController);
m_newsController = new NewsController(m_appSettingsRepository, m_serversRepository);
m_updateController = new UpdateController(m_appSettingsRepository, this);
m_installController = new InstallController(m_serversRepository, m_appSettingsRepository, this);
@@ -165,12 +173,12 @@ void CoreController::initControllers()
setQmlContextProperty("FocusController", m_focusController);
}
m_installUiController = new InstallUiController(m_installController, m_serversController, m_settingsController, m_protocolsModel, m_usersController,
m_installUiController = new InstallUiController(m_installController, m_serversController, m_settingsController, m_protocolsModel, m_usersController,
m_awgConfigModel, m_wireGuardConfigModel, m_openVpnConfigModel, m_xrayConfigModel, m_torConfigModel,
#ifdef Q_OS_WINDOWS
m_ikev2ConfigModel,
#endif
m_sftpConfigModel, m_socks5ConfigModel, this);
m_sftpConfigModel, m_socks5ConfigModel, m_mtProxyConfigModel, m_telemtConfigModel, this);
setQmlContextProperty("InstallController", m_installUiController);
m_importController = new ImportUiController(m_importCoreController, this);
@@ -203,6 +211,10 @@ void CoreController::initControllers()
m_systemController = new SystemController(this);
setQmlContextProperty("SystemController", m_systemController);
m_networkReachabilityController = new NetworkReachabilityController(this);
m_engine->rootContext()->setContextProperty("NetworkReachabilityController", m_networkReachabilityController);
m_engine->rootContext()->setContextProperty("NetworkReachability", m_networkReachabilityController);
m_servicesCatalogUiController = new ServicesCatalogUiController(m_servicesCatalogController, m_apiServicesModel, this);
setQmlContextProperty("ServicesCatalogUiController", m_servicesCatalogUiController);
@@ -262,9 +274,12 @@ void CoreController::initSignalHandlers()
{
m_signalHandlers = new CoreSignalHandlers(this, this);
m_signalHandlers->initAllHandlers();
// Trigger initial update after handlers are connected
m_serversUiController->updateModel();
if (m_serversUiController->hasServersFromGatewayApi()) {
m_apiNewsUiController->fetchNews(false);
}
}
void CoreController::updateTranslator(const QLocale &locale)
@@ -322,11 +337,16 @@ PageController* CoreController::pageController() const
void CoreController::openConnectionByIndex(int serverIndex)
{
const QString serverId =
m_serversUiController ? m_serversUiController->getServerId(serverIndex) : QString();
if (serverId.isEmpty()) {
return;
}
if (m_serversModel) {
m_serversModel->setProcessedServerIndex(serverIndex);
}
if (m_serversController) {
m_serversController->setDefaultServerIndex(serverIndex);
m_serversController->setDefaultServer(serverId);
}
m_connectionUiController->toggleConnection();
}

View File

@@ -28,6 +28,7 @@
#include "ui/controllers/languageUiController.h"
#include "ui/controllers/updateUiController.h"
#include "ui/controllers/api/servicesCatalogUiController.h"
#include "ui/controllers/networkReachabilityController.h"
#include "core/controllers/serversController.h"
#include "core/controllers/selfhosted/usersController.h"
@@ -64,11 +65,15 @@
#include "ui/models/protocols/openvpnConfigModel.h"
#include "ui/models/protocols/wireguardConfigModel.h"
#include "ui/models/protocols/xrayConfigModel.h"
#include "ui/models/protocols/xrayConfigSnapshotsModel.h"
#include "ui/models/protocolsModel.h"
#include "ui/models/services/torConfigModel.h"
#include "ui/models/serversModel.h"
#include "ui/models/services/sftpConfigModel.h"
#include "ui/models/services/socks5ProxyConfigModel.h"
#include "ui/models/services/mtProxyConfigModel.h"
#include "ui/models/services/telemtConfigModel.h"
#include "ui/models/ipSplitTunnelingModel.h"
#include "ui/models/newsModel.h"
@@ -84,7 +89,6 @@ class TestDefaultServerChange;
class TestServerEdgeCases;
class TestSignalOrder;
class TestServersModelSync;
class TestGatewayStacks;
class TestComplexOperations;
class TestSettingsSignals;
class TestUiServersModelAndController;
@@ -101,7 +105,6 @@ class CoreController : public QObject
friend class TestServerEdgeCases;
friend class TestSignalOrder;
friend class TestServersModelSync;
friend class TestGatewayStacks;
friend class TestComplexOperations;
friend class TestSettingsSignals;
friend class TestUiServersModelAndController;
@@ -158,6 +161,7 @@ private:
ServersUiController* m_serversUiController;
IpSplitTunnelingUiController* m_ipSplitTunnelingUiController;
SystemController* m_systemController;
NetworkReachabilityController* m_networkReachabilityController;
AppSplitTunnelingUiController* m_appSplitTunnelingUiController;
AllowedDnsUiController* m_allowedDnsUiController;
LanguageUiController* m_languageUiController;
@@ -202,6 +206,7 @@ private:
OpenVpnConfigModel* m_openVpnConfigModel;
XrayConfigModel* m_xrayConfigModel;
XrayConfigSnapshotsModel* m_xrayConfigSnapshotsModel;
TorConfigModel* m_torConfigModel;
WireGuardConfigModel* m_wireGuardConfigModel;
AwgConfigModel* m_awgConfigModel;
@@ -210,6 +215,8 @@ private:
#endif
SftpConfigModel* m_sftpConfigModel;
Socks5ProxyConfigModel* m_socks5ConfigModel;
MtProxyConfigModel* m_mtProxyConfigModel;
TelemtConfigModel* m_telemtConfigModel;
CoreSignalHandlers* m_signalHandlers;
};

View File

@@ -7,6 +7,7 @@
#include "core/utils/routeModes.h"
#include "core/controllers/coreController.h"
#include "core/repositories/secureServersRepository.h"
#include "core/utils/serverConfigUtils.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "vpnConnection.h"
#include "ui/controllers/qml/pageController.h"
@@ -65,7 +66,6 @@ void CoreSignalHandlers::initAllHandlers()
initImportControllerHandler();
initApiCountryModelUpdateHandler();
initSubscriptionRefreshHandler();
initContainerModelUpdateHandler();
initAdminConfigRevokedHandler();
initPassphraseRequestHandler();
initTranslationsUpdatedHandler();
@@ -78,6 +78,7 @@ void CoreSignalHandlers::initAllHandlers()
initAllowedDnsModelUpdateHandler();
initAppSplitTunnelingModelUpdateHandler();
initPrepareConfigHandler();
initUnsupportedConnectDrawerHandler();
initStrictKillSwitchHandler();
initAndroidSettingsHandler();
initAndroidConnectionHandler();
@@ -124,11 +125,9 @@ void CoreSignalHandlers::initInstallControllerHandler()
{
connect(m_coreController->m_installController, &InstallController::serverIsBusy, m_coreController->m_installUiController, &InstallUiController::serverIsBusy);
connect(m_coreController->m_installUiController, &InstallUiController::cancelInstallation, m_coreController->m_installController, &InstallController::cancelInstallation);
connect(m_coreController->m_installUiController, &InstallUiController::currentContainerUpdated, m_coreController->m_connectionUiController,
&ConnectionUiController::onCurrentContainerUpdated);
connect(m_coreController->m_serversUiController, &ServersUiController::processedServerIndexChanged,
m_coreController->m_installUiController, [this](int index) {
if (index >= 0) {
m_coreController->m_installUiController, [this](int serverIndex) {
if (serverIndex >= 0) {
m_coreController->m_installUiController->clearProcessedServerCredentials();
}
});
@@ -137,20 +136,20 @@ void CoreSignalHandlers::initInstallControllerHandler()
void CoreSignalHandlers::initExportControllerHandler()
{
connect(m_coreController->m_exportController, &ExportController::appendClientRequested, this,
[this](int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container) {
m_coreController->m_usersController->appendClient(serverIndex, clientId, clientName, container);
[this](const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container) {
m_coreController->m_usersController->appendClient(serverId, clientId, clientName, container);
});
connect(m_coreController->m_exportController, &ExportController::updateClientsRequested, this,
[this](int serverIndex, DockerContainer container) {
m_coreController->m_usersController->updateClients(serverIndex, container);
[this](const QString &serverId, DockerContainer container) {
m_coreController->m_usersController->updateClients(serverId, container);
});
connect(m_coreController->m_exportController, &ExportController::revokeClientRequested, this,
[this](int serverIndex, int row, DockerContainer container) {
m_coreController->m_usersController->revokeClient(serverIndex, row, container);
[this](const QString &serverId, int row, DockerContainer container) {
m_coreController->m_usersController->revokeClient(serverId, row, container);
});
connect(m_coreController->m_exportController, &ExportController::renameClientRequested, this,
[this](int serverIndex, int row, const QString &clientName, DockerContainer container) {
m_coreController->m_usersController->renameClient(serverIndex, row, clientName, container);
[this](const QString &serverId, int row, const QString &clientName, DockerContainer container) {
m_coreController->m_usersController->renameClient(serverId, row, clientName, container);
});
}
@@ -159,9 +158,12 @@ void CoreSignalHandlers::initImportControllerHandler()
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
if (!m_coreController->m_connectionController->isConnected()) {
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
m_coreController->m_serversController->setDefaultServerIndex(newServerIndex);
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
if (!serverId.isEmpty()) {
m_coreController->m_serversController->setDefaultServer(serverId);
}
if (m_coreController->m_serversUiController) {
m_coreController->m_serversUiController->setProcessedServerIndex(newServerIndex);
m_coreController->m_serversUiController->setProcessedServerId(serverId);
}
}
});
@@ -170,21 +172,18 @@ void CoreSignalHandlers::initImportControllerHandler()
void CoreSignalHandlers::initApiCountryModelUpdateHandler()
{
connect(m_coreController->m_serversUiController, &ServersUiController::updateApiCountryModel, this, [this]() {
int processedIndex = m_coreController->m_serversUiController->getProcessedServerIndex();
if (processedIndex < 0 || processedIndex >= m_coreController->m_serversRepository->serversCount()) {
const QString processedServerId = m_coreController->m_serversUiController->getProcessedServerId();
if (processedServerId.isEmpty()) {
return;
}
ServerConfig server = m_coreController->m_serversRepository->server(processedIndex);
QJsonArray availableCountries;
QString serverCountryCode;
if (server.isApiV2()) {
const ApiV2ServerConfig* apiV2 = server.as<ApiV2ServerConfig>();
if (apiV2) {
availableCountries = apiV2->apiConfig.availableCountries;
serverCountryCode = apiV2->apiConfig.serverCountryCode;
}
const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId);
if (apiV2.has_value()) {
availableCountries = apiV2->apiConfig.availableCountries;
serverCountryCode = apiV2->apiConfig.serverCountryCode;
}
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode);
@@ -194,18 +193,9 @@ void CoreSignalHandlers::initApiCountryModelUpdateHandler()
void CoreSignalHandlers::initSubscriptionRefreshHandler()
{
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::subscriptionRefreshNeeded, this, [this]() {
const int defaultServerIndex = m_coreController->m_serversController->getDefaultServerIndex();
if (defaultServerIndex >= 0) {
m_coreController->m_subscriptionUiController->getAccountInfo(defaultServerIndex, false);
}
});
}
void CoreSignalHandlers::initContainerModelUpdateHandler()
{
connect(m_coreController->m_serversController, &ServersController::gatewayStacksExpanded, this, [this]() {
if (m_coreController->m_serversUiController->hasServersFromGatewayApi()) {
m_coreController->m_apiNewsUiController->fetchNews(false);
const QString defaultServerId = m_coreController->m_serversController->getDefaultServerId();
if (!defaultServerId.isEmpty()) {
m_coreController->m_subscriptionUiController->getAccountInfo(defaultServerId, false);
}
});
}
@@ -213,17 +203,17 @@ void CoreSignalHandlers::initContainerModelUpdateHandler()
void CoreSignalHandlers::initAdminConfigRevokedHandler()
{
connect(m_coreController->m_installController, &InstallController::clientRevocationRequested, this,
[this](int serverIndex, const ContainerConfig &containerConfig, DockerContainer container) {
m_coreController->m_usersController->revokeClient(serverIndex, containerConfig, container);
[this](const QString &serverId, const ContainerConfig &containerConfig, DockerContainer container) {
m_coreController->m_usersController->revokeClient(serverId, containerConfig, container);
});
connect(m_coreController->m_installController, &InstallController::clientAppendRequested, this,
[this](int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container) {
m_coreController->m_usersController->appendClient(serverIndex, clientId, clientName, container);
[this](const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container) {
m_coreController->m_usersController->appendClient(serverId, clientId, clientName, container);
});
connect(m_coreController->m_usersController, &UsersController::adminConfigRevoked, m_coreController->m_serversController,
&ServersController::clearCachedProfile);
connect(m_coreController->m_usersController, &UsersController::adminConfigRevoked, m_coreController->m_installController,
&InstallController::clearCachedProfile);
}
void CoreSignalHandlers::initPassphraseRequestHandler()
@@ -251,7 +241,8 @@ void CoreSignalHandlers::initLanguageHandler()
void CoreSignalHandlers::initAutoConnectHandler()
{
if (m_coreController->m_settingsUiController->isAutoConnectEnabled() && m_coreController->m_serversController->getDefaultServerIndex() >= 0) {
if (m_coreController->m_settingsUiController->isAutoConnectEnabled()
&& !m_coreController->m_serversController->getDefaultServerId().isEmpty()) {
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); });
}
}
@@ -271,16 +262,20 @@ void CoreSignalHandlers::initServersModelUpdateHandler()
m_coreController->m_serversUiController, &ServersUiController::updateModel);
connect(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged,
m_coreController->m_serversUiController, &ServersUiController::onDefaultServerChanged);
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded,
m_coreController->m_serversController, &ServersController::recomputeGatewayStacks);
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited,
m_coreController->m_serversController, &ServersController::recomputeGatewayStacks);
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved,
m_coreController->m_serversController, &ServersController::recomputeGatewayStacks);
connect(m_coreController->m_settingsUiController, &SettingsUiController::restoreBackupFinished,
m_coreController->m_serversUiController, &ServersUiController::updateModel);
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded, this,
[this](const QString &serverId) {
if (m_coreController->m_serversRepository->apiV2Config(serverId).has_value()) {
m_coreController->m_apiNewsUiController->fetchNews(false);
}
});
connect(m_coreController->m_settingsUiController, &SettingsUiController::restoreBackupFinished, this, [this]() {
m_coreController->m_serversUiController->updateModel();
if (m_coreController->m_serversUiController->hasServersFromGatewayApi()) {
m_coreController->m_apiNewsUiController->fetchNews(false);
}
});
}
void CoreSignalHandlers::initClientManagementModelUpdateHandler()
@@ -315,7 +310,19 @@ void CoreSignalHandlers::initPrepareConfigHandler()
connect(m_coreController->m_connectionUiController, &ConnectionUiController::prepareConfig, this, [this]() {
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Preparing);
m_coreController->m_subscriptionUiController->validateConfig();
const QString serverId = m_coreController->m_serversController->getDefaultServerId();
if (serverId.isEmpty()) {
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
return;
}
const serverConfigUtils::ConfigType kind = m_coreController->m_serversRepository->serverKind(serverId);
if (serverConfigUtils::isApiV2Subscription(kind) || serverConfigUtils::isLegacyApiSubscription(kind)) {
m_coreController->m_subscriptionUiController->validateConfig();
} else {
m_coreController->m_installUiController->validateConfig();
}
});
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::configValidated, this, [this](bool isValid) {
@@ -324,7 +331,7 @@ void CoreSignalHandlers::initPrepareConfigHandler()
return;
}
m_coreController->m_installUiController->validateConfig();
m_coreController->m_connectionUiController->openConnection();
});
connect(m_coreController->m_installUiController, &InstallUiController::configValidated, this, [this](bool isValid) {
@@ -337,6 +344,12 @@ void CoreSignalHandlers::initPrepareConfigHandler()
});
}
void CoreSignalHandlers::initUnsupportedConnectDrawerHandler()
{
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::unsupportedConnectDrawerRequested,
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
}
void CoreSignalHandlers::initStrictKillSwitchHandler()
{
connect(m_coreController->m_settingsUiController, &SettingsUiController::strictKillSwitchEnabledChanged, m_coreController->m_connectionController,
@@ -348,7 +361,10 @@ void CoreSignalHandlers::initAndroidSettingsHandler()
#ifdef Q_OS_ANDROID
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved, this,
[](const QString &/*serverId*/, int removedIndex) {
AndroidController::instance()->resetLastServer(removedIndex);
});
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
#endif
}

View File

@@ -21,7 +21,6 @@ private:
void initImportControllerHandler();
void initApiCountryModelUpdateHandler();
void initSubscriptionRefreshHandler();
void initContainerModelUpdateHandler();
void initAdminConfigRevokedHandler();
void initPassphraseRequestHandler();
void initTranslationsUpdatedHandler();
@@ -34,6 +33,7 @@ private:
void initAllowedDnsModelUpdateHandler();
void initAppSplitTunnelingModelUpdateHandler();
void initPrepareConfigHandler();
void initUnsupportedConnectDrawerHandler();
void initStrictKillSwitchHandler();
void initAndroidSettingsHandler();
void initAndroidConnectionHandler();

View File

@@ -239,7 +239,7 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
connect(reply, &QNetworkReply::finished, reply, [promise, sslErrors, encRequestData, endpoint, apiPayload, reply, this]() mutable {
connect(reply, &QNetworkReply::finished, this, [promise, sslErrors, encRequestData, endpoint, apiPayload, reply, this]() mutable {
QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString();
auto replyError = reply->error();

View File

@@ -5,14 +5,13 @@
#include "core/configurators/configuratorBase.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/qrCodeUtils.h"
#include "core/utils/serialization/serialization.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/models/serverConfig.h"
#include "core/models/selfhosted/selfHostedAdminServerConfig.h"
#include "core/models/containerConfig.h"
#include "core/models/protocolConfig.h"
@@ -27,18 +26,20 @@ ExportController::ExportController(SecureServersRepository* serversRepository,
{
}
ExportController::ExportResult ExportController::generateFullAccessConfig(int serverIndex)
ExportController::ExportResult ExportController::generateFullAccessConfig(const QString &serverId)
{
ExportResult result;
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
serverConfig.visit([](auto& arg) {
for (auto it = arg.containers.begin(); it != arg.containers.end(); ++it) {
it.value().protocolConfig.clearClientConfig();
}
});
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
result.errorCode = ErrorCode::InternalError;
return result;
}
for (auto it = adminConfig->containers.begin(); it != adminConfig->containers.end(); ++it) {
it.value().protocolConfig.clearClientConfig();
}
QJsonObject serverJson = serverConfig.toJson();
QJsonObject serverJson = adminConfig->toJson();
QByteArray compressedConfig = QJsonDocument(serverJson).toJson();
compressedConfig = qCompress(compressedConfig, 8);
result.config = generateVpnUrl(compressedConfig);
@@ -47,13 +48,22 @@ ExportController::ExportResult ExportController::generateFullAccessConfig(int se
return result;
}
ExportController::ExportResult ExportController::generateConnectionConfig(int serverIndex, int containerIndex, const QString &clientName)
ExportController::ExportResult ExportController::generateConnectionConfig(const QString &serverId, int containerIndex, const QString &clientName)
{
ExportResult result;
DockerContainer container = static_cast<DockerContainer>(containerIndex);
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
result.errorCode = ErrorCode::InternalError;
return result;
}
const ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
result.errorCode = ErrorCode::InternalError;
return result;
}
ContainerConfig containerConfig = adminConfig->containerConfig(container);
if (ContainerUtils::containerService(container) != ServiceType::Other) {
SshSession sshSession;
@@ -74,35 +84,25 @@ ExportController::ExportResult ExportController::generateConnectionConfig(int se
QString clientId = newProtocolConfig.clientId();
if (!clientId.isEmpty()) {
emit appendClientRequested(serverIndex, clientId, clientName, container);
emit appendClientRequested(serverId, clientId, clientName, container);
}
}
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
serverConfig.visit([container, containerConfig](auto& arg) {
arg.containers.clear();
arg.containers[container] = containerConfig;
arg.defaultContainer = container;
});
const QPair<QString, QString> dns = adminConfig->getDnsPair(m_appSettingsRepository->useAmneziaDns(),
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns());
if (serverConfig.isSelfHosted()) {
SelfHostedServerConfig* selfHosted = serverConfig.as<SelfHostedServerConfig>();
if (selfHosted) {
selfHosted->userName.reset();
selfHosted->password.reset();
selfHosted->port.reset();
}
}
adminConfig->containers.clear();
adminConfig->containers[container] = containerConfig;
adminConfig->defaultContainer = container;
adminConfig->userName.clear();
adminConfig->password.clear();
adminConfig->port = 0;
auto dns = serverConfig.getDnsPair(m_appSettingsRepository->useAmneziaDns(),
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns());
serverConfig.visit([&dns](auto& arg) {
arg.dns1 = dns.first;
arg.dns2 = dns.second;
});
adminConfig->dns1 = dns.first;
adminConfig->dns2 = dns.second;
QJsonObject serverJson = serverConfig.toJson();
QJsonObject serverJson = adminConfig->toJson();
QByteArray compressedConfig = QJsonDocument(serverJson).toJson();
compressedConfig = qCompress(compressedConfig, 8);
result.config = generateVpnUrl(compressedConfig);
@@ -111,7 +111,7 @@ ExportController::ExportResult ExportController::generateConnectionConfig(int se
return result;
}
ExportController::NativeConfigResult ExportController::generateNativeConfig(int serverIndex, DockerContainer container,
ExportController::NativeConfigResult ExportController::generateNativeConfig(const QString &serverId, DockerContainer container,
const ContainerConfig &containerConfig,
const QString &clientName)
{
@@ -123,11 +123,19 @@ ExportController::NativeConfigResult ExportController::generateNativeConfig(int
Proto protocol = ContainerUtils::defaultProtocol(container);
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
auto dns = serverConfig.getDnsPair(m_appSettingsRepository->useAmneziaDns(),
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns());
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
result.errorCode = ErrorCode::InternalError;
return result;
}
const ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
result.errorCode = ErrorCode::InternalError;
return result;
}
const QPair<QString, QString> dns = adminConfig->getDnsPair(m_appSettingsRepository->useAmneziaDns(),
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns());
ContainerConfig modifiedContainerConfig = containerConfig;
modifiedContainerConfig.container = container;
@@ -157,20 +165,25 @@ ExportController::NativeConfigResult ExportController::generateNativeConfig(int
if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg || protocol == Proto::Xray) {
QString clientId = newProtocolConfig.clientId();
if (!clientId.isEmpty()) {
emit appendClientRequested(serverIndex, clientId, clientName, container);
emit appendClientRequested(serverId, clientId, clientName, container);
}
}
return result;
}
ExportController::ExportResult ExportController::generateOpenVpnConfig(int serverIndex, const QString &clientName)
ExportController::ExportResult ExportController::generateOpenVpnConfig(const QString &serverId, const QString &clientName)
{
ExportResult result;
DockerContainer container = DockerContainer::OpenVpn;
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
result.errorCode = ErrorCode::InternalError;
return result;
}
ContainerConfig containerConfig = adminConfig->containerConfig(container);
auto nativeResult = generateNativeConfig(serverIndex, container, containerConfig, clientName);
auto nativeResult = generateNativeConfig(serverId, container, containerConfig, clientName);
if (nativeResult.errorCode != ErrorCode::NoError) {
result.errorCode = nativeResult.errorCode;
return result;
@@ -185,13 +198,18 @@ ExportController::ExportResult ExportController::generateOpenVpnConfig(int serve
return result;
}
ExportController::ExportResult ExportController::generateWireGuardConfig(int serverIndex, const QString &clientName)
ExportController::ExportResult ExportController::generateWireGuardConfig(const QString &serverId, const QString &clientName)
{
ExportResult result;
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, DockerContainer::WireGuard);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
result.errorCode = ErrorCode::InternalError;
return result;
}
ContainerConfig containerConfig = adminConfig->containerConfig(DockerContainer::WireGuard);
auto nativeResult = generateNativeConfig(serverIndex, DockerContainer::WireGuard, containerConfig, clientName);
auto nativeResult = generateNativeConfig(serverId, DockerContainer::WireGuard, containerConfig, clientName);
if (nativeResult.errorCode != ErrorCode::NoError) {
result.errorCode = nativeResult.errorCode;
return result;
@@ -206,7 +224,7 @@ ExportController::ExportResult ExportController::generateWireGuardConfig(int ser
return result;
}
ExportController::ExportResult ExportController::generateAwgConfig(int serverIndex, int containerIndex, const QString &clientName)
ExportController::ExportResult ExportController::generateAwgConfig(const QString &serverId, int containerIndex, const QString &clientName)
{
ExportResult result;
@@ -215,9 +233,14 @@ ExportController::ExportResult ExportController::generateAwgConfig(int serverInd
result.errorCode = ErrorCode::InternalError;
return result;
}
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
result.errorCode = ErrorCode::InternalError;
return result;
}
ContainerConfig containerConfig = adminConfig->containerConfig(container);
auto nativeResult = generateNativeConfig(serverIndex, container, containerConfig, clientName);
auto nativeResult = generateNativeConfig(serverId, container, containerConfig, clientName);
if (nativeResult.errorCode != ErrorCode::NoError) {
result.errorCode = nativeResult.errorCode;
return result;
@@ -233,13 +256,18 @@ ExportController::ExportResult ExportController::generateAwgConfig(int serverInd
}
ExportController::ExportResult ExportController::generateXrayConfig(int serverIndex, const QString &clientName)
ExportController::ExportResult ExportController::generateXrayConfig(const QString &serverId, const QString &clientName)
{
ExportResult result;
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, DockerContainer::Xray);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
result.errorCode = ErrorCode::InternalError;
return result;
}
ContainerConfig containerConfig = adminConfig->containerConfig(DockerContainer::Xray);
auto nativeResult = generateNativeConfig(serverIndex, DockerContainer::Xray, containerConfig, clientName);
auto nativeResult = generateNativeConfig(serverId, DockerContainer::Xray, containerConfig, clientName);
if (nativeResult.errorCode != ErrorCode::NoError) {
result.errorCode = nativeResult.errorCode;
return result;
@@ -295,6 +323,18 @@ ExportController::ExportResult ExportController::generateXrayConfig(int serverIn
vlessServer.shortId = realitySettings.value(amnezia::protocols::xray::shortId).toString();
vlessServer.fingerprint = realitySettings.value(amnezia::protocols::xray::fingerprint).toString("chrome");
vlessServer.spiderX = realitySettings.value(amnezia::protocols::xray::spiderX).toString("");
} else if (vlessServer.security == "tls") {
QJsonObject tlsSettings = streamSettings.value("tlsSettings").toObject();
vlessServer.serverName = tlsSettings.value(amnezia::protocols::xray::serverName).toString();
vlessServer.fingerprint = tlsSettings.value(amnezia::protocols::xray::fingerprint).toString();
// alpn: serialize array back to comma-separated for VLESS URI
QJsonArray alpnArr = tlsSettings.value("alpn").toArray();
QStringList alpnList;
for (const QJsonValue &v : alpnArr) {
alpnList << v.toString();
}
// alpn goes into vless URI query param — handled by Serialize via serverName/alpn fields
// VlessServerObject doesn't have alpn field, so we embed in serverName if needed
}
result.nativeConfigString = amnezia::serialization::vless::Serialize(vlessServer, "AmneziaVPN");
@@ -302,22 +342,22 @@ ExportController::ExportResult ExportController::generateXrayConfig(int serverIn
return result;
}
void ExportController::updateClientManagementModel(int serverIndex, int containerIndex)
void ExportController::updateClientManagementModel(const QString &serverId, int containerIndex)
{
DockerContainer container = static_cast<DockerContainer>(containerIndex);
emit updateClientsRequested(serverIndex, container);
emit updateClientsRequested(serverId, container);
}
void ExportController::revokeConfig(int row, int serverIndex, int containerIndex)
void ExportController::revokeConfig(int row, const QString &serverId, int containerIndex)
{
DockerContainer container = static_cast<DockerContainer>(containerIndex);
emit revokeClientRequested(serverIndex, row, container);
emit revokeClientRequested(serverId, row, container);
}
void ExportController::renameClient(int row, const QString &clientName, int serverIndex, int containerIndex)
void ExportController::renameClient(int row, const QString &clientName, const QString &serverId, int containerIndex)
{
DockerContainer container = static_cast<DockerContainer>(containerIndex);
emit renameClientRequested(serverIndex, row, clientName, container);
emit renameClientRequested(serverId, row, clientName, container);
}
QString ExportController::generateVpnUrl(const QByteArray &compressedConfig)

View File

@@ -37,23 +37,23 @@ public:
SecureAppSettingsRepository* appSettingsRepository,
QObject *parent = nullptr);
ExportResult generateFullAccessConfig(int serverIndex);
ExportResult generateConnectionConfig(int serverIndex, int containerIndex, const QString &clientName);
ExportResult generateOpenVpnConfig(int serverIndex, const QString &clientName);
ExportResult generateWireGuardConfig(int serverIndex, const QString &clientName);
ExportResult generateAwgConfig(int serverIndex, int containerIndex, const QString &clientName);
ExportResult generateXrayConfig(int serverIndex, const QString &clientName);
ExportResult generateFullAccessConfig(const QString &serverId);
ExportResult generateConnectionConfig(const QString &serverId, int containerIndex, const QString &clientName);
ExportResult generateOpenVpnConfig(const QString &serverId, const QString &clientName);
ExportResult generateWireGuardConfig(const QString &serverId, const QString &clientName);
ExportResult generateAwgConfig(const QString &serverId, int containerIndex, const QString &clientName);
ExportResult generateXrayConfig(const QString &serverId, const QString &clientName);
signals:
void appendClientRequested(int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container);
void updateClientsRequested(int serverIndex, DockerContainer container);
void revokeClientRequested(int serverIndex, int row, DockerContainer container);
void renameClientRequested(int serverIndex, int row, const QString &clientName, DockerContainer container);
void appendClientRequested(const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container);
void updateClientsRequested(const QString &serverId, DockerContainer container);
void revokeClientRequested(const QString &serverId, int row, DockerContainer container);
void renameClientRequested(const QString &serverId, int row, const QString &clientName, DockerContainer container);
public slots:
void updateClientManagementModel(int serverIndex, int containerIndex);
void revokeConfig(int row, int serverIndex, int containerIndex);
void renameClient(int row, const QString &clientName, int serverIndex, int containerIndex);
void updateClientManagementModel(const QString &serverId, int containerIndex);
void revokeConfig(int row, const QString &serverId, int containerIndex);
void renameClient(int row, const QString &clientName, const QString &serverId, int containerIndex);
private:
struct NativeConfigResult
@@ -62,7 +62,7 @@ private:
QJsonObject jsonNativeConfig;
};
NativeConfigResult generateNativeConfig(int serverIndex, DockerContainer container,
NativeConfigResult generateNativeConfig(const QString &serverId, DockerContainer container,
const ContainerConfig &containerConfig,
const QString &clientName);

View File

@@ -16,7 +16,7 @@
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/api/apiEnums.h"
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "core/utils/api/apiUtils.h"
@@ -27,7 +27,6 @@
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/qrCodeUtils.h"
#include "core/models/serverConfig.h"
using namespace amnezia;
using namespace ProtocolUtils;
@@ -208,12 +207,18 @@ ImportController::ImportResult ImportController::extractConfigFromData(const QSt
case ConfigTypes::Amnezia: {
result.config = QJsonDocument::fromJson(config.toUtf8()).object();
if (apiUtils::isServerFromApi(result.config)) {
if (serverConfigUtils::isServerFromApi(result.config)) {
auto apiConfig = result.config.value(apiDefs::key::apiConfig).toObject();
apiConfig[apiDefs::key::vpnKey] = data;
result.config[apiDefs::key::apiConfig] = apiConfig;
}
if (serverConfigUtils::isLegacyApiSubscription(serverConfigUtils::configTypeFromJson(result.config))) {
result.errorCode = ErrorCode::LegacyApiV1NotSupportedError;
result.config = {};
return result;
}
processAmneziaConfig(result.config);
if (!result.config.empty()) {
checkForMaliciousStrings(result.config, result.maliciousWarningText);
@@ -381,18 +386,29 @@ void ImportController::importConfig(const QJsonObject &config)
credentials.secretData = config.value(configKey::password).toString();
if (credentials.isValid() || config.contains(configKey::containers)) {
ServerConfig serverConfig = ServerConfig::fromJson(config);
m_serversRepository->addServer(serverConfig);
m_serversRepository->addServer(QString(), config, serverConfigUtils::configTypeFromJson(config));
emit importFinished();
} else if (config.contains(configKey::configVersion)) {
quint16 crc = qChecksum(QJsonDocument(config).toJson());
if (m_serversRepository->hasServerWithCrc(crc)) {
bool hasServerWithCrc = false;
const QVector<QString> ids = m_serversRepository->orderedServerIds();
for (const QString &id : ids) {
const auto apiV2 = m_serversRepository->apiV2Config(id);
if (!apiV2.has_value()) {
continue;
}
if (static_cast<quint16>(apiV2->crc) == crc) {
hasServerWithCrc = true;
break;
}
}
if (hasServerWithCrc) {
emit importErrorOccurred(ErrorCode::ApiConfigAlreadyAdded, true);
} else {
QJsonObject configWithCrc = config;
configWithCrc.insert(configKey::crc, crc);
ServerConfig serverConfig = ServerConfig::fromJson(configWithCrc);
m_serversRepository->addServer(serverConfig);
m_serversRepository->addServer(QString(), configWithCrc, serverConfigUtils::configTypeFromJson(configWithCrc));
emit importFinished();
}
} else {

View File

@@ -19,6 +19,8 @@
#include "core/installers/openvpnInstaller.h"
#include "core/installers/sftpInstaller.h"
#include "core/installers/socks5Installer.h"
#include "core/installers/mtProxyInstaller.h"
#include "core/installers/telemtInstaller.h"
#include "core/installers/torInstaller.h"
#include "core/installers/wireguardInstaller.h"
#include "core/installers/xrayInstaller.h"
@@ -33,8 +35,8 @@
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/models/serverConfig.h"
#include "core/models/containerConfig.h"
#include "core/models/protocols/mtProxyProtocolConfig.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include "ui/models/protocols/wireguardConfigModel.h"
#include "core/utils/utilities.h"
@@ -54,6 +56,21 @@ using namespace ProtocolUtils;
namespace
{
Logger logger("InstallController");
bool dockerDaemonContainerMissing(const QString &out, const QString &containerDockerName)
{
if (!out.contains(QLatin1String("Error response from daemon"), Qt::CaseInsensitive)) {
return false;
}
if (out.contains(QLatin1String("No such container"), Qt::CaseInsensitive)
&& out.contains(containerDockerName, Qt::CaseInsensitive)) {
return true;
}
if (out.size() < 700 && out.contains(QLatin1String("is not running"), Qt::CaseInsensitive)) {
return true;
}
return false;
}
}
InstallController::InstallController(SecureServersRepository *serversRepository,
@@ -129,15 +146,36 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
return startupContainerWorker(credentials, container, config, sshSession);
}
ErrorCode InstallController::updateContainer(int serverIndex, DockerContainer container, const ContainerConfig &oldConfig,
ErrorCode InstallController::updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
ContainerConfig &newConfig)
{
if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) {
m_serversRepository->setContainerConfig(serverIndex, container, newConfig);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
if (container == DockerContainer::MtProxy) {
ServerCredentials credentials = adminConfig->credentials();
SshSession sshSession(this);
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
} else if (container == DockerContainer::Telemt) {
ServerCredentials credentials = adminConfig->credentials();
SshSession sshSession(this);
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
}
adminConfig->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
return ErrorCode::NoError;
}
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession(this);
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
@@ -154,64 +192,116 @@ ErrorCode InstallController::updateContainer(int serverIndex, DockerContainer co
}
if (errorCode == ErrorCode::NoError) {
clearCachedProfile(serverIndex, container);
m_serversRepository->setContainerConfig(serverIndex, container, newConfig);
if (container == DockerContainer::MtProxy) {
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
} else if (container == DockerContainer::Telemt) {
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
}
clearCachedProfile(serverId, container);
adminConfig->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
}
return errorCode;
}
void InstallController::clearCachedProfile(int serverIndex, DockerContainer container)
void InstallController::clearCachedProfile(const QString &serverId, DockerContainer container)
{
if (ContainerUtils::containerService(container) == ServiceType::Other) {
return;
}
ContainerConfig containerConfigModel = m_serversRepository->containerConfig(serverIndex, container);
m_serversRepository->clearLastConnectionConfig(serverIndex, container);
emit clientRevocationRequested(serverIndex, containerConfigModel, container);
}
ErrorCode InstallController::validateAndPrepareConfig(int serverIndex)
{
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
if (serverConfigModel.isApiConfig()) {
return ErrorCode::NoError;
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return;
}
DockerContainer container = serverConfigModel.defaultContainer();
adminConfig->clearCachedClientProfile(container);
const ContainerConfig containerConfigModel = adminConfig->containerConfig(container);
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
emit clientRevocationRequested(serverId, containerConfigModel, container);
}
ErrorCode InstallController::validateAndPrepareConfig(const QString &serverId)
{
const auto kind = m_serversRepository->serverKind(serverId);
DockerContainer container = DockerContainer::None;
ContainerConfig containerConfig;
switch (kind) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
containerConfig = cfg->containerConfig(container);
break;
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
containerConfig = cfg->containerConfig(container);
break;
}
case serverConfigUtils::ConfigType::Native: {
const auto cfg = m_serversRepository->nativeConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
containerConfig = cfg->containerConfig(container);
break;
}
default:
return ErrorCode::InternalError;
}
if (container == DockerContainer::None) {
return ErrorCode::NoInstalledContainersError;
}
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container);
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
SshSession sshSession;
auto isProtocolConfigExists = [](const ContainerConfig &cfg) {
return cfg.protocolConfig.hasClientConfig();
};
if (!isProtocolConfigExists(containerConfig)) {
QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
ErrorCode errorCode = processContainerForAdmin(container, containerConfig, credentials, sshSession, serverIndex, clientName);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
m_serversRepository->setContainerConfig(serverIndex, container, containerConfig);
if (containerConfig.protocolConfig.hasClientConfig()) {
return ErrorCode::NoError;
}
if (kind != serverConfigUtils::ConfigType::SelfHostedAdmin) {
return ErrorCode::InternalError;
}
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession;
const QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
const ErrorCode errorCode = processContainerForAdmin(container, containerConfig, credentials, sshSession, serverId, clientName);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
adminConfig->updateContainerConfig(container, containerConfig);
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
return ErrorCode::NoError;
}
void InstallController::validateConfig(int serverIndex)
void InstallController::validateConfig(const QString &serverId)
{
QFuture<ErrorCode> future = QtConcurrent::run([this, serverIndex]() {
return validateAndPrepareConfig(serverIndex);
QFuture<ErrorCode> future = QtConcurrent::run([this, serverId]() {
return validateAndPrepareConfig(serverId);
});
auto *watcher = new QFutureWatcher<ErrorCode>(this);
@@ -230,6 +320,21 @@ void InstallController::validateConfig(int serverIndex)
watcher->setFuture(future);
}
void InstallController::addEmptyServer(const ServerCredentials &credentials)
{
SelfHostedAdminServerConfig serverConfig;
serverConfig.hostName = credentials.hostName;
serverConfig.userName = credentials.userName;
serverConfig.password = credentials.secretData;
serverConfig.port = credentials.port;
serverConfig.description = m_appSettingsRepository->nextAvailableServerName();
serverConfig.displayName = serverConfig.description.isEmpty() ? serverConfig.hostName : serverConfig.description;
serverConfig.defaultContainer = DockerContainer::None;
m_serversRepository->addServer(QString(), serverConfig.toJson(),
serverConfigUtils::ConfigType::SelfHostedAdmin);
}
ErrorCode InstallController::prepareContainerConfig(DockerContainer container, const ServerCredentials &credentials, ContainerConfig &containerConfig, SshSession &sshSession)
{
if (!ContainerUtils::isSupportedByCurrentPlatform(container)) {
@@ -257,7 +362,7 @@ ErrorCode InstallController::prepareContainerConfig(DockerContainer container, c
return ErrorCode::NoError;
}
void InstallController::adminAppendRequested(int serverIndex, DockerContainer container,
void InstallController::adminAppendRequested(const QString &serverId, DockerContainer container,
const ContainerConfig &containerConfig, const QString &clientName)
{
if (ContainerUtils::containerService(container) == ServiceType::Other
@@ -266,13 +371,13 @@ void InstallController::adminAppendRequested(int serverIndex, DockerContainer co
}
QString clientId = containerConfig.protocolConfig.clientId();
if (!clientId.isEmpty()) {
emit clientAppendRequested(serverIndex, clientId, clientName, container);
emit clientAppendRequested(serverId, clientId, clientName, container);
}
}
ErrorCode InstallController::processContainerForAdmin(DockerContainer container, ContainerConfig &containerConfig,
const ServerCredentials &credentials, SshSession &sshSession,
int serverIndex, const QString &clientName)
const QString &serverId, const QString &clientName)
{
if (ContainerUtils::isSupportedByCurrentPlatform(container)) {
ErrorCode errorCode = prepareContainerConfig(container, credentials, containerConfig, sshSession);
@@ -280,7 +385,7 @@ ErrorCode InstallController::processContainerForAdmin(DockerContainer container,
return errorCode;
}
}
adminAppendRequested(serverIndex, container, containerConfig, clientName);
adminAppendRequested(serverId, container, containerConfig, clientName);
return ErrorCode::NoError;
}
@@ -372,9 +477,24 @@ ErrorCode InstallController::configureContainerWorker(const ServerCredentials &c
sshSession.replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container), baseVars),
cbReadStdOut, cbReadStdErr);
if (e != ErrorCode::NoError) {
return e;
}
if (dockerDaemonContainerMissing(stdOut, ContainerUtils::containerToString(container))) {
qDebug() << "configureContainerWorker: Docker daemon reports container missing/stopped, output:" << stdOut;
return ErrorCode::ServerContainerMissingError;
}
updateContainerConfigAfterInstallation(container, config, stdOut);
return e;
if (container == DockerContainer::MtProxy) {
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, config);
} else if (container == DockerContainer::Telemt) {
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, config);
}
return ErrorCode::NoError;
}
ErrorCode InstallController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession)
@@ -527,6 +647,79 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
}
}
if (container == DockerContainer::MtProxy) {
const auto *oldMt = oldConfig.getMtProxyProtocolConfig();
const auto *newMt = newConfig.getMtProxyProtocolConfig();
if (oldMt && newMt) {
const QString oldPort =
oldMt->port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : oldMt->port;
const QString newPort =
newMt->port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : newMt->port;
if (oldPort != newPort) {
return true;
}
const QString oldTransport = oldMt->transportMode.isEmpty() ? QString(
protocols::mtProxy::transportModeStandard)
: oldMt->transportMode;
const QString newTransport = newMt->transportMode.isEmpty() ? QString(
protocols::mtProxy::transportModeStandard)
: newMt->transportMode;
if (oldTransport != newTransport) {
return true;
}
if (oldMt->tlsDomain != newMt->tlsDomain) {
return true;
}
}
}
if (container == DockerContainer::Telemt) {
const auto *oldT = oldConfig.getTelemtProtocolConfig();
const auto *newT = newConfig.getTelemtProtocolConfig();
if (oldT && newT) {
const QString oldPort =
oldT->port.isEmpty() ? QString(protocols::telemt::defaultPort) : oldT->port;
const QString newPort =
newT->port.isEmpty() ? QString(protocols::telemt::defaultPort) : newT->port;
if (oldPort != newPort) {
return true;
}
const QString oldTransport = oldT->transportMode.isEmpty()
? QString(protocols::telemt::transportModeStandard)
: oldT->transportMode;
const QString newTransport = newT->transportMode.isEmpty()
? QString(protocols::telemt::transportModeStandard)
: newT->transportMode;
if (oldTransport != newTransport) {
return true;
}
if (oldT->tlsDomain != newT->tlsDomain) {
return true;
}
if (oldT->maskEnabled != newT->maskEnabled) {
return true;
}
if (oldT->tlsEmulation != newT->tlsEmulation) {
return true;
}
if (oldT->useMiddleProxy != newT->useMiddleProxy) {
return true;
}
if (oldT->tag != newT->tag) {
return true;
}
const QString oldUser = oldT->userName.isEmpty()
? QString::fromUtf8(protocols::telemt::defaultUserName)
: oldT->userName;
const QString newUser = newT->userName.isEmpty()
? QString::fromUtf8(protocols::telemt::defaultUserName)
: newT->userName;
if (oldUser != newUser) {
return true;
}
}
}
if (container == DockerContainer::Socks5Proxy) {
return true;
}
@@ -618,7 +811,7 @@ ErrorCode InstallController::isUserInSudo(const ServerCredentials &credentials,
return ErrorCode::ServerUserDirectoryNotAccessible;
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
return ErrorCode::ServerUserNotAllowedInSudoers;
if (stdOut.contains("password is required"))
if (stdOut.contains("password is required") || stdOut.contains("authentication is required"))
return ErrorCode::ServerUserPasswordRequired;
return error;
@@ -688,9 +881,16 @@ ErrorCode InstallController::setupServerFirewall(const ServerCredentials &creden
amnezia::genBaseVars(credentials, DockerContainer::None, QString(), QString())));
}
ErrorCode InstallController::rebootServer(int serverIndex)
ErrorCode InstallController::rebootServer(const QString &serverId)
{
auto credentials = m_serversRepository->serverCredentials(serverIndex);
const auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession(this);
QString script = QString("sudo reboot");
@@ -709,27 +909,38 @@ ErrorCode InstallController::rebootServer(int serverIndex)
return sshSession.runScript(credentials, script, cbReadStdOut, cbReadStdErr);
}
ErrorCode InstallController::removeAllContainers(int serverIndex)
ErrorCode InstallController::removeAllContainers(const QString &serverId)
{
auto credentials = m_serversRepository->serverCredentials(serverIndex);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession(this);
ErrorCode errorCode = sshSession.runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers));
if (errorCode == ErrorCode::NoError) {
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
serverConfigModel.visit([](auto& arg) {
arg.containers.clear();
arg.defaultContainer = DockerContainer::None;
});
m_serversRepository->editServer(serverIndex, serverConfigModel);
adminConfig->containers.clear();
adminConfig->defaultContainer = DockerContainer::None;
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
}
return errorCode;
}
ErrorCode InstallController::removeContainer(int serverIndex, DockerContainer container)
ErrorCode InstallController::removeContainer(const QString &serverId, DockerContainer container)
{
auto credentials = m_serversRepository->serverCredentials(serverIndex);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession(this);
ErrorCode errorCode = sshSession.runScript(
credentials,
@@ -737,11 +948,10 @@ ErrorCode InstallController::removeContainer(int serverIndex, DockerContainer co
amnezia::genBaseVars(credentials, container, QString(), QString())));
if (errorCode == ErrorCode::NoError) {
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
QMap<DockerContainer, ContainerConfig> containers = serverConfigModel.containers();
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
containers.remove(container);
DockerContainer defaultContainer = serverConfigModel.defaultContainer();
DockerContainer defaultContainer = adminConfig->defaultContainer;
if (defaultContainer == container) {
if (containers.isEmpty()) {
defaultContainer = DockerContainer::None;
@@ -749,12 +959,10 @@ ErrorCode InstallController::removeContainer(int serverIndex, DockerContainer co
defaultContainer = containers.begin().key();
}
}
serverConfigModel.visit([&containers, defaultContainer](auto& arg) {
arg.containers = containers;
arg.defaultContainer = defaultContainer;
});
m_serversRepository->editServer(serverIndex, serverConfigModel);
adminConfig->containers = containers;
adminConfig->defaultContainer = defaultContainer;
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
}
return errorCode;
@@ -772,6 +980,8 @@ QScopedPointer<InstallerBase> InstallController::createInstaller(DockerContainer
case DockerContainer::TorWebSite: return QScopedPointer<InstallerBase>(new TorInstaller(this));
case DockerContainer::Sftp: return QScopedPointer<InstallerBase>(new SftpInstaller(this));
case DockerContainer::Socks5Proxy: return QScopedPointer<InstallerBase>(new Socks5Installer(this));
case DockerContainer::MtProxy: return QScopedPointer<InstallerBase>(new MtProxyInstaller(this));
case DockerContainer::Telemt: return QScopedPointer<InstallerBase>(new TelemtInstaller(this));
default: return QScopedPointer<InstallerBase>(new InstallerBase(this));
}
}
@@ -810,14 +1020,35 @@ bool InstallController::isUpdateDockerContainerRequired(DockerContainer containe
return false;
}
}
} else if (container == DockerContainer::MtProxy) {
const auto *oldMt = oldConfig.getMtProxyProtocolConfig();
const auto *newMt = newConfig.getMtProxyProtocolConfig();
if (!oldMt || !newMt) {
return true;
}
return !oldMt->equalsDockerDeploymentSettings(*newMt);
} else if (container == DockerContainer::Telemt) {
const auto *oldT = oldConfig.getTelemtProtocolConfig();
const auto *newT = newConfig.getTelemtProtocolConfig();
if (!oldT || !newT) {
return true;
}
return !oldT->equalsDockerDeploymentSettings(*newT);
}
return true;
}
ErrorCode InstallController::scanServerForInstalledContainers(int serverIndex)
ErrorCode InstallController::scanServerForInstalledContainers(const QString &serverId)
{
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession(this);
QMap<DockerContainer, ContainerConfig> installedContainers;
@@ -826,8 +1057,7 @@ ErrorCode InstallController::scanServerForInstalledContainers(int serverIndex)
return errorCode;
}
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
QMap<DockerContainer, ContainerConfig> containers = serverConfigModel.containers();
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
bool hasNewContainers = false;
QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
@@ -835,29 +1065,25 @@ ErrorCode InstallController::scanServerForInstalledContainers(int serverIndex)
if (!containers.contains(iterator.key())) {
ContainerConfig containerConfig = iterator.value();
errorCode = processContainerForAdmin(iterator.key(), containerConfig, credentials, sshSession,
serverIndex, clientName);
serverId, clientName);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
containers.insert(iterator.key(), containerConfig);
hasNewContainers = true;
DockerContainer defaultContainer = serverConfigModel.defaultContainer();
DockerContainer defaultContainer = adminConfig->defaultContainer;
if (defaultContainer == DockerContainer::None
&& ContainerUtils::containerService(iterator.key()) != ServiceType::Other
&& ContainerUtils::isSupportedByCurrentPlatform(iterator.key())) {
serverConfigModel.visit([iterator](auto& arg) {
arg.defaultContainer = iterator.key();
});
adminConfig->defaultContainer = iterator.key();
}
}
}
if (hasNewContainers) {
serverConfigModel.visit([&containers](auto& arg) {
arg.containers = containers;
});
m_serversRepository->editServer(serverIndex, serverConfigModel);
adminConfig->containers = containers;
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
}
return ErrorCode::NoError;
@@ -899,7 +1125,7 @@ ErrorCode InstallController::installServer(const ServerCredentials &credentials,
preparedContainers.insert(container, containerConfig);
}
SelfHostedServerConfig serverConfig;
SelfHostedAdminServerConfig serverConfig;
serverConfig.hostName = credentials.hostName;
serverConfig.userName = credentials.userName;
serverConfig.password = credentials.secretData;
@@ -912,21 +1138,29 @@ ErrorCode InstallController::installServer(const ServerCredentials &credentials,
serverConfig.defaultContainer = container;
m_serversRepository->addServer(ServerConfig(serverConfig));
serverConfig.displayName = serverConfig.description.isEmpty() ? serverConfig.hostName : serverConfig.description;
int serverIndex = m_serversRepository->serversCount() - 1;
const QString newServerId = m_serversRepository->addServer(QString(), serverConfig.toJson(),
serverConfigUtils::ConfigType::SelfHostedAdmin);
QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
for (auto iterator = preparedContainers.begin(); iterator != preparedContainers.end(); iterator++) {
adminAppendRequested(serverIndex, iterator.key(), iterator.value(), clientName);
adminAppendRequested(newServerId, iterator.key(), iterator.value(), clientName);
}
return ErrorCode::NoError;
}
ErrorCode InstallController::installContainer(int serverIndex, DockerContainer container, int port,
ErrorCode InstallController::installContainer(const QString &serverId, DockerContainer container, int port,
TransportProto transportProto, bool &wasContainerInstalled)
{
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession(this);
QMap<DockerContainer, ContainerConfig> installedContainers;
@@ -949,15 +1183,17 @@ ErrorCode InstallController::installContainer(int serverIndex, DockerContainer c
QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) {
ContainerConfig existingConfigModel = m_serversRepository->containerConfig(serverIndex, iterator.key());
ContainerConfig existingConfigModel = adminConfig->containerConfig(iterator.key());
if (existingConfigModel.container == DockerContainer::None) {
ContainerConfig containerConfig = iterator.value();
errorCode = processContainerForAdmin(iterator.key(), containerConfig, credentials, sshSession,
serverIndex, clientName);
serverId, clientName);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
m_serversRepository->setContainerConfig(serverIndex, iterator.key(), containerConfig);
adminConfig->updateContainerConfig(iterator.key(), containerConfig);
m_serversRepository->editServer(serverId, adminConfig->toJson(),
serverConfigUtils::ConfigType::SelfHostedAdmin);
}
}
@@ -993,7 +1229,15 @@ bool InstallController::isServerAlreadyExists(const ServerCredentials &credentia
{
int serversCount = m_serversRepository->serversCount();
for (int i = 0; i < serversCount; i++) {
const ServerCredentials existingCredentials = m_serversRepository->serverCredentials(i);
const QString existingServerId = m_serversRepository->serverIdAt(i);
const auto adminConfig = m_serversRepository->selfHostedAdminConfig(existingServerId);
if (!adminConfig.has_value()) {
continue;
}
const ServerCredentials existingCredentials = adminConfig->credentials();
if (!existingCredentials.isValid()) {
continue;
}
if (credentials.hostName == existingCredentials.hostName && credentials.port == existingCredentials.port) {
existingServerIndex = i;
return true;
@@ -1093,6 +1337,56 @@ void InstallController::updateContainerConfigAfterInstallation(DockerContainer c
onion.replace("\n", "");
torProtocolConfig->serverConfig.site = onion;
}
} else if (container == DockerContainer::MtProxy) {
if (auto* mtProxyConfig = containerConfig.getMtProxyProtocolConfig()) {
qDebug() << "amnezia mtproxy" << stdOut;
static const QRegularExpression reSecret(
QStringLiteral(R"(\[\*\]\s+Secret:\s+([0-9a-fA-F]{32}))"),
QRegularExpression::CaseInsensitiveOption);
static const QRegularExpression reTgLink(QStringLiteral(R"(\[\*\]\s+tg://\s+link:\s+(tg://proxy\?[^\s]+))"));
static const QRegularExpression reTmeLink(
QStringLiteral(R"(\[\*\]\s+t\.me\s+link:\s+(https://t\.me/proxy\?[^\s]+))"));
const QRegularExpressionMatch mSecret = reSecret.match(stdOut);
const QRegularExpressionMatch mTgLink = reTgLink.match(stdOut);
const QRegularExpressionMatch mTmeLink = reTmeLink.match(stdOut);
if (mSecret.hasMatch()) {
mtProxyConfig->secret = mSecret.captured(1);
}
if (mTgLink.hasMatch()) {
mtProxyConfig->tgLink = mTgLink.captured(1);
}
if (mTmeLink.hasMatch()) {
mtProxyConfig->tmeLink = mTmeLink.captured(1);
}
}
} else if (container == DockerContainer::Telemt) {
if (auto *telemtConfig = containerConfig.getTelemtProtocolConfig()) {
qDebug() << "amnezia-telemt configure stdout" << stdOut;
static const QRegularExpression reSecret(
QStringLiteral(R"(\[\*\]\s+Secret:\s+([0-9a-fA-F]{32}))"),
QRegularExpression::CaseInsensitiveOption);
static const QRegularExpression reTgLink(QStringLiteral(R"(\[\*\]\s+tg://\s+link:\s+(tg://proxy\?[^\s]+))"));
static const QRegularExpression reTmeLink(
QStringLiteral(R"(\[\*\]\s+t\.me\s+link:\s+(https://t\.me/proxy\?[^\s]+))"));
const QRegularExpressionMatch mSecret = reSecret.match(stdOut);
const QRegularExpressionMatch mTgLink = reTgLink.match(stdOut);
const QRegularExpressionMatch mTmeLink = reTmeLink.match(stdOut);
if (mSecret.hasMatch()) {
telemtConfig->secret = mSecret.captured(1);
}
if (mTgLink.hasMatch()) {
telemtConfig->tgLink = mTgLink.captured(1);
}
if (mTmeLink.hasMatch()) {
telemtConfig->tmeLink = mTmeLink.captured(1);
}
}
}
}
@@ -1177,3 +1471,126 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
return ErrorCode::NoError;
}
ErrorCode InstallController::setDockerContainerEnabledState(const QString &serverId, DockerContainer container, bool enabled)
{
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt) {
return ErrorCode::InternalError;
}
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
const QString containerName = ContainerUtils::containerToString(container);
SshSession sshSession(this);
const QString script = enabled ? QStringLiteral("sudo docker start %1").arg(containerName)
: QStringLiteral("sudo docker stop %1").arg(containerName);
const ErrorCode runError = sshSession.runScript(credentials, script);
if (runError != ErrorCode::NoError) {
return runError;
}
ContainerConfig currentConfig = adminConfig->containerConfig(container);
bool persist = false;
if (auto *mtConfig = currentConfig.getMtProxyProtocolConfig()) {
mtConfig->isEnabled = enabled;
persist = true;
} else if (auto *telemtConfig = currentConfig.getTelemtProtocolConfig()) {
telemtConfig->isEnabled = enabled;
persist = true;
}
if (persist) {
adminConfig->updateContainerConfig(container, currentConfig);
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
}
return ErrorCode::NoError;
}
ErrorCode InstallController::queryDockerContainerStatus(const QString &serverId, DockerContainer container, int &statusOut)
{
statusOut = 3;
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
const QString containerName = ContainerUtils::containerToString(container);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data;
return ErrorCode::NoError;
};
SshSession sshSession(this);
const QString script = QStringLiteral(
"sudo docker inspect --format '{{.State.Status}}' %1 2>/dev/null || echo 'not_found'")
.arg(containerName);
const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
const QString status = stdOut.trimmed();
if (status == QLatin1String("running")) {
statusOut = 1;
} else if (status == QLatin1String("not_found") || status.isEmpty()) {
statusOut = 0;
} else if (status == QLatin1String("exited") || status == QLatin1String("created")
|| status == QLatin1String("paused")) {
statusOut = 2;
} else {
statusOut = 3;
}
return ErrorCode::NoError;
}
ErrorCode InstallController::queryMtProxyDiagnostics(const QString &serverId, DockerContainer container, int listenPort,
MtProxyContainerDiagnostics &out)
{
out = {};
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession(this);
return MtProxyInstaller::queryDiagnostics(sshSession, credentials, container, listenPort, out);
}
QString InstallController::fetchDockerContainerSecret(const QString &serverId, DockerContainer container)
{
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt) {
return {};
}
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return {};
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return {};
}
const QString containerName = ContainerUtils::containerToString(container);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data;
return ErrorCode::NoError;
};
SshSession sshSession(this);
const QString path = QStringLiteral("/data/secret");
const QString cmd = QStringLiteral("sudo docker exec %1 cat %2").arg(containerName, path);
const ErrorCode errorCode = sshSession.runScript(credentials, cmd, cbReadStdOut);
if (errorCode != ErrorCode::NoError) {
return {};
}
const QString secret = stdOut.trimmed();
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
return hex32.match(secret).hasMatch() ? secret : QString();
}

View File

@@ -16,6 +16,7 @@
#include "core/models/containerConfig.h"
#include "core/repositories/secureServersRepository.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/installers/mtProxyInstaller.h"
class SshSession;
class InstallerBase;
@@ -33,22 +34,32 @@ public:
~InstallController();
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
ErrorCode updateContainer(int serverIndex, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
ErrorCode rebootServer(int serverIndex);
ErrorCode removeAllContainers(int serverIndex);
ErrorCode removeContainer(int serverIndex, DockerContainer container);
ErrorCode rebootServer(const QString &serverId);
ErrorCode removeAllContainers(const QString &serverId);
ErrorCode removeContainer(const QString &serverId, DockerContainer container);
ErrorCode setDockerContainerEnabledState(const QString &serverId, DockerContainer container, bool enabled);
/// statusOut: 0 = not deployed, 1 = running, 2 = stopped, 3 = error
ErrorCode queryDockerContainerStatus(const QString &serverId, DockerContainer container, int &statusOut);
ErrorCode queryMtProxyDiagnostics(const QString &serverId, DockerContainer container, int listenPort,
MtProxyContainerDiagnostics &out);
QString fetchDockerContainerSecret(const QString &serverId, DockerContainer container);
ContainerConfig generateConfig(DockerContainer container, int port, TransportProto transportProto);
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, ContainerConfig> &installedContainers, SshSession &sshSession);
ErrorCode scanServerForInstalledContainers(int serverIndex);
ErrorCode scanServerForInstalledContainers(const QString &serverId);
ErrorCode installContainer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto, ContainerConfig &config);
ErrorCode installServer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto,
bool &wasContainerInstalled);
ErrorCode installContainer(int serverIndex, DockerContainer container, int port, TransportProto transportProto,
ErrorCode installContainer(const QString &serverId, DockerContainer container, int port, TransportProto transportProto,
bool &wasContainerInstalled);
bool isUpdateDockerContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig);
@@ -62,11 +73,13 @@ public:
void cancelInstallation();
void clearCachedProfile(int serverIndex, DockerContainer container);
void clearCachedProfile(const QString &serverId, DockerContainer container);
ErrorCode validateAndPrepareConfig(int serverIndex);
ErrorCode validateAndPrepareConfig(const QString &serverId);
void validateConfig(int serverIndex);
void validateConfig(const QString &serverId);
void addEmptyServer(const ServerCredentials &credentials);
signals:
void configValidated(bool isValid);
@@ -74,8 +87,8 @@ signals:
void serverIsBusy(const bool isBusy);
void cancelInstallationRequested();
void clientRevocationRequested(int serverIndex, const ContainerConfig &containerConfig, DockerContainer container);
void clientAppendRequested(int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container);
void clientRevocationRequested(const QString &serverId, const ContainerConfig &containerConfig, DockerContainer container);
void clientAppendRequested(const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container);
private:
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container, SshSession &sshSession);
@@ -95,9 +108,9 @@ private:
ErrorCode processContainerForAdmin(DockerContainer container, ContainerConfig &containerConfig,
const ServerCredentials &credentials, SshSession &sshSession,
int serverIndex, const QString &clientName);
const QString &serverId, const QString &clientName);
void adminAppendRequested(int serverIndex, DockerContainer container,
void adminAppendRequested(const QString &serverId, DockerContainer container,
const ContainerConfig &containerConfig, const QString &clientName);
static void updateContainerConfigAfterInstallation(DockerContainer container, ContainerConfig &containerConfig, const QString &stdOut);
@@ -114,4 +127,3 @@ private:
};
#endif // INSTALLCONTROLLER_H

View File

@@ -14,7 +14,6 @@
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/models/serverConfig.h"
#include "core/models/containerConfig.h"
using namespace amnezia;
@@ -292,11 +291,18 @@ ErrorCode UsersController::getXrayClients(const DockerContainer container, const
return error;
}
ErrorCode UsersController::updateClients(int serverIndex, const DockerContainer container)
ErrorCode UsersController::updateClients(const QString &serverId, const DockerContainer container)
{
ErrorCode error = ErrorCode::NoError;
SshSession sshSession;
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
if (container == DockerContainer::OpenVpn) {
@@ -381,20 +387,27 @@ ErrorCode UsersController::updateClients(int serverIndex, const DockerContainer
}
ErrorCode UsersController::appendClient(int serverIndex, const QString &clientId, const QString &clientName, const DockerContainer container)
ErrorCode UsersController::appendClient(const QString &serverId, const QString &clientId, const QString &clientName, const DockerContainer container)
{
ErrorCode error = ErrorCode::NoError;
SshSession sshSession;
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
error = updateClients(serverIndex, container);
error = updateClients(serverId, container);
if (error != ErrorCode::NoError) {
return error;
}
int existingIndex = clientIndexById(clientId, m_clientsTable);
if (existingIndex >= 0) {
return renameClient(serverIndex, existingIndex, clientName, container, true);
return renameClient(serverId, existingIndex, clientName, container, true);
}
QJsonObject client;
@@ -426,7 +439,7 @@ ErrorCode UsersController::appendClient(int serverIndex, const QString &clientId
return error;
}
ErrorCode UsersController::renameClient(int serverIndex, const int row, const QString &clientName,
ErrorCode UsersController::renameClient(const QString &serverId, const int row, const QString &clientName,
const DockerContainer container, bool addTimeStamp)
{
if (row < 0 || row >= m_clientsTable.size()) {
@@ -434,7 +447,14 @@ ErrorCode UsersController::renameClient(int serverIndex, const int row, const QS
}
SshSession sshSession;
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
auto client = m_clientsTable.at(row).toObject();
auto userData = client[configKey::userData].toObject();
@@ -470,7 +490,7 @@ ErrorCode UsersController::renameClient(int serverIndex, const int row, const QS
}
ErrorCode UsersController::revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials,
const int serverIndex, SshSession* sshSession, QJsonArray &clientsTable)
SshSession* sshSession, QJsonArray &clientsTable)
{
if (row < 0 || row >= clientsTable.size()) {
return ErrorCode::InternalError;
@@ -689,14 +709,21 @@ ErrorCode UsersController::revokeXray(const int row,
return error;
}
ErrorCode UsersController::revokeClient(int serverIndex, const int index, const DockerContainer container)
ErrorCode UsersController::revokeClient(const QString &serverId, const int index, const DockerContainer container)
{
if (index < 0 || index >= m_clientsTable.size()) {
return ErrorCode::InternalError;
}
SshSession sshSession;
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
QString clientId = m_clientsTable.at(index).toObject().value(configKey::clientId).toString();
ErrorCode errorCode = ErrorCode::NoError;
@@ -704,7 +731,7 @@ ErrorCode UsersController::revokeClient(int serverIndex, const int index, const
switch(container)
{
case DockerContainer::OpenVpn: {
errorCode = revokeOpenVpn(index, container, credentials, serverIndex, &sshSession, m_clientsTable);
errorCode = revokeOpenVpn(index, container, credentials, &sshSession, m_clientsTable);
break;
}
case DockerContainer::WireGuard:
@@ -724,12 +751,15 @@ ErrorCode UsersController::revokeClient(int serverIndex, const int index, const
}
if (errorCode == ErrorCode::NoError) {
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
ContainerConfig containerCfg = m_serversRepository->containerConfig(serverIndex, container);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ContainerConfig containerCfg = adminConfig->containerConfig(container);
QString containerClientId = containerCfg.protocolConfig.clientId();
if (!clientId.isEmpty() && !containerClientId.isEmpty() && containerClientId.contains(clientId)) {
emit adminConfigRevoked(serverIndex, container);
emit adminConfigRevoked(serverId, container);
}
emit clientRevoked(index);
@@ -739,13 +769,20 @@ ErrorCode UsersController::revokeClient(int serverIndex, const int index, const
return errorCode;
}
ErrorCode UsersController::revokeClient(int serverIndex, const ContainerConfig &containerConfig, const DockerContainer container)
ErrorCode UsersController::revokeClient(const QString &serverId, const ContainerConfig &containerConfig, const DockerContainer container)
{
SshSession sshSession;
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
ErrorCode errorCode = ErrorCode::NoError;
errorCode = updateClients(serverIndex, container);
errorCode = updateClients(serverId, container);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
@@ -778,7 +815,7 @@ ErrorCode UsersController::revokeClient(int serverIndex, const ContainerConfig &
switch (container)
{
case DockerContainer::OpenVpn: {
errorCode = revokeOpenVpn(row, container, credentials, serverIndex, &sshSession, m_clientsTable);
errorCode = revokeOpenVpn(row, container, credentials, &sshSession, m_clientsTable);
break;
}
case DockerContainer::WireGuard:
@@ -797,7 +834,7 @@ ErrorCode UsersController::revokeClient(int serverIndex, const ContainerConfig &
}
if (errorCode == ErrorCode::NoError) {
emit adminConfigRevoked(serverIndex, container);
emit adminConfigRevoked(serverId, container);
emit clientRevoked(row);
emit clientsUpdated(m_clientsTable);
}

View File

@@ -37,21 +37,21 @@ signals:
void clientAdded(const QJsonObject &client);
void clientRenamed(int row, const QString &newName);
void clientRevoked(int row);
void adminConfigRevoked(int serverIndex, DockerContainer container);
void adminConfigRevoked(const QString &serverId, DockerContainer container);
public slots:
ErrorCode updateClients(int serverIndex, const DockerContainer container);
ErrorCode appendClient(int serverIndex, const QString &clientId, const QString &clientName, const DockerContainer container);
ErrorCode renameClient(int serverIndex, const int row, const QString &userName, const DockerContainer container, bool addTimeStamp = false);
ErrorCode revokeClient(int serverIndex, const int index, const DockerContainer container);
ErrorCode revokeClient(int serverIndex, const ContainerConfig &containerConfig, const DockerContainer container);
ErrorCode updateClients(const QString &serverId, const DockerContainer container);
ErrorCode appendClient(const QString &serverId, const QString &clientId, const QString &clientName, const DockerContainer container);
ErrorCode renameClient(const QString &serverId, const int row, const QString &userName, const DockerContainer container, bool addTimeStamp = false);
ErrorCode revokeClient(const QString &serverId, const int index, const DockerContainer container);
ErrorCode revokeClient(const QString &serverId, const ContainerConfig &containerConfig, const DockerContainer container);
private:
bool isClientExists(const QString &clientId, const QJsonArray &clientsTable);
int clientIndexById(const QString &clientId, const QJsonArray &clientsTable);
void migration(const QByteArray &clientsTableString, QJsonArray &clientsTable);
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials, const int serverIndex,
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, QJsonArray &clientsTable);
ErrorCode revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials,
SshSession* sshSession, QJsonArray &clientsTable);
@@ -73,4 +73,3 @@ private:
};
#endif // USERSCONTROLLER_H

View File

@@ -1,81 +1,268 @@
#include "serversController.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/api/apiEnums.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "core/utils/serverConfigUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/models/serverConfig.h"
#include "core/models/containerConfig.h"
#include "core/models/serverDescription.h"
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include <AmneziaVPN-Swift.h>
#endif
ServersController::ServersController(SecureServersRepository* serversRepository,
SecureAppSettingsRepository* appSettingsRepository,
QObject *parent)
ServersController::ServersController(SecureServersRepository *serversRepository,
SecureAppSettingsRepository *appSettingsRepository, QObject *parent)
: QObject(parent), m_serversRepository(serversRepository), m_appSettingsRepository(appSettingsRepository)
{
recomputeGatewayStacks();
ensureDefaultServerValid();
}
void ServersController::addServer(const ServerConfig &server)
void ServersController::ensureDefaultServerValid()
{
m_serversRepository->addServer(server);
}
void ServersController::editServer(int index, const ServerConfig &server)
{
m_serversRepository->editServer(index, server);
}
void ServersController::removeServer(int index)
{
m_serversRepository->removeServer(index);
}
void ServersController::setDefaultServerIndex(int index)
{
m_serversRepository->setDefaultServer(index);
}
void ServersController::setDefaultContainer(int serverIndex, DockerContainer container)
{
m_serversRepository->setDefaultContainer(serverIndex, container);
}
void ServersController::updateContainerConfig(int serverIndex, DockerContainer container, const ContainerConfig &config)
{
m_serversRepository->setContainerConfig(serverIndex, container, config);
}
void ServersController::clearCachedProfile(int serverIndex, DockerContainer container)
{
m_serversRepository->clearLastConnectionConfig(serverIndex, container);
}
QJsonArray ServersController::getServersArray() const
{
QJsonArray result;
QVector<ServerConfig> servers = m_serversRepository->servers();
for (const ServerConfig& server : servers) {
result.append(server.toJson());
if (!getServersCount()) {
return;
}
const QString defaultId = getDefaultServerId();
if (!defaultId.isEmpty() && indexOfServerId(defaultId) >= 0) {
return;
}
const QString firstId = getServerId(0);
if (!firstId.isEmpty()) {
setDefaultServer(firstId);
}
return result;
}
QVector<ServerConfig> ServersController::getServers() const
bool ServersController::renameServer(const QString &serverId, const QString &name)
{
return m_serversRepository->servers();
const serverConfigUtils::ConfigType kind = m_serversRepository->serverKind(serverId);
switch (kind) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
if (!cfg.has_value()) return false;
cfg->description = name;
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
return true;
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
if (!cfg.has_value()) return false;
cfg->description = name;
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
return true;
}
case serverConfigUtils::ConfigType::Native: {
auto cfg = m_serversRepository->nativeConfig(serverId);
if (!cfg.has_value()) return false;
cfg->description = name;
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
return true;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
case serverConfigUtils::ConfigType::AmneziaFreeV3:
case serverConfigUtils::ConfigType::ExternalPremium: {
auto cfg = m_serversRepository->apiV2Config(serverId);
if (!cfg.has_value()) return false;
cfg->name = name;
cfg->nameOverriddenByUser = true;
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
return true;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
case serverConfigUtils::ConfigType::AmneziaFreeV2:
case serverConfigUtils::ConfigType::Invalid:
default:
return false;
}
}
ContainerConfig ServersController::getContainerConfig(int serverIndex, DockerContainer container) const
void ServersController::removeServer(const QString &serverId)
{
return m_serversRepository->containerConfig(serverIndex, container);
m_serversRepository->removeServer(serverId);
}
void ServersController::setDefaultServer(const QString &serverId)
{
m_serversRepository->setDefaultServer(serverId);
}
void ServersController::setDefaultContainer(const QString &serverId, DockerContainer container)
{
const serverConfigUtils::ConfigType kind = m_serversRepository->serverKind(serverId);
switch (kind) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
if (!cfg.has_value()) return;
cfg->defaultContainer = container;
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
return;
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
if (!cfg.has_value()) return;
cfg->defaultContainer = container;
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
return;
}
case serverConfigUtils::ConfigType::Native: {
auto cfg = m_serversRepository->nativeConfig(serverId);
if (!cfg.has_value()) return;
cfg->defaultContainer = container;
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
return;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
case serverConfigUtils::ConfigType::AmneziaFreeV3:
case serverConfigUtils::ConfigType::ExternalPremium: {
auto cfg = m_serversRepository->apiV2Config(serverId);
if (!cfg.has_value()) return;
cfg->defaultContainer = container;
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
return;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
case serverConfigUtils::ConfigType::AmneziaFreeV2:
case serverConfigUtils::ConfigType::Invalid:
default:
return;
}
}
QVector<ServerDescription> ServersController::buildServerDescriptions(bool isAmneziaDnsEnabled) const
{
QVector<ServerDescription> out;
const QVector<QString> ids = m_serversRepository->orderedServerIds();
out.reserve(ids.size());
for (const QString &id : ids) {
ServerDescription d;
using Kind = serverConfigUtils::ConfigType;
const Kind kind = m_serversRepository->serverKind(id);
switch (kind) {
case Kind::SelfHostedAdmin: {
const auto cfg = m_serversRepository->selfHostedAdminConfig(id);
if (!cfg) {
continue;
}
d = buildServerDescription(*cfg, isAmneziaDnsEnabled);
break;
}
case Kind::SelfHostedUser: {
const auto cfg = m_serversRepository->selfHostedUserConfig(id);
if (!cfg) {
continue;
}
d = buildServerDescription(*cfg, isAmneziaDnsEnabled);
break;
}
case Kind::Native: {
const auto cfg = m_serversRepository->nativeConfig(id);
if (!cfg) {
continue;
}
d = buildServerDescription(*cfg, isAmneziaDnsEnabled);
break;
}
case Kind::AmneziaPremiumV2:
case Kind::AmneziaFreeV3:
case Kind::ExternalPremium: {
const auto cfg = m_serversRepository->apiV2Config(id);
if (!cfg) {
continue;
}
d = buildServerDescription(*cfg, isAmneziaDnsEnabled);
break;
}
case Kind::AmneziaPremiumV1:
case Kind::AmneziaFreeV2: {
const auto cfg = m_serversRepository->legacyApiConfig(id);
if (!cfg) {
continue;
}
d = buildServerDescription(*cfg, isAmneziaDnsEnabled);
break;
}
case Kind::Invalid:
default:
continue;
}
d.serverId = id;
out.append(d);
}
return out;
}
QMap<DockerContainer, ContainerConfig> ServersController::getServerContainersMap(const QString &serverId) const
{
switch (m_serversRepository->serverKind(serverId)) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
return cfg.has_value() ? cfg->containers : QMap<DockerContainer, ContainerConfig>{};
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
return cfg.has_value() ? cfg->containers : QMap<DockerContainer, ContainerConfig>{};
}
case serverConfigUtils::ConfigType::Native: {
const auto cfg = m_serversRepository->nativeConfig(serverId);
return cfg.has_value() ? cfg->containers : QMap<DockerContainer, ContainerConfig>{};
}
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
case serverConfigUtils::ConfigType::AmneziaFreeV3:
case serverConfigUtils::ConfigType::ExternalPremium: {
const auto cfg = m_serversRepository->apiV2Config(serverId);
return cfg.has_value() ? cfg->containers : QMap<DockerContainer, ContainerConfig>{};
}
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
case serverConfigUtils::ConfigType::AmneziaFreeV2: {
const auto cfg = m_serversRepository->legacyApiConfig(serverId);
return cfg.has_value() ? cfg->containers : QMap<DockerContainer, ContainerConfig>{};
}
case serverConfigUtils::ConfigType::Invalid:
default:
return {};
}
}
DockerContainer ServersController::getDefaultContainer(const QString &serverId) const
{
switch (m_serversRepository->serverKind(serverId)) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
return cfg.has_value() ? cfg->defaultContainer : DockerContainer::None;
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
return cfg.has_value() ? cfg->defaultContainer : DockerContainer::None;
}
case serverConfigUtils::ConfigType::Native: {
const auto cfg = m_serversRepository->nativeConfig(serverId);
return cfg.has_value() ? cfg->defaultContainer : DockerContainer::None;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
case serverConfigUtils::ConfigType::AmneziaFreeV3:
case serverConfigUtils::ConfigType::ExternalPremium: {
const auto cfg = m_serversRepository->apiV2Config(serverId);
return cfg.has_value() ? cfg->defaultContainer : DockerContainer::None;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
case serverConfigUtils::ConfigType::AmneziaFreeV2: {
const auto cfg = m_serversRepository->legacyApiConfig(serverId);
return cfg.has_value() ? cfg->defaultContainer : DockerContainer::None;
}
case serverConfigUtils::ConfigType::Invalid:
default:
return DockerContainer::None;
}
}
ContainerConfig ServersController::getContainerConfig(const QString &serverId, DockerContainer container) const
{
return getServerContainersMap(serverId).value(container);
}
int ServersController::getDefaultServerIndex() const
@@ -83,114 +270,131 @@ int ServersController::getDefaultServerIndex() const
return m_serversRepository->defaultServerIndex();
}
QString ServersController::getDefaultServerId() const
{
return m_serversRepository->defaultServerId();
}
int ServersController::getServersCount() const
{
return m_serversRepository->serversCount();
}
ServerConfig ServersController::getServerConfig(int serverIndex) const
QString ServersController::getServerId(int serverIndex) const
{
return m_serversRepository->server(serverIndex);
return m_serversRepository->serverIdAt(serverIndex);
}
ServerCredentials ServersController::getServerCredentials(int serverIndex) const
int ServersController::indexOfServerId(const QString &serverId) const
{
return m_serversRepository->serverCredentials(serverIndex);
return m_serversRepository->indexOfServerId(serverId);
}
QPair<QString, QString> ServersController::getDnsPair(int serverIndex, bool isAmneziaDnsEnabled) const
QString ServersController::notificationDisplayName(const QString &serverId) const
{
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
return serverConfig.getDnsPair(isAmneziaDnsEnabled,
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns());
}
if (serverId.isEmpty()) {
return {};
}
ServersController::GatewayStacksData ServersController::gatewayStacks() const
{
return m_gatewayStacks;
}
void ServersController::recomputeGatewayStacks()
{
GatewayStacksData computed;
bool hasNewTags = false;
QVector<ServerConfig> servers = m_serversRepository->servers();
for (const ServerConfig& serverConfig : servers) {
if (serverConfig.isApiV2()) {
const ApiV2ServerConfig* apiV2 = serverConfig.as<ApiV2ServerConfig>();
if (!apiV2) continue;
const QString userCountryCode = apiV2->apiConfig.userCountryCode;
const QString serviceType = apiV2->serviceType();
if (!userCountryCode.isEmpty()) {
if (!m_gatewayStacks.userCountryCodes.contains(userCountryCode)) {
hasNewTags = true;
}
computed.userCountryCodes.insert(userCountryCode);
}
if (!serviceType.isEmpty()) {
if (!m_gatewayStacks.serviceTypes.contains(serviceType)) {
hasNewTags = true;
}
computed.serviceTypes.insert(serviceType);
using Kind = serverConfigUtils::ConfigType;
switch (m_serversRepository->serverKind(serverId)) {
case Kind::SelfHostedAdmin: {
if (const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId)) {
if (!cfg->displayName.isEmpty()) {
return cfg->displayName;
}
}
break;
}
m_gatewayStacks = std::move(computed);
if (hasNewTags) {
emit gatewayStacksExpanded();
}
}
bool ServersController::GatewayStacksData::operator==(const GatewayStacksData &other) const
{
return userCountryCodes == other.userCountryCodes && serviceTypes == other.serviceTypes;
}
QJsonObject ServersController::GatewayStacksData::toJson() const
{
QJsonObject json;
QJsonArray userCountryCodesArray;
for (const QString &code : userCountryCodes) {
userCountryCodesArray.append(code);
}
json[apiDefs::key::userCountryCode] = userCountryCodesArray;
QJsonArray serviceTypesArray;
for (const QString &type : serviceTypes) {
serviceTypesArray.append(type);
}
json[apiDefs::key::serviceType] = serviceTypesArray;
return json;
}
bool ServersController::isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType, const QString &serviceProtocol) const
{
QVector<ServerConfig> servers = m_serversRepository->servers();
for (const ServerConfig& serverConfig : servers) {
if (serverConfig.isApiV2()) {
const ApiV2ServerConfig* apiV2 = serverConfig.as<ApiV2ServerConfig>();
if (!apiV2) return false;
if (apiV2->apiConfig.userCountryCode == userCountryCode
&& apiV2->serviceType() == serviceType
&& apiV2->serviceProtocol() == serviceProtocol) {
return true;
case Kind::SelfHostedUser: {
if (const auto cfg = m_serversRepository->selfHostedUserConfig(serverId)) {
if (!cfg->displayName.isEmpty()) {
return cfg->displayName;
}
}
break;
}
case Kind::Native: {
if (const auto cfg = m_serversRepository->nativeConfig(serverId)) {
if (!cfg->displayName.isEmpty()) {
return cfg->displayName;
}
}
break;
}
case Kind::AmneziaPremiumV2:
case Kind::AmneziaFreeV3:
case Kind::ExternalPremium: {
if (const auto cfg = m_serversRepository->apiV2Config(serverId)) {
if (!cfg->displayName.isEmpty()) {
return cfg->displayName;
}
}
break;
}
case Kind::AmneziaPremiumV1:
case Kind::AmneziaFreeV2: {
if (const auto cfg = m_serversRepository->legacyApiConfig(serverId)) {
if (!cfg->displayName.isEmpty()) {
return cfg->displayName;
}
}
break;
}
default:
break;
}
const int idx = indexOfServerId(serverId);
if (idx >= 0) {
return QString::number(idx + 1);
}
return serverId;
}
std::optional<ApiV2ServerConfig> ServersController::apiV2Config(const QString &serverId) const
{
return m_serversRepository->apiV2Config(serverId);
}
std::optional<SelfHostedAdminServerConfig> ServersController::selfHostedAdminConfig(const QString &serverId) const
{
return m_serversRepository->selfHostedAdminConfig(serverId);
}
ServerCredentials ServersController::getServerCredentials(const QString &serverId) const
{
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
if (cfg.has_value()) {
const ServerCredentials creds = cfg->credentials();
if (creds.isValid()) {
return creds;
}
}
return ServerCredentials {};
}
bool ServersController::isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol) const
{
const QVector<QString> ids = m_serversRepository->orderedServerIds();
for (const QString &id : ids) {
const auto apiV2 = m_serversRepository->apiV2Config(id);
if (!apiV2.has_value()) {
continue;
}
if (apiV2->apiConfig.userCountryCode == userCountryCode && apiV2->serviceType() == serviceType
&& apiV2->serviceProtocol() == serviceProtocol) {
return true;
}
}
return false;
}
bool ServersController::hasInstalledContainers(int serverIndex) const
bool ServersController::hasInstalledContainers(const QString &serverId) const
{
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
QMap<DockerContainer, ContainerConfig> containers = serverConfig.containers();
const QMap<DockerContainer, ContainerConfig> containers = getServerContainersMap(serverId);
for (auto it = containers.begin(); it != containers.end(); ++it) {
DockerContainer container = it.key();
if (ContainerUtils::containerService(container) == ServiceType::Vpn) {
@@ -203,3 +407,8 @@ bool ServersController::hasInstalledContainers(int serverIndex) const
return false;
}
bool ServersController::isLegacyApiV1Server(const QString &serverId) const
{
return !serverId.isEmpty()
&& serverConfigUtils::isLegacyApiSubscription(m_serversRepository->serverKind(serverId));
}

View File

@@ -1,11 +1,11 @@
#ifndef SERVERSCONTROLLER_H
#define SERVERSCONTROLLER_H
#include <optional>
#include <QObject>
#include <QJsonObject>
#include <QJsonArray>
#include <QSet>
#include <QVector>
#include <QMap>
#include <QPair>
@@ -17,34 +17,18 @@
#include "core/utils/commonStructs.h"
#include "core/repositories/secureServersRepository.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/models/serverConfig.h"
#include "core/models/containerConfig.h"
#include "core/models/serverDescription.h"
class SshSession;
class InstallController;
using namespace amnezia;
/**
* @brief Core business logic controller for server operations
*
* This controller contains pure business logic for managing servers.
*/
class ServersController : public QObject
{
Q_OBJECT
public:
struct GatewayStacksData
{
QSet<QString> userCountryCodes;
QSet<QString> serviceTypes;
bool isEmpty() const { return userCountryCodes.isEmpty() && serviceTypes.isEmpty(); }
bool operator==(const GatewayStacksData &other) const;
QJsonObject toJson() const;
};
public:
explicit ServersController(SecureServersRepository* serversRepository,
SecureAppSettingsRepository* appSettingsRepository = nullptr,
@@ -52,44 +36,38 @@ public:
~ServersController() = default;
// Server management
void addServer(const ServerConfig &server);
void editServer(int index, const ServerConfig &server);
void removeServer(int index);
void setDefaultServerIndex(int index);
bool renameServer(const QString &serverId, const QString &name);
void removeServer(const QString &serverId);
void setDefaultServer(const QString &serverId);
// Container management
void setDefaultContainer(int serverIndex, DockerContainer container);
void updateContainerConfig(int serverIndex, DockerContainer container, const ContainerConfig &config);
// Cache management
void clearCachedProfile(int serverIndex, DockerContainer container);
void setDefaultContainer(const QString &serverId, DockerContainer container);
// Getters
QJsonArray getServersArray() const;
QVector<ServerConfig> getServers() const;
QVector<ServerDescription> buildServerDescriptions(bool isAmneziaDnsEnabled) const;
int getDefaultServerIndex() const;
QString getDefaultServerId() const;
int getServersCount() const;
ServerConfig getServerConfig(int serverIndex) const;
ServerCredentials getServerCredentials(int serverIndex) const;
ContainerConfig getContainerConfig(int serverIndex, DockerContainer container) const;
QPair<QString, QString> getDnsPair(int serverIndex, bool isAmneziaDnsEnabled) const;
GatewayStacksData gatewayStacks() const;
QString getServerId(int serverIndex) const;
int indexOfServerId(const QString &serverId) const;
QString notificationDisplayName(const QString &serverId) const;
std::optional<ApiV2ServerConfig> apiV2Config(const QString &serverId) const;
std::optional<SelfHostedAdminServerConfig> selfHostedAdminConfig(const QString &serverId) const;
ServerCredentials getServerCredentials(const QString &serverId) const;
QMap<DockerContainer, ContainerConfig> getServerContainersMap(const QString &serverId) const;
DockerContainer getDefaultContainer(const QString &serverId) const;
ContainerConfig getContainerConfig(const QString &serverId, DockerContainer container) const;
// Validation
bool isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType, const QString &serviceProtocol) const;
bool hasInstalledContainers(int serverIndex) const;
signals:
void gatewayStacksExpanded();
public slots:
void recomputeGatewayStacks();
bool hasInstalledContainers(const QString &serverId) const;
bool isLegacyApiV1Server(const QString &serverId) const;
private:
void ensureDefaultServerValid();
SecureServersRepository* m_serversRepository;
SecureAppSettingsRepository* m_appSettingsRepository;
GatewayStacksData m_gatewayStacks;
};
#endif // SERVERSCONTROLLER_H

View File

@@ -179,12 +179,9 @@ QString SettingsController::getAppVersion() const
void SettingsController::clearSettings()
{
int serverCount = m_serversRepository->serversCount();
m_appSettingsRepository->clearSettings();
m_serversRepository->setServersArray(QJsonArray());
m_serversRepository->setDefaultServer(0);
m_serversRepository->clearServers();
emit siteSplitTunnelingRouteModeChanged(RouteMode::VpnOnlyForwardSites);
emit siteSplitTunnelingToggled(false);

View File

@@ -21,14 +21,14 @@ namespace
Logger logger("UpdateController");
#if defined(Q_OS_WINDOWS)
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_x64.exe");
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN-%1-win64.exe");
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN_installer.exe";
#elif defined(Q_OS_MACOS)
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_macos.pkg");
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN-%1-Darwin.pkg");
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.pkg";
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_linux_x64.tar");
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.tar";
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN-%1-Linux.run");
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.run";
#endif
}
@@ -106,7 +106,7 @@ void UpdateController::fetchGatewayUrl()
// Workaround: wait before contacting gateway to avoid rate limit triggered by other requests (news etc.)
QTimer::singleShot(1000, this, [this, gatewayController, apiPayload]() {
gatewayController->postAsync(QStringLiteral("%1v1/updater_endpoint"), apiPayload)
.then(this, [this](QPair<ErrorCode, QByteArray> result) {
.then(this, [this, gatewayController](QPair<ErrorCode, QByteArray> result) {
auto [err, gatewayResponse] = result;
if (err != ErrorCode::NoError) {
logger.error() << errorString(err);
@@ -346,36 +346,10 @@ int UpdateController::runMacInstaller(const QString &installerPath)
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
int UpdateController::runLinuxInstaller(const QString &installerPath)
{
// Create temporary directory for extraction
QTemporaryDir extractDir;
extractDir.setAutoRemove(false);
if (!extractDir.isValid()) {
logger.error() << "Failed to create temporary directory";
return -1;
}
logger.info() << "Temporary directory created:" << extractDir.path();
QFile::setPermissions(installerPath, QFile::permissions(installerPath) | QFile::ExeUser);
// Create script file in the temporary directory
QString scriptPath = extractDir.path() + "/installer.sh";
QFile scriptFile(scriptPath);
if (!scriptFile.open(QIODevice::WriteOnly)) {
logger.error() << "Failed to create script file";
return -1;
}
// Get script content from registry
QString scriptContent = amnezia::scriptData(amnezia::ClientScriptType::linux_installer);
scriptFile.write(scriptContent.toUtf8());
scriptFile.close();
logger.info() << "Script file created:" << scriptPath;
// Make script executable
QFile::setPermissions(scriptPath, QFile::permissions(scriptPath) | QFile::ExeUser);
// Start detached process
qint64 pid;
bool success =
QProcess::startDetached("/bin/bash", QStringList() << scriptPath << extractDir.path() << installerPath, extractDir.path(), &pid);
bool success = QProcess::startDetached(installerPath, QStringList(), QString(), &pid);
if (success) {
logger.info() << "Installation process started with PID:" << pid;
@@ -387,5 +361,3 @@ int UpdateController::runLinuxInstaller(const QString &installerPath)
return 0;
}
#endif

View File

@@ -0,0 +1,16 @@
#ifndef CONTAINERDIAGNOSTICS_H
#define CONTAINERDIAGNOSTICS_H
namespace amnezia
{
struct ContainerDiagnostics
{
bool available = false;
bool portReachable = false;
virtual ~ContainerDiagnostics() = default;
};
} // namespace amnezia
#endif // CONTAINERDIAGNOSTICS_H

View File

@@ -0,0 +1,18 @@
#ifndef MTPROXYDIAGNOSTICS_H
#define MTPROXYDIAGNOSTICS_H
#include "containerDiagnostics.h"
#include <QString>
namespace amnezia {
struct MtProxyDiagnostics : ContainerDiagnostics {
bool upstreamReachable = false;
int clientsConnected = -1;
QString lastConfigRefresh;
QString statsEndpoint;
};
} // namespace amnezia
#endif // MTPROXYDIAGNOSTICS_H

View File

@@ -0,0 +1,20 @@
#ifndef TELEMTDIAGNOSTICS_H
#define TELEMTDIAGNOSTICS_H
#include "containerDiagnostics.h"
#include <QString>
namespace amnezia
{
struct TelemtDiagnostics : ContainerDiagnostics
{
bool upstreamReachable = false;
int clientsConnected = -1;
QString lastConfigRefresh;
QString statsEndpoint;
};
} // namespace amnezia
#endif // TELEMTDIAGNOSTICS_H

View File

@@ -14,6 +14,8 @@
#include "core/models/protocols/xrayProtocolConfig.h"
#include "core/models/protocols/sftpProtocolConfig.h"
#include "core/models/protocols/socks5ProxyProtocolConfig.h"
#include "core/models/protocols/mtProxyProtocolConfig.h"
#include "core/models/protocols/telemtProtocolConfig.h"
#include "core/models/protocols/ikev2ProtocolConfig.h"
#include "core/models/protocols/torProtocolConfig.h"
@@ -91,6 +93,18 @@ ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int p
config.protocolConfig = socks5Config;
break;
}
case Proto::MtProxy: {
MtProxyProtocolConfig mtConfig;
mtConfig.port = portStr;
config.protocolConfig = mtConfig;
break;
}
case Proto::Telemt: {
TelemtProtocolConfig telemtConfig;
telemtConfig.port = portStr;
config.protocolConfig = telemtConfig;
break;
}
case Proto::Ikev2: {
Ikev2ProtocolConfig ikev2Config;
config.protocolConfig = ikev2Config;

View File

@@ -0,0 +1,130 @@
#include "mtProxyInstaller.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/models/containerConfig.h"
#include "core/models/protocols/mtProxyProtocolConfig.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QRegularExpression>
#include <QtGlobal>
using namespace amnezia;
namespace {
constexpr QLatin1String kMtProxyClientJsonPath("/data/amnezia-mtproxy-client.json");
constexpr QLatin1String kMtProxyClientJsonUploadPath("data/amnezia-mtproxy-client.json");
constexpr QLatin1String kMtProxySecretPath("/data/secret");
}
MtProxyInstaller::MtProxyInstaller(QObject *parent)
: InstallerBase(parent) {
}
ErrorCode MtProxyInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
SshSession *sshSession, ContainerConfig &config) {
if (container != DockerContainer::MtProxy || !sshSession) {
return ErrorCode::NoError;
}
MtProxyProtocolConfig *mt = config.getMtProxyProtocolConfig();
if (!mt) {
return ErrorCode::NoError;
}
ErrorCode jsonErr = ErrorCode::NoError;
const QByteArray jsonRaw =
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxyClientJsonPath), jsonErr);
if (jsonErr == ErrorCode::NoError && !jsonRaw.trimmed().isEmpty()) {
QJsonParseError parseError;
const QJsonDocument doc = QJsonDocument::fromJson(jsonRaw.trimmed(), &parseError);
if (parseError.error == QJsonParseError::NoError && doc.isObject()) {
QJsonObject merged = mt->toJson();
const QJsonObject snap = doc.object();
for (auto it = snap.constBegin(); it != snap.constEnd(); ++it) {
merged.insert(it.key(), it.value());
}
*mt = MtProxyProtocolConfig::fromJson(merged);
}
}
ErrorCode secretErr = ErrorCode::NoError;
const QByteArray secretRaw =
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxySecretPath), secretErr);
const QString sec = QString::fromUtf8(secretRaw).trimmed();
if (sec.length() == 32) {
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
if (hex32.match(sec).hasMatch()) {
mt->secret = sec;
}
}
return ErrorCode::NoError;
}
ErrorCode MtProxyInstaller::queryDiagnostics(SshSession &sshSession, const ServerCredentials &credentials,
DockerContainer container, int listenPort,
MtProxyContainerDiagnostics &out)
{
out = {};
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt) {
return ErrorCode::InternalError;
}
const QString containerName = ContainerUtils::containerToString(container);
const QString script =
QStringLiteral(
"PORT_OK=$(sudo docker exec %1 sh -c 'ss -tlnp 2>/dev/null | grep -q :%2 && echo yes || echo no' 2>/dev/null || echo no); "
"TG_OK=$(curl -s --max-time 5 -o /dev/null -w '%%{http_code}' https://core.telegram.org/getProxySecret 2>/dev/null | grep -q '200' && echo yes || echo no); "
"CLIENTS=$(sudo docker exec amnezia-mtproxy sh -c 'curl -s --max-time 3 http://localhost:2398/stats 2>/dev/null | grep -o \"total_special_connections:[0-9]*\" | cut -d: -f2' 2>/dev/null); "
"CONF_TIME=$(sudo docker exec amnezia-mtproxy sh -c 'stat -c \"%%y\" /data/proxy-multi.conf 2>/dev/null | cut -d. -f1' 2>/dev/null || echo unknown); "
"echo \"PORT_OK=${PORT_OK}\"; "
"echo \"TG_OK=${TG_OK}\"; "
"echo \"CLIENTS=${CLIENTS:-0}\"; "
"echo \"CONF_TIME=${CONF_TIME}\"; "
"echo \"STATS=http://localhost:2398/stats\";")
.arg(containerName)
.arg(listenPort);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data;
return ErrorCode::NoError;
};
const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
for (const QString &line : stdOut.split('\n', Qt::SkipEmptyParts)) {
if (line.startsWith(QLatin1String("PORT_OK="))) {
out.portReachable = line.mid(8).trimmed() == QLatin1String("yes");
} else if (line.startsWith(QLatin1String("TG_OK="))) {
out.upstreamReachable = line.mid(6).trimmed() == QLatin1String("yes");
} else if (line.startsWith(QLatin1String("CLIENTS="))) {
out.clientsConnected = line.mid(8).trimmed().toInt();
} else if (line.startsWith(QLatin1String("CONF_TIME="))) {
out.lastConfigRefresh = line.mid(10).trimmed();
} else if (line.startsWith(QLatin1String("STATS="))) {
out.statsEndpoint = line.mid(6).trimmed();
}
}
return ErrorCode::NoError;
}
void MtProxyInstaller::uploadClientSettingsSnapshot(SshSession &sshSession, const ServerCredentials &credentials,
DockerContainer container, const ContainerConfig &config) {
const MtProxyProtocolConfig *mt = config.getMtProxyProtocolConfig();
if (!mt) {
return;
}
const QByteArray payload = QJsonDocument(mt->toJson()).toJson(QJsonDocument::Compact);
const ErrorCode err = sshSession.uploadTextFileToContainer(container, credentials, QString::fromUtf8(payload),
QString(kMtProxyClientJsonUploadPath));
if (err != ErrorCode::NoError) {
qWarning() << "MtProxyInstaller::uploadClientSettingsSnapshot failed" << err;
}
}

View File

@@ -0,0 +1,34 @@
#ifndef MTPROXYINSTALLER_H
#define MTPROXYINSTALLER_H
#include "installerBase.h"
#include <QString>
struct MtProxyContainerDiagnostics {
bool portReachable = false;
bool upstreamReachable = false;
int clientsConnected = -1;
QString lastConfigRefresh;
QString statsEndpoint;
};
class MtProxyInstaller : public InstallerBase {
Q_OBJECT
public:
explicit MtProxyInstaller(QObject *parent = nullptr);
amnezia::ErrorCode
extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
SshSession *sshSession, amnezia::ContainerConfig &config) override;
static void uploadClientSettingsSnapshot(SshSession &sshSession, const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container,
const amnezia::ContainerConfig &config);
static amnezia::ErrorCode queryDiagnostics(SshSession &sshSession, const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container, int listenPort,
MtProxyContainerDiagnostics &out);
};
#endif // MTPROXYINSTALLER_H

View File

@@ -0,0 +1,79 @@
#include "telemtInstaller.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/models/containerConfig.h"
#include "core/models/protocols/telemtProtocolConfig.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QRegularExpression>
#include <QtGlobal>
using namespace amnezia;
namespace {
constexpr QLatin1String kTelemtClientJsonPath("/data/amnezia-telemt-client.json");
constexpr QLatin1String kTelemtClientJsonUploadPath("data/amnezia-telemt-client.json");
constexpr QLatin1String kTelemtSecretPath("/data/secret");
}
TelemtInstaller::TelemtInstaller(QObject *parent) : InstallerBase(parent) {}
ErrorCode TelemtInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
SshSession *sshSession, ContainerConfig &config) {
if (container != DockerContainer::Telemt || !sshSession) {
return ErrorCode::NoError;
}
TelemtProtocolConfig *tc = config.getTelemtProtocolConfig();
if (!tc) {
return ErrorCode::NoError;
}
ErrorCode jsonErr = ErrorCode::NoError;
const QByteArray jsonRaw =
sshSession->getTextFileFromContainer(container, credentials, QString(kTelemtClientJsonPath), jsonErr);
if (jsonErr == ErrorCode::NoError && !jsonRaw.trimmed().isEmpty()) {
QJsonParseError parseError;
const QJsonDocument doc = QJsonDocument::fromJson(jsonRaw.trimmed(), &parseError);
if (parseError.error == QJsonParseError::NoError && doc.isObject()) {
QJsonObject merged = tc->toJson();
const QJsonObject snap = doc.object();
for (auto it = snap.constBegin(); it != snap.constEnd(); ++it) {
merged.insert(it.key(), it.value());
}
*tc = TelemtProtocolConfig::fromJson(merged);
}
}
ErrorCode secretErr = ErrorCode::NoError;
const QByteArray secretRaw =
sshSession->getTextFileFromContainer(container, credentials, QString(kTelemtSecretPath), secretErr);
const QString sec = QString::fromUtf8(secretRaw).trimmed();
if (sec.length() == 32) {
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
if (hex32.match(sec).hasMatch()) {
tc->secret = sec;
}
}
return ErrorCode::NoError;
}
void TelemtInstaller::uploadClientSettingsSnapshot(SshSession &sshSession, const ServerCredentials &credentials,
DockerContainer container, const ContainerConfig &config) {
const TelemtProtocolConfig *tc = config.getTelemtProtocolConfig();
if (!tc) {
return;
}
const QByteArray payload = QJsonDocument(tc->toJson()).toJson(QJsonDocument::Compact);
const ErrorCode err = sshSession.uploadTextFileToContainer(container, credentials, QString::fromUtf8(payload),
QString(kTelemtClientJsonUploadPath));
if (err != ErrorCode::NoError) {
qWarning() << "TelemtInstaller::uploadClientSettingsSnapshot failed" << err;
}
}

View File

@@ -0,0 +1,20 @@
#ifndef TELEMTINSTALLER_H
#define TELEMTINSTALLER_H
#include "installerBase.h"
class TelemtInstaller : public InstallerBase {
Q_OBJECT
public:
explicit TelemtInstaller(QObject *parent = nullptr);
amnezia::ErrorCode
extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
SshSession *sshSession, amnezia::ContainerConfig &config) override;
static void uploadClientSettingsSnapshot(SshSession &sshSession, const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container,
const amnezia::ContainerConfig &config);
};
#endif // TELEMTINSTALLER_H

View File

@@ -14,8 +14,18 @@
#include "core/models/protocols/xrayProtocolConfig.h"
#include "logger.h"
namespace {
namespace
{
Logger logger("XrayInstaller");
// Xray expects uTLS preset names (chrome, firefox, …). Old Amnezia/server templates used "Mozilla/5.0".
QString normalizeXrayFingerprint(const QString &fp)
{
if (fp.isEmpty() || fp.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
return QString::fromLatin1(protocols::xray::defaultFingerprint);
}
return fp;
}
}
using namespace amnezia;
@@ -63,18 +73,251 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
}
QJsonObject streamSettings = inbound[protocols::xray::streamSettings].toObject();
QJsonObject realitySettings = streamSettings[protocols::xray::realitySettings].toObject();
if (!realitySettings.contains(protocols::xray::serverNames)) {
logger.error() << "Settings missing 'serverNames' field";
auto *xrayConfig = config.getXrayProtocolConfig();
if (!xrayConfig) {
logger.error() << "No XrayProtocolConfig in ContainerConfig";
return ErrorCode::InternalError;
}
QString siteName = realitySettings[protocols::xray::serverNames][0].toString();
XrayServerConfig &srv = xrayConfig->serverConfig;
if (auto* xrayConfig = config.getXrayProtocolConfig()) {
xrayConfig->serverConfig.site = siteName;
// ── Port ─────────────────────────────────────────────────────────
if (inbound.contains(protocols::xray::port)) {
srv.port = QString::number(inbound[protocols::xray::port].toInt());
}
// ── Network (transport) ───────────────────────────────────────────
QString networkVal = streamSettings.value(protocols::xray::network).toString("tcp");
if (networkVal == "xhttp") {
srv.transport = "xhttp";
} else if (networkVal == "kcp") {
srv.transport = "mkcp";
} else {
srv.transport = "raw";
}
// ── Security ──────────────────────────────────────────────────────
srv.security = streamSettings.value(protocols::xray::security).toString("reality");
// ── Reality settings ──────────────────────────────────────────────
if (srv.security == "reality") {
QJsonObject rs = streamSettings.value(protocols::xray::realitySettings).toObject();
// serverNames array → site + sni
if (rs.contains(protocols::xray::serverNames)) {
QString sniVal = rs[protocols::xray::serverNames].toArray().first().toString();
srv.sni = sniVal;
srv.site = sniVal;
} else if (rs.contains(protocols::xray::serverName)) {
srv.sni = rs[protocols::xray::serverName].toString();
srv.site = srv.sni;
}
srv.fingerprint = normalizeXrayFingerprint(rs.value(protocols::xray::fingerprint).toString());
}
// ── TLS settings ──────────────────────────────────────────────────
if (srv.security == "tls") {
QJsonObject tls = streamSettings.value("tlsSettings").toObject();
srv.sni = tls.value(protocols::xray::serverName).toString();
srv.fingerprint = normalizeXrayFingerprint(tls.value(protocols::xray::fingerprint).toString());
QJsonArray alpnArr = tls.value("alpn").toArray();
QStringList alpnList;
for (const QJsonValue &v : alpnArr) {
alpnList << v.toString();
}
srv.alpn = alpnList.join(",");
}
// ── Flow (from users array) ───────────────────────────────────────
if (inbound.contains(protocols::xray::settings)) {
QJsonObject s = inbound[protocols::xray::settings].toObject();
QJsonArray clientsArr = s.value(protocols::xray::clients).toArray();
if (!clientsArr.isEmpty()) {
srv.flow = clientsArr[0].toObject().value(protocols::xray::flow).toString();
}
}
// ── XHTTP settings (Xray-core SplitHTTPConfig + legacy Amnezia keys) ──
if (srv.transport == "xhttp") {
QJsonObject xhttpObj = streamSettings.value("xhttpSettings").toObject();
{
const QString m = xhttpObj.value("mode").toString();
if (m.isEmpty() || m == QLatin1String("auto"))
srv.xhttp.mode = QStringLiteral("Auto");
else if (m == QLatin1String("packet-up"))
srv.xhttp.mode = QStringLiteral("Packet-up");
else if (m == QLatin1String("stream-up"))
srv.xhttp.mode = QStringLiteral("Stream-up");
else if (m == QLatin1String("stream-one"))
srv.xhttp.mode = QStringLiteral("Stream-one");
else
srv.xhttp.mode = m;
}
srv.xhttp.host = xhttpObj.value("host").toString();
srv.xhttp.path = xhttpObj.value("path").toString();
{
const QJsonObject hdrs = xhttpObj.value("headers").toObject();
if (hdrs.contains(QLatin1String("Host")) || !hdrs.isEmpty())
srv.xhttp.headersTemplate = QStringLiteral("HTTP");
}
if (xhttpObj.contains(QLatin1String("uplinkHTTPMethod")))
srv.xhttp.uplinkMethod = xhttpObj.value("uplinkHTTPMethod").toString();
else
srv.xhttp.uplinkMethod = xhttpObj.value("method").toString();
srv.xhttp.disableGrpc = xhttpObj.value("noGRPCHeader").toBool(true);
srv.xhttp.disableSse = xhttpObj.value("noSSEHeader").toBool(true);
auto sessionSeqUi = [](const QString &core) -> QString {
if (core.isEmpty() || core == QLatin1String("path"))
return QStringLiteral("Path");
if (core == QLatin1String("cookie"))
return QStringLiteral("Cookie");
if (core == QLatin1String("header"))
return QStringLiteral("Header");
if (core == QLatin1String("query"))
return QStringLiteral("Query");
return core;
};
QString sess = xhttpObj.value("sessionPlacement").toString();
if (sess.isEmpty())
sess = xhttpObj.value("scSessionPlacement").toString();
srv.xhttp.sessionPlacement = sessionSeqUi(sess);
QString seq = xhttpObj.value("seqPlacement").toString();
if (seq.isEmpty())
seq = xhttpObj.value("scSeqPlacement").toString();
srv.xhttp.seqPlacement = sessionSeqUi(seq);
auto uplinkDataUi = [](const QString &core) -> QString {
if (core.isEmpty() || core == QLatin1String("body"))
return QStringLiteral("Body");
if (core == QLatin1String("auto"))
return QStringLiteral("Auto");
if (core == QLatin1String("header"))
return QStringLiteral("Header");
if (core == QLatin1String("cookie"))
return QStringLiteral("Cookie");
return core;
};
QString udata = xhttpObj.value("uplinkDataPlacement").toString();
if (udata.isEmpty())
udata = xhttpObj.value("scUplinkDataPlacement").toString();
srv.xhttp.uplinkDataPlacement = uplinkDataUi(udata);
srv.xhttp.sessionKey = xhttpObj.value("sessionKey").toString();
srv.xhttp.seqKey = xhttpObj.value("seqKey").toString();
srv.xhttp.uplinkDataKey = xhttpObj.value("uplinkDataKey").toString();
if (xhttpObj.contains(QLatin1String("uplinkChunkSize"))) {
QJsonObject uc = xhttpObj.value("uplinkChunkSize").toObject();
if (!uc.isEmpty())
srv.xhttp.uplinkChunkSize = QString::number(uc.value("from").toInt());
} else if (xhttpObj.contains(QLatin1String("xhttpUplinkChunkSize"))) {
srv.xhttp.uplinkChunkSize = QString::number(xhttpObj.value("xhttpUplinkChunkSize").toInt());
}
if (xhttpObj.contains(QLatin1String("scMaxBufferedPosts"))) {
srv.xhttp.scMaxBufferedPosts = QString::number(xhttpObj.value("scMaxBufferedPosts").toVariant().toLongLong());
}
auto readRange = [&](const char *key, QString &minOut, QString &maxOut) {
QJsonObject r = xhttpObj.value(QLatin1String(key)).toObject();
if (!r.isEmpty()) {
minOut = QString::number(r.value("from").toInt());
maxOut = QString::number(r.value("to").toInt());
}
};
readRange("scMaxEachPostBytes", srv.xhttp.scMaxEachPostBytesMin, srv.xhttp.scMaxEachPostBytesMax);
readRange("scMinPostsIntervalMs", srv.xhttp.scMinPostsIntervalMsMin, srv.xhttp.scMinPostsIntervalMsMax);
readRange("scStreamUpServerSecs", srv.xhttp.scStreamUpServerSecsMin, srv.xhttp.scStreamUpServerSecsMax);
auto loadPaddingFromObject = [&](const QJsonObject &pad) {
if (pad.contains(QLatin1String("xPaddingObfsMode")))
srv.xhttp.xPadding.obfsMode = pad.value("xPaddingObfsMode").toBool(true);
srv.xhttp.xPadding.key = pad.value("xPaddingKey").toString();
srv.xhttp.xPadding.header = pad.value("xPaddingHeader").toString();
srv.xhttp.xPadding.placement = pad.value("xPaddingPlacement").toString();
srv.xhttp.xPadding.method = pad.value("xPaddingMethod").toString();
QJsonObject bytesRange = pad.value("xPaddingBytes").toObject();
if (!bytesRange.isEmpty()) {
srv.xhttp.xPadding.bytesMin = QString::number(bytesRange.value("from").toInt());
srv.xhttp.xPadding.bytesMax = QString::number(bytesRange.value("to").toInt());
}
QString pl = srv.xhttp.xPadding.placement.toLower();
if (pl == QLatin1String("cookie"))
srv.xhttp.xPadding.placement = QStringLiteral("Cookie");
else if (pl == QLatin1String("header"))
srv.xhttp.xPadding.placement = QStringLiteral("Header");
else if (pl == QLatin1String("query"))
srv.xhttp.xPadding.placement = QStringLiteral("Query");
else if (pl == QLatin1String("queryinheader"))
srv.xhttp.xPadding.placement = QStringLiteral("Query in header");
QString met = srv.xhttp.xPadding.method.toLower();
if (met == QLatin1String("repeat-x"))
srv.xhttp.xPadding.method = QStringLiteral("Repeat-x");
else if (met == QLatin1String("tokenish"))
srv.xhttp.xPadding.method = QStringLiteral("Tokenish");
};
if (xhttpObj.contains(QLatin1String("xPaddingObfsMode")) || xhttpObj.contains(QLatin1String("xPaddingKey"))
|| !xhttpObj.value("xPaddingBytes").toObject().isEmpty()) {
loadPaddingFromObject(xhttpObj);
} else if (xhttpObj.contains(QLatin1String("xPadding")) && xhttpObj.value("xPadding").isObject()) {
const QJsonObject nested = xhttpObj.value("xPadding").toObject();
if (!nested.isEmpty()) {
loadPaddingFromObject(nested);
if (!nested.contains(QLatin1String("xPaddingObfsMode")))
srv.xhttp.xPadding.obfsMode = true;
}
}
if (xhttpObj.contains(QLatin1String("xmux"))) {
QJsonObject mux = xhttpObj.value("xmux").toObject();
srv.xhttp.xmux.enabled = true;
auto readMuxRange = [&](const char *key, QString &minOut, QString &maxOut) {
QJsonObject r = mux.value(QLatin1String(key)).toObject();
if (!r.isEmpty()) {
minOut = QString::number(r.value("from").toInt());
maxOut = QString::number(r.value("to").toInt());
}
};
readMuxRange("maxConcurrency", srv.xhttp.xmux.maxConcurrencyMin, srv.xhttp.xmux.maxConcurrencyMax);
readMuxRange("maxConnections", srv.xhttp.xmux.maxConnectionsMin, srv.xhttp.xmux.maxConnectionsMax);
readMuxRange("cMaxReuseTimes", srv.xhttp.xmux.cMaxReuseTimesMin, srv.xhttp.xmux.cMaxReuseTimesMax);
readMuxRange("hMaxRequestTimes", srv.xhttp.xmux.hMaxRequestTimesMin, srv.xhttp.xmux.hMaxRequestTimesMax);
readMuxRange("hMaxReusableSecs", srv.xhttp.xmux.hMaxReusableSecsMin, srv.xhttp.xmux.hMaxReusableSecsMax);
if (mux.contains(QLatin1String("hKeepAlivePeriod")))
srv.xhttp.xmux.hKeepAlivePeriod = QString::number(mux.value("hKeepAlivePeriod").toVariant().toLongLong());
}
}
// ── mKCP settings ─────────────────────────────────────────────────
if (srv.transport == "mkcp") {
QJsonObject kcp = streamSettings.value("kcpSettings").toObject();
if (kcp.contains("tti")) {
srv.mkcp.tti = QString::number(kcp["tti"].toInt());
}
if (kcp.contains("uplinkCapacity")) {
srv.mkcp.uplinkCapacity = QString::number(kcp["uplinkCapacity"].toInt());
}
if (kcp.contains("downlinkCapacity")) {
srv.mkcp.downlinkCapacity = QString::number(kcp["downlinkCapacity"].toInt());
}
if (kcp.contains("readBufferSize")) {
srv.mkcp.readBufferSize = QString::number(kcp["readBufferSize"].toInt());
}
if (kcp.contains("writeBufferSize")) {
srv.mkcp.writeBufferSize = QString::number(kcp["writeBufferSize"].toInt());
}
srv.mkcp.congestion = kcp.value("congestion").toBool(true);
}
return ErrorCode::NoError;
}

View File

@@ -6,7 +6,7 @@
#include <QString>
#include <QDateTime>
#include "core/utils/api/apiEnums.h"
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"

View File

@@ -1,140 +0,0 @@
#include "apiV1ServerConfig.h"
#include <QJsonArray>
#include <QJsonDocument>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/api/apiUtils.h"
namespace amnezia
{
using namespace ContainerEnumNS;
bool ApiV1ServerConfig::isPremium() const
{
constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
return apiEndpoint.contains(premiumV1Endpoint);
}
bool ApiV1ServerConfig::isFree() const
{
constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
return apiEndpoint.contains(freeV2Endpoint);
}
QString ApiV1ServerConfig::vpnKey() const
{
QJsonObject json = toJson();
return apiUtils::getPremiumV1VpnKey(json);
}
bool ApiV1ServerConfig::hasContainers() const
{
return !containers.isEmpty();
}
ContainerConfig ApiV1ServerConfig::containerConfig(DockerContainer container) const
{
if (!containers.contains(container)) {
return ContainerConfig{};
}
return containers.value(container);
}
QJsonObject ApiV1ServerConfig::toJson() const
{
QJsonObject obj;
if (!name.isEmpty()) {
obj[configKey::name] = name;
}
if (!description.isEmpty()) {
obj[configKey::description] = description;
}
if (!protocol.isEmpty()) {
obj[apiDefs::key::protocol] = protocol;
}
if (!apiEndpoint.isEmpty()) {
obj[apiDefs::key::apiEndpoint] = apiEndpoint;
}
if (!apiKey.isEmpty()) {
obj[apiDefs::key::apiKey] = apiKey;
}
obj[configKey::configVersion] = configVersion;
if (!hostName.isEmpty()) {
obj[configKey::hostName] = hostName;
}
QJsonArray containersArray;
for (auto it = containers.begin(); it != containers.end(); ++it) {
QJsonObject containerObj = it.value().toJson();
containersArray.append(containerObj);
}
if (!containersArray.isEmpty()) {
obj[configKey::containers] = containersArray;
}
if (defaultContainer != DockerContainer::None) {
obj[configKey::defaultContainer] = ContainerUtils::containerToString(defaultContainer);
}
if (!dns1.isEmpty()) {
obj[configKey::dns1] = dns1;
}
if (!dns2.isEmpty()) {
obj[configKey::dns2] = dns2;
}
if (crc > 0) {
obj[configKey::crc] = crc;
}
return obj;
}
ApiV1ServerConfig ApiV1ServerConfig::fromJson(const QJsonObject& json)
{
ApiV1ServerConfig config;
config.name = json.value(configKey::name).toString();
config.description = json.value(configKey::description).toString();
config.protocol = json.value(apiDefs::key::protocol).toString();
config.apiEndpoint = json.value(apiDefs::key::apiEndpoint).toString();
config.apiKey = json.value(apiDefs::key::apiKey).toString();
config.configVersion = json.value(configKey::configVersion).toInt(1);
config.hostName = json.value(configKey::hostName).toString();
QJsonArray containersArray = json.value(configKey::containers).toArray();
for (const QJsonValue& val : containersArray) {
QJsonObject containerObj = val.toObject();
ContainerConfig containerConfig = ContainerConfig::fromJson(containerObj);
QString containerStr = containerObj.value(configKey::container).toString();
DockerContainer container = ContainerUtils::containerFromString(containerStr);
config.containers.insert(container, containerConfig);
}
QString defaultContainerStr = json.value(configKey::defaultContainer).toString();
config.defaultContainer = ContainerUtils::containerFromString(defaultContainerStr);
config.dns1 = json.value(configKey::dns1).toString();
config.dns2 = json.value(configKey::dns2).toString();
config.crc = json.value(configKey::crc).toInt(0);
return config;
}
} // namespace amnezia

View File

@@ -1,47 +0,0 @@
#ifndef APIV1SERVERCONFIG_H
#define APIV1SERVERCONFIG_H
#include <QJsonObject>
#include <QMap>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/models/containerConfig.h"
#include "core/utils/api/apiEnums.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
namespace amnezia
{
using namespace ContainerEnumNS;
struct ApiV1ServerConfig {
QString description;
QString hostName;
QMap<DockerContainer, ContainerConfig> containers;
DockerContainer defaultContainer;
QString dns1;
QString dns2;
QString name;
QString protocol;
QString apiEndpoint;
QString apiKey;
int crc;
int configVersion;
bool isPremium() const;
bool isFree() const;
QString vpnKey() const;
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
QJsonObject toJson() const;
static ApiV1ServerConfig fromJson(const QJsonObject& json);
};
} // namespace amnezia
#endif // APIV1SERVERCONFIG_H

View File

@@ -80,6 +80,9 @@ QJsonObject ApiV2ServerConfig::toJson() const
if (!description.isEmpty()) {
obj[configKey::description] = description;
}
if (!displayName.isEmpty()) {
obj[configKey::displayName] = displayName;
}
obj[configKey::configVersion] = configVersion;
@@ -131,6 +134,7 @@ ApiV2ServerConfig ApiV2ServerConfig::fromJson(const QJsonObject& json)
config.name = json.value(configKey::name).toString();
config.nameOverriddenByUser = json.value(configKey::nameOverriddenByUser).toBool(false);
config.description = json.value(configKey::description).toString();
config.displayName = json.value(configKey::displayName).toString();
config.configVersion = json.value(configKey::configVersion).toInt(2);
config.hostName = json.value(configKey::hostName).toString();
@@ -163,6 +167,10 @@ ApiV2ServerConfig ApiV2ServerConfig::fromJson(const QJsonObject& json)
config.authData = AuthData::fromJson(authDataObj);
}
if (config.displayName.isEmpty()) {
config.displayName = config.name.isEmpty() ? config.description : config.name;
}
return config;
}

View File

@@ -10,7 +10,7 @@
#include "core/models/containerConfig.h"
#include "core/models/api/apiConfig.h"
#include "core/models/api/authData.h"
#include "core/utils/api/apiEnums.h"
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
@@ -21,6 +21,7 @@ using namespace ContainerEnumNS;
struct ApiV2ServerConfig {
QString description;
QString displayName;
QString hostName;
QMap<DockerContainer, ContainerConfig> containers;
DockerContainer defaultContainer;

View File

@@ -4,7 +4,7 @@
#include <QJsonObject>
#include <QString>
#include "core/utils/api/apiEnums.h"
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"

View File

@@ -0,0 +1,43 @@
#include "legacyApiServerConfig.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/configKeys.h"
namespace amnezia
{
bool LegacyApiServerConfig::hasContainers() const
{
return !containers.isEmpty();
}
ContainerConfig LegacyApiServerConfig::containerConfig(DockerContainer container) const
{
if (!containers.contains(container)) {
return ContainerConfig{};
}
return containers.value(container);
}
LegacyApiServerConfig LegacyApiServerConfig::fromJson(const QJsonObject &json)
{
LegacyApiServerConfig config;
config.name = json.value(configKey::name).toString();
config.description = json.value(configKey::description).toString();
config.displayName = json.value(configKey::displayName).toString();
config.hostName = json.value(configKey::hostName).toString();
config.crc = json.value(configKey::crc).toInt(0);
config.configVersion = json.value(configKey::configVersion).toInt(1);
config.apiEndpoint = json.value(apiDefs::key::apiEndpoint).toString();
if (config.displayName.isEmpty()) {
config.displayName = config.name.isEmpty() ? config.description : config.name;
}
return config;
}
} // namespace amnezia

View File

@@ -0,0 +1,38 @@
#ifndef LEGACYAPISERVERCONFIG_H
#define LEGACYAPISERVERCONFIG_H
#include <QJsonObject>
#include <QMap>
#include "core/utils/containerEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/models/containerConfig.h"
namespace amnezia
{
using namespace ContainerEnumNS;
struct LegacyApiServerConfig {
QString description;
QString displayName;
QString hostName;
QMap<DockerContainer, ContainerConfig> containers;
DockerContainer defaultContainer = DockerContainer::None;
QString dns1;
QString dns2;
QString name;
int crc = 0;
int configVersion = 0;
QString apiEndpoint;
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
static LegacyApiServerConfig fromJson(const QJsonObject &json);
};
} // namespace amnezia
#endif // LEGACYAPISERVERCONFIG_H

View File

@@ -113,6 +113,26 @@ const Socks5ProxyProtocolConfig* ContainerConfig::getSocks5ProxyProtocolConfig()
return protocolConfig.as<Socks5ProxyProtocolConfig>();
}
MtProxyProtocolConfig* ContainerConfig::getMtProxyProtocolConfig()
{
return protocolConfig.as<MtProxyProtocolConfig>();
}
const MtProxyProtocolConfig* ContainerConfig::getMtProxyProtocolConfig() const
{
return protocolConfig.as<MtProxyProtocolConfig>();
}
TelemtProtocolConfig* ContainerConfig::getTelemtProtocolConfig()
{
return protocolConfig.as<TelemtProtocolConfig>();
}
const TelemtProtocolConfig* ContainerConfig::getTelemtProtocolConfig() const
{
return protocolConfig.as<TelemtProtocolConfig>();
}
Ikev2ProtocolConfig* ContainerConfig::getIkev2ProtocolConfig()
{
return protocolConfig.as<Ikev2ProtocolConfig>();

View File

@@ -57,6 +57,12 @@ struct ContainerConfig {
Socks5ProxyProtocolConfig* getSocks5ProxyProtocolConfig();
const Socks5ProxyProtocolConfig* getSocks5ProxyProtocolConfig() const;
MtProxyProtocolConfig* getMtProxyProtocolConfig();
const MtProxyProtocolConfig* getMtProxyProtocolConfig() const;
TelemtProtocolConfig* getTelemtProtocolConfig();
const TelemtProtocolConfig* getTelemtProtocolConfig() const;
Ikev2ProtocolConfig* getIkev2ProtocolConfig();
const Ikev2ProtocolConfig* getIkev2ProtocolConfig() const;

View File

@@ -9,6 +9,8 @@
#include "core/utils/protocolEnum.h"
#include "core/models/protocols/ikev2ProtocolConfig.h"
#include "core/models/protocols/dnsProtocolConfig.h"
#include "core/models/protocols/mtProxyProtocolConfig.h"
#include "core/models/protocols/telemtProtocolConfig.h"
namespace amnezia
{
@@ -38,6 +40,10 @@ Proto ProtocolConfig::type() const
return Proto::TorWebSite;
} else if constexpr (std::is_same_v<T, DnsProtocolConfig>) {
return Proto::Dns;
} else if constexpr (std::is_same_v<T, MtProxyProtocolConfig>) {
return Proto::MtProxy;
} else if constexpr (std::is_same_v<T, TelemtProtocolConfig>) {
return Proto::Telemt;
}
return Proto::Unknown;
}, data);
@@ -65,6 +71,10 @@ QString ProtocolConfig::port() const
return QString();
} else if constexpr (std::is_same_v<T, DnsProtocolConfig>) {
return QString();
} else if constexpr (std::is_same_v<T, MtProxyProtocolConfig>) {
return arg.port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : arg.port;
} else if constexpr (std::is_same_v<T, TelemtProtocolConfig>) {
return arg.port.isEmpty() ? QString(protocols::telemt::defaultPort) : arg.port;
}
return QString();
}, data);
@@ -88,6 +98,10 @@ QString ProtocolConfig::transportProto() const
return QString();
} else if constexpr (std::is_same_v<T, DnsProtocolConfig>) {
return QString();
} else if constexpr (std::is_same_v<T, MtProxyProtocolConfig>) {
return QStringLiteral("tcp");
} else if constexpr (std::is_same_v<T, TelemtProtocolConfig>) {
return QStringLiteral("tcp");
}
return QString();
}, data);
@@ -299,6 +313,10 @@ ProtocolConfig ProtocolConfig::fromJson(const QJsonObject& json, Proto type)
return ProtocolConfig{TorProtocolConfig::fromJson(json)};
case Proto::Dns:
return ProtocolConfig{DnsProtocolConfig::fromJson(json)};
case Proto::MtProxy:
return ProtocolConfig{MtProxyProtocolConfig::fromJson(json)};
case Proto::Telemt:
return ProtocolConfig{TelemtProtocolConfig::fromJson(json)};
default:
return ProtocolConfig{AwgProtocolConfig{}};
}

View File

@@ -22,6 +22,8 @@
#include "core/models/protocols/ikev2ProtocolConfig.h"
#include "core/models/protocols/torProtocolConfig.h"
#include "core/models/protocols/dnsProtocolConfig.h"
#include "core/models/protocols/mtProxyProtocolConfig.h"
#include "core/models/protocols/telemtProtocolConfig.h"
namespace amnezia
{
@@ -36,6 +38,8 @@ struct ProtocolConfig {
XrayProtocolConfig,
SftpProtocolConfig,
Socks5ProxyProtocolConfig,
MtProxyProtocolConfig,
TelemtProtocolConfig,
Ikev2ProtocolConfig,
TorProtocolConfig,
DnsProtocolConfig

View File

@@ -0,0 +1,147 @@
#include "mtProxyProtocolConfig.h"
#include "../../../core/utils/protocolEnum.h"
#include "../../../core/protocols/protocolUtils.h"
#include "../../../core/utils/constants/configKeys.h"
#include "../../../core/utils/constants/protocolConstants.h"
#include <QJsonArray>
#include <algorithm>
using namespace amnezia;
namespace amnezia {
QJsonObject MtProxyProtocolConfig::toJson() const {
QJsonObject obj;
if (!port.isEmpty()) {
obj[configKey::port] = port;
}
if (!secret.isEmpty()) {
obj[protocols::mtProxy::secretKey] = secret;
}
if (!tag.isEmpty()) {
obj[protocols::mtProxy::tagKey] = tag;
}
if (!tgLink.isEmpty()) {
obj[protocols::mtProxy::tgLinkKey] = tgLink;
}
if (!tmeLink.isEmpty()) {
obj[protocols::mtProxy::tmeLinkKey] = tmeLink;
}
obj[protocols::mtProxy::isEnabledKey] = isEnabled;
if (!publicHost.isEmpty()) {
obj[protocols::mtProxy::publicHostKey] = publicHost;
}
if (!transportMode.isEmpty()) {
obj[protocols::mtProxy::transportModeKey] = transportMode;
}
if (!tlsDomain.isEmpty()) {
obj[protocols::mtProxy::tlsDomainKey] = tlsDomain;
}
if (!additionalSecrets.isEmpty()) {
obj[protocols::mtProxy::additionalSecretsKey] = QJsonArray::fromStringList(additionalSecrets);
}
if (!workersMode.isEmpty()) {
obj[protocols::mtProxy::workersModeKey] = workersMode;
}
if (!workers.isEmpty()) {
obj[protocols::mtProxy::workersKey] = workers;
}
obj[protocols::mtProxy::natEnabledKey] = natEnabled;
if (!natInternalIp.isEmpty()) {
obj[protocols::mtProxy::natInternalIpKey] = natInternalIp;
}
if (!natExternalIp.isEmpty()) {
obj[protocols::mtProxy::natExternalIpKey] = natExternalIp;
}
return obj;
}
MtProxyProtocolConfig MtProxyProtocolConfig::fromJson(const QJsonObject &json) {
MtProxyProtocolConfig config;
config.port = json.value(configKey::port).toString();
config.secret = json.value(protocols::mtProxy::secretKey).toString();
config.tag = json.value(protocols::mtProxy::tagKey).toString();
config.tgLink = json.value(protocols::mtProxy::tgLinkKey).toString();
config.tmeLink = json.value(protocols::mtProxy::tmeLinkKey).toString();
config.isEnabled = json.value(protocols::mtProxy::isEnabledKey).toBool(true);
config.publicHost = json.value(protocols::mtProxy::publicHostKey).toString();
config.transportMode = json.value(protocols::mtProxy::transportModeKey).toString();
config.tlsDomain = json.value(protocols::mtProxy::tlsDomainKey).toString();
for (const auto &v: json.value(protocols::mtProxy::additionalSecretsKey).toArray()) {
const QString s = v.toString();
if (!s.isEmpty()) {
config.additionalSecrets.append(s);
}
}
config.workersMode = json.value(protocols::mtProxy::workersModeKey).toString();
config.workers = json.value(protocols::mtProxy::workersKey).toString();
config.natEnabled = json.value(protocols::mtProxy::natEnabledKey).toBool(false);
config.natInternalIp = json.value(protocols::mtProxy::natInternalIpKey).toString();
config.natExternalIp = json.value(protocols::mtProxy::natExternalIpKey).toString();
return config;
}
bool MtProxyProtocolConfig::equalsDockerDeploymentSettings(const MtProxyProtocolConfig &other) const {
const auto normPort = [](const QString &p) {
return p.isEmpty() ? QString(protocols::mtProxy::defaultPort) : p;
};
const auto normTransport = [](const QString &t) {
return t.isEmpty() ? QString(protocols::mtProxy::transportModeStandard) : t;
};
const auto normWorkersMode = [](const QString &m) {
return m.isEmpty() ? QString(protocols::mtProxy::workersModeAuto) : m;
};
if (normPort(port) != normPort(other.port)) {
return false;
}
if (normTransport(transportMode) != normTransport(other.transportMode)) {
return false;
}
if (tlsDomain != other.tlsDomain) {
return false;
}
if (secret != other.secret) {
return false;
}
if (tag != other.tag) {
return false;
}
if (publicHost != other.publicHost) {
return false;
}
if (normWorkersMode(workersMode) != normWorkersMode(other.workersMode)) {
return false;
}
if (workers != other.workers) {
return false;
}
if (natEnabled != other.natEnabled) {
return false;
}
if (natInternalIp != other.natInternalIp) {
return false;
}
if (natExternalIp != other.natExternalIp) {
return false;
}
if (isEnabled != other.isEnabled) {
return false;
}
QStringList aa = additionalSecrets;
QStringList bb = other.additionalSecrets;
aa.removeAll(QString());
bb.removeAll(QString());
std::sort(aa.begin(), aa.end());
std::sort(bb.begin(), bb.end());
return aa == bb;
}
} // namespace amnezia

View File

@@ -0,0 +1,38 @@
#ifndef MTPROXYPROTOCOLCONFIG_H
#define MTPROXYPROTOCOLCONFIG_H
#include <QJsonObject>
#include <QString>
#include <QStringList>
namespace amnezia {
struct MtProxyProtocolConfig {
QString port;
QString secret;
QString tag;
QString tgLink;
QString tmeLink;
bool isEnabled = true;
QString publicHost;
QString transportMode;
QString tlsDomain;
QStringList additionalSecrets;
QString workersMode;
QString workers;
bool natEnabled = false;
QString natInternalIp;
QString natExternalIp;
QJsonObject toJson() const;
static MtProxyProtocolConfig fromJson(const QJsonObject &json);
// Port, transport, TLS, secrets, NAT, workers, isEnabled, additionalSecrets (order-independent).
// Ignores tgLink / tmeLink (derived / display).
bool equalsDockerDeploymentSettings(const MtProxyProtocolConfig &other) const;
};
} // namespace amnezia
#endif // MTPROXYPROTOCOLCONFIG_H

View File

@@ -0,0 +1,162 @@
#include "telemtProtocolConfig.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include <QJsonArray>
#include <algorithm>
using namespace amnezia;
QJsonObject TelemtProtocolConfig::toJson() const
{
QJsonObject obj;
if (!port.isEmpty()) {
obj[QString(configKey::port)] = port;
}
if (!secret.isEmpty()) {
obj[protocols::telemt::secretKey] = secret;
}
if (!tag.isEmpty()) {
obj[protocols::telemt::tagKey] = tag;
}
if (!tgLink.isEmpty()) {
obj[protocols::telemt::tgLinkKey] = tgLink;
}
if (!tmeLink.isEmpty()) {
obj[protocols::telemt::tmeLinkKey] = tmeLink;
}
obj[protocols::telemt::isEnabledKey] = isEnabled;
if (!publicHost.isEmpty()) {
obj[protocols::telemt::publicHostKey] = publicHost;
}
if (!transportMode.isEmpty()) {
obj[protocols::telemt::transportModeKey] = transportMode;
}
if (!tlsDomain.isEmpty()) {
obj[protocols::telemt::tlsDomainKey] = tlsDomain;
}
obj[protocols::telemt::maskEnabledKey] = maskEnabled;
obj[protocols::telemt::tlsEmulationKey] = tlsEmulation;
obj[protocols::telemt::useMiddleProxyKey] = useMiddleProxy;
if (!userName.isEmpty()) {
obj[protocols::telemt::userNameKey] = userName;
}
if (!additionalSecrets.isEmpty()) {
obj[protocols::telemt::additionalSecretsKey] = QJsonArray::fromStringList(additionalSecrets);
}
if (!workersMode.isEmpty()) {
obj[protocols::telemt::workersModeKey] = workersMode;
}
if (!workers.isEmpty()) {
obj[protocols::telemt::workersKey] = workers;
}
obj[protocols::telemt::natEnabledKey] = natEnabled;
if (!natInternalIp.isEmpty()) {
obj[protocols::telemt::natInternalIpKey] = natInternalIp;
}
if (!natExternalIp.isEmpty()) {
obj[protocols::telemt::natExternalIpKey] = natExternalIp;
}
return obj;
}
TelemtProtocolConfig TelemtProtocolConfig::fromJson(const QJsonObject &json)
{
TelemtProtocolConfig c;
c.port = json.value(QString(configKey::port)).toString();
c.secret = json.value(protocols::telemt::secretKey).toString();
c.tag = json.value(protocols::telemt::tagKey).toString();
c.tgLink = json.value(protocols::telemt::tgLinkKey).toString();
c.tmeLink = json.value(protocols::telemt::tmeLinkKey).toString();
c.isEnabled = json.value(protocols::telemt::isEnabledKey).toBool(true);
c.publicHost = json.value(protocols::telemt::publicHostKey).toString();
c.transportMode = json.value(protocols::telemt::transportModeKey).toString();
c.tlsDomain = json.value(protocols::telemt::tlsDomainKey).toString();
c.maskEnabled = json.value(protocols::telemt::maskEnabledKey).toBool(true);
c.tlsEmulation = json.value(protocols::telemt::tlsEmulationKey).toBool(false);
c.useMiddleProxy = json.value(protocols::telemt::useMiddleProxyKey).toBool(true);
c.userName = json.value(protocols::telemt::userNameKey).toString();
for (const auto &v : json.value(protocols::telemt::additionalSecretsKey).toArray()) {
const QString s = v.toString();
if (!s.isEmpty()) {
c.additionalSecrets.append(s);
}
}
c.workersMode = json.value(protocols::telemt::workersModeKey).toString();
c.workers = json.value(protocols::telemt::workersKey).toString();
c.natEnabled = json.value(protocols::telemt::natEnabledKey).toBool(false);
c.natInternalIp = json.value(protocols::telemt::natInternalIpKey).toString();
c.natExternalIp = json.value(protocols::telemt::natExternalIpKey).toString();
return c;
}
bool TelemtProtocolConfig::equalsDockerDeploymentSettings(const TelemtProtocolConfig &other) const
{
const auto normPort = [](const QString &p) {
return p.isEmpty() ? QString(protocols::telemt::defaultPort) : p;
};
const auto normTransport = [](const QString &t) {
return t.isEmpty() ? QString(protocols::telemt::transportModeStandard) : t;
};
const auto normWorkersMode = [](const QString &m) {
return m.isEmpty() ? QString(protocols::telemt::workersModeAuto) : m;
};
if (normPort(port) != normPort(other.port)) {
return false;
}
if (normTransport(transportMode) != normTransport(other.transportMode)) {
return false;
}
if (tlsDomain != other.tlsDomain) {
return false;
}
if (secret != other.secret) {
return false;
}
if (tag != other.tag) {
return false;
}
if (publicHost != other.publicHost) {
return false;
}
if (maskEnabled != other.maskEnabled) {
return false;
}
if (tlsEmulation != other.tlsEmulation) {
return false;
}
if (useMiddleProxy != other.useMiddleProxy) {
return false;
}
if (userName != other.userName) {
return false;
}
if (normWorkersMode(workersMode) != normWorkersMode(other.workersMode)) {
return false;
}
if (workers != other.workers) {
return false;
}
if (natEnabled != other.natEnabled) {
return false;
}
if (natInternalIp != other.natInternalIp) {
return false;
}
if (natExternalIp != other.natExternalIp) {
return false;
}
if (isEnabled != other.isEnabled) {
return false;
}
QStringList aa = additionalSecrets;
QStringList bb = other.additionalSecrets;
aa.removeAll(QString());
bb.removeAll(QString());
std::sort(aa.begin(), aa.end());
std::sort(bb.begin(), bb.end());
return aa == bb;
}

View File

@@ -0,0 +1,38 @@
#ifndef TELEMTPROTOCOLCONFIG_H
#define TELEMTPROTOCOLCONFIG_H
#include <QJsonObject>
#include <QString>
#include <QStringList>
namespace amnezia {
struct TelemtProtocolConfig {
QString port;
QString secret;
QString tag;
QString tgLink;
QString tmeLink;
bool isEnabled = true;
QString publicHost;
QString transportMode;
QString tlsDomain;
bool maskEnabled = true;
bool tlsEmulation = false;
bool useMiddleProxy = true;
QString userName;
QStringList additionalSecrets;
QString workersMode;
QString workers;
bool natEnabled = false;
QString natInternalIp;
QString natExternalIp;
QJsonObject toJson() const;
static TelemtProtocolConfig fromJson(const QJsonObject &json);
bool equalsDockerDeploymentSettings(const TelemtProtocolConfig &other) const;
};
} // namespace amnezia
#endif // TELEMTPROTOCOLCONFIG_H

View File

@@ -3,20 +3,173 @@
#include <QJsonDocument>
#include <QJsonArray>
#include "../../../core/utils/protocolEnum.h"
#include "../../../core/protocols/protocolUtils.h"
#include "../../../core/utils/constants/configKeys.h"
#include "../../../core/utils/constants/protocolConstants.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
using namespace amnezia;
using namespace ProtocolUtils;
namespace amnezia
{
QJsonObject XrayXPaddingConfig::toJson() const
{
QJsonObject obj;
if (!bytesMin.isEmpty()) obj[configKey::xPaddingBytesMin] = bytesMin;
if (!bytesMax.isEmpty()) obj[configKey::xPaddingBytesMax] = bytesMax;
obj[configKey::xPaddingObfsMode] = obfsMode;
if (!key.isEmpty()) obj[configKey::xPaddingKey] = key;
if (!header.isEmpty()) obj[configKey::xPaddingHeader] = header;
if (!placement.isEmpty()) obj[configKey::xPaddingPlacement] = placement;
if (!method.isEmpty()) obj[configKey::xPaddingMethod] = method;
return obj;
}
XrayXPaddingConfig XrayXPaddingConfig::fromJson(const QJsonObject &json)
{
XrayXPaddingConfig c;
c.bytesMin = json.value(configKey::xPaddingBytesMin).toString();
c.bytesMax = json.value(configKey::xPaddingBytesMax).toString();
c.obfsMode = json.value(configKey::xPaddingObfsMode).toBool(true);
c.key = json.value(configKey::xPaddingKey).toString(protocols::xray::defaultSite);
c.header = json.value(configKey::xPaddingHeader).toString();
c.placement = json.value(configKey::xPaddingPlacement).toString(protocols::xray::defaultXPaddingPlacement);
c.method = json.value(configKey::xPaddingMethod).toString(protocols::xray::defaultXPaddingMethod);
return c;
}
QJsonObject XrayXmuxConfig::toJson() const
{
QJsonObject obj;
obj[configKey::xmuxEnabled] = enabled;
if (!maxConcurrencyMin.isEmpty()) obj[configKey::xmuxMaxConcurrencyMin] = maxConcurrencyMin;
if (!maxConcurrencyMax.isEmpty()) obj[configKey::xmuxMaxConcurrencyMax] = maxConcurrencyMax;
if (!maxConnectionsMin.isEmpty()) obj[configKey::xmuxMaxConnectionsMin] = maxConnectionsMin;
if (!maxConnectionsMax.isEmpty()) obj[configKey::xmuxMaxConnectionsMax] = maxConnectionsMax;
if (!cMaxReuseTimesMin.isEmpty()) obj[configKey::xmuxCMaxReuseTimesMin] = cMaxReuseTimesMin;
if (!cMaxReuseTimesMax.isEmpty()) obj[configKey::xmuxCMaxReuseTimesMax] = cMaxReuseTimesMax;
if (!hMaxRequestTimesMin.isEmpty()) obj[configKey::xmuxHMaxRequestTimesMin] = hMaxRequestTimesMin;
if (!hMaxRequestTimesMax.isEmpty()) obj[configKey::xmuxHMaxRequestTimesMax] = hMaxRequestTimesMax;
if (!hMaxReusableSecsMin.isEmpty()) obj[configKey::xmuxHMaxReusableSecsMin] = hMaxReusableSecsMin;
if (!hMaxReusableSecsMax.isEmpty()) obj[configKey::xmuxHMaxReusableSecsMax] = hMaxReusableSecsMax;
if (!hKeepAlivePeriod.isEmpty()) obj[configKey::xmuxHKeepAlivePeriod] = hKeepAlivePeriod;
return obj;
}
XrayXmuxConfig XrayXmuxConfig::fromJson(const QJsonObject &json)
{
XrayXmuxConfig c;
c.enabled = json.value(configKey::xmuxEnabled).toBool(true);
c.maxConcurrencyMin = json.value(configKey::xmuxMaxConcurrencyMin).toString("0");
c.maxConcurrencyMax = json.value(configKey::xmuxMaxConcurrencyMax).toString("0");
c.maxConnectionsMin = json.value(configKey::xmuxMaxConnectionsMin).toString("0");
c.maxConnectionsMax = json.value(configKey::xmuxMaxConnectionsMax).toString("0");
c.cMaxReuseTimesMin = json.value(configKey::xmuxCMaxReuseTimesMin).toString("0");
c.cMaxReuseTimesMax = json.value(configKey::xmuxCMaxReuseTimesMax).toString("0");
c.hMaxRequestTimesMin = json.value(configKey::xmuxHMaxRequestTimesMin).toString("0");
c.hMaxRequestTimesMax = json.value(configKey::xmuxHMaxRequestTimesMax).toString("0");
c.hMaxReusableSecsMin = json.value(configKey::xmuxHMaxReusableSecsMin).toString("0");
c.hMaxReusableSecsMax = json.value(configKey::xmuxHMaxReusableSecsMax).toString("0");
c.hKeepAlivePeriod = json.value(configKey::xmuxHKeepAlivePeriod).toString();
return c;
}
QJsonObject XrayXhttpConfig::toJson() const
{
QJsonObject obj;
if (!mode.isEmpty()) obj[configKey::xhttpMode] = mode;
if (!host.isEmpty()) obj[configKey::xhttpHost] = host;
if (!path.isEmpty()) obj[configKey::xhttpPath] = path;
if (!headersTemplate.isEmpty()) obj[configKey::xhttpHeadersTemplate] = headersTemplate;
if (!uplinkMethod.isEmpty()) obj[configKey::xhttpUplinkMethod] = uplinkMethod;
obj[configKey::xhttpDisableGrpc] = disableGrpc;
obj[configKey::xhttpDisableSse] = disableSse;
if (!sessionPlacement.isEmpty()) obj[configKey::xhttpSessionPlacement] = sessionPlacement;
if (!sessionKey.isEmpty()) obj[configKey::xhttpSessionKey] = sessionKey;
if (!seqPlacement.isEmpty()) obj[configKey::xhttpSeqPlacement] = seqPlacement;
if (!seqKey.isEmpty()) obj[configKey::xhttpSeqKey] = seqKey;
if (!uplinkDataPlacement.isEmpty()) obj[configKey::xhttpUplinkDataPlacement] = uplinkDataPlacement;
if (!uplinkDataKey.isEmpty()) obj[configKey::xhttpUplinkDataKey] = uplinkDataKey;
if (!uplinkChunkSize.isEmpty()) obj[configKey::xhttpUplinkChunkSize] = uplinkChunkSize;
if (!scMaxBufferedPosts.isEmpty()) obj[configKey::xhttpScMaxBufferedPosts] = scMaxBufferedPosts;
if (!scMaxEachPostBytesMin.isEmpty()) obj[configKey::xhttpScMaxEachPostBytesMin] = scMaxEachPostBytesMin;
if (!scMaxEachPostBytesMax.isEmpty()) obj[configKey::xhttpScMaxEachPostBytesMax] = scMaxEachPostBytesMax;
if (!scMinPostsIntervalMsMin.isEmpty()) obj[configKey::xhttpScMinPostsIntervalMsMin] = scMinPostsIntervalMsMin;
if (!scMinPostsIntervalMsMax.isEmpty()) obj[configKey::xhttpScMinPostsIntervalMsMax] = scMinPostsIntervalMsMax;
if (!scStreamUpServerSecsMin.isEmpty()) obj[configKey::xhttpScStreamUpServerSecsMin] = scStreamUpServerSecsMin;
if (!scStreamUpServerSecsMax.isEmpty()) obj[configKey::xhttpScStreamUpServerSecsMax] = scStreamUpServerSecsMax;
obj["xPadding"] = xPadding.toJson();
obj["xmux"] = xmux.toJson();
return obj;
}
XrayXhttpConfig XrayXhttpConfig::fromJson(const QJsonObject &json)
{
XrayXhttpConfig c;
c.mode = json.value(configKey::xhttpMode).toString(protocols::xray::defaultXhttpMode);
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);
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");
c.scMaxBufferedPosts = json.value(configKey::xhttpScMaxBufferedPosts).toString();
c.scMaxEachPostBytesMin = json.value(configKey::xhttpScMaxEachPostBytesMin).toString("1");
c.scMaxEachPostBytesMax = json.value(configKey::xhttpScMaxEachPostBytesMax).toString("100");
c.scMinPostsIntervalMsMin = json.value(configKey::xhttpScMinPostsIntervalMsMin).toString("100");
c.scMinPostsIntervalMsMax = json.value(configKey::xhttpScMinPostsIntervalMsMax).toString("800");
c.scStreamUpServerSecsMin = json.value(configKey::xhttpScStreamUpServerSecsMin).toString("1");
c.scStreamUpServerSecsMax = json.value(configKey::xhttpScStreamUpServerSecsMax).toString("100");
c.xPadding = XrayXPaddingConfig::fromJson(json.value("xPadding").toObject());
c.xmux = XrayXmuxConfig::fromJson(json.value("xmux").toObject());
return c;
}
QJsonObject XrayMkcpConfig::toJson() const
{
QJsonObject obj;
if (!tti.isEmpty()) obj[configKey::mkcpTti] = tti;
if (!uplinkCapacity.isEmpty()) obj[configKey::mkcpUplinkCapacity] = uplinkCapacity;
if (!downlinkCapacity.isEmpty()) obj[configKey::mkcpDownlinkCapacity] = downlinkCapacity;
if (!readBufferSize.isEmpty()) obj[configKey::mkcpReadBufferSize] = readBufferSize;
if (!writeBufferSize.isEmpty()) obj[configKey::mkcpWriteBufferSize] = writeBufferSize;
obj[configKey::mkcpCongestion] = congestion;
return obj;
}
XrayMkcpConfig XrayMkcpConfig::fromJson(const QJsonObject &json)
{
XrayMkcpConfig c;
c.tti = json.value(configKey::mkcpTti).toString();
c.uplinkCapacity = json.value(configKey::mkcpUplinkCapacity).toString();
c.downlinkCapacity = json.value(configKey::mkcpDownlinkCapacity).toString();
c.readBufferSize = json.value(configKey::mkcpReadBufferSize).toString();
c.writeBufferSize = json.value(configKey::mkcpWriteBufferSize).toString();
c.congestion = json.value(configKey::mkcpCongestion).toBool(true);
return c;
}
QJsonObject XrayServerConfig::toJson() const
{
QJsonObject obj;
// Existing fields
if (!port.isEmpty()) {
obj[configKey::port] = port;
}
@@ -29,60 +182,96 @@ QJsonObject XrayServerConfig::toJson() const
if (!site.isEmpty()) {
obj[configKey::site] = site;
}
if (isThirdPartyConfig) {
obj[configKey::isThirdPartyConfig] = isThirdPartyConfig;
}
// New: Security
if (!security.isEmpty()) {
obj[configKey::xraySecurity] = security;
}
if (!flow.isEmpty()) {
obj[configKey::xrayFlow] = flow;
}
if (!fingerprint.isEmpty()) {
obj[configKey::xrayFingerprint] = fingerprint;
}
if (!sni.isEmpty()) {
obj[configKey::xraySni] = sni;
}
if (!alpn.isEmpty()) {
obj[configKey::xrayAlpn] = alpn;
}
// New: Transport
if (!transport.isEmpty()) {
obj[configKey::xrayTransport] = transport;
}
obj["xhttp"] = xhttp.toJson();
obj["mkcp"] = mkcp.toJson();
return obj;
}
XrayServerConfig XrayServerConfig::fromJson(const QJsonObject& json)
XrayServerConfig XrayServerConfig::fromJson(const QJsonObject &json)
{
XrayServerConfig config;
config.port = json.value(configKey::port).toString();
config.transportProto = json.value(configKey::transportProto).toString();
config.subnetAddress = json.value(configKey::subnetAddress).toString();
config.site = json.value(configKey::site).toString();
config.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false);
return config;
XrayServerConfig c;
// Existing fields
c.port = json.value(configKey::port).toString();
c.transportProto = json.value(configKey::transportProto).toString();
c.subnetAddress = json.value(configKey::subnetAddress).toString();
c.site = json.value(configKey::site).toString();
c.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false);
// New: Security
c.security = json.value(configKey::xraySecurity).toString(protocols::xray::defaultSecurity);
c.flow = json.value(configKey::xrayFlow).toString(protocols::xray::defaultFlow);
c.fingerprint = json.value(configKey::xrayFingerprint).toString(protocols::xray::defaultFingerprint);
if (c.fingerprint.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
c.fingerprint = QString::fromLatin1(protocols::xray::defaultFingerprint);
}
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;
}
bool XrayServerConfig::hasEqualServerSettings(const XrayServerConfig& other) const
bool XrayServerConfig::hasEqualServerSettings(const XrayServerConfig &other) const
{
return port == other.port && site == other.site;
return port == other.port
&& site == other.site
&& security == other.security
&& flow == other.flow
&& transport == other.transport
&& fingerprint == other.fingerprint
&& sni == other.sni;
}
QJsonObject XrayClientConfig::toJson() const
{
QJsonObject obj;
if (!nativeConfig.isEmpty()) {
obj[configKey::config] = nativeConfig;
}
if (!localPort.isEmpty()) {
obj[configKey::localPort] = localPort;
}
if (!id.isEmpty()) {
obj[configKey::clientId] = id;
}
if (!nativeConfig.isEmpty()) obj[configKey::config] = nativeConfig;
if (!localPort.isEmpty()) obj[configKey::localPort] = localPort;
if (!id.isEmpty()) obj[configKey::clientId] = id;
return obj;
}
XrayClientConfig XrayClientConfig::fromJson(const QJsonObject& json)
XrayClientConfig XrayClientConfig::fromJson(const QJsonObject &json)
{
XrayClientConfig config;
config.nativeConfig = json.value(configKey::config).toString();
config.localPort = json.value(configKey::localPort).toString();
config.id = json.value(configKey::clientId).toString();
if (config.id.isEmpty() && !config.nativeConfig.isEmpty()) {
QJsonDocument doc = QJsonDocument::fromJson(config.nativeConfig.toUtf8());
XrayClientConfig c;
c.nativeConfig = json.value(configKey::config).toString();
c.localPort = json.value(configKey::localPort).toString();
c.id = json.value(configKey::clientId).toString();
if (c.id.isEmpty() && !c.nativeConfig.isEmpty()) {
QJsonDocument doc = QJsonDocument::fromJson(c.nativeConfig.toUtf8());
if (!doc.isNull() && doc.isObject()) {
QJsonObject configObj = doc.object();
if (configObj.contains(protocols::xray::outbounds)) {
@@ -100,7 +289,7 @@ XrayClientConfig XrayClientConfig::fromJson(const QJsonObject& json)
if (!users.isEmpty()) {
QJsonObject user = users[0].toObject();
if (user.contains(protocols::xray::id)) {
config.id = user[protocols::xray::id].toString();
c.id = user[protocols::xray::id].toString();
}
}
}
@@ -111,16 +300,15 @@ XrayClientConfig XrayClientConfig::fromJson(const QJsonObject& json)
}
}
}
return config;
return c;
}
QJsonObject XrayProtocolConfig::toJson() const
{
QJsonObject obj = serverConfig.toJson();
if (clientConfig.has_value()) {
// Third-party import: nativeConfig is raw Xray JSON (inbounds/outbounds)
QJsonDocument doc = QJsonDocument::fromJson(clientConfig->nativeConfig.toUtf8());
if (!doc.isNull() && doc.isObject() && doc.object().contains(protocols::xray::outbounds)
&& !doc.object().contains(configKey::config)) {
@@ -130,22 +318,20 @@ QJsonObject XrayProtocolConfig::toJson() const
obj[configKey::lastConfig] = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact));
}
}
return obj;
}
XrayProtocolConfig XrayProtocolConfig::fromJson(const QJsonObject& json)
XrayProtocolConfig XrayProtocolConfig::fromJson(const QJsonObject &json)
{
XrayProtocolConfig config;
config.serverConfig = XrayServerConfig::fromJson(json);
XrayProtocolConfig c;
c.serverConfig = XrayServerConfig::fromJson(json);
QString lastConfigStr = json.value(configKey::lastConfig).toString();
if (!lastConfigStr.isEmpty()) {
QJsonDocument doc = QJsonDocument::fromJson(lastConfigStr.toUtf8());
if (doc.isObject()) {
QJsonObject parsed = doc.object();
// Third-party import stores raw Xray config (inbounds/outbounds) directly
if (parsed.contains(protocols::xray::outbounds) && !parsed.contains(configKey::config)) {
XrayClientConfig clientCfg;
clientCfg.nativeConfig = lastConfigStr;
@@ -158,14 +344,14 @@ XrayProtocolConfig XrayProtocolConfig::fromJson(const QJsonObject& json)
}
}
}
config.clientConfig = clientCfg;
c.clientConfig = clientCfg;
} else {
config.clientConfig = XrayClientConfig::fromJson(parsed);
c.clientConfig = XrayClientConfig::fromJson(parsed);
}
}
}
return config;
return c;
}
bool XrayProtocolConfig::hasClientConfig() const
@@ -173,7 +359,7 @@ bool XrayProtocolConfig::hasClientConfig() const
return clientConfig.has_value();
}
void XrayProtocolConfig::setClientConfig(const XrayClientConfig& config)
void XrayProtocolConfig::setClientConfig(const XrayClientConfig &config)
{
clientConfig = config;
}
@@ -184,4 +370,3 @@ void XrayProtocolConfig::clearClientConfig()
}
} // namespace amnezia

View File

@@ -2,47 +2,145 @@
#define XRAYPROTOCOLCONFIG_H
#include <QJsonObject>
#include "core/utils/constants/protocolConstants.h"
#include <QString>
#include <optional>
namespace amnezia
{
// ── xPadding ─────────────────────────────────────────────────────────────────
struct XrayXPaddingConfig {
QString bytesMin; // xPaddingBytes min
QString bytesMax; // xPaddingBytes max
bool obfsMode = true; // xPaddingObfsMode
QString key; // xPaddingKey
QString header; // xPaddingHeader
QString placement = protocols::xray::defaultXPaddingPlacement; // xPaddingPlacement: Cookie|Header|Query|Body
QString method = protocols::xray::defaultXPaddingMethod; // xPaddingMethod: Repeat-x|Random|Zero
QJsonObject toJson() const;
static XrayXPaddingConfig fromJson(const QJsonObject &json);
};
// ── xmux ─────────────────────────────────────────────────────────────────────
struct XrayXmuxConfig {
bool enabled = true;
QString maxConcurrencyMin = "0";
QString maxConcurrencyMax = "0";
QString maxConnectionsMin = "0";
QString maxConnectionsMax = "0";
QString cMaxReuseTimesMin = "0";
QString cMaxReuseTimesMax = "0";
QString hMaxRequestTimesMin = "0";
QString hMaxRequestTimesMax = "0";
QString hMaxReusableSecsMin = "0";
QString hMaxReusableSecsMax = "0";
QString hKeepAlivePeriod;
QJsonObject toJson() const;
static XrayXmuxConfig fromJson(const QJsonObject &json);
};
// ── XHTTP transport ───────────────────────────────────────────────────────────
struct XrayXhttpConfig {
QString mode = protocols::xray::defaultXhttpMode; // Auto|Packet-up|Stream-up|Stream-one
QString host = protocols::xray::defaultXhttpHost;
QString path;
QString headersTemplate = protocols::xray::defaultXhttpHeadersTemplate; // HTTP|None
QString uplinkMethod = protocols::xray::defaultXhttpUplinkMethod; // POST|PUT|PATCH
bool disableGrpc = true;
bool disableSse = true;
// Session & Sequence
QString sessionPlacement = protocols::xray::defaultXhttpSessionPlacement;
QString sessionKey = protocols::xray::defaultXhttpSessionKey;
QString seqPlacement = protocols::xray::defaultXhttpSeqPlacement;
QString seqKey;
QString uplinkDataPlacement = protocols::xray::defaultXhttpUplinkDataPlacement;
QString uplinkDataKey;
// Traffic Shaping
QString uplinkChunkSize = protocols::xray::defaultXhttpUplinkChunkSize;
QString scMaxBufferedPosts;
QString scMaxEachPostBytesMin = protocols::xray::defaultXhttpScMaxEachPostBytesMin;
QString scMaxEachPostBytesMax = protocols::xray::defaultXhttpScMaxEachPostBytesMax;
QString scMinPostsIntervalMsMin = protocols::xray::defaultXhttpScMinPostsIntervalMsMin;
QString scMinPostsIntervalMsMax = protocols::xray::defaultXhttpScMinPostsIntervalMsMax;
QString scStreamUpServerSecsMin = protocols::xray::defaultXhttpScStreamUpServerSecsMin;
QString scStreamUpServerSecsMax = protocols::xray::defaultXhttpScStreamUpServerSecsMax;
XrayXPaddingConfig xPadding;
XrayXmuxConfig xmux;
QJsonObject toJson() const;
static XrayXhttpConfig fromJson(const QJsonObject &json);
};
// ── mKCP transport ────────────────────────────────────────────────────────────
struct XrayMkcpConfig {
QString tti;
QString uplinkCapacity;
QString downlinkCapacity;
QString readBufferSize;
QString writeBufferSize;
bool congestion = true;
QJsonObject toJson() const;
static XrayMkcpConfig fromJson(const QJsonObject &json);
};
// ── Server config (settings editable by user) ─────────────────────────────────
struct XrayServerConfig {
QString port;
QString transportProto;
QString subnetAddress;
QString site;
bool isThirdPartyConfig = false;
// New: Security
QString security = protocols::xray::defaultSecurity;
QString flow = protocols::xray::defaultFlow;
QString fingerprint = protocols::xray::defaultFingerprint;
QString sni = protocols::xray::defaultSni;
QString alpn = protocols::xray::defaultAlpn;
// New: Transport
QString transport = protocols::xray::defaultTransport;
XrayXhttpConfig xhttp;
XrayMkcpConfig mkcp;
QJsonObject toJson() const;
static XrayServerConfig fromJson(const QJsonObject& json);
bool hasEqualServerSettings(const XrayServerConfig& other) const;
static XrayServerConfig fromJson(const QJsonObject &json);
bool hasEqualServerSettings(const XrayServerConfig &other) const;
};
// ── Client config (generated, not edited by user) ─────────────────────────────
struct XrayClientConfig {
QString nativeConfig;
QString localPort;
QString id;
QJsonObject toJson() const;
static XrayClientConfig fromJson(const QJsonObject& json);
static XrayClientConfig fromJson(const QJsonObject &json);
};
// ── Top-level protocol config ──────────────────────────────────────────────────
struct XrayProtocolConfig {
XrayServerConfig serverConfig;
std::optional<XrayClientConfig> clientConfig;
QJsonObject toJson() const;
static XrayProtocolConfig fromJson(const QJsonObject& json);
static XrayProtocolConfig fromJson(const QJsonObject &json);
bool hasClientConfig() const;
void setClientConfig(const XrayClientConfig& config);
void setClientConfig(const XrayClientConfig &config);
void clearClientConfig();
};
} // namespace amnezia
#endif // XRAYPROTOCOLCONFIG_H

View File

@@ -35,6 +35,9 @@ QJsonObject NativeServerConfig::toJson() const
if (!description.isEmpty()) {
obj[configKey::description] = this->description;
}
if (!displayName.isEmpty()) {
obj[configKey::displayName] = displayName;
}
if (!hostName.isEmpty()) {
obj[configKey::hostName] = hostName;
}
@@ -67,6 +70,7 @@ NativeServerConfig NativeServerConfig::fromJson(const QJsonObject& json)
NativeServerConfig config;
config.description = json.value(configKey::description).toString();
config.displayName = json.value(configKey::displayName).toString();
config.hostName = json.value(configKey::hostName).toString();
QJsonArray containersArray = json.value(configKey::containers).toArray();
@@ -86,6 +90,10 @@ NativeServerConfig NativeServerConfig::fromJson(const QJsonObject& json)
config.dns1 = json.value(configKey::dns1).toString();
config.dns2 = json.value(configKey::dns2).toString();
if (config.displayName.isEmpty()) {
config.displayName = config.description.isEmpty() ? config.hostName : config.description;
}
return config;
}

View File

@@ -16,6 +16,7 @@ using namespace ContainerEnumNS;
struct NativeServerConfig {
QString description;
QString displayName;
QString hostName;
QMap<DockerContainer, ContainerConfig> containers;
DockerContainer defaultContainer;

View File

@@ -0,0 +1,170 @@
#include "selfHostedAdminServerConfig.h"
#include <QJsonArray>
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/networkUtilities.h"
namespace amnezia
{
using namespace ContainerEnumNS;
bool SelfHostedAdminServerConfig::hasCredentials() const
{
return !userName.isEmpty() && !password.isEmpty() && port > 0;
}
bool SelfHostedAdminServerConfig::isReadOnly() const
{
return !hasCredentials();
}
ServerCredentials SelfHostedAdminServerConfig::credentials() const
{
ServerCredentials creds;
creds.hostName = hostName;
creds.userName = userName;
creds.secretData = password;
creds.port = port;
return creds;
}
bool SelfHostedAdminServerConfig::hasContainers() const
{
return !containers.isEmpty();
}
ContainerConfig SelfHostedAdminServerConfig::containerConfig(DockerContainer container) const
{
if (!containers.contains(container)) {
return ContainerConfig{};
}
return containers.value(container);
}
void SelfHostedAdminServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
{
containers[container] = config;
}
void SelfHostedAdminServerConfig::clearCachedClientProfile(DockerContainer container)
{
if (ContainerUtils::containerService(container) == ServiceType::Other) {
return;
}
ContainerConfig cleared = containerConfig(container);
cleared.protocolConfig.clearClientConfig();
containers[container] = cleared;
}
QPair<QString, QString> SelfHostedAdminServerConfig::getDnsPair(bool isAmneziaDnsEnabled, const QString &primaryDns,
const QString &secondaryDns) const
{
QString d1 = dns1;
QString d2 = dns2;
const bool dnsOnServer = containers.contains(DockerContainer::Dns);
if (d1.isEmpty() || !NetworkUtilities::checkIPv4Format(d1)) {
d1 = (isAmneziaDnsEnabled && dnsOnServer) ? protocols::dns::amneziaDnsIp : primaryDns;
}
if (d2.isEmpty() || !NetworkUtilities::checkIPv4Format(d2)) {
d2 = secondaryDns;
}
return { d1, d2 };
}
QJsonObject SelfHostedAdminServerConfig::toJson() const
{
QJsonObject obj;
if (!description.isEmpty()) {
obj[configKey::description] = this->description;
}
if (!displayName.isEmpty()) {
obj[configKey::displayName] = displayName;
}
if (!hostName.isEmpty()) {
obj[configKey::hostName] = hostName;
}
QJsonArray containersArray;
for (auto it = containers.begin(); it != containers.end(); ++it) {
QJsonObject containerObj = it.value().toJson();
containersArray.append(containerObj);
}
if (!containersArray.isEmpty()) {
obj[configKey::containers] = containersArray;
}
if (defaultContainer != DockerContainer::None) {
obj[configKey::defaultContainer] = ContainerUtils::containerToString(defaultContainer);
}
if (!dns1.isEmpty()) {
obj[configKey::dns1] = dns1;
}
if (!dns2.isEmpty()) {
obj[configKey::dns2] = dns2;
}
if (!userName.isEmpty()) {
obj[configKey::userName] = userName;
}
if (!password.isEmpty()) {
obj[configKey::password] = password;
}
if (port > 0) {
obj[configKey::port] = port;
}
return obj;
}
SelfHostedAdminServerConfig SelfHostedAdminServerConfig::fromJson(const QJsonObject &json)
{
SelfHostedAdminServerConfig config;
config.description = json.value(configKey::description).toString();
config.displayName = json.value(configKey::displayName).toString();
config.hostName = json.value(configKey::hostName).toString();
QJsonArray containersArray = json.value(configKey::containers).toArray();
for (const QJsonValue &val : containersArray) {
QJsonObject containerObj = val.toObject();
ContainerConfig cc = ContainerConfig::fromJson(containerObj);
QString containerStr = containerObj.value(configKey::container).toString();
DockerContainer container = ContainerUtils::containerFromString(containerStr);
config.containers.insert(container, cc);
}
QString defaultContainerStr = json.value(configKey::defaultContainer).toString();
config.defaultContainer = ContainerUtils::containerFromString(defaultContainerStr);
config.dns1 = json.value(configKey::dns1).toString();
config.dns2 = json.value(configKey::dns2).toString();
config.userName = json.value(configKey::userName).toString();
config.password = json.value(configKey::password).toString();
if (json.contains(configKey::port)) {
config.port = json.value(configKey::port).toInt();
} else {
config.port = 0;
}
if (config.displayName.isEmpty()) {
config.displayName = config.description.isEmpty() ? config.hostName : config.description;
}
return config;
}
} // namespace amnezia

View File

@@ -0,0 +1,53 @@
#ifndef SELFHOSTEDADMINSERVERCONFIG_H
#define SELFHOSTEDADMINSERVERCONFIG_H
#include <QJsonObject>
#include <QMap>
#include <QPair>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/models/containerConfig.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
namespace amnezia
{
using namespace ContainerEnumNS;
struct SelfHostedAdminServerConfig {
QString description;
QString displayName;
QString hostName;
QMap<DockerContainer, ContainerConfig> containers;
DockerContainer defaultContainer;
QString dns1;
QString dns2;
QString userName;
QString password;
int port = 0;
bool hasCredentials() const;
bool isReadOnly() const;
ServerCredentials credentials() const;
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
void clearCachedClientProfile(DockerContainer container);
QPair<QString, QString> getDnsPair(bool isAmneziaDnsEnabled, const QString &primaryDns,
const QString &secondaryDns) const;
QJsonObject toJson() const;
static SelfHostedAdminServerConfig fromJson(const QJsonObject &json);
};
} // namespace amnezia
#endif // SELFHOSTEDADMINSERVERCONFIG_H

View File

@@ -1,53 +1,40 @@
#include "selfHostedServerConfig.h"
#include "selfHostedUserServerConfig.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <stdexcept>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
namespace amnezia
{
using namespace ContainerEnumNS;
bool SelfHostedServerConfig::hasCredentials() const
bool SelfHostedUserServerConfig::hasCredentials() const
{
return userName.has_value() && password.has_value() && port.has_value();
return false;
}
bool SelfHostedServerConfig::isReadOnly() const
bool SelfHostedUserServerConfig::isReadOnly() const
{
return !hasCredentials();
return true;
}
std::optional<ServerCredentials> SelfHostedServerConfig::credentials() const
std::optional<ServerCredentials> SelfHostedUserServerConfig::credentials() const
{
if (!hasCredentials()) {
return std::nullopt;
}
ServerCredentials creds;
creds.hostName = hostName;
creds.userName = userName.value();
creds.secretData = password.value();
creds.port = port.value();
return creds;
return std::nullopt;
}
bool SelfHostedServerConfig::hasContainers() const
bool SelfHostedUserServerConfig::hasContainers() const
{
return !containers.isEmpty();
}
ContainerConfig SelfHostedServerConfig::containerConfig(DockerContainer container) const
ContainerConfig SelfHostedUserServerConfig::containerConfig(DockerContainer container) const
{
if (!containers.contains(container)) {
return ContainerConfig{};
@@ -55,17 +42,20 @@ ContainerConfig SelfHostedServerConfig::containerConfig(DockerContainer containe
return containers.value(container);
}
QJsonObject SelfHostedServerConfig::toJson() const
QJsonObject SelfHostedUserServerConfig::toJson() const
{
QJsonObject obj;
if (!description.isEmpty()) {
obj[configKey::description] = this->description;
}
if (!displayName.isEmpty()) {
obj[configKey::displayName] = displayName;
}
if (!hostName.isEmpty()) {
obj[configKey::hostName] = hostName;
}
QJsonArray containersArray;
for (auto it = containers.begin(); it != containers.end(); ++it) {
QJsonObject containerObj = it.value().toJson();
@@ -74,67 +64,51 @@ QJsonObject SelfHostedServerConfig::toJson() const
if (!containersArray.isEmpty()) {
obj[configKey::containers] = containersArray;
}
if (defaultContainer != DockerContainer::None) {
obj[configKey::defaultContainer] = ContainerUtils::containerToString(defaultContainer);
}
if (!dns1.isEmpty()) {
obj[configKey::dns1] = dns1;
}
if (!dns2.isEmpty()) {
obj[configKey::dns2] = dns2;
}
if (userName.has_value()) {
obj[configKey::userName] = userName.value();
}
if (password.has_value()) {
obj[configKey::password] = password.value();
}
if (port.has_value()) {
obj[configKey::port] = port.value();
}
return obj;
}
SelfHostedServerConfig SelfHostedServerConfig::fromJson(const QJsonObject& json)
SelfHostedUserServerConfig SelfHostedUserServerConfig::fromJson(const QJsonObject &json)
{
SelfHostedServerConfig config;
SelfHostedUserServerConfig config;
config.description = json.value(configKey::description).toString();
config.displayName = json.value(configKey::displayName).toString();
config.hostName = json.value(configKey::hostName).toString();
QJsonArray containersArray = json.value(configKey::containers).toArray();
for (const QJsonValue& val : containersArray) {
for (const QJsonValue &val : containersArray) {
QJsonObject containerObj = val.toObject();
ContainerConfig containerConfig = ContainerConfig::fromJson(containerObj);
ContainerConfig cc = ContainerConfig::fromJson(containerObj);
QString containerStr = containerObj.value(configKey::container).toString();
DockerContainer container = ContainerUtils::containerFromString(containerStr);
config.containers.insert(container, containerConfig);
config.containers.insert(container, cc);
}
QString defaultContainerStr = json.value(configKey::defaultContainer).toString();
config.defaultContainer = ContainerUtils::containerFromString(defaultContainerStr);
config.dns1 = json.value(configKey::dns1).toString();
config.dns2 = json.value(configKey::dns2).toString();
if (json.contains(configKey::userName)) {
config.userName = json.value(configKey::userName).toString();
if (config.displayName.isEmpty()) {
config.displayName = config.description.isEmpty() ? config.hostName : config.description;
}
if (json.contains(configKey::password)) {
config.password = json.value(configKey::password).toString();
}
if (json.contains(configKey::port)) {
config.port = json.value(configKey::port).toInt();
}
return config;
}
} // namespace amnezia

View File

@@ -1,5 +1,5 @@
#ifndef SELFHOSTEDSERVERCONFIG_H
#define SELFHOSTEDSERVERCONFIG_H
#ifndef SELFHOSTEDUSERSERVERCONFIG_H
#define SELFHOSTEDUSERSERVERCONFIG_H
#include <QJsonObject>
#include <QMap>
@@ -9,8 +9,6 @@
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/models/containerConfig.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
namespace amnezia
@@ -18,28 +16,24 @@ namespace amnezia
using namespace ContainerEnumNS;
struct SelfHostedServerConfig {
struct SelfHostedUserServerConfig {
QString description;
QString displayName;
QString hostName;
QMap<DockerContainer, ContainerConfig> containers;
DockerContainer defaultContainer;
QString dns1;
QString dns2;
std::optional<QString> userName;
std::optional<QString> password;
std::optional<int> port;
bool hasCredentials() const;
bool isReadOnly() const;
std::optional<ServerCredentials> credentials() const;
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
QJsonObject toJson() const;
static SelfHostedServerConfig fromJson(const QJsonObject& json);
static SelfHostedUserServerConfig fromJson(const QJsonObject &json);
};
} // namespace amnezia
#endif // SELFHOSTEDSERVERCONFIG_H
#endif // SELFHOSTEDUSERSERVERCONFIG_H

View File

@@ -1,234 +0,0 @@
#include "serverConfig.h"
#include "core/utils/api/apiUtils.h"
#include "core/utils/networkUtilities.h"
#include "core/models/selfhosted/selfHostedServerConfig.h"
#include "core/models/selfhosted/nativeServerConfig.h"
#include "core/models/api/apiV1ServerConfig.h"
#include "core/models/api/apiV2ServerConfig.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
namespace amnezia
{
using namespace ContainerEnumNS;
QString ServerConfig::description() const
{
return std::visit([](const auto& v) { return v.description; }, data);
}
QString ServerConfig::hostName() const
{
return std::visit([](const auto& v) { return v.hostName; }, data);
}
QString ServerConfig::displayName() const
{
if (isApiV1()) {
const auto *apiV1 = as<ApiV1ServerConfig>();
return apiV1 ? apiV1->name : description();
}
if (isApiV2()) {
const auto *apiV2 = as<ApiV2ServerConfig>();
return apiV2 ? apiV2->name : description();
}
QString name = description();
return name.isEmpty() ? hostName() : name;
}
QMap<DockerContainer, ContainerConfig> ServerConfig::containers() const
{
return std::visit([](const auto& v) { return v.containers; }, data);
}
DockerContainer ServerConfig::defaultContainer() const
{
return std::visit([](const auto& v) { return v.defaultContainer; }, data);
}
QString ServerConfig::dns1() const
{
return std::visit([](const auto& v) { return v.dns1; }, data);
}
QString ServerConfig::dns2() const
{
return std::visit([](const auto& v) { return v.dns2; }, data);
}
bool ServerConfig::hasContainers() const
{
return std::visit([](const auto& v) { return v.hasContainers(); }, data);
}
ContainerConfig ServerConfig::containerConfig(DockerContainer container) const
{
return std::visit([container](const auto& v) { return v.containerConfig(container); }, data);
}
int ServerConfig::crc() const
{
return std::visit([](const auto& v) -> int {
using T = std::decay_t<decltype(v)>;
if constexpr (std::is_same_v<T, ApiV1ServerConfig> ||
std::is_same_v<T, ApiV2ServerConfig>) {
return v.crc;
}
return 0;
}, data);
}
int ServerConfig::configVersion() const
{
return std::visit([](const auto& v) -> int {
using T = std::decay_t<decltype(v)>;
if constexpr (std::is_same_v<T, ApiV1ServerConfig>) {
return apiDefs::ConfigSource::Telegram;
} else if constexpr (std::is_same_v<T, ApiV2ServerConfig>) {
return apiDefs::ConfigSource::AmneziaGateway;
}
return 0; // SelfHostedServerConfig or NativeServerConfig
}, data);
}
bool ServerConfig::isSelfHosted() const
{
return std::holds_alternative<SelfHostedServerConfig>(data);
}
bool ServerConfig::isNative() const
{
return std::holds_alternative<NativeServerConfig>(data);
}
bool ServerConfig::isApiV1() const
{
return std::holds_alternative<ApiV1ServerConfig>(data);
}
bool ServerConfig::isApiV2() const
{
return std::holds_alternative<ApiV2ServerConfig>(data);
}
bool ServerConfig::isApiConfig() const
{
return isApiV1() || isApiV2();
}
QJsonObject ServerConfig::toJson() const
{
return std::visit([](const auto& v) { return v.toJson(); }, data);
}
ServerConfig ServerConfig::fromJson(const QJsonObject& json)
{
apiDefs::ConfigType configType = apiUtils::getConfigType(json);
switch (configType) {
case apiDefs::ConfigType::SelfHosted: {
bool hasThirdPartyConfig = false;
QJsonArray containersArray = json.value(configKey::containers).toArray();
for (const QJsonValue& val : containersArray) {
QJsonObject containerObj = val.toObject();
for (auto it = containerObj.begin(); it != containerObj.end(); ++it) {
QString key = it.key();
if (key != configKey::container) {
QJsonObject protocolObj = it.value().toObject();
if (protocolObj.contains(configKey::isThirdPartyConfig) &&
protocolObj.value(configKey::isThirdPartyConfig).toBool()) {
hasThirdPartyConfig = true;
break;
}
}
}
if (hasThirdPartyConfig) {
break;
}
}
if (hasThirdPartyConfig) {
return ServerConfig{NativeServerConfig::fromJson(json)};
} else {
return ServerConfig{SelfHostedServerConfig::fromJson(json)};
}
}
case apiDefs::ConfigType::AmneziaPremiumV1:
case apiDefs::ConfigType::AmneziaFreeV2:
return ServerConfig{ApiV1ServerConfig::fromJson(json)};
case apiDefs::ConfigType::AmneziaPremiumV2:
case apiDefs::ConfigType::AmneziaFreeV3:
case apiDefs::ConfigType::ExternalPremium:
return ServerConfig{ApiV2ServerConfig::fromJson(json)};
default: {
// Check if any container has isThirdPartyConfig
bool hasThirdPartyConfig = false;
QJsonArray containersArray = json.value(configKey::containers).toArray();
for (const QJsonValue& val : containersArray) {
QJsonObject containerObj = val.toObject();
// Check all protocol keys in the container object
for (auto it = containerObj.begin(); it != containerObj.end(); ++it) {
QString key = it.key();
if (key != configKey::container) {
QJsonObject protocolObj = it.value().toObject();
if (protocolObj.contains(configKey::isThirdPartyConfig) &&
protocolObj.value(configKey::isThirdPartyConfig).toBool()) {
hasThirdPartyConfig = true;
break;
}
}
}
if (hasThirdPartyConfig) {
break;
}
}
if (hasThirdPartyConfig) {
return ServerConfig{NativeServerConfig::fromJson(json)};
} else {
return ServerConfig{SelfHostedServerConfig::fromJson(json)};
}
}
}
}
QPair<QString, QString> ServerConfig::getDnsPair(bool isAmneziaDnsEnabled,
const QString &primaryDns,
const QString &secondaryDns) const
{
QPair<QString, QString> dns;
QMap<DockerContainer, ContainerConfig> serverContainers = containers();
bool isDnsContainerInstalled = false;
for (auto it = serverContainers.begin(); it != serverContainers.end(); ++it) {
if (it.key() == DockerContainer::Dns) {
isDnsContainerInstalled = true;
break;
}
}
dns.first = dns1();
dns.second = dns2();
if (dns.first.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.first)) {
if (isAmneziaDnsEnabled && isDnsContainerInstalled) {
dns.first = protocols::dns::amneziaDnsIp;
} else {
dns.first = primaryDns;
}
}
if (dns.second.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.second)) {
dns.second = secondaryDns;
}
return dns;
}
} // namespace amnezia

View File

@@ -1,92 +0,0 @@
#ifndef SERVERCONFIG_H
#define SERVERCONFIG_H
#include <variant>
#include <QJsonObject>
#include <QMap>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/models/selfhosted/selfHostedServerConfig.h"
#include "core/models/selfhosted/nativeServerConfig.h"
#include "core/models/api/apiV1ServerConfig.h"
#include "core/models/api/apiV2ServerConfig.h"
#include "core/models/containerConfig.h"
namespace amnezia
{
using namespace ContainerEnumNS;
struct ServerConfig {
using Variant = std::variant<
SelfHostedServerConfig,
NativeServerConfig,
ApiV1ServerConfig,
ApiV2ServerConfig
>;
Variant data;
ServerConfig() = default;
ServerConfig(const Variant& v) : data(v) {}
ServerConfig(Variant&& v) : data(std::move(v)) {}
template<typename T, typename = std::enable_if_t<!std::is_same<std::remove_cv_t<std::remove_reference_t<T>>, ServerConfig>::value>>
ServerConfig(const T& v) : data(v) {}
template<typename T, typename = std::enable_if_t<!std::is_same<std::remove_cv_t<std::remove_reference_t<T>>, ServerConfig>::value>>
ServerConfig(T&& v) : data(std::forward<T>(v)) {}
QString description() const;
QString hostName() const;
QString displayName() const;
QMap<DockerContainer, ContainerConfig> containers() const;
DockerContainer defaultContainer() const;
QString dns1() const;
QString dns2() const;
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
int crc() const;
int configVersion() const;
bool isSelfHosted() const;
bool isNative() const;
bool isApiV1() const;
bool isApiV2() const;
bool isApiConfig() const;
template<typename T>
T* as() {
return std::get_if<T>(&data);
}
template<typename T>
const T* as() const {
return std::get_if<T>(&data);
}
QJsonObject toJson() const;
static ServerConfig fromJson(const QJsonObject& json);
template<typename Visitor>
auto visit(Visitor&& visitor) {
return std::visit(std::forward<Visitor>(visitor), data);
}
template<typename Visitor>
auto visit(Visitor&& visitor) const {
return std::visit(std::forward<Visitor>(visitor), data);
}
QPair<QString, QString> getDnsPair(bool isAmneziaDnsEnabled,
const QString &primaryDns,
const QString &secondaryDns) const;
};
} // namespace amnezia
#endif // SERVERCONFIG_H

View File

@@ -0,0 +1,187 @@
#include "serverDescription.h"
#include <QMap>
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/api/apiUtils.h"
#include "core/utils/containers/containerUtils.h"
#include "core/protocols/protocolUtils.h"
#include "core/models/protocols/awgProtocolConfig.h"
using namespace amnezia;
namespace
{
bool computeHasInstalledVpnContainers(const QMap<DockerContainer, ContainerConfig> &containers)
{
for (auto it = containers.begin(); it != containers.end(); ++it) {
const DockerContainer container = it.key();
if (ContainerUtils::containerService(container) == ServiceType::Vpn || container == DockerContainer::SSXray) {
return true;
}
}
return false;
}
template <typename T>
ServerDescription buildBaseDescription(const T &server)
{
ServerDescription row;
row.hostName = server.hostName;
row.defaultContainer = server.defaultContainer;
row.primaryDnsIsAmnezia = (server.dns1 == protocols::dns::amneziaDnsIp);
row.hasInstalledVpnContainers = computeHasInstalledVpnContainers(server.containers);
return row;
}
QString getBaseDescription(const QMap<DockerContainer, ContainerConfig> &containers,
bool isAmneziaDnsEnabled,
bool hasWriteAccess,
bool primaryDnsIsAmnezia)
{
QString description;
if (hasWriteAccess) {
const bool isDnsInstalled = containers.contains(DockerContainer::Dns);
if (isAmneziaDnsEnabled && isDnsInstalled) {
description += QStringLiteral("Amnezia DNS | ");
}
} else if (primaryDnsIsAmnezia) {
description += QStringLiteral("Amnezia DNS | ");
}
return description;
}
QString getProtocolName(DockerContainer defaultContainer, const QMap<DockerContainer, ContainerConfig> &containers)
{
QString containerName = ContainerUtils::containerHumanNames().value(defaultContainer);
QString protocolVersion;
if (ContainerUtils::isAwgContainer(defaultContainer)) {
const auto it = containers.constFind(defaultContainer);
if (it != containers.cend()) {
if (const AwgProtocolConfig *awg = it->getAwgProtocolConfig()) {
protocolVersion = ProtocolUtils::getProtocolVersionString(awg->toJson());
if (defaultContainer == DockerContainer::Awg && !awg->serverConfig.isThirdPartyConfig) {
containerName = QStringLiteral("AmneziaWG Legacy");
}
}
}
}
return containerName + protocolVersion + QStringLiteral(" | ");
}
} // namespace
namespace amnezia
{
ServerDescription buildServerDescription(const SelfHostedAdminServerConfig &server, bool isAmneziaDnsEnabled)
{
ServerDescription row = buildBaseDescription(server);
row.selfHostedSshCredentials.hostName = server.hostName;
row.selfHostedSshCredentials.userName = server.userName;
row.selfHostedSshCredentials.secretData = server.password;
row.selfHostedSshCredentials.port = server.port > 0 ? server.port : 22;
row.hasWriteAccess = !row.selfHostedSshCredentials.userName.isEmpty()
&& !row.selfHostedSshCredentials.secretData.isEmpty();
row.serverName = server.displayName;
row.baseDescription = getBaseDescription(server.containers, isAmneziaDnsEnabled, row.hasWriteAccess, row.primaryDnsIsAmnezia);
const QString protocolName = getProtocolName(server.defaultContainer, server.containers);
row.expandedServerDescription = row.baseDescription + row.hostName;
row.collapsedServerDescription = row.baseDescription + protocolName + row.hostName;
return row;
}
ServerDescription buildServerDescription(const SelfHostedUserServerConfig &server, bool isAmneziaDnsEnabled)
{
ServerDescription row = buildBaseDescription(server);
row.selfHostedSshCredentials.hostName = server.hostName;
row.selfHostedSshCredentials.port = 22;
row.hasWriteAccess = false;
row.serverName = server.displayName;
row.baseDescription = getBaseDescription(server.containers, isAmneziaDnsEnabled, row.hasWriteAccess, row.primaryDnsIsAmnezia);
const QString protocolName = getProtocolName(server.defaultContainer, server.containers);
row.expandedServerDescription = row.baseDescription + row.hostName;
row.collapsedServerDescription = row.baseDescription + protocolName + row.hostName;
return row;
}
ServerDescription buildServerDescription(const NativeServerConfig &server, bool isAmneziaDnsEnabled)
{
ServerDescription row = buildBaseDescription(server);
row.hasWriteAccess = false;
row.serverName = server.displayName;
row.baseDescription = getBaseDescription(server.containers, isAmneziaDnsEnabled, row.hasWriteAccess, row.primaryDnsIsAmnezia);
const QString protocolName = getProtocolName(server.defaultContainer, server.containers);
row.expandedServerDescription = row.baseDescription + row.hostName;
row.collapsedServerDescription = row.baseDescription + protocolName + row.hostName;
return row;
}
ServerDescription buildServerDescription(const LegacyApiServerConfig &server, bool /*isAmneziaDnsEnabled*/)
{
ServerDescription row = buildBaseDescription(server);
row.configVersion = serverConfigUtils::ConfigSource::Telegram;
row.isApiV1 = true;
row.isServerFromGatewayApi = false;
row.hasWriteAccess = false;
row.serverName = server.displayName;
row.baseDescription = server.description;
const QString fullDescriptionForCollapsed = row.baseDescription;
row.collapsedServerDescription = fullDescriptionForCollapsed;
row.expandedServerDescription = fullDescriptionForCollapsed;
return row;
}
ServerDescription buildServerDescription(const ApiV2ServerConfig &server, bool /*isAmneziaDnsEnabled*/)
{
ServerDescription row = buildBaseDescription(server);
row.configVersion = serverConfigUtils::ConfigSource::AmneziaGateway;
row.isApiV2 = true;
row.isServerFromGatewayApi = true;
row.isPremium = server.isPremium() || server.isExternalPremium();
row.hasWriteAccess = false;
row.serverName = server.displayName;
row.baseDescription = server.apiConfig.serverCountryCode.isEmpty() ? server.description : server.apiConfig.serverCountryName;
row.isCountrySelectionAvailable = !server.apiConfig.availableCountries.isEmpty();
row.apiAvailableCountries = server.apiConfig.availableCountries;
row.apiServerCountryCode = server.apiConfig.serverCountryCode;
row.isAdVisible = server.apiConfig.serviceInfo.isAdVisible;
row.adHeader = server.apiConfig.serviceInfo.adHeader;
row.adDescription = server.apiConfig.serviceInfo.adDescription;
row.adEndpoint = server.apiConfig.serviceInfo.adEndpoint;
row.isRenewalAvailable = server.apiConfig.serviceInfo.isRenewalAvailable;
if (!server.apiConfig.isInAppPurchase) {
if (server.apiConfig.subscriptionExpiredByServer) {
row.isSubscriptionExpired = true;
} else if (!server.apiConfig.subscription.endDate.isEmpty()) {
row.isSubscriptionExpired = apiUtils::isSubscriptionExpired(server.apiConfig.subscription.endDate);
row.isSubscriptionExpiringSoon = apiUtils::isSubscriptionExpiringSoon(server.apiConfig.subscription.endDate);
}
}
const QString fullDescriptionForCollapsed = row.baseDescription;
row.collapsedServerDescription = fullDescriptionForCollapsed;
row.expandedServerDescription = fullDescriptionForCollapsed;
return row;
}
} // namespace amnezia

View File

@@ -0,0 +1,64 @@
#ifndef SERVERDESCRIPTION_H
#define SERVERDESCRIPTION_H
#include <QString>
#include <QJsonArray>
#include "core/utils/containerEnum.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/models/selfhosted/selfHostedAdminServerConfig.h"
#include "core/models/selfhosted/selfHostedUserServerConfig.h"
#include "core/models/selfhosted/nativeServerConfig.h"
#include "core/models/api/legacyApiServerConfig.h"
#include "core/models/api/apiV2ServerConfig.h"
namespace amnezia
{
struct ServerDescription
{
QString serverId;
QString serverName;
QString baseDescription;
QString hostName;
int configVersion = 0;
ServerCredentials selfHostedSshCredentials;
bool hasWriteAccess = false;
bool primaryDnsIsAmnezia = false;
DockerContainer defaultContainer = DockerContainer::None;
bool hasInstalledVpnContainers = false;
bool isApiV1 = false;
bool isApiV2 = false;
bool isServerFromGatewayApi = false;
bool isPremium = false;
bool isCountrySelectionAvailable = false;
QJsonArray apiAvailableCountries;
QString apiServerCountryCode;
bool isAdVisible = false;
QString adHeader;
QString adDescription;
QString adEndpoint;
bool isRenewalAvailable = false;
bool isSubscriptionExpired = false;
bool isSubscriptionExpiringSoon = false;
QString collapsedServerDescription;
QString expandedServerDescription;
};
ServerDescription buildServerDescription(const SelfHostedAdminServerConfig &server, bool isAmneziaDnsEnabled);
ServerDescription buildServerDescription(const SelfHostedUserServerConfig &server, bool isAmneziaDnsEnabled);
ServerDescription buildServerDescription(const NativeServerConfig &server, bool isAmneziaDnsEnabled);
ServerDescription buildServerDescription(const LegacyApiServerConfig &server, bool isAmneziaDnsEnabled);
ServerDescription buildServerDescription(const ApiV2ServerConfig &server, bool isAmneziaDnsEnabled);
} // namespace amnezia
#endif

View File

@@ -68,7 +68,10 @@ QMap<Proto, QString> ProtocolUtils::protocolHumanNames()
{ Proto::TorWebSite, "Website in Tor network" },
{ Proto::Dns, "DNS Service" },
{ Proto::Sftp, QObject::tr("SFTP service") },
{ Proto::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
{ Proto::Socks5Proxy, QObject::tr("SOCKS5 proxy server") },
{ Proto::MtProxy, QObject::tr("MTProxy (Telegram)") },
{ Proto::Telemt, QObject::tr("Telemt (Telegram)") },
};
}
QMap<Proto, QString> ProtocolUtils::protocolDescriptions()
@@ -92,6 +95,8 @@ ServiceType ProtocolUtils::protocolService(Proto p)
case Proto::Dns: return ServiceType::Other;
case Proto::Sftp: return ServiceType::Other;
case Proto::Socks5Proxy: return ServiceType::Other;
case Proto::MtProxy: return ServiceType::Other;
case Proto::Telemt: return ServiceType::Other;
default: return ServiceType::Other;
}
}
@@ -104,6 +109,8 @@ int ProtocolUtils::getPortForInstall(Proto p)
case OpenVpn:
case Socks5Proxy:
return QRandomGenerator::global()->bounded(30000, 50000);
case MtProxy:
case Telemt:
default:
return defaultPort(p);
}
@@ -123,6 +130,8 @@ int ProtocolUtils::defaultPort(Proto p)
case Proto::Dns: return 53;
case Proto::Sftp: return 222;
case Proto::Socks5Proxy: return 38080;
case Proto::MtProxy: return QString(protocols::mtProxy::defaultPort).toInt();
case Proto::Telemt: return QString(protocols::telemt::defaultPort).toInt();
default: return -1;
}
}
@@ -141,6 +150,8 @@ bool ProtocolUtils::defaultPortChangeable(Proto p)
case Proto::Dns: return false;
case Proto::Sftp: return true;
case Proto::Socks5Proxy: return true;
case Proto::MtProxy: return true;
case Proto::Telemt: return true;
default: return false;
}
}
@@ -161,6 +172,8 @@ TransportProto ProtocolUtils::defaultTransportProto(Proto p)
case Proto::Dns: return TransportProto::Udp;
case Proto::Sftp: return TransportProto::Tcp;
case Proto::Socks5Proxy: return TransportProto::Tcp;
case Proto::MtProxy: return TransportProto::Tcp;
case Proto::Telemt: return TransportProto::Tcp;
default: return TransportProto::Udp;
}
}
@@ -180,9 +193,10 @@ bool ProtocolUtils::defaultTransportProtoChangeable(Proto p)
case Proto::Dns: return false;
case Proto::Sftp: return false;
case Proto::Socks5Proxy: return false;
case Proto::MtProxy: return false;
case Proto::Telemt: return false;
default: return false;
}
return false;
}
QString ProtocolUtils::key_proto_config_data(Proto p)
@@ -208,4 +222,3 @@ QString ProtocolUtils::getProtocolVersionString(const QJsonObject &protocolConfi
if (version == protocols::awg::awgV1_5) return QObject::tr(" (version 1.5)");
return "";
}

48
client/core/protocols/xrayProtocol.cpp Executable file → Normal file
View File

@@ -2,6 +2,7 @@
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/ipcClient.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/serialization/serialization.h"
@@ -9,6 +10,7 @@
#include <QCryptographicHash>
#include <QJsonDocument>
#include <QTimer>
#include <QJsonObject>
#include <QNetworkInterface>
#include <QtCore/qlogging.h>
@@ -79,12 +81,29 @@ ErrorCode XrayProtocol::start()
m_socksPassword = creds.password;
m_socksPort = creds.port;
const QString xrayConfigStr = QJsonDocument(m_xrayConfig).toJson(QJsonDocument::Compact);
QString xrayConfigStr = QJsonDocument(m_xrayConfig).toJson(QJsonDocument::Compact);
if (xrayConfigStr.isEmpty()) {
qCritical() << "Xray config is empty";
return ErrorCode::XrayExecutableCrashed;
}
// Fix fingerprint: old configs may contain "Mozilla/5.0" which xray-core rejects.
// Replace with the correct default at runtime so stale stored configs still work.
if (xrayConfigStr.contains("Mozilla/5.0", Qt::CaseInsensitive)) {
xrayConfigStr.replace("Mozilla/5.0", amnezia::protocols::xray::defaultFingerprint,
Qt::CaseInsensitive);
qDebug() << "XrayProtocol: patched legacy fingerprint to"
<< amnezia::protocols::xray::defaultFingerprint;
}
// Fix inbound listen address: old configs may use "10.33.0.2" which doesn't exist
// until TUN is created. xray must listen on 127.0.0.1 so tun2socks can connect.
if (xrayConfigStr.contains(amnezia::protocols::xray::defaultLocalAddr)) {
xrayConfigStr.replace(amnezia::protocols::xray::defaultLocalAddr,
amnezia::protocols::xray::defaultLocalListenAddr);
qDebug() << "XrayProtocol: patched legacy inbound listen address to 127.0.0.1";
}
return IpcClient::withInterface(
[&](QSharedPointer<IpcInterfaceReplica> iface) {
auto xrayStart = iface->xrayStart(xrayConfigStr);
@@ -188,6 +207,33 @@ ErrorCode XrayProtocol::startTun2Socks()
connect(
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::finished, this,
[this](int exitCode, QProcess::ExitStatus exitStatus) {
// Check stdout for "resource busy" — the TUN device was not yet released
// by the previous tun2socks instance. Retry after a short delay.
bool resourceBusy = false;
if (m_tun2socksProcess) {
auto readOut = m_tun2socksProcess->readAllStandardOutput();
if (readOut.waitForFinished()) {
resourceBusy = readOut.returnValue().contains("resource busy");
}
}
if (resourceBusy && m_tun2socksRetryCount < maxTun2SocksRetries) {
m_tun2socksRetryCount++;
qWarning() << QString("Tun2socks: TUN resource busy, retrying (%1/%2) in %3ms...")
.arg(m_tun2socksRetryCount)
.arg(maxTun2SocksRetries)
.arg(tun2socksRetryDelayMs);
QTimer::singleShot(tun2socksRetryDelayMs, this, [this]() {
if (ErrorCode err = startTun2Socks(); err != ErrorCode::NoError) {
stop();
setLastError(err);
}
});
return;
}
m_tun2socksRetryCount = 0;
if (exitStatus == QProcess::ExitStatus::CrashExit) {
qCritical() << "Tun2socks process crashed!";
} else {

View File

@@ -35,6 +35,9 @@ private:
int m_socksPort = 10808;
QSharedPointer<IpcProcessInterfaceReplica> m_tun2socksProcess;
int m_tun2socksRetryCount = 0;
static constexpr int maxTun2SocksRetries = 5;
static constexpr int tun2socksRetryDelayMs = 400;
};
#endif // XRAYPROTOCOL_H

View File

@@ -2,15 +2,16 @@
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QUuid>
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/utils/api/apiEnums.h"
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "core/models/serverConfig.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/networkUtilities.h"
using namespace amnezia;
@@ -450,4 +451,12 @@ void SecureAppSettingsRepository::setInstallationUuid(const QString &uuid)
m_settings->setValue("Conf/installationUuid", uuid);
}
QByteArray SecureAppSettingsRepository::xraySavedConfigs() const
{
return value("Xray/savedConfigs").toByteArray();
}
void SecureAppSettingsRepository::setXraySavedConfigs(const QByteArray &data)
{
setValue("Xray/savedConfigs", data);
}

View File

@@ -92,6 +92,9 @@ public:
QString nextAvailableServerName() const;
QByteArray xraySavedConfigs() const;
void setXraySavedConfigs(const QByteArray &data);
signals:
void appLanguageChanged(QLocale locale);
void allowedDnsServersChanged(const QStringList &servers);

View File

@@ -1,26 +1,44 @@
#include "secureServersRepository.h"
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonValue>
#include <QUuid>
#include "core/utils/api/apiEnums.h"
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "core/models/serverConfig.h"
#include "core/models/containerConfig.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
SecureServersRepository::SecureServersRepository(SecureQSettings* settings, QObject *parent)
using namespace amnezia;
namespace {
QString readStorageServerId(const QJsonObject &json)
{
return json.value(QString(configKey::storageServerId)).toString().trimmed();
}
QJsonObject withoutStorageServerId(const QJsonObject &json)
{
QJsonObject o = json;
o.remove(QString(configKey::storageServerId));
return o;
}
QJsonObject embedStorageServerId(const QString &serverId, const QJsonObject &payloadSansId)
{
QJsonObject o = payloadSansId;
o.insert(QString(configKey::storageServerId), serverId);
return o;
}
} // namespace
SecureServersRepository::SecureServersRepository(SecureQSettings *settings, QObject *parent)
: QObject(parent), m_settings(settings)
{
QJsonArray arr = QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array();
for (const QJsonValue &val : arr) {
m_servers.append(ServerConfig::fromJson(val.toObject()));
}
m_defaultServerIndex = value("Servers/defaultServerIndex", 0).toInt();
loadFromStorage();
persistDefaultServerFields();
}
QVariant SecureServersRepository::value(const QString &key, const QVariant &defaultValue) const
@@ -33,216 +51,322 @@ void SecureServersRepository::setValue(const QString &key, const QVariant &value
m_settings->setValue(key, value);
}
void SecureServersRepository::clearServerStateMaps()
{
m_serverJsonById.clear();
m_orderedServerIds.clear();
}
QString SecureServersRepository::normalizedOrGeneratedServerId(const QString &candidateId) const
{
const QString trimmed = candidateId.trimmed();
if (!trimmed.isEmpty() && !m_serverJsonById.contains(trimmed)) {
return trimmed;
}
return QUuid::createUuid().toString(QUuid::WithoutBraces);
}
void SecureServersRepository::updateDefaultServerFromStorage()
{
const QString storedDefaultId = value(QStringLiteral("Servers/defaultServerId"), QString()).toString();
if (!storedDefaultId.isEmpty() && m_serverJsonById.contains(storedDefaultId)) {
m_defaultServerId = storedDefaultId;
return;
}
const int storedDefaultIndex = value("Servers/defaultServerIndex", 0).toInt();
if (storedDefaultIndex >= 0 && storedDefaultIndex < m_orderedServerIds.size()) {
m_defaultServerId = m_orderedServerIds.at(storedDefaultIndex);
return;
}
if (!m_orderedServerIds.isEmpty()) {
m_defaultServerId = m_orderedServerIds.first();
return;
}
m_defaultServerId.clear();
}
void SecureServersRepository::persistDefaultServerFields()
{
if (m_orderedServerIds.isEmpty()) {
m_defaultServerId.clear();
} else if (!m_orderedServerIds.contains(m_defaultServerId)) {
m_defaultServerId = m_orderedServerIds.first();
}
setValue("Servers/defaultServerId", m_defaultServerId);
}
void SecureServersRepository::loadFromStorage()
{
clearServerStateMaps();
const QJsonArray serversArray =
QJsonDocument::fromJson(value(QStringLiteral("Servers/serversList"), QByteArray()).toByteArray())
.array();
for (int i = 0; i < serversArray.size(); ++i) {
const QJsonObject json = serversArray.at(i).toObject();
const QString candidateId = readStorageServerId(json);
const QString serverId = normalizedOrGeneratedServerId(candidateId);
const QJsonObject strippedJson = withoutStorageServerId(json);
const serverConfigUtils::ConfigType kind = serverConfigUtils::configTypeFromJson(strippedJson);
if (m_serverJsonById.contains(serverId) || kind == serverConfigUtils::ConfigType::Invalid) {
continue;
}
m_serverJsonById.insert(serverId, embedStorageServerId(serverId, strippedJson));
m_orderedServerIds.append(serverId);
}
updateDefaultServerFromStorage();
}
void SecureServersRepository::syncToStorage()
{
QJsonArray arr;
for (const ServerConfig &cfg : m_servers) {
arr.append(cfg.toJson());
QJsonArray serversArray;
for (const QString &serverId : m_orderedServerIds) {
if (!m_serverJsonById.contains(serverId)) {
continue;
}
serversArray.append(m_serverJsonById.value(serverId));
}
setValue("Servers/serversList", QJsonDocument(arr).toJson());
setValue("Servers/serversList", QJsonDocument(serversArray).toJson());
persistDefaultServerFields();
}
void SecureServersRepository::invalidateCache()
{
m_servers.clear();
QJsonArray arr = QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array();
for (const QJsonValue &val : arr) {
m_servers.append(ServerConfig::fromJson(val.toObject()));
}
m_defaultServerIndex = value("Servers/defaultServerIndex", 0).toInt();
loadFromStorage();
}
void SecureServersRepository::setServersArray(const QJsonArray &servers)
void SecureServersRepository::clearServers()
{
m_servers.clear();
for (const QJsonValue &val : servers) {
m_servers.append(ServerConfig::fromJson(val.toObject()));
}
clearServerStateMaps();
m_defaultServerId.clear();
syncToStorage();
}
void SecureServersRepository::addServer(const ServerConfig &server)
QString SecureServersRepository::addServer(const QString &serverId, const QJsonObject &serverJson, serverConfigUtils::ConfigType kind)
{
m_servers.append(server);
const QString id = normalizedOrGeneratedServerId(serverId);
if (m_serverJsonById.contains(id) || kind == serverConfigUtils::ConfigType::Invalid) {
return id;
}
const QJsonObject strippedJson = withoutStorageServerId(serverJson);
if (serverConfigUtils::configTypeFromJson(strippedJson) != kind) {
return id;
}
m_serverJsonById.insert(id, embedStorageServerId(id, strippedJson));
m_orderedServerIds.append(id);
if (m_defaultServerId.isEmpty()) {
m_defaultServerId = id;
}
syncToStorage();
emit serverAdded(server);
emit serverAdded(id);
return id;
}
void SecureServersRepository::editServer(int index, const ServerConfig &server)
void SecureServersRepository::editServer(const QString &serverId, const QJsonObject &serverJson, serverConfigUtils::ConfigType kind)
{
if (index < 0 || index >= m_servers.size()) {
if (indexOfServerId(serverId) < 0 || kind == serverConfigUtils::ConfigType::Invalid) {
return;
}
m_servers.replace(index, server);
syncToStorage();
emit serverEdited(index, server);
}
void SecureServersRepository::removeServer(int index)
{
if (index < 0 || index >= m_servers.size()) {
if (!m_serverJsonById.contains(serverId)) {
return;
}
int defaultIndex = m_defaultServerIndex;
m_servers.removeAt(index);
if (defaultIndex == index) {
setDefaultServer(0);
} else if (defaultIndex > index) {
setDefaultServer(defaultIndex - 1);
const QJsonObject oldJson = m_serverJsonById.value(serverId);
const serverConfigUtils::ConfigType oldKind = serverConfigUtils::configTypeFromJson(withoutStorageServerId(oldJson));
m_serverJsonById.remove(serverId);
const QJsonObject strippedNew = withoutStorageServerId(serverJson);
if (serverConfigUtils::configTypeFromJson(strippedNew) != kind) {
const QJsonObject strippedOld = withoutStorageServerId(oldJson);
if (oldKind != serverConfigUtils::ConfigType::Invalid && serverConfigUtils::configTypeFromJson(strippedOld) == oldKind) {
m_serverJsonById.insert(serverId, embedStorageServerId(serverId, strippedOld));
}
return;
}
m_serverJsonById.insert(serverId, embedStorageServerId(serverId, strippedNew));
syncToStorage();
emit serverEdited(serverId);
}
void SecureServersRepository::removeServer(const QString &serverId)
{
const int removedIndex = indexOfServerId(serverId);
if (removedIndex < 0) {
return;
}
if (!m_serverJsonById.contains(serverId)) {
return;
}
if (m_servers.isEmpty()) {
setDefaultServer(0);
const QString previousDefaultId = m_defaultServerId;
const int previousDefaultIndex = defaultServerIndex();
m_serverJsonById.remove(serverId);
m_orderedServerIds.removeAt(removedIndex);
if (m_orderedServerIds.isEmpty()) {
m_defaultServerId.clear();
} else if (m_defaultServerId == serverId) {
const int fallbackIndex = qMin(removedIndex, m_orderedServerIds.size() - 1);
m_defaultServerId = m_orderedServerIds.at(fallbackIndex);
} else if (!m_orderedServerIds.contains(m_defaultServerId)) {
m_defaultServerId = m_orderedServerIds.first();
}
const int newDefaultIndex = defaultServerIndex();
if (previousDefaultId != m_defaultServerId || previousDefaultIndex != newDefaultIndex) {
emit defaultServerChanged(m_defaultServerId);
}
syncToStorage();
emit serverRemoved(index);
emit serverRemoved(serverId, removedIndex);
}
ServerConfig SecureServersRepository::server(int index) const
serverConfigUtils::ConfigType SecureServersRepository::serverKind(const QString &serverId) const
{
if (index < 0 || index >= m_servers.size()) {
return SelfHostedServerConfig{};
const auto it = m_serverJsonById.constFind(serverId);
if (it == m_serverJsonById.constEnd()) {
return serverConfigUtils::ConfigType::Invalid;
}
return m_servers.at(index);
return serverConfigUtils::configTypeFromJson(withoutStorageServerId(it.value()));
}
QVector<ServerConfig> SecureServersRepository::servers() const
std::optional<SelfHostedAdminServerConfig> SecureServersRepository::selfHostedAdminConfig(const QString &serverId) const
{
return m_servers;
const auto it = m_serverJsonById.constFind(serverId);
if (it == m_serverJsonById.constEnd()) {
return std::nullopt;
}
const QJsonObject strippedJson = withoutStorageServerId(it.value());
if (serverConfigUtils::configTypeFromJson(strippedJson) != serverConfigUtils::ConfigType::SelfHostedAdmin) {
return std::nullopt;
}
return SelfHostedAdminServerConfig::fromJson(strippedJson);
}
std::optional<SelfHostedUserServerConfig> SecureServersRepository::selfHostedUserConfig(const QString &serverId) const
{
const auto it = m_serverJsonById.constFind(serverId);
if (it == m_serverJsonById.constEnd()) {
return std::nullopt;
}
const QJsonObject strippedJson = withoutStorageServerId(it.value());
if (serverConfigUtils::configTypeFromJson(strippedJson) != serverConfigUtils::ConfigType::SelfHostedUser) {
return std::nullopt;
}
return SelfHostedUserServerConfig::fromJson(strippedJson);
}
std::optional<NativeServerConfig> SecureServersRepository::nativeConfig(const QString &serverId) const
{
const auto it = m_serverJsonById.constFind(serverId);
if (it == m_serverJsonById.constEnd()) {
return std::nullopt;
}
const QJsonObject strippedJson = withoutStorageServerId(it.value());
if (serverConfigUtils::configTypeFromJson(strippedJson) != serverConfigUtils::ConfigType::Native) {
return std::nullopt;
}
return NativeServerConfig::fromJson(strippedJson);
}
std::optional<ApiV2ServerConfig> SecureServersRepository::apiV2Config(const QString &serverId) const
{
const auto it = m_serverJsonById.constFind(serverId);
if (it == m_serverJsonById.constEnd()) {
return std::nullopt;
}
const QJsonObject strippedJson = withoutStorageServerId(it.value());
if (!serverConfigUtils::isApiV2Subscription(serverConfigUtils::configTypeFromJson(strippedJson))) {
return std::nullopt;
}
return ApiV2ServerConfig::fromJson(strippedJson);
}
std::optional<LegacyApiServerConfig> SecureServersRepository::legacyApiConfig(const QString &serverId) const
{
const auto it = m_serverJsonById.constFind(serverId);
if (it == m_serverJsonById.constEnd()) {
return std::nullopt;
}
const QJsonObject strippedJson = withoutStorageServerId(it.value());
if (!serverConfigUtils::isLegacyApiSubscription(serverConfigUtils::configTypeFromJson(strippedJson))) {
return std::nullopt;
}
return LegacyApiServerConfig::fromJson(strippedJson);
}
int SecureServersRepository::serversCount() const
{
return m_servers.size();
return m_orderedServerIds.size();
}
QString SecureServersRepository::serverIdAt(int index) const
{
if (index < 0 || index >= m_orderedServerIds.size()) {
return QString();
}
return m_orderedServerIds.at(index);
}
QVector<QString> SecureServersRepository::orderedServerIds() const
{
return m_orderedServerIds;
}
int SecureServersRepository::indexOfServerId(const QString &serverId) const
{
return m_orderedServerIds.indexOf(serverId);
}
int SecureServersRepository::defaultServerIndex() const
{
return m_defaultServerIndex;
if (m_orderedServerIds.isEmpty()) {
return 0;
}
const int idx = m_orderedServerIds.indexOf(m_defaultServerId);
return idx >= 0 ? idx : 0;
}
void SecureServersRepository::setDefaultServer(int index)
QString SecureServersRepository::defaultServerId() const
{
if (index < 0) {
return m_defaultServerId;
}
void SecureServersRepository::setDefaultServer(const QString &serverId)
{
if (m_orderedServerIds.isEmpty()) {
return;
}
if (m_servers.size() > 0 && index >= m_servers.size()) {
if (!m_serverJsonById.contains(serverId)) {
return;
}
if (m_servers.isEmpty() && index != 0) {
if (indexOfServerId(serverId) < 0) {
return;
}
if (m_defaultServerIndex == index) {
if (m_defaultServerId == serverId) {
return;
}
m_defaultServerIndex = index;
setValue("Servers/defaultServerIndex", index);
emit defaultServerChanged(index);
}
void SecureServersRepository::setDefaultContainer(int serverIndex, DockerContainer container)
{
ServerConfig config = server(serverIndex);
config.visit([container](auto& arg) {
arg.defaultContainer = container;
});
editServer(serverIndex, config);
}
ContainerConfig SecureServersRepository::containerConfig(int serverIndex, DockerContainer container) const
{
ServerConfig config = server(serverIndex);
return config.containerConfig(container);
}
void SecureServersRepository::setContainerConfig(int serverIndex, DockerContainer container, const ContainerConfig &config)
{
ServerConfig serverConfig = server(serverIndex);
serverConfig.visit([container, &config](auto& arg) {
arg.containers[container] = config;
});
editServer(serverIndex, serverConfig);
}
void SecureServersRepository::clearLastConnectionConfig(int serverIndex, DockerContainer container)
{
ServerConfig serverConfig = server(serverIndex);
ContainerConfig containerCfg = serverConfig.containerConfig(container);
containerCfg.protocolConfig.clearClientConfig();
setContainerConfig(serverIndex, container, containerCfg);
}
ServerCredentials SecureServersRepository::serverCredentials(int index) const
{
ServerConfig config = server(index);
if (config.isSelfHosted()) {
const SelfHostedServerConfig* selfHosted = config.as<SelfHostedServerConfig>();
if (!selfHosted) return ServerCredentials();
auto creds = selfHosted->credentials();
if (creds.has_value()) {
return creds.value();
}
}
return ServerCredentials{};
}
bool SecureServersRepository::hasServerWithVpnKey(const QString &vpnKey) const
{
QString normalizedInput = vpnKey.trimmed();
if (normalizedInput.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) {
normalizedInput = normalizedInput.mid(QStringLiteral("vpn://").size());
}
if (normalizedInput.isEmpty()) {
return false;
}
QVector<ServerConfig> serversList = servers();
for (const ServerConfig& serverConfig : serversList) {
if (serverConfig.isApiV1()) {
const ApiV1ServerConfig* apiV1 = serverConfig.as<ApiV1ServerConfig>();
if (!apiV1) continue;
QString storedKey = apiV1->vpnKey();
if (storedKey.isEmpty()) {
continue;
}
QString normalizedStored = storedKey.trimmed();
if (normalizedStored.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) {
normalizedStored = normalizedStored.mid(QStringLiteral("vpn://").size());
}
if (normalizedInput == normalizedStored) {
return true;
}
} else if (serverConfig.isApiV2()) {
const ApiV2ServerConfig* apiV2 = serverConfig.as<ApiV2ServerConfig>();
if (!apiV2) continue;
QString storedKey = apiV2->vpnKey();
if (storedKey.isEmpty()) {
continue;
}
QString normalizedStored = storedKey.trimmed();
if (normalizedStored.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) {
normalizedStored = normalizedStored.mid(QStringLiteral("vpn://").size());
}
if (normalizedInput == normalizedStored) {
return true;
}
}
}
return false;
}
bool SecureServersRepository::hasServerWithCrc(quint16 crc) const
{
for (const ServerConfig& serverConfig : m_servers) {
if (static_cast<quint16>(serverConfig.crc()) == crc) {
return true;
}
}
return false;
m_defaultServerId = serverId;
persistDefaultServerFields();
emit defaultServerChanged(m_defaultServerId);
}

View File

@@ -1,14 +1,20 @@
#ifndef SECURESERVERSREPOSITORY_H
#define SECURESERVERSREPOSITORY_H
#include <QHash>
#include <QJsonObject>
#include <QObject>
#include <QVector>
#include <QJsonArray>
#include <QJsonDocument>
#include <QtGlobal>
#include <optional>
#include "core/models/serverConfig.h"
#include "core/models/selfhosted/selfHostedAdminServerConfig.h"
#include "core/models/selfhosted/selfHostedUserServerConfig.h"
#include "core/models/selfhosted/nativeServerConfig.h"
#include "core/models/api/apiV2ServerConfig.h"
#include "core/models/api/legacyApiServerConfig.h"
#include "core/models/containerConfig.h"
#include "core/utils/serverConfigUtils.h"
#include "secureQSettings.h"
using namespace amnezia;
@@ -18,47 +24,57 @@ class SecureServersRepository : public QObject
Q_OBJECT
public:
explicit SecureServersRepository(SecureQSettings* settings, QObject *parent = nullptr);
explicit SecureServersRepository(SecureQSettings *settings, QObject *parent = nullptr);
QString addServer(const QString &serverId, const QJsonObject &serverJson, serverConfigUtils::ConfigType kind);
void editServer(const QString &serverId, const QJsonObject &serverJson, serverConfigUtils::ConfigType kind);
void removeServer(const QString &serverId);
serverConfigUtils::ConfigType serverKind(const QString &serverId) const;
std::optional<SelfHostedAdminServerConfig> selfHostedAdminConfig(const QString &serverId) const;
std::optional<SelfHostedUserServerConfig> selfHostedUserConfig(const QString &serverId) const;
std::optional<NativeServerConfig> nativeConfig(const QString &serverId) const;
std::optional<ApiV2ServerConfig> apiV2Config(const QString &serverId) const;
std::optional<LegacyApiServerConfig> legacyApiConfig(const QString &serverId) const;
void addServer(const ServerConfig &server);
void editServer(int index, const ServerConfig &server);
void removeServer(int index);
ServerConfig server(int index) const;
QVector<ServerConfig> servers() const;
int serversCount() const;
int indexOfServerId(const QString &serverId) const;
QString serverIdAt(int index) const;
QVector<QString> orderedServerIds() const;
int defaultServerIndex() const;
void setDefaultServer(int index);
QString defaultServerId() const;
void setDefaultServer(const QString &serverId);
void setDefaultContainer(int serverIndex, DockerContainer container);
ContainerConfig containerConfig(int serverIndex, DockerContainer container) const;
void setContainerConfig(int serverIndex, DockerContainer container, const ContainerConfig &config);
void clearLastConnectionConfig(int serverIndex, DockerContainer container);
ServerCredentials serverCredentials(int index) const;
bool hasServerWithVpnKey(const QString &vpnKey) const;
bool hasServerWithCrc(quint16 crc) const;
void setServersArray(const QJsonArray &servers);
void clearServers();
void invalidateCache();
signals:
void serverAdded(ServerConfig config);
void serverEdited(int index, ServerConfig config);
void serverRemoved(int index);
void defaultServerChanged(int index);
void serverAdded(const QString &serverId);
void serverEdited(const QString &serverId);
void serverRemoved(const QString &serverId, int removedIndex);
void defaultServerChanged(const QString &defaultServerId);
private:
void loadFromStorage();
void updateDefaultServerFromStorage();
void persistDefaultServerFields();
QString normalizedOrGeneratedServerId(const QString &candidateId) const;
void syncToStorage();
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
QVariant value(const QString &key, const QVariant &defaultValue) const;
void setValue(const QString &key, const QVariant &value);
SecureQSettings* m_settings;
QVector<ServerConfig> m_servers;
int m_defaultServerIndex = 0;
void clearServerStateMaps();
SecureQSettings *m_settings;
QHash<QString, QJsonObject> m_serverJsonById;
QVector<QString> m_orderedServerIds;
QString m_defaultServerId;
};
#endif // SECURESERVERSREPOSITORY_H

View File

@@ -1,25 +0,0 @@
#ifndef APIENUMS_H
#define APIENUMS_H
namespace apiDefs
{
enum ConfigType {
AmneziaFreeV2 = 0,
AmneziaFreeV3,
AmneziaPremiumV1,
AmneziaPremiumV2,
AmneziaTrialV2,
SelfHosted,
ExternalPremium,
ExternalTrial
};
enum ConfigSource {
Telegram = 1,
AmneziaGateway
};
}
#endif // APIENUMS_H

View File

@@ -1,5 +1,6 @@
#include "apiUtils.h"
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/configKeys.h"
#include <QDateTime>
#include <QJsonDocument>
@@ -75,63 +76,6 @@ bool apiUtils::isSubscriptionExpiringSoon(const QString &subscriptionEndDate, in
return endDate <= nowUtc.addDays(withinDays);
}
bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject)
{
auto configVersion = serverConfigObject.value(configKey::configVersion).toInt();
switch (configVersion) {
case apiDefs::ConfigSource::Telegram: return true;
case apiDefs::ConfigSource::AmneziaGateway: return true;
default: return false;
}
}
apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObject)
{
auto configVersion = serverConfigObject.value(configKey::configVersion).toInt();
switch (configVersion) {
case apiDefs::ConfigSource::Telegram: {
constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
auto apiEndpoint = serverConfigObject.value(apiDefs::key::apiEndpoint).toString();
if (apiEndpoint.contains(premiumV1Endpoint)) {
return apiDefs::ConfigType::AmneziaPremiumV1;
} else if (apiEndpoint.contains(freeV2Endpoint)) {
return apiDefs::ConfigType::AmneziaFreeV2;
}
};
case apiDefs::ConfigSource::AmneziaGateway: {
constexpr QLatin1String servicePremium("amnezia-premium");
constexpr QLatin1String serviceFree("amnezia-free");
constexpr QLatin1String serviceExternalPremium("external-premium");
constexpr QLatin1String serviceExternalTrial("external-trial");
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
if (serviceType == servicePremium) {
return apiDefs::ConfigType::AmneziaPremiumV2;
} else if (serviceType == serviceFree) {
return apiDefs::ConfigType::AmneziaFreeV3;
} else if (serviceType == serviceExternalPremium) {
return apiDefs::ConfigType::ExternalPremium;
} else if (serviceType == serviceExternalTrial) {
return apiDefs::ConfigType::ExternalTrial;
}
}
default: {
return apiDefs::ConfigType::SelfHosted;
}
};
}
apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigObject)
{
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(configKey::configVersion).toInt());
}
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
const QByteArray &responseBody)
@@ -159,10 +103,6 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
return amnezia::ErrorCode::ApiUpdateRequestError;
}
qDebug() << QString::fromUtf8(responseBody);
qDebug() << replyError;
qDebug() << httpStatusCode;
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
if (jsonDoc.isObject()) {
QJsonObject jsonObj = jsonDoc.object();
@@ -197,14 +137,14 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
{
static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
apiDefs::ConfigType::ExternalPremium, apiDefs::ConfigType::ExternalTrial };
return premiumTypes.contains(getConfigType(serverConfigObject));
static const QSet<serverConfigUtils::ConfigType> premiumTypes = { serverConfigUtils::ConfigType::AmneziaPremiumV1, serverConfigUtils::ConfigType::AmneziaPremiumV2,
serverConfigUtils::ConfigType::ExternalPremium };
return premiumTypes.contains(serverConfigUtils::configTypeFromJson(serverConfigObject));
}
QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
{
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) {
if (serverConfigUtils::configTypeFromJson(serverConfigObject) != serverConfigUtils::ConfigType::AmneziaPremiumV1) {
return {};
}
@@ -242,9 +182,8 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
{
auto configType = apiUtils::getConfigType(serverConfigObject);
if (configType != apiDefs::ConfigType::AmneziaPremiumV2 && configType != apiDefs::ConfigType::ExternalPremium
&& configType != apiDefs::ConfigType::ExternalTrial) {
auto configType = serverConfigUtils::configTypeFromJson(serverConfigObject);
if (configType != serverConfigUtils::ConfigType::AmneziaPremiumV2 && configType != serverConfigUtils::ConfigType::ExternalPremium) {
return {};
}

View File

@@ -4,7 +4,7 @@
#include <QNetworkReply>
#include <QObject>
#include "core/utils/api/apiEnums.h"
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "core/utils/errorCodes.h"
@@ -13,17 +13,12 @@
namespace apiUtils
{
bool isServerFromApi(const QJsonObject &serverConfigObject);
bool isSubscriptionExpired(const QString &subscriptionEndDate);
bool isSubscriptionExpiringSoon(const QString &subscriptionEndDate, int withinDays = 30);
bool isPremiumServer(const QJsonObject &serverConfigObject);
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
const QByteArray &responseBody);

View File

@@ -3,9 +3,9 @@
namespace apiDefs
{
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
}
constexpr int requestTimeoutMsecs = 12 * 1000; // 12 secs
} // namespace apiDefs
#endif // APICONSTANTS_H

View File

@@ -2,7 +2,6 @@
#define APIKEYS_H
#include <QLatin1String>
#include "core/utils/api/apiEnums.h"
namespace apiDefs
{
@@ -82,7 +81,7 @@ namespace apiDefs
constexpr QLatin1String expiresAt("expires_at");
constexpr QLatin1String isConnectEvent("is_connect_event");
constexpr QLatin1String certificate("certificate");
}
}
} // namespace key
} // namespace apiDefs
#endif // APIKEYS_H

View File

@@ -18,6 +18,7 @@ namespace amnezia
constexpr QLatin1String serverIndex("serverIndex");
constexpr QLatin1String description("description");
constexpr QLatin1String displayName("displayName");
constexpr QLatin1String name("name");
constexpr QLatin1String cert("cert");
constexpr QLatin1String accessToken("api_key");
@@ -92,6 +93,8 @@ namespace amnezia
constexpr QLatin1String xray("xray");
constexpr QLatin1String ssxray("ssxray");
constexpr QLatin1String socks5proxy("socks5proxy");
constexpr QLatin1String mtproxy("mtproxy");
constexpr QLatin1String telemt("telemt");
constexpr QLatin1String splitTunnelSites("splitTunnelSites");
constexpr QLatin1String splitTunnelType("splitTunnelType");
@@ -121,6 +124,78 @@ namespace amnezia
constexpr QLatin1String latestHandshake("latestHandshake");
constexpr QLatin1String dataReceived("dataReceived");
constexpr QLatin1String dataSent("dataSent");
constexpr QLatin1String storageServerId("storageServerId");
// ── Xray-specific keys ────────────────────────────────────────
// Security
constexpr QLatin1String xraySecurity("xray_security"); // none | tls | reality
constexpr QLatin1String xrayFlow("xray_flow"); // "" | xtls-rprx-vision | xtls-rprx-vision-udp443
constexpr QLatin1String xrayFingerprint("xray_fingerprint"); // Mozilla/5.0 | chrome | firefox | ...
constexpr QLatin1String xraySni("xray_sni"); // Server Name (SNI)
constexpr QLatin1String xrayAlpn("xray_alpn"); // HTTP/2 | HTTP/1.1 | HTTP/2,HTTP/1.1
// Transport — common
constexpr QLatin1String xrayTransport("xray_transport"); // raw | xhttp | mkcp
// Transport — XHTTP
constexpr QLatin1String xhttpMode("xhttp_mode"); // Auto | Packet-up | Stream-up | Stream-one
constexpr QLatin1String xhttpHost("xhttp_host");
constexpr QLatin1String xhttpPath("xhttp_path");
constexpr QLatin1String xhttpHeadersTemplate("xhttp_headers_template"); // HTTP | None
constexpr QLatin1String xhttpUplinkMethod("xhttp_uplink_method"); // POST | PUT | PATCH
constexpr QLatin1String xhttpDisableGrpc("xhttp_disable_grpc"); // bool
constexpr QLatin1String xhttpDisableSse("xhttp_disable_sse"); // bool
// Transport — XHTTP Session & Sequence
constexpr QLatin1String xhttpSessionPlacement("xhttp_session_placement"); // Path | Header | Cookie | None
constexpr QLatin1String xhttpSessionKey("xhttp_session_key");
constexpr QLatin1String xhttpSeqPlacement("xhttp_seq_placement");
constexpr QLatin1String xhttpSeqKey("xhttp_seq_key");
constexpr QLatin1String xhttpUplinkDataPlacement("xhttp_uplink_data_placement"); // Body | Query
constexpr QLatin1String xhttpUplinkDataKey("xhttp_uplink_data_key");
// Transport — XHTTP Traffic Shaping
constexpr QLatin1String xhttpUplinkChunkSize("xhttp_uplink_chunk_size");
constexpr QLatin1String xhttpScMaxBufferedPosts("xhttp_sc_max_buffered_posts");
constexpr QLatin1String xhttpScMaxEachPostBytesMin("xhttp_sc_max_each_post_bytes_min");
constexpr QLatin1String xhttpScMaxEachPostBytesMax("xhttp_sc_max_each_post_bytes_max");
constexpr QLatin1String xhttpScMinPostsIntervalMsMin("xhttp_sc_min_posts_interval_ms_min");
constexpr QLatin1String xhttpScMinPostsIntervalMsMax("xhttp_sc_min_posts_interval_ms_max");
constexpr QLatin1String xhttpScStreamUpServerSecsMin("xhttp_sc_stream_up_server_secs_min");
constexpr QLatin1String xhttpScStreamUpServerSecsMax("xhttp_sc_stream_up_server_secs_max");
// Transport — mKCP
constexpr QLatin1String mkcpTti("mkcp_tti");
constexpr QLatin1String mkcpUplinkCapacity("mkcp_uplink_capacity");
constexpr QLatin1String mkcpDownlinkCapacity("mkcp_downlink_capacity");
constexpr QLatin1String mkcpReadBufferSize("mkcp_read_buffer_size");
constexpr QLatin1String mkcpWriteBufferSize("mkcp_write_buffer_size");
constexpr QLatin1String mkcpCongestion("mkcp_congestion"); // bool
// xPadding
constexpr QLatin1String xPaddingBytesMin("xpadding_bytes_min");
constexpr QLatin1String xPaddingBytesMax("xpadding_bytes_max");
constexpr QLatin1String xPaddingObfsMode("xpadding_obfs_mode"); // bool
constexpr QLatin1String xPaddingKey("xpadding_key");
constexpr QLatin1String xPaddingHeader("xpadding_header");
constexpr QLatin1String xPaddingPlacement("xpadding_placement"); // Cookie | Header | Query | Body
constexpr QLatin1String xPaddingMethod("xpadding_method"); // Repeat-x | Random | Zero
// xmux
constexpr QLatin1String xmuxEnabled("xmux_enabled"); // bool
constexpr QLatin1String xmuxMaxConcurrencyMin("xmux_max_concurrency_min");
constexpr QLatin1String xmuxMaxConcurrencyMax("xmux_max_concurrency_max");
constexpr QLatin1String xmuxMaxConnectionsMin("xmux_max_connections_min");
constexpr QLatin1String xmuxMaxConnectionsMax("xmux_max_connections_max");
constexpr QLatin1String xmuxCMaxReuseTimesMin("xmux_c_max_reuse_times_min");
constexpr QLatin1String xmuxCMaxReuseTimesMax("xmux_c_max_reuse_times_max");
constexpr QLatin1String xmuxHMaxRequestTimesMin("xmux_h_max_request_times_min");
constexpr QLatin1String xmuxHMaxRequestTimesMax("xmux_h_max_request_times_max");
constexpr QLatin1String xmuxHMaxReusableSecsMin("xmux_h_max_reusable_secs_min");
constexpr QLatin1String xmuxHMaxReusableSecsMax("xmux_h_max_reusable_secs_max");
constexpr QLatin1String xmuxHKeepAlivePeriod("xmux_h_keep_alive_period");
}
}

View File

@@ -3,6 +3,7 @@
namespace amnezia
{
namespace protocols
{
@@ -57,6 +58,40 @@ namespace amnezia
constexpr char defaultPort[] = "443";
constexpr char defaultLocalProxyPort[] = "10808";
constexpr char defaultLocalAddr[] = "10.33.0.2";
constexpr char defaultLocalListenAddr[] = "127.0.0.1";
constexpr char defaultSecurity[] = "reality";
constexpr char defaultFlow[] = "xtls-rprx-vision";
constexpr char defaultTransport[] = "raw";
constexpr char defaultFingerprint[] = "chrome";
constexpr char defaultSni[] = "cdn.example.com";
constexpr char defaultAlpn[] = "HTTP/2";
constexpr char defaultXhttpMode[] = "Auto";
constexpr char defaultXhttpHeadersTemplate[] = "HTTP";
constexpr char defaultXhttpUplinkMethod[] = "POST";
constexpr char defaultXhttpSessionPlacement[] = "Path";
constexpr char defaultXhttpSessionKey[] = "Path";
constexpr char defaultXhttpSeqPlacement[] = "Path";
constexpr char defaultXhttpUplinkDataPlacement[] = "Body";
constexpr char defaultXhttpHost[] = "www.googletagmanager.com";
constexpr char defaultXhttpUplinkChunkSize[] = "0";
constexpr char defaultXhttpScMaxEachPostBytesMin[] = "1";
constexpr char defaultXhttpScMaxEachPostBytesMax[] = "100";
constexpr char defaultXhttpScMinPostsIntervalMsMin[] = "100";
constexpr char defaultXhttpScMinPostsIntervalMsMax[] = "800";
constexpr char defaultXhttpScStreamUpServerSecsMin[] = "1";
constexpr char defaultXhttpScStreamUpServerSecsMax[] = "100";
constexpr char defaultXPaddingPlacement[] = "Cookie";
constexpr char defaultXPaddingMethod[] = "Repeat-x";
constexpr char defaultMkcpTti[] = "50";
constexpr char defaultMkcpUplinkCapacity[] = "5";
constexpr char defaultMkcpDownlinkCapacity[] = "20";
constexpr char defaultMkcpReadBufferSize[] = "2";
constexpr char defaultMkcpWriteBufferSize[] = "2";
constexpr char outbounds[] = "outbounds";
constexpr char inbounds[] = "inbounds";
@@ -174,9 +209,71 @@ namespace amnezia
constexpr char proxyConfigPath[] = "/usr/local/3proxy/conf/3proxy.cfg";
}
namespace mtProxy
{
constexpr char secretKey[] = "mtproxy_secret";
constexpr char tagKey[] = "mtproxy_tag";
constexpr char tgLinkKey[] = "mtproxy_tg_link";
constexpr char tmeLinkKey[] = "mtproxy_tme_link";
constexpr char isEnabledKey[] = "mtproxy_is_enabled";
constexpr char publicHostKey[] = "mtproxy_public_host";
constexpr char transportModeKey[] = "mtproxy_transport_mode";
constexpr char tlsDomainKey[] = "mtproxy_tls_domain";
constexpr char additionalSecretsKey[] = "mtproxy_additional_secrets";
constexpr char workersKey[] = "mtproxy_workers";
constexpr char workersModeKey[] = "mtproxy_workers_mode";
constexpr char natEnabledKey[] = "mtproxy_nat_enabled";
constexpr char natInternalIpKey[] = "mtproxy_nat_internal_ip";
constexpr char natExternalIpKey[] = "mtproxy_nat_external_ip";
constexpr char transportModeStandard[] = "standard";
constexpr char transportModeFakeTLS[] = "faketls";
constexpr char workersModeAuto[] = "auto";
constexpr char workersModeManual[] = "manual";
constexpr char defaultPort[] = "443";
constexpr char defaultWorkers[] = "2";
constexpr int maxWorkers = 32;
constexpr int botTagHexLength = 32;
constexpr char defaultTlsDomain[] = "googletagmanager.com";
}
namespace telemt
{
constexpr char secretKey[] = "telemt_secret";
constexpr char tagKey[] = "telemt_tag";
constexpr char tgLinkKey[] = "telemt_tg_link";
constexpr char tmeLinkKey[] = "telemt_tme_link";
constexpr char isEnabledKey[] = "telemt_is_enabled";
constexpr char publicHostKey[] = "telemt_public_host";
constexpr char transportModeKey[] = "telemt_transport_mode";
constexpr char tlsDomainKey[] = "telemt_tls_domain";
constexpr char maskEnabledKey[] = "telemt_mask_enabled";
constexpr char tlsEmulationKey[] = "telemt_tls_emulation";
constexpr char useMiddleProxyKey[] = "telemt_use_middle_proxy";
constexpr char userNameKey[] = "telemt_user_name";
// Stored for UI only (Telemt server ignores these; same controls as MTProxy page)
constexpr char additionalSecretsKey[] = "telemt_additional_secrets";
constexpr char workersKey[] = "telemt_workers";
constexpr char workersModeKey[] = "telemt_workers_mode";
constexpr char natEnabledKey[] = "telemt_nat_enabled";
constexpr char natInternalIpKey[] = "telemt_nat_internal_ip";
constexpr char natExternalIpKey[] = "telemt_nat_external_ip";
constexpr char transportModeStandard[] = "standard";
constexpr char transportModeFakeTLS[] = "faketls";
constexpr char defaultPort[] = "443";
constexpr char defaultTlsDomain[] = "googletagmanager.com";
constexpr char defaultUserName[] = "amnezia";
constexpr char defaultWorkers[] = "2";
constexpr char workersModeAuto[] = "auto";
constexpr char workersModeManual[] = "manual";
constexpr int maxWorkers = 32;
}
} // namespace protocols
}
#endif // PROTOCOLCONSTANTS_H

View File

@@ -23,7 +23,9 @@ namespace amnezia
TorWebSite,
Dns,
Sftp,
Socks5Proxy
Socks5Proxy,
MtProxy,
Telemt,
};
Q_ENUM_NS(DockerContainer)
} // namespace ContainerEnumNS

View File

@@ -72,7 +72,10 @@ QMap<DockerContainer, QString> ContainerUtils::containerHumanNames()
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
{ DockerContainer::Dns, QObject::tr("AmneziaDNS") },
{ DockerContainer::Sftp, QObject::tr("SFTP file sharing service") },
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") },
{ DockerContainer::MtProxy, QObject::tr("MTProxy (Telegram)") },
{ DockerContainer::Telemt, QObject::tr("Telemt (Telegram)") },
};
}
QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
@@ -102,7 +105,12 @@ QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
{ DockerContainer::Sftp,
QObject::tr("Create a file vault on your server to securely store and transfer files.") },
{ DockerContainer::Socks5Proxy,
QObject::tr("") } };
QObject::tr("") },
{ DockerContainer::MtProxy,
QObject::tr("Telegram MTProto proxy server") },
{ DockerContainer::Telemt,
QObject::tr("Telegram MTProto proxy (Telemt, Rust)") },
};
}
QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
@@ -172,7 +180,15 @@ QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
"You will be able to access it using\n FileZilla or other SFTP clients, "
"as well as mount the disk on your device to access\n it directly from your device.\n\n"
"For more detailed information, you can\n find it in the support section under \"Create SFTP file storage.\" ") },
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") }
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") },
{ DockerContainer::MtProxy,
QObject::tr("Telegram MTProto proxy server. "
"Allows Telegram clients to connect through your server "
"using the MTProto protocol. Supports FakeTLS mode for "
"bypassing DPI-based blocking.") },
{ DockerContainer::Telemt,
QObject::tr("Telegram MTProto proxy powered by Telemt (Rust). "
"Supports secure and TLS fronting modes with optional traffic masking.") },
};
}
@@ -197,6 +213,8 @@ Proto ContainerUtils::defaultProtocol(DockerContainer c)
case DockerContainer::Dns: return Proto::Dns;
case DockerContainer::Sftp: return Proto::Sftp;
case DockerContainer::Socks5Proxy: return Proto::Socks5Proxy;
case DockerContainer::MtProxy: return Proto::MtProxy;
case DockerContainer::Telemt: return Proto::Telemt;
default: return Proto::Unknown;
}
}
@@ -224,6 +242,8 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::SSXray: return true;
case DockerContainer::MtProxy: return true;
case DockerContainer::Telemt: return true;
default:
return false;
}
@@ -237,7 +257,8 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::SSXray: return true;
return false;
case DockerContainer::MtProxy: return true;
case DockerContainer::Telemt: return true;
default:
return false;
}
@@ -256,6 +277,8 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::SSXray: return true;
case DockerContainer::MtProxy: return true;
case DockerContainer::Telemt: return true;
default: return false;
}
@@ -318,6 +341,8 @@ bool ContainerUtils::isShareable(DockerContainer container)
case DockerContainer::Dns: return false;
case DockerContainer::Sftp: return false;
case DockerContainer::Socks5Proxy: return false;
case DockerContainer::MtProxy: return false;
case DockerContainer::Telemt: return false;
default: return true;
}
}
@@ -346,8 +371,10 @@ int ContainerUtils::installPageOrder(DockerContainer container)
case DockerContainer::Xray: return 3;
case DockerContainer::Ipsec: return 7;
case DockerContainer::SSXray: return 8;
case DockerContainer::MtProxy:
case DockerContainer::Telemt:
return 20;
default: return 0;
}
}

View File

@@ -71,10 +71,11 @@ namespace amnezia
// import and install errors
ImportInvalidConfigError = 900,
ImportBackupFileUseRestoreInstead = 903,
RestoreBackupInvalidError = 904,
ImportOpenConfigError = 901,
NoInstalledContainersError = 902,
ImportBackupFileUseRestoreInstead = 903,
RestoreBackupInvalidError = 904,
LegacyApiV1NotSupportedError = 905,
// Android errors
AndroidError = 1000,

View File

@@ -59,6 +59,7 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
case (ErrorCode::ImportBackupFileUseRestoreInstead): errorMessage = QObject::tr("Backup files cannot be imported here. Use 'Restore from backup' instead."); break;
case (ErrorCode::RestoreBackupInvalidError): errorMessage = QObject::tr("Backup file is corrupted or has invalid format"); break;
case (ErrorCode::LegacyApiV1NotSupportedError): errorMessage = QObject::tr("This legacy Amnezia subscription format is no longer supported"); break;
case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break;
case (ErrorCode::NoInstalledContainersError): errorMessage = QObject::tr("VPN Protocols is not installed.\n Please install VPN container at first"); break;

View File

@@ -30,7 +30,9 @@ namespace amnezia
TorWebSite,
Dns,
Sftp,
Socks5Proxy
Socks5Proxy,
MtProxy,
Telemt,
};
Q_ENUM_NS(Proto)

View File

@@ -9,7 +9,6 @@
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
@@ -20,6 +19,8 @@
#include "core/models/protocols/xrayProtocolConfig.h"
#include "core/models/protocols/sftpProtocolConfig.h"
#include "core/models/protocols/socks5ProxyProtocolConfig.h"
#include "core/models/protocols/mtProxyProtocolConfig.h"
#include "core/models/protocols/telemtProtocolConfig.h"
using namespace amnezia;
using namespace ProtocolUtils;
@@ -38,6 +39,8 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
case DockerContainer::Dns: return QLatin1String("dns");
case DockerContainer::Sftp: return QLatin1String("sftp");
case DockerContainer::Socks5Proxy: return QLatin1String("socks5_proxy");
case DockerContainer::MtProxy: return QLatin1String("mtproxy");
case DockerContainer::Telemt: return QLatin1String("telemt");
default: return QString();
}
}
@@ -76,7 +79,6 @@ QString amnezia::scriptName(ProtocolScriptType type)
QString amnezia::scriptName(ClientScriptType type)
{
switch (type) {
case ClientScriptType::linux_installer: return QLatin1String("linux_installer.sh");
case ClientScriptType::mac_installer: return QLatin1String("mac_installer.sh");
default: return QString();
}
@@ -285,6 +287,86 @@ amnezia::ScriptVars amnezia::genSocks5ProxyVars(const ContainerConfig &container
return vars;
}
amnezia::ScriptVars amnezia::genMtProxyVars(const ContainerConfig &containerConfig) {
ScriptVars vars;
if (auto *mtProxyProtocolConfig = containerConfig.getMtProxyProtocolConfig()) {
const MtProxyProtocolConfig &c = *mtProxyProtocolConfig;
vars.append({{"$MTPROXY_PORT", c.port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : c.port}});
vars.append({{"$MTPROXY_SECRET", c.secret}});
vars.append({{"$MTPROXY_TAG", c.tag}});
vars.append({{"$MTPROXY_TRANSPORT_MODE",
c.transportMode.isEmpty() ? QString(protocols::mtProxy::transportModeStandard)
: c.transportMode}});
QString tlsDomain = c.tlsDomain;
if (tlsDomain.isEmpty()) {
tlsDomain = QString(protocols::mtProxy::defaultTlsDomain);
}
vars.append({{"$MTPROXY_TLS_DOMAIN", tlsDomain}});
vars.append({{"$MTPROXY_PUBLIC_HOST", c.publicHost}});
QStringList additionalList;
for (const QString &s: c.additionalSecrets) {
if (!s.isEmpty()) {
additionalList << s;
}
}
vars.append({{"$MTPROXY_ADDITIONAL_SECRETS", additionalList.join(QLatin1Char(','))}});
const QString workersMode = c.workersMode.isEmpty() ? QString(protocols::mtProxy::workersModeAuto)
: c.workersMode;
QString workers;
if (workersMode == QLatin1String(protocols::mtProxy::workersModeManual)) {
workers = c.workers.isEmpty() ? QString(protocols::mtProxy::defaultWorkers) : c.workers;
} else {
const QString transportMode =
c.transportMode.isEmpty() ? QString(protocols::mtProxy::transportModeStandard) : c.transportMode;
workers = (transportMode == QLatin1String(protocols::mtProxy::transportModeFakeTLS)) ? QStringLiteral("0")
: QStringLiteral("2");
}
vars.append({{"$MTPROXY_WORKERS", workers}});
vars.append({{"$MTPROXY_NAT_ENABLED", c.natEnabled ? QStringLiteral("1") : QStringLiteral("0")}});
vars.append({{"$MTPROXY_NAT_INTERNAL_IP", c.natInternalIp}});
vars.append({{"$MTPROXY_NAT_EXTERNAL_IP", c.natExternalIp}});
}
return vars;
}
amnezia::ScriptVars amnezia::genTelemtVars(const ContainerConfig &containerConfig)
{
ScriptVars vars;
if (auto *telemtProtocolConfig = containerConfig.getTelemtProtocolConfig()) {
const TelemtProtocolConfig &c = *telemtProtocolConfig;
const QString transport = c.transportMode.isEmpty() ? QString(protocols::telemt::transportModeStandard)
: c.transportMode;
const bool faketls = (transport == QLatin1String(protocols::telemt::transportModeFakeTLS));
vars.append({ { "$TELEMT_TOML_SECURE", faketls ? QLatin1String("false") : QLatin1String("true") } });
vars.append({ { "$TELEMT_TOML_TLS", faketls ? QLatin1String("true") : QLatin1String("false") } });
vars.append({ { "$TELEMT_PORT", c.port.isEmpty() ? QString(protocols::telemt::defaultPort) : c.port } });
vars.append({ { "$TELEMT_SECRET", c.secret } });
vars.append({ { "$TELEMT_TAG", c.tag } });
QString tlsDomain = c.tlsDomain;
if (tlsDomain.isEmpty()) {
tlsDomain = QString(protocols::telemt::defaultTlsDomain);
}
vars.append({ { "$TELEMT_TLS_DOMAIN", tlsDomain } });
vars.append({ { "$TELEMT_PUBLIC_HOST", c.publicHost } });
vars.append({ { "$TELEMT_USER_NAME",
c.userName.isEmpty() ? QString::fromUtf8(protocols::telemt::defaultUserName) : c.userName } });
vars.append({ { "$TELEMT_USE_MIDDLE_PROXY", c.useMiddleProxy ? QLatin1String("true") : QLatin1String("false") } });
vars.append({ { "$TELEMT_MASK", c.maskEnabled ? QLatin1String("true") : QLatin1String("false") } });
vars.append({ { "$TELEMT_TLS_EMULATION", c.tlsEmulation ? QLatin1String("true") : QLatin1String("false") } });
}
return vars;
}
amnezia::ScriptVars amnezia::genProtocolVarsForContainer(DockerContainer container, const ContainerConfig &containerConfig)
{
ScriptVars vars;
@@ -309,6 +391,12 @@ amnezia::ScriptVars amnezia::genProtocolVarsForContainer(DockerContainer contain
case Proto::Socks5Proxy:
vars.append(genSocks5ProxyVars(containerConfig));
break;
case Proto::MtProxy:
vars.append(genMtProxyVars(containerConfig));
break;
case Proto::Telemt:
vars.append(genTelemtVars(containerConfig));
break;
default:
break;
}

View File

@@ -43,7 +43,6 @@ enum ProtocolScriptType {
enum ClientScriptType {
// Client-side scripts
linux_installer,
mac_installer
};
@@ -68,6 +67,8 @@ ScriptVars genWireGuardVars(const ContainerConfig &containerConfig);
ScriptVars genAwgVars(const ContainerConfig &containerConfig);
ScriptVars genSftpVars(const ContainerConfig &containerConfig);
ScriptVars genSocks5ProxyVars(const ContainerConfig &containerConfig);
ScriptVars genMtProxyVars(const ContainerConfig &containerConfig);
ScriptVars genTelemtVars(const ContainerConfig &containerConfig);
ScriptVars genProtocolVarsForContainer(DockerContainer container, const ContainerConfig &containerConfig);
}

View File

@@ -56,7 +56,7 @@ namespace libssh {
QEventLoop wait;
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
watcher.setFuture(future);
wait.exec();
wait.exec(QEventLoop::ExcludeUserInputEvents);
int connectionResult = watcher.result();
@@ -189,7 +189,7 @@ namespace libssh {
QEventLoop wait;
QObject::connect(this, &Client::writeToChannelFinished, &wait, &QEventLoop::quit);
wait.exec();
wait.exec(QEventLoop::ExcludeUserInputEvents);
return watcher.result();
}
@@ -284,7 +284,7 @@ namespace libssh {
QEventLoop wait;
QObject::connect(this, &Client::scpFileCopyFinished, &wait, &QEventLoop::quit);
wait.exec();
wait.exec(QEventLoop::ExcludeUserInputEvents);
closeScpSession();
return watcher.result();

View File

@@ -103,8 +103,8 @@ ErrorCode SshSession::runContainerScript(const ServerCredentials &credentials, D
if (e)
return e;
QString runner =
QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, (container == DockerContainer::Socks5Proxy ? "sh" : "bash"));
const bool useSh = container == DockerContainer::Socks5Proxy || container == DockerContainer::MtProxy || container == DockerContainer::Telemt;
QString runner = QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, useSh ? "sh" : "bash");
e = runScript(credentials, replaceVars(runner, amnezia::genBaseVars(credentials, container, QString(), QString())), cbReadStdOut, cbReadStdErr);
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);

View File

@@ -0,0 +1,122 @@
#include "serverConfigUtils.h"
#include <QJsonArray>
#include <QJsonValue>
#include "core/models/selfhosted/selfHostedAdminServerConfig.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/configKeys.h"
namespace
{
bool hasThirdPartyConfig(const QJsonObject &json)
{
const QJsonArray containersArray = json.value(amnezia::configKey::containers).toArray();
for (const QJsonValue &val : containersArray) {
const QJsonObject containerObj = val.toObject();
for (auto it = containerObj.begin(); it != containerObj.end(); ++it) {
if (it.key() == amnezia::configKey::container) {
continue;
}
const QJsonObject protocolObj = it.value().toObject();
if (protocolObj.contains(amnezia::configKey::isThirdPartyConfig)
&& protocolObj.value(amnezia::configKey::isThirdPartyConfig).toBool()) {
return true;
}
}
}
return false;
}
} // namespace
namespace serverConfigUtils
{
bool isServerFromApi(const QJsonObject &serverConfigObject)
{
const int configVersion = serverConfigObject.value(amnezia::configKey::configVersion).toInt();
switch (configVersion) {
case ConfigSource::Telegram:
case ConfigSource::AmneziaGateway:
return true;
default:
return false;
}
}
ConfigSource getConfigSource(const QJsonObject &serverConfigObject)
{
return static_cast<ConfigSource>(serverConfigObject.value(amnezia::configKey::configVersion).toInt());
}
ConfigType configTypeFromJson(const QJsonObject &serverConfigObject)
{
const int configVersion = serverConfigObject.value(amnezia::configKey::configVersion).toInt();
switch (configVersion) {
case ConfigSource::Telegram: {
constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
const QString apiEndpointValue = serverConfigObject.value(apiDefs::key::apiEndpoint).toString();
if (apiEndpointValue.contains(premiumV1Endpoint)) {
return ConfigType::AmneziaPremiumV1;
}
if (apiEndpointValue.contains(freeV2Endpoint)) {
return ConfigType::AmneziaFreeV2;
}
}
[[fallthrough]];
case ConfigSource::AmneziaGateway: {
constexpr QLatin1String servicePremium("amnezia-premium");
constexpr QLatin1String serviceFree("amnezia-free");
constexpr QLatin1String serviceExternalPremium("external-premium");
const QJsonObject apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
const QString serviceTypeStr = apiConfigObject.value(apiDefs::key::serviceType).toString();
if (serviceTypeStr == servicePremium) {
return ConfigType::AmneziaPremiumV2;
}
if (serviceTypeStr == serviceFree) {
return ConfigType::AmneziaFreeV3;
}
if (serviceTypeStr == serviceExternalPremium) {
return ConfigType::ExternalPremium;
}
break;
}
default:
break;
}
if (hasThirdPartyConfig(serverConfigObject)) {
return ConfigType::Native;
}
const amnezia::SelfHostedAdminServerConfig adminProbe =
amnezia::SelfHostedAdminServerConfig::fromJson(serverConfigObject);
return adminProbe.hasCredentials() ? ConfigType::SelfHostedAdmin : ConfigType::SelfHostedUser;
}
bool isLegacyApiSubscription(ConfigType configType)
{
return configType == ConfigType::AmneziaPremiumV1 || configType == ConfigType::AmneziaFreeV2;
}
bool isApiV2Subscription(ConfigType configType)
{
switch (configType) {
case ConfigType::AmneziaPremiumV2:
case ConfigType::AmneziaFreeV3:
case ConfigType::ExternalPremium:
return true;
default:
return false;
}
}
} // namespace serverConfigUtils

View File

@@ -0,0 +1,40 @@
#ifndef SERVERCONFIGUTILS_H
#define SERVERCONFIGUTILS_H
#include <QJsonObject>
namespace serverConfigUtils
{
enum ConfigType {
AmneziaFreeV2 = 0,
AmneziaFreeV3,
AmneziaPremiumV1,
AmneziaPremiumV2,
SelfHosted,
ExternalPremium,
SelfHostedAdmin = 8,
SelfHostedUser,
Native,
Invalid
};
enum ConfigSource {
Telegram = 1,
AmneziaGateway
};
bool isServerFromApi(const QJsonObject &serverConfigObject);
ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
ConfigType configTypeFromJson(const QJsonObject &serverConfigObject);
bool isLegacyApiSubscription(ConfigType configType);
bool isApiV2Subscription(ConfigType configType);
} // namespace serverConfigUtils
#endif // SERVERCONFIGUTILS_H

View File

@@ -220,7 +220,7 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur
m_rawConfig = configuration;
m_serverAddress = configuration.value(configKey::hostName).toString().toNSString();
const QString serverDescription = configuration.value(config_key::description).toString().trimmed();
const QString serverDescription = configuration.value(configKey::description).toString().trimmed();
QString tunnelName;
if (serverDescription.isEmpty()) {
tunnelName = ProtocolUtils::protoToString(proto);
@@ -977,7 +977,9 @@ bool IosController::shareText(const QStringList& filesToSend) {
}
#if !MACOS_NE
UIViewController *qtController = getViewController();
if (!qtController) return;
if (!qtController) {
return false;
}
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
#endif

View File

@@ -1,4 +1,4 @@
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install --install-recommends"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="opensuse";\

View File

@@ -0,0 +1,9 @@
FROM amneziavpn/mtproxy:latest
RUN mkdir -p /opt/amnezia /data
RUN printf '#!/bin/sh\ntail -f /dev/null\n' > /opt/amnezia/start.sh && \
chmod a+x /opt/amnezia/start.sh
VOLUME /data
ENTRYPOINT ["/bin/sh", "/opt/amnezia/start.sh"]
CMD [""]

View File

@@ -0,0 +1,60 @@
#!/bin/sh
# Download Telegram config files
curl -s https://core.telegram.org/getProxySecret -o /data/proxy-secret
curl -s https://core.telegram.org/getProxyConfig -o /data/proxy-multi.conf
# Determine secret: env var -> saved file -> generate new
if [ -n "$MTPROXY_SECRET" ]; then
SECRET="$MTPROXY_SECRET"
elif [ -f /data/secret ]; then
SECRET=$(cat /data/secret)
else
SECRET=$(openssl rand -hex 16)
fi
# Validate: must be exactly 32 hex chars
echo "$SECRET" | grep -qE '^[0-9a-fA-F]{32}$' || SECRET=$(openssl rand -hex 16)
# Persist secret for start.sh restarts
echo "$SECRET" > /data/secret
# Detect external IP
IP=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null)
[ -z "$IP" ] && IP=$(curl -s --max-time 5 https://ifconfig.me 2>/dev/null)
[ -z "$IP" ] && IP=$(curl -s --max-time 5 https://icanhazip.com 2>/dev/null)
# Use custom public host/domain if provided, otherwise fall back to detected IP
if [ -n "$MTPROXY_PUBLIC_HOST" ]; then
LINK_HOST="$MTPROXY_PUBLIC_HOST"
else
LINK_HOST="$IP"
fi
PORT=$MTPROXY_PORT
# Transport mode is substituted by replaceVars — plain variable, no curly braces
TRANSPORT_MODE=$MTPROXY_TRANSPORT_MODE
PADDED_SECRET="dd${SECRET}"
if [ "$TRANSPORT_MODE" = "faketls" ] && [ -n "$MTPROXY_TLS_DOMAIN" ]; then
DOMAIN_HEX=$(echo -n "$MTPROXY_TLS_DOMAIN" | od -A n -t x1 | tr -d ' \n')
FAKETLS_SECRET="ee${SECRET}${DOMAIN_HEX}"
else
FAKETLS_SECRET=""
fi
# Active link secret depends on transport mode
if [ "$TRANSPORT_MODE" = "faketls" ] && [ -n "$FAKETLS_SECRET" ]; then
LINK_SECRET="$FAKETLS_SECRET"
else
LINK_SECRET="$PADDED_SECRET"
fi
# Output stable markers — parsed by updateContainerConfigAfterInstallation()
echo "[*] MTProxy configuration"
echo "[*] Secret: ${SECRET}"
echo "[*] FakeTLS: ${FAKETLS_SECRET}"
echo "[*] tg:// link: tg://proxy?server=${LINK_HOST}&port=${PORT}&secret=${LINK_SECRET}"
echo "[*] t.me link: https://t.me/proxy?server=${LINK_HOST}&port=${PORT}&secret=${LINK_SECRET}"

View File

@@ -0,0 +1,9 @@
# Run container
sudo docker run -d \
--log-driver none \
--restart always \
-p $MTPROXY_PORT:$MTPROXY_PORT/tcp \
-v amnezia-mtproxy-data:/data \
--name $CONTAINER_NAME \
$CONTAINER_NAME

View File

@@ -0,0 +1,71 @@
#!/bin/sh
echo "Container startup"
# Read persisted secret
SECRET=""
if [ -f /data/secret ]; then
SECRET=$(cat /data/secret)
fi
if [ -z "$SECRET" ]; then
echo "ERROR: /data/secret not found — run configure_container first"
tail -f /dev/null
exit 1
fi
# Build tag argument
TAG_ARG=""
if [ -n "$MTPROXY_TAG" ]; then
TAG_ARG="-P $MTPROXY_TAG"
fi
# Build domain argument for FakeTLS mode
DOMAIN_ARG=""
if [ "$MTPROXY_TRANSPORT_MODE" = "faketls" ] && [ -n "$MTPROXY_TLS_DOMAIN" ]; then
DOMAIN_ARG="--domain $MTPROXY_TLS_DOMAIN"
fi
WORKERS=$MTPROXY_WORKERS
STATS_PORT=2398
LISTEN_PORT=$MTPROXY_PORT
NAT_FLAG=""
NAT_VALUE=""
if [ "$MTPROXY_NAT_ENABLED" = "1" ] && [ -n "$MTPROXY_NAT_INTERNAL_IP" ] && [ -n "$MTPROXY_NAT_EXTERNAL_IP" ]; then
NAT_FLAG="--nat-info"
NAT_VALUE="$MTPROXY_NAT_INTERNAL_IP:$MTPROXY_NAT_EXTERNAL_IP"
else
INTERNAL_IP=$(hostname -i 2>/dev/null | awk '{print $1}')
EXTERNAL_IP=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null)
[ -z "$EXTERNAL_IP" ] && EXTERNAL_IP=$(curl -s --max-time 5 https://ifconfig.me 2>/dev/null)
if [ -n "$INTERNAL_IP" ] && [ -n "$EXTERNAL_IP" ] && [ "$INTERNAL_IP" != "$EXTERNAL_IP" ]; then
NAT_FLAG="--nat-info"
NAT_VALUE="${INTERNAL_IP}:${EXTERNAL_IP}"
fi
fi
# Build additional secrets arguments
ADDITIONAL_SECRETS_ARG=""
if [ -n "$MTPROXY_ADDITIONAL_SECRETS" ]; then
for S in $(echo "$MTPROXY_ADDITIONAL_SECRETS" | tr ',' ' '); do
ADDITIONAL_SECRETS_ARG="$ADDITIONAL_SECRETS_ARG -S $S"
done
fi
# Start proxy (foreground)
exec mtproto-proxy \
-u root \
-p ${STATS_PORT} \
-H ${LISTEN_PORT} \
-S ${SECRET} \
${ADDITIONAL_SECRETS_ARG} \
--aes-pwd /data/proxy-secret \
-M ${WORKERS} \
-C 60000 \
--allow-skip-dh \
${NAT_FLAG:+${NAT_FLAG} ${NAT_VALUE}} \
${TAG_ARG} \
${DOMAIN_ARG} \
/data/proxy-multi.conf

View File

@@ -24,6 +24,14 @@
<file>ipsec/run_container.sh</file>
<file>ipsec/start.sh</file>
<file>ipsec/strongswan.profile</file>
<file>mtproxy/configure_container.sh</file>
<file>mtproxy/Dockerfile</file>
<file>mtproxy/run_container.sh</file>
<file>mtproxy/start.sh</file>
<file>telemt/configure_container.sh</file>
<file>telemt/Dockerfile</file>
<file>telemt/run_container.sh</file>
<file>telemt/start.sh</file>
<file>openvpn/configure_container.sh</file>
<file>openvpn/Dockerfile</file>
<file>openvpn/run_container.sh</file>
@@ -55,4 +63,3 @@
<file>xray/template.json</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,42 @@
# syntax=docker/dockerfile:1
# Debian-based image with Telemt binary (shell + jq for Amnezia configure scripts).
# Binary from https://github.com/telemt/telemt releases (same pattern as upstream Dockerfile minimal stage).
FROM debian:12-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
binutils \
ca-certificates \
curl \
jq \
openssl \
tar \
&& rm -rf /var/lib/apt/lists/*
# Use machine arch (works with classic `docker build`; TARGETARCH is only set with BuildKit).
RUN set -eux; \
ARCH="$(uname -m)"; \
case "$ARCH" in \
x86_64) ASSET="telemt-x86_64-linux-musl.tar.gz" ;; \
aarch64|arm64) ASSET="telemt-aarch64-linux-musl.tar.gz" ;; \
*) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;; \
esac; \
curl -fL --retry 5 --retry-delay 3 --connect-timeout 10 --max-time 120 \
-o "/tmp/${ASSET}" "https://github.com/telemt/telemt/releases/latest/download/${ASSET}"; \
curl -fL --retry 5 --retry-delay 3 --connect-timeout 10 --max-time 120 \
-o "/tmp/${ASSET}.sha256" "https://github.com/telemt/telemt/releases/latest/download/${ASSET}.sha256"; \
cd /tmp && sha256sum -c "${ASSET}.sha256"; \
tar -xzf "${ASSET}" -C /tmp; \
test -f /tmp/telemt; \
install -m 0755 /tmp/telemt /usr/local/bin/telemt; \
strip --strip-unneeded /usr/local/bin/telemt || true; \
rm -f "/tmp/${ASSET}" "/tmp/${ASSET}.sha256" /tmp/telemt
RUN mkdir -p /opt/amnezia /data
RUN printf '#!/bin/sh\ntail -f /dev/null\n' > /opt/amnezia/start.sh && \
chmod a+x /opt/amnezia/start.sh
VOLUME /data
ENTRYPOINT ["/bin/sh", "/opt/amnezia/start.sh"]
CMD [""]

Some files were not shown because too many files have changed in this diff Show More