feat: новая функция
This commit is contained in:
@@ -241,7 +241,7 @@ services:
|
|||||||
- 9.9.9.9 # Quad9 (безопасность + блокировка вредоносных доменов)
|
- 9.9.9.9 # Quad9 (безопасность + блокировка вредоносных доменов)
|
||||||
- 8.8.8.8 # Google (надежность, fallback)
|
- 8.8.8.8 # Google (надежность, fallback)
|
||||||
volumes:
|
volumes:
|
||||||
- ~/.ssh:/root/.ssh:rw
|
- ~/.ssh:/home/webssh/.ssh:rw
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro # Только чтение для безопасности
|
- /var/run/docker.sock:/var/run/docker.sock:ro # Только чтение для безопасности
|
||||||
- /tmp:/tmp # для временных файлов
|
- /tmp:/tmp # для временных файлов
|
||||||
- ./ssl:/app/ssl # для доступа к ключу шифрования
|
- ./ssl:/app/ssl # для доступа к ключу шифрования
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const cors = require('cors');
|
|||||||
const { exec } = require('child_process');
|
const { exec } = require('child_process');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const os = require('os');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const WebSocket = require('ws');
|
const WebSocket = require('ws');
|
||||||
|
|
||||||
@@ -15,6 +16,8 @@ const { createAllUsers } = require('./utils/userUtils');
|
|||||||
const { cleanupVdsServer, setupRootSshKeys, disablePasswordAuth, setupFirewall } = require('./utils/cleanupUtils');
|
const { cleanupVdsServer, setupRootSshKeys, disablePasswordAuth, setupFirewall } = require('./utils/cleanupUtils');
|
||||||
const { createSshKeys } = require('./utils/localUtils');
|
const { createSshKeys } = require('./utils/localUtils');
|
||||||
|
|
||||||
|
const PUBLIC_KEY_PATH = path.join(os.homedir(), '.ssh', 'id_rsa.pub');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
@@ -321,7 +324,7 @@ app.post('/vds/setup', logRequest, async (req, res) => {
|
|||||||
sendWebSocketLog('success', '✅ SSH ключи созданы', 'ssh_keys', 20);
|
sendWebSocketLog('success', '✅ SSH ключи созданы', 'ssh_keys', 20);
|
||||||
|
|
||||||
// Читаем созданный публичный ключ с хоста
|
// Читаем созданный публичный ключ с хоста
|
||||||
const publicKeyContent = await fs.readFile('/root/.ssh/id_rsa.pub', 'utf8');
|
const publicKeyContent = await fs.readFile(PUBLIC_KEY_PATH, 'utf8');
|
||||||
const publicKeyLine = publicKeyContent.trim();
|
const publicKeyLine = publicKeyContent.trim();
|
||||||
|
|
||||||
// 2. Настройка SSH ключей для root
|
// 2. Настройка SSH ключей для root
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
const { exec } = require('child_process');
|
const { exec } = require('child_process');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
const log = require('./logger');
|
const log = require('./logger');
|
||||||
|
|
||||||
|
const sshDir = path.join(os.homedir(), '.ssh');
|
||||||
|
const privateKeyPath = path.join(sshDir, 'id_rsa');
|
||||||
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
||||||
|
const sshConfigPath = path.join(sshDir, 'config');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Выполнение команд локально (на хосте)
|
* Выполнение команд локально (на хосте)
|
||||||
*/
|
*/
|
||||||
@@ -24,20 +31,20 @@ const createSshKeys = async (email) => {
|
|||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// Сначала исправляем права доступа к SSH конфигу
|
// Сначала исправляем права доступа к SSH конфигу
|
||||||
exec('chmod 600 /root/.ssh/config 2>/dev/null || true', (configError) => {
|
exec(`mkdir -p "${sshDir}" && chmod 700 "${sshDir}" && chmod 600 "${sshConfigPath}" 2>/dev/null || true`, (configError) => {
|
||||||
if (configError) {
|
if (configError) {
|
||||||
log.warn('Не удалось исправить права доступа к SSH конфигу: ' + configError.message);
|
log.warn('Не удалось исправить права доступа к SSH конфигу: ' + configError.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаем SSH ключи
|
// Создаем SSH ключи
|
||||||
exec(`ssh-keygen -t rsa -b 4096 -C "${email}" -f ~/.ssh/id_rsa -N ""`, (error, stdout, stderr) => {
|
exec(`ssh-keygen -t rsa -b 4096 -C "${email}" -f "${privateKeyPath}" -N ""`, (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
log.error('Ошибка создания SSH ключей: ' + error.message);
|
log.error('Ошибка создания SSH ключей: ' + error.message);
|
||||||
} else {
|
} else {
|
||||||
log.success('SSH ключи успешно созданы на хосте');
|
log.success('SSH ключи успешно созданы на хосте');
|
||||||
|
|
||||||
// Устанавливаем правильные права доступа к созданным ключам
|
// Устанавливаем правильные права доступа к созданным ключам
|
||||||
exec('chmod 600 /root/.ssh/id_rsa && chmod 644 /root/.ssh/id_rsa.pub', (permError) => {
|
exec(`chmod 600 "${privateKeyPath}" && chmod 644 "${publicKeyPath}"`, (permError) => {
|
||||||
if (permError) {
|
if (permError) {
|
||||||
log.warn('Не удалось установить права доступа к SSH ключам: ' + permError.message);
|
log.warn('Не удалось установить права доступа к SSH ключам: ' + permError.message);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
const { exec } = require('child_process');
|
const { exec } = require('child_process');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
const log = require('./logger');
|
const log = require('./logger');
|
||||||
|
|
||||||
/**
|
const sshDir = path.join(os.homedir(), '.ssh');
|
||||||
* Исправление прав доступа к SSH конфигурации
|
const privateKeyPath = path.join(sshDir, 'id_rsa');
|
||||||
*/
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
||||||
const fixSshPermissions = async () => {
|
const sshConfigPath = path.join(sshDir, 'config');
|
||||||
return new Promise((resolve) => {
|
|
||||||
// Исправляем владельца и права доступа к SSH конфигу
|
const ensureSshPermissions = async () => {
|
||||||
exec('chown root:root /root/.ssh/config 2>/dev/null || true && chmod 600 /root/.ssh/config 2>/dev/null || true', (error) => {
|
try {
|
||||||
if (error) {
|
await fs.ensureDir(sshDir);
|
||||||
log.warn('Не удалось исправить права доступа к SSH конфигу: ' + error.message);
|
await fs.chmod(sshDir, 0o700).catch(() => {});
|
||||||
} else {
|
await fs.chmod(privateKeyPath, 0o600).catch(() => {});
|
||||||
log.info('Права доступа к SSH конфигу исправлены');
|
await fs.chmod(publicKeyPath, 0o644).catch(() => {});
|
||||||
|
await fs.chmod(sshConfigPath, 0o600).catch(() => {});
|
||||||
|
} catch (error) {
|
||||||
|
log.warn('Не удалось скорректировать права доступа к SSH директории: ' + error.message);
|
||||||
}
|
}
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Выполнение SSH команд с поддержкой ключей и пароля
|
|
||||||
*/
|
|
||||||
const execSshCommand = async (command, options = {}) => {
|
const execSshCommand = async (command, options = {}) => {
|
||||||
const {
|
const {
|
||||||
sshHost,
|
sshHost,
|
||||||
@@ -30,11 +30,16 @@ const execSshCommand = async (command, options = {}) => {
|
|||||||
vdsIp
|
vdsIp
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
// Исправляем права доступа к SSH конфигу перед выполнением команды
|
await ensureSshPermissions();
|
||||||
await fixSshPermissions();
|
|
||||||
|
|
||||||
// Сначала пробуем подключиться с SSH ключами (без BatchMode для возможности fallback на пароль)
|
const privateKeyExists = await fs.pathExists(privateKeyPath);
|
||||||
let sshCommand = `ssh -p ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sshConnectUser}@${sshHost || vdsIp} "${command.replace(/"/g, '\\"')}"`;
|
const escapedCommand = command.replace(/"/g, '\\"');
|
||||||
|
|
||||||
|
let sshCommand = `ssh -p ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sshConnectUser}@${sshHost || vdsIp} "${escapedCommand}"`;
|
||||||
|
|
||||||
|
if (privateKeyExists) {
|
||||||
|
sshCommand = `ssh -i "${privateKeyPath}" -p ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sshConnectUser}@${sshHost || vdsIp} "${escapedCommand}"`;
|
||||||
|
}
|
||||||
|
|
||||||
log.info(`🔍 Выполняем SSH команду: ${sshCommand}`);
|
log.info(`🔍 Выполняем SSH команду: ${sshCommand}`);
|
||||||
|
|
||||||
@@ -43,11 +48,8 @@ const execSshCommand = async (command, options = {}) => {
|
|||||||
log.info(`📤 SSH результат - код: ${error ? error.code : 0}, stdout: "${stdout}", stderr: "${stderr}"`);
|
log.info(`📤 SSH результат - код: ${error ? error.code : 0}, stdout: "${stdout}", stderr: "${stderr}"`);
|
||||||
|
|
||||||
if (error && error.code === 255 && sshConnectPassword) {
|
if (error && error.code === 255 && sshConnectPassword) {
|
||||||
// Если подключение с ключами не удалось, пробуем с паролем
|
|
||||||
log.info('SSH ключи не сработали, пробуем с паролем...');
|
log.info('SSH ключи не сработали, пробуем с паролем...');
|
||||||
const passwordCommand = `sshpass -p "${sshConnectPassword}" ssh -p ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sshConnectUser}@${sshHost || vdsIp} "${command.replace(/"/g, '\\"')}"`;
|
const passwordCommand = `sshpass -p "${sshConnectPassword}" ssh -p ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sshConnectUser}@${sshHost || vdsIp} "${escapedCommand}"`;
|
||||||
|
|
||||||
log.info(`🔍 Выполняем SSH команду с паролем: ${passwordCommand}`);
|
|
||||||
|
|
||||||
exec(passwordCommand, (passwordError, passwordStdout, passwordStderr) => {
|
exec(passwordCommand, (passwordError, passwordStdout, passwordStderr) => {
|
||||||
log.info(`📤 SSH с паролем результат - код: ${passwordError ? passwordError.code : 0}, stdout: "${passwordStdout}", stderr: "${passwordStderr}"`);
|
log.info(`📤 SSH с паролем результат - код: ${passwordError ? passwordError.code : 0}, stdout: "${passwordStdout}", stderr: "${passwordStderr}"`);
|
||||||
@@ -68,9 +70,6 @@ const execSshCommand = async (command, options = {}) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Выполнение SCP команд с поддержкой ключей и пароля
|
|
||||||
*/
|
|
||||||
const execScpCommand = async (sourcePath, targetPath, options = {}) => {
|
const execScpCommand = async (sourcePath, targetPath, options = {}) => {
|
||||||
const {
|
const {
|
||||||
sshHost,
|
sshHost,
|
||||||
@@ -80,15 +79,19 @@ const execScpCommand = async (sourcePath, targetPath, options = {}) => {
|
|||||||
vdsIp
|
vdsIp
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
// Исправляем права доступа к SSH конфигу перед выполнением команды
|
await ensureSshPermissions();
|
||||||
await fixSshPermissions();
|
|
||||||
|
|
||||||
const scpCommand = `scp -P ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sourcePath} ${sshConnectUser}@${sshHost || vdsIp}:${targetPath}`;
|
const privateKeyExists = await fs.pathExists(privateKeyPath);
|
||||||
|
|
||||||
|
let scpCommand = `scp -P ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sourcePath} ${sshConnectUser}@${sshHost || vdsIp}:${targetPath}`;
|
||||||
|
|
||||||
|
if (privateKeyExists) {
|
||||||
|
scpCommand = `scp -i "${privateKeyPath}" -P ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sourcePath} ${sshConnectUser}@${sshHost || vdsIp}:${targetPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
exec(scpCommand, (error, stdout, stderr) => {
|
exec(scpCommand, (error, stdout, stderr) => {
|
||||||
if (error && error.code === 255 && sshConnectPassword) {
|
if (error && error.code === 255 && sshConnectPassword) {
|
||||||
// Если SCP с ключами не удался, пробуем с паролем
|
|
||||||
log.info('SCP с ключами не сработал, пробуем с паролем...');
|
log.info('SCP с ключами не сработал, пробуем с паролем...');
|
||||||
const passwordScpCommand = `sshpass -p "${sshConnectPassword}" scp -P ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sourcePath} ${sshConnectUser}@${sshHost || vdsIp}:${targetPath}`;
|
const passwordScpCommand = `sshpass -p "${sshConnectPassword}" scp -P ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${sourcePath} ${sshConnectUser}@${sshHost || vdsIp}:${targetPath}`;
|
||||||
|
|
||||||
@@ -123,5 +126,5 @@ const execScpCommand = async (sourcePath, targetPath, options = {}) => {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
execSshCommand,
|
execSshCommand,
|
||||||
execScpCommand,
|
execScpCommand,
|
||||||
fixSshPermissions
|
fixSshPermissions: ensureSshPermissions
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user