ssh! и документы
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import dotenv from 'dotenv';
|
||||
import http from 'http';
|
||||
import { Server as SocketIOServer } from 'socket.io';
|
||||
import authRoutes from './modules/auth/auth.routes';
|
||||
import ticketRoutes from './modules/ticket/ticket.routes';
|
||||
import checkRoutes from './modules/check/check.routes';
|
||||
@@ -10,25 +8,18 @@ import proxmoxRoutes from '../proxmox/proxmox.routes';
|
||||
import tariffRoutes from './modules/tariff';
|
||||
import osRoutes from './modules/os';
|
||||
import serverRoutes from './modules/server';
|
||||
import { MonitoringService } from './modules/server/monitoring.service';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
|
||||
// Настройка Socket.IO с CORS
|
||||
const io = new SocketIOServer(server, {
|
||||
cors: {
|
||||
origin: ['http://localhost:3000', 'http://localhost:5173'],
|
||||
methods: ['GET', 'POST'],
|
||||
credentials: true
|
||||
}
|
||||
});
|
||||
|
||||
// ИСПРАВЛЕНО: более точная настройка CORS
|
||||
app.use(cors({
|
||||
origin: ['http://localhost:3000', 'http://localhost:5173'], // Vite обычно использует 5173
|
||||
origin: [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:5173',
|
||||
'https://ospab.host'
|
||||
], // Vite обычно использует 5173
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization']
|
||||
@@ -78,13 +69,19 @@ app.use('/api/server', serverRoutes);
|
||||
|
||||
const PORT = process.env.PORT || 5000;
|
||||
|
||||
// Инициализация сервиса мониторинга
|
||||
const monitoringService = new MonitoringService(io);
|
||||
monitoringService.startMonitoring();
|
||||
import { setupConsoleWSS } from './modules/server/server.console';
|
||||
import https from 'https';
|
||||
import fs from 'fs';
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`🚀 Сервер запущен на порту ${PORT}`);
|
||||
const sslOptions = {
|
||||
key: fs.readFileSync('/etc/apache2/ssl/ospab.host.key'),
|
||||
cert: fs.readFileSync('/etc/apache2/ssl/ospab.host.crt'),
|
||||
};
|
||||
|
||||
const httpsServer = https.createServer(sslOptions, app);
|
||||
setupConsoleWSS(httpsServer);
|
||||
|
||||
httpsServer.listen(PORT, () => {
|
||||
console.log(`🚀 HTTPS сервер запущен на порту ${PORT}`);
|
||||
console.log(`📊 База данных: ${process.env.DATABASE_URL ? 'подключена' : 'НЕ НАСТРОЕНА'}`);
|
||||
console.log(`🔌 WebSocket сервер запущен`);
|
||||
console.log(`📡 Мониторинг серверов активен`);
|
||||
});
|
||||
@@ -1,10 +1,14 @@
|
||||
|
||||
import { Router } from 'express';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { authMiddleware } from '../auth/auth.middleware';
|
||||
|
||||
const router = Router();
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// GET /api/os — получить все ОС
|
||||
router.use(authMiddleware);
|
||||
|
||||
// GET /api/os — получить все ОС (только для авторизованных)
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const oses = await prisma.operatingSystem.findMany();
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
// Смена root-пароля через SSH (для LXC)
|
||||
import { exec } from 'child_process';
|
||||
|
||||
export async function changeRootPasswordSSH(vmid: number): Promise<{ status: string; password?: string; message?: string }> {
|
||||
const newPassword = generateSecurePassword();
|
||||
return new Promise((resolve) => {
|
||||
exec(`ssh -o StrictHostKeyChecking=no root@${process.env.PROXMOX_NODE} pct set ${vmid} --password ${newPassword}`, (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
console.error('Ошибка смены пароля через SSH:', stderr);
|
||||
resolve({ status: 'error', message: stderr });
|
||||
} else {
|
||||
resolve({ status: 'success', password: newPassword });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
import axios from 'axios';
|
||||
import crypto from 'crypto';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
70
ospabhost/backend/src/modules/server/server.console.ts
Normal file
70
ospabhost/backend/src/modules/server/server.console.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Server as WebSocketServer, WebSocket } from 'ws';
|
||||
import { Client as SSHClient } from 'ssh2';
|
||||
import dotenv from 'dotenv';
|
||||
import { IncomingMessage } from 'http';
|
||||
import { Server as HttpServer } from 'http';
|
||||
dotenv.config();
|
||||
|
||||
export function setupConsoleWSS(server: HttpServer) {
|
||||
const wss = new WebSocketServer({ noServer: true });
|
||||
|
||||
wss.on('connection', (ws: WebSocket, req: IncomingMessage) => {
|
||||
const url = req.url || '';
|
||||
const match = url.match(/\/api\/server\/(\d+)\/console/);
|
||||
const vmid = match ? match[1] : null;
|
||||
if (!vmid) {
|
||||
ws.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем IP и root-пароль из БД (упрощённо)
|
||||
// Здесь можно добавить реальный запрос к Prisma
|
||||
const host = process.env.PROXMOX_IP || process.env.PROXMOX_NODE;
|
||||
const username = 'root';
|
||||
const password = process.env.PROXMOX_ROOT_PASSWORD;
|
||||
|
||||
const ssh = new SSHClient();
|
||||
const port = process.env.PROXMOX_SSH_PORT ? Number(process.env.PROXMOX_SSH_PORT) : 22;
|
||||
ssh.on('ready', () => {
|
||||
ssh.shell((err: Error | undefined, stream: any) => {
|
||||
if (err) {
|
||||
ws.send('Ошибка запуска shell: ' + err.message);
|
||||
ws.close();
|
||||
ssh.end();
|
||||
return;
|
||||
}
|
||||
ws.on('message', (msg: string | Buffer) => {
|
||||
stream.write(msg.toString());
|
||||
});
|
||||
stream.on('data', (data: Buffer) => {
|
||||
ws.send(data.toString());
|
||||
});
|
||||
stream.on('close', () => {
|
||||
ws.close();
|
||||
ssh.end();
|
||||
});
|
||||
});
|
||||
}).connect({
|
||||
host,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
hostVerifier: (hash: string) => {
|
||||
console.log('SSH fingerprint:', hash);
|
||||
return true; // всегда принимаем fingerprint
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
ssh.end();
|
||||
});
|
||||
});
|
||||
|
||||
server.on('upgrade', (request: IncomingMessage, socket: any, head: Buffer) => {
|
||||
if (request.url?.startsWith('/api/server/') && request.url?.endsWith('/console')) {
|
||||
wss.handleUpgrade(request, socket, head, (ws: WebSocket) => {
|
||||
wss.emit('connection', ws, request);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -66,13 +66,13 @@ export async function createServer(req: Request, res: Response) {
|
||||
});
|
||||
}
|
||||
|
||||
// Сохраняем сервер в БД с реальным статусом
|
||||
// Сохраняем сервер в БД, статус всегда 'running' после покупки
|
||||
const server = await prisma.server.create({
|
||||
data: {
|
||||
userId,
|
||||
tariffId,
|
||||
osId,
|
||||
status: result.containerStatus || 'creating',
|
||||
status: 'running',
|
||||
proxmoxId: Number(result.vmid),
|
||||
ipAddress: result.ipAddress,
|
||||
rootPassword: result.rootPassword,
|
||||
@@ -146,10 +146,11 @@ async function handleControl(req: Request, res: Response, action: 'start' | 'sto
|
||||
const result = await controlContainer(server.proxmoxId, action);
|
||||
// Polling статуса VM после управления
|
||||
let newStatus = server.status;
|
||||
let actionSuccess = false;
|
||||
let status = '';
|
||||
let attempts = 0;
|
||||
const maxAttempts = 10;
|
||||
if (result.status === 'success') {
|
||||
let status = '';
|
||||
let attempts = 0;
|
||||
const maxAttempts = 10;
|
||||
while (attempts < maxAttempts) {
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
const stats = await getContainerStats(server.proxmoxId);
|
||||
@@ -158,6 +159,7 @@ async function handleControl(req: Request, res: Response, action: 'start' | 'sto
|
||||
if ((action === 'start' && status === 'running') ||
|
||||
(action === 'stop' && status === 'stopped') ||
|
||||
(action === 'restart' && status === 'running')) {
|
||||
actionSuccess = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -178,6 +180,11 @@ async function handleControl(req: Request, res: Response, action: 'start' | 'sto
|
||||
}
|
||||
await prisma.server.update({ where: { id }, data: { status: newStatus } });
|
||||
}
|
||||
// Если статус изменился, считаем действие успешным даже если result.status !== 'success'
|
||||
if (newStatus !== server.status) {
|
||||
return res.json({ status: 'success', newStatus, message: 'Статус сервера изменён успешно' });
|
||||
}
|
||||
// Если не удалось, возвращаем исходный ответ
|
||||
res.json({ ...result, status: newStatus });
|
||||
} catch (error: any) {
|
||||
res.status(500).json({ error: error?.message || 'Ошибка управления сервером' });
|
||||
@@ -208,7 +215,9 @@ export async function changeRootPassword(req: Request, res: Response) {
|
||||
const id = Number(req.params.id);
|
||||
const server = await prisma.server.findUnique({ where: { id } });
|
||||
if (!server || !server.proxmoxId) return res.status(404).json({ error: 'Сервер не найден или нет VMID' });
|
||||
const result = await proxmoxChangeRootPassword(server.proxmoxId);
|
||||
// Используем SSH для смены пароля
|
||||
const { changeRootPasswordSSH } = require('./proxmoxApi');
|
||||
const result = await changeRootPasswordSSH(server.proxmoxId);
|
||||
if (result?.status === 'success' && result.password) {
|
||||
await prisma.server.update({ where: { id }, data: { rootPassword: result.password } });
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
import { Router } from 'express';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const router = Router();
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// GET /api/tariff — получить все тарифы
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const tariffs = await prisma.tariff.findMany();
|
||||
|
||||
Reference in New Issue
Block a user