shadowsocks impl
This commit is contained in:
parent
95cbb07cbb
commit
ec9ae0ef4f
20 changed files with 380 additions and 117 deletions
|
@ -12,6 +12,12 @@ enum class Protocol {
|
|||
WireGuard
|
||||
};
|
||||
|
||||
enum class DockerContainer {
|
||||
OpenVpn,
|
||||
ShadowSocks,
|
||||
WireGuard
|
||||
};
|
||||
|
||||
struct ServerCredentials
|
||||
{
|
||||
QString hostName;
|
||||
|
|
|
@ -144,46 +144,77 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
|||
return connData;
|
||||
}
|
||||
|
||||
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials, ErrorCode *errorCode)
|
||||
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
|
||||
Protocol proto, ErrorCode *errorCode)
|
||||
{
|
||||
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
|
||||
connData.host = credentials.hostName;
|
||||
|
||||
if (connData.privKey.isEmpty() || connData.request.isEmpty()) {
|
||||
*errorCode = ErrorCode::EasyRsaExecutableMissing;
|
||||
if (errorCode) *errorCode = ErrorCode::EasyRsaExecutableMissing;
|
||||
return connData;
|
||||
}
|
||||
|
||||
QString reqFileName = QString("/opt/amneziavpn_data/clients/%1.req").arg(connData.clientId);
|
||||
ErrorCode e = ServerController::uploadTextFileToContainer(credentials, connData.request, reqFileName);
|
||||
if (e) {
|
||||
*errorCode = e;
|
||||
|
||||
DockerContainer container;
|
||||
if (proto == Protocol::OpenVpn) container = DockerContainer::OpenVpn;
|
||||
else if (proto == Protocol::ShadowSocks) container = DockerContainer::ShadowSocks;
|
||||
else {
|
||||
if (errorCode) *errorCode = ErrorCode::InternalError;
|
||||
return connData;
|
||||
}
|
||||
|
||||
ServerController::signCert(credentials, connData.clientId);
|
||||
|
||||
connData.caCert = ServerController::getTextFileFromContainer(credentials, ServerController::caCertPath(), &e);
|
||||
connData.clientCert = ServerController::getTextFileFromContainer(credentials, ServerController::clientCertPath() + QString("%1.crt").arg(connData.clientId), &e);
|
||||
ErrorCode e = ServerController::uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
|
||||
if (e) {
|
||||
*errorCode = e;
|
||||
if (errorCode) *errorCode = e;
|
||||
return connData;
|
||||
}
|
||||
|
||||
connData.taKey = ServerController::getTextFileFromContainer(credentials, ServerController::taKeyPath(), &e);
|
||||
e = ServerController::signCert(container, credentials, connData.clientId);
|
||||
if (e) {
|
||||
if (errorCode) *errorCode = e;
|
||||
return connData;
|
||||
}
|
||||
|
||||
connData.caCert = ServerController::getTextFileFromContainer(container, credentials, ServerController::caCertPath(), &e);
|
||||
connData.clientCert = ServerController::getTextFileFromContainer(container, credentials, ServerController::clientCertPath() + QString("%1.crt").arg(connData.clientId), &e);
|
||||
if (e) {
|
||||
if (errorCode) *errorCode = e;
|
||||
return connData;
|
||||
}
|
||||
|
||||
connData.taKey = ServerController::getTextFileFromContainer(container, credentials, ServerController::taKeyPath(), &e);
|
||||
|
||||
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
|
||||
if (errorCode) *errorCode = ErrorCode::RemoteProcessCrashError;
|
||||
}
|
||||
|
||||
return connData;
|
||||
}
|
||||
|
||||
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, ErrorCode *errorCode)
|
||||
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials,
|
||||
Protocol proto, ErrorCode *errorCode)
|
||||
{
|
||||
QFile configTemplFile(":/server_scripts/template.ovpn");
|
||||
QFile configTemplFile;
|
||||
if (proto == Protocol::OpenVpn)
|
||||
configTemplFile.setFileName(":/server_scripts/template_openvpn.ovpn");
|
||||
else if (proto == Protocol::ShadowSocks) {
|
||||
configTemplFile.setFileName(":/server_scripts/template_shadowsocks.ovpn");
|
||||
}
|
||||
|
||||
configTemplFile.open(QIODevice::ReadOnly);
|
||||
QString config = configTemplFile.readAll();
|
||||
|
||||
ConnectionData connData = prepareOpenVpnConfig(credentials, errorCode);
|
||||
ConnectionData connData = prepareOpenVpnConfig(credentials, proto, errorCode);
|
||||
|
||||
if (proto == Protocol::OpenVpn)
|
||||
config.replace("$PROTO", "udp");
|
||||
else if (proto == Protocol::ShadowSocks) {
|
||||
config.replace("$PROTO", "tcp");
|
||||
config.replace("$LOCAL_PROXY_PORT", QString::number(ServerController::ssContainerPort()));
|
||||
}
|
||||
|
||||
config.replace("$PROTO", "udp");
|
||||
config.replace("$REMOTE_HOST", connData.host);
|
||||
config.replace("$REMOTE_PORT", "1194");
|
||||
config.replace("$CA_CERT", connData.caCert);
|
||||
|
|
|
@ -22,7 +22,8 @@ public:
|
|||
QString host; // host ip
|
||||
};
|
||||
|
||||
static QString genOpenVpnConfig(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr);
|
||||
static QString genOpenVpnConfig(const ServerCredentials &credentials, Protocol proto,
|
||||
ErrorCode *errorCode = nullptr);
|
||||
|
||||
private:
|
||||
static QString getRandomString(int len);
|
||||
|
@ -34,7 +35,8 @@ private:
|
|||
|
||||
static ConnectionData createCertRequest();
|
||||
|
||||
static ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr);
|
||||
static ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials,
|
||||
Protocol proto, ErrorCode *errorCode = nullptr);
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -5,13 +5,25 @@
|
|||
#include <QLoggingCategory>
|
||||
#include <QPointer>
|
||||
#include <QTimer>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "sshconnectionmanager.h"
|
||||
|
||||
|
||||
using namespace QSsh;
|
||||
|
||||
ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams, QString script)
|
||||
QString ServerController::getContainerName(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case(DockerContainer::OpenVpn): return "amnezia-openvpn";
|
||||
case(DockerContainer::ShadowSocks): return "amnezia-shadowsocks";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ServerController::runScript(DockerContainer container,
|
||||
const SshConnectionParameters &sshParams, QString script)
|
||||
{
|
||||
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
|
||||
|
||||
|
@ -26,7 +38,9 @@ ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams,
|
|||
|
||||
const QStringList &lines = script.split("\n", QString::SkipEmptyParts);
|
||||
for (int i = 0; i < lines.count(); i++) {
|
||||
const QString &line = lines.at(i);
|
||||
QString line = lines.at(i);
|
||||
line.replace("$CONTAINER_NAME", getContainerName(container));
|
||||
|
||||
if (line.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
|
@ -52,23 +66,22 @@ ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams,
|
|||
wait.quit();
|
||||
});
|
||||
|
||||
// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
|
||||
// QString s = proc->readAllStandardOutput();
|
||||
// if (s != "." && !s.isEmpty()) {
|
||||
// qDebug().noquote() << s;
|
||||
// }
|
||||
// });
|
||||
// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
|
||||
// QString s = proc->readAllStandardOutput();
|
||||
// if (s != "." && !s.isEmpty()) {
|
||||
// qDebug().noquote() << s;
|
||||
// }
|
||||
// });
|
||||
|
||||
// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
|
||||
// QString s = proc->readAllStandardError();
|
||||
// if (s != "." && !s.isEmpty()) {
|
||||
// qDebug().noquote() << s;
|
||||
// }
|
||||
// });
|
||||
// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
|
||||
// QString s = proc->readAllStandardError();
|
||||
// if (s != "." && !s.isEmpty()) {
|
||||
// qDebug().noquote() << s;
|
||||
// }
|
||||
// });
|
||||
|
||||
proc->start();
|
||||
|
||||
if (i < lines.count() - 1) {
|
||||
if (i < lines.count()) {
|
||||
wait.exec();
|
||||
}
|
||||
|
||||
|
@ -81,13 +94,13 @@ ErrorCode ServerController::runScript(const SshConnectionParameters &sshParams,
|
|||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ServerController::uploadTextFileToContainer(const ServerCredentials &credentials,
|
||||
QString &file, const QString &path)
|
||||
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
||||
const ServerCredentials &credentials, QString &file, const QString &path)
|
||||
{
|
||||
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
|
||||
|
||||
QString script = QString("docker exec -i amneziavpn sh -c \"echo \'%1\' > %2\"").
|
||||
arg(file).arg(path);
|
||||
QString script = QString("docker exec -i %1 sh -c \"echo \'%2\' > %3\"").
|
||||
arg(getContainerName(container)).arg(file).arg(path);
|
||||
|
||||
qDebug().noquote() << script;
|
||||
|
||||
|
@ -116,25 +129,29 @@ ErrorCode ServerController::uploadTextFileToContainer(const ServerCredentials &c
|
|||
wait.quit();
|
||||
});
|
||||
|
||||
// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
|
||||
// qDebug().noquote() << proc->readAllStandardOutput();
|
||||
// });
|
||||
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, [proc](){
|
||||
qDebug().noquote() << proc->readAllStandardOutput();
|
||||
});
|
||||
|
||||
// QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
|
||||
// qDebug().noquote() << proc->readAllStandardError();
|
||||
// });
|
||||
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, [proc](){
|
||||
qDebug().noquote() << proc->readAllStandardError();
|
||||
});
|
||||
|
||||
proc->start();
|
||||
wait.exec();
|
||||
|
||||
// if (proc->isRunning()) {
|
||||
// wait.exec();
|
||||
// }
|
||||
|
||||
return fromSshProcessExitStatus(exitStatus);
|
||||
}
|
||||
|
||||
QString ServerController::getTextFileFromContainer(const ServerCredentials &credentials, const QString &path,
|
||||
ErrorCode *errorCode)
|
||||
QString ServerController::getTextFileFromContainer(DockerContainer container,
|
||||
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode)
|
||||
{
|
||||
QString script = QString("docker exec -i amneziavpn sh -c \"cat \'%1\'\"").
|
||||
arg(path);
|
||||
QString script = QString("docker exec -i %1 sh -c \"cat \'%2\'\"").
|
||||
arg(getContainerName(container)).arg(path);
|
||||
|
||||
qDebug().noquote() << "Copy file from container\n" << script;
|
||||
|
||||
|
@ -162,6 +179,10 @@ QString ServerController::getTextFileFromContainer(const ServerCredentials &cred
|
|||
proc->start();
|
||||
wait.exec();
|
||||
|
||||
// if (proc->isRunning()) {
|
||||
// wait.exec();
|
||||
// }
|
||||
|
||||
if (SshRemoteProcess::ExitStatus(exitStatus) != QSsh::SshRemoteProcess::ExitStatus::NormalExit) {
|
||||
if (errorCode) *errorCode = fromSshProcessExitStatus(exitStatus);
|
||||
}
|
||||
|
@ -169,25 +190,28 @@ QString ServerController::getTextFileFromContainer(const ServerCredentials &cred
|
|||
return proc->readAllStandardOutput();
|
||||
}
|
||||
|
||||
ErrorCode ServerController::signCert(const ServerCredentials &credentials, QString clientId)
|
||||
ErrorCode ServerController::signCert(DockerContainer container,
|
||||
const ServerCredentials &credentials, QString clientId)
|
||||
{
|
||||
QString script_import = QString("docker exec -i amneziavpn bash -c \"cd /opt/amneziavpn_data && "
|
||||
"easyrsa import-req /opt/amneziavpn_data/clients/%1.req %1 &>/dev/null\"")
|
||||
.arg(clientId);
|
||||
QString script_import = QString("docker exec -i %1 bash -c \"cd /opt/amneziavpn_data && "
|
||||
"easyrsa import-req /opt/amneziavpn_data/clients/%2.req %2\"")
|
||||
.arg(getContainerName(container)).arg(clientId);
|
||||
|
||||
QString script_sign = QString("docker exec -i amneziavpn bash -c \"export EASYRSA_BATCH=1; cd /opt/amneziavpn_data && "
|
||||
"easyrsa sign-req client %1 &>/dev/null\"")
|
||||
.arg(clientId);
|
||||
QString script_sign = QString("docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amneziavpn_data && "
|
||||
"easyrsa sign-req client %2\"")
|
||||
.arg(getContainerName(container)).arg(clientId);
|
||||
|
||||
QStringList script {script_import, script_sign};
|
||||
|
||||
return runScript(sshParams(credentials), script.join("\n"));
|
||||
return runScript(container, sshParams(credentials), script.join("\n"));
|
||||
}
|
||||
|
||||
ErrorCode ServerController::checkOpenVpnServer(const ServerCredentials &credentials)
|
||||
ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials)
|
||||
{
|
||||
QString caCert = ServerController::getTextFileFromContainer(credentials, ServerController::caCertPath());
|
||||
QString taKey = ServerController::getTextFileFromContainer(credentials, ServerController::taKeyPath());
|
||||
QString caCert = ServerController::getTextFileFromContainer(container,
|
||||
credentials, ServerController::caCertPath());
|
||||
QString taKey = ServerController::getTextFileFromContainer(container,
|
||||
credentials, ServerController::taKeyPath());
|
||||
|
||||
if (!caCert.isEmpty() && !taKey.isEmpty()) {
|
||||
return ErrorCode::NoError;
|
||||
|
@ -209,15 +233,18 @@ ErrorCode ServerController::fromSshConnectionErrorCode(SshError error)
|
|||
case(QSsh::SshAuthenticationError): return ErrorCode::SshAuthenticationError;
|
||||
case(QSsh::SshClosedByServerError): return ErrorCode::SshClosedByServerError;
|
||||
case(QSsh::SshInternalError): return ErrorCode::SshInternalError;
|
||||
default: return ErrorCode::SshInternalError;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ServerController::fromSshProcessExitStatus(int exitStatus)
|
||||
{
|
||||
qDebug() << exitStatus;
|
||||
switch (SshRemoteProcess::ExitStatus(exitStatus)) {
|
||||
case(SshRemoteProcess::ExitStatus::NormalExit): return ErrorCode::NoError;
|
||||
case(SshRemoteProcess::ExitStatus::FailedToStart): return ErrorCode::FailedToStartRemoteProcessError;
|
||||
case(SshRemoteProcess::ExitStatus::CrashExit): return ErrorCode::RemoteProcessCrashError;
|
||||
default: return ErrorCode::SshInternalError;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,10 +265,24 @@ SshConnectionParameters ServerController::sshParams(const ServerCredentials &cre
|
|||
ErrorCode ServerController::removeServer(const ServerCredentials &credentials, Protocol proto)
|
||||
{
|
||||
QString scriptFileName;
|
||||
DockerContainer container;
|
||||
|
||||
if (proto == Protocol::OpenVpn || proto == Protocol::Any) {
|
||||
scriptFileName = ":/server_scripts/remove_openvpn_server.sh";
|
||||
ErrorCode errorCode;
|
||||
if (proto == Protocol::Any) {
|
||||
removeServer(credentials, Protocol::OpenVpn);
|
||||
removeServer(credentials, Protocol::ShadowSocks);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
else if (proto == Protocol::OpenVpn) {
|
||||
scriptFileName = ":/server_scripts/remove_container.sh";
|
||||
container = DockerContainer::OpenVpn;
|
||||
}
|
||||
else if (proto == Protocol::ShadowSocks) {
|
||||
scriptFileName = ":/server_scripts/remove_container.sh";
|
||||
container = DockerContainer::ShadowSocks;
|
||||
}
|
||||
else return ErrorCode::NotImplementedError;
|
||||
|
||||
|
||||
QString scriptData;
|
||||
|
||||
|
@ -251,7 +292,7 @@ ErrorCode ServerController::removeServer(const ServerCredentials &credentials, P
|
|||
scriptData = file.readAll();
|
||||
if (scriptData.isEmpty()) return ErrorCode::InternalError;
|
||||
|
||||
return runScript(sshParams(credentials), scriptData);
|
||||
return runScript(container, sshParams(credentials), scriptData);
|
||||
}
|
||||
|
||||
ErrorCode ServerController::setupServer(const ServerCredentials &credentials, Protocol proto)
|
||||
|
@ -263,8 +304,10 @@ ErrorCode ServerController::setupServer(const ServerCredentials &credentials, Pr
|
|||
return setupShadowSocksServer(credentials);
|
||||
}
|
||||
else if (proto == Protocol::Any) {
|
||||
return ErrorCode::NotImplementedError;
|
||||
|
||||
// TODO: run concurently
|
||||
return setupOpenVpnServer(credentials);
|
||||
// return setupOpenVpnServer(credentials);
|
||||
//setupShadowSocksServer(credentials);
|
||||
}
|
||||
|
||||
|
@ -281,18 +324,49 @@ ErrorCode ServerController::setupOpenVpnServer(const ServerCredentials &credenti
|
|||
scriptData = file.readAll();
|
||||
if (scriptData.isEmpty()) return ErrorCode::InternalError;
|
||||
|
||||
ErrorCode e = runScript(sshParams(credentials), scriptData);
|
||||
ErrorCode e = runScript(DockerContainer::OpenVpn, sshParams(credentials), scriptData);
|
||||
if (e) return e;
|
||||
|
||||
//return ok;
|
||||
return checkOpenVpnServer(credentials);
|
||||
return checkOpenVpnServer(DockerContainer::OpenVpn, credentials);
|
||||
}
|
||||
|
||||
ErrorCode ServerController::setupShadowSocksServer(const ServerCredentials &credentials)
|
||||
{
|
||||
Q_UNUSED(credentials)
|
||||
// Setup openvpn part
|
||||
QString scriptData;
|
||||
QString scriptFileName = ":/server_scripts/setup_shadowsocks_server.sh";
|
||||
QFile file(scriptFileName);
|
||||
if (! file.open(QIODevice::ReadOnly)) return ErrorCode::InternalError;
|
||||
|
||||
return ErrorCode::NotImplementedError;
|
||||
scriptData = file.readAll();
|
||||
if (scriptData.isEmpty()) return ErrorCode::InternalError;
|
||||
|
||||
ErrorCode e = runScript(DockerContainer::ShadowSocks, sshParams(credentials), scriptData);
|
||||
if (e) return e;
|
||||
|
||||
// Create ss config
|
||||
QJsonObject ssConfig;
|
||||
ssConfig.insert("server", "0.0.0.0");
|
||||
ssConfig.insert("server_port", ssRemotePort());
|
||||
ssConfig.insert("local_port", ssContainerPort());
|
||||
ssConfig.insert("password", credentials.password);
|
||||
ssConfig.insert("timeout", 60);
|
||||
ssConfig.insert("method", ssEncryption());
|
||||
QString configData = QJsonDocument(ssConfig).toJson();
|
||||
QString sSConfigPath = "/opt/amneziavpn_data/ssConfig.json";
|
||||
|
||||
qDebug().noquote() << configData;
|
||||
configData.replace("\"", "\\\"");
|
||||
qDebug().noquote() << configData;
|
||||
|
||||
uploadTextFileToContainer(DockerContainer::ShadowSocks, credentials, configData, sSConfigPath);
|
||||
|
||||
// Start ss
|
||||
QString script = QString("docker exec -i %1 sh -c \"ss-server -c %2 &\"").
|
||||
arg(getContainerName(DockerContainer::ShadowSocks)).arg(sSConfigPath);
|
||||
|
||||
e = runScript(DockerContainer::ShadowSocks, sshParams(credentials), script);
|
||||
return e;
|
||||
}
|
||||
|
||||
SshConnection *ServerController::connectToHost(const SshConnectionParameters &sshParams)
|
||||
|
|
|
@ -22,21 +22,32 @@ public:
|
|||
static QString clientCertPath() { return "/opt/amneziavpn_data/pki/issued/"; }
|
||||
static QString taKeyPath() { return "/opt/amneziavpn_data/ta.key"; }
|
||||
|
||||
static QString getContainerName(amnezia::DockerContainer container);
|
||||
|
||||
static QSsh::SshConnectionParameters sshParams(const ServerCredentials &credentials);
|
||||
|
||||
static ErrorCode removeServer(const ServerCredentials &credentials, Protocol proto);
|
||||
static ErrorCode setupServer(const ServerCredentials &credentials, Protocol proto);
|
||||
|
||||
static ErrorCode checkOpenVpnServer(const ServerCredentials &credentials);
|
||||
static ErrorCode checkOpenVpnServer(DockerContainer container, const ServerCredentials &credentials);
|
||||
|
||||
static ErrorCode uploadTextFileToContainer(const ServerCredentials &credentials, QString &file, const QString &path);
|
||||
static QString getTextFileFromContainer(const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr);
|
||||
static ErrorCode uploadTextFileToContainer(DockerContainer container,
|
||||
const ServerCredentials &credentials, QString &file, const QString &path);
|
||||
|
||||
static ErrorCode signCert(const ServerCredentials &credentials, QString clientId);
|
||||
static QString getTextFileFromContainer(DockerContainer container,
|
||||
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr);
|
||||
|
||||
static ErrorCode signCert(DockerContainer container,
|
||||
const ServerCredentials &credentials, QString clientId);
|
||||
|
||||
static int ssRemotePort() { return 6789; } // TODO move to ShadowSocksDefs.h
|
||||
static int ssContainerPort() { return 8585; } // TODO move to ShadowSocksDefs.h
|
||||
static QString ssEncryption() { return "chacha20-ietf-poly1305"; } // TODO move to ShadowSocksDefs.h
|
||||
|
||||
private:
|
||||
static QSsh::SshConnection *connectToHost(const QSsh::SshConnectionParameters &sshParams);
|
||||
static ErrorCode runScript(const QSsh::SshConnectionParameters &sshParams, QString script);
|
||||
static ErrorCode runScript(DockerContainer container,
|
||||
const QSsh::SshConnectionParameters &sshParams, QString script);
|
||||
|
||||
static ErrorCode setupOpenVpnServer(const ServerCredentials &credentials);
|
||||
static ErrorCode setupShadowSocksServer(const ServerCredentials &credentials);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue