Files
DLE/backend/routes/vds.js

823 lines
32 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
* All rights reserved.
*
* This software is proprietary and confidential.
* Unauthorized copying, modification, or distribution is prohibited.
*
* For licensing inquiries: info@hb3-accelerator.com
* Website: https://hb3-accelerator.com
* GitHub: https://github.com/VC-HB3-Accelerator
*/
const express = require('express');
const router = express.Router();
const { exec } = require('child_process');
const { promisify } = require('util');
const logger = require('../utils/logger');
const { requireAuth } = require('../middleware/auth');
const { requirePermission } = require('../middleware/permissions');
const { PERMISSIONS } = require('../shared/permissions');
const db = require('../db');
const encryptedDb = require('../services/encryptedDatabaseService');
const execAsync = promisify(exec);
/**
* Получить настройки VDS
*/
router.get('/settings', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const encryptionUtils = require('../utils/encryptionUtils');
const encryptionKey = encryptionUtils.getEncryptionKey();
const { rows } = await db.getQuery()(
`SELECT
id,
decrypt_text(domain_encrypted, $1) as domain,
decrypt_text(email_encrypted, $1) as email,
decrypt_text(ubuntu_user_encrypted, $1) as ubuntu_user,
decrypt_text(docker_user_encrypted, $1) as docker_user,
decrypt_text(ssh_host_encrypted, $1) as ssh_host,
ssh_port,
decrypt_text(ssh_user_encrypted, $1) as ssh_user,
decrypt_text(ssh_password_encrypted, $1) as ssh_password,
created_at,
updated_at
FROM vds_settings
ORDER BY id DESC
LIMIT 1`,
[encryptionKey]
);
if (rows.length === 0) {
return res.json({ success: true, settings: null });
}
res.json({
success: true,
settings: {
domain: rows[0].domain,
email: rows[0].email,
ubuntuUser: rows[0].ubuntu_user,
dockerUser: rows[0].docker_user,
sshHost: rows[0].ssh_host,
sshPort: rows[0].ssh_port,
sshUser: rows[0].ssh_user
// sshPassword не возвращаем по соображениям безопасности
}
});
} catch (error) {
logger.error('[VDS] Ошибка получения настроек:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Сохранить настройки VDS
*/
router.post('/settings', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const {
domain,
email,
ubuntuUser,
dockerUser,
sshHost,
sshPort,
sshUser,
sshPassword
} = req.body;
// Если передан только домен (для обратной совместимости)
if (domain && !email && !sshHost) {
const normalizedDomain = domain.trim().toLowerCase().replace(/^https?:\/\//, '').replace(/\/$/, '');
const existing = await encryptedDb.getData('vds_settings', {}, 1);
const settings = {
domain_encrypted: normalizedDomain, // encryptedDb автоматически зашифрует
updated_at: new Date()
};
if (existing.length > 0) {
await encryptedDb.saveData('vds_settings', settings, { id: existing[0].id });
} else {
await encryptedDb.saveData('vds_settings', {
...settings,
created_at: new Date()
});
}
process.env.BASE_URL = `https://${normalizedDomain}`;
// Сбрасываем кэш домена в consentService
const consentService = require('../services/consentService');
consentService.clearDomainCache();
logger.info(`[VDS] Домен сохранен: ${normalizedDomain}`);
return res.json({ success: true, domain: normalizedDomain });
}
// Валидация обязательных полей (пароль опционален при обновлении)
if (!domain || !email || !ubuntuUser || !dockerUser || !sshHost || !sshPort || !sshUser) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны для заполнения (кроме пароля при обновлении)'
});
}
// Нормализуем домен
const normalizedDomain = domain.trim().toLowerCase().replace(/^https?:\/\//, '').replace(/\/$/, '');
// Проверяем существующие настройки
const existing = await encryptedDb.getData('vds_settings', {}, 1);
const encryptionUtils = require('../utils/encryptionUtils');
const encryptionKey = encryptionUtils.getEncryptionKey();
// Подготавливаем данные для сохранения с правильными именами полей для шифрования
const settings = {
domain_encrypted: normalizedDomain, // encryptedDb автоматически зашифрует поля с _encrypted
email_encrypted: email.trim(),
ubuntu_user_encrypted: ubuntuUser.trim(),
docker_user_encrypted: dockerUser.trim(),
ssh_host_encrypted: sshHost.trim(),
ssh_port: parseInt(sshPort, 10),
ssh_user_encrypted: sshUser.trim(),
updated_at: new Date()
};
// Пароль добавляем только если он указан (при обновлении можно не менять)
if (sshPassword !== undefined && sshPassword !== null && sshPassword.trim() !== '') {
settings.ssh_password_encrypted = sshPassword;
} else if (existing.length === 0) {
// При создании пароль обязателен
return res.status(400).json({
success: false,
error: 'SSH пароль обязателен при первой настройке'
});
}
// Если пароль не указан (undefined/null/пустая строка) и настройки уже есть - не обновляем пароль
if (existing.length > 0) {
await encryptedDb.saveData('vds_settings', settings, { id: existing[0].id });
} else {
await encryptedDb.saveData('vds_settings', {
...settings,
created_at: new Date()
});
}
// Обновляем process.env.BASE_URL для текущего процесса
process.env.BASE_URL = `https://${normalizedDomain}`;
// Сбрасываем кэш домена в consentService
const consentService = require('../services/consentService');
consentService.clearDomainCache();
logger.info(`[VDS] Настройки сохранены: ${normalizedDomain}`);
res.json({ success: true, settings });
} catch (error) {
logger.error('[VDS] Ошибка сохранения настроек:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Проверка доступности Docker
*/
async function checkDockerAvailable() {
try {
await execAsync('docker --version');
return true;
} catch (error) {
return false;
}
}
/**
* Получить список контейнеров
*/
router.get('/containers', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const dockerAvailable = await checkDockerAvailable();
if (!dockerAvailable) {
return res.json({ success: true, containers: [], message: 'Docker недоступен (работает локально, не на VDS)' });
}
const { stdout } = await execAsync('docker ps -a --format "{{.Names}}|{{.Status}}|{{.Image}}"');
const containers = stdout.trim().split('\n').filter(line => line.trim()).map(line => {
const [name, status, image] = line.split('|');
return { name, status, image };
});
res.json({ success: true, containers });
} catch (error) {
logger.error('[VDS] Ошибка получения списка контейнеров:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Перезапустить контейнер
*/
router.post('/containers/:name/restart', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { name } = req.params;
if (!name) {
return res.status(400).json({ success: false, error: 'Имя контейнера обязательно' });
}
await execAsync(`docker restart ${name}`);
logger.info(`[VDS] Контейнер ${name} перезапущен`);
res.json({ success: true, message: `Контейнер ${name} перезапущен` });
} catch (error) {
logger.error(`[VDS] Ошибка перезапуска контейнера ${req.params.name}:`, error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Перезапустить все контейнеры
*/
router.post('/containers/restart-all', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { stdout } = await execAsync('docker ps -q');
const containerIds = stdout.trim().split('\n').filter(id => id.trim());
if (containerIds.length === 0) {
return res.json({ success: true, message: 'Нет запущенных контейнеров', restarted: 0 });
}
await execAsync(`docker restart ${containerIds.join(' ')}`);
logger.info(`[VDS] Перезапущено контейнеров: ${containerIds.length}`);
res.json({ success: true, message: `Перезапущено контейнеров: ${containerIds.length}`, restarted: containerIds.length });
} catch (error) {
logger.error('[VDS] Ошибка перезапуска всех контейнеров:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Пересобрать контейнер
*/
router.post('/containers/:name/rebuild', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { name } = req.params;
if (!name) {
return res.status(400).json({ success: false, error: 'Имя контейнера обязательно' });
}
// Получаем информацию о контейнере
const { stdout: inspectOutput } = await execAsync(`docker inspect ${name} --format '{{.Config.Image}}'`);
const imageName = inspectOutput.trim();
if (!imageName) {
return res.status(404).json({ success: false, error: 'Контейнер не найден' });
}
// Останавливаем контейнер
await execAsync(`docker stop ${name}`).catch(() => {});
// Удаляем контейнер
await execAsync(`docker rm ${name}`).catch(() => {});
// Пересобираем образ (если есть Dockerfile)
// Для простоты просто пересоздаем контейнер из образа
await execAsync(`docker run -d --name ${name} ${imageName}`).catch(() => {
throw new Error('Не удалось пересоздать контейнер. Возможно, нужны дополнительные параметры запуска.');
});
logger.info(`[VDS] Контейнер ${name} пересобран`);
res.json({ success: true, message: `Контейнер ${name} пересобран` });
} catch (error) {
logger.error(`[VDS] Ошибка пересборки контейнера ${req.params.name}:`, error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Получить статистику системы (CPU, RAM, трафик)
*/
router.get('/stats', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { period = '1h' } = req.query; // 1h, 6h, 24h, 7d
// Получаем текущую статистику CPU и RAM
let cpuUsage = 0;
let ramUsage = 0;
let totalTraffic = 0;
let rxBytes = 0;
let txBytes = 0;
try {
const { stdout: cpuRam } = await execAsync('top -bn1 | grep "Cpu(s)" | sed "s/.*, *\\([0-9.]*\\)%* id.*/\\1/" | awk \'{print 100 - $1}\'');
cpuUsage = parseFloat(cpuRam.trim()) || 0;
} catch (error) {
logger.warn('[VDS] Не удалось получить статистику CPU:', error.message);
}
try {
const { stdout: memInfo } = await execAsync('free -m | awk \'NR==2{printf "%.2f", $3*100/$2}\'');
ramUsage = parseFloat(memInfo.trim()) || 0;
} catch (error) {
logger.warn('[VDS] Не удалось получить статистику RAM:', error.message);
}
try {
// Используем /host/proc если доступно, иначе /proc
const procPath = require('fs').existsSync('/host/proc') ? '/host/proc' : '/proc';
const { stdout: networkStats } = await execAsync(`cat ${procPath}/net/dev | awk 'NR>2 {rx+=$2; tx+=$10} END {print rx, tx}'`);
[rxBytes, txBytes] = networkStats.trim().split(' ').map(Number);
totalTraffic = (rxBytes + txBytes) / 1024 / 1024; // MB
} catch (error) {
logger.warn('[VDS] Не удалось получить статистику трафика:', error.message);
}
// Получаем статистику по контейнерам (если Docker доступен)
let containers = [];
const dockerAvailable = await checkDockerAvailable();
if (dockerAvailable) {
try {
const { stdout: containerStats } = await execAsync('docker stats --no-stream --format "{{.Name}}|{{.CPUPerc}}|{{.MemUsage}}|{{.NetIO}}"');
containers = containerStats.trim().split('\n').filter(line => line.trim()).map(line => {
const [name, cpu, mem, net] = line.split('|');
return { name, cpu, mem, net };
});
} catch (error) {
logger.warn('[VDS] Не удалось получить статистику контейнеров:', error.message);
// Продолжаем без статистики контейнеров
}
}
res.json({
success: true,
stats: {
cpu: {
usage: cpuUsage,
cores: require('os').cpus().length
},
ram: {
usage: ramUsage,
total: Math.round(require('os').totalmem() / 1024 / 1024), // MB
used: Math.round(require('os').totalmem() / 1024 / 1024 * ramUsage / 100) // MB
},
traffic: {
total: totalTraffic, // MB
rx: rxBytes / 1024 / 1024, // MB
tx: txBytes / 1024 / 1024 // MB
},
containers
},
timestamp: new Date().toISOString()
});
} catch (error) {
logger.error('[VDS] Ошибка получения статистики:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Остановить контейнер
*/
router.post('/containers/:name/stop', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { name } = req.params;
await execAsync(`docker stop ${name}`);
logger.info(`[VDS] Контейнер ${name} остановлен`);
res.json({ success: true, message: `Контейнер ${name} остановлен` });
} catch (error) {
logger.error(`[VDS] Ошибка остановки контейнера ${req.params.name}:`, error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Запустить контейнер
*/
router.post('/containers/:name/start', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { name } = req.params;
await execAsync(`docker start ${name}`);
logger.info(`[VDS] Контейнер ${name} запущен`);
res.json({ success: true, message: `Контейнер ${name} запущен` });
} catch (error) {
logger.error(`[VDS] Ошибка запуска контейнера ${req.params.name}:`, error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Удалить контейнер
*/
router.delete('/containers/:name', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { name } = req.params;
await execAsync(`docker stop ${name}`).catch(() => {});
await execAsync(`docker rm ${name}`);
logger.info(`[VDS] Контейнер ${name} удален`);
res.json({ success: true, message: `Контейнер ${name} удален` });
} catch (error) {
logger.error(`[VDS] Ошибка удаления контейнера ${req.params.name}:`, error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Получить логи контейнера
*/
router.get('/containers/:name/logs', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { name } = req.params;
const { tail = 100 } = req.query;
const { stdout } = await execAsync(`docker logs --tail ${tail} ${name}`);
res.json({ success: true, logs: stdout });
} catch (error) {
logger.error(`[VDS] Ошибка получения логов контейнера ${req.params.name}:`, error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Получить статистику контейнера
*/
router.get('/containers/:name/stats', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { name } = req.params;
const { stdout } = await execAsync(`docker stats --no-stream --format "{{.CPUPerc}}|{{.MemUsage}}|{{.NetIO}}|{{.BlockIO}}" ${name}`);
const [cpu, mem, net, block] = stdout.trim().split('|');
res.json({ success: true, stats: { cpu, mem, net, block } });
} catch (error) {
logger.error(`[VDS] Ошибка получения статистики контейнера ${req.params.name}:`, error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Очистить Docker (удалить неиспользуемые образы, контейнеры, сети)
*/
router.post('/docker/cleanup', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { type = 'all' } = req.body; // all, images, containers, volumes, networks
let command;
switch (type) {
case 'images':
command = 'docker image prune -a -f';
break;
case 'containers':
command = 'docker container prune -f';
break;
case 'volumes':
command = 'docker volume prune -f';
break;
case 'networks':
command = 'docker network prune -f';
break;
default:
command = 'docker system prune -a -f';
}
const { stdout } = await execAsync(command);
logger.info(`[VDS] Docker очистка выполнена: ${type}`);
res.json({ success: true, message: `Очистка Docker (${type}) выполнена`, output: stdout });
} catch (error) {
logger.error('[VDS] Ошибка очистки Docker:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Перезагрузить сервер
*/
router.post('/server/reboot', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
res.json({ success: true, message: 'Сервер будет перезагружен через 5 секунд' });
setTimeout(() => {
execAsync('reboot').catch(err => logger.error('[VDS] Ошибка перезагрузки:', err));
}, 5000);
} catch (error) {
logger.error('[VDS] Ошибка перезагрузки сервера:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Выключить сервер
*/
router.post('/server/shutdown', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
res.json({ success: true, message: 'Сервер будет выключен через 5 секунд' });
setTimeout(() => {
execAsync('shutdown -h now').catch(err => logger.error('[VDS] Ошибка выключения:', err));
}, 5000);
} catch (error) {
logger.error('[VDS] Ошибка выключения сервера:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Обновить систему
*/
router.post('/server/update', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { stdout } = await execAsync('apt update && apt upgrade -y');
logger.info('[VDS] Система обновлена');
res.json({ success: true, message: 'Система обновлена', output: stdout });
} catch (error) {
logger.error('[VDS] Ошибка обновления системы:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Получить системные логи
*/
router.get('/server/logs', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { type = 'syslog', lines = 100 } = req.query; // syslog, journalctl, auth
let command;
switch (type) {
case 'journalctl':
command = `journalctl -n ${lines} --no-pager`;
break;
case 'auth':
command = `tail -n ${lines} /var/log/auth.log`;
break;
default:
command = `tail -n ${lines} /var/log/syslog`;
}
const { stdout } = await execAsync(command);
res.json({ success: true, logs: stdout, type });
} catch (error) {
logger.error('[VDS] Ошибка получения системных логов:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Получить информацию о диске
*/
router.get('/server/disk', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { stdout: df } = await execAsync('df -h');
const { stdout: du } = await execAsync('du -sh / 2>/dev/null || echo "N/A"');
res.json({ success: true, disk: { df, du } });
} catch (error) {
logger.error('[VDS] Ошибка получения информации о диске:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Получить список процессов
*/
router.get('/server/processes', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { stdout } = await execAsync('ps aux --sort=-%cpu | head -20');
res.json({ success: true, processes: stdout });
} catch (error) {
logger.error('[VDS] Ошибка получения списка процессов:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Создать пользователя
*/
router.post('/users/create', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { username, password, addToDocker = false } = req.body;
if (!username || !password) {
return res.status(400).json({ success: false, error: 'Имя пользователя и пароль обязательны' });
}
let command = `useradd -m -s /bin/bash ${username}`;
if (addToDocker) {
command += ` && usermod -aG docker ${username}`;
}
await execAsync(command);
await execAsync(`echo "${username}:${password}" | chpasswd`);
logger.info(`[VDS] Пользователь ${username} создан`);
res.json({ success: true, message: `Пользователь ${username} создан` });
} catch (error) {
logger.error('[VDS] Ошибка создания пользователя:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Удалить пользователя
*/
router.delete('/users/:username', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { username } = req.params;
const { removeHome = false } = req.query;
const command = removeHome ? `userdel -r ${username}` : `userdel ${username}`;
await execAsync(command);
logger.info(`[VDS] Пользователь ${username} удален`);
res.json({ success: true, message: `Пользователь ${username} удален` });
} catch (error) {
logger.error(`[VDS] Ошибка удаления пользователя ${req.params.username}:`, error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Изменить пароль пользователя
*/
router.post('/users/:username/password', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { username } = req.params;
const { password } = req.body;
if (!password) {
return res.status(400).json({ success: false, error: 'Пароль обязателен' });
}
await execAsync(`echo "${username}:${password}" | chpasswd`);
logger.info(`[VDS] Пароль пользователя ${username} изменен`);
res.json({ success: true, message: `Пароль пользователя ${username} изменен` });
} catch (error) {
logger.error(`[VDS] Ошибка изменения пароля пользователя ${req.params.username}:`, error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Получить список пользователей
*/
router.get('/users', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { stdout } = await execAsync('cat /etc/passwd | awk -F: \'{print $1, $3, $7}\'');
const users = stdout.trim().split('\n').map(line => {
const [username, uid, shell] = line.split(' ');
return { username, uid, shell };
});
res.json({ success: true, users });
} catch (error) {
logger.error('[VDS] Ошибка получения списка пользователей:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Получить SSH ключи пользователя
*/
router.get('/users/:username/ssh-keys', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { username } = req.params;
const { stdout } = await execAsync(`cat /home/${username}/.ssh/authorized_keys 2>/dev/null || echo ""`);
const keys = stdout.trim().split('\n').filter(k => k.trim()).map((key, index) => ({
id: index + 1,
key: key.trim()
}));
res.json({ success: true, keys });
} catch (error) {
logger.error(`[VDS] Ошибка получения SSH ключей пользователя ${req.params.username}:`, error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Добавить SSH ключ пользователю
*/
router.post('/users/:username/ssh-keys', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { username } = req.params;
const { key } = req.body;
if (!key) {
return res.status(400).json({ success: false, error: 'SSH ключ обязателен' });
}
await execAsync(`mkdir -p /home/${username}/.ssh`);
await execAsync(`chmod 700 /home/${username}/.ssh`);
await execAsync(`echo "${key}" >> /home/${username}/.ssh/authorized_keys`);
await execAsync(`chmod 600 /home/${username}/.ssh/authorized_keys`);
await execAsync(`chown -R ${username}:${username} /home/${username}/.ssh`);
logger.info(`[VDS] SSH ключ добавлен пользователю ${username}`);
res.json({ success: true, message: `SSH ключ добавлен пользователю ${username}` });
} catch (error) {
logger.error(`[VDS] Ошибка добавления SSH ключа пользователю ${req.params.username}:`, error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Удалить SSH ключ пользователя
*/
router.delete('/users/:username/ssh-keys/:keyId', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { username, keyId } = req.params;
const keyIndex = parseInt(keyId, 10) - 1;
const { stdout } = await execAsync(`cat /home/${username}/.ssh/authorized_keys`);
const keys = stdout.trim().split('\n').filter(k => k.trim());
if (keyIndex < 0 || keyIndex >= keys.length) {
return res.status(400).json({ success: false, error: 'Неверный ID ключа' });
}
keys.splice(keyIndex, 1);
await execAsync(`echo "${keys.join('\n')}" > /home/${username}/.ssh/authorized_keys`);
await execAsync(`chmod 600 /home/${username}/.ssh/authorized_keys`);
logger.info(`[VDS] SSH ключ удален у пользователя ${username}`);
res.json({ success: true, message: `SSH ключ удален у пользователя ${username}` });
} catch (error) {
logger.error(`[VDS] Ошибка удаления SSH ключа пользователя ${req.params.username}:`, error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Создать бэкап базы данных
*/
router.post('/backup/create', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupFile = `/tmp/backup-${timestamp}.sql`;
// Получаем настройки БД из переменных окружения или конфига
const dbName = process.env.DB_NAME || 'dapp_db';
const dbUser = process.env.DB_USER || 'dapp_user';
await execAsync(`PGPASSWORD=${process.env.DB_PASSWORD || ''} pg_dump -U ${dbUser} -d ${dbName} > ${backupFile}`);
logger.info(`[VDS] Бэкап создан: ${backupFile}`);
res.json({ success: true, message: 'Бэкап создан', file: backupFile });
} catch (error) {
logger.error('[VDS] Ошибка создания бэкапа:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Отправить бэкап на локальную машину
*/
router.post('/backup/send', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { file, localHost, localUser, localPath, sshKeyPath } = req.body;
if (!file || !localHost || !localUser || !localPath) {
return res.status(400).json({
success: false,
error: 'Файл, локальный хост, пользователь и путь обязательны'
});
}
let command;
if (sshKeyPath) {
command = `scp -i ${sshKeyPath} ${file} ${localUser}@${localHost}:${localPath}`;
} else {
command = `scp ${file} ${localUser}@${localHost}:${localPath}`;
}
const { stdout } = await execAsync(command);
logger.info(`[VDS] Бэкап отправлен на ${localHost}:${localPath}`);
res.json({ success: true, message: 'Бэкап отправлен', output: stdout });
} catch (error) {
logger.error('[VDS] Ошибка отправки бэкапа:', error);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Получить историю статистики (для графиков)
*/
router.get('/stats/history', requireAuth, requirePermission(PERMISSIONS.MANAGE_SETTINGS), async (req, res) => {
try {
const { period = '1h' } = req.query; // 1h, 6h, 24h, 7d
// Пока возвращаем пустую историю, так как нужно настроить сбор данных
// В будущем можно использовать временную БД или файлы для хранения истории
res.json({
success: true,
history: {
cpu: [],
ram: [],
traffic: []
},
period
});
} catch (error) {
logger.error('[VDS] Ошибка получения истории статистики:', error);
res.status(500).json({ success: false, error: error.message });
}
});
module.exports = router;