mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 77418c7f79 | |||
| f975a75e23 | |||
| 87b0a98460 | |||
| 17b34f7891 | |||
| 1c32561dd4 | |||
| a9861d18b7 | |||
| c14138f031 |
@@ -63,16 +63,18 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
|
||||
return connData;
|
||||
}
|
||||
|
||||
connData.caCert =
|
||||
m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, errorCode);
|
||||
connData.clientCert = m_sshSession->getTextFileFromContainer(
|
||||
container, credentials, QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), errorCode);
|
||||
|
||||
const QStringList certPaths = {
|
||||
QString::fromLatin1(amnezia::protocols::openvpn::caCertPath),
|
||||
QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId),
|
||||
QString::fromLatin1(amnezia::protocols::openvpn::taKeyPath)
|
||||
};
|
||||
const QList<QByteArray> certs = m_sshSession->getTextFilesFromContainer(container, credentials, certPaths, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
|
||||
connData.taKey = m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, errorCode);
|
||||
connData.caCert = certs.value(0);
|
||||
connData.clientCert = certs.value(1);
|
||||
connData.taKey = certs.value(2);
|
||||
|
||||
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
|
||||
errorCode = ErrorCode::SshScpFailureError;
|
||||
|
||||
@@ -165,20 +165,16 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
||||
connData.clientIP = nextIp.toString();
|
||||
|
||||
// Get keys
|
||||
connData.serverPubKey =
|
||||
m_sshSession->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
|
||||
const QList<QByteArray> keys =
|
||||
m_sshSession->getTextFilesFromContainer(container, credentials, {m_serverPublicKeyPath, m_serverPskKeyPath}, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
connData.serverPubKey = keys.value(0);
|
||||
connData.serverPubKey.replace("\n", "");
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
|
||||
connData.pskKey = m_sshSession->getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode);
|
||||
connData.pskKey = keys.value(1);
|
||||
connData.pskKey.replace("\n", "");
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
|
||||
// Add client to config
|
||||
QString configPart = QString("[Peer]\n"
|
||||
"PublicKey = %1\n"
|
||||
|
||||
@@ -418,9 +418,7 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
||||
}
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QString serviceProtocol = apiV2->serviceProtocol();
|
||||
// Auto mode (empty) defaults to AWG — gateway requires public_key for all requests
|
||||
const QString effectiveProtocol = serviceProtocol.isEmpty() ? configKey::awg : serviceProtocol;
|
||||
ProtocolData protocolData = generateProtocolData(effectiveProtocol);
|
||||
ProtocolData protocolData = generateProtocolData(serviceProtocol);
|
||||
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
@@ -434,7 +432,7 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
appendProtocolDataToApiPayload(effectiveProtocol, protocolData, apiPayload);
|
||||
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
|
||||
|
||||
if (isConnectEvent) {
|
||||
apiPayload[apiDefs::key::isConnectEvent] = true;
|
||||
@@ -453,11 +451,11 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
||||
}
|
||||
|
||||
QJsonObject serverConfigJson;
|
||||
errorCode = extractServerConfigJsonFromResponse(responseBody, effectiveProtocol, protocolData, serverConfigJson);
|
||||
errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
|
||||
updateApiConfigInJson(serverConfigJson, apiV2->apiConfig.serviceType, serviceProtocol, apiV2->apiConfig.userCountryCode, responseBody);
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
@@ -743,9 +741,6 @@ void SubscriptionController::setCurrentProtocol(const QString &serverId, const Q
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (apiV2.has_value()) {
|
||||
apiV2->apiConfig.serviceProtocol = protocolName;
|
||||
if (protocolName.isEmpty()) {
|
||||
apiV2->defaultContainer = DockerContainer::Awg;
|
||||
}
|
||||
m_serversRepository->editServer(serverId, apiV2->toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
|
||||
}
|
||||
@@ -757,12 +752,6 @@ bool SubscriptionController::isVlessProtocol(const QString &serverId) const
|
||||
return apiV2.has_value() && apiV2->serviceProtocol() == "vless";
|
||||
}
|
||||
|
||||
bool SubscriptionController::isAwgProtocol(const QString &serverId) const
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
return apiV2.has_value() && apiV2->serviceProtocol() == "awg";
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::processAppStorePurchase(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &productId,
|
||||
int *duplicateServerIndex)
|
||||
|
||||
@@ -86,7 +86,6 @@ public:
|
||||
|
||||
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
|
||||
bool isVlessProtocol(const QString &serverId) const;
|
||||
bool isAwgProtocol(const QString &serverId) const;
|
||||
|
||||
ErrorCode getAccountInfo(const QString &serverId, QJsonObject &accountInfo);
|
||||
QFuture<QPair<ErrorCode, QString>> getRenewalLink(const QString &serverId);
|
||||
|
||||
@@ -225,22 +225,6 @@ void CoreController::initControllers()
|
||||
m_connectionController, this);
|
||||
setQmlContextProperty("SubscriptionUiController", m_subscriptionUiController);
|
||||
|
||||
connect(m_connectionUiController, &ConnectionUiController::requestSetCurrentProtocol,
|
||||
m_subscriptionUiController, &SubscriptionUiController::setCurrentProtocol, Qt::QueuedConnection);
|
||||
connect(m_connectionUiController, &ConnectionUiController::requestUpdateServiceFromGateway,
|
||||
m_subscriptionUiController, &SubscriptionUiController::updateServiceFromGateway, Qt::QueuedConnection);
|
||||
connect(m_subscriptionUiController, &SubscriptionUiController::updateServiceFromGatewayCompleted,
|
||||
m_connectionUiController, &ConnectionUiController::onUpdateServiceFromGatewayCompleted,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_connectionUiController, &ConnectionUiController::requestSetProcessedServer,
|
||||
this, [this](const QString &serverId) {
|
||||
m_serversUiController->setProcessedServerId(serverId);
|
||||
}, Qt::QueuedConnection);
|
||||
connect(m_installUiController, &InstallUiController::updateContainerFinished,
|
||||
m_connectionUiController, [this](const QString &, bool) {
|
||||
m_connectionUiController->onCurrentContainerUpdated();
|
||||
});
|
||||
|
||||
m_apiNewsUiController = new ApiNewsUiController(m_newsModel, m_newsController, this);
|
||||
setQmlContextProperty("ApiNewsController", m_apiNewsUiController);
|
||||
|
||||
|
||||
@@ -82,33 +82,11 @@
|
||||
#endif
|
||||
|
||||
class CoreSignalHandlers;
|
||||
class TestMultipleImports;
|
||||
class TestAdminSelfHostedExport;
|
||||
class TestServerEdit;
|
||||
class TestDefaultServerChange;
|
||||
class TestServerEdgeCases;
|
||||
class TestSignalOrder;
|
||||
class TestServersModelSync;
|
||||
class TestComplexOperations;
|
||||
class TestSettingsSignals;
|
||||
class TestUiServersModelAndController;
|
||||
class TestSelfHostedServerSetup;
|
||||
|
||||
class CoreController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class CoreSignalHandlers;
|
||||
friend class TestMultipleImports;
|
||||
friend class TestAdminSelfHostedExport;
|
||||
friend class TestServerEdit;
|
||||
friend class TestDefaultServerChange;
|
||||
friend class TestServerEdgeCases;
|
||||
friend class TestSignalOrder;
|
||||
friend class TestServersModelSync;
|
||||
friend class TestComplexOperations;
|
||||
friend class TestSettingsSignals;
|
||||
friend class TestUiServersModelAndController;
|
||||
friend class TestSelfHostedServerSetup;
|
||||
|
||||
public:
|
||||
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
|
||||
|
||||
@@ -161,12 +161,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
return encRequestData.errorCode;
|
||||
}
|
||||
|
||||
QNetworkAccessManager *nam = amnApp ? amnApp->networkManager() : nullptr;
|
||||
if (!nam) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
QNetworkReply *reply = nam->post(encRequestData.request, encRequestData.requestBody);
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||
|
||||
QEventLoop wait;
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
@@ -241,14 +236,7 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QNetworkAccessManager *nam = amnApp ? amnApp->networkManager() : nullptr;
|
||||
if (!nam) {
|
||||
promise->addResult(qMakePair(ErrorCode::InternalError, QByteArray()));
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QNetworkReply *reply = nam->post(encRequestData.request, encRequestData.requestBody);
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||
|
||||
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
|
||||
|
||||
@@ -390,14 +378,9 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
return {};
|
||||
}
|
||||
|
||||
QNetworkAccessManager *nam = amnApp ? amnApp->networkManager() : nullptr;
|
||||
if (!nam) {
|
||||
return {};
|
||||
}
|
||||
|
||||
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||
request.setUrl(proxyStorageUrl);
|
||||
reply = nam->get(request);
|
||||
reply = amnApp->networkManager()->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
|
||||
@@ -90,10 +90,9 @@ InstallController::~InstallController()
|
||||
}
|
||||
|
||||
ErrorCode InstallController::setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config,
|
||||
bool isUpdate)
|
||||
SshSession &sshSession, bool isUpdate)
|
||||
{
|
||||
qDebug().noquote() << "InstallController::setupContainer" << ContainerUtils::containerToString(container);
|
||||
SshSession sshSession(this);
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
|
||||
e = isUserInSudo(credentials, sshSession);
|
||||
@@ -199,7 +198,7 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
if (reinstallRequired) {
|
||||
errorCode = setupContainer(credentials, container, newConfig, true);
|
||||
errorCode = setupContainer(credentials, container, newConfig, sshSession, true);
|
||||
} else {
|
||||
errorCode = configureContainerWorker(credentials, container, newConfig, sshSession);
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
@@ -1033,10 +1032,10 @@ ContainerConfig InstallController::generateConfig(DockerContainer container, int
|
||||
}
|
||||
|
||||
ErrorCode InstallController::installContainer(const ServerCredentials &credentials, DockerContainer container, int port,
|
||||
TransportProto transportProto, ContainerConfig &config)
|
||||
TransportProto transportProto, ContainerConfig &config, SshSession &sshSession)
|
||||
{
|
||||
config = generateConfig(container, port, transportProto);
|
||||
return setupContainer(credentials, container, config, false);
|
||||
return setupContainer(credentials, container, config, sshSession, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -1142,7 +1141,7 @@ ErrorCode InstallController::installServer(const ServerCredentials &credentials,
|
||||
wasContainerInstalled = false;
|
||||
if (!installedContainers.contains(container)) {
|
||||
ContainerConfig config;
|
||||
errorCode = installContainer(credentials, container, port, transportProto, config);
|
||||
errorCode = installContainer(credentials, container, port, transportProto, config, sshSession);
|
||||
if (errorCode) {
|
||||
return errorCode;
|
||||
}
|
||||
@@ -1212,7 +1211,7 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
|
||||
wasContainerInstalled = false;
|
||||
if (!installedContainers.contains(container)) {
|
||||
ContainerConfig config;
|
||||
errorCode = installContainer(credentials, container, port, transportProto, config);
|
||||
errorCode = installContainer(credentials, container, port, transportProto, config, sshSession);
|
||||
if (errorCode) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
QObject *parent = nullptr);
|
||||
~InstallController();
|
||||
|
||||
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
|
||||
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, SshSession &sshSession, bool isUpdate = false);
|
||||
ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
ErrorCode rebootServer(const QString &serverId);
|
||||
@@ -55,7 +55,7 @@ public:
|
||||
|
||||
ErrorCode scanServerForInstalledContainers(const QString &serverId);
|
||||
|
||||
ErrorCode installContainer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto, ContainerConfig &config);
|
||||
ErrorCode installContainer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto, ContainerConfig &config, SshSession &sshSession);
|
||||
|
||||
ErrorCode installServer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto,
|
||||
bool &wasContainerInstalled);
|
||||
|
||||
@@ -48,6 +48,9 @@ namespace libssh {
|
||||
ssh_options_set(m_session, SSH_OPTIONS_USER, hostUsername.c_str());
|
||||
ssh_options_set(m_session, SSH_OPTIONS_LOG_VERBOSITY, &logVerbosity);
|
||||
|
||||
long connectTimeoutSec = 30;
|
||||
ssh_options_set(m_session, SSH_OPTIONS_TIMEOUT, &connectTimeoutSec);
|
||||
|
||||
QFutureWatcher<int> watcher;
|
||||
QFuture<int> future = QtConcurrent::run([this]() {
|
||||
return ssh_connect(m_session);
|
||||
@@ -61,7 +64,9 @@ namespace libssh {
|
||||
int connectionResult = watcher.result();
|
||||
|
||||
if (connectionResult != SSH_OK) {
|
||||
return fromLibsshErrorCode();
|
||||
ErrorCode errorCode = fromLibsshErrorCode();
|
||||
disconnectFromHost();
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
std::string authUsername = credentials.userName.toStdString();
|
||||
@@ -95,14 +100,20 @@ namespace libssh {
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
errorCode = ErrorCode::SshPrivateKeyFormatError;
|
||||
}
|
||||
disconnectFromHost();
|
||||
return errorCode;
|
||||
}
|
||||
} else {
|
||||
authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.secretData.toStdString().c_str());
|
||||
if (authResult != SSH_OK) {
|
||||
return fromLibsshErrorCode();
|
||||
ErrorCode errorCode = fromLibsshErrorCode();
|
||||
disconnectFromHost();
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
|
||||
long sessionTimeoutSec = 86400;
|
||||
ssh_options_set(m_session, SSH_OPTIONS_TIMEOUT, &sessionTimeoutSec);
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ ErrorCode SshSession::runScript(const ServerCredentials &credentials, QString sc
|
||||
qDebug() << "SshSession::Run script";
|
||||
|
||||
QString totalLine;
|
||||
QStringList statements;
|
||||
const QStringList &lines = script.split("\n", Qt::SkipEmptyParts);
|
||||
for (int i = 0; i < lines.count(); i++) {
|
||||
QString currentLine = lines.at(i);
|
||||
@@ -69,24 +70,31 @@ ErrorCode SshSession::runScript(const ServerCredentials &credentials, QString sc
|
||||
totalLine = totalLine + "\n" + currentLine;
|
||||
}
|
||||
|
||||
QString lineToExec;
|
||||
if (currentLine.endsWith("\\")) {
|
||||
continue;
|
||||
} else {
|
||||
lineToExec = totalLine;
|
||||
totalLine.clear();
|
||||
}
|
||||
|
||||
QString lineToExec = totalLine;
|
||||
totalLine.clear();
|
||||
|
||||
if (lineToExec.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
qDebug().noquote() << lineToExec;
|
||||
statements << lineToExec;
|
||||
}
|
||||
|
||||
error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr);
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
}
|
||||
if (statements.isEmpty()) {
|
||||
qDebug().noquote() << "SshSession::runScript finished (no statements)\n";
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
const QString batchedScript = statements.join("\n");
|
||||
qDebug().noquote() << batchedScript;
|
||||
|
||||
error = m_sshClient.executeCommand(batchedScript, cbReadStdOut, cbReadStdErr);
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
}
|
||||
|
||||
qDebug().noquote() << "SshSession::runScript finished\n";
|
||||
@@ -97,30 +105,25 @@ ErrorCode SshSession::runContainerScript(const ServerCredentials &credentials, D
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut,
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
|
||||
{
|
||||
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
|
||||
|
||||
ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName);
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
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);
|
||||
const QString shell = useSh ? QStringLiteral("sh") : QStringLiteral("bash");
|
||||
const QString b64 = QString::fromLatin1(script.toUtf8().toBase64());
|
||||
|
||||
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
|
||||
runScript(credentials, replaceVars(remover, amnezia::genBaseVars(credentials, container, QString(), QString())), cbReadStdOut, cbReadStdErr);
|
||||
const QString command = QStringLiteral("printf '%s' '%1' | base64 -d | sudo docker exec -i $CONTAINER_NAME %2")
|
||||
.arg(b64, shell);
|
||||
|
||||
return e;
|
||||
return runScript(credentials,
|
||||
replaceVars(command, amnezia::genBaseVars(credentials, container, QString(), QString())),
|
||||
cbReadStdOut, cbReadStdErr);
|
||||
}
|
||||
|
||||
ErrorCode SshSession::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file,
|
||||
const QString &path, libssh::ScpOverwriteMode overwriteMode)
|
||||
{
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
|
||||
e = uploadFileToHost(credentials, file.toUtf8(), tmpFileName);
|
||||
if (e)
|
||||
return e;
|
||||
if (overwriteMode != libssh::ScpOverwriteMode::ScpOverwriteExisting
|
||||
&& overwriteMode != libssh::ScpOverwriteMode::ScpAppendToExisting) {
|
||||
return ErrorCode::NotImplementedError;
|
||||
}
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStd = [&](const QString &data, libssh::Client &) {
|
||||
@@ -128,45 +131,26 @@ ErrorCode SshSession::uploadTextFileToContainer(DockerContainer container, const
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
// mkdir
|
||||
QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"").arg(path);
|
||||
auto baseVars = amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
|
||||
e = runScript(credentials, replaceVars(mkdir, amnezia::genBaseVars(credentials, container, QString(), QString())));
|
||||
const QString b64 = QString::fromLatin1(file.toUtf8().toBase64());
|
||||
const QString dir = QFileInfo(path).path();
|
||||
const QString redirect = (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting)
|
||||
? QStringLiteral(">>")
|
||||
: QStringLiteral(">");
|
||||
|
||||
const QString command = QStringLiteral("printf '%s' '%1' | base64 -d | "
|
||||
"sudo docker exec -i $CONTAINER_NAME sh -c 'mkdir -p \"%2\" && cat %3 \"%4\"'")
|
||||
.arg(b64, dir, redirect, path);
|
||||
|
||||
ErrorCode e = runScript(credentials, replaceVars(command, baseVars), cbReadStd, cbReadStd);
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) {
|
||||
e = runScript(credentials,
|
||||
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, path),
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString())),
|
||||
cbReadStd, cbReadStd);
|
||||
|
||||
if (e)
|
||||
return e;
|
||||
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
|
||||
e = runScript(credentials,
|
||||
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, tmpFileName),
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString())),
|
||||
cbReadStd, cbReadStd);
|
||||
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
e = runScript(credentials,
|
||||
replaceVars(QStringLiteral("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName, path),
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString())),
|
||||
cbReadStd, cbReadStd);
|
||||
|
||||
if (e)
|
||||
return e;
|
||||
} else
|
||||
return ErrorCode::NotImplementedError;
|
||||
|
||||
if (stdOut.contains("Error") && stdOut.contains("No such container")) {
|
||||
return ErrorCode::ServerContainerMissingError;
|
||||
}
|
||||
|
||||
runScript(credentials, replaceVars(QString("sudo shred -u %1").arg(tmpFileName), amnezia::genBaseVars(credentials, container, QString(), QString())));
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -188,6 +172,38 @@ QByteArray SshSession::getTextFileFromContainer(DockerContainer container, const
|
||||
return QByteArray::fromHex(stdOut.toUtf8());
|
||||
}
|
||||
|
||||
QList<QByteArray> SshSession::getTextFilesFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
const QStringList &paths, ErrorCode &errorCode)
|
||||
{
|
||||
errorCode = ErrorCode::NoError;
|
||||
QList<QByteArray> result;
|
||||
if (paths.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const QString sep = QStringLiteral("ZZAMNSEPZZ");
|
||||
QString inner;
|
||||
for (const QString &path : paths) {
|
||||
inner += QStringLiteral("xxd -p '%1'; echo '%2'; ").arg(path, sep);
|
||||
}
|
||||
QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"%2\"")
|
||||
.arg(ContainerUtils::containerToString(container), inner);
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
errorCode = runScript(credentials, script, cbReadStdOut);
|
||||
|
||||
const QStringList parts = stdOut.split(sep);
|
||||
for (int i = 0; i < paths.size(); ++i) {
|
||||
const QString hex = (i < parts.size()) ? parts.at(i) : QString();
|
||||
result.append(QByteArray::fromHex(hex.toUtf8()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ErrorCode SshSession::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
|
||||
libssh::ScpOverwriteMode overwriteMode)
|
||||
{
|
||||
|
||||
@@ -28,6 +28,8 @@ public:
|
||||
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path,
|
||||
ErrorCode &errorCode);
|
||||
QList<QByteArray> getTextFilesFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
const QStringList &paths, ErrorCode &errorCode);
|
||||
|
||||
static QString replaceVars(const QString &script, const Vars &vars);
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ dev tun
|
||||
ca /opt/amnezia/openvpn/ca.crt
|
||||
cert /opt/amnezia/openvpn/AmneziaReq.crt
|
||||
key /opt/amnezia/openvpn/AmneziaReq.key
|
||||
dh /opt/amnezia/openvpn/dh.pem
|
||||
dh none
|
||||
ecdh-curve secp384r1
|
||||
server $OPENVPN_SUBNET_IP $OPENVPN_SUBNET_MASK
|
||||
ifconfig-pool-persist ipp.txt
|
||||
duplicate-cn
|
||||
|
||||
@@ -7,6 +7,8 @@ sudo docker run -d \
|
||||
-p $OPENVPN_PORT:$OPENVPN_PORT/$OPENVPN_TRANSPORT_PROTO \
|
||||
--name $CONTAINER_NAME $CONTAINER_NAME
|
||||
|
||||
amn_i=0; while [ "$(sudo docker inspect -f '{{.State.Running}}' $CONTAINER_NAME 2>/dev/null)" != "true" ] && [ $amn_i -lt 30 ]; do sleep 0.5; amn_i=$((amn_i+1)); done
|
||||
|
||||
sudo docker network connect amnezia-dns-net $CONTAINER_NAME
|
||||
|
||||
# Create tun device if not exist
|
||||
@@ -18,8 +20,7 @@ sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS ne
|
||||
# OpenVPN config
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amnezia/openvpn/clients; \
|
||||
cd /opt/amnezia/openvpn && easyrsa init-pki; \
|
||||
cd /opt/amnezia/openvpn && easyrsa gen-dh; \
|
||||
cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
|
||||
cd /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
|
||||
cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\
|
||||
cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\
|
||||
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn;\
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
|
||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
|
||||
sudo docker images -a --format table | grep amnezia | awk '{print $3, $1 ":" $2}' | xargs sudo docker rmi;\
|
||||
sudo docker volume ls | grep amnezia | awk '{print $2}' | xargs sudo docker volume rm -f;\
|
||||
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
|
||||
sudo rm -frd /opt/amnezia
|
||||
|
||||
@@ -2042,16 +2042,6 @@ Thank you for staying with us!</source>
|
||||
<source>Cannot change protocol during active connection</source>
|
||||
<translation>Невозможно изменить протокол во время активного соединения</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="289"/>
|
||||
<source>Connection</source>
|
||||
<translation>Соединение</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="290"/>
|
||||
<source>Protocol selection and local proxy setup</source>
|
||||
<translation>Выбор протокола и настройка локального прокси</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="319"/>
|
||||
<source>Subscription Key</source>
|
||||
@@ -5409,52 +5399,4 @@ FileZilla или другие SFTP-клиенты, а также смонтир
|
||||
<translation>Сохранить</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PageSettingsConnectionProtocols</name>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="142"/>
|
||||
<source>VPN Protocol</source>
|
||||
<translation>VPN-протокол</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="161"/>
|
||||
<source>Choose automatically</source>
|
||||
<translation>Выбирать автоматически</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="162"/>
|
||||
<source>AmneziaWG is used by default. If the connection is unstable, the app will switch to VLESS. On the next launch, AmneziaWG will be tried again</source>
|
||||
<translation>По умолчанию используется AmneziaWG. Если соединение нестабильно, приложение переключится на VLESS. При следующем запуске снова будет использован AmneziaWG</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="166"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="196"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="227"/>
|
||||
<source>Cannot change protocol during active connection</source>
|
||||
<translation>Невозможно изменить протокол во время активного соединения</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="192"/>
|
||||
<source>AmneziaWG</source>
|
||||
<translation>AmneziaWG</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="223"/>
|
||||
<source>XRay VLESS Reality</source>
|
||||
<translation>XRay VLESS Reality</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PageSettingsConnectionType</name>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionType.qml" line="46"/>
|
||||
<source>Connection</source>
|
||||
<translation>Соединение</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionType.qml" line="63"/>
|
||||
<source>VPN protocol</source>
|
||||
<translation>VPN-протокол</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
||||
@@ -436,7 +436,6 @@ bool SubscriptionUiController::updateServiceFromGateway(const QString &serverId,
|
||||
} else {
|
||||
emit changeApiCountryFinished(tr("Successfully changed the country of connection to %1").arg(newCountryName));
|
||||
}
|
||||
emit updateServiceFromGatewayCompleted(true, serverId);
|
||||
return true;
|
||||
} else {
|
||||
if (errorCode == ErrorCode::ApiSubscriptionExpiredError) {
|
||||
@@ -444,7 +443,6 @@ bool SubscriptionUiController::updateServiceFromGateway(const QString &serverId,
|
||||
} else {
|
||||
emit errorOccurred(errorCode);
|
||||
}
|
||||
emit updateServiceFromGatewayCompleted(false, serverId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -510,11 +508,6 @@ bool SubscriptionUiController::isVlessProtocol(const QString &serverId)
|
||||
return m_subscriptionController->isVlessProtocol(serverId);
|
||||
}
|
||||
|
||||
bool SubscriptionUiController::isAwgProtocol(const QString &serverId)
|
||||
{
|
||||
return m_subscriptionController->isAwgProtocol(serverId);
|
||||
}
|
||||
|
||||
|
||||
void SubscriptionUiController::removeApiConfig(const QString &serverId)
|
||||
{
|
||||
|
||||
@@ -57,7 +57,6 @@ public slots:
|
||||
|
||||
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
|
||||
bool isVlessProtocol(const QString &serverId);
|
||||
bool isAwgProtocol(const QString &serverId);
|
||||
|
||||
bool isCaptchaAwaitingUser() const;
|
||||
void onCaptchaSolved(const QString &captchaId, const QString &solution);
|
||||
@@ -84,7 +83,6 @@ signals:
|
||||
void changeApiCountryFinished(const QString &message);
|
||||
void reloadServerFromApiFinished(const QString &message);
|
||||
void updateServerFromApiFinished();
|
||||
void updateServiceFromGatewayCompleted(bool success, const QString &serverId);
|
||||
void subscriptionRefreshNeeded();
|
||||
|
||||
void apiConfigRemoved(const QString &message);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
|
||||
ConnectionUiController::ConnectionUiController(ConnectionController* connectionController,
|
||||
ServersController* serversController,
|
||||
@@ -21,9 +20,6 @@ ConnectionUiController::ConnectionUiController(ConnectionController* connectionC
|
||||
|
||||
connect(this, &ConnectionUiController::connectButtonClicked, this, &ConnectionUiController::toggleConnection, Qt::QueuedConnection);
|
||||
|
||||
m_awgStateTimer.setSingleShot(true);
|
||||
connect(&m_awgStateTimer, &QTimer::timeout, this, &ConnectionUiController::onAwgStateTimeout);
|
||||
|
||||
m_state = Vpn::ConnectionState::Disconnected;
|
||||
}
|
||||
|
||||
@@ -60,9 +56,6 @@ void ConnectionUiController::onConnectionStateChanged(Vpn::ConnectionState state
|
||||
m_connectionStateText = tr("Connecting...");
|
||||
switch (state) {
|
||||
case Vpn::ConnectionState::Connected: {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
amnApp->networkManager()->clearConnectionCache();
|
||||
|
||||
m_isConnectionInProgress = false;
|
||||
@@ -71,55 +64,36 @@ void ConnectionUiController::onConnectionStateChanged(Vpn::ConnectionState state
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Connecting: {
|
||||
checkAndStartAwgStateTimer();
|
||||
m_isConnectionInProgress = true;
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Reconnecting: {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
m_isConnectionInProgress = true;
|
||||
m_connectionStateText = tr("Reconnecting...");
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Disconnected: {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
m_isConnectionInProgress = false;
|
||||
m_connectionStateText = tr("Connect");
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Disconnecting: {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
m_isConnectionInProgress = true;
|
||||
m_connectionStateText = tr("Disconnecting...");
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Preparing: {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
m_isConnectionInProgress = true;
|
||||
m_connectionStateText = tr("Preparing...");
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Error: {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
m_isConnectionInProgress = false;
|
||||
m_connectionStateText = tr("Connect");
|
||||
emit connectionErrorOccurred(getLastConnectionError());
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Unknown: {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
m_isConnectionInProgress = false;
|
||||
m_connectionStateText = tr("Connect");
|
||||
emit connectionErrorOccurred(getLastConnectionError());
|
||||
@@ -169,113 +143,3 @@ bool ConnectionUiController::isConnected() const
|
||||
{
|
||||
return m_isConnected;
|
||||
}
|
||||
|
||||
void ConnectionUiController::onCurrentContainerUpdated()
|
||||
{
|
||||
if (m_isConnected || m_isConnectionInProgress) {
|
||||
emit reconnectWithUpdatedContainer(tr("Settings updated successfully, reconnecting..."));
|
||||
openConnection();
|
||||
} else {
|
||||
emit reconnectWithUpdatedContainer(tr("Settings updated successfully"));
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionUiController::checkAndStartAwgStateTimer()
|
||||
{
|
||||
const QString serverId = m_serversController->getDefaultServerId();
|
||||
if (serverId.isEmpty()) {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const DockerContainer container = m_serversController->getDefaultContainer(serverId);
|
||||
const Proto proto = ContainerUtils::defaultProtocol(container);
|
||||
if (proto == Proto::Awg) {
|
||||
const auto v2Config = m_serversController->apiV2Config(serverId);
|
||||
if (v2Config.has_value() && (v2Config->isPremium() || v2Config->isExternalPremium())) {
|
||||
const bool isAutoMode = v2Config->serviceProtocol().isEmpty();
|
||||
if (isAutoMode) {
|
||||
if (!m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.start(kAwgSwitchTimeoutMs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (m_serversController->isLegacyApiV1Server(serverId)) {
|
||||
if (!m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.start(kAwgSwitchTimeoutMs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionUiController::onAwgStateTimeout()
|
||||
{
|
||||
if (m_state != Vpn::ConnectionState::Connecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString serverId = m_serversController->getDefaultServerId();
|
||||
if (serverId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const DockerContainer container = m_serversController->getDefaultContainer(serverId);
|
||||
const Proto proto = ContainerUtils::defaultProtocol(container);
|
||||
if (proto != Proto::Awg) {
|
||||
return;
|
||||
}
|
||||
|
||||
closeConnection();
|
||||
|
||||
QTimer::singleShot(1000, this, [this, serverId]() {
|
||||
if (m_isConnected || m_isConnectionInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug().noquote() << "AWG connect timeout: trying to switch API protocol to VLESS and reload config from gateway";
|
||||
|
||||
m_pendingApiServerId = serverId;
|
||||
m_apiSwitched = false;
|
||||
m_waitingForApiUpdate = true;
|
||||
|
||||
emit requestSetProcessedServer(serverId);
|
||||
emit requestSetCurrentProtocol(serverId, QStringLiteral("vless"));
|
||||
emit requestUpdateServiceFromGateway(serverId, QString(), QString(), true);
|
||||
});
|
||||
}
|
||||
|
||||
void ConnectionUiController::onUpdateServiceFromGatewayCompleted(bool success, const QString &serverId)
|
||||
{
|
||||
if (!m_waitingForApiUpdate || m_pendingApiServerId != serverId) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_waitingForApiUpdate = false;
|
||||
m_apiSwitched = success;
|
||||
|
||||
if (success) {
|
||||
const QMap<DockerContainer, ContainerConfig> containersMap = m_serversController->getServerContainersMap(serverId);
|
||||
if (containersMap.contains(DockerContainer::Xray)) {
|
||||
qDebug().noquote() << "AWG connect timeout (10s), switching default container to Xray and reconnecting";
|
||||
m_serversController->setDefaultContainer(serverId, DockerContainer::Xray);
|
||||
emit requestSetCurrentProtocol(serverId, QStringLiteral("vless"));
|
||||
m_pendingApiServerId.clear();
|
||||
|
||||
if (!m_isConnected && !m_isConnectionInProgress) {
|
||||
emit prepareConfig();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug().noquote() << "AWG connect timeout: no Xray available (API switch success ="
|
||||
<< (m_apiSwitched ? "YES" : "NO") << ")";
|
||||
m_pendingApiServerId.clear();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define CONNECTIONUICONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
@@ -41,14 +40,6 @@ public slots:
|
||||
|
||||
void onTranslationsUpdated();
|
||||
|
||||
public slots:
|
||||
void checkAndStartAwgStateTimer();
|
||||
void onUpdateServiceFromGatewayCompleted(bool success, const QString &serverId);
|
||||
void onCurrentContainerUpdated();
|
||||
|
||||
private slots:
|
||||
void onAwgStateTimeout();
|
||||
|
||||
signals:
|
||||
void connectionStateChanged();
|
||||
|
||||
@@ -58,19 +49,9 @@ signals:
|
||||
void preparingConfig();
|
||||
void prepareConfig();
|
||||
|
||||
// serverId + protocol — both carried so the receiver doesn't need to re-read default server
|
||||
void requestSetCurrentProtocol(const QString &serverId, const QString &protocol);
|
||||
void requestUpdateServiceFromGateway(const QString &serverId, const QString &newCountryCode,
|
||||
const QString &newCountryName, bool reloadServiceConfig);
|
||||
void requestSetProcessedServer(const QString &serverId);
|
||||
void reconnectWithUpdatedContainer(const QString &message);
|
||||
|
||||
private:
|
||||
Vpn::ConnectionState getCurrentConnectionState();
|
||||
|
||||
static constexpr int kAwgSwitchTimeoutMs = 10000;
|
||||
|
||||
QTimer m_awgStateTimer;
|
||||
ConnectionController* m_connectionController;
|
||||
ServersController* m_serversController;
|
||||
|
||||
@@ -79,10 +60,6 @@ private:
|
||||
QString m_connectionStateText = tr("Connect");
|
||||
|
||||
Vpn::ConnectionState m_state;
|
||||
|
||||
QString m_pendingApiServerId;
|
||||
bool m_apiSwitched = false;
|
||||
bool m_waitingForApiUpdate = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -45,8 +45,6 @@ namespace PageLoader
|
||||
PageSettingsApiDevices,
|
||||
PageSettingsApiSubscriptionKey,
|
||||
PageSettingsKillSwitchExceptions,
|
||||
PageSettingsConnectionType,
|
||||
PageSettingsConnectionProtocols,
|
||||
|
||||
PageServiceSftpSettings,
|
||||
PageServiceTorWebsiteSettings,
|
||||
|
||||
@@ -112,7 +112,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -279,7 +279,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -17,6 +17,10 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
enableTimer: false
|
||||
|
||||
property bool portDirty: false
|
||||
|
||||
function formatTransport(value) {
|
||||
if (value === "raw") return "RAW (TCP)"
|
||||
if (value === "xhttp") return "XHTTP"
|
||||
@@ -39,8 +43,8 @@ PageType {
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + PageController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
onActiveFocusChanged: {
|
||||
if (backButton.enabled && backButton.activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
@@ -60,8 +64,6 @@ PageType {
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
property alias focusItemId: textFieldWithHeaderType.textField
|
||||
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
@@ -107,13 +109,32 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
enabled: listView.enabled
|
||||
headerText: qsTr("Port")
|
||||
textField.text: port
|
||||
|
||||
Binding {
|
||||
target: textFieldWithHeaderType.textField
|
||||
property: "text"
|
||||
value: port
|
||||
when: !textFieldWithHeaderType.textField.activeFocus
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
|
||||
textField.maximumLength: 5
|
||||
textField.validator: IntValidator {
|
||||
bottom: 1; top: 65535
|
||||
}
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus && textField.text === "" && port !== "") {
|
||||
textField.text = port
|
||||
}
|
||||
}
|
||||
textField.onTextChanged: {
|
||||
root.portDirty = (textField.text !== port)
|
||||
}
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== port) port = textField.text
|
||||
if (textField.text !== port) {
|
||||
port = textField.text
|
||||
}
|
||||
root.portDirty = false
|
||||
}
|
||||
checkEmptyText: true
|
||||
}
|
||||
@@ -172,9 +193,8 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
visible: listView.enabled
|
||||
&& (XrayConfigModel.hasUnsavedChanges
|
||||
|| textFieldWithHeaderType.textField.text !== port)
|
||||
enabled: visible && textFieldWithHeaderType.errorText === ""
|
||||
&& (XrayConfigModel.hasUnsavedChanges || root.portDirty)
|
||||
enabled: visible && textFieldWithHeaderType.textField.text !== ""
|
||||
text: qsTr("Save")
|
||||
onClicked: function() {
|
||||
forceActiveFocus()
|
||||
|
||||
@@ -742,7 +742,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -95,7 +95,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -211,7 +211,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -208,7 +208,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -263,16 +263,46 @@ PageType {
|
||||
&& root.isSubscriptionRenewalAvailable && !root.isInAppPurchase
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
id: switcher
|
||||
|
||||
readonly property bool isVlessProtocol: SubscriptionUiController.isVlessProtocol(ServersUiController.processedServerId)
|
||||
readonly property bool isProtocolSwitchBlocked: ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
visible: ApiAccountInfoModel.data("isProtocolSelectionSupported")
|
||||
enabled: !switcher.isProtocolSwitchBlocked
|
||||
|
||||
text: qsTr("Use VLESS protocol")
|
||||
checked: switcher.isVlessProtocol
|
||||
onToggled: function() {
|
||||
if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
||||
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
|
||||
} else {
|
||||
PageController.showBusyIndicator(true)
|
||||
SubscriptionUiController.setCurrentProtocol(ServersUiController.processedServerId, switcher.isVlessProtocol ? "awg" : "vless")
|
||||
SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, "", "", true)
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {
|
||||
visible: footer.isVisibleForAmneziaFree
|
||||
}
|
||||
|
||||
WarningType {
|
||||
id: warning
|
||||
|
||||
Layout.topMargin: visible ? 24 : 0
|
||||
Layout.topMargin: 24
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||
|
||||
backGroundColor: AmneziaStyle.color.translucentRichBrown
|
||||
|
||||
@@ -289,26 +319,12 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: connectionSwitcher
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: warning.visible ? 16 : 0
|
||||
text: qsTr("Connection")
|
||||
descriptionText: qsTr("Protocol selection and local proxy setup")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsConnectionType)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: vpnKey
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: warning.visible ? 16 : 0
|
||||
|
||||
visible: footer.isVisibleForAmneziaFree
|
||||
|
||||
text: qsTr("Subscription Key")
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Config"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
// Protocol to re-assert after updateServiceFromGateway completes (empty = auto)
|
||||
property string pendingProtocol: ""
|
||||
property bool waitingForGatewayUpdate: false
|
||||
|
||||
Timer {
|
||||
id: updateProtocolTimer
|
||||
interval: 100
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
var serverId = ServersUiController.getServerId(ServersUiController.processedServerIndex)
|
||||
root.waitingForGatewayUpdate = true
|
||||
SubscriptionUiController.updateServiceFromGateway(serverId, "", "", true)
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentProtocol() {
|
||||
try {
|
||||
if (SubscriptionUiController.isVlessProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex))) {
|
||||
return "vless"
|
||||
}
|
||||
|
||||
if (SubscriptionUiController.isAwgProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex))) {
|
||||
return "awg"
|
||||
}
|
||||
|
||||
// If neither VLESS nor AWG, it's auto mode
|
||||
return "auto"
|
||||
} catch (e) {
|
||||
console.log("Error getting current protocol:", e)
|
||||
return "auto"
|
||||
}
|
||||
}
|
||||
|
||||
property string currentProtocol: "auto"
|
||||
|
||||
Connections {
|
||||
target: ServersModel
|
||||
function onDataChanged() {
|
||||
if (!root || !root.visible) {
|
||||
return
|
||||
}
|
||||
Qt.callLater(function() {
|
||||
try {
|
||||
if (root && root.visible && typeof root.getCurrentProtocol === "function") {
|
||||
root.currentProtocol = root.getCurrentProtocol()
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error in ServersModel.onDataChanged:", e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SubscriptionUiController
|
||||
function onUpdateServerFromApiFinished() {
|
||||
if (!root.waitingForGatewayUpdate) {
|
||||
return
|
||||
}
|
||||
root.waitingForGatewayUpdate = false
|
||||
|
||||
// Re-assert the protocol the user chose (gateway reload may have reset it)
|
||||
if (root.pendingProtocol !== "") {
|
||||
var protocolToSet = root.pendingProtocol === "auto" ? "" : root.pendingProtocol
|
||||
SubscriptionUiController.setCurrentProtocol(
|
||||
ServersUiController.getServerId(ServersUiController.processedServerIndex),
|
||||
protocolToSet)
|
||||
}
|
||||
|
||||
root.currentProtocol = root.getCurrentProtocol()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.currentProtocol = root.getCurrentProtocol()
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
try {
|
||||
if (typeof root.getCurrentProtocol === "function") {
|
||||
root.currentProtocol = root.getCurrentProtocol()
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error in onVisibleChanged:", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
flickable.contentY = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: flickable
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
contentHeight: content.height
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
|
||||
headerText: qsTr("VPN Protocol")
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: protocolButtonGroup
|
||||
}
|
||||
|
||||
VerticalRadioButton {
|
||||
id: autoProtocolButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
ButtonGroup.group: protocolButtonGroup
|
||||
checked: root.currentProtocol === "auto"
|
||||
enabled: !ConnectionController.isConnected || !ServersUiController.isDefaultServerCurrentlyProcessed()
|
||||
|
||||
text: qsTr("Choose automatically")
|
||||
descriptionText: qsTr("AmneziaWG is used by default. If the connection is unstable, the app will switch to VLESS. On the next launch, AmneziaWG will be tried again")
|
||||
|
||||
onClicked: function() {
|
||||
if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
||||
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
|
||||
return
|
||||
}
|
||||
|
||||
SubscriptionUiController.setCurrentProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex), "")
|
||||
root.pendingProtocol = "auto"
|
||||
updateProtocolTimer.start()
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: this.clicked()
|
||||
Keys.onReturnPressed: this.clicked()
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
VerticalRadioButton {
|
||||
id: awgProtocolButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
ButtonGroup.group: protocolButtonGroup
|
||||
checked: root.currentProtocol === "awg"
|
||||
enabled: !ConnectionController.isConnected || !ServersUiController.isDefaultServerCurrentlyProcessed()
|
||||
|
||||
text: qsTr("AmneziaWG")
|
||||
|
||||
onClicked: function() {
|
||||
if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
||||
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
|
||||
return
|
||||
}
|
||||
|
||||
root.currentProtocol = "awg"
|
||||
root.pendingProtocol = "awg"
|
||||
SubscriptionUiController.setCurrentProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex), "awg")
|
||||
updateProtocolTimer.start()
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: this.clicked()
|
||||
Keys.onReturnPressed: this.clicked()
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
VerticalRadioButton {
|
||||
id: vlessProtocolButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
ButtonGroup.group: protocolButtonGroup
|
||||
checked: root.currentProtocol === "vless"
|
||||
enabled: !ConnectionController.isConnected || !ServersUiController.isDefaultServerCurrentlyProcessed()
|
||||
|
||||
text: qsTr("XRay VLESS Reality")
|
||||
|
||||
onClicked: function() {
|
||||
if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
||||
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
|
||||
return
|
||||
}
|
||||
|
||||
root.currentProtocol = "vless"
|
||||
root.pendingProtocol = "vless"
|
||||
SubscriptionUiController.setCurrentProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex), "vless")
|
||||
updateProtocolTimer.start()
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: this.clicked()
|
||||
Keys.onReturnPressed: this.clicked()
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Config"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
header: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: qsTr("Connection")
|
||||
}
|
||||
}
|
||||
|
||||
model: 1
|
||||
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
LabelWithButtonType {
|
||||
id: vpnProtocolButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
text: qsTr("VPN protocol")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsConnectionProtocols)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Local proxy
|
||||
// DividerType {}
|
||||
// LabelWithButtonType {
|
||||
// id: localProxyButton
|
||||
// Layout.fillWidth: true
|
||||
// Layout.leftMargin: 16
|
||||
// Layout.rightMargin: 16
|
||||
// text: qsTr("Local proxy")
|
||||
// descriptionText: qsTr("Running: 127.0.0.1:1080")
|
||||
// rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
// clickedFunction: function() {}
|
||||
// }
|
||||
// DividerType {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,8 +102,6 @@
|
||||
<file>Pages2/PageSettingsAppSplitTunneling.qml</file>
|
||||
<file>Pages2/PageSettingsBackup.qml</file>
|
||||
<file>Pages2/PageSettingsConnection.qml</file>
|
||||
<file>Pages2/PageSettingsConnectionProtocols.qml</file>
|
||||
<file>Pages2/PageSettingsConnectionType.qml</file>
|
||||
<file>Pages2/PageSettingsDns.qml</file>
|
||||
<file>Pages2/PageSettingsKillSwitch.qml</file>
|
||||
<file>Pages2/PageSettingsKillSwitchExceptions.qml</file>
|
||||
|
||||
Reference in New Issue
Block a user