Files
vServer/Backend/admin/go/sites/methods.go
Falknat 4b13923375 Добавление и Удаление сайта
Backend (Go):
- Добавлен полный функционал создания сайтов
- Добавлен функционал удаления сайтов
- Новые API методы в admin.go:
- Добавлен шаблон стартовой страницы
- Добавлена функция DecodeBase64

Исправления критических ошибок:
- Исправлена работа wildcard алиасов (*.domain.com) в handler.go
- Исправлены ошибки "файл не найден" при создании файлов

Frontend (JavaScript + HTML + CSS):
- Добавлена страница создания сайта
- Добавлена кнопка "Удалить сайт" в редактировании
- Мелкие доработки стилей

Build:
- Обновлён build_admin.ps1 - добавлен шаг генерации биндингов (wails generate module)

Fixes:
- #fix Wildcard алиасы (*.domain.com) теперь работают корректно
- #fix Удалён порт из host при проверке алиасов
- #fix Приоритет точных доменов над wildcard
- #fix Ошибки "файл не найден" при создании сайтов/vAccess
- #fix Секция добавления сайта теперь скрывается при навигации
2025-11-14 14:18:26 +07:00

313 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package sites
import (
_ "embed"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
config "vServer/Backend/config"
tools "vServer/Backend/tools"
)
//go:embed templates/index.tmpl
var indexTemplate string
// CreateNewSite создаёт новый сайт со всей необходимой структурой
func CreateNewSite(siteData SiteInfo) error {
// 1. Валидация данных
if err := ValidateSiteData(siteData); err != nil {
return err
}
// 2. Создание структуры папок
if err := CreateSiteFolder(siteData.Host); err != nil {
return fmt.Errorf("ошибка создания папок: %w", err)
}
// 3. Создание стартового файла
if err := CreateStarterFile(siteData.Host, siteData.RootFile); err != nil {
return fmt.Errorf("ошибка создания стартового файла: %w", err)
}
// 4. Создание пустого vAccess.conf
if err := CreateVAccessFile(siteData.Host); err != nil {
return fmt.Errorf("ошибка создания vAccess.conf: %w", err)
}
// 5. Добавление сайта в конфиг
if err := AddSiteToConfig(siteData); err != nil {
return fmt.Errorf("ошибка добавления в конфиг: %w", err)
}
tools.Logs_file(0, "SITES", fmt.Sprintf("✅ Новый сайт создан: %s (%s)", siteData.Name, siteData.Host), "logs_config.log", true)
return nil
}
// ValidateSiteData проверяет данные нового сайта
func ValidateSiteData(siteData SiteInfo) error {
// Проверка обязательных полей
if strings.TrimSpace(siteData.Host) == "" {
return errors.New("поле Host обязательно для заполнения")
}
if strings.TrimSpace(siteData.Name) == "" {
return errors.New("поле Name обязательно для заполнения")
}
if strings.TrimSpace(siteData.RootFile) == "" {
return errors.New("поле RootFile обязательно для заполнения")
}
// Проверка уникальности host
for _, site := range config.ConfigData.Site_www {
if strings.EqualFold(site.Host, siteData.Host) {
return fmt.Errorf("сайт с host '%s' уже существует", siteData.Host)
}
}
// Проверка валидности status
if siteData.Status != "active" && siteData.Status != "inactive" {
return errors.New("status должен быть 'active' или 'inactive'")
}
return nil
}
// CreateSiteFolder создаёт структуру папок для нового сайта
func CreateSiteFolder(host string) error {
// Создаём путь: WebServer/www/{host}/public_www/
folderPath := filepath.Join("WebServer", "www", host, "public_www")
absPath, err := tools.AbsPath(folderPath)
if err != nil {
return err
}
// Создаём все необходимые папки
if err := os.MkdirAll(absPath, 0755); err != nil {
return fmt.Errorf("не удалось создать папку: %w", err)
}
tools.Logs_file(0, "SITES", fmt.Sprintf("📁 Создана папка: %s", folderPath), "logs_config.log", false)
return nil
}
// CreateStarterFile создаёт стартовый файл (index.html или index.php)
func CreateStarterFile(host, rootFile string) error {
filePath := filepath.Join("WebServer", "www", host, "public_www", rootFile)
// Получаем абсолютный путь БЕЗ проверки существования
absPath, err := filepath.Abs(filePath)
if err != nil {
return fmt.Errorf("ошибка получения абсолютного пути: %w", err)
}
// Генерируем контент из шаблона
content := generateTemplate(host, rootFile)
// Записываем файл
if err := os.WriteFile(absPath, []byte(content), 0644); err != nil {
return fmt.Errorf("не удалось создать файл: %w", err)
}
tools.Logs_file(0, "SITES", fmt.Sprintf("📄 Создан стартовый файл: %s", rootFile), "logs_config.log", false)
return nil
}
// CreateVAccessFile создаёт пустой конфиг vAccess
func CreateVAccessFile(host string) error {
filePath := filepath.Join("WebServer", "www", host, "vAccess.conf")
// Получаем абсолютный путь БЕЗ проверки существования
absPath, err := filepath.Abs(filePath)
if err != nil {
return fmt.Errorf("ошибка получения абсолютного пути: %w", err)
}
content := `# vAccess Configuration
# Правила применяются сверху вниз
# Пример правила (закомментировано):
# type: Disable
# type_file: *.php
# path_access: /uploads/*
# url_error: 404
`
if err := os.WriteFile(absPath, []byte(content), 0644); err != nil {
return fmt.Errorf("не удалось создать vAccess.conf: %w", err)
}
tools.Logs_file(0, "SITES", "🔒 Создан vAccess.conf", "logs_config.log", false)
return nil
}
// AddSiteToConfig добавляет новый сайт в config.json
func AddSiteToConfig(siteData SiteInfo) error {
// Создаём новую запись
newSite := config.Site_www{
Name: siteData.Name,
Host: siteData.Host,
Alias: siteData.Alias,
Status: siteData.Status,
Root_file: siteData.RootFile,
Root_file_routing: siteData.RootFileRouting,
}
// Добавляем в массив
config.ConfigData.Site_www = append(config.ConfigData.Site_www, newSite)
// Сохраняем конфиг в файл
if err := SaveConfig(); err != nil {
return err
}
tools.Logs_file(0, "SITES", "💾 Конфигурация обновлена", "logs_config.log", false)
return nil
}
// SaveConfig сохраняет текущую конфигурацию в файл
func SaveConfig() error {
// Форматируем JSON с отступами
jsonData, err := json.MarshalIndent(config.ConfigData, "", " ")
if err != nil {
return fmt.Errorf("ошибка форматирования JSON: %w", err)
}
// Получаем абсолютный путь к файлу конфига
absPath, err := tools.AbsPath(config.ConfigPath)
if err != nil {
return err
}
// Записываем в файл
if err := os.WriteFile(absPath, jsonData, 0644); err != nil {
return fmt.Errorf("ошибка записи файла: %w", err)
}
return nil
}
// UploadSiteCertificate загружает SSL сертификат для сайта
func UploadSiteCertificate(host, certType string, certData []byte) error {
// Создаём папку для сертификатов
certDir := filepath.Join("WebServer", "cert", host)
absCertDir, err := tools.AbsPath(certDir)
if err != nil {
return err
}
if err := os.MkdirAll(absCertDir, 0755); err != nil {
return fmt.Errorf("не удалось создать папку для сертификатов: %w", err)
}
// Определяем имя файла
var fileName string
switch certType {
case "certificate":
fileName = "certificate.crt"
case "privatekey":
fileName = "private.key"
case "cabundle":
fileName = "ca_bundle.crt"
default:
return fmt.Errorf("неизвестный тип сертификата: %s", certType)
}
// Путь к файлу сертификата
certFilePath := filepath.Join(absCertDir, fileName)
// Записываем файл
if err := os.WriteFile(certFilePath, certData, 0644); err != nil {
return fmt.Errorf("не удалось сохранить сертификат: %w", err)
}
tools.Logs_file(0, "SITES", fmt.Sprintf("🔒 Загружен сертификат: %s для %s", fileName, host), "logs_config.log", true)
return nil
}
// DeleteSiteCertificates удаляет сертификаты сайта
func DeleteSiteCertificates(host string) error {
certDir := filepath.Join("WebServer", "cert", host)
// Получаем абсолютный путь к папке сертификатов
absCertDir, err := filepath.Abs(certDir)
if err != nil {
return fmt.Errorf("ошибка получения пути: %w", err)
}
// Проверяем, существует ли папка
if _, err := os.Stat(absCertDir); os.IsNotExist(err) {
return nil // Папки нет - ничего удалять не нужно
}
// Удаляем папку со всем содержимым
if err := os.RemoveAll(absCertDir); err != nil {
return fmt.Errorf("не удалось удалить папку сертификатов: %w", err)
}
tools.Logs_file(0, "SITES", fmt.Sprintf("🗑️ Удалены сертификаты для: %s", host), "logs_config.log", true)
return nil
}
// DeleteSite полностью удаляет сайт
func DeleteSite(host string) error {
// 1. Проверяем, существует ли сайт в конфиге
siteIndex := -1
for i, site := range config.ConfigData.Site_www {
if site.Host == host {
siteIndex = i
break
}
}
if siteIndex == -1 {
return fmt.Errorf("сайт с host '%s' не найден в конфигурации", host)
}
// 2. Удаляем папку сайта
siteDir := filepath.Join("WebServer", "www", host)
absSiteDir, err := filepath.Abs(siteDir)
if err != nil {
return fmt.Errorf("ошибка получения пути: %w", err)
}
if _, err := os.Stat(absSiteDir); err == nil {
if err := os.RemoveAll(absSiteDir); err != nil {
return fmt.Errorf("не удалось удалить папку сайта: %w", err)
}
tools.Logs_file(0, "SITES", fmt.Sprintf("🗑️ Удалена папка сайта: %s", siteDir), "logs_config.log", false)
}
// 3. Удаляем сертификаты
if err := DeleteSiteCertificates(host); err != nil {
// Логируем ошибку, но продолжаем удаление
tools.Logs_file(1, "SITES", fmt.Sprintf("Ошибка удаления сертификатов: %v", err), "logs_config.log", false)
}
// 4. Удаляем из конфига
config.ConfigData.Site_www = append(
config.ConfigData.Site_www[:siteIndex],
config.ConfigData.Site_www[siteIndex+1:]...,
)
// 5. Сохраняем конфиг
if err := SaveConfig(); err != nil {
return fmt.Errorf("ошибка сохранения конфигурации: %w", err)
}
tools.Logs_file(0, "SITES", fmt.Sprintf("✅ Сайт '%s' полностью удалён", host), "logs_config.log", true)
return nil
}
// generateTemplate генерирует шаблон для нового сайта
func generateTemplate(host, rootFile string) string {
// Для всех типов файлов используем один HTML шаблон
return strings.ReplaceAll(indexTemplate, "{{.Host}}", host)
}