feat: default locale Russian, geo determines language for other countries

- localization-svc: defaultLocale ru, resolveLocale only by geo
- web-svc: DEFAULT_LOCALE ru, layout lang=ru, embeddedTranslations fallback ru
- countryToLocale: default ru when no country or unknown country

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
home
2026-02-23 15:10:38 +03:00
parent 8fc82a3b90
commit cd6b7857ba
606 changed files with 26148 additions and 14297 deletions

View File

@@ -0,0 +1,85 @@
# api-gateway — прокси к микросервисам
# namespace: gooseek
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-gateway
namespace: gooseek
spec:
replicas: 1
selector:
matchLabels:
app: api-gateway
template:
metadata:
labels:
app: api-gateway
spec:
containers:
- name: api-gateway
image: gooseek/api-gateway:latest
imagePullPolicy: Never
ports:
- containerPort: 3015
env:
- name: PORT
value: "3015"
- name: CHAT_SVC_URL
value: "http://chat-svc.gooseek:3005"
- name: LIBRARY_SVC_URL
value: "http://library-svc.gooseek:3009"
- name: DISCOVER_SVC_URL
value: "http://discover-svc.gooseek:3002"
- name: SEARCH_SVC_URL
value: "http://search-svc.gooseek:3001"
- name: FINANCE_SVC_URL
value: "http://finance-svc.gooseek:3003"
- name: TRAVEL_SVC_URL
value: "http://travel-svc.gooseek:3004"
- name: CREATE_SVC_URL
value: "http://create-svc.gooseek:3011"
- name: MEMORY_SVC_URL
value: "http://memory-svc.gooseek:3010"
- name: PROJECTS_SVC_URL
value: "http://projects-svc.gooseek:3006"
- name: NOTIFICATIONS_SVC_URL
value: "http://notifications-svc.gooseek:3013"
- name: BILLING_SVC_URL
value: "http://billing-svc.gooseek:3008"
- name: AUDIT_SVC_URL
value: "http://audit-svc.gooseek:3012"
- name: AUTH_SVC_URL
value: "http://auth-svc.gooseek-auth:3014"
- name: PROFILE_SVC_URL
value: "http://profile-svc.gooseek:3019"
- name: LLM_SVC_URL
value: "http://llm-svc.gooseek:3020"
- name: ALLOWED_ORIGINS
valueFrom:
configMapKeyRef:
name: gooseek-env
key: allowed-origins
livenessProbe:
httpGet:
path: /health
port: 3015
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3015
initialDelaySeconds: 3
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: api-gateway
namespace: gooseek
spec:
selector:
app: api-gateway
ports:
- port: 3015
targetPort: 3015

60
deploy/k3s/audit-svc.yaml Normal file
View File

@@ -0,0 +1,60 @@
# audit-svc — Enterprise audit logs
# docs/architecture: 02-k3s-microservices-spec.md §3.13
apiVersion: apps/v1
kind: Deployment
metadata:
name: audit-svc
namespace: gooseek
spec:
replicas: 1
selector:
matchLabels:
app: audit-svc
template:
metadata:
labels:
app: audit-svc
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3012"
prometheus.io/path: "/metrics"
spec:
containers:
- name: audit-svc
image: gooseek/audit-svc:latest
ports:
- containerPort: 3012
env:
- name: PORT
value: "3012"
livenessProbe:
httpGet:
path: /health
port: 3012
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3012
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: audit-svc
namespace: gooseek
spec:
selector:
app: audit-svc
ports:
- port: 3012
targetPort: 3012

94
deploy/k3s/auth-svc.yaml Normal file
View File

@@ -0,0 +1,94 @@
# auth-svc — SSO, JWT, Bearer validation (better-auth)
# namespace: gooseek-auth
# BETTER_AUTH_URL — для callback/redirect (https://gooseek.ru)
# docs/architecture: 02-k3s-microservices-spec.md
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-svc
namespace: gooseek-auth
spec:
replicas: 1
selector:
matchLabels:
app: auth-svc
template:
metadata:
labels:
app: auth-svc
spec:
containers:
- name: auth-svc
image: gooseek/auth-svc:latest
imagePullPolicy: Never
ports:
- containerPort: 3014
env:
- name: PORT
value: "3014"
- name: BETTER_AUTH_URL
valueFrom:
configMapKeyRef:
name: gooseek-env
key: better-auth-url
- name: AUTH_SERVICE_URL
value: "http://auth-svc.gooseek-auth:3014"
- name: TRUSTED_ORIGINS
valueFrom:
configMapKeyRef:
name: gooseek-env
key: trusted-origins
- name: DATABASE_PATH
value: "/data/auth.db"
- name: BETTER_AUTH_TELEMETRY
value: "0"
volumeMounts:
- name: auth-data
mountPath: /data
livenessProbe:
httpGet:
path: /health
port: 3014
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3014
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: auth-data
persistentVolumeClaim:
claimName: auth-data-pvc
---
apiVersion: v1
kind: Service
metadata:
name: auth-svc
namespace: gooseek-auth
spec:
selector:
app: auth-svc
ports:
- port: 3014
targetPort: 3014
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: auth-data-pvc
namespace: gooseek-auth
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

View File

@@ -0,0 +1,74 @@
# billing-svc — тарифы, подписки, ЮKassa
# docs/architecture: 02-k3s-microservices-spec.md §3.10
apiVersion: apps/v1
kind: Deployment
metadata:
name: billing-svc
namespace: gooseek
spec:
replicas: 2
selector:
matchLabels:
app: billing-svc
template:
metadata:
labels:
app: billing-svc
spec:
containers:
- name: billing-svc
image: gooseek/billing-svc:latest
imagePullPolicy: Never
ports:
- containerPort: 3008
env:
- name: PORT
value: "3008"
- name: POSTGRES_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url
- name: AUTH_SERVICE_URL
value: "http://auth-svc.gooseek-auth:3014"
- name: YOOKASSA_SHOP_ID
valueFrom:
secretKeyRef:
name: yookassa-credentials
key: shop_id
- name: YOOKASSA_SECRET
valueFrom:
secretKeyRef:
name: yookassa-credentials
key: secret
livenessProbe:
httpGet:
path: /health
port: 3008
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3008
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: billing-svc
namespace: gooseek
spec:
selector:
app: billing-svc
ports:
- port: 3008
targetPort: 3008

View File

@@ -0,0 +1,85 @@
# cache-worker — pre-compute discover, finance, travel
# docs/architecture: 02-k3s-microservices-spec.md §3.15, 05-gaps-and-best-practices §6
# activeDeadlineSeconds защищает от зависших задач
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: cache-worker-finance
namespace: gooseek
spec:
schedule: "*/2 * * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
activeDeadlineSeconds: 300
template:
spec:
restartPolicy: OnFailure
containers:
- name: cache-worker
image: gooseek/cache-worker:latest
args: ["--task=finance"]
env:
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: redis-credentials
key: url
- name: FINANCE_SVC_URL
value: "http://finance-svc.gooseek:3003"
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: cache-worker-discover
namespace: gooseek
spec:
schedule: "*/15 * * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
activeDeadlineSeconds: 600
template:
spec:
restartPolicy: OnFailure
containers:
- name: cache-worker
image: gooseek/cache-worker:latest
args: ["--task=discover"]
env:
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: redis-credentials
key: url
- name: DISCOVER_SVC_URL
value: "http://discover-svc.gooseek:3002"
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: cache-worker-travel
namespace: gooseek
spec:
schedule: "0 */4 * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
activeDeadlineSeconds: 1200
template:
spec:
restartPolicy: OnFailure
containers:
- name: cache-worker
image: gooseek/cache-worker:latest
args: ["--task=travel"]
env:
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: redis-credentials
key: url
- name: TRAVEL_SVC_URL
value: "http://travel-svc.gooseek:3004"

103
deploy/k3s/chat-svc.yaml Normal file
View File

@@ -0,0 +1,103 @@
# chat-svc — LLM, Mastra, Writer, Classifier, Researcher
# docs/architecture: 02-k3s-microservices-spec.md
# Frontend при CHAT_SVC_URL проксирует /api/chat сюда
apiVersion: apps/v1
kind: Deployment
metadata:
name: chat-svc
namespace: gooseek
spec:
replicas: 2
selector:
matchLabels:
app: chat-svc
template:
metadata:
labels:
app: chat-svc
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3005"
prometheus.io/path: "/metrics"
spec:
containers:
- name: chat-svc
image: gooseek/chat-svc:latest
ports:
- containerPort: 3005
env:
- name: PORT
value: "3005"
- name: DATA_DIR
value: "/app/data"
- name: SEARCH_SVC_URL
value: "http://search-svc.gooseek:3001"
- name: MEMORY_SVC_URL
value: "http://memory-svc.gooseek:3010"
- name: LLM_SVC_URL
value: "http://llm-svc.gooseek:3020"
- name: LLM_PROVIDER
valueFrom:
configMapKeyRef:
name: chat-svc-config
key: llm-provider
- name: OLLAMA_BASE_URL
valueFrom:
configMapKeyRef:
name: chat-svc-config
key: ollama-base-url
optional: true
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: llm-credentials
key: openai-api-key
optional: true
volumeMounts:
- name: chat-data
mountPath: /app/data
livenessProbe:
httpGet:
path: /health
port: 3005
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3005
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 2000m
memory: 2Gi
volumes:
- name: chat-data
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: chat-svc
namespace: gooseek
spec:
selector:
app: chat-svc
ports:
- port: 3005
targetPort: 3005
---
# ConfigMap для LLM — секреты в llm-credentials (опционально)
# При отсутствии ollama в кластере укажите внешний URL или LLM_PROVIDER=openai
apiVersion: v1
kind: ConfigMap
metadata:
name: chat-svc-config
namespace: gooseek
data:
llm-provider: "ollama"
ollama-base-url: "http://host.docker.internal:11434"

View File

@@ -0,0 +1,57 @@
# create-svc — Create (таблицы, дашборды), Export PDF/MD
# docs/architecture: 01-perplexity-analogue-design.md §5.10
apiVersion: apps/v1
kind: Deployment
metadata:
name: create-svc
namespace: gooseek
spec:
replicas: 1
selector:
matchLabels:
app: create-svc
template:
metadata:
labels:
app: create-svc
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3011"
prometheus.io/path: "/metrics"
spec:
containers:
- name: create-svc
image: gooseek/create-svc:latest
ports:
- containerPort: 3011
livenessProbe:
httpGet:
path: /health
port: 3011
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3011
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: create-svc
namespace: gooseek
spec:
selector:
app: create-svc
ports:
- port: 3011
targetPort: 3011

View File

@@ -0,0 +1,59 @@
# Конфигурация деплоя GooSeek в Kubernetes (локальный K3s)
# Docker build → kubectl apply → rollout restart
#
# Запуск: ./deploy/k3s/deploy.sh
# Или: ./deploy/k3s/deploy.sh --no-build (только apply, без сборки образов)
# ./deploy/k3s/deploy.sh --build-only (только сборка, без apply)
services:
# === Обязательные (ядро) ===
web-svc: true # Next.js UI — всегда нужен
api-gateway: true # Прокси к микросервисам — всегда нужен
# === Авторизация ===
auth-svc: true # better-auth, Sign In/Up (гости работают без него)
profile-svc: true # Личные данные, preferences, персонализация (требует PostgreSQL)
# === Основные функции ===
llm-svc: true # LLM providers (Ollama, OpenAI, Timeweb и др.) — единый источник
chat-svc: false # Чат, LLM, Writer, SearchAgent
search-svc: false # Поиск, SearXNG, патенты
library-svc: false # История чатов, треды, экспорт
memory-svc: false # AI Memory, персонализация
# === Контент и discovery ===
discover-svc: false # Discover, новости
finance-svc: false # Finance, котировки, heatmap
travel-svc: false # Travel, маршруты, погода
projects-svc: false # Spaces, коллекции, connectors
# === Дополнительные ===
create-svc: false # Create (таблицы, дашборды, изображения)
notifications-svc: false # Web Push (требует PostgreSQL)
billing-svc: true # Подписки (требует PostgreSQL + yookassa-credentials)
audit-svc: false # Enterprise audit logs
# === Тематические (заглушки в меню, сервисы — в разработке) ===
children-svc: false # Дети
medicine-svc: false # Медицина
education-svc: false # Обучение
goods-svc: false # Товары
health-svc: false # Здоровье
psychology-svc: false # Психология
sports-svc: false # Спорт
realestate-svc: false # Недвижимость
shopping-svc: false # Покупки
games-svc: false # Игры
taxes-svc: false # Налоги
legislation-svc: false # Законодательство
# === Фоновые задачи ===
cache-worker: false # CronJob: discover, finance, travel (требует redis-credentials)
# Ingress — production https://gooseek.ru
ingress:
enabled: true
# SSL: если backup/fullchain.pem есть — берём из backup, иначе cert-manager (Let's Encrypt)
ssl:
# true = cert-manager (Let's Encrypt), false = из backup/ (fullchain.pem, privkey.pem)
auto: true

318
deploy/k3s/deploy.sh Executable file
View File

@@ -0,0 +1,318 @@
#!/usr/bin/env bash
# Деплой GooSeek в Kubernetes (локальный K3s на этой машине)
# Docker build → kubectl apply → rollout restart (подхват новых образов)
# Конфигурация: deploy/k3s/deploy.config.yaml
#
# Использование:
# ./deploy/k3s/deploy.sh # полный деплой (build + apply)
# ./deploy/k3s/deploy.sh --no-build # только apply манифестов
# ./deploy/k3s/deploy.sh --build-only # только сборка образов
# ./deploy/k3s/deploy.sh --list # показать включённые сервисы
# ./deploy/k3s/deploy.sh --skip-migrate # без миграции auth-svc (быстрее)
# ./deploy/k3s/deploy.sh --migrate # принудительно запустить миграцию auth-svc
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
CONFIG="$SCRIPT_DIR/deploy.config.yaml"
# Контекст сборки: . = корень репо, иначе путь к папке сервиса
get_build_ctx() {
case "$1" in
web-svc|auth-svc) echo "." ;;
profile-svc) echo "services/profile-svc" ;;
api-gateway) echo "services/api-gateway" ;;
chat-svc) echo "services/chat-svc" ;;
search-svc) echo "services/search-svc" ;;
discover-svc) echo "services/discover-svc" ;;
finance-svc) echo "services/finance-svc" ;;
travel-svc) echo "services/travel-svc" ;;
memory-svc) echo "services/memory-svc" ;;
library-svc) echo "services/library-svc" ;;
create-svc) echo "services/create-svc" ;;
projects-svc) echo "services/projects-svc" ;;
notifications-svc) echo "services/notifications-svc" ;;
billing-svc) echo "services/billing-svc" ;;
audit-svc) echo "services/audit-svc" ;;
cache-worker) echo "services/cache-worker" ;;
llm-svc) echo "services/llm-svc" ;;
children-svc|medicine-svc|education-svc|goods-svc|health-svc|psychology-svc|sports-svc|realestate-svc|shopping-svc|games-svc|taxes-svc|legislation-svc) echo "" ;;
*) echo "" ;;
esac
}
# Манифест для сервиса
get_manifest() {
case "$1" in
web-svc) echo "web-svc.yaml" ;;
api-gateway) echo "api-gateway.yaml" ;;
auth-svc) echo "auth-svc.yaml" ;;
profile-svc) echo "profile-svc.yaml" ;;
chat-svc) echo "chat-svc.yaml" ;;
search-svc) echo "search-svc.yaml" ;;
discover-svc) echo "discover-svc.yaml" ;;
finance-svc) echo "finance-svc.yaml" ;;
travel-svc) echo "travel-svc.yaml" ;;
memory-svc) echo "memory-svc.yaml" ;;
library-svc) echo "library-svc.yaml" ;;
create-svc) echo "create-svc.yaml" ;;
projects-svc) echo "projects-svc.yaml" ;;
notifications-svc) echo "notifications-svc.yaml" ;;
billing-svc) echo "billing-svc.yaml" ;;
audit-svc) echo "audit-svc.yaml" ;;
cache-worker) echo "cache-worker.yaml" ;;
llm-svc) echo "llm-svc.yaml" ;;
children-svc|medicine-svc|education-svc|goods-svc|health-svc|psychology-svc|sports-svc|realestate-svc|shopping-svc|games-svc|taxes-svc|legislation-svc) echo "" ;;
*) echo "" ;;
esac
}
get_enabled_services() {
local in_services=0
while IFS= read -r line; do
if [[ "$line" =~ ^[a-z].*: ]]; then
[[ "$line" =~ ^ingress: ]] && in_services=0
fi
if [[ "$line" =~ ^services: ]]; then
in_services=1
continue
fi
if (( in_services )) && [[ "$line" =~ ^[[:space:]]+([a-z0-9-]+):[[:space:]]+true ]]; then
echo "${BASH_REMATCH[1]}"
fi
done < "$CONFIG"
}
get_ingress_enabled() {
grep -A 5 "^ingress:" "$CONFIG" 2>/dev/null | grep "enabled:" | grep -q "true"
}
get_ssl_auto() {
grep -A 25 "^ingress:" "$CONFIG" 2>/dev/null | grep -E "^\s+auto:" | head -1 | sed 's/.*auto:[[:space:]]*//' | sed 's/[[:space:]#].*//' | tr -d ' '
}
# SSL: cert-manager + Let's Encrypt (production https://gooseek.ru)
do_ssl_production() {
if ! kubectl get deployment -n ingress-nginx ingress-nginx-controller &>/dev/null 2>&1; then
echo " ⚠ ingress-nginx не найден. Установите: kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml"
return 1
fi
local cert_manager_version="v1.13.0"
if ! kubectl get deployment -n cert-manager cert-manager &>/dev/null 2>&1; then
echo " → cert-manager"
kubectl apply -f "https://github.com/cert-manager/cert-manager/releases/download/${cert_manager_version}/cert-manager.yaml"
echo " Ожидание cert-manager (до 2 мин)..."
kubectl wait --for=condition=Available deployment/cert-manager -n cert-manager --timeout=120s 2>/dev/null || true
kubectl wait --for=condition=Available deployment/cert-manager-webhook -n cert-manager --timeout=120s 2>/dev/null || true
else
echo " cert-manager уже установлен"
fi
echo " → ClusterIssuer letsencrypt-prod"
kubectl apply -f "$SCRIPT_DIR/ssl/cert-manager-issuer.yaml"
echo " → Ingress (TLS, gooseek.ru)"
kubectl delete ingress gooseek-web-ingress -n gooseek 2>/dev/null || true
kubectl apply -f "$SCRIPT_DIR/ingress-production.yaml"
}
build_image() {
local svc="$1"
local ctx
ctx=$(get_build_ctx "$svc")
[[ -z "$ctx" ]] && return 1
local img="gooseek/${svc}:latest"
local df="services/${svc}/Dockerfile"
echo "$img"
if [[ "$ctx" == "." ]]; then
docker build -t "$img" -f "$df" .
else
docker build -t "$img" -f "$df" "$ctx"
fi
}
do_build() {
local enabled
enabled=($(get_enabled_services))
if [[ ${#enabled[@]} -eq 0 ]]; then
echo "Ошибка: нет включённых сервисов в $CONFIG"
exit 1
fi
echo "=== Сборка образов (${#enabled[@]} сервисов) ==="
cd "$REPO_ROOT"
for svc in "${enabled[@]}"; do
if get_build_ctx "$svc" | grep -q .; then
build_image "$svc"
else
echo " ⚠ Пропуск $svc (нет конфига сборки)"
fi
done
}
do_apply() {
local enabled
enabled=($(get_enabled_services))
if [[ ${#enabled[@]} -eq 0 ]]; then
echo "Ошибка: нет включённых сервисов в $CONFIG"
exit 1
fi
echo "=== Namespace ==="
kubectl apply -f "$SCRIPT_DIR/namespace.yaml"
echo ""
echo "=== Config (production https://gooseek.ru) ==="
local auth_url="https://gooseek.ru"
local origins="https://gooseek.ru,https://www.gooseek.ru"
kubectl create configmap gooseek-env -n gooseek \
--from-literal=allowed-origins="$origins" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap gooseek-env -n gooseek-auth \
--from-literal=better-auth-url="$auth_url" \
--from-literal=trusted-origins="$origins" \
--dry-run=client -o yaml | kubectl apply -f -
echo ""
echo "=== Манифесты (${#enabled[@]} сервисов) ==="
for svc in "${enabled[@]}"; do
local mf
mf=$(get_manifest "$svc")
if [[ -n "$mf" && -f "$SCRIPT_DIR/$mf" ]]; then
echo "$mf"
kubectl apply -f "$SCRIPT_DIR/$mf"
else
echo " ⚠ Пропуск $svc (манифест не найден)"
fi
done
echo ""
echo "=== Rollout restart (подхват новых образов :latest) ==="
for svc in "${enabled[@]}"; do
local mf
mf=$(get_manifest "$svc")
[[ -z "$mf" || ! -f "$SCRIPT_DIR/$mf" ]] && continue
case "$svc" in
auth-svc)
kubectl rollout restart deployment/auth-svc -n gooseek-auth 2>/dev/null || true
echo " → auth-svc (gooseek-auth)"
;;
cache-worker)
echo " (cache-worker — CronJob, пропуск)"
;;
*)
kubectl rollout restart deployment/"$svc" -n gooseek 2>/dev/null || true
echo "$svc"
;;
esac
done
# Миграция auth-svc — только при --migrate (не при каждом деплое)
# Запускайте ./deploy.sh --migrate после обновления auth-svc или при первой установке
# PVC ReadWriteOnce: временно scale down auth-svc, чтобы migrate pod мог смонтировать том
if [[ "$FORCE_MIGRATE" -eq 1 ]] && printf '%s\n' "${enabled[@]}" | grep -qx "auth-svc"; then
echo ""
echo "=== Миграция auth-svc ==="
kubectl scale deployment auth-svc -n gooseek-auth --replicas=0 2>/dev/null || true
echo " Ожидание остановки auth-svc..."
kubectl wait --for=delete pod -l app=auth-svc -n gooseek-auth --timeout=60s 2>/dev/null || sleep 8
kubectl run auth-migrate --rm --restart=Never \
--image=gooseek/auth-svc:latest \
--namespace=gooseek-auth \
--overrides='{"spec":{"containers":[{"name":"migrate","image":"gooseek/auth-svc:latest","command":["npx","@better-auth/cli","migrate","--yes"],"env":[{"name":"DATABASE_PATH","value":"/data/auth.db"}],"volumeMounts":[{"name":"auth-data","mountPath":"/data"}]}],"volumes":[{"name":"auth-data","persistentVolumeClaim":{"claimName":"auth-data-pvc"}}]}}' \
2>/dev/null || echo " ⚠ Миграция завершилась с ошибкой"
kubectl scale deployment auth-svc -n gooseek-auth --replicas=1 2>/dev/null || true
echo " auth-svc возвращён в работу"
fi
# Ingress — production https://gooseek.ru
if get_ingress_enabled; then
echo ""
echo "=== Ingress (https://gooseek.ru) ==="
kubectl delete ingress gooseek-web-ingress -n gooseek 2>/dev/null || true
if [[ -f "$SCRIPT_DIR/ssl/backup/fullchain.pem" ]] && [[ -f "$SCRIPT_DIR/ssl/backup/privkey.pem" ]]; then
echo " SSL: серты из backup/"
"$SCRIPT_DIR/ssl/apply-secret.sh" 2>/dev/null || true
kubectl apply -f "$SCRIPT_DIR/ingress-production-manual.yaml"
else
local ssl_auto
ssl_auto=$(get_ssl_auto)
ssl_auto=${ssl_auto:-true}
if [[ "$ssl_auto" == "true" ]]; then
echo " SSL: cert-manager (Let's Encrypt, первый деплой)"
do_ssl_production
else
echo " ⚠ Нет сертов в backup/. Варианты:"
echo " 1) ssl.auto: true в deploy.config.yaml — cert-manager получит серты"
echo " 2) Получите серты (certbot), положите в deploy/k3s/ssl/backup/, затем перезапустите деплой"
exit 1
fi
fi
fi
echo ""
echo "=== Готово ==="
echo "Проверка: kubectl get pods -n gooseek"
}
do_list() {
echo "Включённые сервисы:"
get_enabled_services | while read -r svc; do
echo " - $svc"
done
echo ""
echo "Ingress: $(get_ingress_enabled && echo 'enabled' || echo 'disabled')"
echo "Production: https://gooseek.ru"
if [[ -f "$SCRIPT_DIR/ssl/backup/fullchain.pem" ]] && [[ -f "$SCRIPT_DIR/ssl/backup/privkey.pem" ]]; then
echo "SSL: backup/"
else
if [[ "$(get_ssl_auto)" == "true" ]]; then
echo "SSL: cert-manager (Let's Encrypt)"
else
echo "SSL: нужен backup/ или ssl.auto: true"
fi
fi
}
# Main
NO_BUILD=0
BUILD_ONLY=0
LIST_ONLY=0
FORCE_MIGRATE=0
for arg in "$@"; do
case "$arg" in
--no-build) NO_BUILD=1 ;;
--build-only) BUILD_ONLY=1 ;;
--list) LIST_ONLY=1 ;;
--migrate) FORCE_MIGRATE=1 ;;
--skip-migrate) FORCE_MIGRATE=0 ;;
-h|--help)
echo "Использование: $0 [--no-build|--build-only|--list|--migrate|--skip-migrate]"
echo ""
echo " --no-build Только apply манифестов (без сборки образов)"
echo " --build-only Только сборка образов (без apply)"
echo " --list Показать включённые сервисы из deploy.config.yaml"
echo " --migrate Запустить миграцию auth-svc (при изменении схемы)"
echo " --skip-migrate Пропустить миграцию (по умолчанию)"
exit 0
;;
esac
done
if [[ "$LIST_ONLY" -eq 1 ]]; then
do_list
exit 0
fi
if [[ "$BUILD_ONLY" -eq 1 ]]; then
do_build
exit 0
fi
if [[ "$NO_BUILD" -eq 0 ]]; then
do_build
echo ""
fi
do_apply

View File

@@ -0,0 +1,77 @@
# discover-svc — агрегация новостей
# docs/architecture: 02-k3s-microservices-spec.md
apiVersion: apps/v1
kind: Deployment
metadata:
name: discover-svc
namespace: gooseek
spec:
replicas: 2
selector:
matchLabels:
app: discover-svc
template:
metadata:
labels:
app: discover-svc
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3002"
prometheus.io/path: "/metrics"
spec:
containers:
- name: discover-svc
image: gooseek/discover-svc:latest
ports:
- containerPort: 3002
env:
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: redis-credentials
key: url
- name: SEARXNG_URL
value: "http://searxng.gooseek-infra:8080"
- name: GHOST_URL
valueFrom:
secretKeyRef:
name: ghost-credentials
key: url
optional: true
- name: GHOST_CONTENT_API_KEY
valueFrom:
secretKeyRef:
name: ghost-credentials
key: content_api_key
optional: true
livenessProbe:
httpGet:
path: /health
port: 3002
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3002
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: discover-svc
namespace: gooseek
spec:
selector:
app: discover-svc
ports:
- port: 3002
targetPort: 3002

View File

@@ -0,0 +1,67 @@
# finance-svc — Market data, heatmap
apiVersion: apps/v1
kind: Deployment
metadata:
name: finance-svc
namespace: gooseek
spec:
replicas: 2
selector:
matchLabels:
app: finance-svc
template:
metadata:
labels:
app: finance-svc
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3003"
prometheus.io/path: "/metrics"
spec:
containers:
- name: finance-svc
image: gooseek/finance-svc:latest
ports:
- containerPort: 3003
env:
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: redis-credentials
key: url
- name: FMP_API_KEY
valueFrom:
secretKeyRef:
name: finance-keys
key: fmp
livenessProbe:
httpGet:
path: /health
port: 3003
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3003
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: finance-svc
namespace: gooseek
spec:
selector:
app: finance-svc
ports:
- port: 3003
targetPort: 3003

153
deploy/k3s/hpa.yaml Normal file
View File

@@ -0,0 +1,153 @@
# HPA — Horizontal Pod Autoscaler
# docs/architecture: 02-k3s-microservices-spec.md §3.3, 05-gaps-and-best-practices.md §9
# Требует metrics-server в кластере: kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
---
# PodDisruptionBudget — docs/architecture: 05-gaps-and-best-practices.md §9
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: chat-svc-pdb
namespace: gooseek
spec:
minAvailable: 1
selector:
matchLabels:
app: chat-svc
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: search-svc-pdb
namespace: gooseek
spec:
minAvailable: 1
selector:
matchLabels:
app: search-svc
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: chat-svc-hpa
namespace: gooseek
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: chat-svc
minReplicas: 2
maxReplicas: 8
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: search-svc-hpa
namespace: gooseek
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: search-svc
minReplicas: 2
maxReplicas: 6
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: discover-svc-hpa
namespace: gooseek
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: discover-svc
minReplicas: 1
maxReplicas: 4
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: finance-svc-hpa
namespace: gooseek
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: finance-svc
minReplicas: 1
maxReplicas: 4
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: travel-svc-hpa
namespace: gooseek
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: travel-svc
minReplicas: 1
maxReplicas: 3
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: memory-svc-hpa
namespace: gooseek
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: memory-svc
minReplicas: 1
maxReplicas: 3
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70

View File

@@ -0,0 +1,39 @@
# Production Ingress для gooseek.ru — HTTPS (ручной Secret)
# Используется когда cert-manager НЕ установлен. Secret gooseek-tls создаётся через apply-secret.sh
# kubectl apply -f deploy/k3s/ingress-production-manual.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gooseek-production
namespace: gooseek
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
tls:
- hosts:
- gooseek.ru
- www.gooseek.ru
secretName: gooseek-tls
rules:
- host: gooseek.ru
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 3000
- host: www.gooseek.ru
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 3000

View File

@@ -0,0 +1,43 @@
# Production Ingress для gooseek.ru — HTTPS
# cert-manager создаёт Secret gooseek-tls автоматически (Let's Encrypt)
# Требования: DNS gooseek.ru → IP ingress-nginx, порт 80 доступен из интернета
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gooseek-production
namespace: gooseek
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
cert-manager.io/cluster-issuer: letsencrypt-prod
# Добавить ACME challenge в этот ingress (избегает 404 при отдельном challenge ingress)
acme.cert-manager.io/http01-edit-in-place: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- gooseek.ru
- www.gooseek.ru
secretName: gooseek-tls
rules:
- host: gooseek.ru
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 3000
- host: www.gooseek.ru
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 3000

132
deploy/k3s/ingress.yaml Normal file
View File

@@ -0,0 +1,132 @@
# Ingress для GooSeek — path-based маршрутизация к микросервисам
# docs/architecture: 02-k3s-microservices-spec.md §4
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gooseek-ingress
namespace: gooseek
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- gooseek.ru
- www.gooseek.ru
secretName: gooseek-tls
rules:
- host: gooseek.ru
http:
paths:
- path: /api/v1/discover
pathType: Prefix
backend:
service:
name: discover-svc
port:
number: 3002
- path: /api/v1/finance
pathType: Prefix
backend:
service:
name: finance-svc
port:
number: 3003
- path: /api/v1/travel
pathType: Prefix
backend:
service:
name: travel-svc
port:
number: 3004
- path: /api/v1/library
pathType: Prefix
backend:
service:
name: library-svc
port:
number: 3009
- path: /api/v1/collections
pathType: Prefix
backend:
service:
name: projects-svc
port:
number: 3006
- path: /api/v1/templates
pathType: Prefix
backend:
service:
name: projects-svc
port:
number: 3006
- path: /api/v1/connectors
pathType: Prefix
backend:
service:
name: projects-svc
port:
number: 3006
- path: /api/v1/memory
pathType: Prefix
backend:
service:
name: memory-svc
port:
number: 3010
- path: /api/v1/create
pathType: Prefix
backend:
service:
name: create-svc
port:
number: 3011
- path: /api/v1/export
pathType: Prefix
backend:
service:
name: create-svc
port:
number: 3011
- path: /api/v1/search
pathType: Prefix
backend:
service:
name: search-svc
port:
number: 3001
- path: /api/v1/tasks
pathType: Prefix
backend:
service:
name: chat-svc
port:
number: 3005
- path: /api/v1/patents
pathType: Prefix
backend:
service:
name: search-svc
port:
number: 3001
- path: /api/v1/notifications
pathType: Prefix
backend:
service:
name: notifications-svc
port:
number: 3013
- path: /api/v1/billing
pathType: Prefix
backend:
service:
name: billing-svc
port:
number: 3008
- path: /api/v1/admin
pathType: Prefix
backend:
service:
name: audit-svc
port:
number: 3012

View File

@@ -0,0 +1,64 @@
# library-svc — история тредов
apiVersion: apps/v1
kind: Deployment
metadata:
name: library-svc
namespace: gooseek
spec:
replicas: 2
selector:
matchLabels:
app: library-svc
template:
metadata:
labels:
app: library-svc
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3009"
prometheus.io/path: "/metrics"
spec:
containers:
- name: library-svc
image: gooseek/library-svc:latest
ports:
- containerPort: 3009
env:
- name: POSTGRES_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url
- name: AUTH_SERVICE_URL
value: "http://auth-svc.gooseek-auth:3014"
livenessProbe:
httpGet:
path: /health
port: 3009
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3009
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: library-svc
namespace: gooseek
spec:
selector:
app: library-svc
ports:
- port: 3009
targetPort: 3009

130
deploy/k3s/llm-svc.yaml Normal file
View File

@@ -0,0 +1,130 @@
# llm-svc — LLM providers microservice
# API: GET/POST/PATCH/DELETE /api/v1/providers, models CRUD
# Используется chat-svc, media-svc, suggestions-svc и др.
apiVersion: apps/v1
kind: Deployment
metadata:
name: llm-svc
namespace: gooseek
spec:
replicas: 1
selector:
matchLabels:
app: llm-svc
template:
metadata:
labels:
app: llm-svc
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3020"
prometheus.io/path: "/metrics"
spec:
containers:
- name: llm-svc
image: gooseek/llm-svc:latest
imagePullPolicy: Never
ports:
- containerPort: 3020
env:
- name: PORT
value: "3020"
- name: DATA_DIR
value: "/app/data"
- name: LLM_PROVIDER
valueFrom:
configMapKeyRef:
name: llm-svc-config
key: llm-provider
- name: OLLAMA_BASE_URL
valueFrom:
configMapKeyRef:
name: llm-svc-config
key: ollama-base-url
optional: true
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: llm-credentials
key: openai-api-key
optional: true
- name: TIMEWEB_API_BASE_URL
valueFrom:
configMapKeyRef:
name: llm-svc-config
key: timeweb-api-base-url
optional: true
- name: LLM_CHAT_MODEL
valueFrom:
configMapKeyRef:
name: llm-svc-config
key: llm-chat-model
optional: true
- name: TIMEWEB_X_PROXY_SOURCE
valueFrom:
configMapKeyRef:
name: llm-svc-config
key: timeweb-x-proxy-source
optional: true
- name: TIMEWEB_AGENT_ACCESS_ID
valueFrom:
secretKeyRef:
name: llm-credentials
key: timeweb-agent-access-id
optional: true
- name: TIMEWEB_API_KEY
valueFrom:
secretKeyRef:
name: llm-credentials
key: timeweb-api-key
optional: true
volumeMounts:
- name: llm-data
mountPath: /app/data
livenessProbe:
httpGet:
path: /health
port: 3020
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3020
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: llm-data
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: llm-svc
namespace: gooseek
spec:
selector:
app: llm-svc
ports:
- port: 3020
targetPort: 3020
---
# ConfigMap: llm-provider = ollama | timeweb
# Для timeweb: создайте Secret llm-credentials с ключами timeweb-agent-access-id, timeweb-api-key
apiVersion: v1
kind: ConfigMap
metadata:
name: llm-svc-config
namespace: gooseek
data:
llm-provider: "timeweb"
ollama-base-url: "http://host.docker.internal:11434"
timeweb-api-base-url: "https://api.timeweb.cloud"
llm-chat-model: "gpt-4"

View File

@@ -0,0 +1,67 @@
# memory-svc — персональная память AI, Enterprise Memory
# docs/architecture: 01-perplexity-analogue-design.md §5.9
apiVersion: apps/v1
kind: Deployment
metadata:
name: memory-svc
namespace: gooseek
spec:
replicas: 2
selector:
matchLabels:
app: memory-svc
template:
metadata:
labels:
app: memory-svc
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3010"
prometheus.io/path: "/metrics"
spec:
containers:
- name: memory-svc
image: gooseek/memory-svc:latest
ports:
- containerPort: 3010
env:
- name: PORT
value: "3010"
- name: POSTGRES_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url
- name: AUTH_SERVICE_URL
value: "http://auth-svc.gooseek-auth:3014"
livenessProbe:
httpGet:
path: /health
port: 3010
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3010
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: memory-svc
namespace: gooseek
spec:
selector:
app: memory-svc
ports:
- port: 3010
targetPort: 3010

21
deploy/k3s/namespace.yaml Normal file
View File

@@ -0,0 +1,21 @@
# docs/architecture: 02-k3s-microservices-spec.md
apiVersion: v1
kind: Namespace
metadata:
name: gooseek
labels:
app.kubernetes.io/name: gooseek
---
apiVersion: v1
kind: Namespace
metadata:
name: gooseek-auth
labels:
app.kubernetes.io/name: gooseek-auth
---
apiVersion: v1
kind: Namespace
metadata:
name: gooseek-infra
labels:
app.kubernetes.io/name: gooseek-infra

View File

@@ -0,0 +1,17 @@
# NetworkPolicy — базовая сетевая изоляция
# docs/architecture: 05-gaps-and-best-practices.md §3
# Разрешает трафик внутри namespace gooseek (chat→search, cache-worker→redis и т.д.)
# Для production добавьте ingress namespace в from
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: gooseek-allow-internal
namespace: gooseek
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- podSelector: {}

View File

@@ -0,0 +1,77 @@
# notifications-svc — Web Push, SMTP email, preferences, reminders
# docs/architecture: 02-k3s-microservices-spec.md
apiVersion: apps/v1
kind: Deployment
metadata:
name: notifications-svc
namespace: gooseek
spec:
replicas: 1
selector:
matchLabels:
app: notifications-svc
template:
metadata:
labels:
app: notifications-svc
spec:
containers:
- name: notifications-svc
image: gooseek/notifications-svc:latest
ports:
- containerPort: 3013
env:
- name: POSTGRES_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url
- name: AUTH_SERVICE_URL
value: "http://auth-svc.gooseek-auth:3014"
- name: VAPID_PUBLIC_KEY
valueFrom:
secretKeyRef:
name: notifications-credentials
key: vapid_public
- name: VAPID_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: notifications-credentials
key: vapid_private
- name: SMTP_URL
valueFrom:
secretKeyRef:
name: notifications-credentials
key: smtp_url
optional: true
livenessProbe:
httpGet:
path: /health
port: 3013
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3013
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: notifications-svc
namespace: gooseek
spec:
selector:
app: notifications-svc
ports:
- port: 3013
targetPort: 3013

View File

@@ -0,0 +1,67 @@
# profile-svc — личные данные и персонализация пользователя
apiVersion: apps/v1
kind: Deployment
metadata:
name: profile-svc
namespace: gooseek
spec:
replicas: 1
selector:
matchLabels:
app: profile-svc
template:
metadata:
labels:
app: profile-svc
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3019"
prometheus.io/path: "/metrics"
spec:
containers:
- name: profile-svc
image: gooseek/profile-svc:latest
imagePullPolicy: Never
ports:
- containerPort: 3019
env:
- name: PORT
value: "3019"
- name: POSTGRES_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url
- name: AUTH_SERVICE_URL
value: "http://auth-svc.gooseek-auth:3014"
livenessProbe:
httpGet:
path: /health
port: 3019
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3019
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: profile-svc
namespace: gooseek
spec:
selector:
app: profile-svc
ports:
- port: 3019
targetPort: 3019

View File

@@ -0,0 +1,55 @@
# projects-svc — Spaces, Collections
apiVersion: apps/v1
kind: Deployment
metadata:
name: projects-svc
namespace: gooseek
spec:
replicas: 2
selector:
matchLabels:
app: projects-svc
template:
metadata:
labels:
app: projects-svc
spec:
containers:
- name: projects-svc
image: gooseek/projects-svc:latest
ports:
- containerPort: 3006
env:
- name: AUTH_SERVICE_URL
value: "http://auth-svc.gooseek-auth:3014"
livenessProbe:
httpGet:
path: /health
port: 3006
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3006
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: projects-svc
namespace: gooseek
spec:
selector:
app: projects-svc
ports:
- port: 3006
targetPort: 3006

View File

@@ -0,0 +1,65 @@
# search-svc — SearXNG proxy, кэш по query_hash
# docs/architecture: 02-k3s-microservices-spec.md
apiVersion: apps/v1
kind: Deployment
metadata:
name: search-svc
namespace: gooseek
spec:
replicas: 2
selector:
matchLabels:
app: search-svc
template:
metadata:
labels:
app: search-svc
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3001"
prometheus.io/path: "/metrics"
spec:
containers:
- name: search-svc
image: gooseek/search-svc:latest
ports:
- containerPort: 3001
env:
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: redis-credentials
key: url
- name: SEARXNG_URL
value: "http://searxng.gooseek-infra:8080"
livenessProbe:
httpGet:
path: /health
port: 3001
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3001
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: search-svc
namespace: gooseek
spec:
selector:
app: search-svc
ports:
- port: 3001
targetPort: 3001

187
deploy/k3s/ssl/README.md Normal file
View File

@@ -0,0 +1,187 @@
# SSL-сертификат для gooseek.ru
Инструкция по получению сертификата, бэкапу и подключению к K3s.
---
## 1. Получение сертификата (Let's Encrypt)
### Вариант A: certbot на сервере (рекомендуется)
```bash
# Установка certbot (Ubuntu/Debian)
sudo apt update && sudo apt install -y certbot
# Получение сертификата (standalone — порт 80 должен быть свободен)
sudo certbot certonly --standalone -d gooseek.ru -d www.gooseek.ru \
--email admin@gooseek.ru \
--agree-tos \
--no-eff-email
# Файлы появятся в:
# /etc/letsencrypt/live/gooseek.ru/fullchain.pem
# /etc/letsencrypt/live/gooseek.ru/privkey.pem
```
### Вариант B: certbot с webroot (если nginx уже слушает 80)
```bash
sudo certbot certonly --webroot -w /var/www/html \
-d gooseek.ru -d www.gooseek.ru \
--email admin@gooseek.ru \
--agree-tos \
--no-eff-email
```
### Вариант C: DNS challenge (если порт 80 недоступен)
```bash
sudo certbot certonly --manual --preferred-challenges dns \
-d gooseek.ru -d www.gooseek.ru \
--email admin@gooseek.ru \
--agree-tos
# Certbot попросит добавить TXT-запись в DNS
```
---
## 2. Бэкап сертификата
Создайте папку бэкапа и скопируйте туда сертификаты:
```bash
# Из корня репозитория
mkdir -p deploy/k3s/ssl/backup
# Копирование с сервера (после certbot)
sudo cp /etc/letsencrypt/live/gooseek.ru/fullchain.pem deploy/k3s/ssl/backup/
sudo cp /etc/letsencrypt/live/gooseek.ru/privkey.pem deploy/k3s/ssl/backup/
# Или через scp с production-сервера:
# scp user@gooseek.ru:/etc/letsencrypt/live/gooseek.ru/fullchain.pem deploy/k3s/ssl/backup/
# scp user@gooseek.ru:/etc/letsencrypt/live/gooseek.ru/privkey.pem deploy/k3s/ssl/backup/
```
**Важно:** Папка `backup/` в `.gitignore` — сертификаты не попадут в git.
---
## 3. Создание Kubernetes Secret из бэкапа
```bash
# Из корня репозитория
kubectl create secret tls gooseek-tls \
--namespace=gooseek \
--cert=deploy/k3s/ssl/backup/fullchain.pem \
--key=deploy/k3s/ssl/backup/privkey.pem \
--dry-run=client -o yaml | kubectl apply -f -
```
Или обновить существующий:
```bash
kubectl delete secret gooseek-tls -n gooseek 2>/dev/null || true
kubectl create secret tls gooseek-tls \
--namespace=gooseek \
--cert=deploy/k3s/ssl/backup/fullchain.pem \
--key=deploy/k3s/ssl/backup/privkey.pem
```
---
## 4. Применение Ingress с TLS
При ручных сертах (из backup/):
```bash
./deploy/k3s/ssl/apply-secret.sh
kubectl apply -f deploy/k3s/ingress-production-manual.yaml
```
При cert-manager:
```bash
kubectl apply -f deploy/k3s/ingress-production.yaml
```
---
## 5. Автообновление (certbot)
Let's Encrypt выдаёт сертификаты на 90 дней. Настройте автообновление:
```bash
# Проверка таймера
sudo systemctl status certbot.timer
# Ручное обновление
sudo certbot renew --dry-run
# После обновления — пересоздать Secret и перезапустить ingress
sudo cp /etc/letsencrypt/live/gooseek.ru/fullchain.pem deploy/k3s/ssl/backup/
sudo cp /etc/letsencrypt/live/gooseek.ru/privkey.pem deploy/k3s/ssl/backup/
kubectl create secret tls gooseek-tls -n gooseek \
--cert=deploy/k3s/ssl/backup/fullchain.pem \
--key=deploy/k3s/ssl/backup/privkey.pem \
--dry-run=client -o yaml | kubectl apply -f -
```
---
## 6. Альтернатива: cert-manager (автоматически)
Если не хотите вручную обновлять — установите cert-manager:
```bash
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
# Создать ClusterIssuer для Let's Encrypt
kubectl apply -f deploy/k3s/ssl/cert-manager-issuer.yaml
```
Тогда Secret `gooseek-tls` создаётся автоматически, бэкап не нужен (но можно экспортировать для переноса).
---
---
## 7. Настройка в Kubernetes
### Автоматически при деплое (рекомендуется)
В `deploy.config.yaml` установите `ssl.auto: true`. При запуске `./deploy/k3s/deploy.sh` cert-manager и SSL настраиваются автоматически.
**Требования для HTTP-01 (Let's Encrypt):**
- DNS: gooseek.ru и www.gooseek.ru → публичный IP ingress-nginx (LoadBalancer или NodePort)
- Порт 80 доступен из интернета (firewall, Security Groups)
### Вручную
```bash
# Из корня репозитория
# Вариант A: cert-manager — автоматические сертификаты
./deploy/k3s/ssl/setup-kubernetes.sh cert-manager
# Вариант B: ручные сертификаты (certbot уже выполнен, файлы в backup/)
./deploy/k3s/ssl/setup-kubernetes.sh manual
```
**Требования:**
- Namespace `gooseek` создан
- Ingress-nginx установлен (порт 80 и 443)
- Домен gooseek.ru указывает на IP кластера
---
## Структура папки
```
deploy/k3s/ssl/
├── README.md # эта инструкция
├── setup-kubernetes.sh # настройка SSL в K8s (cert-manager или manual)
├── obtain-cert.sh # получение сертификата на сервере (certbot)
├── apply-secret.sh # создание Secret из backup/
├── backup/ # сертификаты (в .gitignore)
│ ├── fullchain.pem
│ └── privkey.pem
└── cert-manager-issuer.yaml
```

24
deploy/k3s/ssl/apply-secret.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Создаёт/обновляет Secret gooseek-tls из бэкапа
# Запуск из корня репозитория: ./deploy/k3s/ssl/apply-secret.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BACKUP_DIR="$SCRIPT_DIR/backup"
CERT="$BACKUP_DIR/fullchain.pem"
KEY="$BACKUP_DIR/privkey.pem"
if [[ ! -f "$CERT" ]] || [[ ! -f "$KEY" ]]; then
echo "Ошибка: нужны fullchain.pem и privkey.pem в $BACKUP_DIR"
echo "См. deploy/k3s/ssl/README.md"
exit 1
fi
kubectl delete secret gooseek-tls -n gooseek 2>/dev/null || true
kubectl create secret tls gooseek-tls \
--namespace=gooseek \
--cert="$CERT" \
--key="$KEY"
echo "Secret gooseek-tls создан. Примените: kubectl apply -f deploy/k3s/ingress-production.yaml"

View File

@@ -0,0 +1 @@
# Сюда класть fullchain.pem и privkey.pem (см. deploy/k3s/ssl/README.md)

View File

@@ -0,0 +1,18 @@
# Опционально: cert-manager для автоматического получения сертификатов
# Установка: kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
# Затем: kubectl apply -f deploy/k3s/ssl/cert-manager-issuer.yaml
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@gooseek.ru
privateKeySecretRef:
name: letsencrypt-prod-account
solvers:
- http01:
ingress:
class: nginx

24
deploy/k3s/ssl/check-cert.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Проверка статуса сертификата gooseek.ru
# Запуск: ./deploy/k3s/ssl/check-cert.sh
set -e
echo "=== Certificate (cert-manager) ==="
kubectl get certificate -n gooseek 2>/dev/null || echo "Нет Certificate (используется backup?)"
echo ""
echo "=== Secret gooseek-tls ==="
kubectl get secret gooseek-tls -n gooseek 2>/dev/null || echo "Secret не найден"
echo ""
echo "=== События Certificate ==="
kubectl describe certificate -n gooseek 2>/dev/null | tail -30 || true
echo ""
echo "=== Challenge (ACME) ==="
kubectl get challenge -A 2>/dev/null || true
echo ""
echo "=== Ingress ==="
kubectl get ingress -n gooseek

33
deploy/k3s/ssl/obtain-cert.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Получение SSL-сертификата Let's Encrypt для gooseek.ru
# Запускать НА СЕРВЕРЕ (5.187.83.209), где домен указывает на этот IP
# Перед запуском: порт 80 должен быть свободен (остановите nginx/приложения)
set -e
DOMAIN="gooseek.ru"
EMAIL="admin@gooseek.ru"
echo "=== Установка certbot (если не установлен) ==="
sudo apt update && sudo apt install -y certbot 2>/dev/null || true
echo ""
echo "=== Получение сертификата для $DOMAIN и www.$DOMAIN ==="
sudo certbot certonly --standalone \
-d "$DOMAIN" \
-d "www.$DOMAIN" \
--email "$EMAIL" \
--agree-tos \
--no-eff-email \
--non-interactive
echo ""
echo "=== Готово! Сертификаты в: ==="
echo " /etc/letsencrypt/live/$DOMAIN/fullchain.pem"
echo " /etc/letsencrypt/live/$DOMAIN/privkey.pem"
echo ""
echo "Скопируйте в backup и примените Secret:"
echo " sudo cp /etc/letsencrypt/live/$DOMAIN/fullchain.pem deploy/k3s/ssl/backup/"
echo " sudo cp /etc/letsencrypt/live/$DOMAIN/privkey.pem deploy/k3s/ssl/backup/"
echo " ./deploy/k3s/ssl/apply-secret.sh"
echo " kubectl apply -f deploy/k3s/ingress-production.yaml"

View File

@@ -0,0 +1,86 @@
#!/usr/bin/env bash
# Настройка SSL в Kubernetes для gooseek.ru
# Запуск из корня репозитория: ./deploy/k3s/ssl/setup-kubernetes.sh
#
# Вариант A: cert-manager (автоматические сертификаты Let's Encrypt)
# Вариант B: ручные сертификаты (certbot на сервере → apply-secret.sh)
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
CERT_MANAGER_VERSION="v1.13.0"
usage() {
echo "Использование: $0 [cert-manager|manual]"
echo ""
echo " cert-manager — установить cert-manager и настроить автоматические сертификаты (рекомендуется)"
echo " manual — применить ingress с ручным Secret (нужны fullchain.pem, privkey.pem в backup/)"
echo ""
echo "Без аргумента — cert-manager"
}
apply_cert_manager() {
echo "=== 1. Установка cert-manager ==="
kubectl apply -f "https://github.com/cert-manager/cert-manager/releases/download/${CERT_MANAGER_VERSION}/cert-manager.yaml"
echo "Ожидание готовности cert-manager (до 2 мин)..."
kubectl wait --for=condition=Available deployment/cert-manager -n cert-manager --timeout=120s 2>/dev/null || true
kubectl wait --for=condition=Available deployment/cert-manager-webhook -n cert-manager --timeout=120s 2>/dev/null || true
kubectl wait --for=condition=Available deployment/cert-manager-cainjector -n cert-manager --timeout=120s 2>/dev/null || true
echo ""
echo "=== 2. ClusterIssuer Let's Encrypt ==="
kubectl apply -f "$SCRIPT_DIR/cert-manager-issuer.yaml"
echo ""
echo "=== 3. Production Ingress (cert-manager создаст Secret gooseek-tls) ==="
kubectl apply -f "$REPO_ROOT/deploy/k3s/ingress-production.yaml"
echo ""
echo "=== Готово ==="
echo "Сертификат будет получен в течение 12 минут."
echo "Проверка: kubectl get certificate -n gooseek"
echo "Проверка: kubectl get secret gooseek-tls -n gooseek"
}
apply_manual() {
if [[ ! -f "$SCRIPT_DIR/backup/fullchain.pem" ]] || [[ ! -f "$SCRIPT_DIR/backup/privkey.pem" ]]; then
echo "Ошибка: нужны fullchain.pem и privkey.pem в $SCRIPT_DIR/backup/"
echo "См. deploy/k3s/ssl/README.md — получите сертификат через certbot на сервере"
exit 1
fi
echo "=== 1. Secret gooseek-tls из backup ==="
"$SCRIPT_DIR/apply-secret.sh"
echo ""
echo "=== 2. Production Ingress (ручной Secret) ==="
kubectl apply -f "$REPO_ROOT/deploy/k3s/ingress-production-manual.yaml"
echo ""
echo "=== Готово ==="
}
# Проверка namespace
if ! kubectl get namespace gooseek &>/dev/null; then
echo "Создание namespace gooseek..."
kubectl apply -f "$REPO_ROOT/deploy/k3s/namespace.yaml"
fi
# Проверка ingress-nginx
if ! kubectl get deployment -n ingress-nginx ingress-nginx-controller &>/dev/null 2>&1; then
echo "Внимание: ingress-nginx не найден. Установите:"
echo " kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml"
echo ""
read -p "Продолжить? (y/n) " -n 1 -r
echo
[[ $REPLY =~ ^[Yy]$ ]] || exit 1
fi
MODE="${1:-cert-manager}"
case "$MODE" in
cert-manager) apply_cert_manager ;;
manual) apply_manual ;;
-h|--help) usage; exit 0 ;;
*) echo "Неизвестный режим: $MODE"; usage; exit 1 ;;
esac

View File

@@ -0,0 +1,62 @@
# travel-svc — Trending, Inspiration
apiVersion: apps/v1
kind: Deployment
metadata:
name: travel-svc
namespace: gooseek
spec:
replicas: 2
selector:
matchLabels:
app: travel-svc
template:
metadata:
labels:
app: travel-svc
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3004"
prometheus.io/path: "/metrics"
spec:
containers:
- name: travel-svc
image: gooseek/travel-svc:latest
ports:
- containerPort: 3004
env:
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: redis-credentials
key: url
livenessProbe:
httpGet:
path: /health
port: 3004
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3004
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: travel-svc
namespace: gooseek
spec:
selector:
app: travel-svc
ports:
- port: 3004
targetPort: 3004

60
deploy/k3s/web-svc.yaml Normal file
View File

@@ -0,0 +1,60 @@
# web-svc — Next.js UI
# namespace: gooseek
# API_GATEWAY_URL — URL api-gateway (внутри кластера или через ingress)
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-svc
namespace: gooseek
spec:
replicas: 1
selector:
matchLabels:
app: web-svc
template:
metadata:
labels:
app: web-svc
spec:
containers:
- name: web-svc
image: gooseek/web-svc:latest
imagePullPolicy: Never
ports:
- containerPort: 3000
env:
- name: PORT
value: "3000"
- name: API_GATEWAY_URL
value: "http://api-gateway.gooseek:3015"
livenessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: /api/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: web-svc
namespace: gooseek
spec:
selector:
app: web-svc
ports:
- port: 3000
targetPort: 3000