Deploy: migrate k3s → Docker; search logic → master-agents-svc

- deploy/k3s удалён, deploy/docker добавлен (Caddyfile, docker-compose, searxng)
- chat-svc: agents/models/prompts удалены, использует llm-svc (LLMClient, EmbeddingClient)
- master-agents-svc: SearchOrchestrator, classifier, researcher, actions, widgets
- web-svc: ChatModelSelector, Optimization, Sources удалены; InputBarPlus; UnregisterSW
- geo-device-svc, localization-svc: Dockerfiles
- docs: 02-k3s-services-spec.md, RUNBOOK/TELEMETRY/WORKING удалены

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
home
2026-02-23 22:14:00 +03:00
parent cd6b7857ba
commit 328d968f3f
180 changed files with 3022 additions and 9798 deletions

View File

@@ -1,7 +1,9 @@
# syntax=docker/dockerfile:1
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json ./
RUN npm install
RUN --mount=type=cache,target=/root/.npm \
npm install
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
@@ -9,7 +11,8 @@ RUN npm run build
FROM node:22-alpine
WORKDIR /app
COPY package.json ./
RUN npm install --omit=dev
RUN --mount=type=cache,target=/root/.npm \
npm install --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3015
ENV PORT=3015

View File

@@ -4,9 +4,9 @@ import { fileURLToPath } from 'node:url';
config({ path: path.resolve(fileURLToPath(import.meta.url), '../../../../.env') });
/**
* api-gateway — прокси к микросервисам
* api-gateway — прокси к сервисам (СОА)
* web-svc = только UI, вся логика и API здесь
* docs/architecture: 02-k3s-microservices-spec.md §5
* docs/architecture: 02-k3s-services-spec.md §5
*/
import Fastify, { FastifyRequest, FastifyReply } from 'fastify';
@@ -76,12 +76,17 @@ async function proxyRequest(req: FastifyRequest, reply: FastifyReply, stream = f
const fullUrl = `${target.base.replace(/\/$/, '')}${target.rewrite}${url.search}`;
const headers: Record<string, string> = {};
const pass = ['authorization', 'content-type', 'accept', 'x-forwarded-for', 'x-real-ip', 'user-agent', 'accept-language'];
const pass = ['authorization', 'accept', 'x-forwarded-for', 'x-real-ip', 'user-agent', 'accept-language'];
for (const h of pass) {
const v = req.headers[h];
if (v && typeof v === 'string') headers[h] = v;
}
if (!headers['Content-Type'] && req.method !== 'GET') headers['Content-Type'] = 'application/json';
const ct = req.headers['content-type'];
if (ct && typeof ct === 'string' && ct.toLowerCase().includes('application/json')) {
headers['Content-Type'] = 'application/json';
} else if (req.method !== 'GET') {
headers['Content-Type'] = 'application/json';
}
try {
const method = req.method;
@@ -129,11 +134,25 @@ async function proxyRequest(req: FastifyRequest, reply: FastifyReply, stream = f
return reply.send(data);
} catch (err: unknown) {
req.log.error(err);
// Заглушки для сервисов, не запущенных в Docker
if (path.startsWith('/api/v1/discover')) return reply.send({ items: [] });
if (path.startsWith('/api/geo-context')) return reply.send({ country: null, city: null });
if (path.startsWith('/api/translations')) return reply.send({});
if (path.startsWith('/api/v1/weather')) return reply.send({});
return reply.status(503).send({ error: 'Service unavailable' });
}
}
const app = Fastify({ logger: true });
// Парсер JSON — принимает application/json с charset и дублированием
app.addContentTypeParser(/application\/json/i, { parseAs: 'string' }, (_, body, done) => {
try {
const str = typeof body === 'string' ? body : (body ? String(body) : '');
done(null, str ? JSON.parse(str) : {});
} catch (e) {
done(e as Error, undefined);
}
});
const corsOrigin = process.env.ALLOWED_ORIGINS
? process.env.ALLOWED_ORIGINS.split(',')
.map((s) => s.trim())