Files
DLE/webssh-agent/utils/cleanupUtils.js

217 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

const { execSshCommand } = require('./sshUtils');
const log = require('./logger');
/**
* Очистка VDS сервера
*/
const cleanupVdsServer = async (options) => {
log.info('Очистка VDS сервера...');
// Проверяем наличие Docker перед попыткой очистки
log.info('🔍 Проверка наличия Docker...');
const dockerCheck = await execSshCommand('command -v docker >/dev/null 2>&1 && echo "docker installed" || echo "docker not installed"', options);
// Если ошибка SSH подключения (код 255), пропускаем очистку Docker
if (dockerCheck.code === 255) {
log.warn('⚠️ Ошибка SSH подключения при проверке Docker, пропускаем очистку Docker');
log.info('Продолжаем настройку сервера...');
} else {
const hasDocker = dockerCheck.stdout.trim().includes('docker installed');
if (hasDocker) {
log.info('Docker обнаружен, выполняем полную очистку...');
// 1. Остановка всех Docker контейнеров (включая запущенные)
log.info('🛑 Остановка всех Docker контейнеров...');
const stopResult = await execSshCommand('docker ps -aq | xargs -r docker stop 2>/dev/null || true', options);
if (stopResult.code !== 0 && stopResult.code !== 255) {
log.warn(`Предупреждение при остановке контейнеров: ${stopResult.stderr}`);
}
// 2. Удаление всех контейнеров (включая остановленные)
log.info('🗑️ Удаление всех Docker контейнеров...');
// Удаляем все контейнеры (запущенные и остановленные)
const rmResult = await execSshCommand('docker container ls -aq | xargs -r docker rm -f 2>/dev/null || true', options);
if (rmResult.code !== 0 && rmResult.code !== 255) {
log.warn(`Предупреждение при удалении контейнеров: ${rmResult.stderr}`);
}
// Дополнительная проверка и удаление любых оставшихся контейнеров
await execSshCommand('docker ps -aq 2>/dev/null | xargs -r docker rm -f 2>/dev/null || true', options);
// 3. Удаление всех volumes (включая именованные)
log.info('🗑️ Удаление всех Docker volumes...');
// Сначала получаем список всех volumes
const allVolumesList = await execSshCommand('docker volume ls -q 2>/dev/null || true', options);
if (allVolumesList.stdout.trim()) {
const volumes = allVolumesList.stdout.trim().split('\n').filter(v => v);
log.info(`Найдено ${volumes.length} volumes для удаления`);
for (const volume of volumes) {
// Принудительно удаляем каждый volume
await execSshCommand(`docker volume rm -f "${volume}" 2>/dev/null || true`, options);
}
log.info(`Удалено ${volumes.length} volumes`);
}
// 4. Дополнительная очистка неиспользуемых volumes (на случай если что-то осталось)
log.info('🧹 Финальная очистка volumes...');
const volumePruneResult = await execSshCommand('docker volume prune -f 2>/dev/null || true', options);
if (volumePruneResult.code !== 0 && volumePruneResult.code !== 255) {
log.warn(`Предупреждение при финальной очистке volumes: ${volumePruneResult.stderr}`);
}
// 5. Удаление всех Docker образов приложения
log.info('🗑️ Удаление старых Docker образов приложения...');
const imagesList = await execSshCommand('docker images -q | xargs -r docker rmi -f 2>/dev/null || true', options);
// Удаляем все образы, связанные с приложением
await execSshCommand('docker images --format "{{.Repository}}:{{.Tag}}" | grep -E "digital_legal_entity|dapp-" | xargs -r docker rmi -f 2>/dev/null || true', options);
// 6. Полная очистка Docker системы (все образы, кэш, сети)
log.info('🧹 Полная очистка Docker системы...');
const pruneResult = await execSshCommand('docker system prune -af --volumes 2>/dev/null || true', options);
if (pruneResult.code !== 0 && pruneResult.code !== 255) {
log.warn(`Предупреждение при очистке Docker: ${pruneResult.stderr}`);
}
// 7. Удаление всех Docker сетей
log.info('🗑️ Удаление всех Docker сетей...');
const networkPruneResult = await execSshCommand('docker network prune -f 2>/dev/null || true', options);
if (networkPruneResult.code !== 0 && networkPruneResult.code !== 255) {
log.warn(`Предупреждение при очистке сетей: ${networkPruneResult.stderr}`);
}
log.success('✅ Docker полностью очищен');
} else {
log.info(' Docker не установлен, пропускаем очистку Docker');
}
}
// Удаление старых директорий приложения
log.info('🗑️ Удаление старых директорий приложения...');
await execSshCommand('find /home -maxdepth 3 -type d -name "dapp" -exec rm -rf {} + 2>/dev/null || true', options);
await execSshCommand('find /home -maxdepth 3 -type d -name "digital_legal_entity" -exec rm -rf {} + 2>/dev/null || true', options);
// Удаление старых docker-compose файлов
log.info('🗑️ Удаление старых конфигурационных файлов...');
await execSshCommand('find /home -name "docker-compose*.yml" -type f -delete 2>/dev/null || true', options);
await execSshCommand('find /home -name ".env" -path "*/dapp/*" -type f -delete 2>/dev/null || true', options);
await execSshCommand('find /home -name "import-images-and-data.sh" -type f -delete 2>/dev/null || true', options);
await execSshCommand('find /home -name "renew-ssl.sh" -type f -delete 2>/dev/null || true', options);
// Очистка старых cron задач, связанных с приложением
log.info('🗑️ Очистка старых cron задач приложения...');
const crontabBackup = await execSshCommand('crontab -l 2>/dev/null || echo ""', options);
if (crontabBackup.stdout.trim()) {
const cleanedCrontab = crontabBackup.stdout
.split('\n')
.filter(line => !line.includes('renew-ssl.sh') && !line.includes('dapp') && !line.includes('digital_legal_entity'))
.join('\n');
if (cleanedCrontab.trim()) {
await execSshCommand(`echo '${cleanedCrontab.replace(/'/g, "'\\''")}' | crontab -`, options);
} else {
await execSshCommand('crontab -r 2>/dev/null || true', options);
}
}
// Очистка старых SSL сертификатов (опционально, можно оставить для повторного использования)
log.info('🧹 Проверка старых SSL сертификатов...');
await execSshCommand('rm -rf /var/www/certbot/.well-known 2>/dev/null || true', options);
// 🆕 Умная проверка и удаление системного nginx для избежания конфликтов портов
log.info('🔍 Проверка наличия системного nginx...');
const nginxCheck = await execSshCommand('systemctl list-units --type=service --state=active,inactive | grep nginx || echo "nginx not found"', options);
if (nginxCheck.stdout.includes('nginx.service')) {
log.info('⚠️ Обнаружен системный nginx - удаляем для освобождения портов 80/443...');
// Полная остановка и удаление системного nginx
await execSshCommand('systemctl stop nginx || true', options);
await execSshCommand('systemctl disable nginx || true', options);
await execSshCommand('systemctl mask nginx || true', options);
await execSshCommand('pkill -f nginx || true', options);
await execSshCommand('apt-get purge -y nginx nginx-common nginx-full || true', options);
await execSshCommand('apt-get autoremove -y || true', options);
log.success('✅ Системный nginx полностью удален, порты 80/443 освобождены для Docker nginx');
} else {
log.info(' Системный nginx не обнаружен - порты 80/443 свободны для Docker nginx');
}
// Остановка других конфликтующих сервисов
log.info('Остановка других конфликтующих сервисов...');
await execSshCommand('systemctl stop apache2 2>/dev/null || true', options);
await execSshCommand('systemctl disable apache2 2>/dev/null || true', options);
await execSshCommand('systemctl mask apache2 2>/dev/null || true', options);
// Очистка старых пакетов
log.info('Очистка старых пакетов...');
await execSshCommand('apt-get autoremove -y || true', options);
await execSshCommand('apt-get autoclean || true', options);
// Очистка временных файлов
log.info('Очистка временных файлов...');
await execSshCommand('rm -rf /tmp/* /var/tmp/* 2>/dev/null || true', options);
log.success('VDS сервер очищен');
};
/**
* Настройка SSH ключей для root
*/
const setupRootSshKeys = async (publicKey, options) => {
log.info('Настройка SSH ключей...');
// Создание директории .ssh для root
await execSshCommand('mkdir -p /root/.ssh', options);
await execSshCommand('chmod 700 /root/.ssh', options);
// ВАЖНО: Устанавливаем правильного владельца директории (root:root)
// SSH не принимает ключи, если директория принадлежит другому пользователю
await execSshCommand('chown root:root /root/.ssh', options);
// Добавление публичного ключа в authorized_keys
// Используем printf для безопасной обработки специальных символов в ключе
// Экранируем обратные слеши и знаки доллара в публичном ключе
const escapedPublicKey = publicKey.replace(/\\/g, '\\\\').replace(/\$/g, '\\$');
await execSshCommand(`printf '%s\\n' "${escapedPublicKey}" >> /root/.ssh/authorized_keys`, options);
await execSshCommand('chmod 600 /root/.ssh/authorized_keys', options);
await execSshCommand('chown root:root /root/.ssh/authorized_keys', options);
// Проверяем, что ключ действительно добавлен
const verifyResult = await execSshCommand(`grep -Fx "${escapedPublicKey}" /root/.ssh/authorized_keys > /dev/null && echo "OK" || echo "FAIL"`, options);
if (verifyResult.stdout.trim() === 'OK') {
log.success('SSH ключи созданы и публичный ключ добавлен в authorized_keys');
} else {
log.error('Ошибка: публичный ключ не был добавлен в authorized_keys');
throw new Error('Не удалось добавить публичный ключ в authorized_keys');
}
};
/**
* Отключение парольной аутентификации
*/
const disablePasswordAuth = async (options) => {
log.info('Отключение парольной аутентификации...');
await execSshCommand('sed -i "s/#PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config', options);
await execSshCommand('sed -i "s/PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config', options);
await execSshCommand('systemctl restart ssh', options);
log.success('Парольная аутентификация отключена, доступ только через SSH ключи');
};
/**
* Настройка firewall
*/
const setupFirewall = async (options) => {
log.info('Настройка firewall...');
await execSshCommand('ufw --force enable', options);
await execSshCommand('ufw allow ssh', options);
await execSshCommand('ufw allow 80', options);
await execSshCommand('ufw allow 443', options);
log.success('Firewall настроен');
};
module.exports = {
cleanupVdsServer,
setupRootSshKeys,
disablePasswordAuth,
setupFirewall
};