565 lines
14 KiB
Go
565 lines
14 KiB
Go
package admin
|
||
|
||
import (
|
||
"context"
|
||
"crypto/ecdsa"
|
||
"crypto/elliptic"
|
||
"crypto/rand"
|
||
"crypto/x509"
|
||
"crypto/x509/pkix"
|
||
"encoding/json"
|
||
"encoding/pem"
|
||
"fmt"
|
||
"math/big"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"time"
|
||
|
||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||
|
||
webserver "vServer/Backend/WebServer"
|
||
"vServer/Backend/WebServer/acme"
|
||
"vServer/Backend/admin/go/proxy"
|
||
"vServer/Backend/admin/go/services"
|
||
"vServer/Backend/admin/go/sites"
|
||
"vServer/Backend/admin/go/vaccess"
|
||
config "vServer/Backend/config"
|
||
tools "vServer/Backend/tools"
|
||
)
|
||
|
||
type App struct {
|
||
ctx context.Context
|
||
}
|
||
|
||
var appContext context.Context
|
||
|
||
func NewApp() *App {
|
||
return &App{}
|
||
}
|
||
|
||
var isSingleInstance bool = false
|
||
|
||
func initDirectories() {
|
||
dirs := []string{
|
||
"WebServer",
|
||
"WebServer/www",
|
||
"WebServer/cert",
|
||
"WebServer/cert/no_cert",
|
||
"WebServer/tools",
|
||
"WebServer/tools/logs",
|
||
"WebServer/tools/error_page",
|
||
"WebServer/tools/Proxy_vAccess",
|
||
}
|
||
|
||
for _, dir := range dirs {
|
||
os.MkdirAll(dir, 0755)
|
||
}
|
||
|
||
// Дефолтный config.json
|
||
if _, err := os.Stat(config.ConfigPath); os.IsNotExist(err) {
|
||
defaultConfig := map[string]interface{}{
|
||
"Site_www": []interface{}{},
|
||
"Proxy_Service": []interface{}{},
|
||
"Soft_Settings": map[string]interface{}{
|
||
"php_host": "localhost",
|
||
"php_port": 8000,
|
||
"mysql_host": "127.0.0.1",
|
||
"mysql_port": 3306,
|
||
"proxy_enabled": false,
|
||
"ACME_enabled": false,
|
||
},
|
||
}
|
||
data, _ := json.MarshalIndent(defaultConfig, "", " ")
|
||
os.WriteFile(config.ConfigPath, data, 0644)
|
||
}
|
||
|
||
// Страница ошибки
|
||
errorPage := "WebServer/tools/error_page/index.html"
|
||
if _, err := os.Stat(errorPage); os.IsNotExist(err) {
|
||
os.WriteFile(errorPage, []byte("<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Error</title></head><body><h1>Error</h1></body></html>"), 0644)
|
||
}
|
||
|
||
// Fallback SSL-сертификат (самоподписанный)
|
||
certFile := filepath.Join("WebServer", "cert", "no_cert", "certificate.crt")
|
||
keyFile := filepath.Join("WebServer", "cert", "no_cert", "private.key")
|
||
if _, err := os.Stat(certFile); os.IsNotExist(err) {
|
||
generateSelfSignedCert(certFile, keyFile)
|
||
}
|
||
}
|
||
|
||
func generateSelfSignedCert(certPath, keyPath string) {
|
||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
template := x509.Certificate{
|
||
SerialNumber: big.NewInt(1),
|
||
Subject: pkix.Name{CommonName: "localhost"},
|
||
NotBefore: time.Now(),
|
||
NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour),
|
||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||
IsCA: true,
|
||
BasicConstraintsValid: true,
|
||
}
|
||
|
||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
certOut, err := os.Create(certPath)
|
||
if err != nil {
|
||
return
|
||
}
|
||
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||
certOut.Close()
|
||
|
||
keyDER, err := x509.MarshalECPrivateKey(key)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
keyOut, err := os.Create(keyPath)
|
||
if err != nil {
|
||
return
|
||
}
|
||
pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER})
|
||
keyOut.Close()
|
||
}
|
||
|
||
func (a *App) Startup(ctx context.Context) {
|
||
a.ctx = ctx
|
||
appContext = ctx
|
||
|
||
// Создаём структуру папок при первом запуске
|
||
initDirectories()
|
||
|
||
// Проверяем, не запущен ли уже vServer
|
||
if !tools.CheckSingleInstance() {
|
||
runtime.EventsEmit(ctx, "server:already_running", true)
|
||
// Только мониторинг, не запускаем сервисы
|
||
config.LoadConfig()
|
||
go a.monitorServices()
|
||
isSingleInstance = false
|
||
return
|
||
}
|
||
|
||
isSingleInstance = true
|
||
|
||
// Инициализируем время запуска
|
||
tools.ServerUptime("start")
|
||
|
||
// Загружаем конфигурацию
|
||
config.LoadConfig()
|
||
time.Sleep(50 * time.Millisecond)
|
||
|
||
// Запускаем handler
|
||
webserver.StartHandler()
|
||
time.Sleep(50 * time.Millisecond)
|
||
|
||
// Загружаем сертификаты
|
||
webserver.Cert_start()
|
||
time.Sleep(50 * time.Millisecond)
|
||
|
||
// Инициализируем ACME менеджер (true = production, false = staging)
|
||
if err := acme.Init(true); err != nil {
|
||
tools.Logs_file(1, "ACME", "❌ Ошибка инициализации ACME: "+err.Error(), "logs_acme.log", true)
|
||
} else {
|
||
// Запускаем фоновую проверку сертификатов каждые 24 часа
|
||
acme.StartBackgroundRenewal(24 * time.Hour)
|
||
}
|
||
time.Sleep(50 * time.Millisecond)
|
||
|
||
// Запускаем серверы
|
||
go webserver.StartHTTPS()
|
||
time.Sleep(50 * time.Millisecond)
|
||
|
||
go webserver.StartHTTP()
|
||
time.Sleep(50 * time.Millisecond)
|
||
|
||
// Запускаем PHP
|
||
webserver.PHP_Start()
|
||
time.Sleep(50 * time.Millisecond)
|
||
|
||
// Запускаем MySQL асинхронно
|
||
go webserver.StartMySQLServer(false)
|
||
|
||
// Автоматическое получение SSL сертификатов для доменов с AutoCreateSSL=true
|
||
if config.ConfigData.Soft_Settings.ACME_enabled {
|
||
go func() {
|
||
time.Sleep(2 * time.Second) // Ждём пока HTTP сервер полностью запустится
|
||
results := acme.ObtainAllCertificates()
|
||
if len(results) > 0 {
|
||
// Перезагружаем сертификаты после получения
|
||
webserver.ReloadCertificates()
|
||
}
|
||
}()
|
||
}
|
||
|
||
// Запускаем мониторинг статусов
|
||
go a.monitorServices()
|
||
}
|
||
|
||
func (a *App) GetAllServicesStatus() services.AllServicesStatus {
|
||
return services.GetAllServicesStatus()
|
||
}
|
||
|
||
func (a *App) CheckServicesReady() bool {
|
||
status := services.GetAllServicesStatus()
|
||
return status.HTTP.Status && status.HTTPS.Status && status.MySQL.Status && status.PHP.Status
|
||
}
|
||
|
||
func (a *App) Shutdown(ctx context.Context) {
|
||
// Останавливаем все сервисы при закрытии приложения
|
||
if isSingleInstance {
|
||
webserver.StopHTTPServer()
|
||
webserver.StopHTTPSServer()
|
||
webserver.PHP_Stop()
|
||
webserver.StopMySQLServer()
|
||
|
||
// Освобождаем мьютекс
|
||
tools.ReleaseMutex()
|
||
}
|
||
}
|
||
|
||
func (a *App) monitorServices() {
|
||
time.Sleep(300 * time.Millisecond)
|
||
|
||
// Первое событие сразу
|
||
status := services.GetAllServicesStatus()
|
||
runtime.EventsEmit(appContext, "service:changed", status)
|
||
|
||
for {
|
||
time.Sleep(500 * time.Millisecond)
|
||
status := services.GetAllServicesStatus()
|
||
runtime.EventsEmit(appContext, "service:changed", status)
|
||
}
|
||
}
|
||
|
||
func (a *App) GetSitesList() []sites.SiteInfo {
|
||
return sites.GetSitesList()
|
||
}
|
||
|
||
func (a *App) GetProxyList() []proxy.ProxyInfo {
|
||
return proxy.GetProxyList()
|
||
}
|
||
|
||
func (a *App) StartServer() string {
|
||
webserver.Cert_start()
|
||
|
||
go webserver.StartHTTPS()
|
||
go webserver.StartHTTP()
|
||
|
||
webserver.PHP_Start()
|
||
go webserver.StartMySQLServer(false)
|
||
|
||
return "Server started"
|
||
}
|
||
|
||
func (a *App) StopServer() string {
|
||
webserver.StopHTTPServer()
|
||
webserver.StopHTTPSServer()
|
||
webserver.PHP_Stop()
|
||
webserver.StopMySQLServer()
|
||
|
||
return "Server stopped"
|
||
}
|
||
|
||
func (a *App) ReloadConfig() string {
|
||
config.LoadConfig()
|
||
return "Config reloaded"
|
||
}
|
||
|
||
func (a *App) GetConfig() interface{} {
|
||
return config.ConfigData
|
||
}
|
||
|
||
func (a *App) SaveConfig(configJSON string) string {
|
||
// Форматируем JSON перед сохранением
|
||
var tempConfig interface{}
|
||
err := json.Unmarshal([]byte(configJSON), &tempConfig)
|
||
if err != nil {
|
||
return "Error: Invalid JSON"
|
||
}
|
||
|
||
formattedJSON, err := json.MarshalIndent(tempConfig, "", " ")
|
||
if err != nil {
|
||
return "Error: " + err.Error()
|
||
}
|
||
|
||
// Сохранение конфига в файл
|
||
err = os.WriteFile(config.ConfigPath, formattedJSON, 0644)
|
||
if err != nil {
|
||
return "Error: " + err.Error()
|
||
}
|
||
|
||
// Перезагружаем конфигурацию
|
||
config.LoadConfig()
|
||
return "Config saved"
|
||
}
|
||
|
||
func (a *App) RestartAllServices() string {
|
||
// Останавливаем все сервисы
|
||
webserver.StopHTTPServer()
|
||
webserver.StopHTTPSServer()
|
||
webserver.PHP_Stop()
|
||
webserver.StopMySQLServer()
|
||
time.Sleep(500 * time.Millisecond)
|
||
|
||
// Перезагружаем конфиг
|
||
config.LoadConfig()
|
||
time.Sleep(200 * time.Millisecond)
|
||
|
||
// Обновляем кэш статусов сайтов
|
||
webserver.UpdateSiteStatusCache()
|
||
|
||
// Перезагружаем сертификаты
|
||
webserver.Cert_start()
|
||
time.Sleep(50 * time.Millisecond)
|
||
|
||
// Запускаем серверы заново
|
||
go webserver.StartHTTPS()
|
||
time.Sleep(50 * time.Millisecond)
|
||
|
||
go webserver.StartHTTP()
|
||
time.Sleep(50 * time.Millisecond)
|
||
|
||
webserver.PHP_Start()
|
||
time.Sleep(200 * time.Millisecond)
|
||
|
||
go webserver.StartMySQLServer(false)
|
||
|
||
return "All services restarted"
|
||
}
|
||
|
||
// Управление отдельными сервисами
|
||
func (a *App) StartHTTPService() string {
|
||
// Обновляем кэш перед запуском
|
||
webserver.UpdateSiteStatusCache()
|
||
go webserver.StartHTTP()
|
||
return "HTTP started"
|
||
}
|
||
|
||
func (a *App) StopHTTPService() string {
|
||
webserver.StopHTTPServer()
|
||
return "HTTP stopped"
|
||
}
|
||
|
||
func (a *App) StartHTTPSService() string {
|
||
// Обновляем кэш перед запуском
|
||
webserver.UpdateSiteStatusCache()
|
||
go webserver.StartHTTPS()
|
||
return "HTTPS started"
|
||
}
|
||
|
||
func (a *App) StopHTTPSService() string {
|
||
webserver.StopHTTPSServer()
|
||
return "HTTPS stopped"
|
||
}
|
||
|
||
func (a *App) StartMySQLService() string {
|
||
go webserver.StartMySQLServer(false)
|
||
return "MySQL started"
|
||
}
|
||
|
||
func (a *App) StopMySQLService() string {
|
||
webserver.StopMySQLServer()
|
||
return "MySQL stopped"
|
||
}
|
||
|
||
func (a *App) StartPHPService() string {
|
||
webserver.PHP_Start()
|
||
return "PHP started"
|
||
}
|
||
|
||
func (a *App) StopPHPService() string {
|
||
webserver.PHP_Stop()
|
||
return "PHP stopped"
|
||
}
|
||
|
||
func (a *App) EnableProxyService() string {
|
||
config.ConfigData.Soft_Settings.Proxy_enabled = true
|
||
|
||
// Сохраняем в файл
|
||
configJSON, _ := json.Marshal(config.ConfigData)
|
||
os.WriteFile(config.ConfigPath, configJSON, 0644)
|
||
|
||
return "Proxy enabled"
|
||
}
|
||
|
||
func (a *App) DisableProxyService() string {
|
||
config.ConfigData.Soft_Settings.Proxy_enabled = false
|
||
|
||
// Сохраняем в файл
|
||
configJSON, _ := json.Marshal(config.ConfigData)
|
||
os.WriteFile(config.ConfigPath, configJSON, 0644)
|
||
|
||
return "Proxy disabled"
|
||
}
|
||
|
||
func (a *App) EnableACMEService() string {
|
||
config.ConfigData.Soft_Settings.ACME_enabled = true
|
||
|
||
// Сохраняем в файл
|
||
configJSON, _ := json.MarshalIndent(config.ConfigData, "", " ")
|
||
os.WriteFile(config.ConfigPath, configJSON, 0644)
|
||
|
||
return "ACME enabled"
|
||
}
|
||
|
||
func (a *App) DisableACMEService() string {
|
||
config.ConfigData.Soft_Settings.ACME_enabled = false
|
||
|
||
// Сохраняем в файл
|
||
configJSON, _ := json.MarshalIndent(config.ConfigData, "", " ")
|
||
os.WriteFile(config.ConfigPath, configJSON, 0644)
|
||
|
||
return "ACME disabled"
|
||
}
|
||
|
||
func (a *App) OpenSiteFolder(host string) string {
|
||
folderPath := "WebServer/www/" + host
|
||
|
||
absPath, err := tools.AbsPath(folderPath)
|
||
if err != nil {
|
||
return "Error: " + err.Error()
|
||
}
|
||
|
||
exec.Command("explorer", absPath).Start()
|
||
return "Folder opened"
|
||
}
|
||
|
||
func (a *App) GetVAccessRules(host string, isProxy bool) *vaccess.VAccessConfig {
|
||
config, err := vaccess.GetVAccessConfig(host, isProxy)
|
||
if err != nil {
|
||
return &vaccess.VAccessConfig{Rules: []vaccess.VAccessRule{}}
|
||
}
|
||
return config
|
||
}
|
||
|
||
func (a *App) SaveVAccessRules(host string, isProxy bool, configJSON string) string {
|
||
var config vaccess.VAccessConfig
|
||
err := json.Unmarshal([]byte(configJSON), &config)
|
||
if err != nil {
|
||
return "Error: Invalid JSON"
|
||
}
|
||
|
||
err = vaccess.SaveVAccessConfig(host, isProxy, &config)
|
||
if err != nil {
|
||
return "Error: " + err.Error()
|
||
}
|
||
|
||
return "vAccess saved"
|
||
}
|
||
|
||
func (a *App) UpdateSiteCache() string {
|
||
webserver.UpdateSiteStatusCache()
|
||
return "Cache updated"
|
||
}
|
||
|
||
func (a *App) CreateNewSite(siteJSON string) string {
|
||
var siteData sites.SiteInfo
|
||
err := json.Unmarshal([]byte(siteJSON), &siteData)
|
||
if err != nil {
|
||
return "Error: Invalid JSON - " + err.Error()
|
||
}
|
||
|
||
err = sites.CreateNewSite(siteData)
|
||
if err != nil {
|
||
return "Error: " + err.Error()
|
||
}
|
||
|
||
config.LoadConfig()
|
||
return "Site created successfully"
|
||
}
|
||
|
||
func (a *App) UploadCertificate(host, certType, certDataBase64 string) string {
|
||
certData, err := tools.DecodeBase64(certDataBase64)
|
||
if err != nil {
|
||
return "Error: Invalid base64 data - " + err.Error()
|
||
}
|
||
|
||
err = sites.UploadSiteCertificate(host, certType, certData)
|
||
if err != nil {
|
||
return "Error: " + err.Error()
|
||
}
|
||
|
||
webserver.ReloadCertificates()
|
||
return "Certificate uploaded successfully"
|
||
}
|
||
|
||
func (a *App) ReloadSSLCertificates() string {
|
||
webserver.ReloadCertificates()
|
||
return "SSL certificates reloaded"
|
||
}
|
||
|
||
func (a *App) DeleteSite(host string) string {
|
||
err := sites.DeleteSite(host)
|
||
if err != nil {
|
||
return "Error: " + err.Error()
|
||
}
|
||
|
||
config.LoadConfig()
|
||
return "Site deleted successfully"
|
||
}
|
||
|
||
// ObtainSSLCertificate получает SSL сертификат для домена через Let's Encrypt
|
||
func (a *App) ObtainSSLCertificate(domain string) string {
|
||
result := acme.ObtainCertificate(domain)
|
||
|
||
if result.Success {
|
||
// Перезагружаем сертификаты после получения
|
||
webserver.ReloadCertificates()
|
||
return "SSL certificate obtained successfully for " + domain
|
||
}
|
||
|
||
return "Error: " + result.Error
|
||
}
|
||
|
||
// ObtainAllSSLCertificates получает сертификаты для всех доменов с AutoCreateSSL
|
||
func (a *App) ObtainAllSSLCertificates() string {
|
||
results := acme.ObtainAllCertificates()
|
||
|
||
successCount := 0
|
||
errorCount := 0
|
||
|
||
for _, r := range results {
|
||
if r.Success {
|
||
successCount++
|
||
} else {
|
||
errorCount++
|
||
}
|
||
}
|
||
|
||
if len(results) > 0 {
|
||
// Перезагружаем сертификаты после получения
|
||
webserver.ReloadCertificates()
|
||
}
|
||
|
||
return fmt.Sprintf("Completed: %d success, %d errors", successCount, errorCount)
|
||
}
|
||
|
||
// GetCertInfo получает информацию о сертификате для домена
|
||
func (a *App) GetCertInfo(domain string) acme.CertInfo {
|
||
return acme.GetCertInfo(domain)
|
||
}
|
||
|
||
// GetAllCertsInfo получает информацию о всех сертификатах
|
||
func (a *App) GetAllCertsInfo() []acme.CertInfo {
|
||
return acme.GetAllCertsInfo()
|
||
}
|
||
|
||
// DeleteCertificate удаляет сертификат для домена
|
||
func (a *App) DeleteCertificate(domain string) string {
|
||
err := acme.DeleteCertificate(domain)
|
||
if err != nil {
|
||
return "Error: " + err.Error()
|
||
}
|
||
|
||
// Перезагружаем сертификаты после удаления
|
||
webserver.ReloadCertificates()
|
||
return "Certificate deleted successfully"
|
||
} |