Compare commits

...

14 Commits

18 changed files with 70 additions and 48 deletions

View File

@@ -1 +1 @@
v2.0.3 v2.0.6

View File

@@ -68,6 +68,15 @@ type ValidationConfig struct {
MaxExplanationLength int MaxExplanationLength int
} }
func GetEnvBool(key string, defaultValue bool) bool {
if value, exists := os.LookupEnv(key); exists {
if boolValue, err := strconv.ParseBool(value); err == nil {
return boolValue
}
}
return defaultValue
}
func getEnv(key, defaultValue string) string { func getEnv(key, defaultValue string) string {
if value, exists := os.LookupEnv(key); exists { if value, exists := os.LookupEnv(key); exists {
return value return value

View File

@@ -16,7 +16,7 @@ data:
LCG_CONFIG_FOLDER: "/app/data/config" LCG_CONFIG_FOLDER: "/app/data/config"
LCG_NO_HISTORY: "false" LCG_NO_HISTORY: "false"
LCG_ALLOW_EXECUTION: "false" LCG_ALLOW_EXECUTION: "false"
LCG_DEBUG: "false" LCG_DEBUG: "true"
# Настройки аутентификации # Настройки аутентификации
LCG_SERVER_REQUIRE_AUTH: "true" LCG_SERVER_REQUIRE_AUTH: "true"

View File

@@ -58,21 +58,18 @@ spec:
readOnly: true readOnly: true
# Health checks # Health checks
startupProbe: startupProbe:
httpGet: tcpSocket:
path: /login
port: 8080 port: 8080
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 5 periodSeconds: 5
failureThreshold: 30 failureThreshold: 30
readinessProbe: readinessProbe:
httpGet: tcpSocket:
path: /login
port: 8080 port: 8080
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
livenessProbe: livenessProbe:
httpGet: tcpSocket:
path: /login
port: 8080 port: 8080
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 60 periodSeconds: 60

View File

@@ -127,15 +127,24 @@ fi
if [ "$current_branch" != "main" ]; then if [ "$current_branch" != "main" ]; then
git checkout main git checkout main
git merge --no-ff -m "Merged branch '$current_branch' into main while building $VERSION" "$current_branch" git merge --no-ff -m "Merged branch '$current_branch' into main while building $VERSION" "$current_branch"
git push origin main
elif [ "$current_branch" = "main" ]; then elif [ "$current_branch" = "main" ]; then
log "🔄 Вы находитесь на ветке main. Слияние с release..." log "🔄 Вы находитесь на ветке main. Слияние с release..."
git add . git add .
git commit -m "Исправления в ветке $current_branch" git commit -m "Исправления в ветке $current_branch"
git push origin main
fi fi
# переключиться на ветку release и слить с веткой main # переключиться на ветку release и слить с веткой main
git checkout release if git show-ref --quiet refs/heads/release; then
git merge --no-ff -m "Merged main into release while building $VERSION" main log " Branch 'release' exists. Proceeding with merge."
git checkout release
git merge --no-ff -m "Merged main into release while building $VERSION" main
else
log "❌ Branch 'release' does not exist. Please create the branch before proceeding."
git checkout -b release
git merge --no-ff -m "Merged main into release while building $VERSION" main
fi
# если тег $VERSION существует, удалить его и принудительно запушить # если тег $VERSION существует, удалить его и принудительно запушить
tag_exists=$(git tag -l "$VERSION") tag_exists=$(git tag -l "$VERSION")

View File

@@ -1 +1 @@
v2.0.3 v2.0.6

View File

@@ -124,6 +124,11 @@ func (p *ProxyAPIProvider) Chat(messages []Chat) (string, error) {
req.Header.Set("Authorization", "Bearer "+p.JWTToken) req.Header.Set("Authorization", "Bearer "+p.JWTToken)
} }
if config.AppConfig.MainFlags.Debug {
fmt.Println("Chat URL: ", p.BaseURL+config.AppConfig.Server.ProxyUrl)
fmt.Println("ProxyChatRequest: ", req)
}
resp, err := p.HTTPClient.Do(req) resp, err := p.HTTPClient.Do(req)
if err != nil { if err != nil {
return "", fmt.Errorf("ошибка выполнения запроса: %w", err) return "", fmt.Errorf("ошибка выполнения запроса: %w", err)

View File

@@ -5,7 +5,7 @@ metadata:
namespace: lcg namespace: lcg
data: data:
# Основные настройки # Основные настройки
LCG_VERSION: "v2.0.3" LCG_VERSION: "v2.0.6"
LCG_BASE_PATH: "/lcg" LCG_BASE_PATH: "/lcg"
LCG_SERVER_HOST: "0.0.0.0" LCG_SERVER_HOST: "0.0.0.0"
LCG_SERVER_PORT: "8080" LCG_SERVER_PORT: "8080"
@@ -16,7 +16,7 @@ data:
LCG_CONFIG_FOLDER: "/app/data/config" LCG_CONFIG_FOLDER: "/app/data/config"
LCG_NO_HISTORY: "false" LCG_NO_HISTORY: "false"
LCG_ALLOW_EXECUTION: "false" LCG_ALLOW_EXECUTION: "false"
LCG_DEBUG: "false" LCG_DEBUG: "true"
# Настройки аутентификации # Настройки аутентификации
LCG_SERVER_REQUIRE_AUTH: "true" LCG_SERVER_REQUIRE_AUTH: "true"

View File

@@ -5,7 +5,7 @@ metadata:
namespace: lcg namespace: lcg
labels: labels:
app: lcg app: lcg
version: v2.0.3 version: v2.0.6
spec: spec:
replicas: 1 replicas: 1
selector: selector:
@@ -15,11 +15,11 @@ spec:
metadata: metadata:
labels: labels:
app: lcg app: lcg
version: v2.0.3 version: v2.0.6
spec: spec:
containers: containers:
- name: lcg - name: lcg
image: kuznetcovay/lcg:v2.0.3 image: kuznetcovay/lcg:v2.0.6
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- containerPort: 8080 - containerPort: 8080
@@ -58,21 +58,18 @@ spec:
readOnly: true readOnly: true
# Health checks # Health checks
startupProbe: startupProbe:
httpGet: tcpSocket:
path: /login
port: 8080 port: 8080
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 5 periodSeconds: 5
failureThreshold: 30 failureThreshold: 30
readinessProbe: readinessProbe:
httpGet: tcpSocket:
path: /login
port: 8080 port: 8080
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
livenessProbe: livenessProbe:
httpGet: tcpSocket:
path: /login
port: 8080 port: 8080
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 60 periodSeconds: 60

View File

@@ -5,7 +5,7 @@ metadata:
namespace: lcg namespace: lcg
labels: labels:
app: lcg app: lcg
version: v2.0.3 version: v2.0.6
spec: spec:
entryPoints: entryPoints:
- websecure - websecure

View File

@@ -15,11 +15,11 @@ resources:
# Common labels # Common labels
commonLabels: commonLabels:
app: lcg app: lcg
version: v2.0.3 version: v2.0.6
managed-by: kustomize managed-by: kustomize
# Images # Images
images: images:
- name: lcg - name: lcg
newName: kuznetcovay/lcg newName: kuznetcovay/lcg
newTag: v2.0.3 newTag: v2.0.6

View File

@@ -5,7 +5,7 @@ metadata:
namespace: lcg namespace: lcg
labels: labels:
app: lcg app: lcg
version: v2.0.3 version: v2.0.6
spec: spec:
type: ClusterIP type: ClusterIP
ports: ports:
@@ -15,4 +15,4 @@ spec:
name: http name: http
selector: selector:
app: lcg app: lcg
version: v2.0.3 version: v2.0.6

View File

@@ -156,6 +156,9 @@ lcg [опции] <описание команды>
Debug: c.Bool("debug"), Debug: c.Bool("debug"),
} }
disableHistory = config.AppConfig.MainFlags.NoHistory || config.AppConfig.IsNoHistoryEnabled() disableHistory = config.AppConfig.MainFlags.NoHistory || config.AppConfig.IsNoHistoryEnabled()
config.AppConfig.MainFlags.Debug = config.AppConfig.MainFlags.Debug || config.GetEnvBool("LCG_DEBUG", false)
args := c.Args().Slice() args := c.Args().Slice()
if len(args) == 0 { if len(args) == 0 {

View File

@@ -94,7 +94,7 @@ func validateJWTToken(tokenString string) (*JWTClaims, error) {
} }
// Парсим токен // Парсим токен
token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) { token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (any, error) {
// Проверяем метод подписи // Проверяем метод подписи
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])

View File

@@ -14,7 +14,7 @@ import (
func handleLoginPage(w http.ResponseWriter, r *http.Request) { func handleLoginPage(w http.ResponseWriter, r *http.Request) {
// Если пользователь уже авторизован, перенаправляем на главную // Если пользователь уже авторизован, перенаправляем на главную
if isAuthenticated(r) { if isAuthenticated(r) {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, makePath("/"), http.StatusSeeOther)
return return
} }
@@ -41,6 +41,7 @@ func handleLoginPage(w http.ResponseWriter, r *http.Request) {
Message: "", Message: "",
Error: "", Error: "",
CSRFToken: csrfToken, CSRFToken: csrfToken,
BasePath: getBasePath(),
} }
if err := RenderLoginPage(w, data); err != nil { if err := RenderLoginPage(w, data); err != nil {
@@ -73,6 +74,7 @@ type LoginPageData struct {
Message string Message string
Error string Error string
CSRFToken string CSRFToken string
BasePath string
} }
// RenderLoginPage рендерит страницу входа // RenderLoginPage рендерит страницу входа

View File

@@ -2,6 +2,7 @@ package serve
import ( import (
"net/http" "net/http"
"strings"
"github.com/direct-dev-ru/linux-command-gpt/config" "github.com/direct-dev-ru/linux-command-gpt/config"
) )
@@ -15,8 +16,8 @@ func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
return return
} }
// Исключаем страницу входа и API логина из проверки // Исключаем страницу входа и API логина из проверки (с учетом BasePath)
if r.URL.Path == "/login" || r.URL.Path == "/api/login" || r.URL.Path == "/api/validate-token" { if r.URL.Path == makePath("/login") || r.URL.Path == makePath("/api/login") || r.URL.Path == makePath("/api/validate-token") {
next(w, r) next(w, r)
return return
} }
@@ -31,8 +32,8 @@ func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
return return
} }
// Для веб-запросов перенаправляем на страницу входа // Для веб-запросов перенаправляем на страницу входа (с учетом BasePath)
http.Redirect(w, r, "/login", http.StatusSeeOther) http.Redirect(w, r, makePath("/login"), http.StatusSeeOther)
return return
} }
@@ -50,8 +51,8 @@ func CSRFMiddleware(next http.HandlerFunc) http.HandlerFunc {
return return
} }
// Исключаем некоторые API endpoints // Исключаем некоторые API endpoints (с учетом BasePath)
if r.URL.Path == "/api/login" || r.URL.Path == "/api/logout" { if r.URL.Path == makePath("/api/login") || r.URL.Path == makePath("/api/logout") {
next(w, r) next(w, r)
return return
} }
@@ -103,7 +104,8 @@ func CSRFMiddleware(next http.HandlerFunc) http.HandlerFunc {
// isAPIRequest проверяет, является ли запрос API запросом // isAPIRequest проверяет, является ли запрос API запросом
func isAPIRequest(r *http.Request) bool { func isAPIRequest(r *http.Request) bool {
path := r.URL.Path path := r.URL.Path
return len(path) >= 4 && path[:4] == "/api" apiPrefix := makePath("/api")
return strings.HasPrefix(path, apiPrefix)
} }
// RequireAuth обертка для requireAuth из auth.go // RequireAuth обертка для requireAuth из auth.go

View File

@@ -285,7 +285,7 @@ const LoginPageTemplate = `
try { try {
const csrfToken = document.getElementById('csrf_token').value; const csrfToken = document.getElementById('csrf_token').value;
const response = await fetch('/api/login', { const response = await fetch('{{.BasePath}}/api/login', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -302,7 +302,7 @@ const LoginPageTemplate = `
if (data.success) { if (data.success) {
// Успешная авторизация, перенаправляем на главную страницу // Успешная авторизация, перенаправляем на главную страницу
window.location.href = '/'; window.location.href = '{{.BasePath}}/';
} else { } else {
// Ошибка авторизации // Ошибка авторизации
showMessage(data.error || 'Ошибка авторизации', 'error'); showMessage(data.error || 'Ошибка авторизации', 'error');

View File

@@ -1,6 +1,7 @@
package ssl package ssl
import ( import (
"slices"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/tls" "crypto/tls"
@@ -139,25 +140,22 @@ func LoadOrGenerateCert(host string) (*tls.Certificate, error) {
// IsSecureHost проверяет, является ли хост безопасным для HTTP // IsSecureHost проверяет, является ли хост безопасным для HTTP
func IsSecureHost(host string) bool { func IsSecureHost(host string) bool {
secureHosts := []string{"localhost", "127.0.0.1", "::1"} secureHosts := []string{"localhost", "127.0.0.1", "::1"}
for _, secureHost := range secureHosts { return slices.Contains(secureHosts, host)
if host == secureHost {
return true
}
}
return false
} }
// ShouldUseHTTPS определяет, нужно ли использовать HTTPS // ShouldUseHTTPS определяет, нужно ли использовать HTTPS
func ShouldUseHTTPS(host string) bool { func ShouldUseHTTPS(host string) bool {
// Если явно разрешен HTTP, используем HTTP
if config.AppConfig.Server.AllowHTTP {
return false
}
// Если хост не localhost/127.0.0.1, принуждаем к HTTPS // Если хост не localhost/127.0.0.1, принуждаем к HTTPS
if !IsSecureHost(host) { if !IsSecureHost(host) {
return true return true
} }
// Если явно разрешен HTTP, используем HTTP
if config.AppConfig.Server.AllowHTTP {
return false
}
// По умолчанию для localhost используем HTTP // По умолчанию для localhost используем HTTP
return false return false