mirror of
https://github.com/Direct-Dev-Ru/go-lcg.git
synced 2025-11-16 01:29:55 +00:00
324 lines
14 KiB
Go
324 lines
14 KiB
Go
package serve
|
||
|
||
import (
|
||
"crypto/tls"
|
||
"fmt"
|
||
"html/template"
|
||
"net/http"
|
||
"os"
|
||
"strings"
|
||
|
||
"github.com/direct-dev-ru/linux-command-gpt/config"
|
||
"github.com/direct-dev-ru/linux-command-gpt/serve/templates"
|
||
"github.com/direct-dev-ru/linux-command-gpt/ssl"
|
||
)
|
||
|
||
// makePath создает путь с учетом BasePath
|
||
func makePath(path string) string {
|
||
basePath := config.AppConfig.Server.BasePath
|
||
if basePath == "" || basePath == "/" {
|
||
return path
|
||
}
|
||
|
||
// Убираем слэш в конце basePath если есть
|
||
basePath = strings.TrimSuffix(basePath, "/")
|
||
|
||
// Убираем слэш в начале path если есть
|
||
path = strings.TrimPrefix(path, "/")
|
||
|
||
// Если path пустой, возвращаем basePath с слэшем в конце
|
||
if path == "" {
|
||
return basePath + "/"
|
||
}
|
||
|
||
return basePath + "/" + path
|
||
}
|
||
|
||
// getBasePath возвращает BasePath для использования в шаблонах
|
||
func getBasePath() string {
|
||
basePath := config.AppConfig.Server.BasePath
|
||
if basePath == "" || basePath == "/" {
|
||
return ""
|
||
}
|
||
return strings.TrimSuffix(basePath, "/")
|
||
}
|
||
|
||
// StartResultServer запускает HTTP/HTTPS сервер для просмотра сохраненных результатов
|
||
func StartResultServer(host, port string) error {
|
||
// Инициализируем CSRF менеджер
|
||
if err := InitCSRFManager(); err != nil {
|
||
return fmt.Errorf("failed to initialize CSRF manager: %v", err)
|
||
}
|
||
|
||
// Гарантируем наличие папки результатов и файла истории
|
||
if _, err := os.Stat(config.AppConfig.ResultFolder); os.IsNotExist(err) {
|
||
if mkErr := os.MkdirAll(config.AppConfig.ResultFolder, 0755); mkErr != nil {
|
||
return fmt.Errorf("failed to create results folder: %v", mkErr)
|
||
}
|
||
}
|
||
if _, err := os.Stat(config.AppConfig.ResultHistory); os.IsNotExist(err) {
|
||
if writeErr := Write(config.AppConfig.ResultHistory, []HistoryEntry{}); writeErr != nil {
|
||
return fmt.Errorf("failed to create history file: %v", writeErr)
|
||
}
|
||
}
|
||
|
||
addr := fmt.Sprintf("%s:%s", host, port)
|
||
|
||
// Проверяем, нужно ли использовать HTTPS
|
||
useHTTPS := ssl.ShouldUseHTTPS(host)
|
||
|
||
if useHTTPS {
|
||
// Регистрируем HTTPS маршруты (включая редирект)
|
||
registerHTTPSRoutes()
|
||
|
||
// Создаем директорию для SSL сертификатов
|
||
sslDir := fmt.Sprintf("%s/server/ssl", config.AppConfig.Server.ConfigFolder)
|
||
if err := os.MkdirAll(sslDir, 0755); err != nil {
|
||
return fmt.Errorf("failed to create SSL directory: %v", err)
|
||
}
|
||
|
||
// Загружаем или генерируем SSL сертификат
|
||
cert, err := ssl.LoadOrGenerateCert(host)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to load/generate SSL certificate: %v", err)
|
||
}
|
||
|
||
// Настраиваем TLS
|
||
tlsConfig := &tls.Config{
|
||
Certificates: []tls.Certificate{*cert},
|
||
MinVersion: tls.VersionTLS12,
|
||
MaxVersion: tls.VersionTLS13,
|
||
// Отключаем проверку клиентских сертификатов
|
||
ClientAuth: tls.NoClientCert,
|
||
// Добавляем логирование для отладки
|
||
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||
if config.AppConfig.MainFlags.Debug {
|
||
fmt.Printf("🔍 TLS запрос от %s (SNI: %s)\n", clientHello.Conn.RemoteAddr(), clientHello.ServerName)
|
||
}
|
||
return cert, nil
|
||
},
|
||
}
|
||
|
||
// Создаем HTTPS сервер
|
||
server := &http.Server{
|
||
Addr: addr,
|
||
TLSConfig: tlsConfig,
|
||
}
|
||
|
||
fmt.Printf("🔒 Сервер запущен на https://%s (SSL включен)\n", addr)
|
||
fmt.Println("Нажмите Ctrl+C для остановки")
|
||
|
||
// Тестовое логирование для проверки debug флага
|
||
if config.AppConfig.MainFlags.Debug {
|
||
fmt.Printf("🔍 DEBUG РЕЖИМ ВКЛЮЧЕН - веб-операции будут логироваться\n")
|
||
} else {
|
||
fmt.Printf("🔍 DEBUG РЕЖИМ ОТКЛЮЧЕН - веб-операции не будут логироваться\n")
|
||
}
|
||
|
||
return server.ListenAndServeTLS("", "")
|
||
} else {
|
||
// Регистрируем обычные маршруты для HTTP
|
||
registerRoutes()
|
||
|
||
fmt.Printf("🌐 Сервер запущен на http://%s (HTTP режим)\n", addr)
|
||
fmt.Println("Нажмите Ctrl+C для остановки")
|
||
|
||
// Тестовое логирование для проверки debug флага
|
||
if config.AppConfig.MainFlags.Debug {
|
||
fmt.Printf("🔍 DEBUG РЕЖИМ ВКЛЮЧЕН - веб-операции будут логироваться\n")
|
||
} else {
|
||
fmt.Printf("🔍 DEBUG РЕЖИМ ОТКЛЮЧЕН - веб-операции не будут логироваться\n")
|
||
}
|
||
|
||
return http.ListenAndServe(addr, nil)
|
||
}
|
||
}
|
||
|
||
// handleHTTPSRedirect обрабатывает редирект с HTTP на HTTPS
|
||
func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) {
|
||
// Определяем протокол и хост
|
||
host := r.Host
|
||
if host == "" {
|
||
host = r.Header.Get("Host")
|
||
}
|
||
|
||
// Редиректим на HTTPS
|
||
httpsURL := fmt.Sprintf("https://%s%s", host, r.RequestURI)
|
||
http.Redirect(w, r, httpsURL, http.StatusMovedPermanently)
|
||
}
|
||
|
||
// registerHTTPSRoutes регистрирует маршруты для HTTPS сервера
|
||
func registerHTTPSRoutes() {
|
||
// Регистрируем все маршруты кроме главной страницы
|
||
registerRoutesExceptHome()
|
||
|
||
// Регистрируем главную страницу (строго по BasePath) с проверкой HTTPS
|
||
http.HandleFunc(makePath("/"), func(w http.ResponseWriter, r *http.Request) {
|
||
// Проверяем, пришел ли запрос по HTTP (не HTTPS)
|
||
if r.TLS == nil {
|
||
handleHTTPSRedirect(w, r)
|
||
return
|
||
}
|
||
// Обрабатываем только точные пути: BasePath или BasePath/
|
||
bp := getBasePath()
|
||
p := r.URL.Path
|
||
if (bp == "" && (p == "/" || p == "")) || (bp != "" && (p == bp || p == bp+"/")) {
|
||
AuthMiddleware(handleResultsPage)(w, r)
|
||
return
|
||
}
|
||
renderNotFound(w, "Страница не найдена", bp)
|
||
})
|
||
|
||
// Регистрируем главную страницу без слэша в конце для BasePath
|
||
basePath := config.AppConfig.Server.BasePath
|
||
if basePath != "" && basePath != "/" {
|
||
basePath = strings.TrimSuffix(basePath, "/")
|
||
http.HandleFunc(basePath, func(w http.ResponseWriter, r *http.Request) {
|
||
// Проверяем, пришел ли запрос по HTTP (не HTTPS)
|
||
if r.TLS == nil {
|
||
handleHTTPSRedirect(w, r)
|
||
return
|
||
}
|
||
// Если уже HTTPS, обрабатываем как обычно
|
||
AuthMiddleware(handleResultsPage)(w, r)
|
||
})
|
||
}
|
||
|
||
// Catch-all 404 для любых незарегистрированных путей (только когда BasePath задан)
|
||
if getBasePath() != "" {
|
||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||
renderNotFound(w, "Страница не найдена", getBasePath())
|
||
})
|
||
}
|
||
}
|
||
|
||
// registerRoutesExceptHome регистрирует все маршруты кроме главной страницы
|
||
func registerRoutesExceptHome() {
|
||
// Страница входа (без аутентификации)
|
||
http.HandleFunc(makePath("/login"), handleLoginPage)
|
||
|
||
// API для аутентификации (без аутентификации)
|
||
http.HandleFunc(makePath("/api/login"), handleLogin)
|
||
http.HandleFunc(makePath("/api/logout"), handleLogout)
|
||
http.HandleFunc(makePath("/api/validate-token"), handleValidateToken)
|
||
|
||
// Файлы
|
||
http.HandleFunc(makePath("/file/"), AuthMiddleware(handleFileView))
|
||
http.HandleFunc(makePath("/delete/"), AuthMiddleware(handleDeleteFile))
|
||
|
||
// История запросов
|
||
http.HandleFunc(makePath("/history"), AuthMiddleware(handleHistoryPage))
|
||
http.HandleFunc(makePath("/history/view/"), AuthMiddleware(handleHistoryView))
|
||
http.HandleFunc(makePath("/history/delete/"), AuthMiddleware(handleDeleteHistoryEntry))
|
||
http.HandleFunc(makePath("/history/clear"), AuthMiddleware(handleClearHistory))
|
||
|
||
// Управление промптами
|
||
http.HandleFunc(makePath("/prompts"), AuthMiddleware(handlePromptsPage))
|
||
http.HandleFunc(makePath("/prompts/add"), AuthMiddleware(handleAddPrompt))
|
||
http.HandleFunc(makePath("/prompts/edit/"), AuthMiddleware(handleEditPrompt))
|
||
http.HandleFunc(makePath("/prompts/edit-verbose/"), AuthMiddleware(handleEditVerbosePrompt))
|
||
http.HandleFunc(makePath("/prompts/delete/"), AuthMiddleware(handleDeletePrompt))
|
||
http.HandleFunc(makePath("/prompts/restore/"), AuthMiddleware(handleRestorePrompt))
|
||
http.HandleFunc(makePath("/prompts/restore-verbose/"), AuthMiddleware(handleRestoreVerbosePrompt))
|
||
http.HandleFunc(makePath("/prompts/save-lang"), AuthMiddleware(handleSaveLang))
|
||
|
||
// Веб-страница для выполнения запросов
|
||
http.HandleFunc(makePath("/run"), AuthMiddleware(CSRFMiddleware(handleExecutePage)))
|
||
|
||
// API для выполнения запросов
|
||
http.HandleFunc(makePath("/api/execute"), AuthMiddleware(CSRFMiddleware(handleExecute)))
|
||
// API для сохранения результатов и истории
|
||
http.HandleFunc(makePath("/api/save-result"), AuthMiddleware(CSRFMiddleware(handleSaveResult)))
|
||
http.HandleFunc(makePath("/api/add-to-history"), AuthMiddleware(CSRFMiddleware(handleAddToHistory)))
|
||
|
||
// Catch-all 404 для любых незарегистрированных путей (только когда BasePath задан)
|
||
// if getBasePath() != "" {
|
||
// http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||
// renderNotFound(w, "Страница не найдена", getBasePath())
|
||
// })
|
||
// }
|
||
}
|
||
|
||
// registerRoutes регистрирует все маршруты сервера
|
||
func registerRoutes() {
|
||
// Страница входа (без аутентификации)
|
||
http.HandleFunc(makePath("/login"), handleLoginPage)
|
||
|
||
// API для аутентификации (без аутентификации)
|
||
http.HandleFunc(makePath("/api/login"), handleLogin)
|
||
http.HandleFunc(makePath("/api/logout"), handleLogout)
|
||
http.HandleFunc(makePath("/api/validate-token"), handleValidateToken)
|
||
|
||
// Главная страница (строго по BasePath) и файлы
|
||
http.HandleFunc(makePath("/"), func(w http.ResponseWriter, r *http.Request) {
|
||
// Обрабатываем только точные пути: BasePath или BasePath/
|
||
bp := getBasePath()
|
||
p := r.URL.Path
|
||
if (bp == "" && (p == "/" || p == "")) || (bp != "" && (p == bp || p == bp+"/")) {
|
||
AuthMiddleware(handleResultsPage)(w, r)
|
||
return
|
||
}
|
||
renderNotFound(w, "Страница не найдена", bp)
|
||
})
|
||
http.HandleFunc(makePath("/file/"), AuthMiddleware(handleFileView))
|
||
http.HandleFunc(makePath("/delete/"), AuthMiddleware(handleDeleteFile))
|
||
|
||
// История запросов
|
||
http.HandleFunc(makePath("/history"), AuthMiddleware(handleHistoryPage))
|
||
http.HandleFunc(makePath("/history/view/"), AuthMiddleware(handleHistoryView))
|
||
http.HandleFunc(makePath("/history/delete/"), AuthMiddleware(handleDeleteHistoryEntry))
|
||
http.HandleFunc(makePath("/history/clear"), AuthMiddleware(handleClearHistory))
|
||
|
||
// Управление промптами
|
||
http.HandleFunc(makePath("/prompts"), AuthMiddleware(handlePromptsPage))
|
||
http.HandleFunc(makePath("/prompts/add"), AuthMiddleware(handleAddPrompt))
|
||
http.HandleFunc(makePath("/prompts/edit/"), AuthMiddleware(handleEditPrompt))
|
||
http.HandleFunc(makePath("/prompts/edit-verbose/"), AuthMiddleware(handleEditVerbosePrompt))
|
||
http.HandleFunc(makePath("/prompts/delete/"), AuthMiddleware(handleDeletePrompt))
|
||
http.HandleFunc(makePath("/prompts/restore/"), AuthMiddleware(handleRestorePrompt))
|
||
http.HandleFunc(makePath("/prompts/restore-verbose/"), AuthMiddleware(handleRestoreVerbosePrompt))
|
||
http.HandleFunc(makePath("/prompts/save-lang"), AuthMiddleware(handleSaveLang))
|
||
|
||
// Веб-страница для выполнения запросов
|
||
http.HandleFunc(makePath("/run"), AuthMiddleware(CSRFMiddleware(handleExecutePage)))
|
||
|
||
// API для выполнения запросов
|
||
http.HandleFunc(makePath("/api/execute"), AuthMiddleware(CSRFMiddleware(handleExecute)))
|
||
// API для сохранения результатов и истории
|
||
http.HandleFunc(makePath("/api/save-result"), AuthMiddleware(CSRFMiddleware(handleSaveResult)))
|
||
http.HandleFunc(makePath("/api/add-to-history"), AuthMiddleware(CSRFMiddleware(handleAddToHistory)))
|
||
|
||
// Регистрируем главную страницу без слэша в конце для BasePath
|
||
basePath := config.AppConfig.Server.BasePath
|
||
if basePath != "" && basePath != "/" {
|
||
basePath = strings.TrimSuffix(basePath, "/")
|
||
http.HandleFunc(basePath, AuthMiddleware(handleResultsPage))
|
||
}
|
||
|
||
// Catch-all 404 для любых незарегистрированных путей (только когда BasePath задан)
|
||
if getBasePath() != "" {
|
||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||
renderNotFound(w, "Страница не найдена", getBasePath())
|
||
})
|
||
}
|
||
}
|
||
|
||
// renderNotFound рендерит кастомную страницу 404
|
||
func renderNotFound(w http.ResponseWriter, message, basePath string) {
|
||
w.WriteHeader(http.StatusNotFound)
|
||
data := struct {
|
||
Message string
|
||
BasePath string
|
||
}{
|
||
Message: message,
|
||
BasePath: basePath,
|
||
}
|
||
tmpl, err := template.New("not_found").Parse(templates.NotFoundTemplate)
|
||
if err != nil {
|
||
http.Error(w, "404 Not Found", http.StatusNotFound)
|
||
return
|
||
}
|
||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||
_ = tmpl.Execute(w, data)
|
||
}
|