Compare commits

..

5 Commits

Author SHA1 Message Date
dranik
77418c7f79 fix disconnect Host 2026-06-04 17:35:44 +03:00
dranik
f975a75e23 remove comment 2026-06-04 15:56:37 +03:00
dranik
87b0a98460 reset file 2026-06-04 15:55:46 +03:00
dranik
17b34f7891 fix moved to if-statement 2026-06-04 15:53:24 +03:00
dranik
1c32561dd4 Fix: selfhosted server installations 2026-06-04 15:25:40 +03:00
10 changed files with 130 additions and 111 deletions

View File

@@ -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;

View File

@@ -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"

View File

@@ -72,21 +72,6 @@ namespace
}
return false;
}
bool usesNamedDataVolume(DockerContainer container)
{
return container == DockerContainer::MtProxy || container == DockerContainer::Telemt;
}
QString buildRemoveContainerScript(const amnezia::ScriptVars &vars, bool removeDataVolume)
{
QString script = SshSession::replaceVars(amnezia::scriptData(SharedScriptType::remove_container), vars);
if (removeDataVolume) {
script += QLatin1String("\nsudo docker volume rm -f $CONTAINER_NAME-data 2>/dev/null || true");
script = SshSession::replaceVars(script, vars);
}
return script;
}
}
InstallController::InstallController(SecureServersRepository *serversRepository,
@@ -105,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);
@@ -135,10 +119,14 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
return e;
qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished";
const amnezia::ScriptVars removeContainerVars =
amnezia::ScriptVars removeContainerVars =
amnezia::genBaseVars(credentials, container, QString(), QString());
const bool removeDataVolume = !isUpdate && usesNamedDataVolume(container);
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
if (!isUpdate) {
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
}
sshSession.runScript(credentials,
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
removeContainerVars));
qDebug().noquote() << "InstallController::setupContainer removeContainer finished";
qDebug().noquote() << "buildContainerWorker start";
@@ -210,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) {
@@ -991,11 +979,12 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
return ErrorCode::InternalError;
}
SshSession sshSession(this);
const amnezia::ScriptVars removeContainerVars =
amnezia::ScriptVars removeContainerVars =
amnezia::genBaseVars(credentials, container, QString(), QString());
const bool removeDataVolume = usesNamedDataVolume(container);
ErrorCode errorCode =
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
ErrorCode errorCode = sshSession.runScript(
credentials,
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), removeContainerVars));
if (errorCode == ErrorCode::NoError) {
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
@@ -1043,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);
}
@@ -1152,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;
}
@@ -1222,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;
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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)
{

View File

@@ -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);

View File

@@ -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

View File

@@ -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;\

View File

@@ -1,3 +1,4 @@
sudo docker stop $CONTAINER_NAME;\
sudo docker rm -fv $CONTAINER_NAME;\
sudo docker rmi $CONTAINER_NAME;
sudo docker rmi $CONTAINER_NAME;\
test "$REMOVE_CONTAINER_DATA" = "1" && sudo docker volume rm -f ${CONTAINER_NAME}-data 2>/dev/null || true