Compare commits

...

4 Commits

11 changed files with 84 additions and 33 deletions

View File

@@ -60,6 +60,8 @@ RUN chown -R ollama:ollama /app/data 2>/dev/null || \
(chown -R 1000:1000 /app/data 2>/dev/null || true) (chown -R 1000:1000 /app/data 2>/dev/null || true)
# Настройки по умолчанию # Настройки по умолчанию
ENV TZ='Asia/Omsk'
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV LCG_PROVIDER=ollama ENV LCG_PROVIDER=ollama
ENV LCG_HOST=http://127.0.0.1:11434/ ENV LCG_HOST=http://127.0.0.1:11434/
ENV LCG_MODEL=qwen2.5-coder:1.5b ENV LCG_MODEL=qwen2.5-coder:1.5b

View File

@@ -46,27 +46,31 @@ build-all-podman: build-binaries build-podman ## Собрать бинарник
run: ## Запустить контейнер (Docker) run: ## Запустить контейнер (Docker)
docker run -d \ docker run -d \
--name $(CONTAINER_NAME) \ --name ${CONTAINER_NAME} \
-p 8080:8080 \ -p 8989:8080 \
-p 11434:11434 \
-v ollama-data:/home/ollama/.ollama \ -v ollama-data:/home/ollama/.ollama \
-v lcg-results:/app/data/results \ -v lcg-results:/app/data/results \
-v lcg-prompts:/app/data/prompts \ -v lcg-prompts:/app/data/prompts \
-v lcg-config:/app/data/config \ -v lcg-config:/app/data/config \
$(IMAGE_NAME):$(IMAGE_TAG) ${IMAGE_NAME}:${IMAGE_TAG}
@echo "Контейнер $(CONTAINER_NAME) запущен" @echo "Контейнер ${CONTAINER_NAME} запущен"
run-podman: ## Запустить контейнер (Podman) run-podman: ## Запустить контейнер (Podman)
echo "Запустить контейнер ${CONTAINER_NAME}"
echo "IMAGE_NAME: ${IMAGE_NAME}"
echo "IMAGE_TAG: ${IMAGE_TAG}"
echo "CONTAINER_NAME: ${CONTAINER_NAME}"
podman run -d \ podman run -d \
--name $(CONTAINER_NAME) \ --name ${CONTAINER_NAME} \
-p 8080:8080 \ --restart always \
-p 11434:11434 \ -p 8989:8080 \
-v ollama-data:/home/ollama/.ollama \ -v ollama-data:/home/ollama/.ollama \
-v lcg-results:/app/data/results \ -v lcg-results:/app/data/results \
-v lcg-prompts:/app/data/prompts \ -v lcg-prompts:/app/data/prompts \
-v lcg-config:/app/data/config \ -v lcg-config:/app/data/config \
$(IMAGE_NAME):$(IMAGE_TAG) ${IMAGE_NAME}:${IMAGE_TAG}
@echo "Контейнер $(CONTAINER_NAME) запущен" @echo "Контейнер ${CONTAINER_NAME} запущен"
stop: ## Остановить контейнер (Docker) stop: ## Остановить контейнер (Docker)
docker stop $(CONTAINER_NAME) || true docker stop $(CONTAINER_NAME) || true

View File

@@ -51,13 +51,13 @@ mkdir -p "${LCG_PROMPT_FOLDER:-/app/data/prompts}"
mkdir -p "${LCG_CONFIG_FOLDER:-/app/data/config}" mkdir -p "${LCG_CONFIG_FOLDER:-/app/data/config}"
# Настройка переменных окружения для Ollama # Настройка переменных окружения для Ollama
export OLLAMA_HOST="${OLLAMA_HOST:-127.0.0.1}" export OLLAMA_HOST="${OLLAMA_HOST:-0.0.0.0}"
export OLLAMA_PORT="${OLLAMA_PORT:-11434}" export OLLAMA_PORT="${OLLAMA_PORT:-11434}"
export OLLAMA_ORIGINS="*" export OLLAMA_ORIGINS="*"
# Настройка переменных окружения для LCG # Настройка переменных окружения для LCG
export LCG_PROVIDER="${LCG_PROVIDER:-ollama}" export LCG_PROVIDER="${LCG_PROVIDER:-ollama}"
export LCG_HOST="${LCG_HOST:-http://127.0.0.1:11434/}" export LCG_HOST="${LCG_HOST:-http://0.0.0.0:11434/}"
export LCG_MODEL="${LCG_MODEL:-qwen2.5-coder:1.5b}" export LCG_MODEL="${LCG_MODEL:-qwen2.5-coder:1.5b}"
export LCG_RESULT_FOLDER="${LCG_RESULT_FOLDER:-/app/data/results}" export LCG_RESULT_FOLDER="${LCG_RESULT_FOLDER:-/app/data/results}"
export LCG_PROMPT_FOLDER="${LCG_PROMPT_FOLDER:-/app/data/prompts}" export LCG_PROMPT_FOLDER="${LCG_PROMPT_FOLDER:-/app/data/prompts}"

View File

@@ -1 +1 @@
v.2.0.16 v.2.0.18

View File

@@ -1 +1 @@
v.2.0.16 v.2.0.18

View File

@@ -5,7 +5,7 @@ metadata:
namespace: lcg namespace: lcg
data: data:
# Основные настройки # Основные настройки
LCG_VERSION: "v.2.0.16" LCG_VERSION: "v.2.0.18"
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"

View File

@@ -5,7 +5,7 @@ metadata:
namespace: lcg namespace: lcg
labels: labels:
app: lcg app: lcg
version: v.2.0.16 version: v.2.0.18
spec: spec:
replicas: 1 replicas: 1
selector: selector:
@@ -18,7 +18,7 @@ spec:
spec: spec:
containers: containers:
- name: lcg - name: lcg
image: kuznetcovay/lcg:v.2.0.16 image: kuznetcovay/lcg:v.2.0.18
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- containerPort: 8080 - containerPort: 8080

View File

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

View File

@@ -127,7 +127,6 @@ func getTokenFromCookie(r *http.Request) (string, error) {
func setAuthCookie(w http.ResponseWriter, token string) { func setAuthCookie(w http.ResponseWriter, token string) {
cookie := &http.Cookie{ cookie := &http.Cookie{
Name: "auth_token", Name: "auth_token",
Domain: config.AppConfig.Server.Domain,
Value: token, Value: token,
Path: config.AppConfig.Server.CookiePath, Path: config.AppConfig.Server.CookiePath,
HttpOnly: true, HttpOnly: true,
@@ -136,9 +135,16 @@ func setAuthCookie(w http.ResponseWriter, token string) {
MaxAge: config.AppConfig.Server.CookieTTLHours * 60 * 60, MaxAge: config.AppConfig.Server.CookieTTLHours * 60 * 60,
} }
// Добавляем домен если указан // Устанавливаем Domain только если это не IP адрес и не 0.0.0.0
// При доступе по IP адресу не устанавливаем Domain, иначе cookie не будет работать
if config.AppConfig.Server.Domain != "" { if config.AppConfig.Server.Domain != "" {
cookie.Domain = config.AppConfig.Server.Domain domain := config.AppConfig.Server.Domain
// Проверяем, не является ли домен IP адресом или 0.0.0.0
if !isIPAddress(domain) && domain != "0.0.0.0" && domain != "::" && domain != "::1" {
cookie.Domain = domain
}
// Если domain пустой, 0.0.0.0 или IP адрес - не устанавливаем Domain
// Браузер автоматически применит cookie к текущему хосту
} }
http.SetCookie(w, cookie) http.SetCookie(w, cookie)
@@ -156,9 +162,12 @@ func clearAuthCookie(w http.ResponseWriter) {
MaxAge: -1, // Удаляем cookie MaxAge: -1, // Удаляем cookie
} }
// Добавляем домен если указан // Устанавливаем Domain только если это не IP адрес
if config.AppConfig.Server.Domain != "" { if config.AppConfig.Server.Domain != "" {
cookie.Domain = config.AppConfig.Server.Domain domain := config.AppConfig.Server.Domain
if !isIPAddress(domain) && domain != "0.0.0.0" && domain != "::" && domain != "::1" {
cookie.Domain = domain
}
} }
http.SetCookie(w, cookie) http.SetCookie(w, cookie)

View File

@@ -8,11 +8,20 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
"strconv"
"strings"
"time" "time"
"github.com/direct-dev-ru/linux-command-gpt/config" "github.com/direct-dev-ru/linux-command-gpt/config"
) )
const (
// CSRFTokenLifetimeHours минимальное время жизни CSRF токена в часах (не менее 12 часов)
CSRFTokenLifetimeHours = 12
// CSRFTokenLifetimeSeconds минимальное время жизни CSRF токена в секундах
CSRFTokenLifetimeSeconds = CSRFTokenLifetimeHours * 60 * 60
)
// CSRFManager управляет CSRF токенами // CSRFManager управляет CSRF токенами
type CSRFManager struct { type CSRFManager struct {
secretKey []byte secretKey []byte
@@ -113,13 +122,14 @@ func (c *CSRFManager) ValidateToken(token, userID string) bool {
return false return false
} }
// Проверяем время жизни токена (24 часа) // Проверяем время жизни токена (минимум 12 часов)
timestamp, err := parseInt64(timestampStr) timestamp, err := parseInt64(timestampStr)
if err != nil { if err != nil {
return false return false
} }
if time.Now().Unix()-timestamp > 24*60*60 { // Минимальное время жизни токена: 12 часов (не менее 12 часов согласно требованиям)
if time.Now().Unix()-timestamp > CSRFTokenLifetimeSeconds {
return false return false
} }
@@ -153,6 +163,7 @@ func GetCSRFTokenFromCookie(r *http.Request) string {
// setCSRFCookie устанавливает CSRF токен в cookie // setCSRFCookie устанавливает CSRF токен в cookie
func setCSRFCookie(w http.ResponseWriter, token string) { func setCSRFCookie(w http.ResponseWriter, token string) {
// Минимальное время жизни токена: 12 часов (не менее 12 часов согласно требованиям)
cookie := &http.Cookie{ cookie := &http.Cookie{
Name: "csrf_token", Name: "csrf_token",
Value: token, Value: token,
@@ -160,12 +171,19 @@ func setCSRFCookie(w http.ResponseWriter, token string) {
HttpOnly: true, HttpOnly: true,
Secure: config.AppConfig.Server.CookieSecure, Secure: config.AppConfig.Server.CookieSecure,
SameSite: http.SameSiteLaxMode, // Более мягкий режим для reverse proxy SameSite: http.SameSiteLaxMode, // Более мягкий режим для reverse proxy
MaxAge: 1 * 60 * 60, MaxAge: CSRFTokenLifetimeSeconds, // Минимум 12 часов в секундах
} }
// Добавляем домен если указан // Устанавливаем Domain только если это не IP адрес и не 0.0.0.0
// При доступе по IP адресу не устанавливаем Domain, иначе cookie не будет работать
if config.AppConfig.Server.Domain != "" { if config.AppConfig.Server.Domain != "" {
cookie.Domain = config.AppConfig.Server.Domain domain := config.AppConfig.Server.Domain
// Проверяем, не является ли домен IP адресом или 0.0.0.0
if !isIPAddress(domain) && domain != "0.0.0.0" && domain != "::" && domain != "::1" {
cookie.Domain = domain
}
// Если domain пустой, 0.0.0.0 или IP адрес - не устанавливаем Domain
// Браузер автоматически применит cookie к текущему хосту
} }
http.SetCookie(w, cookie) http.SetCookie(w, cookie)
@@ -183,9 +201,12 @@ func СlearCSRFCookie(w http.ResponseWriter) {
MaxAge: -1, MaxAge: -1,
} }
// Добавляем домен если указан // Устанавливаем Domain только если это не IP адрес
if config.AppConfig.Server.Domain != "" { if config.AppConfig.Server.Domain != "" {
cookie.Domain = config.AppConfig.Server.Domain domain := config.AppConfig.Server.Domain
if !isIPAddress(domain) && domain != "0.0.0.0" && domain != "::" && domain != "::1" {
cookie.Domain = domain
}
} }
http.SetCookie(w, cookie) http.SetCookie(w, cookie)
@@ -261,3 +282,17 @@ func InitCSRFManager() error {
func GetCSRFManager() *CSRFManager { func GetCSRFManager() *CSRFManager {
return csrfManager return csrfManager
} }
// isIPAddress проверяет, является ли строка IPv4 адресом
func isIPAddress(s string) bool {
parts := strings.Split(s, ".")
if len(parts) != 4 {
return false
}
for _, part := range parts {
if num, err := strconv.Atoi(part); err != nil || num < 0 || num > 255 {
return false
}
}
return true
}

View File

@@ -72,8 +72,9 @@ var ExecutePageCSSTemplate = template.Must(template.New("execute_css").Parse(`
text-align: center; text-align: center;
} }
.header h1 { .header h1 {
margin: 0;
font-size: 2.5em; font-size: 2.5em;
margin-bottom: 10px; font-weight: 300;
} }
.header p { .header p {
opacity: 0.9; opacity: 0.9;