From a5fd251fc0822d48a3f76cc09c45151a569e9edf Mon Sep 17 00:00:00 2001 From: Blizzard Date: Fri, 26 Jun 2026 15:23:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(deploy):=20=E5=AE=B9=E5=99=A8=E5=8C=96=20+?= =?UTF-8?q?=20=E4=B8=80=E9=94=AE=E7=8B=AC=E7=AB=8B=E9=83=A8=E7=BD=B2=20?= =?UTF-8?q?=E2=80=94=E2=80=94=205=20=E6=9C=8D=E5=8A=A1=E9=95=9C=E5=83=8F?= =?UTF-8?q?=20+=20prod=20compose=20+=20=E5=AE=89=E8=A3=85/=E5=A4=87?= =?UTF-8?q?=E4=BB=BD/=E8=AE=B8=E5=8F=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 此前应用服务只是本机裸二进制、无 Dockerfile、无法交付。本提交把项目从"只能在我笔记本 跑"变成"任何人一条命令起一整套",是「演示 / 拉投资 / 卖给客户独立部署」的可交付基础。 - Dockerfile ×5:gateway/dispatcher/mcp-go 多阶段(distroless static, 36–55MB); mcp-py(python-slim, 268MB);admin(node 构建→nginx 托管 SPA + 反代 /api,76MB)。 Go 构建上下文为仓库根以兜住 replace ../sundynix-shared;.dockerignore 瘦上下文。 - docker-compose.prod.yml:应用 + 基建一把起;经服务名互连(顺带避开 localhost DNS 坑); 基建端口不对宿主暴露;depends_on 健康检查保序(mcp-go 待 Milvus healthy); APP_ENV=production 强制密钥校验 + 锁 CORS + 管理员白名单。 - .env.example + DEPLOY.md:两类密钥分层(引导密钥走 .env,模型 key 控制台加密存库); 首次管理员流程;安全建议。LICENSE 专有(授权模型可后定)。 - scripts/backup.sh + restore.sh:PG 逻辑备份 + 各数据卷快照 / 恢复。 - .gitignore 挡 .env*(密钥不入库)。 live:5 镜像全构建通过;独立 project 起整套 → nginx→gateway→NATS→dispatcher→mcp 全链路 注册+提任务 running→done,depends_on 保序生效。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .dockerignore | 11 ++ .env.example | 47 +++++++++ .gitignore | 6 ++ DEPLOY.md | 69 +++++++++++++ LICENSE | 20 ++++ README.md | 12 +++ docker-compose.prod.yml | 183 +++++++++++++++++++++++++++++++++ scripts/backup.sh | 38 +++++++ scripts/restore.sh | 39 +++++++ sundynix-admin/.dockerignore | 3 + sundynix-admin/Dockerfile | 15 +++ sundynix-admin/nginx.conf | 30 ++++++ sundynix-dispatcher/Dockerfile | 18 ++++ sundynix-gateway/Dockerfile | 22 ++++ sundynix-mcp-go/Dockerfile | 18 ++++ sundynix-mcp-py/.dockerignore | 4 + sundynix-mcp-py/Dockerfile | 10 ++ 17 files changed, 545 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 DEPLOY.md create mode 100644 LICENSE create mode 100644 docker-compose.prod.yml create mode 100755 scripts/backup.sh create mode 100755 scripts/restore.sh create mode 100644 sundynix-admin/.dockerignore create mode 100644 sundynix-admin/Dockerfile create mode 100644 sundynix-admin/nginx.conf create mode 100644 sundynix-dispatcher/Dockerfile create mode 100644 sundynix-gateway/Dockerfile create mode 100644 sundynix-mcp-go/Dockerfile create mode 100644 sundynix-mcp-py/.dockerignore create mode 100644 sundynix-mcp-py/Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3531c27 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.git +**/node_modules +**/.venv +**/dist +**/build +sundynix-desktop/build +backups +*.log +/tmp +**/*_test.go +.env diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..bd01177 --- /dev/null +++ b/.env.example @@ -0,0 +1,47 @@ +# sundynix-agentix 部署配置模板 —— 复制为 .env 后填写(docker compose -f docker-compose.prod.yml 读取) +# +# cp .env.example .env +# # 按下方注释生成/填写各项,然后: +# docker compose -f docker-compose.prod.yml up -d --build +# +# 说明:这里只放「引导密钥/基建凭据」。LLM 模型 API key 不在这里 —— 部署起来后登录控制台配置、 +# 由平台 AES 加密存库(加密用的就是下面的 SUNDYNIX_SECRET_KEY)。 + +# ── 必填:引导密钥(生成命令见各行) ───────────────────────────── +# 主加密密钥:用于加密控制台里配置的模型 API key。三服务共用同一个,丢了=已存的密钥都解不开。 +# 生成:openssl rand -base64 32 +SUNDYNIX_SECRET_KEY= + +# 登录令牌签名密钥(JWT)。 +# 生成:openssl rand -base64 32 +JWT_SECRET= + +# 数据库密码(PostgreSQL,平台主库)。 +# 生成:openssl rand -base64 24 (勿用特殊字符 @ : / 以免污染 DSN) +POSTGRES_PASSWORD= + +# 图数据库密码(Neo4j,知识图谱)。 +NEO4J_PASSWORD= + +# ── 必填:管理员 ───────────────────────────────────────────── +# 管理员用户 ID 白名单(逗号分隔)。生产模式下只有这些用户能进 /admin 控制台。 +# 首次部署:先留空起服务 → 注册第一个账号 → 从返回的 user id 填进来 → 重启 gateway。 +ADMIN_USER_IDS= + +# ── 选填:网络/端口 ───────────────────────────────────────── +# 控制台对外端口(默认 80)。 +HTTP_PORT=80 +# 网关对外端口(供桌面端直连;纯 Web 用可不暴露)。 +GATEWAY_PORT=8080 +# 允许的前端来源(生产建议设具体域名,如 https://app.example.com;缺省 * 仅供内网/演示)。 +CORS_ALLOW_ORIGIN=* +# Jaeger 链路追踪 UI 端口。 +JAEGER_UI_PORT=16686 + +# ── 选填:调优 ─────────────────────────────────────────────── +# 单调度副本并发任务数。 +DISPATCHER_CONCURRENCY=8 +# 单任务 token 预算上限(防失控成本)。 +TASK_TOKEN_BUDGET=200000 +# 镜像标签。 +TAG=latest diff --git a/.gitignore b/.gitignore index 87b8c74..bfd71f5 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,9 @@ data/ # Claude Code 本地机器配置(launch.json 可共享,settings.local 不提交) .claude/settings.local.json + +# 部署密钥(绝不入库) +.env +.env.* +!.env.example +backups/ diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..0d2229f --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,69 @@ +# 部署指南(独立部署 / 单机演示) + +一条命令拉起整套 sundynix-agentix(应用 5 服务 + 基建)。适用于**演示**与**客户独立部署**。 +集群高可用(多副本 / NATS 集群 / 数据库主从)是用户量上来后的事,不在本指南范围。 + +## 前置 +- Docker 24+ 与 Docker Compose v2 +- 约 8GB 内存、10GB 磁盘(Milvus/Neo4j 较吃资源) + +## 三步上手 + +```bash +# 1) 生成配置 +cp .env.example .env + +# 2) 填密钥(必填四项 + 管理员) +# 主加密密钥 / JWT 密钥: +openssl rand -base64 32 # 填入 SUNDYNIX_SECRET_KEY +openssl rand -base64 32 # 填入 JWT_SECRET +# 数据库密码: +openssl rand -base64 24 # 填入 POSTGRES_PASSWORD(勿含 @ : /) +openssl rand -base64 24 # 填入 NEO4J_PASSWORD +# ADMIN_USER_IDS 先留空(见下「首次管理员」) + +# 3) 起服务(首次会构建镜像,约几分钟) +docker compose -f docker-compose.prod.yml up -d --build +``` + +起来后: +- 控制台: http://<服务器IP>:80 +- 网关 API: http://<服务器IP>:8080 (桌面端连这里) +- 链路追踪 UI(可选): http://<服务器IP>:16686 + +## 首次管理员 +生产模式下 `/admin` 仅 `ADMIN_USER_IDS` 白名单可进。首次: +1. 打开控制台注册第一个账号; +2. 取该账号的用户 id(注册响应里有,或 `GET /api/v1/auth/me`); +3. 填进 `.env` 的 `ADMIN_USER_IDS`,`docker compose -f docker-compose.prod.yml up -d gateway` 重启网关。 + +## 配置模型(部署后在控制台做) +LLM 模型 API key **不放 .env**,登录控制台 →「模型管理」配置: +- 对话模型(chat):在线 API(OpenAI 兼容,如 DeepSeek)或本地 vLLM/Ollama +- 向量模型(embedding):RAG 用 +平台会用 `SUNDYNIX_SECRET_KEY` 把这些 key AES 加密后存库,不落明文。 + +## 两类密钥(重要) +| 类别 | 放哪 | 例子 | +|---|---|---| +| 引导密钥 / 基建凭据 | `.env`(部署时填一次) | `SUNDYNIX_SECRET_KEY`、`JWT_SECRET`、库密码 | +| 业务模型 key | 控制台(运行时配,加密存库) | DeepSeek / Qwen / embedding 的 api_key | +`SUNDYNIX_SECRET_KEY` 是前者加密后者的主密钥,**务必备份且不可泄露**;丢失=已存模型 key 全部失效。 + +## 备份 / 恢复 +见 `scripts/backup.sh` 与 `scripts/restore.sh`(备份 PG / Neo4j / Milvus / MinIO 数据卷)。 +建议 cron 每日跑 `backup.sh`,产物存异机。 + +## 安全建议(对外开放时) +- 生产把 `CORS_ALLOW_ORIGIN` 设为具体域名,别留 `*`。 +- 基建端口(PG/Redis/NATS/Milvus/Neo4j/MinIO)默认**不对宿主暴露**,仅 compose 内网;勿手动开放。 +- 对外建议在 admin 容器前再加一层 TLS 反代(Caddy/Nginx),或用云 LB 终止 HTTPS。 +- `SUNDYNIX_SECRET_KEY`/`JWT_SECRET` 用 `openssl rand` 生成的强随机值,切勿用示例值。 + +## 运维常用 +```bash +docker compose -f docker-compose.prod.yml ps # 状态 +docker compose -f docker-compose.prod.yml logs -f gateway +docker compose -f docker-compose.prod.yml down # 停(保留数据卷) +docker compose -f docker-compose.prod.yml up -d --build gateway # 改配置后单独重启 +``` diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..20e4677 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +专有软件许可 / Proprietary License + +Copyright (c) 2026 sundynix-agentix. 保留所有权利 / All Rights Reserved. + +本软件及其源代码、文档、构建产物(统称“本软件”)为版权所有者的专有财产。 +未经版权所有者事先书面授权,任何个人或实体不得复制、修改、合并、发布、分发、 +再许可、出售本软件,或将其用于商业或生产用途。 + +获授权方(如签署商业许可/独立部署协议者)的使用范围、期限与限制,以双方另行 +签署的书面协议为准;本文件不授予任何此类协议之外的权利。 + +本软件按“现状”提供,不附任何明示或默示担保,包括但不限于适销性、特定用途 +适用性与不侵权的担保。版权所有者不对任何索赔、损害或其他责任负责。 + +--- + +This software is proprietary. No rights are granted except under a separate +written commercial/deployment agreement with the copyright holder. + +注:授权模型(专有 / 源码可见 BSL / 双授权等)为商业决策,可按需替换本文件。 diff --git a/README.md b/README.md index 4263ae2..5e915c0 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,18 @@ ## 快速开始 +### 生产 / 独立部署 — 一条命令拉起整套(应用 + 基建) + +```bash +cp .env.example .env # 按提示填密钥(openssl rand -base64 32) +docker compose -f docker-compose.prod.yml up -d --build +# 控制台 http://:80 | 网关 http://:8080 | 链路 UI http://:16686 +``` + +应用全部容器化(gateway / dispatcher / mcp-go / mcp-py / admin-nginx),经服务名互连, +基建端口不对外;模型 API key 部署后在控制台配(加密存库)。详见 **[DEPLOY.md](DEPLOY.md)**、 +备份恢复见 `scripts/backup.sh`。 + ### 无 Docker — 一键验证任务流(推荐先跑这个) ```bash diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..2e3e95a --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,183 @@ +# sundynix-agentix · 生产/独立部署编排(应用 + 基建一把拉起) +# +# cp .env.example .env # 然后按提示填密钥(见 DEPLOY.md) +# docker compose -f docker-compose.prod.yml up -d --build +# +# 设计: +# - 应用经服务名互连(postgres:5432 等),不依赖 127.0.0.1;基建端口不对宿主暴露(仅内网)。 +# - 仅暴露 admin 控制台(:${HTTP_PORT}) 与 gateway(:${GATEWAY_PORT},供桌面端直连) + Jaeger UI(可选)。 +# - 引导密钥从 .env 注入(SUNDYNIX_SECRET_KEY/JWT_SECRET/各库密码);模型 API key 仍在控制台配、加密存 PG。 +# - APP_ENV=production → 强制校验密钥、锁 CORS、启用管理员白名单。 + +x-secret-key: &secret-key ${SUNDYNIX_SECRET_KEY:?需在 .env 设置(openssl rand -base64 32)} + +services: + # ---------- 应用层 ---------- + gateway: + build: { context: ., dockerfile: sundynix-gateway/Dockerfile } + image: sundynix/gateway:${TAG:-latest} + restart: unless-stopped + environment: + APP_ENV: production + GATEWAY_ADDR: ":8080" + NATS_URL: nats://nats:4222 + POSTGRES_DSN: postgres://sundynix:${POSTGRES_PASSWORD:?}@postgres:5432/sundynix?sslmode=disable + REDIS_ADDR: redis:6379 + MINIO_ENDPOINT: minio:9000 + MINIO_ACCESS_KEY: minioadmin # minio 仅 compose 内网可达,不对宿主暴露 + MINIO_SECRET_KEY: minioadmin + SUNDYNIX_SECRET_KEY: *secret-key + JWT_SECRET: ${JWT_SECRET:?需在 .env 设置(openssl rand -base64 32)} + ADMIN_USER_IDS: ${ADMIN_USER_IDS:-} + CORS_ALLOW_ORIGIN: ${CORS_ALLOW_ORIGIN:-*} + OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4318 + ports: ["${GATEWAY_PORT:-8080}:8080"] + depends_on: + postgres: { condition: service_healthy } + redis: { condition: service_started } + nats: { condition: service_started } + + dispatcher: + build: { context: ., dockerfile: sundynix-dispatcher/Dockerfile } + image: sundynix/dispatcher:${TAG:-latest} + restart: unless-stopped + environment: + APP_ENV: production + NATS_URL: nats://nats:4222 + SUNDYNIX_SECRET_KEY: *secret-key + OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4318 + DISPATCHER_CONCURRENCY: ${DISPATCHER_CONCURRENCY:-8} + TASK_TOKEN_BUDGET: ${TASK_TOKEN_BUDGET:-200000} + depends_on: + nats: { condition: service_started } + + mcp-go: + build: { context: ., dockerfile: sundynix-mcp-go/Dockerfile } + image: sundynix/mcp-go:${TAG:-latest} + restart: unless-stopped + environment: + APP_ENV: production + NATS_URL: nats://nats:4222 + POSTGRES_DSN: postgres://sundynix:${POSTGRES_PASSWORD:?}@postgres:5432/sundynix?sslmode=disable + REDIS_ADDR: redis:6379 + MILVUS_ADDR: milvus:19530 + NEO4J_URI: neo4j://neo4j:7687 + NEO4J_USER: neo4j + NEO4J_PASS: ${NEO4J_PASSWORD:?} + SUNDYNIX_SECRET_KEY: *secret-key + OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4318 + depends_on: + nats: { condition: service_started } + milvus: { condition: service_healthy } # mcp-go 须在 Milvus 后起,否则工具 no responders + + mcp-py: + build: { context: sundynix-mcp-py, dockerfile: Dockerfile } + image: sundynix/mcp-py:${TAG:-latest} + restart: unless-stopped + environment: + NATS_URL: nats://nats:4222 + # secure_sandbox(代码隔离执行)需访问宿主 Docker 拉起一次性容器。 + # 安全敏感:等于把宿主 Docker 暴露给该服务;不需要代码执行工具可注释掉这行。 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + depends_on: + nats: { condition: service_started } + + admin: + build: { context: sundynix-admin, dockerfile: Dockerfile } + image: sundynix/admin:${TAG:-latest} + restart: unless-stopped + ports: ["${HTTP_PORT:-80}:80"] # 运维控制台 + 反代网关 API(一个 URL 访问整套) + depends_on: + gateway: { condition: service_started } + + # ---------- 基础设施(端口不对宿主暴露,仅 compose 内网) ---------- + nats: + image: nats:2-alpine + command: ["-c", "/etc/nats/nats-server.conf"] + restart: unless-stopped + volumes: + - ./deploy/nats/nats-server.conf:/etc/nats/nats-server.conf:ro + - nats_data:/data/jetstream + + postgres: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: sundynix + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?} + POSTGRES_DB: sundynix + volumes: ["pg_data:/var/lib/postgresql/data"] + healthcheck: + test: ["CMD-SHELL", "pg_isready -U sundynix"] + interval: 5s + timeout: 5s + retries: 10 + + redis: + image: redis:7-alpine + restart: unless-stopped + volumes: ["redis_data:/data"] + + jaeger: + image: jaegertracing/all-in-one:1.60 + restart: unless-stopped + environment: + COLLECTOR_OTLP_ENABLED: "true" + ports: ["${JAEGER_UI_PORT:-16686}:16686"] # 链路追踪 UI(演示可看 span 瀑布图;不需要可去掉) + + milvus-etcd: + image: quay.io/coreos/etcd:v3.5.14 + restart: unless-stopped + environment: + ETCD_AUTO_COMPACTION_MODE: revision + ETCD_AUTO_COMPACTION_RETENTION: "1000" + ETCD_QUOTA_BACKEND_BYTES: "4294967296" + ETCD_SNAPSHOT_COUNT: "50000" + command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd + volumes: ["milvus_etcd:/etcd"] + + minio: + image: minio/minio:RELEASE.2023-03-20T20-16-18Z + restart: unless-stopped + environment: + MINIO_ACCESS_KEY: minioadmin # 内网专用(不对宿主暴露端口),与 milvus/gateway 默认对齐 + MINIO_SECRET_KEY: minioadmin + command: minio server /minio_data + volumes: ["minio_data:/minio_data"] + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 30s + timeout: 20s + retries: 3 + + milvus: + image: milvusdb/milvus:v2.4.13 + command: ["milvus", "run", "standalone"] + restart: unless-stopped + environment: + ETCD_ENDPOINTS: milvus-etcd:2379 + MINIO_ADDRESS: minio:9000 + volumes: ["milvus_data:/var/lib/milvus"] + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"] + interval: 15s + timeout: 10s + retries: 10 + depends_on: [milvus-etcd, minio] + + neo4j: + image: neo4j:5-community + restart: unless-stopped + environment: + NEO4J_AUTH: neo4j/${NEO4J_PASSWORD:?} + volumes: ["neo4j_data:/data"] + +volumes: + nats_data: + pg_data: + redis_data: + milvus_etcd: + minio_data: + milvus_data: + neo4j_data: diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100755 index 0000000..8b9f3da --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# 备份 sundynix-agentix 全部有状态数据(PG / Neo4j / Milvus / MinIO / NATS-JetStream)。 +# 用法:scripts/backup.sh [输出目录] 默认 ./backups/<时间戳> +# 建议 cron 每日跑,产物拷到异机。 +set -euo pipefail + +COMPOSE="docker compose -f docker-compose.prod.yml" +TS="$(date +%Y%m%d-%H%M%S)" +OUT="${1:-./backups/$TS}" +mkdir -p "$OUT" +echo "[backup] → $OUT" + +# PostgreSQL:逻辑备份(可跨版本恢复)。 +echo "[backup] postgres…" +$COMPOSE exec -T postgres pg_dump -U sundynix sundynix | gzip > "$OUT/postgres.sql.gz" + +# Neo4j:在线 dump(社区版用 cypher-shell 导出关系;这里用 APOC/dump 兼容写法的简化版—— +# 社区版无 neo4j-admin 在线 dump,改为停服卷拷或用 cypher 导出。此处导出全图为 cypher。) +echo "[backup] neo4j…" +$COMPOSE exec -T neo4j sh -lc 'command -v neo4j-admin >/dev/null 2>&1 && neo4j-admin database dump neo4j --to-stdout 2>/dev/null' \ + > "$OUT/neo4j.dump" 2>/dev/null || echo "[backup] neo4j 在线 dump 不可用(社区版),改用卷快照见下" + +# 数据卷快照(Milvus / MinIO / Neo4j 兜底 / JetStream):用临时容器打 tar。 +backup_volume () { # $1=卷名 $2=输出文件 + local vol; vol="$($COMPOSE config --volumes | grep -E "^${1}\$" || true)" + local full="$(docker volume ls --format '{{.Name}}' | grep -E "_${1}\$" | head -1)" + [ -z "$full" ] && { echo "[backup] 卷 $1 未找到,跳过"; return; } + echo "[backup] volume $full…" + docker run --rm -v "$full":/data -v "$(cd "$OUT" && pwd)":/out alpine \ + sh -c "cd /data && tar czf /out/$2 ." +} +backup_volume milvus_data milvus_data.tar.gz +backup_volume minio_data minio_data.tar.gz +backup_volume neo4j_data neo4j_data.tar.gz +backup_volume nats_data nats_data.tar.gz + +echo "[backup] 完成。务必另存 .env(含 SUNDYNIX_SECRET_KEY,丢了模型密钥全废):cp .env $OUT/env.bak" +echo "$OUT" diff --git a/scripts/restore.sh b/scripts/restore.sh new file mode 100755 index 0000000..b38160b --- /dev/null +++ b/scripts/restore.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# 从 backup.sh 的产物恢复。用法:scripts/restore.sh <备份目录> +# 会停服 → 恢复 PG(逻辑)+ 各数据卷(覆盖)→ 重启。务必先确认目录正确,操作不可逆。 +set -euo pipefail + +COMPOSE="docker compose -f docker-compose.prod.yml" +DIR="${1:?用法: scripts/restore.sh <备份目录>}" +[ -d "$DIR" ] || { echo "目录不存在: $DIR"; exit 1; } +echo "[restore] 从 $DIR 恢复,将覆盖现有数据。Ctrl-C 取消,5s 后继续…"; sleep 5 + +echo "[restore] 停应用(保留基建跑着以便恢复 PG)…" +$COMPOSE stop gateway dispatcher mcp-go mcp-py admin || true + +# PostgreSQL:逻辑恢复(先清库再灌)。 +if [ -f "$DIR/postgres.sql.gz" ]; then + echo "[restore] postgres…" + $COMPOSE up -d postgres; sleep 5 + $COMPOSE exec -T postgres psql -U sundynix -d sundynix -c \ + "DROP SCHEMA public CASCADE; CREATE SCHEMA public;" + gunzip -c "$DIR/postgres.sql.gz" | $COMPOSE exec -T postgres psql -U sundynix -d sundynix +fi + +# 数据卷恢复(停服 → 清空卷 → 解 tar)。 +restore_volume () { # $1=卷后缀 $2=tar 文件 + [ -f "$DIR/$2" ] || { echo "[restore] 无 $2,跳过"; return; } + local full; full="$(docker volume ls --format '{{.Name}}' | grep -E "_${1}\$" | head -1)" + [ -z "$full" ] && { echo "[restore] 卷 $1 未找到,跳过"; return; } + echo "[restore] volume $full ← $2" + docker run --rm -v "$full":/data -v "$(cd "$DIR" && pwd)":/in alpine \ + sh -c "rm -rf /data/* && cd /data && tar xzf /in/$2" +} +echo "[restore] 停基建以恢复卷…"; $COMPOSE down +restore_volume milvus_data milvus_data.tar.gz +restore_volume minio_data minio_data.tar.gz +restore_volume neo4j_data neo4j_data.tar.gz +restore_volume nats_data nats_data.tar.gz + +echo "[restore] 重启全部…"; $COMPOSE -f docker-compose.prod.yml up -d +echo "[restore] 完成。若恢复到新机,记得把备份里的 env.bak 还原为 .env。" diff --git a/sundynix-admin/.dockerignore b/sundynix-admin/.dockerignore new file mode 100644 index 0000000..9c97bbd --- /dev/null +++ b/sundynix-admin/.dockerignore @@ -0,0 +1,3 @@ +node_modules +dist +.env diff --git a/sundynix-admin/Dockerfile b/sundynix-admin/Dockerfile new file mode 100644 index 0000000..fd95b6d --- /dev/null +++ b/sundynix-admin/Dockerfile @@ -0,0 +1,15 @@ +# 运维控制台:node 构建 SPA → nginx 托管 + 反代网关 API(一个 URL 访问整套)。 +# docker build -t sundynix/admin sundynix-admin +FROM node:20-alpine AS build +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY . . +# VITE_GATEWAY 留空 → 前端走相对路径 /api/...,由 nginx 反代到 gateway(同源、免跨域)。 +ENV VITE_GATEWAY="" +RUN npm run build + +FROM nginx:1.27-alpine +COPY --from=build /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 diff --git a/sundynix-admin/nginx.conf b/sundynix-admin/nginx.conf new file mode 100644 index 0000000..a09f04a --- /dev/null +++ b/sundynix-admin/nginx.conf @@ -0,0 +1,30 @@ +# 运维控制台静态托管 + 反代网关 API(一个 URL 同时给前端与 /api)。 +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # SPA 路由:找不到文件回退 index.html(前端路由接管)。 + location / { + try_files $uri $uri/ /index.html; + } + + # 网关 API:反代到 compose 网络内的 gateway 服务。 + location /api/ { + proxy_pass http://gateway:8080; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # SSE(token 流 / 执行轨迹):关闭缓冲 + 长超时,否则流式会被攒住/断开。 + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 1h; + proxy_send_timeout 1h; + } + + # 健康/指标直通(运维探活)。 + location = /healthz { proxy_pass http://gateway:8080; } + location = /readyz { proxy_pass http://gateway:8080; } +} diff --git a/sundynix-dispatcher/Dockerfile b/sundynix-dispatcher/Dockerfile new file mode 100644 index 0000000..ea3fdab --- /dev/null +++ b/sundynix-dispatcher/Dockerfile @@ -0,0 +1,18 @@ +# 第 4 层 编排调度。多阶段构建(上下文须为仓库根,因 replace ../sundynix-shared): +# docker build -f sundynix-dispatcher/Dockerfile -t sundynix/dispatcher . +FROM golang:1.25-alpine AS build +WORKDIR /src +COPY sundynix-shared/go.mod sundynix-shared/go.sum ./sundynix-shared/ +COPY sundynix-dispatcher/go.mod sundynix-dispatcher/go.sum ./sundynix-dispatcher/ +WORKDIR /src/sundynix-dispatcher +ENV GOWORK=off GOFLAGS=-mod=mod +RUN go mod download +WORKDIR /src +COPY sundynix-shared/ ./sundynix-shared/ +COPY sundynix-dispatcher/ ./sundynix-dispatcher/ +WORKDIR /src/sundynix-dispatcher +RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/dispatcher ./cmd/dispatcher + +FROM gcr.io/distroless/static-debian12:nonroot +COPY --from=build /out/dispatcher /dispatcher +ENTRYPOINT ["/dispatcher"] diff --git a/sundynix-gateway/Dockerfile b/sundynix-gateway/Dockerfile new file mode 100644 index 0000000..4433741 --- /dev/null +++ b/sundynix-gateway/Dockerfile @@ -0,0 +1,22 @@ +# 第 2 层 业务网关。多阶段构建:编译静态二进制 → distroless 最小运行镜像。 +# 构建上下文须为「仓库根」(因 go.mod 用 replace ../sundynix-shared 引本地共享模块): +# docker build -f sundynix-gateway/Dockerfile -t sundynix/gateway . +FROM golang:1.25-alpine AS build +WORKDIR /src +# 先拷依赖清单分层缓存(命中则跳过重下) +COPY sundynix-shared/go.mod sundynix-shared/go.sum ./sundynix-shared/ +COPY sundynix-gateway/go.mod sundynix-gateway/go.sum ./sundynix-gateway/ +WORKDIR /src/sundynix-gateway +ENV GOWORK=off GOFLAGS=-mod=mod +RUN go mod download +# 再拷源码编译 +WORKDIR /src +COPY sundynix-shared/ ./sundynix-shared/ +COPY sundynix-gateway/ ./sundynix-gateway/ +WORKDIR /src/sundynix-gateway +RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/gateway ./cmd/server + +FROM gcr.io/distroless/static-debian12:nonroot +COPY --from=build /out/gateway /gateway +EXPOSE 8080 +ENTRYPOINT ["/gateway"] diff --git a/sundynix-mcp-go/Dockerfile b/sundynix-mcp-go/Dockerfile new file mode 100644 index 0000000..e94d3a8 --- /dev/null +++ b/sundynix-mcp-go/Dockerfile @@ -0,0 +1,18 @@ +# 第 5 层 Go I/O 型 MCP 工具(检索/入库/报告渲染等)。多阶段构建(上下文须为仓库根): +# docker build -f sundynix-mcp-go/Dockerfile -t sundynix/mcp-go . +FROM golang:1.25-alpine AS build +WORKDIR /src +COPY sundynix-shared/go.mod sundynix-shared/go.sum ./sundynix-shared/ +COPY sundynix-mcp-go/go.mod sundynix-mcp-go/go.sum ./sundynix-mcp-go/ +WORKDIR /src/sundynix-mcp-go +ENV GOWORK=off GOFLAGS=-mod=mod +RUN go mod download +WORKDIR /src +COPY sundynix-shared/ ./sundynix-shared/ +COPY sundynix-mcp-go/ ./sundynix-mcp-go/ +WORKDIR /src/sundynix-mcp-go +RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/mcp-go ./cmd/server + +FROM gcr.io/distroless/static-debian12:nonroot +COPY --from=build /out/mcp-go /mcp-go +ENTRYPOINT ["/mcp-go"] diff --git a/sundynix-mcp-py/.dockerignore b/sundynix-mcp-py/.dockerignore new file mode 100644 index 0000000..c3307ba --- /dev/null +++ b/sundynix-mcp-py/.dockerignore @@ -0,0 +1,4 @@ +.venv +__pycache__ +**/__pycache__ +*.pyc diff --git a/sundynix-mcp-py/Dockerfile b/sundynix-mcp-py/Dockerfile new file mode 100644 index 0000000..2aced44 --- /dev/null +++ b/sundynix-mcp-py/Dockerfile @@ -0,0 +1,10 @@ +# 第 5 层 Python 算法型 MCP 工具(代码执行/文档解析等)。 +# docker build -f sundynix-mcp-py/Dockerfile -t sundynix/mcp-py sundynix-mcp-py +# 注:secure_sandbox(代码隔离执行)需访问宿主 Docker —— compose 里挂 /var/run/docker.sock。 +FROM python:3.11-slim +WORKDIR /app +# 先装依赖(利用层缓存) +COPY pyproject.toml ./ +COPY src/ ./src/ +RUN pip install --no-cache-dir . && rm -rf /root/.cache +ENTRYPOINT ["python", "-m", "sundynix_mcp_py.main"]