Compare commits

..

18 Commits

Author SHA1 Message Date
81b01d74ae Merged main into release while building v2.0.6 2025-10-28 14:33:44 +06:00
3c95eb85db Исправления в ветке main 2025-10-28 14:33:40 +06:00
1fbdd237a3 Merged main into release while building v2.0.5 2025-10-28 14:25:53 +06:00
5b78e775c1 Исправления в ветке main 2025-10-28 14:25:49 +06:00
2d82b91090 Merged main into release while building v2.0.5 2025-10-28 14:24:25 +06:00
9044b02d27 Исправления в ветке main 2025-10-28 14:24:22 +06:00
5d3829d1fe Merged main into release while building v2.0.5 2025-10-28 12:56:19 +06:00
e7c11879a1 Исправления в ветке main 2025-10-28 12:56:16 +06:00
edadedcf80 Merged main into release while building v2.0.4 2025-10-28 12:48:28 +06:00
6444c35bbb Исправления в ветке main 2025-10-28 12:48:24 +06:00
5ff6d4e072 Merged main into release while building v2.0.4 2025-10-28 12:05:38 +06:00
6ec41355d3 Исправления в ветке main 2025-10-28 12:05:34 +06:00
ffc2d6ba0a Merged main into release while building v2.0.4 2025-10-28 11:58:25 +06:00
7a0d0746d4 Исправления в ветке main 2025-10-28 11:58:22 +06:00
dab94df7d2 Merged main into release while building v2.0.3 2025-10-28 10:38:19 +06:00
0da366cad5 Исправления в ветке main 2025-10-28 10:38:19 +06:00
281f7f877a Merged main into release while building v2.0.3 2025-10-28 10:29:20 +06:00
3be2880dd2 Исправления в ветке main 2025-10-28 10:29:20 +06:00
24 changed files with 177 additions and 95 deletions

2
.gitignore vendored
View File

@@ -18,3 +18,5 @@ shell-code/jwt.admin.token
run.sh
lcg_history.json
deploy/0.create_sealed_secrets.sh
deploy/0.create_git_secrets.sh
deploy/0.create_app_secrets.sh

View File

@@ -1 +1 @@
v2.0.2
v2.0.6

View File

@@ -68,6 +68,15 @@ type ValidationConfig struct {
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 {
if value, exists := os.LookupEnv(key); exists {
return value

View File

@@ -16,7 +16,7 @@ data:
LCG_CONFIG_FOLDER: "/app/data/config"
LCG_NO_HISTORY: "false"
LCG_ALLOW_EXECUTION: "false"
LCG_DEBUG: "false"
LCG_DEBUG: "true"
# Настройки аутентификации
LCG_SERVER_REQUIRE_AUTH: "true"
@@ -41,6 +41,4 @@ data:
# Настройки таймаутов
LCG_TIMEOUT: "300"
# Настройки отладки
LCG_DEBUG: "false"

View File

@@ -1,12 +0,0 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: linux-command-gpt
namespace: flux-system
spec:
interval: 3m
url: https://direct-dev.ru/gitea/GiteaAdmin/go-lcg.git
ref:
branch: release
secretRef:
name: gitea-token

77
deploy/2.lcg-flux.yaml Normal file
View File

@@ -0,0 +1,77 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: go-lcg
namespace: flux-system
spec:
interval: 3m
url: https://github.com/Direct-Dev-Ru/go-lcg.git
ref:
branch: release
secretRef:
name: git-secrets
---
# apiVersion: source.toolkit.fluxcd.io/v1
# kind: GitRepository
# metadata:
# name: linux-command-gpt
# namespace: flux-system
# spec:
# interval: 3m
# url: https://direct-dev.ru/gitea/GiteaAdmin/go-lcg.git
# ref:
# branch: release
# secretRef:
# name: gitea-token
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: lcg
namespace: flux-system
spec:
healthChecks:
- kind: Deployment
name: lcg
namespace: lcg
interval: 3m15s
path: ./kustomize
prune: true
sourceRef:
kind: GitRepository
name: go-lcg
targetNamespace: lcg
timeout: 2m0s
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: lcg
namespace: flux-system
spec:
image: kuznetcovay/lcg
interval: 3m
secretRef:
name: regcred
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: lcg
namespace: flux-system
spec:
imageRepositoryRef:
name: lcg
policy:
semver:
range: '>=1.0.0'
---

View File

@@ -28,7 +28,7 @@ spec:
- configMapRef:
name: lcg-config
- secretRef:
name: lcg-secret
name: lcg-secrets
env:
# Pod information
- name: POD_NAME
@@ -58,21 +58,18 @@ spec:
readOnly: true
# Health checks
startupProbe:
httpGet:
path: /login
tcpSocket:
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 30
readinessProbe:
httpGet:
path: /login
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /login
tcpSocket:
port: 8080
initialDelaySeconds: 10
periodSeconds: 60

View File

@@ -1,19 +0,0 @@
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: lcg
namespace: flux-system
spec:
healthChecks:
- kind: Deployment
name: lcg
namespace: lcg
interval: 3m15s
path: ./kustomize
prune: true
sourceRef:
kind: GitRepository
name: linux-command-gpt
targetNamespace: lcg
timeout: 2m0s

View File

@@ -50,9 +50,7 @@ log "🚀 Полная сборка LCG (бинарные файлы + Docker о
# Этап 1: Сборка бинарных файлов
log "📦 Этап 1: Сборка бинарных файлов с goreleaser..."
./deploy/4.build-binaries.sh "$VERSION"
if [ $? -ne 0 ]; then
if ! ./deploy/4.build-binaries.sh "$VERSION"; then
error "Ошибка при сборке бинарных файлов"
exit 1
fi
@@ -61,9 +59,7 @@ success "✅ Бинарные файлы собраны успешно"
# Этап 2: Сборка Docker образа
log "🐳 Этап 2: Сборка Docker образа..."
./deploy/5.build-docker.sh "$REPOSITORY" "$VERSION" "$PLATFORMS"
if [ $? -ne 0 ]; then
if ! ./deploy/5.build-docker.sh "$REPOSITORY" "$VERSION" "$PLATFORMS"; then
error "Ошибка при сборке Docker образа"
exit 1
fi
@@ -78,14 +74,14 @@ export VERSION=$VERSION
export PLATFORMS=$PLATFORMS
export KUBECONFIG="${HOME}/.kube/config_hlab" && kubectx default
if ! envsubst < deploy/1.configmap.yaml > kustomize/configmap.yaml; then
if ! envsubst < deploy/1.configmap.tmpl.yaml > kustomize/configmap.yaml; then
error "Ошибка при генерации deploy/1.configmap.yaml"
exit 1
fi
success "✅ kustomize/configmap.yaml сгенерирован успешно"
if ! envsubst < deploy/deployment.tmpl.yaml > kustomize/deployment.yaml; then
if ! envsubst < deploy/3.deployment.tmpl.yaml > kustomize/deployment.yaml; then
error "Ошибка при генерации kustomize/deployment.yaml"
exit 1
fi
@@ -131,11 +127,24 @@ fi
if [ "$current_branch" != "main" ]; then
git checkout main
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
log "🔄 Вы находитесь на ветке main. Слияние с release..."
git add .
git commit -m "Исправления в ветке $current_branch"
git push origin main
fi
# переключиться на ветку release и слить с веткой main
git checkout -b release
git merge --no-ff -m "Merged main into release while building $VERSION" main
if git show-ref --quiet refs/heads/release; then
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 существует, удалить его и принудительно запушить
tag_exists=$(git tag -l "$VERSION")

View File

@@ -1 +1 @@
v2.0.2
v2.0.6

16
git-sealed-cfg.yaml Normal file
View File

@@ -0,0 +1,16 @@
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: git-secrets
namespace: flux-system
spec:
encryptedData:
password: AgB8/7lk3onjQG9R2OzX02rv9Lwh3tkOkvV/DjySSGZDHnYtdOw6ttOZXSeQqibAfKIOqtbKvpGAmK0x0/1tQXfB1zg2unFh1p5oEa0sgYBNd0eeNV5nRXDpgl8gv3lWKLaLvLCmhxloYze1B6Js8UrPDGeH3lvxRk9A5oja87HEBnx/VIGm5h4crvb5fUn+7wibr+oQNP+LBRLiko4eqR8PZAA0qgWUvoJnAF6NIsGPEtNi6WjErtwwvYIsZbCIChjPHQll+FQYwSH7M9jXVEBPejiR92+XIR8RMMo6hsWWxkEvvZs5prNREirAOSErOHBRTMEYkI5JIAb6uaRBKZ2nP0G4YQAkx3N7DIZPD7MVQF3u5jIpqaRwP4S+v703/19uPID1D8RuXt99QUv4d7Bfs8PlwkV5/SOgx2ZPVn1viuieZZ9M9hdk5uXYqOqgLOjU4aQSVpVV5pdR7ifn2fcbcTsG6fE3srrjUt8c2t+bV1IBUw8CIzyA1lebxj31s2XRpY+6JsfihSneKm/RkR4ySkyLjF+BeAO7CuXeCdziSFsoCzRDGQqaf/3YKCv1HFbLUsAfHvbCOYuZH7X9UAw51OVqqhEpdD21Ms0W29NGRVUFz80/eglLpK5OmvlXr1kME2P9Wo8TEkLIK1Fjo10f6WzHsUU9eI1cfy6/JgyoaFn4Sjga9SaRgdMkv2neztyFLx6cRbdGui+5qh5Zewqaawvp72j7ZhosXVi0uBnKSoBkN6CLEzlw
username: AgB1HiFs7wfaiAaCrwGNr6zWfd8IoNMZUvJrKeaLbKqEJ2oAr3Db406mjq1gz/O0lhvOJqnmBR/Iel4PMVfixMBtmA8j2anytEkj7ZuruFF3OHOzEkN6OeYfJa9ddkJmUs+Zq62TxQdcaJufQnVfuSdScGvE+Exc8ruFWE0xM1cDgFsRp75d0lo6UmhxaJlBRewQexyH6izOmtecAZDX6WVyBopIGx2cPgNfyxp/4sU5CI5sGCjeqcq+5INRELk9kofbDIUnQuHxrH3nbzABNnK76sU6DhqS3jBmT5Pv9azB2AWaaYZ8z1di/fdYia3wKoQAbef36wKyQ+/ZLApnuaVkj9F96tcjqlV4T3SGp4179k99M8zf/7g1GwsljYRPEcRmc00L2FrC5m3S+oK8wIk6fLVT5KYJBlEP6ja3eTrgGDttosuNa4BcpzqLC2Ov6x1b6RKLRVPN9/+1eC9gMBxBqKnvsdjZn4kQb8yUYgjyRHMZl8kHehLnRDhIoE1O7xCQAMkrdeNyE1kAXsij2jgTDR7a72J86nnE0NPAyct4mCpn2x+2aFUVeyZ8u2e0nDLjxPIAs/ceN51ybz5f2cXH0JrEk3Yy0ZXTtHOF6NbZNcxrCrZGODYYIbtTpjI0MjYd+nxJt5TI3UN0pnrdR7FxHai+76Gw+fGWhXKQk+NgmSXsjyiQkBfoU7LCcnv8iFI5T3IpxtmlckZvcn/b
template:
metadata:
creationTimestamp: null
name: git-secrets
namespace: flux-system
---

View File

@@ -124,6 +124,11 @@ func (p *ProxyAPIProvider) Chat(messages []Chat) (string, error) {
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)
if err != nil {
return "", fmt.Errorf("ошибка выполнения запроса: %w", err)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -156,6 +156,9 @@ lcg [опции] <описание команды>
Debug: c.Bool("debug"),
}
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()
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 {
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) {
// Если пользователь уже авторизован, перенаправляем на главную
if isAuthenticated(r) {
http.Redirect(w, r, "/", http.StatusSeeOther)
http.Redirect(w, r, makePath("/"), http.StatusSeeOther)
return
}
@@ -41,6 +41,7 @@ func handleLoginPage(w http.ResponseWriter, r *http.Request) {
Message: "",
Error: "",
CSRFToken: csrfToken,
BasePath: getBasePath(),
}
if err := RenderLoginPage(w, data); err != nil {
@@ -73,6 +74,7 @@ type LoginPageData struct {
Message string
Error string
CSRFToken string
BasePath string
}
// RenderLoginPage рендерит страницу входа

View File

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

View File

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

View File

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