feat: новая функция
This commit is contained in:
@@ -10,33 +10,45 @@ RUN apt-get update && apt-get install -y \
|
||||
python3 \
|
||||
make \
|
||||
g++ \
|
||||
tar \
|
||||
gzip \
|
||||
zip \
|
||||
unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Docker CLI НЕ устанавливаем - используем Docker Socket + dockerode SDK
|
||||
# Устанавливаем Docker CLI
|
||||
RUN curl -fsSL https://get.docker.com | sh
|
||||
|
||||
# Создаем пользователя для безопасности
|
||||
RUN useradd -m -s /bin/bash webssh
|
||||
|
||||
# Создаем рабочую директорию
|
||||
WORKDIR /app
|
||||
|
||||
# Копируем package.json и package-lock.json
|
||||
# Копируем package.json
|
||||
COPY package*.json ./
|
||||
|
||||
# Устанавливаем зависимости
|
||||
RUN npm install
|
||||
# Устанавливаем зависимости через yarn
|
||||
RUN yarn install
|
||||
|
||||
# Копируем исходный код
|
||||
COPY . .
|
||||
|
||||
# Создаем SSH директорию для root с правильными правами
|
||||
RUN mkdir -p /root/.ssh && \
|
||||
chmod 700 /root/.ssh && \
|
||||
touch /root/.ssh/config && \
|
||||
chmod 600 /root/.ssh/config
|
||||
# Создаем SSH директорию для пользователя
|
||||
RUN mkdir -p /home/webssh/.ssh && \
|
||||
chmod 700 /home/webssh/.ssh && \
|
||||
touch /home/webssh/.ssh/config && \
|
||||
chmod 600 /home/webssh/.ssh/config && \
|
||||
chown -R webssh:webssh /home/webssh/.ssh
|
||||
|
||||
# Оставляем root для доступа к Docker socket
|
||||
# USER webssh
|
||||
# Добавляем пользователя в группу docker
|
||||
RUN usermod -aG docker webssh
|
||||
|
||||
# Переключаемся на пользователя
|
||||
USER webssh
|
||||
|
||||
# Открываем порт
|
||||
EXPOSE 3000
|
||||
|
||||
# Команда запуска
|
||||
CMD ["npm", "start"]
|
||||
CMD ["yarn", "start"]
|
||||
|
||||
@@ -1,10 +1,33 @@
|
||||
const Docker = require('dockerode');
|
||||
const { exec } = require('child_process');
|
||||
const { promisify } = require('util');
|
||||
const fs = require('fs-extra');
|
||||
const { execSshCommand, execScpCommand } = require('./sshUtils');
|
||||
const log = require('./logger');
|
||||
|
||||
// Инициализируем Docker клиент через socket
|
||||
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
|
||||
// Безопасные CLI команды
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
// Разрешенные Docker команды для безопасности
|
||||
const ALLOWED_DOCKER_COMMANDS = [
|
||||
'docker save',
|
||||
'docker load',
|
||||
'docker images',
|
||||
'docker ps',
|
||||
'docker run'
|
||||
];
|
||||
|
||||
// Валидация команд
|
||||
const validateDockerCommand = (command) => {
|
||||
return ALLOWED_DOCKER_COMMANDS.some(allowed => command.startsWith(allowed));
|
||||
};
|
||||
|
||||
// Безопасное выполнение Docker команд
|
||||
const execDockerCommand = async (command) => {
|
||||
if (!validateDockerCommand(command)) {
|
||||
throw new Error(`Command not allowed: ${command}`);
|
||||
}
|
||||
return execAsync(command);
|
||||
};
|
||||
|
||||
/**
|
||||
* Экспорт Docker образов и данных с локальной машины
|
||||
@@ -30,21 +53,12 @@ const exportDockerImages = async (sendWebSocketLog) => {
|
||||
sendWebSocketLog('info', `📦 Экспорт образа: ${image.name}`, 'export_images', progress);
|
||||
|
||||
try {
|
||||
const dockerImage = docker.getImage(image.name);
|
||||
const stream = await dockerImage.get();
|
||||
const outputPath = `/tmp/${image.file}`;
|
||||
|
||||
// Сохраняем stream в файл
|
||||
await new Promise((resolve, reject) => {
|
||||
const writeStream = fs.createWriteStream(outputPath);
|
||||
stream.pipe(writeStream);
|
||||
stream.on('end', () => {
|
||||
sendWebSocketLog('success', `✅ Экспорт ${image.name} завершен`, 'export_images', progress);
|
||||
resolve();
|
||||
});
|
||||
stream.on('error', reject);
|
||||
writeStream.on('error', reject);
|
||||
});
|
||||
// Безопасный экспорт через CLI
|
||||
await execDockerCommand(`docker save ${image.name} > ${outputPath}`);
|
||||
|
||||
sendWebSocketLog('success', `✅ Экспорт ${image.name} завершен`, 'export_images', progress);
|
||||
} catch (error) {
|
||||
log.error(`Ошибка экспорта ${image.name}: ${error.message}`);
|
||||
sendWebSocketLog('error', `❌ Ошибка экспорта ${image.name}`, 'export_images', progress);
|
||||
@@ -75,18 +89,9 @@ const exportDockerImages = async (sendWebSocketLog) => {
|
||||
const tarFiles = images.map(img => img.file).join(' ');
|
||||
const dataFiles = 'postgres_data.tar.gz ollama_data.tar.gz vector_search_data.tar.gz';
|
||||
|
||||
// Создаем архив через временный контейнер
|
||||
const container = await docker.createContainer({
|
||||
Image: 'alpine',
|
||||
Cmd: ['sh', '-c', `cd /tmp && tar -czf docker-images-and-data.tar.gz ${tarFiles} ${dataFiles}`],
|
||||
HostConfig: {
|
||||
Binds: ['/tmp:/tmp'],
|
||||
AutoRemove: true
|
||||
}
|
||||
});
|
||||
|
||||
await container.start();
|
||||
await container.wait();
|
||||
// Безопасное создание архива через CLI
|
||||
const archiveCommand = `cd /tmp && tar -czf docker-images-and-data.tar.gz ${tarFiles} ${dataFiles}`;
|
||||
await execDockerCommand(archiveCommand);
|
||||
|
||||
sendWebSocketLog('success', '✅ Архив создан успешно', 'export_data', 80);
|
||||
} catch (error) {
|
||||
@@ -103,20 +108,9 @@ const exportDockerImages = async (sendWebSocketLog) => {
|
||||
*/
|
||||
const exportVolumeData = async (volumeName, outputFile, sendWebSocketLog, progress) => {
|
||||
try {
|
||||
const container = await docker.createContainer({
|
||||
Image: 'alpine',
|
||||
Cmd: ['tar', 'czf', `/backup/${outputFile}`, '-C', '/data', '.'],
|
||||
HostConfig: {
|
||||
Binds: [
|
||||
`${volumeName}:/data:ro`,
|
||||
'/tmp:/backup'
|
||||
],
|
||||
AutoRemove: true
|
||||
}
|
||||
});
|
||||
|
||||
await container.start();
|
||||
await container.wait();
|
||||
// Безопасный экспорт через CLI с временным контейнером
|
||||
const exportCommand = `docker run --rm -v ${volumeName}:/data:ro -v /tmp:/backup alpine tar czf /backup/${outputFile} -C /data .`;
|
||||
await execDockerCommand(exportCommand);
|
||||
|
||||
sendWebSocketLog('success', `✅ Экспорт ${outputFile} завершен`, 'export_data', progress);
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user