Files
go-lcg/serve/serve.go
2025-11-08 16:04:17 +06:00

324 lines
14 KiB
Go
Raw 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 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)
}