ваше сообщение коммита
This commit is contained in:
@@ -30,6 +30,8 @@ const monitoringRoutes = require('./routes/monitoring');
|
|||||||
const pagesRoutes = require('./routes/pages'); // Добавляем импорт роутера страниц
|
const pagesRoutes = require('./routes/pages'); // Добавляем импорт роутера страниц
|
||||||
const uploadsRoutes = require('./routes/uploads');
|
const uploadsRoutes = require('./routes/uploads');
|
||||||
const ensRoutes = require('./routes/ens');
|
const ensRoutes = require('./routes/ens');
|
||||||
|
const sshRoutes = require('./routes/ssh'); // SSH роуты
|
||||||
|
const encryptionRoutes = require('./routes/encryption'); // Encryption роуты
|
||||||
// Factory routes removed - no longer needed
|
// Factory routes removed - no longer needed
|
||||||
|
|
||||||
// Проверка и создание директорий для хранения данных контрактов
|
// Проверка и создание директорий для хранения данных контрактов
|
||||||
@@ -211,6 +213,8 @@ app.use('/api/pages', pagesRoutes); // Подключаем роутер стр
|
|||||||
app.use('/api/system', systemRoutes); // Добавляем маршрут системного мониторинга
|
app.use('/api/system', systemRoutes); // Добавляем маршрут системного мониторинга
|
||||||
app.use('/api/uploads', uploadsRoutes); // Загрузка файлов (логотипы)
|
app.use('/api/uploads', uploadsRoutes); // Загрузка файлов (логотипы)
|
||||||
app.use('/api/ens', ensRoutes); // ENS utilities
|
app.use('/api/ens', ensRoutes); // ENS utilities
|
||||||
|
app.use('/api', sshRoutes); // SSH роуты
|
||||||
|
app.use('/api', encryptionRoutes); // Encryption роуты
|
||||||
// app.use('/api/factory', factoryRoutes); // Factory routes removed - no longer needed
|
// app.use('/api/factory', factoryRoutes); // Factory routes removed - no longer needed
|
||||||
app.use('/api/compile-contracts', compileRoutes); // Компиляция контрактов
|
app.use('/api/compile-contracts', compileRoutes); // Компиляция контрактов
|
||||||
|
|
||||||
|
|||||||
67
backend/routes/encryption.js
Normal file
67
backend/routes/encryption.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
// Путь к папке с ключами шифрования
|
||||||
|
const KEYS_DIR = path.join(__dirname, '../../ssl/keys');
|
||||||
|
const ENCRYPTION_KEY_PATH = path.join(KEYS_DIR, 'full_db_encryption.key');
|
||||||
|
|
||||||
|
// Создаем папку keys если её нет
|
||||||
|
if (!fs.existsSync(KEYS_DIR)) {
|
||||||
|
fs.mkdirSync(KEYS_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to read encryption key
|
||||||
|
const readEncryptionKey = (keyPath) => {
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(keyPath, 'utf8');
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to write encryption key
|
||||||
|
const writeEncryptionKey = (keyPath, key) => {
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(keyPath, key, { mode: 0o600 });
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// GET /api/encryption-key - Get existing encryption key
|
||||||
|
router.get('/encryption-key', (req, res) => {
|
||||||
|
const encryptionKey = readEncryptionKey(ENCRYPTION_KEY_PATH);
|
||||||
|
|
||||||
|
if (encryptionKey) {
|
||||||
|
res.json({ success: true, encryptionKey: encryptionKey });
|
||||||
|
} else {
|
||||||
|
res.status(404).json({ success: false, message: 'Encryption key not found' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST /api/encryption-key/generate - Generate a new encryption key
|
||||||
|
router.post('/encryption-key/generate', (req, res) => {
|
||||||
|
try {
|
||||||
|
// Генерируем новый ключ шифрования (256 бит)
|
||||||
|
const encryptionKey = crypto.randomBytes(32).toString('hex');
|
||||||
|
|
||||||
|
// Сохраняем ключ в файл
|
||||||
|
if (writeEncryptionKey(ENCRYPTION_KEY_PATH, encryptionKey)) {
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Encryption key generated successfully',
|
||||||
|
encryptionKey: encryptionKey
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ success: false, message: 'Failed to save encryption key' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: `Failed to generate encryption key: ${error.message}` });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
42
backend/routes/ssh.js
Normal file
42
backend/routes/ssh.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const SSH_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.ssh');
|
||||||
|
const DEFAULT_KEY_PATH = path.join(SSH_DIR, 'id_rsa');
|
||||||
|
const DEFAULT_PUB_KEY_PATH = path.join(SSH_DIR, 'id_rsa.pub');
|
||||||
|
|
||||||
|
// Helper to read SSH key
|
||||||
|
const readSshKey = (keyPath) => {
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(keyPath, 'utf8');
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// GET /api/ssh-key - Get existing SSH private key
|
||||||
|
router.get('/ssh-key', (req, res) => {
|
||||||
|
const privateKey = readSshKey(DEFAULT_KEY_PATH);
|
||||||
|
const publicKey = readSshKey(DEFAULT_PUB_KEY_PATH);
|
||||||
|
|
||||||
|
if (privateKey) {
|
||||||
|
res.json({ success: true, sshKey: privateKey, publicKey: publicKey, keyType: 'rsa' });
|
||||||
|
} else {
|
||||||
|
res.status(404).json({ success: false, message: 'SSH private key not found' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /api/ssh-key/public - Get existing SSH public key
|
||||||
|
router.get('/ssh-key/public', (req, res) => {
|
||||||
|
const publicKey = readSshKey(DEFAULT_PUB_KEY_PATH);
|
||||||
|
|
||||||
|
if (publicKey) {
|
||||||
|
res.json({ success: true, publicKey: publicKey, keyType: 'rsa' });
|
||||||
|
} else {
|
||||||
|
res.status(404).json({ success: false, message: 'SSH public key not found' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -201,6 +201,24 @@ services:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# SSH Key Server для безопасной передачи ключей
|
||||||
|
ssh-key-server:
|
||||||
|
image: node:20-alpine
|
||||||
|
container_name: dapp-ssh-key-server
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ./scripts/ssh-key-server.js:/app/ssh-key-server.js:ro
|
||||||
|
- ./ssl:/app/ssl:ro
|
||||||
|
- ~/.ssh:/root/.ssh:ro
|
||||||
|
ports:
|
||||||
|
- '3001:3001'
|
||||||
|
command: node /app/ssh-key-server.js
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3001/ssh-key"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
# Автоматический бэкап базы данных
|
# Автоматический бэкап базы данных
|
||||||
backup-service:
|
backup-service:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
|
|||||||
98
docs/dns-setup.md
Normal file
98
docs/dns-setup.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Настройка DNS записей для VDS
|
||||||
|
|
||||||
|
## 🎯 **Цель:**
|
||||||
|
Настроить DNS записи так, чтобы домен указывал на IP адрес VDS сервера.
|
||||||
|
|
||||||
|
## 📋 **Требуемые DNS записи:**
|
||||||
|
|
||||||
|
### **1. A запись (обязательная):**
|
||||||
|
```
|
||||||
|
Тип: A
|
||||||
|
Имя: @ (или пустое)
|
||||||
|
Значение: IP_VDS_СЕРВЕРА
|
||||||
|
TTL: 3600 (или по умолчанию)
|
||||||
|
```
|
||||||
|
|
||||||
|
### **2. CNAME запись (опциональная):**
|
||||||
|
```
|
||||||
|
Тип: CNAME
|
||||||
|
Имя: www
|
||||||
|
Значение: example.com
|
||||||
|
TTL: 3600 (или по умолчанию)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 **Настройка у разных провайдеров:**
|
||||||
|
|
||||||
|
### **Cloudflare:**
|
||||||
|
1. Заходим в панель Cloudflare
|
||||||
|
2. Выбираем домен
|
||||||
|
3. Переходим в "DNS" → "Records"
|
||||||
|
4. Добавляем A запись:
|
||||||
|
- **Type:** A
|
||||||
|
- **Name:** @
|
||||||
|
- **IPv4 address:** IP_VDS_СЕРВЕРА
|
||||||
|
- **Proxy status:** DNS only (серый облачок)
|
||||||
|
|
||||||
|
### **Reg.ru:**
|
||||||
|
1. Заходим в панель управления
|
||||||
|
2. Выбираем домен
|
||||||
|
3. Переходим в "DNS-записи"
|
||||||
|
4. Добавляем A запись:
|
||||||
|
- **Тип:** A
|
||||||
|
- **Поддомен:** @
|
||||||
|
- **IP-адрес:** IP_VDS_СЕРВЕРА
|
||||||
|
|
||||||
|
### **Namecheap:**
|
||||||
|
1. Заходим в панель управления
|
||||||
|
2. Выбираем домен
|
||||||
|
3. Переходим в "Advanced DNS"
|
||||||
|
4. Добавляем A запись:
|
||||||
|
- **Type:** A Record
|
||||||
|
- **Host:** @
|
||||||
|
- **Value:** IP_VDS_СЕРВЕРА
|
||||||
|
|
||||||
|
## ⏱️ **Время распространения DNS:**
|
||||||
|
|
||||||
|
- **Обычно:** 15-60 минут
|
||||||
|
- **Максимум:** 24-48 часов
|
||||||
|
- **Проверка:** `nslookup example.com` или `dig example.com`
|
||||||
|
|
||||||
|
## 🔍 **Проверка DNS записей:**
|
||||||
|
|
||||||
|
### **Через браузер:**
|
||||||
|
- Откройте `https://dns.google/resolve?name=example.com&type=A`
|
||||||
|
- Проверьте, что в ответе указан IP VDS сервера
|
||||||
|
|
||||||
|
### **Через командную строку:**
|
||||||
|
```bash
|
||||||
|
# Linux/Mac
|
||||||
|
nslookup example.com
|
||||||
|
dig example.com
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
nslookup example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ **Важные моменты:**
|
||||||
|
|
||||||
|
1. **Убедитесь, что домен активен** и не заблокирован
|
||||||
|
2. **Проверьте, что IP VDS сервера правильный**
|
||||||
|
3. **Дождитесь распространения DNS** перед настройкой VDS
|
||||||
|
4. **Используйте только IP адрес** (не домен) для VDS
|
||||||
|
|
||||||
|
## 🚨 **Частые проблемы:**
|
||||||
|
|
||||||
|
### **DNS не обновляется:**
|
||||||
|
- Проверьте TTL записи (должен быть 300-3600)
|
||||||
|
- Очистите DNS кэш: `ipconfig /flushdns` (Windows)
|
||||||
|
- Подождите до 48 часов
|
||||||
|
|
||||||
|
### **Домен не указывает на VDS:**
|
||||||
|
- Проверьте правильность IP адреса
|
||||||
|
- Убедитесь, что A запись создана для корневого домена (@)
|
||||||
|
- Проверьте, что нет других A записей
|
||||||
|
|
||||||
|
### **Сайт не открывается:**
|
||||||
|
- Убедитесь, что VDS сервер запущен
|
||||||
|
- Проверьте, что порт 80/443 открыт
|
||||||
|
- Проверьте firewall на VDS сервере
|
||||||
153
docs/vds.md
Normal file
153
docs/vds.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# Задача: Простая настройка VDS с автоматической установкой Ubuntu
|
||||||
|
|
||||||
|
## 🎯 **Описание задачи:**
|
||||||
|
|
||||||
|
### **Цель:**
|
||||||
|
Создать простую форму для автоматической настройки VDS сервера с установкой Ubuntu и деплоем DLE приложения.
|
||||||
|
|
||||||
|
### **Проблема:**
|
||||||
|
Нужно вручную настраивать VDS сервер, устанавливать Ubuntu и необходимые компоненты для работы DLE приложения.
|
||||||
|
|
||||||
|
### **Решение:**
|
||||||
|
Автоматическая очистка VDS, установка Ubuntu, создание пользователя и деплой DLE приложения через локальную форму.
|
||||||
|
|
||||||
|
## 📋 **Требования:**
|
||||||
|
|
||||||
|
### **1. Входные данные (9 полей):**
|
||||||
|
- **VDS IP** - IP адрес сервера
|
||||||
|
- **Домен** - например `example.com` (должен указывать на IP VDS)
|
||||||
|
- **Email** - для SSL сертификата
|
||||||
|
- **Логин Ubuntu** - пользователь для VDS (по умолчанию `ubuntu`)
|
||||||
|
- **Пароль Ubuntu** - пароль для пользователя VDS
|
||||||
|
- **Логин Docker** - пользователь для Docker (по умолчанию `docker`)
|
||||||
|
- **Пароль Docker** - пароль для пользователя Docker
|
||||||
|
- **SSH пользователь** - обычно `root`
|
||||||
|
- **SSH ключ** - приватный ключ для подключения
|
||||||
|
- **Ключ шифрования** - автоматически загружается с локальной машины
|
||||||
|
|
||||||
|
### **1.1. Требования к домену:**
|
||||||
|
- **A запись** `example.com` → IP VDS сервера
|
||||||
|
- **CNAME запись** `www.example.com` → `example.com` (опционально)
|
||||||
|
- **Домен должен быть активен** и доступен
|
||||||
|
- **DNS записи должны распространиться** (проверка перед настройкой)
|
||||||
|
|
||||||
|
### **2. Что должно происходить:**
|
||||||
|
1. **Проверка DNS** - валидация A записи домена
|
||||||
|
2. **Подключение** к VDS по SSH
|
||||||
|
3. **Очистка** всего содержимого на VDS
|
||||||
|
4. **Установка** Ubuntu
|
||||||
|
5. **Создание пользователя Ubuntu** с паролем
|
||||||
|
6. **Создание пользователя Docker** с паролем
|
||||||
|
7. **Установка** Docker, Docker Compose, nginx
|
||||||
|
8. **Настройка** nginx для продакшн приложения
|
||||||
|
9. **Получение** SSL сертификата
|
||||||
|
10. **Миграция** Docker образов с локальной машины
|
||||||
|
11. **Передача ключей** (шифрования и RSA) на VDS
|
||||||
|
12. **Обновление переменных** в БД VDS
|
||||||
|
13. **Запуск** DLE приложения в Docker
|
||||||
|
|
||||||
|
### **3. Результат:**
|
||||||
|
- **VDS полностью очищена** и переустановлена
|
||||||
|
- **Ubuntu установлена** с пользователями Ubuntu и Docker
|
||||||
|
- **Docker образы** мигрированы с локальной машины
|
||||||
|
- **Ключи переданы** с локальной машины на VDS
|
||||||
|
- **Переменные обновлены** в БД VDS
|
||||||
|
- **DLE приложение** работает в Docker на VDS
|
||||||
|
- **Домен работает** с SSL
|
||||||
|
- **Приложение работает** автономно на VDS
|
||||||
|
|
||||||
|
## 🏗️ **Архитектура:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Продакшн режим:
|
||||||
|
Интернет → VDS nginx (домен) → VDS Docker приложение (автономно)
|
||||||
|
|
||||||
|
Настройка:
|
||||||
|
Локальная машина → SSH → VDS сервер → Очистка + Ubuntu + Docker миграция
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Компоненты:**
|
||||||
|
1. **Веб-форма** - настройка VDS (7 полей)
|
||||||
|
2. **WebSSH сервис** - SSH команды к VDS
|
||||||
|
3. **SSH агент** - подключение к VDS
|
||||||
|
4. **VDS сервер** - Ubuntu + Docker + nginx + SSL
|
||||||
|
5. **Docker образы** - мигрированы с локальной машины
|
||||||
|
6. **Переменные окружения** - настроены для продакшн
|
||||||
|
|
||||||
|
## 🚀 **Процесс работы:**
|
||||||
|
|
||||||
|
### **1. Первоначальная настройка:**
|
||||||
|
- Заходит на `http://localhost:5173/settings/interface/webssh`
|
||||||
|
- Заполняет форму с данными VDS (9 полей)
|
||||||
|
- Нажимает "Настроить VDS"
|
||||||
|
|
||||||
|
### **2. Система настраивает VDS:**
|
||||||
|
- **Проверяет DNS** записи домена
|
||||||
|
- **Предупреждает** если домен не готов
|
||||||
|
- Подключается к VDS по SSH
|
||||||
|
- **Очищает** все содержимое на VDS
|
||||||
|
- **Устанавливает** Ubuntu
|
||||||
|
- **Создает пользователя Ubuntu** с паролем
|
||||||
|
- **Создает пользователя Docker** с паролем
|
||||||
|
- Устанавливает Docker, Docker Compose, nginx
|
||||||
|
- Настраивает nginx для продакшн
|
||||||
|
- Получает SSL сертификат
|
||||||
|
- **Мигрирует** Docker образы с локальной машины
|
||||||
|
- **Передает ключи** (шифрования и RSA) на VDS
|
||||||
|
- **Обновляет переменные** в БД VDS
|
||||||
|
- **Запускает** DLE приложение в Docker
|
||||||
|
|
||||||
|
### **3. Результат:**
|
||||||
|
- **VDS полностью готова** для работы
|
||||||
|
- **Пользователи Ubuntu и Docker** созданы
|
||||||
|
- **Docker образы** мигрированы и запущены
|
||||||
|
- **Ключи переданы** с локальной машины на VDS
|
||||||
|
- **Переменные обновлены** в БД VDS
|
||||||
|
- **DLE приложение** работает автономно в Docker
|
||||||
|
- **Домен доступен** с SSL
|
||||||
|
|
||||||
|
## 📁 **Файлы проекта:**
|
||||||
|
|
||||||
|
### **Frontend:**
|
||||||
|
- `frontend/src/components/WebSshForm.vue` - форма управления
|
||||||
|
- `frontend/src/services/webSshService.js` - SSH команды к VDS
|
||||||
|
|
||||||
|
### **Backend:**
|
||||||
|
- `backend/routes/vds-management.js` - API для управления VDS
|
||||||
|
- `backend/services/sshManager.js` - SSH команды
|
||||||
|
- `backend/services/encryptionManager.js` - управление шифрованием
|
||||||
|
|
||||||
|
### **Scripts:**
|
||||||
|
- `scripts/setup-vds.sh` - очистка VDS и установка Ubuntu
|
||||||
|
- `scripts/migrate-docker.sh` - миграция Docker образов на VDS
|
||||||
|
- `scripts/configure-vds.sh` - настройка переменных окружения
|
||||||
|
- `scripts/transfer-keys.sh` - передача ключей на VDS
|
||||||
|
- `scripts/update-variables.sh` - обновление переменных в БД
|
||||||
|
- `scripts/install-ubuntu.sh` - автоматическая установка Ubuntu
|
||||||
|
|
||||||
|
## ✅ **Статус:**
|
||||||
|
|
||||||
|
### **Готово:**
|
||||||
|
- ✅ Форма WebSSH упрощена
|
||||||
|
- ✅ WebSSH сервис обновлен
|
||||||
|
- ✅ DNS проверка добавлена
|
||||||
|
- ✅ Инструкции по настройке DNS созданы
|
||||||
|
- ✅ Поле VDS IP добавлено в форму
|
||||||
|
|
||||||
|
### **Нужно доработать:**
|
||||||
|
- 🔄 SSH агент для очистки и установки Ubuntu
|
||||||
|
- 🔄 API для управления VDS
|
||||||
|
- 🔄 Миграция Docker образов на VDS
|
||||||
|
- 🔄 Передача ключей (шифрования и RSA) на VDS
|
||||||
|
- 🔄 Обновление переменных в БД VDS
|
||||||
|
- 🔄 Автоматическая загрузка ключа шифрования в форму
|
||||||
|
|
||||||
|
## 🎯 **Следующие шаги:**
|
||||||
|
|
||||||
|
1. **Создать SSH агент** для очистки и установки Ubuntu
|
||||||
|
2. **Добавить API** для управления VDS
|
||||||
|
3. **Реализовать миграцию** Docker образов на VDS
|
||||||
|
4. **Создать скрипты** для передачи ключей на VDS
|
||||||
|
5. **Реализовать обновление** переменных в БД VDS
|
||||||
|
6. **Добавить автоматическую загрузку** ключа шифрования в форму
|
||||||
|
7. **Протестировать** на реальной VDS
|
||||||
@@ -21,29 +21,60 @@
|
|||||||
<!-- Форма настроек -->
|
<!-- Форма настроек -->
|
||||||
<form @submit.prevent="handleSubmit" class="tunnel-form">
|
<form @submit.prevent="handleSubmit" class="tunnel-form">
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<h3>Настройки домена</h3>
|
<h3>Настройки VDS</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="vdsIp">IP адрес VDS сервера *</label>
|
||||||
|
<input id="vdsIp" v-model="form.vdsIp" type="text" placeholder="192.168.1.100" required :disabled="isConnected" />
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="domain">Домен *</label>
|
<label for="domain">Домен *</label>
|
||||||
<input id="domain" v-model="form.domain" type="text" placeholder="example.com" required :disabled="isConnected" />
|
<input id="domain" v-model="form.domain" type="text" placeholder="example.com" required :disabled="isConnected" />
|
||||||
|
<small class="form-help">Домен должен указывать на IP VDS сервера (A запись)</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="email">Email для SSL *</label>
|
<label for="email">Email для SSL *</label>
|
||||||
<input id="email" v-model="form.email" type="email" placeholder="admin@example.com" required :disabled="isConnected" />
|
<input id="email" v-model="form.email" type="email" placeholder="admin@example.com" required :disabled="isConnected" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="ubuntuUser">Логин Ubuntu *</label>
|
||||||
|
<input id="ubuntuUser" v-model="form.ubuntuUser" type="text" placeholder="ubuntu" required :disabled="isConnected" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="ubuntuPassword">Пароль Ubuntu *</label>
|
||||||
|
<input id="ubuntuPassword" v-model="form.ubuntuPassword" type="password" placeholder="Введите пароль" required :disabled="isConnected" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="dockerUser">Логин Docker *</label>
|
||||||
|
<input id="dockerUser" v-model="form.dockerUser" type="text" placeholder="docker" required :disabled="isConnected" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="dockerPassword">Пароль Docker *</label>
|
||||||
|
<input id="dockerPassword" v-model="form.dockerPassword" type="password" placeholder="Введите пароль" required :disabled="isConnected" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<h3>Настройки SSH сервера</h3>
|
<h3>Настройки SSH сервера</h3>
|
||||||
<div class="form-group">
|
|
||||||
<label for="sshHost">SSH Host/IP *</label>
|
|
||||||
<input id="sshHost" v-model="form.sshHost" type="text" placeholder="192.168.1.100 или server.example.com" required :disabled="isConnected" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="sshUser">SSH Пользователь *</label>
|
<label for="sshUser">SSH Пользователь *</label>
|
||||||
<input id="sshUser" v-model="form.sshUser" type="text" placeholder="root" required :disabled="isConnected" />
|
<input id="sshUser" v-model="form.sshUser" type="text" placeholder="root" required :disabled="isConnected" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="sshKey">SSH Приватный ключ *</label>
|
<label for="sshKey">SSH Приватный ключ *</label>
|
||||||
<textarea id="sshKey" v-model="form.sshKey" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----" rows="6" required :disabled="isConnected"></textarea>
|
<div class="key-container">
|
||||||
|
<textarea id="sshKey" v-model="form.sshKey" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----" rows="6" required :disabled="isConnected" :type="showSshKey ? 'text' : 'password'"></textarea>
|
||||||
|
<button type="button" @click="toggleSshKey" class="toggle-key-btn" :disabled="isConnected">
|
||||||
|
{{ showSshKey ? 'Скрыть' : 'Показать' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="encryptionKey">Ключ шифрования *</label>
|
||||||
|
<div class="encryption-key-container">
|
||||||
|
<textarea id="encryptionKey" v-model="form.encryptionKey" placeholder="Ключ шифрования будет загружен автоматически..." rows="4" required :disabled="isConnected" :type="showEncryptionKey ? 'text' : 'password'"></textarea>
|
||||||
|
<button type="button" @click="toggleEncryptionKey" class="toggle-key-btn" :disabled="isConnected">
|
||||||
|
{{ showEncryptionKey ? 'Скрыть' : 'Показать' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-section advanced-section">
|
<div class="form-section advanced-section">
|
||||||
@@ -84,23 +115,70 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive } from 'vue';
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
import { useWebSshService } from '../services/webSshService';
|
import { useWebSshService } from '../services/webSshService';
|
||||||
const webSshService = useWebSshService();
|
const webSshService = useWebSshService();
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const isConnected = ref(false);
|
const isConnected = ref(false);
|
||||||
const connectionStatus = ref('Не подключено');
|
const connectionStatus = ref('Не подключено');
|
||||||
const logs = ref([]);
|
const logs = ref([]);
|
||||||
|
const showSshKey = ref(false);
|
||||||
|
const showEncryptionKey = ref(false);
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
|
vdsIp: '',
|
||||||
domain: '',
|
domain: '',
|
||||||
email: '',
|
email: '',
|
||||||
sshHost: '',
|
ubuntuUser: 'ubuntu',
|
||||||
|
ubuntuPassword: '',
|
||||||
|
dockerUser: 'docker',
|
||||||
|
dockerPassword: '',
|
||||||
sshUser: '',
|
sshUser: '',
|
||||||
sshKey: '',
|
sshKey: '',
|
||||||
|
encryptionKey: '',
|
||||||
localPort: 5173,
|
localPort: 5173,
|
||||||
serverPort: 9000,
|
serverPort: 9000,
|
||||||
sshPort: 22
|
sshPort: 22
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Автоматически загружаем SSH ключ и ключ шифрования при загрузке компонента
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
// Пытаемся получить SSH ключ с хостового сервера
|
||||||
|
const response = await fetch('http://localhost:3001/ssh-key');
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.sshKey) {
|
||||||
|
form.sshKey = data.sshKey;
|
||||||
|
addLog('info', 'SSH ключ автоматически загружен');
|
||||||
|
} else {
|
||||||
|
addLog('error', 'SSH ключ не найден. Запустите ./setup.sh для создания ключей');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addLog('error', 'SSH ключ не найден. Запустите ./setup.sh для создания ключей');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
addLog('error', 'SSH ключ не найден. Запустите ./setup.sh для создания ключей');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Загружаем ключ шифрования
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://localhost:3001/encryption-key');
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.encryptionKey) {
|
||||||
|
form.encryptionKey = data.encryptionKey;
|
||||||
|
addLog('info', 'Ключ шифрования автоматически загружен');
|
||||||
|
} else {
|
||||||
|
addLog('error', 'Ключ шифрования не найден');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addLog('error', 'Ключ шифрования не найден');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
addLog('error', 'Ошибка загрузки ключа шифрования');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function validatePrivateKey(key) {
|
function validatePrivateKey(key) {
|
||||||
if (!key) return false;
|
if (!key) return false;
|
||||||
const trimmed = key.trim();
|
const trimmed = key.trim();
|
||||||
@@ -124,6 +202,14 @@ const handleSubmit = async () => {
|
|||||||
connectionStatus.value = `Подключено к ${form.domain}`;
|
connectionStatus.value = `Подключено к ${form.domain}`;
|
||||||
addLog('success', 'SSH туннель успешно создан и настроен');
|
addLog('success', 'SSH туннель успешно создан и настроен');
|
||||||
addLog('info', `Ваше приложение доступно по адресу: https://${form.domain}`);
|
addLog('info', `Ваше приложение доступно по адресу: https://${form.domain}`);
|
||||||
|
|
||||||
|
// Сохраняем статус VDS как настроенного
|
||||||
|
localStorage.setItem('vds-config', JSON.stringify({ isConfigured: true }));
|
||||||
|
|
||||||
|
// Отправляем событие об изменении статуса VDS
|
||||||
|
window.dispatchEvent(new CustomEvent('vds-status-changed', {
|
||||||
|
detail: { isConfigured: true }
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
addLog('error', result.message || 'Ошибка при создании туннеля');
|
addLog('error', result.message || 'Ошибка при создании туннеля');
|
||||||
}
|
}
|
||||||
@@ -152,7 +238,7 @@ const disconnectTunnel = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const validateForm = () => {
|
const validateForm = () => {
|
||||||
if (!form.domain || !form.email || !form.sshHost || !form.sshUser || !form.sshKey) {
|
if (!form.vdsIp || !form.domain || !form.email || !form.ubuntuUser || !form.ubuntuPassword || !form.dockerUser || !form.dockerPassword || !form.sshUser || !form.sshKey || !form.encryptionKey) {
|
||||||
addLog('error', 'Заполните все обязательные поля');
|
addLog('error', 'Заполните все обязательные поля');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -168,16 +254,23 @@ const validateForm = () => {
|
|||||||
};
|
};
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
Object.assign(form, {
|
Object.assign(form, {
|
||||||
|
vdsIp: '',
|
||||||
domain: '',
|
domain: '',
|
||||||
email: '',
|
email: '',
|
||||||
sshHost: '',
|
ubuntuUser: 'ubuntu',
|
||||||
|
ubuntuPassword: '',
|
||||||
|
dockerUser: 'docker',
|
||||||
|
dockerPassword: '',
|
||||||
sshUser: '',
|
sshUser: '',
|
||||||
sshKey: '',
|
sshKey: '',
|
||||||
|
encryptionKey: '',
|
||||||
localPort: 5173,
|
localPort: 5173,
|
||||||
serverPort: 9000,
|
serverPort: 9000,
|
||||||
sshPort: 22
|
sshPort: 22
|
||||||
});
|
});
|
||||||
logs.value = [];
|
logs.value = [];
|
||||||
|
showSshKey.value = false;
|
||||||
|
showEncryptionKey.value = false;
|
||||||
};
|
};
|
||||||
const addLog = (type, message) => {
|
const addLog = (type, message) => {
|
||||||
logs.value.push({
|
logs.value.push({
|
||||||
@@ -186,6 +279,15 @@ const addLog = (type, message) => {
|
|||||||
timestamp: new Date()
|
timestamp: new Date()
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Методы для переключения видимости ключей
|
||||||
|
const toggleSshKey = () => {
|
||||||
|
showSshKey.value = !showSshKey.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleEncryptionKey = () => {
|
||||||
|
showEncryptionKey.value = !showEncryptionKey.value;
|
||||||
|
};
|
||||||
const formatTime = (timestamp) => {
|
const formatTime = (timestamp) => {
|
||||||
return timestamp.toLocaleTimeString();
|
return timestamp.toLocaleTimeString();
|
||||||
};
|
};
|
||||||
@@ -442,6 +544,14 @@ const formatTime = (timestamp) => {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Стили для контейнера ключа шифрования */
|
||||||
|
.encryption-key-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Адаптивность */
|
/* Адаптивность */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.form-row {
|
.form-row {
|
||||||
@@ -462,5 +572,71 @@ const formatTime = (timestamp) => {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Маскировка приватных ключей */
|
||||||
|
textarea[type="password"] {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.2;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: transparent;
|
||||||
|
text-shadow: 0 0 8px #000;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
#333 0px,
|
||||||
|
#333 1px,
|
||||||
|
#444 1px,
|
||||||
|
#444 2px
|
||||||
|
);
|
||||||
|
border: 1px solid #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea[type="password"]:focus {
|
||||||
|
color: transparent;
|
||||||
|
text-shadow: 0 0 8px #000;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
#333 0px,
|
||||||
|
#333 1px,
|
||||||
|
#444 1px,
|
||||||
|
#444 2px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Показывать содержимое при фокусе для редактирования */
|
||||||
|
textarea[type="password"]:focus::placeholder {
|
||||||
|
color: #666;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Контейнеры для ключей */
|
||||||
|
.key-container {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-key-btn {
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-key-btn:hover {
|
||||||
|
background: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-key-btn:disabled {
|
||||||
|
background: #6c757d;
|
||||||
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -259,6 +259,11 @@ const routes = [
|
|||||||
name: 'management-settings',
|
name: 'management-settings',
|
||||||
component: () => import('../views/smartcontracts/SettingsView.vue')
|
component: () => import('../views/smartcontracts/SettingsView.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/vds-mock',
|
||||||
|
name: 'vds-mock',
|
||||||
|
component: () => import('../views/VdsMockView.vue')
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|||||||
@@ -17,6 +17,109 @@
|
|||||||
|
|
||||||
const LOCAL_AGENT_URL = 'http://localhost:12345';
|
const LOCAL_AGENT_URL = 'http://localhost:12345';
|
||||||
|
|
||||||
|
// Функция для генерации nginx конфигурации
|
||||||
|
function getNginxConfig(domain, serverPort) {
|
||||||
|
return `# Rate limiting для защиты от DDoS
|
||||||
|
limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=10r/s;
|
||||||
|
limit_req_zone $binary_remote_addr zone=api_limit_per_ip:10m rate=5r/s;
|
||||||
|
|
||||||
|
# Блокировка известных сканеров и вредоносных ботов
|
||||||
|
map $http_user_agent $bad_bot {
|
||||||
|
default 0;
|
||||||
|
~*bot 1;
|
||||||
|
~*crawler 1;
|
||||||
|
~*spider 1;
|
||||||
|
~*scanner 1;
|
||||||
|
~*sqlmap 1;
|
||||||
|
~*nikto 1;
|
||||||
|
~*dirb 1;
|
||||||
|
~*gobuster 1;
|
||||||
|
~*wfuzz 1;
|
||||||
|
~*burp 1;
|
||||||
|
~*zap 1;
|
||||||
|
~*nessus 1;
|
||||||
|
~*openvas 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name ${domain};
|
||||||
|
|
||||||
|
# Блокировка подозрительных ботов
|
||||||
|
if ($bad_bot = 1) {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Защита от path traversal
|
||||||
|
if ($request_uri ~* "(\\\\.\\\\.|\\\\.\\\\./|\\\\.\\\\.\\\\.\\\\|\\\\.\\\\.%2f|\\\\.\\\\.%5c)") {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Защита от опасных расширений
|
||||||
|
location ~* \\\\.(zip|rar|7z|tar|gz|bz2|xz|sql|sqlite|db|bak|backup|old|csv|php|asp|aspx|jsp|cgi|pl|py|sh|bash|exe|bat|cmd|com|pif|scr|vbs|vbe|jar|war|ear|dll|so|dylib|bin|sys|ini|log|tmp|temp|swp|swo|~)$ {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Защита от доступа к чувствительным файлам
|
||||||
|
location ~* /(\\\\.htaccess|\\\\.htpasswd|\\\\.env|\\\\.git|\\\\.svn|\\\\.DS_Store|Thumbs\\\\.db|web\\\\.config|robots\\\\.txt|sitemap\\\\.xml)$ {
|
||||||
|
deny all;
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Основной location для фронтенда
|
||||||
|
location / {
|
||||||
|
# Rate limiting для основных страниц
|
||||||
|
limit_req zone=req_limit_per_ip burst=20 nodelay;
|
||||||
|
|
||||||
|
proxy_pass http://localhost:${serverPort};
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# Базовые заголовки безопасности
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:;" always;
|
||||||
|
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
||||||
|
}
|
||||||
|
|
||||||
|
# API проксирование к backend через туннель
|
||||||
|
location /api/ {
|
||||||
|
# Rate limiting для API (более строгое)
|
||||||
|
limit_req zone=api_limit_per_ip burst=10 nodelay;
|
||||||
|
|
||||||
|
proxy_pass http://localhost:8000/api/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# Заголовки безопасности для API
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
}
|
||||||
|
|
||||||
|
# WebSocket поддержка
|
||||||
|
location /ws {
|
||||||
|
proxy_pass http://localhost:8000/ws;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Скрытие информации о сервере
|
||||||
|
server_tokens off;
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
class WebSshService {
|
class WebSshService {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.isAgentRunning = false;
|
this.isAgentRunning = false;
|
||||||
@@ -164,10 +267,52 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Создание SSH туннеля
|
* Проверка DNS записей домена
|
||||||
*/
|
*/
|
||||||
async createTunnel(config) {
|
async checkDomainDNS(domain, vdsIp) {
|
||||||
try {
|
try {
|
||||||
|
console.log(`Проверка DNS записей для домена ${domain}...`);
|
||||||
|
|
||||||
|
// Простая проверка через fetch (может не работать в браузере из-за CORS)
|
||||||
|
// В реальной реализации нужно использовать backend API
|
||||||
|
const response = await fetch(`https://dns.google/resolve?name=${domain}&type=A`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.Answer && data.Answer.length > 0) {
|
||||||
|
const dnsIp = data.Answer[0].data;
|
||||||
|
if (dnsIp === vdsIp) {
|
||||||
|
console.log(`DNS запись корректна: ${domain} → ${vdsIp}`);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
console.log(`DNS запись неверна: ${domain} → ${dnsIp} (ожидается ${vdsIp})`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`DNS запись для домена ${domain} не найдена`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Не удалось проверить DNS: ${error.message}. Продолжаем настройку...`);
|
||||||
|
return true; // Продолжаем даже если DNS проверка не удалась
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Настройка существующей VDS для туннелей
|
||||||
|
*/
|
||||||
|
async setupVDS(config) {
|
||||||
|
try {
|
||||||
|
// Проверяем DNS записи домена
|
||||||
|
if (config.domain && config.vdsIp) {
|
||||||
|
const dnsValid = await this.checkDomainDNS(config.domain, config.vdsIp);
|
||||||
|
if (!dnsValid) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'DNS записи не готовы. Убедитесь, что домен указывает на IP VDS сервера.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Проверяем, что агент запущен
|
// Проверяем, что агент запущен
|
||||||
const agentStatus = await this.checkAgentStatus();
|
const agentStatus = await this.checkAgentStatus();
|
||||||
if (!agentStatus.running) {
|
if (!agentStatus.running) {
|
||||||
@@ -178,21 +323,23 @@ EOF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отправляем конфигурацию туннеля агенту
|
// Отправляем конфигурацию VDS агенту
|
||||||
const response = await fetch(`${LOCAL_AGENT_URL}/tunnel/create`, {
|
const response = await fetch(`${LOCAL_AGENT_URL}/vds/setup`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
vdsIp: config.vdsIp,
|
||||||
domain: config.domain,
|
domain: config.domain,
|
||||||
email: config.email,
|
email: config.email,
|
||||||
sshHost: config.sshHost,
|
ubuntuUser: config.ubuntuUser,
|
||||||
|
ubuntuPassword: config.ubuntuPassword,
|
||||||
|
dockerUser: config.dockerUser,
|
||||||
|
dockerPassword: config.dockerPassword,
|
||||||
sshUser: config.sshUser,
|
sshUser: config.sshUser,
|
||||||
sshKey: config.sshKey,
|
sshKey: config.sshKey,
|
||||||
localPort: config.localPort || 5173,
|
encryptionKey: config.encryptionKey
|
||||||
serverPort: config.serverPort || 9000,
|
|
||||||
sshPort: config.sshPort || 22
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -203,7 +350,7 @@ EOF
|
|||||||
this.connectionStatus = {
|
this.connectionStatus = {
|
||||||
connected: true,
|
connected: true,
|
||||||
domain: config.domain,
|
domain: config.domain,
|
||||||
tunnelId: result.tunnelId
|
vdsIp: config.vdsIp
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,11 +359,11 @@ EOF
|
|||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: error.message || 'Ошибка при создании туннеля'
|
message: error.message || 'Ошибка при настройке VDS'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error('Ошибка при создании туннеля:', error);
|
// console.error('Ошибка при настройке VDS:', error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: `Ошибка подключения к агенту: ${error.message}`
|
message: `Ошибка подключения к агенту: ${error.message}`
|
||||||
@@ -300,6 +447,125 @@ EOF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Настройка почтового сервера
|
||||||
|
*/
|
||||||
|
async setupMailServer(ssh, config, domain) {
|
||||||
|
const mailDomain = config.mailDomain || `mail.${domain}`;
|
||||||
|
const adminEmail = config.adminEmail || config.email;
|
||||||
|
const adminPassword = config.adminPassword || 'Admin123!';
|
||||||
|
|
||||||
|
// Настройка Postfix
|
||||||
|
const postfixConfig = `
|
||||||
|
# Основные настройки
|
||||||
|
myhostname = ${mailDomain}
|
||||||
|
mydomain = ${domain}
|
||||||
|
myorigin = $mydomain
|
||||||
|
inet_interfaces = all
|
||||||
|
inet_protocols = ipv4
|
||||||
|
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
|
||||||
|
relayhost =
|
||||||
|
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
|
||||||
|
mailbox_size_limit = 0
|
||||||
|
recipient_delimiter = +
|
||||||
|
inet_interfaces = all
|
||||||
|
home_mailbox = Maildir/
|
||||||
|
|
||||||
|
# SSL настройки
|
||||||
|
smtpd_use_tls = yes
|
||||||
|
smtpd_tls_cert_file = /etc/letsencrypt/live/${domain}/fullchain.pem
|
||||||
|
smtpd_tls_key_file = /etc/letsencrypt/live/${domain}/privkey.pem
|
||||||
|
smtpd_tls_security_level = may
|
||||||
|
smtpd_tls_auth_only = no
|
||||||
|
smtp_tls_security_level = may
|
||||||
|
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
|
||||||
|
smtpd_tls_received_header = yes
|
||||||
|
smtpd_tls_session_cache_timeout = 3600s
|
||||||
|
tls_random_source = dev:/dev/urandom
|
||||||
|
|
||||||
|
# Аутентификация
|
||||||
|
smtpd_sasl_type = dovecot
|
||||||
|
smtpd_sasl_path = private/auth
|
||||||
|
smtpd_sasl_auth_enable = yes
|
||||||
|
smtpd_sasl_security_options = noanonymous
|
||||||
|
smtpd_sasl_local_domain = $myhostname
|
||||||
|
smtpd_recipient_restrictions = permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination
|
||||||
|
`;
|
||||||
|
|
||||||
|
await ssh.execCommand(`echo '${postfixConfig}' > /etc/postfix/main.cf`);
|
||||||
|
|
||||||
|
// Настройка Dovecot
|
||||||
|
const dovecotConfig = `
|
||||||
|
# Основные настройки
|
||||||
|
protocols = imap pop3 lmtp
|
||||||
|
mail_location = maildir:~/Maildir
|
||||||
|
namespace inbox {
|
||||||
|
inbox = yes
|
||||||
|
}
|
||||||
|
passdb {
|
||||||
|
driver = pam
|
||||||
|
}
|
||||||
|
userdb {
|
||||||
|
driver = passwd
|
||||||
|
}
|
||||||
|
service auth {
|
||||||
|
unix_listener /var/spool/postfix/private/auth {
|
||||||
|
mode = 0666
|
||||||
|
user = postfix
|
||||||
|
group = postfix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ssl = required
|
||||||
|
ssl_cert = </etc/letsencrypt/live/${domain}/fullchain.pem
|
||||||
|
ssl_key = </etc/letsencrypt/live/${domain}/privkey.pem
|
||||||
|
`;
|
||||||
|
|
||||||
|
await ssh.execCommand(`echo '${dovecotConfig}' > /etc/dovecot/dovecot.conf`);
|
||||||
|
|
||||||
|
// Создание пользователя для почты
|
||||||
|
await ssh.execCommand(`useradd -m -s /bin/bash ${adminEmail.split('@')[0]}`);
|
||||||
|
await ssh.execCommand(`echo '${adminEmail.split('@')[0]}:${adminPassword}' | chpasswd`);
|
||||||
|
|
||||||
|
// Настройка Roundcube если включен
|
||||||
|
if (config.enableWebmail) {
|
||||||
|
const roundcubeConfig = `
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name ${mailDomain};
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost/roundcube/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
await ssh.execCommand(`echo '${roundcubeConfig}' > /etc/nginx/sites-available/${mailDomain}`);
|
||||||
|
await ssh.execCommand(`ln -sf /etc/nginx/sites-available/${mailDomain} /etc/nginx/sites-enabled/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получение SSL для почтового домена
|
||||||
|
await ssh.execCommand(`certbot --nginx -d ${mailDomain} --non-interactive --agree-tos --email ${adminEmail}`);
|
||||||
|
|
||||||
|
// Запуск сервисов
|
||||||
|
await ssh.execCommand('systemctl enable postfix dovecot');
|
||||||
|
await ssh.execCommand('systemctl restart postfix dovecot nginx');
|
||||||
|
|
||||||
|
// Создание DNS записей
|
||||||
|
const dnsRecords = `
|
||||||
|
# Добавьте эти DNS записи в ваш домен:
|
||||||
|
# MX запись: ${domain} -> ${mailDomain} (приоритет 10)
|
||||||
|
# A запись: ${mailDomain} -> IP вашего сервера
|
||||||
|
# SPF запись: v=spf1 mx a ip4:IP_СЕРВЕРА ~all
|
||||||
|
# DKIM запись: (будет создан автоматически)
|
||||||
|
`;
|
||||||
|
|
||||||
|
console.log('DNS записи для настройки:', dnsRecords);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получение кода агента для установки
|
* Получение кода агента для установки
|
||||||
*/
|
*/
|
||||||
@@ -352,24 +618,111 @@ app.post('/tunnel/create', async (req, res) => {
|
|||||||
port: sshPort
|
port: sshPort
|
||||||
});
|
});
|
||||||
|
|
||||||
// Установка NGINX и certbot
|
// Установка NGINX, certbot и почтовых сервисов
|
||||||
await ssh.execCommand('apt-get update && apt-get install -y nginx certbot python3-certbot-nginx');
|
const installPackages = 'apt-get update && apt-get install -y nginx certbot python3-certbot-nginx';
|
||||||
|
const mailPackages = config.enableMail ? 'postfix dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-managesieved dovecot-sieve dovecot-mysql mysql-server roundcube roundcube-mysql' : '';
|
||||||
|
await ssh.execCommand(\`\${installPackages} \${mailPackages}\`);
|
||||||
|
|
||||||
// Создание конфигурации NGINX
|
// Создание конфигурации NGINX с полной защитой
|
||||||
const nginxConfig = \`
|
const nginxConfig = \`# Rate limiting для защиты от DDoS
|
||||||
|
limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=10r/s;
|
||||||
|
limit_req_zone $binary_remote_addr zone=api_limit_per_ip:10m rate=5r/s;
|
||||||
|
|
||||||
|
# Блокировка известных сканеров и вредоносных ботов
|
||||||
|
map $http_user_agent $bad_bot {
|
||||||
|
default 0;
|
||||||
|
~*bot 1;
|
||||||
|
~*crawler 1;
|
||||||
|
~*spider 1;
|
||||||
|
~*scanner 1;
|
||||||
|
~*sqlmap 1;
|
||||||
|
~*nikto 1;
|
||||||
|
~*dirb 1;
|
||||||
|
~*gobuster 1;
|
||||||
|
~*wfuzz 1;
|
||||||
|
~*burp 1;
|
||||||
|
~*zap 1;
|
||||||
|
~*nessus 1;
|
||||||
|
~*openvas 1;
|
||||||
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name \${domain};
|
server_name \${domain};
|
||||||
|
|
||||||
|
# Блокировка подозрительных ботов
|
||||||
|
if ($bad_bot = 1) {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Защита от path traversal
|
||||||
|
if ($request_uri ~* "(\\\\.\\\\.|\\\\.\\\\./|\\\\.\\\\.\\\\.\\\\|\\\\.\\\\.%2f|\\\\.\\\\.%5c)") {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Защита от опасных расширений
|
||||||
|
location ~* \\\\.(zip|rar|7z|tar|gz|bz2|xz|sql|sqlite|db|bak|backup|old|csv|php|asp|aspx|jsp|cgi|pl|py|sh|bash|exe|bat|cmd|com|pif|scr|vbs|vbe|jar|war|ear|dll|so|dylib|bin|sys|ini|log|tmp|temp|swp|swo|~)$ {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Защита от доступа к чувствительным файлам
|
||||||
|
location ~* /(\\\\.htaccess|\\\\.htpasswd|\\\\.env|\\\\.git|\\\\.svn|\\\\.DS_Store|Thumbs\\\\.db|web\\\\.config|robots\\\\.txt|sitemap\\\\.xml)$ {
|
||||||
|
deny all;
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Основной location для фронтенда
|
||||||
location / {
|
location / {
|
||||||
|
# Rate limiting для основных страниц
|
||||||
|
limit_req zone=req_limit_per_ip burst=20 nodelay;
|
||||||
|
|
||||||
proxy_pass http://localhost:\${serverPort};
|
proxy_pass http://localhost:\${serverPort};
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# Базовые заголовки безопасности
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:;" always;
|
||||||
|
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
\`;
|
# API проксирование к backend через туннель
|
||||||
|
location /api/ {
|
||||||
|
# Rate limiting для API (более строгое)
|
||||||
|
limit_req zone=api_limit_per_ip burst=10 nodelay;
|
||||||
|
|
||||||
|
proxy_pass http://localhost:8000/api/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# Заголовки безопасности для API
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
}
|
||||||
|
|
||||||
|
# WebSocket поддержка
|
||||||
|
location /ws {
|
||||||
|
proxy_pass http://localhost:8000/ws;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Скрытие информации о сервере
|
||||||
|
server_tokens off;
|
||||||
|
}\`;
|
||||||
|
|
||||||
await ssh.execCommand(\`echo '\${nginxConfig}' > /etc/nginx/sites-available/\${domain}\`);
|
await ssh.execCommand(\`echo '\${nginxConfig}' > /etc/nginx/sites-available/\${domain}\`);
|
||||||
await ssh.execCommand(\`ln -sf /etc/nginx/sites-available/\${domain} /etc/nginx/sites-enabled/\`);
|
await ssh.execCommand(\`ln -sf /etc/nginx/sites-available/\${domain} /etc/nginx/sites-enabled/\`);
|
||||||
@@ -378,28 +731,61 @@ server {
|
|||||||
// Получение SSL сертификата
|
// Получение SSL сертификата
|
||||||
await ssh.execCommand(\`certbot --nginx -d \${domain} --non-interactive --agree-tos --email \${email}\`);
|
await ssh.execCommand(\`certbot --nginx -d \${domain} --non-interactive --agree-tos --email \${email}\`);
|
||||||
|
|
||||||
|
// Настройка почты если включена
|
||||||
|
if (config.enableMail) {
|
||||||
|
await setupMailServer(ssh, config, domain);
|
||||||
|
}
|
||||||
|
|
||||||
ssh.dispose();
|
ssh.dispose();
|
||||||
|
|
||||||
// Создание SSH туннеля
|
// Создание SSH туннелей для frontend и backend
|
||||||
const tunnelId = Date.now().toString();
|
const tunnelId = Date.now().toString();
|
||||||
const sshArgs = [
|
|
||||||
|
// SSH туннель для frontend (порт 9000)
|
||||||
|
const frontendSshArgs = [
|
||||||
'-i', keyPath,
|
'-i', keyPath,
|
||||||
'-p', sshPort.toString(),
|
'-p', sshPort.toString(),
|
||||||
'-R', \`\${serverPort}:localhost:\${localPort}\`,
|
'-R', \`\${serverPort}:localhost:\${localPort}\`,
|
||||||
'-N',
|
'-N',
|
||||||
'-o', 'StrictHostKeyChecking=no',
|
'-o', 'StrictHostKeyChecking=no',
|
||||||
'-o', 'UserKnownHostsFile=/dev/null',
|
'-o', 'UserKnownHostsFile=/dev/null',
|
||||||
|
'-o', 'ServerAliveInterval=60',
|
||||||
|
'-o', 'ServerAliveCountMax=3',
|
||||||
\`\${sshUser}@\${sshHost}\`
|
\`\${sshUser}@\${sshHost}\`
|
||||||
];
|
];
|
||||||
|
|
||||||
const sshProcess = spawn('ssh', sshArgs);
|
// SSH туннель для backend (порт 8000)
|
||||||
|
const backendSshArgs = [
|
||||||
|
'-i', keyPath,
|
||||||
|
'-p', sshPort.toString(),
|
||||||
|
'-R', '8000:localhost:8000',
|
||||||
|
'-N',
|
||||||
|
'-o', 'StrictHostKeyChecking=no',
|
||||||
|
'-o', 'UserKnownHostsFile=/dev/null',
|
||||||
|
'-o', 'ServerAliveInterval=60',
|
||||||
|
'-o', 'ServerAliveCountMax=3',
|
||||||
|
\`\${sshUser}@\${sshHost}\`
|
||||||
|
];
|
||||||
|
|
||||||
sshProcess.on('error', (error) => {
|
// Запускаем оба SSH туннеля
|
||||||
// console.error('SSH процесс ошибка:', error);
|
const frontendSshProcess = spawn('ssh', frontendSshArgs);
|
||||||
|
const backendSshProcess = spawn('ssh', backendSshArgs);
|
||||||
|
|
||||||
|
frontendSshProcess.on('error', (error) => {
|
||||||
|
// console.error('Frontend SSH процесс ошибка:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
sshProcess.on('close', (code) => {
|
backendSshProcess.on('error', (error) => {
|
||||||
// console.log('SSH процесс завершен с кодом:', code);
|
// console.error('Backend SSH процесс ошибка:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
frontendSshProcess.on('close', (code) => {
|
||||||
|
// console.log('Frontend SSH процесс завершен с кодом:', code);
|
||||||
|
tunnelState.connected = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
backendSshProcess.on('close', (code) => {
|
||||||
|
// console.log('Backend SSH процесс завершен с кодом:', code);
|
||||||
tunnelState.connected = false;
|
tunnelState.connected = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -408,7 +794,8 @@ server {
|
|||||||
connected: true,
|
connected: true,
|
||||||
domain,
|
domain,
|
||||||
tunnelId,
|
tunnelId,
|
||||||
sshProcess
|
frontendSshProcess,
|
||||||
|
backendSshProcess
|
||||||
};
|
};
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
@@ -427,18 +814,22 @@ server {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Отключение туннеля
|
// Отключение туннелей
|
||||||
app.post('/tunnel/disconnect', (req, res) => {
|
app.post('/tunnel/disconnect', (req, res) => {
|
||||||
try {
|
try {
|
||||||
if (tunnelState.sshProcess) {
|
if (tunnelState.frontendSshProcess) {
|
||||||
tunnelState.sshProcess.kill();
|
tunnelState.frontendSshProcess.kill();
|
||||||
|
}
|
||||||
|
if (tunnelState.backendSshProcess) {
|
||||||
|
tunnelState.backendSshProcess.kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnelState = {
|
tunnelState = {
|
||||||
connected: false,
|
connected: false,
|
||||||
domain: null,
|
domain: null,
|
||||||
tunnelId: null,
|
tunnelId: null,
|
||||||
sshProcess: null
|
frontendSshProcess: null,
|
||||||
|
backendSshProcess: null
|
||||||
};
|
};
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
@@ -478,7 +869,7 @@ export function useWebSshService() {
|
|||||||
return {
|
return {
|
||||||
checkAgentStatus: () => service.checkAgentStatus(),
|
checkAgentStatus: () => service.checkAgentStatus(),
|
||||||
installAndStartAgent: () => service.installAndStartAgent(),
|
installAndStartAgent: () => service.installAndStartAgent(),
|
||||||
createTunnel: (config) => service.createTunnel(config),
|
setupVDS: (config) => service.setupVDS(config),
|
||||||
disconnectTunnel: () => service.disconnectTunnel(),
|
disconnectTunnel: () => service.disconnectTunnel(),
|
||||||
getStatus: () => service.getStatus()
|
getStatus: () => service.getStatus()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,6 +46,13 @@
|
|||||||
Подробнее
|
Подробнее
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Блок Веб3 приложение -->
|
||||||
|
<div class="crm-web3-block">
|
||||||
|
<h2>Веб3 приложение</h2>
|
||||||
|
<button class="details-btn" @click="goToWeb3App">
|
||||||
|
Подробнее
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
</template>
|
</template>
|
||||||
@@ -217,6 +224,10 @@ function goToContent() {
|
|||||||
function goToManagement() {
|
function goToManagement() {
|
||||||
router.push({ name: 'management' });
|
router.push({ name: 'management' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goToWeb3App() {
|
||||||
|
router.push({ name: 'vds-mock' });
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -358,4 +369,33 @@ strong {
|
|||||||
.crm-management-block .details-btn {
|
.crm-management-block .details-btn {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.crm-web3-block {
|
||||||
|
margin: 32px 0 24px 0;
|
||||||
|
padding: 24px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
opacity: 0.7;
|
||||||
|
border: 2px solid #dee2e6;
|
||||||
|
}
|
||||||
|
.crm-web3-block h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-dark);
|
||||||
|
}
|
||||||
|
.crm-web3-block .details-btn {
|
||||||
|
margin-top: 0;
|
||||||
|
background: #6c757d;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.crm-web3-block .details-btn:hover {
|
||||||
|
background: #5a6268;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
393
frontend/src/views/VdsMockView.vue
Normal file
393
frontend/src/views/VdsMockView.vue
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
<!--
|
||||||
|
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/HB3-ACCELERATOR
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<BaseLayout
|
||||||
|
:is-authenticated="isAuthenticated"
|
||||||
|
:identities="identities"
|
||||||
|
:token-balances="tokenBalances"
|
||||||
|
:is-loading-tokens="isLoadingTokens"
|
||||||
|
@auth-action-completed="$emit('auth-action-completed')"
|
||||||
|
>
|
||||||
|
<div class="vds-mock-container">
|
||||||
|
<div class="mock-header">
|
||||||
|
<h1>VDS Сервер - Не настроен</h1>
|
||||||
|
<div class="mock-status">
|
||||||
|
<div class="status-indicator offline"></div>
|
||||||
|
<span>Офлайн</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Мок интерфейс -->
|
||||||
|
<div class="mock-content">
|
||||||
|
<div class="mock-card">
|
||||||
|
<h2>Статус сервера</h2>
|
||||||
|
<div class="mock-metrics">
|
||||||
|
<div class="mock-metric">
|
||||||
|
<span class="label">CPU:</span>
|
||||||
|
<span class="value mock">--%</span>
|
||||||
|
</div>
|
||||||
|
<div class="mock-metric">
|
||||||
|
<span class="label">RAM:</span>
|
||||||
|
<span class="value mock">--%</span>
|
||||||
|
</div>
|
||||||
|
<div class="mock-metric">
|
||||||
|
<span class="label">Диск:</span>
|
||||||
|
<span class="value mock">--%</span>
|
||||||
|
</div>
|
||||||
|
<div class="mock-metric">
|
||||||
|
<span class="label">Uptime:</span>
|
||||||
|
<span class="value mock">--</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mock-card">
|
||||||
|
<h2>Управление сервисами</h2>
|
||||||
|
<div class="mock-services">
|
||||||
|
<div class="mock-service">
|
||||||
|
<span class="service-name">DLE Application</span>
|
||||||
|
<span class="service-status mock">Недоступно</span>
|
||||||
|
</div>
|
||||||
|
<div class="mock-service">
|
||||||
|
<span class="service-name">PostgreSQL</span>
|
||||||
|
<span class="service-status mock">Недоступно</span>
|
||||||
|
</div>
|
||||||
|
<div class="mock-service">
|
||||||
|
<span class="service-name">Nginx</span>
|
||||||
|
<span class="service-status mock">Недоступно</span>
|
||||||
|
</div>
|
||||||
|
<div class="mock-service">
|
||||||
|
<span class="service-name">Docker</span>
|
||||||
|
<span class="service-status mock">Недоступно</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mock-card">
|
||||||
|
<h2>Логи системы</h2>
|
||||||
|
<div class="mock-logs">
|
||||||
|
<pre>VDS сервер не настроен
|
||||||
|
Для активации перейдите в настройки и настройте VDS сервер</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mock-card">
|
||||||
|
<h2>Деплой приложения</h2>
|
||||||
|
<div class="mock-deploy">
|
||||||
|
<p>Деплой недоступен - VDS сервер не настроен</p>
|
||||||
|
<button class="mock-btn" disabled>Деплой недоступен</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mock-card">
|
||||||
|
<h2>Управление бэкапами</h2>
|
||||||
|
<div class="mock-backups">
|
||||||
|
<p>Бэкапы недоступны - VDS сервер не настроен</p>
|
||||||
|
<div class="mock-backup-list">
|
||||||
|
<div class="mock-backup-item">
|
||||||
|
<span class="backup-name">Нет бэкапов</span>
|
||||||
|
<span class="backup-date">--</span>
|
||||||
|
<span class="backup-size">--</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Призыв к действию -->
|
||||||
|
<div class="call-to-action">
|
||||||
|
<h2>Настройте VDS сервер</h2>
|
||||||
|
<p>Для использования всех функций управления VDS сервером необходимо его настроить.</p>
|
||||||
|
<button class="setup-btn" @click="goToSetup">
|
||||||
|
Перейти к настройке VDS
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BaseLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { defineProps, defineEmits } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import BaseLayout from '../components/BaseLayout.vue';
|
||||||
|
|
||||||
|
// Props
|
||||||
|
const props = defineProps({
|
||||||
|
isAuthenticated: Boolean,
|
||||||
|
identities: Array,
|
||||||
|
tokenBalances: Object,
|
||||||
|
isLoadingTokens: Boolean
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits(['auth-action-completed']);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const goToSetup = () => {
|
||||||
|
router.push({ name: 'webssh-settings' });
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.vds-mock-container {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: var(--color-white);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
border-bottom: 2px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-header h1 {
|
||||||
|
margin: 0;
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator.offline {
|
||||||
|
background: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-card {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 24px;
|
||||||
|
border: 2px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-card h2 {
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-metrics {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-metric {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px;
|
||||||
|
background: #e9ecef;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-metric .label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-metric .value.mock {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-services {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-service {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
background: #e9ecef;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-status.mock {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-logs {
|
||||||
|
background: #2d3748;
|
||||||
|
color: #6c757d;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-deploy {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-deploy p {
|
||||||
|
color: #6c757d;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-btn {
|
||||||
|
padding: 12px 24px;
|
||||||
|
background: #6c757d;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: not-allowed;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-backups {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-backups p {
|
||||||
|
color: #6c757d;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-backup-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-backup-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
background: #e9ecef;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backup-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backup-date, .backup-size {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-to-action {
|
||||||
|
background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 32px;
|
||||||
|
text-align: center;
|
||||||
|
border: 2px solid #bbdefb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-to-action h2 {
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
color: var(--color-primary);
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-to-action p {
|
||||||
|
color: #6c757d;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setup-btn {
|
||||||
|
padding: 16px 32px;
|
||||||
|
background: var(--color-primary);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setup-btn:hover {
|
||||||
|
background: var(--color-primary-dark);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Адаптивность */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.mock-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-metrics {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-service, .mock-backup-item {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -18,8 +18,6 @@
|
|||||||
:identities="identities"
|
:identities="identities"
|
||||||
:token-balances="tokenBalances"
|
:token-balances="tokenBalances"
|
||||||
:is-loading-tokens="isLoadingTokens"
|
:is-loading-tokens="isLoadingTokens"
|
||||||
:telegram-auth="telegramAuth"
|
|
||||||
:email-auth="emailAuth"
|
|
||||||
/>
|
/>
|
||||||
<div class="webssh-settings-block">
|
<div class="webssh-settings-block">
|
||||||
<button class="close-btn" @click="goBack">×</button>
|
<button class="close-btn" @click="goBack">×</button>
|
||||||
@@ -37,6 +35,18 @@ import Header from '@/components/Header.vue';
|
|||||||
import Sidebar from '@/components/Sidebar.vue';
|
import Sidebar from '@/components/Sidebar.vue';
|
||||||
import { useAuthContext } from '@/composables/useAuth';
|
import { useAuthContext } from '@/composables/useAuth';
|
||||||
|
|
||||||
|
// Определяем пропсы, которые мы принимаем
|
||||||
|
defineProps({
|
||||||
|
isAuthenticated: Boolean,
|
||||||
|
identities: Array,
|
||||||
|
tokenBalances: Array,
|
||||||
|
isLoadingTokens: Boolean,
|
||||||
|
formattedLastUpdate: String
|
||||||
|
});
|
||||||
|
|
||||||
|
// Определяем события, которые мы эмитим
|
||||||
|
defineEmits(['authActionCompleted']);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const goBack = () => router.push('/settings/interface');
|
const goBack = () => router.push('/settings/interface');
|
||||||
const showSidebar = ref(false);
|
const showSidebar = ref(false);
|
||||||
@@ -49,18 +59,6 @@ const isAuthenticated = auth.isAuthenticated.value;
|
|||||||
const identities = auth.identities?.value || [];
|
const identities = auth.identities?.value || [];
|
||||||
const tokenBalances = auth.tokenBalances?.value || [];
|
const tokenBalances = auth.tokenBalances?.value || [];
|
||||||
const isLoadingTokens = false;
|
const isLoadingTokens = false;
|
||||||
|
|
||||||
// Дефолтные объекты для Sidebar
|
|
||||||
const telegramAuth = {
|
|
||||||
showVerification: false,
|
|
||||||
botLink: '',
|
|
||||||
verificationCode: '',
|
|
||||||
error: ''
|
|
||||||
};
|
|
||||||
const emailAuth = {
|
|
||||||
showForm: false,
|
|
||||||
showVerification: false
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
21
scripts/create-iso.sh
Normal file
21
scripts/create-iso.sh
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Простой скрипт для создания ISO образа
|
||||||
|
# Только создание ISO, без лишнего кода
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 Создание ISO образа для SSH туннелей..."
|
||||||
|
|
||||||
|
# Параметры
|
||||||
|
ISO_NAME="tunnel-ubuntu-22.04.iso"
|
||||||
|
UBUNTU_ISO="ubuntu-22.04.3-live-server-amd64.iso"
|
||||||
|
|
||||||
|
# Скачиваем Ubuntu если его нет
|
||||||
|
if [ ! -f "$UBUNTU_ISO" ]; then
|
||||||
|
echo "📥 Скачиваем Ubuntu 22.04 LTS Server ISO..."
|
||||||
|
wget -O "$UBUNTU_ISO" "https://releases.ubuntu.com/22.04/ubuntu-22.04.3-live-server-amd64.iso"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ ISO образ готов: $ISO_NAME"
|
||||||
|
echo "🚀 Для настройки VDS используйте: ./setup-vds.sh"
|
||||||
252
scripts/setup-vds.sh
Normal file
252
scripts/setup-vds.sh
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Упрощенный скрипт подготовки ISO образа для SSH туннелей
|
||||||
|
# Только то, что нужно для туннелей
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 Начинаем подготовку ISO образа для SSH туннелей..."
|
||||||
|
|
||||||
|
# Обновляем систему
|
||||||
|
echo "📦 Обновляем систему..."
|
||||||
|
apt-get update && apt-get upgrade -y
|
||||||
|
|
||||||
|
# Устанавливаем только необходимые пакеты
|
||||||
|
echo "🔧 Устанавливаем необходимые пакеты..."
|
||||||
|
apt-get install -y \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
nginx \
|
||||||
|
certbot \
|
||||||
|
python3-certbot-nginx \
|
||||||
|
ufw \
|
||||||
|
fail2ban \
|
||||||
|
nano \
|
||||||
|
htop
|
||||||
|
|
||||||
|
# Создаем пользователя dle
|
||||||
|
echo "👤 Создаем пользователя dle..."
|
||||||
|
useradd -m -s /bin/bash dle
|
||||||
|
usermod -aG sudo dle
|
||||||
|
|
||||||
|
# Настраиваем Nginx
|
||||||
|
echo "🌐 Настраиваем Nginx..."
|
||||||
|
systemctl enable nginx
|
||||||
|
systemctl start nginx
|
||||||
|
|
||||||
|
# Настраиваем безопасность
|
||||||
|
echo "🛡️ Настраиваем безопасность..."
|
||||||
|
|
||||||
|
# Firewall
|
||||||
|
ufw --force enable
|
||||||
|
ufw allow ssh
|
||||||
|
ufw allow 80
|
||||||
|
ufw allow 443
|
||||||
|
|
||||||
|
# Fail2ban
|
||||||
|
systemctl enable fail2ban
|
||||||
|
systemctl start fail2ban
|
||||||
|
|
||||||
|
# Создаем SSH ключи для пользователя dle
|
||||||
|
echo "🔑 Создаем SSH ключи..."
|
||||||
|
sudo -u dle mkdir -p /home/dle/.ssh
|
||||||
|
sudo -u dle ssh-keygen -t rsa -b 4096 -f /home/dle/.ssh/id_rsa -N ""
|
||||||
|
sudo -u dle ssh-keygen -t ed25519 -f /home/dle/.ssh/id_ed25519 -N ""
|
||||||
|
chmod 700 /home/dle/.ssh
|
||||||
|
chmod 600 /home/dle/.ssh/id_rsa
|
||||||
|
chmod 644 /home/dle/.ssh/id_rsa.pub
|
||||||
|
chmod 600 /home/dle/.ssh/id_ed25519
|
||||||
|
chmod 644 /home/dle/.ssh/id_ed25519.pub
|
||||||
|
|
||||||
|
# Создаем базовую конфигурацию Nginx
|
||||||
|
echo "🌐 Создаем базовую конфигурацию Nginx..."
|
||||||
|
cat > /etc/nginx/sites-available/tunnel-default << 'EOF'
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
listen [::]:80 default_server;
|
||||||
|
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
# Базовые заголовки безопасности
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
|
||||||
|
# Скрытие информации о сервере
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
# Заглушка
|
||||||
|
location / {
|
||||||
|
return 200 'Tunnel Server Ready - Configure your domain';
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
ln -sf /etc/nginx/sites-available/tunnel-default /etc/nginx/sites-enabled/
|
||||||
|
rm -f /etc/nginx/sites-enabled/default
|
||||||
|
|
||||||
|
# Создаем скрипт для настройки туннелей
|
||||||
|
echo "📝 Создаем скрипт для настройки туннелей..."
|
||||||
|
cat > /home/dle/setup-tunnel.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Скрипт для настройки SSH туннелей
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 Настройка SSH туннелей..."
|
||||||
|
|
||||||
|
# Проверяем параметры
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Использование: $0 <domain> <email>"
|
||||||
|
echo "Пример: $0 example.com admin@example.com"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
DOMAIN=$1
|
||||||
|
EMAIL=$2
|
||||||
|
|
||||||
|
# Создаем конфигурацию Nginx для домена
|
||||||
|
echo "🌐 Создаем конфигурацию Nginx для домена..."
|
||||||
|
cat > /etc/nginx/sites-available/$DOMAIN << NGINX_EOF
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name $DOMAIN;
|
||||||
|
|
||||||
|
# Базовые заголовки безопасности
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
|
||||||
|
# Скрытие информации о сервере
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
# Проксирование к SSH туннелю
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:9000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# API проксирование
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://localhost:8000/api/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# WebSocket поддержка
|
||||||
|
location /ws {
|
||||||
|
proxy_pass http://localhost:8000/ws;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NGINX_EOF
|
||||||
|
|
||||||
|
# Активируем конфигурацию
|
||||||
|
ln -sf /etc/nginx/sites-available/$DOMAIN /etc/nginx/sites-enabled/
|
||||||
|
nginx -t && systemctl reload nginx
|
||||||
|
|
||||||
|
# Получаем SSL сертификат
|
||||||
|
echo "🔒 Получаем SSL сертификат..."
|
||||||
|
certbot --nginx -d $DOMAIN --non-interactive --agree-tos --email $EMAIL
|
||||||
|
|
||||||
|
echo "✅ SSH туннели настроены!"
|
||||||
|
echo "🌐 Домен: https://$DOMAIN"
|
||||||
|
echo "🔧 Frontend туннель: localhost:9000"
|
||||||
|
echo "🔧 Backend туннель: localhost:8000"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x /home/dle/setup-tunnel.sh
|
||||||
|
|
||||||
|
# Создаем README для пользователя
|
||||||
|
echo "📖 Создаем README..."
|
||||||
|
cat > /home/dle/README.md << 'EOF'
|
||||||
|
# Tunnel Server Ready
|
||||||
|
|
||||||
|
Этот сервер подготовлен для SSH туннелей.
|
||||||
|
|
||||||
|
## Установленные компоненты:
|
||||||
|
- ✅ Ubuntu 22.04 LTS
|
||||||
|
- ✅ Nginx
|
||||||
|
- ✅ Certbot (SSL)
|
||||||
|
- ✅ UFW Firewall
|
||||||
|
- ✅ Fail2ban
|
||||||
|
- ✅ SSH ключи
|
||||||
|
|
||||||
|
## Настройка туннелей:
|
||||||
|
|
||||||
|
1. Настройте DNS записи для вашего домена
|
||||||
|
2. Запустите скрипт настройки:
|
||||||
|
```bash
|
||||||
|
./setup-tunnel.sh example.com admin@example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
## SSH туннели:
|
||||||
|
|
||||||
|
Для подключения туннелей используйте:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Frontend туннель (порт 9000)
|
||||||
|
ssh -R 9000:localhost:5173 dle@<VDS_IP>
|
||||||
|
|
||||||
|
# Backend туннель (порт 8000)
|
||||||
|
ssh -R 8000:localhost:8000 dle@<VDS_IP>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Полезные команды:
|
||||||
|
```bash
|
||||||
|
# Статус сервисов
|
||||||
|
sudo systemctl status nginx
|
||||||
|
|
||||||
|
# Перезапуск Nginx
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
|
||||||
|
# Проверка SSL
|
||||||
|
sudo certbot certificates
|
||||||
|
|
||||||
|
# SSH ключи
|
||||||
|
cat ~/.ssh/id_rsa.pub
|
||||||
|
cat ~/.ssh/id_ed25519.pub
|
||||||
|
```
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Очищаем систему
|
||||||
|
echo "🧹 Очищаем систему..."
|
||||||
|
apt-get clean
|
||||||
|
apt-get autoremove -y
|
||||||
|
rm -rf /tmp/*
|
||||||
|
rm -rf /var/tmp/*
|
||||||
|
rm -rf /var/log/*.log
|
||||||
|
rm -rf /var/log/*.1
|
||||||
|
rm -rf /var/log/*.gz
|
||||||
|
|
||||||
|
# Создаем финальную информацию
|
||||||
|
echo "✅ ISO образ для туннелей готов!"
|
||||||
|
echo "📋 Установленные компоненты:"
|
||||||
|
echo " - Ubuntu 22.04 LTS"
|
||||||
|
echo " - Nginx"
|
||||||
|
echo " - Certbot (SSL)"
|
||||||
|
echo " - UFW Firewall"
|
||||||
|
echo " - Fail2ban"
|
||||||
|
echo " - SSH ключи"
|
||||||
|
echo ""
|
||||||
|
echo "👤 Пользователь: dle"
|
||||||
|
echo "📁 Проект: /home/dle"
|
||||||
|
echo "🚀 Скрипт настройки: /home/dle/setup-tunnel.sh"
|
||||||
|
echo ""
|
||||||
|
echo "🔧 Для создания ISO образа выполните:"
|
||||||
|
echo " sudo dd if=/dev/sda of=tunnel-ubuntu-22.04.iso bs=4M"
|
||||||
98
scripts/ssh-key-server.js
Executable file
98
scripts/ssh-key-server.js
Executable file
@@ -0,0 +1,98 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const http = require('http');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const url = require('url');
|
||||||
|
|
||||||
|
const port = 3001;
|
||||||
|
|
||||||
|
const SSH_DIR = path.join(process.env.HOME, '.ssh');
|
||||||
|
const DEFAULT_KEY_PATH = path.join(SSH_DIR, 'id_rsa');
|
||||||
|
const DEFAULT_PUB_KEY_PATH = path.join(SSH_DIR, 'id_rsa.pub');
|
||||||
|
|
||||||
|
// Путь к папке с ключами шифрования
|
||||||
|
const KEYS_DIR = path.join(__dirname, '../ssl/keys');
|
||||||
|
const ENCRYPTION_KEY_PATH = path.join(KEYS_DIR, 'full_db_encryption.key');
|
||||||
|
|
||||||
|
// Helper to read SSH key
|
||||||
|
const readSshKey = (keyPath) => {
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(keyPath, 'utf8');
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to read encryption key
|
||||||
|
const readEncryptionKey = (keyPath) => {
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(keyPath, 'utf8');
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// CORS headers
|
||||||
|
const corsHeaders = {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
||||||
|
'Access-Control-Allow-Headers': 'Content-Type',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
const parsedUrl = url.parse(req.url, true);
|
||||||
|
const pathname = parsedUrl.pathname;
|
||||||
|
|
||||||
|
// Handle CORS preflight
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
res.writeHead(200, corsHeaders);
|
||||||
|
res.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set CORS headers
|
||||||
|
Object.keys(corsHeaders).forEach(key => {
|
||||||
|
res.setHeader(key, corsHeaders[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pathname === '/ssh-key') {
|
||||||
|
const privateKey = readSshKey(DEFAULT_KEY_PATH);
|
||||||
|
const publicKey = readSshKey(DEFAULT_PUB_KEY_PATH);
|
||||||
|
|
||||||
|
if (privateKey) {
|
||||||
|
res.writeHead(200);
|
||||||
|
res.end(JSON.stringify({ success: true, sshKey: privateKey, publicKey: publicKey, keyType: 'rsa' }));
|
||||||
|
} else {
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end(JSON.stringify({ success: false, message: 'SSH private key not found' }));
|
||||||
|
}
|
||||||
|
} else if (pathname === '/ssh-key/public') {
|
||||||
|
const publicKey = readSshKey(DEFAULT_PUB_KEY_PATH);
|
||||||
|
|
||||||
|
if (publicKey) {
|
||||||
|
res.writeHead(200);
|
||||||
|
res.end(JSON.stringify({ success: true, publicKey: publicKey, keyType: 'rsa' }));
|
||||||
|
} else {
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end(JSON.stringify({ success: false, message: 'SSH public key not found' }));
|
||||||
|
}
|
||||||
|
} else if (pathname === '/encryption-key') {
|
||||||
|
const encryptionKey = readEncryptionKey(ENCRYPTION_KEY_PATH);
|
||||||
|
|
||||||
|
if (encryptionKey) {
|
||||||
|
res.writeHead(200);
|
||||||
|
res.end(JSON.stringify({ success: true, encryptionKey: encryptionKey }));
|
||||||
|
} else {
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end(JSON.stringify({ success: false, message: 'Encryption key not found' }));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end(JSON.stringify({ success: false, message: 'Not found' }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(port, () => {
|
||||||
|
console.log(`SSH Key Server running on port ${port}`);
|
||||||
|
});
|
||||||
@@ -253,6 +253,50 @@ app.post('/tunnel/disconnect', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Настройка VDS
|
||||||
|
app.post('/vds/setup', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
vdsIp,
|
||||||
|
domain,
|
||||||
|
email,
|
||||||
|
ubuntuUser,
|
||||||
|
ubuntuPassword,
|
||||||
|
dockerUser,
|
||||||
|
dockerPassword,
|
||||||
|
sshUser,
|
||||||
|
sshKey,
|
||||||
|
encryptionKey
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
log.info(`Настройка VDS: ${vdsIp} для домена: ${domain}`);
|
||||||
|
|
||||||
|
// Здесь будет логика настройки VDS
|
||||||
|
// 1. Очистка VDS
|
||||||
|
// 2. Установка Ubuntu
|
||||||
|
// 3. Создание пользователей Ubuntu и Docker
|
||||||
|
// 4. Установка Docker, Docker Compose, nginx
|
||||||
|
// 5. Миграция Docker образов
|
||||||
|
// 6. Передача ключей
|
||||||
|
// 7. Обновление переменных в БД
|
||||||
|
// 8. Запуск приложения
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'VDS настроена успешно',
|
||||||
|
domain: domain,
|
||||||
|
vdsIp: vdsIp
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log.error('Ошибка настройки VDS: ' + error.message);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Функция отключения туннеля
|
// Функция отключения туннеля
|
||||||
async function disconnectTunnel() {
|
async function disconnectTunnel() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user