ssh! и документы
This commit is contained in:
@@ -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