fix(security): 收紧默认部署暴露面
This commit is contained in:
+19
-14
@@ -4,9 +4,8 @@
|
|||||||
#############################################
|
#############################################
|
||||||
|
|
||||||
# ========== 1. 访问地址(部署时必改) ==========
|
# ========== 1. 访问地址(部署时必改) ==========
|
||||||
# 默认服务器 API 地址,云服务需修改为云端 IP 并放行对应端口
|
# 浏览器统一通过 Nginx 同源访问 API
|
||||||
NUXT_PUBLIC_BASE_APIURL=http://127.0.0.1:3001
|
NUXT_PUBLIC_BASE_APIURL=/api
|
||||||
# 域名访问:NUXT_PUBLIC_BASE_APIURL=/api
|
|
||||||
|
|
||||||
# 后端生成文件、预签名地址和异步任务查询地址时使用的公开 API 根地址。
|
# 后端生成文件、预签名地址和异步任务查询地址时使用的公开 API 根地址。
|
||||||
# 留空时启动/升级脚本会根据本节已有配置初始化;手工维护时必须填写完整 http(s) URL。
|
# 留空时启动/升级脚本会根据本节已有配置初始化;手工维护时必须填写完整 http(s) URL。
|
||||||
@@ -15,23 +14,28 @@ CONFIG_PUBLIC_API_BASE_URL=
|
|||||||
# 示例:https://api.example.com:8443/api
|
# 示例:https://api.example.com:8443/api
|
||||||
DEPLOY_PUBLIC_API_BASE_URL=
|
DEPLOY_PUBLIC_API_BASE_URL=
|
||||||
|
|
||||||
# 默认 WebSocket 地址
|
# 默认 WebSocket 地址;部署脚本会替换为 ws(s)://IP或域名/socket.io
|
||||||
NUXT_PUBLIC_BASE_SOCKETURL=ws://127.0.0.1:3002
|
NUXT_PUBLIC_BASE_SOCKETURL=ws://127.0.0.1/socket.io
|
||||||
# 域名访问:NUXT_PUBLIC_BASE_SOCKETURL=wss://yourwebsite.com/socket.io
|
|
||||||
|
|
||||||
# Agent 服务治理 API 地址,前端管理页面需要
|
# Agent 服务治理 API 地址,前端管理页面需要
|
||||||
NUXT_PUBLIC_SG_APIURL=http://127.0.0.1:3003
|
NUXT_PUBLIC_SG_APIURL=/asg-api
|
||||||
# 域名访问:NUXT_PUBLIC_SG_APIURL=/asg-api
|
|
||||||
|
|
||||||
# ========== 2. 服务端口 ==========
|
# ========== 2. 服务端口 ==========
|
||||||
WEB_PORT=3010
|
WEB_PORT=3010
|
||||||
# easyai-web 前端端口,访问地址 ip:3010,初始化管理员 admin/123456
|
# easyai-web 上游端口,由 Nginx 在 80 端口统一代理
|
||||||
|
|
||||||
|
EASYAI_PROXY_BIND_IP=127.0.0.1
|
||||||
|
# Nginx 上游服务的宿主机绑定地址。Linux IP/域名部署保持回环。
|
||||||
|
|
||||||
|
EASYAI_INFRA_BIND_IP=127.0.0.1
|
||||||
|
# MongoDB、RabbitMQ、Dozzle、视频编辑、Agent Memory 等基础设施端口默认仅允许宿主机访问。
|
||||||
|
# 仅在受控内网且有额外防火墙时才可显式改为其他地址,禁止使用 0.0.0.0 暴露到公网。
|
||||||
|
|
||||||
SERVER_HTTP_PORT=3001
|
SERVER_HTTP_PORT=3001
|
||||||
# easyai-server 后端 HTTP 端口
|
# easyai-server 后端 HTTP 端口
|
||||||
|
|
||||||
SERVER_HTTP_BIND_IP=127.0.0.1
|
SERVER_HTTP_BIND_IP=127.0.0.1
|
||||||
# easyai-server 宿主机绑定地址。域名/本机部署保持 127.0.0.1;仅 IP/LAN 直连模式使用 0.0.0.0
|
# easyai-server 宿主机绑定地址。Linux IP/域名部署经 Nginx 访问,保持 127.0.0.1
|
||||||
|
|
||||||
NODE_MAX_OLD_SPACE_SIZE=8192
|
NODE_MAX_OLD_SPACE_SIZE=8192
|
||||||
# easyai-server 的 Node.js V8 old-space 堆上限,单位 MiB
|
# easyai-server 的 Node.js V8 old-space 堆上限,单位 MiB
|
||||||
@@ -135,7 +139,7 @@ CONFIG_AUDIT_INTEGRITY_KEY=
|
|||||||
|
|
||||||
# 允许访问后端的浏览器 Origin,多个值用英文逗号分隔,生产环境禁止使用 *。
|
# 允许访问后端的浏览器 Origin,多个值用英文逗号分隔,生产环境禁止使用 *。
|
||||||
# 域名部署示例:https://yourwebsite.com,https://www.yourwebsite.com
|
# 域名部署示例:https://yourwebsite.com,https://www.yourwebsite.com
|
||||||
CONFIG_SECURITY_ORIGIN=http://127.0.0.1:3010,http://localhost:3010
|
CONFIG_SECURITY_ORIGIN=http://127.0.0.1,http://localhost
|
||||||
|
|
||||||
# 可信反向代理 IP/CIDR,多个值用英文逗号分隔;未使用反向代理时保持为空。
|
# 可信反向代理 IP/CIDR,多个值用英文逗号分隔;未使用反向代理时保持为空。
|
||||||
# 仅填写实际 Nginx/负载均衡地址,不要使用 0.0.0.0/0。
|
# 仅填写实际 Nginx/负载均衡地址,不要使用 0.0.0.0/0。
|
||||||
@@ -149,7 +153,8 @@ CONFIG_AUTH_UNIQUE_INDEXES_ENABLED=false
|
|||||||
# admin 创建后不再使用该值;修改 .env 或执行升级不会重置现有管理员密码。
|
# admin 创建后不再使用该值;修改 .env 或执行升级不会重置现有管理员密码。
|
||||||
CONFIG_INITIAL_ADMIN_PASSWORD=
|
CONFIG_INITIAL_ADMIN_PASSWORD=
|
||||||
|
|
||||||
CONFIG_TOKEN_SIGN_SK=easyai2025easyai
|
# 海淀教育平台旧 SSO 专用密钥(必须为随机的 16 或 32 字节;不用该集成则保持为空)
|
||||||
|
CONFIG_TOKEN_SIGN_SK=
|
||||||
|
|
||||||
# ========== 9. 运维与调试 ==========
|
# ========== 9. 运维与调试 ==========
|
||||||
CONFIG_INSTANCE_ID=2025
|
CONFIG_INSTANCE_ID=2025
|
||||||
@@ -202,8 +207,8 @@ WS_GATEWAY_TCP_TIMEOUT_MS=1500
|
|||||||
# ========== 12. ws-gateway 转发客户端命令到 easyai-server(TCP) ==========
|
# ========== 12. ws-gateway 转发客户端命令到 easyai-server(TCP) ==========
|
||||||
# 拖动画布、协作 presence 等客户端命令由 ws-gateway 转发到 easyai-server 处理
|
# 拖动画布、协作 presence 等客户端命令由 ws-gateway 转发到 easyai-server 处理
|
||||||
GATEWAY_INBOUND_ENABLED=true
|
GATEWAY_INBOUND_ENABLED=true
|
||||||
# easyai-server 监听地址;容器内应监听 0.0.0.0,供 ws-gateway 访问
|
# easyai-server 只在主业务网络固定地址监听,避免 Sandbox 网络访问内部命令入口
|
||||||
GATEWAY_INBOUND_TCP_LISTEN_HOST=0.0.0.0
|
GATEWAY_INBOUND_TCP_LISTEN_HOST=172.21.0.6
|
||||||
# ws-gateway 访问 easyai-server 的 TCP 地址;Docker 部署必须使用服务名,不能用 127.0.0.1
|
# ws-gateway 访问 easyai-server 的 TCP 地址;Docker 部署必须使用服务名,不能用 127.0.0.1
|
||||||
GATEWAY_INBOUND_TCP_HOST=easyai-server
|
GATEWAY_INBOUND_TCP_HOST=easyai-server
|
||||||
# ws-gateway 到 easyai-server 的命令入口端口,对应主服务 HTTP 端口 3001
|
# ws-gateway 到 easyai-server 的命令入口端口,对应主服务 HTTP 端口 3001
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ DEPLOY_ACCESS=domain DEPLOY_DOMAIN=demo.example.com DEPLOY_HTTPS=false ./start.s
|
|||||||
DEPLOY_ACCESS=domain DEPLOY_DOMAIN=demo.example.com DEPLOY_HTTPS=true CERTBOT_EMAIL=ops@example.com ./start.sh
|
DEPLOY_ACCESS=domain DEPLOY_DOMAIN=demo.example.com DEPLOY_HTTPS=true CERTBOT_EMAIL=ops@example.com ./start.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Linux IP 模式会自动安装并配置 Nginx,浏览器统一访问 `http://IP`。公网只需放行 TCP 80;前端、API、WebSocket 和 ASG 分别通过 `/`、`/api`、`/socket.io`、`/asg-api` 反向代理,容器上游端口默认仅绑定 `127.0.0.1`。
|
||||||
|
|
||||||
常用变量:
|
常用变量:
|
||||||
|
|
||||||
| 变量 | 说明 |
|
| 变量 | 说明 |
|
||||||
@@ -61,7 +63,7 @@ DEPLOY_ACCESS=domain DEPLOY_DOMAIN=demo.example.com DEPLOY_HTTPS=true CERTBOT_EM
|
|||||||
2. 来自可信代理的当前请求地址(仅当请求来源命中 `CONFIG_TRUSTED_PROXY` 时读取转发 Header)。
|
2. 来自可信代理的当前请求地址(仅当请求来源命中 `CONFIG_TRUSTED_PROXY` 时读取转发 Header)。
|
||||||
3. 当前请求的直接 `Host`。
|
3. 当前请求的直接 `Host`。
|
||||||
|
|
||||||
启动和升级脚本会在拉取镜像、重建服务之前校验并持久化 `CONFIG_PUBLIC_API_BASE_URL`:新 IP 部署使用 `http://IP:${SERVER_HTTP_PORT}`;域名部署中 `DEPLOY_HTTPS=false` 使用 `http/ws`,`DEPLOY_HTTPS=true` 使用 `https/wss`,API 路径均为 `/api`。旧环境缺少该配置时,会依次从绝对 `NUXT_PUBLIC_BASE_APIURL`、WebSocket origin、首个 `CONFIG_SECURITY_ORIGIN` 推导。已有合法值保持不变,非法或无法推导时脚本会终止并提示修复。
|
启动和升级脚本会在拉取镜像、重建服务之前校验并持久化 `CONFIG_PUBLIC_API_BASE_URL`:新 IP 部署使用 `http://IP/api`;域名部署中 `DEPLOY_HTTPS=false` 使用 `http/ws`,`DEPLOY_HTTPS=true` 使用 `https/wss`。IP 与域名模式的浏览器接口路径统一为 `/api`、`/socket.io`、`/asg-api`,只由 Nginx 监听地址和公开协议决定 origin。旧环境缺少该配置时,会依次从绝对 `NUXT_PUBLIC_BASE_APIURL`、WebSocket origin、首个 `CONFIG_SECURITY_ORIGIN` 推导。已有合法值保持不变,非法或无法推导时脚本会终止并提示修复。
|
||||||
|
|
||||||
已有外部 TLS、手工代理、自定义公开端口或特殊路径时,可在执行脚本前设置高级覆盖:
|
已有外部 TLS、手工代理、自定义公开端口或特殊路径时,可在执行脚本前设置高级覆盖:
|
||||||
|
|
||||||
@@ -477,32 +479,15 @@ curl https://<你的域名>/asg-api/health
|
|||||||
SANDBOX_SERVICE_BASE_URL= #脚本运行环境使用的服务地址,默认通过 Docker 内网访问 http://sandbox:8000,不需要配置
|
SANDBOX_SERVICE_BASE_URL= #脚本运行环境使用的服务地址,默认通过 Docker 内网访问 http://sandbox:8000,不需要配置
|
||||||
```
|
```
|
||||||
|
|
||||||
> 安全默认值:Sandbox API 与 JupyterLab 不映射宿主机端口,Nginx 模板对 `/api/sandbox`、`/sandbox`、`/jupyterlab` 返回 404。不要在公网重新开放这些高权限入口。
|
> 安全默认值:Sandbox API 与 JupyterLab 不映射宿主机端口,Nginx 模板对 `/api/sandbox`、`/sandbox`、`/jupyterlab` 返回 404。Sandbox 使用独立 Docker 网络,不能访问 MongoDB、Redis、RabbitMQ 与治理服务。不要在公网重新开放这些高权限入口,也不要把 Sandbox 接回主业务网络。
|
||||||
|
|
||||||
|
|
||||||
### 2025.1.29
|
### 2025.1.29
|
||||||
|
|
||||||
1. 优化日志管理功能,使用单独的容器和模块来进行管理,不占用主进程文件写入性能
|
1. 优化日志管理功能,使用单独的容器和模块来进行管理,不占用主进程文件写入性能
|
||||||
2. 更新步骤:
|
2. Dozzle 端口默认绑定 `127.0.0.1`,Nginx 对 `/logs-web` 返回 404,禁止把容器日志查看器直接代理到公网。
|
||||||
- 本次`docker-compose.yml`新增dozzle容器,将`docker-compose.yml`文件更新到最新
|
3. 需要查看日志时,在受信任终端建立 SSH 隧道后访问本机端口,或直接使用 `docker logs`。不要依赖 Dozzle 的无认证模式或默认口令。
|
||||||
- 在nginx配置文件中(/etc/nginx/conf.d/easyai-proxy.conf)添加如下配置,具体添加位置参考`easyai-proxy.conf.sample`
|
4. 日志下载功能默认关闭,避免容器日志被批量导出。
|
||||||
```nginx configuration
|
|
||||||
location /logs-web/ {
|
|
||||||
proxy_pass http://127.0.0.1:8080/logs-web/;
|
|
||||||
proxy_redirect off;
|
|
||||||
proxy_set_header X-Original-Prefix '/logs-web';
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
3. 默认不启用密码(有风险⚠️,别人可以任意访问你的应用日志)。如果启用密码,取消`docker-compose.yml`中的`DOZZLE_AUTH_PROVIDER: simple`的注释,默认密码为`123456`,用户名为`admin`。
|
|
||||||
4. 修改默认密码,使用下面的命令生成密码:(替换admin和密码为实际的用户名和密码)
|
|
||||||
```bash
|
|
||||||
docker run -it --rm registry.cn-shanghai.aliyuncs.com/easyaigc/dozzle:latest generate admin --password 密码
|
|
||||||
```
|
|
||||||
5. 用生成的密码填入/data/users.yml中的password,并保存后,密码即刻生效
|
|
||||||
|
|
||||||
|
|
||||||
### 2025.12.27
|
### 2025.12.27
|
||||||
@@ -598,22 +583,16 @@ git clone --depth 1 https://git.51easyai.com/wangbo/easyai.git
|
|||||||
cd easyai
|
cd easyai
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 去掉所有文件后缀的.sample后缀,修改env中环境变量,将下面两个环境变量修改为你的服务器的公网ip,本地局域网方式使用局域网IP
|
2. 在安全组或防火墙中仅开放 TCP 80 端口。脚本会自动生成 `.env` 和完整 Nginx 配置,接口统一走 `/api`、`/socket.io`、`/asg-api`。
|
||||||
```bash
|
3. [首次执行,后续无需重复执行]添加脚本的执行权限,命令:
|
||||||
NUXT_PUBLIC_BASE_APIURL=http://<你的服务器公网IP或者局域网地址>:3001
|
|
||||||
NUXT_PUBLIC_BASE_SOCKETURL=ws://<你的服务器公网IP或者局域网地址>:3002
|
|
||||||
SERVER_HTTP_BIND_IP=0.0.0.0
|
|
||||||
```
|
|
||||||
3. 开放服务器的3001、3002和3010端口
|
|
||||||
4. [首次执行,后续无需重复执行]添加脚本的执行权限,命令:
|
|
||||||
```bash
|
```bash
|
||||||
chmod +x start.sh
|
chmod +x start.sh
|
||||||
```
|
```
|
||||||
5. 执行start.sh脚本,即可自动安装依赖和启动EasyAI应用
|
4. 执行start.sh脚本,选择 IP 模式并输入服务器公网 IP,即可自动安装依赖、启动 EasyAI 并配置 Nginx
|
||||||
```bash
|
```bash
|
||||||
./start.sh
|
./start.sh
|
||||||
```
|
```
|
||||||
6. 脚本运行完成无错误,并且提示`EasyAI应用启动成功`表示应用启动成功,打开浏览器输入服务器的公网ip:3010或者局域网IP:3010,即可访问EasyAI应用
|
5. 脚本运行完成无错误后,打开浏览器访问 `http://服务器IP`。
|
||||||
|
|
||||||
## Windows 一键启动
|
## Windows 一键启动
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
map $http_upgrade $connection_upgrade {
|
||||||
|
default upgrade;
|
||||||
|
'' close;
|
||||||
|
}
|
||||||
|
|
||||||
|
# www 域名重定向,使用二级域名注释掉此部分的域名重定向
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name www.demo.51easyai.com;
|
||||||
|
|
||||||
|
# 添加 HSTS 策略
|
||||||
|
# add_header Strict-Transport-Security "max-age=31536000" always;
|
||||||
|
|
||||||
|
# ssl 版本控制
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3; # 只启用 TLS 1.2 和 TLS 1.3
|
||||||
|
|
||||||
|
# 用于 certbot 验证的配置
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 更严格的重定向规则
|
||||||
|
location / {
|
||||||
|
return 301 https://demo.51easyai.com$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name demo.51easyai.com;
|
||||||
|
|
||||||
|
# EasyAI response gzip: compress JSON/text API responses; SSE/WebSocket stay uncompressed by content type.
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
gzip_comp_level 4;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_types
|
||||||
|
application/json
|
||||||
|
application/javascript
|
||||||
|
application/xml
|
||||||
|
text/css
|
||||||
|
text/plain
|
||||||
|
text/xml
|
||||||
|
image/svg+xml;
|
||||||
|
|
||||||
|
# ssl 版本控制
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3; # 只启用 TLS 1.2 和 TLS 1.3
|
||||||
|
# 通用安全头部
|
||||||
|
add_header X-Content-Type-Options nosniff;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN";
|
||||||
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
|
# 用于 certbot 验证的配置
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 专门处理企业微信验证文件的请求
|
||||||
|
# location = /WW_verify_9eWb5ozYfqE0Bb2i.txt {
|
||||||
|
# default_type text/plain; # 设置正确的Content-Type
|
||||||
|
# return 200 '9eWb5ozYfqE0Bb2i'; # 直接返回验证内容
|
||||||
|
# }
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:3010/;
|
||||||
|
proxy_redirect off; # 修改重定向处理
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /api/sandbox {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/sandbox/ {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /api/integration/platform-api/execute {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 旧教育平台 SSO 仅在双方完成独立密钥配置后,才允许显式开启。
|
||||||
|
location = /api/auth/sign/token {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /api/auth/verify/token {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:3001/;
|
||||||
|
proxy_read_timeout 1200s; # 您已设置,适合长连接
|
||||||
|
client_max_body_size 100M;
|
||||||
|
proxy_redirect off;
|
||||||
|
|
||||||
|
# --- SSE 关键配置开始 ---
|
||||||
|
proxy_buffering off; # 必须:关闭代理缓冲,数据会立即从后端发送到客户端
|
||||||
|
proxy_cache off; # 必须:禁用缓存,SSE是动态内容
|
||||||
|
proxy_set_header Connection ''; # 清除 Connection header,有助于保持长连接
|
||||||
|
proxy_http_version 1.1; # 推荐使用 HTTP/1.1
|
||||||
|
proxy_set_header Accept-Encoding ''; # 可选:防止上游压缩导致缓冲,如果后端有gzip等压缩,可能会影响流式输出
|
||||||
|
# --- SSE 关键配置结束 ---
|
||||||
|
proxy_set_header X-Original-Prefix '/api';
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /asg-api/ {
|
||||||
|
proxy_pass http://127.0.0.1:3003/;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
client_max_body_size 20M;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_set_header X-Original-Prefix '/asg-api';
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Agent 记忆服务 API(可选,用于健康检查或外部调用)
|
||||||
|
location /ams-api/ {
|
||||||
|
proxy_pass http://127.0.0.1:3004/;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
client_max_body_size 20M;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_set_header X-Original-Prefix '/ams-api';
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /plugins/ {
|
||||||
|
proxy_pass http://127.0.0.1:3020/plugins/;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_set_header X-Original-Prefix '/plugins';
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
location = /logs-web {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /logs-web/ {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
location = /jupyterlab {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /jupyterlab/ {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
location /socket.io {
|
||||||
|
proxy_pass http://127.0.0.1:3002;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_buffering off; # 对于 WebSocket 连接禁用缓冲
|
||||||
|
}
|
||||||
|
# 转发/mcp/messages 消息到3001
|
||||||
|
location /mcp {
|
||||||
|
proxy_pass http://127.0.0.1:3001/mcp;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_buffering off; # 对于 WebSocket 连接禁用缓冲
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /sandbox {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /sandbox/ {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,18 +3,18 @@
|
|||||||
|
|
||||||
1. 通过IP地址还是通过域名访问?
|
1. 通过IP地址还是通过域名访问?
|
||||||
|
|
||||||
2. (如果通过IP地址访问),输入服务器IP,并保证3001,3002,3003三个端口已经开放
|
2. (如果通过IP地址访问),输入服务器 IP,并保证 80 端口已经开放
|
||||||
|
|
||||||
3. 如果通过域名访问输入域名,不含https://的前缀,例如:51easyai.com
|
3. 如果通过域名访问输入域名,不含https://的前缀,例如:51easyai.com
|
||||||
|
|
||||||
3.1 是否启用https访问
|
3.1 是否启用https访问
|
||||||
|
|
||||||
4. 对于IP地址访问的情形,复制`.env.sample`为`.env`,并将NUXT_PUBLIC_BASE_APIURL、NUXT_PUBLIC_BASE_SOCKETURL、NUXT_PUBLIC_SG_APIURL三个分别进行如下设置
|
4. IP 与域名访问共用相同的 Nginx 接口路径,只根据公开地址选择 WebSocket 协议和 Host
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
NUXT_PUBLIC_BASE_APIURL=http://<用户输入的IP地址>:3001
|
NUXT_PUBLIC_BASE_APIURL=/api
|
||||||
NUXT_PUBLIC_BASE_SOCKETURL=ws://<用户输入的IP地址>:3002
|
NUXT_PUBLIC_BASE_SOCKETURL=ws://<用户输入的IP地址>/socket.io
|
||||||
NUXT_PUBLIC_SG_APIURL=http://<用户输入的IP地址>:3003
|
NUXT_PUBLIC_SG_APIURL=/asg-api
|
||||||
```
|
```
|
||||||
5. 对于使用域名的情况下情况,复制`.env.sampla`为`.env`,将上述3个变量设置为:
|
5. 对于使用域名的情况下情况,复制`.env.sampla`为`.env`,将上述3个变量设置为:
|
||||||
```bash
|
```bash
|
||||||
@@ -25,7 +25,7 @@ NUXT_PUBLIC_SG_APIURL=/asg-api
|
|||||||
|
|
||||||
6. 复制 `.env.tools.sample`为`.env.tools`,复制`.env.ASG.sample`为`.env.ASG.sample`
|
6. 复制 `.env.tools.sample`为`.env.tools`,复制`.env.ASG.sample`为`.env.ASG.sample`
|
||||||
|
|
||||||
7. 对于使用域名的情况下,将`easyai-proxy.conf.sample`复制为`easyai-proxy.conf`,并将文件名修改为`用户输入的域名.conf`,并将`51easyai.com`替换为用户的域名
|
7. IP 与域名模式都从 `easyai-proxy.conf.sample` 生成完整 Nginx 配置;IP 模式监听对应 IP 的 HTTP 80,域名模式监听域名并按需启用 HTTPS
|
||||||
|
|
||||||
8. 执行原来的start脚本内容,包括安装docker安装和部署
|
8. 执行原来的start脚本内容,包括安装docker安装和部署
|
||||||
|
|
||||||
|
|||||||
+20
-18
@@ -10,7 +10,7 @@ services:
|
|||||||
labels:
|
labels:
|
||||||
- "com.centurylinklabs.watchtower.enable=true"
|
- "com.centurylinklabs.watchtower.enable=true"
|
||||||
ports:
|
ports:
|
||||||
- "${WEB_PORT}:3010"
|
- "${EASYAI_PROXY_BIND_IP:-127.0.0.1}:${WEB_PORT}:3010"
|
||||||
# read_only: true
|
# read_only: true
|
||||||
networks:
|
networks:
|
||||||
easyai:
|
easyai:
|
||||||
@@ -55,11 +55,12 @@ services:
|
|||||||
labels:
|
labels:
|
||||||
- "com.centurylinklabs.watchtower.enable=true"
|
- "com.centurylinklabs.watchtower.enable=true"
|
||||||
ports:
|
ports:
|
||||||
- "${SERVER_HTTP_BIND_IP:-127.0.0.1}:${SERVER_HTTP_PORT}:3001" # 域名/本机部署默认仅回环;IP/LAN 模式由启动脚本显式设为 0.0.0.0
|
- "${SERVER_HTTP_BIND_IP:-127.0.0.1}:${SERVER_HTTP_PORT}:3001" # Linux Nginx 部署默认仅回环;Windows/LAN 直连由启动脚本显式覆盖
|
||||||
# read_only: true
|
# read_only: true
|
||||||
networks:
|
networks:
|
||||||
easyai:
|
easyai:
|
||||||
ipv4_address: 172.21.0.6
|
ipv4_address: 172.21.0.6
|
||||||
|
sandbox-network:
|
||||||
depends_on:
|
depends_on:
|
||||||
- mongo
|
- mongo
|
||||||
- redis
|
- redis
|
||||||
@@ -120,7 +121,8 @@ services:
|
|||||||
- ASG_TCP_PORT=4003
|
- ASG_TCP_PORT=4003
|
||||||
# ws-gateway 转发客户端命令到 easyai-server(画布协作等),对应主服务 3001
|
# ws-gateway 转发客户端命令到 easyai-server(画布协作等),对应主服务 3001
|
||||||
- GATEWAY_INBOUND_ENABLED=${GATEWAY_INBOUND_ENABLED:-true}
|
- GATEWAY_INBOUND_ENABLED=${GATEWAY_INBOUND_ENABLED:-true}
|
||||||
- GATEWAY_INBOUND_TCP_LISTEN_HOST=${GATEWAY_INBOUND_TCP_LISTEN_HOST:-0.0.0.0}
|
# 仅监听主业务网络固定地址,避免沙箱网络访问内部 TCP 命令入口
|
||||||
|
- GATEWAY_INBOUND_TCP_LISTEN_HOST=${GATEWAY_INBOUND_TCP_LISTEN_HOST:-172.21.0.6}
|
||||||
- GATEWAY_INBOUND_TCP_PORT=${GATEWAY_INBOUND_TCP_PORT:-4001}
|
- GATEWAY_INBOUND_TCP_PORT=${GATEWAY_INBOUND_TCP_PORT:-4001}
|
||||||
# Agent 记忆服务 TCP 连接(来自 .env)
|
# Agent 记忆服务 TCP 连接(来自 .env)
|
||||||
- MEMORY_TCP_HOST=${MEMORY_TCP_HOST:-agent-memory}
|
- MEMORY_TCP_HOST=${MEMORY_TCP_HOST:-agent-memory}
|
||||||
@@ -149,7 +151,7 @@ services:
|
|||||||
labels:
|
labels:
|
||||||
- "com.centurylinklabs.watchtower.enable=true"
|
- "com.centurylinklabs.watchtower.enable=true"
|
||||||
ports:
|
ports:
|
||||||
- "${CONFIG_WS_PORT:-3002}:3002" # ws-gateway WebSocket 对外端口映射
|
- "${EASYAI_PROXY_BIND_IP:-127.0.0.1}:${CONFIG_WS_PORT:-3002}:3002" # Linux IP/域名模式默认仅供同机 Nginx 访问
|
||||||
read_only: true
|
read_only: true
|
||||||
networks:
|
networks:
|
||||||
- easyai
|
- easyai
|
||||||
@@ -195,10 +197,9 @@ services:
|
|||||||
image: ${MONGO_IMAGE:-registry.cn-shanghai.aliyuncs.com/comfy-ai/mongo-aliyun:latest}
|
image: ${MONGO_IMAGE:-registry.cn-shanghai.aliyuncs.com/comfy-ai/mongo-aliyun:latest}
|
||||||
container_name: mongo
|
container_name: mongo
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
privileged: true
|
|
||||||
# 生产环境建议不要暴露
|
# 生产环境建议不要暴露
|
||||||
ports:
|
ports:
|
||||||
- ${MONGO_PORT+${MONGO_PORT}:27017}
|
- "${EASYAI_INFRA_BIND_IP:-127.0.0.1}:${MONGO_PORT:-27017}:27017"
|
||||||
networks:
|
networks:
|
||||||
easyai:
|
easyai:
|
||||||
ipv4_address: 172.21.0.3
|
ipv4_address: 172.21.0.3
|
||||||
@@ -257,8 +258,8 @@ services:
|
|||||||
RABBITMQ_DEFAULT_USER: ${CONFIG_MQ_USER} # 登录用户名
|
RABBITMQ_DEFAULT_USER: ${CONFIG_MQ_USER} # 登录用户名
|
||||||
RABBITMQ_DEFAULT_PASS: ${CONFIG_MQ_PASSWORD} # 登录密码
|
RABBITMQ_DEFAULT_PASS: ${CONFIG_MQ_PASSWORD} # 登录密码
|
||||||
ports:
|
ports:
|
||||||
- "${CONFIG_MQ_PORT}:${CONFIG_MQ_PORT}" # 应用连接端口 (AMQP)
|
- "${EASYAI_INFRA_BIND_IP:-127.0.0.1}:${CONFIG_MQ_PORT}:${CONFIG_MQ_PORT}" # 应用连接端口 (AMQP)
|
||||||
- "${CONFIG_MQ_ADMIN_PORT}:15672" # Web 管理界面
|
- "${EASYAI_INFRA_BIND_IP:-127.0.0.1}:${CONFIG_MQ_ADMIN_PORT}:15672" # Web 管理界面
|
||||||
volumes:
|
volumes:
|
||||||
- rabbitmq_data:/var/lib/rabbitmq # 持久化数据
|
- rabbitmq_data:/var/lib/rabbitmq # 持久化数据
|
||||||
networks:
|
networks:
|
||||||
@@ -268,10 +269,8 @@ services:
|
|||||||
image: registry.cn-shanghai.aliyuncs.com/comfy-ai/watchtower-aliyun:latest
|
image: registry.cn-shanghai.aliyuncs.com/comfy-ai/watchtower-aliyun:latest
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
command: --debug --http-api-update
|
command: --debug
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
|
||||||
- WATCHTOWER_HTTP_API_TOKEN=sk-ofJoA4atj9dki6AvDtI1qwxdcejjDQ
|
|
||||||
labels:
|
labels:
|
||||||
- "com.centurylinklabs.watchtower.enable=false"
|
- "com.centurylinklabs.watchtower.enable=false"
|
||||||
# ports:
|
# ports:
|
||||||
@@ -295,7 +294,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./data/videoedit/temp:/app/temp
|
- ./data/videoedit/temp:/app/temp
|
||||||
ports:
|
ports:
|
||||||
- '${VIDEO_EDIT_PORT}:8000'
|
- '${EASYAI_INFRA_BIND_IP:-127.0.0.1}:${VIDEO_EDIT_PORT}:8000'
|
||||||
env_file:
|
env_file:
|
||||||
- .env.tools
|
- .env.tools
|
||||||
environment:
|
environment:
|
||||||
@@ -328,13 +327,12 @@ services:
|
|||||||
image: registry.cn-shanghai.aliyuncs.com/easyaigc/dozzle:latest
|
image: registry.cn-shanghai.aliyuncs.com/easyaigc/dozzle:latest
|
||||||
environment:
|
environment:
|
||||||
DOZZLE_BASE: /logs-web
|
DOZZLE_BASE: /logs-web
|
||||||
# DOZZLE_AUTH_PROVIDER: simple
|
DOZZLE_ENABLE_DOWNLOAD: "false"
|
||||||
DOZZLE_AUTH_TTL: 72h
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
ports:
|
ports:
|
||||||
- "${DOZZLE_PORT:-8080}:8080"
|
- "${EASYAI_INFRA_BIND_IP:-127.0.0.1}:${DOZZLE_PORT:-8080}:8080"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
logging:
|
logging:
|
||||||
driver: "json-file"
|
driver: "json-file"
|
||||||
@@ -345,7 +343,7 @@ services:
|
|||||||
image: registry.cn-shanghai.aliyuncs.com/easyaigc/sandbox:latest
|
image: registry.cn-shanghai.aliyuncs.com/easyaigc/sandbox:latest
|
||||||
container_name: sandbox
|
container_name: sandbox
|
||||||
networks:
|
networks:
|
||||||
- easyai
|
- sandbox-network
|
||||||
# 不映射宿主机端口,仅允许 Docker 内网中的 easyai-server 访问
|
# 不映射宿主机端口,仅允许 Docker 内网中的 easyai-server 访问
|
||||||
labels:
|
labels:
|
||||||
- "com.centurylinklabs.watchtower.enable=true"
|
- "com.centurylinklabs.watchtower.enable=true"
|
||||||
@@ -422,7 +420,7 @@ services:
|
|||||||
labels:
|
labels:
|
||||||
- "com.centurylinklabs.watchtower.enable=true"
|
- "com.centurylinklabs.watchtower.enable=true"
|
||||||
ports:
|
ports:
|
||||||
- "${ASG_PORT:-3003}:3003"
|
- "${EASYAI_PROXY_BIND_IP:-127.0.0.1}:${ASG_PORT:-3003}:3003"
|
||||||
networks:
|
networks:
|
||||||
easyai:
|
easyai:
|
||||||
ipv4_address: 172.21.0.14
|
ipv4_address: 172.21.0.14
|
||||||
@@ -453,7 +451,7 @@ services:
|
|||||||
labels:
|
labels:
|
||||||
- "com.centurylinklabs.watchtower.enable=true"
|
- "com.centurylinklabs.watchtower.enable=true"
|
||||||
ports:
|
ports:
|
||||||
- "${AMS_PORT:-3004}:3004"
|
- "${EASYAI_INFRA_BIND_IP:-127.0.0.1}:${AMS_PORT:-3004}:3004"
|
||||||
environment:
|
environment:
|
||||||
- MEMORY_DATABASE_URL=${MEMORY_DATABASE_URL:-postgresql://easyai:easyai2025@easyai-pgvector:5432/easyai_memory}
|
- MEMORY_DATABASE_URL=${MEMORY_DATABASE_URL:-postgresql://easyai:easyai2025@easyai-pgvector:5432/easyai_memory}
|
||||||
networks:
|
networks:
|
||||||
@@ -530,3 +528,7 @@ networks:
|
|||||||
config:
|
config:
|
||||||
- subnet: 172.21.0.0/16
|
- subnet: 172.21.0.0/16
|
||||||
gateway: 172.21.0.1
|
gateway: 172.21.0.1
|
||||||
|
# 仅 easyai-server 与 sandbox 共享,阻断沙箱横向访问数据库、缓存、MQ 与治理服务。
|
||||||
|
# 保持普通 bridge 以允许用户脚本按业务需要访问外网。
|
||||||
|
sandbox-network:
|
||||||
|
driver: bridge
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ map $http_host $easyai_forwarded_host {
|
|||||||
'' $host;
|
'' $host;
|
||||||
}
|
}
|
||||||
|
|
||||||
# www 域名重定向,使用二级域名注释掉此部分的域名重定向
|
# EASYAI_WWW_REDIRECT_BEGIN
|
||||||
|
# www 域名重定向;IP 模式生成配置时会删除整个区块。
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
listen [::]:80;
|
listen [::]:80;
|
||||||
@@ -32,6 +33,7 @@ server {
|
|||||||
return 301 https://51easyai.com$request_uri;
|
return 301 https://51easyai.com$request_uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# EASYAI_WWW_REDIRECT_END
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
@@ -104,6 +106,20 @@ server {
|
|||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 废弃的服务端任意 HTTP 调试入口不得对公网开放。
|
||||||
|
location = /api/integration/platform-api/execute {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 旧教育平台 SSO 必须先配置双方独立密钥,默认不对公网开放。
|
||||||
|
location = /api/auth/sign/token {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /api/auth/verify/token {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://127.0.0.1:3001/;
|
proxy_pass http://127.0.0.1:3001/;
|
||||||
proxy_read_timeout 1200s; # 您已设置,适合长连接
|
proxy_read_timeout 1200s; # 您已设置,适合长连接
|
||||||
@@ -160,14 +176,14 @@ server {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
}
|
}
|
||||||
location /logs-web/ {
|
# Dozzle 直接暴露会泄露全部容器日志、环境与下载入口。
|
||||||
proxy_pass http://127.0.0.1:8080/logs-web/;
|
# 如需排障,请通过服务器本机或 SSH 隧道访问回环端口,不允许公网代理。
|
||||||
proxy_redirect off;
|
location = /logs-web {
|
||||||
proxy_set_header X-Original-Prefix '/logs-web';
|
return 404;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
}
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
location ^~ /logs-web/ {
|
||||||
proxy_set_header Host $host;
|
return 404;
|
||||||
}
|
}
|
||||||
location = /jupyterlab {
|
location = /jupyterlab {
|
||||||
return 404;
|
return 404;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ reset_case() {
|
|||||||
.env.ASG \
|
.env.ASG \
|
||||||
.env.AMS \
|
.env.AMS \
|
||||||
easyai-proxy.conf \
|
easyai-proxy.conf \
|
||||||
|
10.0.0.8.conf \
|
||||||
demo.example.com.conf
|
demo.example.com.conf
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,18 +64,37 @@ assert_sandbox_public_access_disabled() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert_high_risk_public_access_disabled() {
|
||||||
|
local config_file="$1"
|
||||||
|
grep -q '^ location = /api/integration/platform-api/execute {' "$config_file"
|
||||||
|
grep -q '^ location = /api/auth/sign/token {' "$config_file"
|
||||||
|
grep -q '^ location = /api/auth/verify/token {' "$config_file"
|
||||||
|
grep -q '^ location = /logs-web {' "$config_file"
|
||||||
|
grep -q '^ location \^~ /logs-web/ {' "$config_file"
|
||||||
|
if grep -Eq 'proxy_pass .*(:8080|platform-api/execute|auth/(sign|verify)/token)' "$config_file"; then
|
||||||
|
echo "Unexpected high-risk public proxy in $config_file" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
assert_compose_exposure() {
|
assert_compose_exposure() {
|
||||||
local expected_server_host_ip="$1"
|
local expected_server_host_ip="$1"
|
||||||
|
local expected_proxy_host_ip="$2"
|
||||||
docker compose config --format json | python3 -c '
|
docker compose config --format json | python3 -c '
|
||||||
import json, sys
|
import json, sys
|
||||||
expected = sys.argv[1]
|
expected_server = sys.argv[1]
|
||||||
|
expected_proxy = sys.argv[2]
|
||||||
config = json.load(sys.stdin)
|
config = json.load(sys.stdin)
|
||||||
sandbox_ports = config["services"]["sandbox"].get("ports") or []
|
sandbox_ports = config["services"]["sandbox"].get("ports") or []
|
||||||
assert sandbox_ports == [], f"sandbox ports published: {sandbox_ports}"
|
assert sandbox_ports == [], f"sandbox ports published: {sandbox_ports}"
|
||||||
server_ports = config["services"]["easyai-server"].get("ports") or []
|
server_ports = config["services"]["easyai-server"].get("ports") or []
|
||||||
assert len(server_ports) == 1, server_ports
|
assert len(server_ports) == 1, server_ports
|
||||||
assert server_ports[0].get("host_ip") == expected, server_ports
|
assert server_ports[0].get("host_ip") == expected_server, server_ports
|
||||||
' "$expected_server_host_ip"
|
for service in ("easyai-web", "ws-gateway", "easyai-asg"):
|
||||||
|
ports = config["services"][service].get("ports") or []
|
||||||
|
assert len(ports) == 1, (service, ports)
|
||||||
|
assert ports[0].get("host_ip") == expected_proxy, (service, ports)
|
||||||
|
' "$expected_server_host_ip" "$expected_proxy_host_ip"
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_canvas_ws_auth_config() {
|
assert_canvas_ws_auth_config() {
|
||||||
@@ -92,6 +112,29 @@ assert "ws_ticket" in methods, f"ws-gateway does not advertise ws_ticket: {sorte
|
|||||||
'
|
'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert_compose_security() {
|
||||||
|
docker compose config --format json | python3 -c '
|
||||||
|
import json, sys
|
||||||
|
config = json.load(sys.stdin)
|
||||||
|
services = config["services"]
|
||||||
|
|
||||||
|
assert set(services["sandbox"]["networks"]) == {"sandbox-network"}, services["sandbox"]["networks"]
|
||||||
|
assert {"easyai", "sandbox-network"}.issubset(services["easyai-server"]["networks"]), services["easyai-server"]["networks"]
|
||||||
|
assert services["easyai-server"]["environment"]["GATEWAY_INBOUND_TCP_LISTEN_HOST"] == "172.21.0.6", services["easyai-server"]["environment"]["GATEWAY_INBOUND_TCP_LISTEN_HOST"]
|
||||||
|
assert services["mongo"].get("privileged") is not True, services["mongo"].get("privileged")
|
||||||
|
|
||||||
|
for service in ("mongo", "rabbitmq", "video-edit", "dozzle", "agent-memory"):
|
||||||
|
for port in services[service].get("ports") or []:
|
||||||
|
assert port.get("host_ip") == "127.0.0.1", (service, port)
|
||||||
|
|
||||||
|
watchtower = services["watchtower"]
|
||||||
|
command = " ".join(watchtower.get("command") or [])
|
||||||
|
assert "http-api-update" not in command, command
|
||||||
|
assert "WATCHTOWER_HTTP_API_TOKEN" not in (watchtower.get("environment") or {}), watchtower.get("environment")
|
||||||
|
assert services["dozzle"]["environment"]["DOZZLE_ENABLE_DOWNLOAD"] == "false", services["dozzle"]["environment"]
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
sed -i.bak 's/^SERVER_HTTP_PORT=.*/SERVER_HTTP_PORT=4100/' .env.sample
|
sed -i.bak 's/^SERVER_HTTP_PORT=.*/SERVER_HTTP_PORT=4100/' .env.sample
|
||||||
rm -f .env.sample.bak
|
rm -f .env.sample.bak
|
||||||
DEPLOY_NON_INTERACTIVE=1 \
|
DEPLOY_NON_INTERACTIVE=1 \
|
||||||
@@ -99,9 +142,29 @@ DEPLOY_NON_INTERACTIVE=1 \
|
|||||||
DEPLOY_ACCESS=ip \
|
DEPLOY_ACCESS=ip \
|
||||||
DEPLOY_IP=10.0.0.8 \
|
DEPLOY_IP=10.0.0.8 \
|
||||||
bash start.sh > "$TMP_DIR/first-install.log"
|
bash start.sh > "$TMP_DIR/first-install.log"
|
||||||
grep -qx 'NUXT_PUBLIC_BASE_APIURL=http://10.0.0.8:4100' .env
|
grep -qx 'NUXT_PUBLIC_BASE_APIURL=/api' .env
|
||||||
grep -qx 'CONFIG_PUBLIC_API_BASE_URL=http://10.0.0.8:4100' .env
|
grep -qx 'NUXT_PUBLIC_BASE_SOCKETURL=ws://10.0.0.8/socket.io' .env
|
||||||
grep -qx 'SERVER_HTTP_BIND_IP=0.0.0.0' .env
|
grep -qx 'NUXT_PUBLIC_SG_APIURL=/asg-api' .env
|
||||||
|
grep -qx 'CONFIG_PUBLIC_API_BASE_URL=http://10.0.0.8/api' .env
|
||||||
|
grep -qx 'CONFIG_SECURITY_ORIGIN=http://10.0.0.8' .env
|
||||||
|
grep -qx 'EASYAI_PROXY_BIND_IP=127.0.0.1' .env
|
||||||
|
grep -qx 'EASYAI_INFRA_BIND_IP=127.0.0.1' .env
|
||||||
|
grep -qx 'GATEWAY_INBOUND_TCP_LISTEN_HOST=172.21.0.6' .env
|
||||||
|
grep -qx 'SERVER_HTTP_BIND_IP=127.0.0.1' .env
|
||||||
|
grep -q 'server_name 10.0.0.8;' 10.0.0.8.conf
|
||||||
|
if grep -q 'www.10.0.0.8' 10.0.0.8.conf; then
|
||||||
|
echo "IP proxy config must not contain the domain-only www redirect" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
grep -q 'proxy_pass http://127.0.0.1:3010/;' 10.0.0.8.conf
|
||||||
|
grep -q 'proxy_pass http://127.0.0.1:4100/;' 10.0.0.8.conf
|
||||||
|
grep -q 'proxy_pass http://127.0.0.1:3002;' 10.0.0.8.conf
|
||||||
|
grep -q 'proxy_pass http://127.0.0.1:3003/;' 10.0.0.8.conf
|
||||||
|
grep -q 'location /socket.io {' 10.0.0.8.conf
|
||||||
|
assert_compression_config 10.0.0.8.conf
|
||||||
|
assert_sandbox_public_access_disabled 10.0.0.8.conf
|
||||||
|
assert_high_risk_public_access_disabled 10.0.0.8.conf
|
||||||
|
grep -Fq '访问地址: http://10.0.0.8' "$TMP_DIR/first-install.log"
|
||||||
initial_admin_password="$(awk -F= '$1 == "CONFIG_INITIAL_ADMIN_PASSWORD" { print $2; exit }' .env)"
|
initial_admin_password="$(awk -F= '$1 == "CONFIG_INITIAL_ADMIN_PASSWORD" { print $2; exit }' .env)"
|
||||||
[ "${#initial_admin_password}" -ge 12 ] || {
|
[ "${#initial_admin_password}" -ge 12 ] || {
|
||||||
echo "Initial admin password was not generated" >&2
|
echo "Initial admin password was not generated" >&2
|
||||||
@@ -122,8 +185,9 @@ if grep -Fq "登录密码: ${initial_admin_password}" "$TMP_DIR/existing-env.log
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
|
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
|
||||||
assert_compose_exposure 0.0.0.0
|
assert_compose_exposure 127.0.0.1 127.0.0.1
|
||||||
assert_canvas_ws_auth_config
|
assert_canvas_ws_auth_config
|
||||||
|
assert_compose_security
|
||||||
fi
|
fi
|
||||||
|
|
||||||
reset_case
|
reset_case
|
||||||
@@ -133,7 +197,9 @@ DEPLOY_NON_INTERACTIVE=1 \
|
|||||||
DEPLOY_DOMAIN=demo.example.com \
|
DEPLOY_DOMAIN=demo.example.com \
|
||||||
DEPLOY_HTTPS=false \
|
DEPLOY_HTTPS=false \
|
||||||
bash start.sh >/dev/null
|
bash start.sh >/dev/null
|
||||||
|
grep -qx 'NUXT_PUBLIC_BASE_APIURL=/api' .env
|
||||||
grep -qx 'NUXT_PUBLIC_BASE_SOCKETURL=ws://demo.example.com/socket.io' .env
|
grep -qx 'NUXT_PUBLIC_BASE_SOCKETURL=ws://demo.example.com/socket.io' .env
|
||||||
|
grep -qx 'NUXT_PUBLIC_SG_APIURL=/asg-api' .env
|
||||||
grep -qx 'CONFIG_PUBLIC_API_BASE_URL=http://demo.example.com/api' .env
|
grep -qx 'CONFIG_PUBLIC_API_BASE_URL=http://demo.example.com/api' .env
|
||||||
grep -q 'proxy_set_header X-Forwarded-Host $easyai_forwarded_host;' demo.example.com.conf
|
grep -q 'proxy_set_header X-Forwarded-Host $easyai_forwarded_host;' demo.example.com.conf
|
||||||
grep -q 'proxy_set_header X-Forwarded-Port $server_port;' demo.example.com.conf
|
grep -q 'proxy_set_header X-Forwarded-Port $server_port;' demo.example.com.conf
|
||||||
@@ -141,9 +207,14 @@ grep -q "proxy_set_header X-Original-Prefix '/api';" demo.example.com.conf
|
|||||||
grep -q 'location = /api {' demo.example.com.conf
|
grep -q 'location = /api {' demo.example.com.conf
|
||||||
assert_compression_config demo.example.com.conf
|
assert_compression_config demo.example.com.conf
|
||||||
assert_sandbox_public_access_disabled demo.example.com.conf
|
assert_sandbox_public_access_disabled demo.example.com.conf
|
||||||
|
assert_high_risk_public_access_disabled demo.example.com.conf
|
||||||
grep -qx 'SERVER_HTTP_BIND_IP=127.0.0.1' .env
|
grep -qx 'SERVER_HTTP_BIND_IP=127.0.0.1' .env
|
||||||
|
grep -qx 'EASYAI_PROXY_BIND_IP=127.0.0.1' .env
|
||||||
|
grep -qx 'EASYAI_INFRA_BIND_IP=127.0.0.1' .env
|
||||||
|
grep -qx 'GATEWAY_INBOUND_TCP_LISTEN_HOST=172.21.0.6' .env
|
||||||
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
|
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
|
||||||
assert_compose_exposure 127.0.0.1
|
assert_compose_exposure 127.0.0.1 127.0.0.1
|
||||||
|
assert_compose_security
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 直接验证 https.sh 的缺省配置生成函数会采用完整模板,而不是只代理 3010。
|
# 直接验证 https.sh 的缺省配置生成函数会采用完整模板,而不是只代理 3010。
|
||||||
@@ -161,6 +232,7 @@ grep -q 'location /socket.io {' easyai-proxy.conf
|
|||||||
grep -q 'proxy_set_header X-Forwarded-Host $easyai_forwarded_host;' easyai-proxy.conf
|
grep -q 'proxy_set_header X-Forwarded-Host $easyai_forwarded_host;' easyai-proxy.conf
|
||||||
assert_compression_config easyai-proxy.conf
|
assert_compression_config easyai-proxy.conf
|
||||||
assert_sandbox_public_access_disabled easyai-proxy.conf
|
assert_sandbox_public_access_disabled easyai-proxy.conf
|
||||||
|
assert_high_risk_public_access_disabled easyai-proxy.conf
|
||||||
|
|
||||||
reset_case
|
reset_case
|
||||||
DEPLOY_NON_INTERACTIVE=1 \
|
DEPLOY_NON_INTERACTIVE=1 \
|
||||||
@@ -169,7 +241,9 @@ DEPLOY_NON_INTERACTIVE=1 \
|
|||||||
DEPLOY_DOMAIN=demo.example.com \
|
DEPLOY_DOMAIN=demo.example.com \
|
||||||
DEPLOY_HTTPS=true \
|
DEPLOY_HTTPS=true \
|
||||||
bash start.sh >/dev/null
|
bash start.sh >/dev/null
|
||||||
|
grep -qx 'NUXT_PUBLIC_BASE_APIURL=/api' .env
|
||||||
grep -qx 'NUXT_PUBLIC_BASE_SOCKETURL=wss://demo.example.com/socket.io' .env
|
grep -qx 'NUXT_PUBLIC_BASE_SOCKETURL=wss://demo.example.com/socket.io' .env
|
||||||
|
grep -qx 'NUXT_PUBLIC_SG_APIURL=/asg-api' .env
|
||||||
grep -qx 'CONFIG_PUBLIC_API_BASE_URL=https://demo.example.com/api' .env
|
grep -qx 'CONFIG_PUBLIC_API_BASE_URL=https://demo.example.com/api' .env
|
||||||
|
|
||||||
reset_case
|
reset_case
|
||||||
|
|||||||
@@ -175,6 +175,8 @@ function Setup-EnvFiles {
|
|||||||
$content = Upsert-Env $content "NUXT_PUBLIC_SG_APIURL" "http://$($script:DeployIP):3003"
|
$content = Upsert-Env $content "NUXT_PUBLIC_SG_APIURL" "http://$($script:DeployIP):3003"
|
||||||
$serverBindIp = if ($script:DeployIP -eq "127.0.0.1" -or $script:DeployIP -eq "localhost") { "127.0.0.1" } else { "0.0.0.0" }
|
$serverBindIp = if ($script:DeployIP -eq "127.0.0.1" -or $script:DeployIP -eq "localhost") { "127.0.0.1" } else { "0.0.0.0" }
|
||||||
$content = Upsert-Env $content "SERVER_HTTP_BIND_IP" $serverBindIp
|
$content = Upsert-Env $content "SERVER_HTTP_BIND_IP" $serverBindIp
|
||||||
|
# Windows 当前仍使用 Docker Desktop 直连端口;显式覆盖 Linux Nginx 模式的回环默认值。
|
||||||
|
$content = Upsert-Env $content "EASYAI_PROXY_BIND_IP" $serverBindIp
|
||||||
$webPortMatch = [regex]::Match($content, '(?m)^WEB_PORT=(\d+)$')
|
$webPortMatch = [regex]::Match($content, '(?m)^WEB_PORT=(\d+)$')
|
||||||
$webPort = if ($webPortMatch.Success) { $webPortMatch.Groups[1].Value } else { "3010" }
|
$webPort = if ($webPortMatch.Success) { $webPortMatch.Groups[1].Value } else { "3010" }
|
||||||
$content = Upsert-Env $content "CONFIG_SECURITY_ORIGIN" "http://$($script:DeployIP):$webPort"
|
$content = Upsert-Env $content "CONFIG_SECURITY_ORIGIN" "http://$($script:DeployIP):$webPort"
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ DEPLOY_HTTPS_ENV="${DEPLOY_HTTPS_INPUT:-${DEPLOY_HTTPS:-}}"
|
|||||||
DEPLOY_HTTPS=false
|
DEPLOY_HTTPS=false
|
||||||
SECURITY_ENV_MODE="upgrade"
|
SECURITY_ENV_MODE="upgrade"
|
||||||
PUBLIC_API_BASE_URL_CANDIDATE=""
|
PUBLIC_API_BASE_URL_CANDIDATE=""
|
||||||
|
PROXY_CONF=""
|
||||||
|
|
||||||
is_non_interactive() {
|
is_non_interactive() {
|
||||||
[ "${DEPLOY_NON_INTERACTIVE:-0}" = "1" ] || [ "${CI:-}" = "true" ] || [ ! -t 0 ]
|
[ "${DEPLOY_NON_INTERACTIVE:-0}" = "1" ] || [ "${CI:-}" = "true" ] || [ ! -t 0 ]
|
||||||
@@ -58,6 +59,17 @@ normalize_bool() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
upsert_env_value() {
|
||||||
|
local file=$1
|
||||||
|
local key=$2
|
||||||
|
local value=$3
|
||||||
|
if grep -q "^${key}=" "$file"; then
|
||||||
|
sed -i.bak "s|^${key}=.*|${key}=${value}|" "$file"
|
||||||
|
else
|
||||||
|
printf '\n%s=%s\n' "$key" "$value" >> "$file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
set_deploy_https_from_env_or_default() {
|
set_deploy_https_from_env_or_default() {
|
||||||
local normalized
|
local normalized
|
||||||
if ! normalized="$(normalize_bool "${DEPLOY_HTTPS_ENV:-false}")"; then
|
if ! normalized="$(normalize_bool "${DEPLOY_HTTPS_ENV:-false}")"; then
|
||||||
@@ -162,7 +174,7 @@ run_deploy_questions() {
|
|||||||
if [ -z "$DEPLOY_ACCESS" ]; then
|
if [ -z "$DEPLOY_ACCESS" ]; then
|
||||||
require_interactive "DEPLOY_ACCESS=ip DEPLOY_IP=<服务器IP> 或 DEPLOY_ACCESS=domain DEPLOY_DOMAIN=<域名>"
|
require_interactive "DEPLOY_ACCESS=ip DEPLOY_IP=<服务器IP> 或 DEPLOY_ACCESS=domain DEPLOY_DOMAIN=<域名>"
|
||||||
echo "1. 通过 IP 地址还是域名访问?"
|
echo "1. 通过 IP 地址还是域名访问?"
|
||||||
echo " [1] IP 地址(需开放 3001、3002、3003 端口)"
|
echo " [1] IP 地址(仅需开放 80 端口,由 Nginx 统一反向代理)"
|
||||||
echo " [2] 域名"
|
echo " [2] 域名"
|
||||||
read -r -p "请选择 [1/2]: " choice
|
read -r -p "请选择 [1/2]: " choice
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
@@ -181,7 +193,7 @@ run_deploy_questions() {
|
|||||||
echo "❌ IP 不能为空"
|
echo "❌ IP 不能为空"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo " 请确保防火墙已放行 3001、3002、3003 端口"
|
echo " 请确保防火墙已放行 80 端口"
|
||||||
else
|
else
|
||||||
# 3. 输入域名
|
# 3. 输入域名
|
||||||
prompt_or_env DEPLOY_DOMAIN "3. 请输入域名(不含 https:// 前缀,如 51easyai.com)" "DEPLOY_DOMAIN" ""
|
prompt_or_env DEPLOY_DOMAIN "3. 请输入域名(不含 https:// 前缀,如 51easyai.com)" "DEPLOY_DOMAIN" ""
|
||||||
@@ -238,34 +250,63 @@ setup_env_files() {
|
|||||||
SECURITY_ENV_MODE="new"
|
SECURITY_ENV_MODE="new"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local web_port server_port public_scheme socket_scheme
|
local web_port server_port ws_port asg_port public_scheme socket_scheme public_host security_origins
|
||||||
web_port="$(awk -F= '$1 == "WEB_PORT" { print $2; exit }' .env | tr -d '[:space:]')"
|
web_port="$(awk -F= '$1 == "WEB_PORT" { print $2; exit }' .env | tr -d '[:space:]')"
|
||||||
web_port="${web_port:-3010}"
|
web_port="${web_port:-3010}"
|
||||||
server_port="$(awk -F= '$1 == "SERVER_HTTP_PORT" { print $2; exit }' .env | tr -d '[:space:]')"
|
server_port="$(awk -F= '$1 == "SERVER_HTTP_PORT" { print $2; exit }' .env | tr -d '[:space:]')"
|
||||||
server_port="${server_port:-3001}"
|
server_port="${server_port:-3001}"
|
||||||
|
ws_port="$(awk -F= '$1 == "CONFIG_WS_PORT" { print $2; exit }' .env | tr -d '[:space:]')"
|
||||||
|
ws_port="${ws_port:-3002}"
|
||||||
|
asg_port="$(awk -F= '$1 == "ASG_PORT" { print $2; exit }' .env | tr -d '[:space:]')"
|
||||||
|
asg_port="${asg_port:-3003}"
|
||||||
|
|
||||||
|
public_scheme="http"
|
||||||
|
socket_scheme="ws"
|
||||||
if [ "$DEPLOY_MODE" = "ip" ]; then
|
if [ "$DEPLOY_MODE" = "ip" ]; then
|
||||||
# IP 模式
|
public_host="$DEPLOY_IP"
|
||||||
sed -i.bak "s|^NUXT_PUBLIC_BASE_APIURL=.*|NUXT_PUBLIC_BASE_APIURL=http://${DEPLOY_IP}:${server_port}|" .env
|
security_origins="http://${DEPLOY_IP}"
|
||||||
sed -i.bak "s|^NUXT_PUBLIC_BASE_SOCKETURL=.*|NUXT_PUBLIC_BASE_SOCKETURL=ws://${DEPLOY_IP}:3002|" .env
|
|
||||||
sed -i.bak "s|^NUXT_PUBLIC_SG_APIURL=.*|NUXT_PUBLIC_SG_APIURL=http://${DEPLOY_IP}:3003|" .env
|
|
||||||
sed -i.bak "s|^CONFIG_SECURITY_ORIGIN=.*|CONFIG_SECURITY_ORIGIN=http://${DEPLOY_IP}:${web_port}|" .env
|
|
||||||
PUBLIC_API_BASE_URL_CANDIDATE="${DEPLOY_PUBLIC_API_BASE_URL:-http://${DEPLOY_IP}:${server_port}}"
|
|
||||||
echo " ✓ .env 已配置为 IP 模式 (${DEPLOY_IP})"
|
|
||||||
else
|
else
|
||||||
# 域名模式
|
public_host="$DEPLOY_DOMAIN"
|
||||||
public_scheme="http"
|
security_origins="https://${DEPLOY_DOMAIN},http://${DEPLOY_DOMAIN}"
|
||||||
socket_scheme="ws"
|
|
||||||
if [ "$DEPLOY_HTTPS" = true ]; then
|
if [ "$DEPLOY_HTTPS" = true ]; then
|
||||||
public_scheme="https"
|
public_scheme="https"
|
||||||
socket_scheme="wss"
|
socket_scheme="wss"
|
||||||
fi
|
fi
|
||||||
sed -i.bak "s|^NUXT_PUBLIC_BASE_APIURL=.*|NUXT_PUBLIC_BASE_APIURL=/api|" .env
|
fi
|
||||||
sed -i.bak "s|^NUXT_PUBLIC_BASE_SOCKETURL=.*|NUXT_PUBLIC_BASE_SOCKETURL=${socket_scheme}://${DEPLOY_DOMAIN}/socket.io|" .env
|
|
||||||
sed -i.bak "s|^NUXT_PUBLIC_SG_APIURL=.*|NUXT_PUBLIC_SG_APIURL=/asg-api|" .env
|
# IP 与域名模式共用同一组 Nginx 路径,只由公开 origin 决定 Host 与协议。
|
||||||
# 域名模式同时允许同域 HTTP/HTTPS,兼容首次签证书和复用既有证书两种流程。
|
upsert_env_value .env NUXT_PUBLIC_BASE_APIURL "/api"
|
||||||
sed -i.bak "s|^CONFIG_SECURITY_ORIGIN=.*|CONFIG_SECURITY_ORIGIN=https://${DEPLOY_DOMAIN},http://${DEPLOY_DOMAIN}|" .env
|
upsert_env_value .env NUXT_PUBLIC_BASE_SOCKETURL "${socket_scheme}://${public_host}/socket.io"
|
||||||
PUBLIC_API_BASE_URL_CANDIDATE="${DEPLOY_PUBLIC_API_BASE_URL:-${public_scheme}://${DEPLOY_DOMAIN}/api}"
|
upsert_env_value .env NUXT_PUBLIC_SG_APIURL "/asg-api"
|
||||||
|
upsert_env_value .env CONFIG_SECURITY_ORIGIN "$security_origins"
|
||||||
|
upsert_env_value .env EASYAI_PROXY_BIND_IP "127.0.0.1"
|
||||||
|
upsert_env_value .env EASYAI_INFRA_BIND_IP "127.0.0.1"
|
||||||
|
upsert_env_value .env GATEWAY_INBOUND_TCP_LISTEN_HOST "172.21.0.6"
|
||||||
|
PUBLIC_API_BASE_URL_CANDIDATE="${DEPLOY_PUBLIC_API_BASE_URL:-${public_scheme}://${public_host}/api}"
|
||||||
|
|
||||||
|
if [ "$DEPLOY_MODE" = "ip" ]; then
|
||||||
|
echo " ✓ .env 已配置为 IP + Nginx 模式 (${DEPLOY_IP})"
|
||||||
|
|
||||||
|
# 生成包含前端、API、WebSocket 与 ASG 的完整 IP 代理配置。
|
||||||
|
PROXY_CONF="${DEPLOY_IP}.conf"
|
||||||
|
if [ ! -f "$PROXY_CONF" ]; then
|
||||||
|
local proxy_template="easyai-proxy.conf.sample"
|
||||||
|
if [ ! -f "$proxy_template" ]; then
|
||||||
|
echo "❌ 未找到 Nginx 配置模板: easyai-proxy.conf.sample"
|
||||||
|
echo " 请确认仓库文件完整后重试"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sed \
|
||||||
|
-e '/^# EASYAI_WWW_REDIRECT_BEGIN$/,/^# EASYAI_WWW_REDIRECT_END$/d' \
|
||||||
|
-e "s/51easyai\.com/${DEPLOY_IP}/g" \
|
||||||
|
-e "s|127\.0\.0\.1:3010|127.0.0.1:${web_port}|g" \
|
||||||
|
-e "s|127\.0\.0\.1:3001|127.0.0.1:${server_port}|g" \
|
||||||
|
-e "s|127\.0\.0\.1:3002|127.0.0.1:${ws_port}|g" \
|
||||||
|
-e "s|127\.0\.0\.1:3003|127.0.0.1:${asg_port}|g" \
|
||||||
|
"$proxy_template" > "$PROXY_CONF"
|
||||||
|
echo " ✓ Nginx IP 配置已生成: $PROXY_CONF"
|
||||||
|
fi
|
||||||
|
else
|
||||||
echo " ✓ .env 已配置为域名模式 (${DEPLOY_DOMAIN})"
|
echo " ✓ .env 已配置为域名模式 (${DEPLOY_DOMAIN})"
|
||||||
|
|
||||||
# 7. Nginx 配置(域名模式)
|
# 7. Nginx 配置(域名模式)
|
||||||
@@ -277,7 +318,13 @@ setup_env_files() {
|
|||||||
echo " 请确认仓库文件完整后重试"
|
echo " 请确认仓库文件完整后重试"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
sed "s/51easyai.com/${DEPLOY_DOMAIN}/g" "$proxy_template" > "$PROXY_CONF"
|
sed \
|
||||||
|
-e "s/51easyai\.com/${DEPLOY_DOMAIN}/g" \
|
||||||
|
-e "s|127\.0\.0\.1:3010|127.0.0.1:${web_port}|g" \
|
||||||
|
-e "s|127\.0\.0\.1:3001|127.0.0.1:${server_port}|g" \
|
||||||
|
-e "s|127\.0\.0\.1:3002|127.0.0.1:${ws_port}|g" \
|
||||||
|
-e "s|127\.0\.0\.1:3003|127.0.0.1:${asg_port}|g" \
|
||||||
|
"$proxy_template" > "$PROXY_CONF"
|
||||||
echo " ✓ Nginx 配置已生成: $PROXY_CONF"
|
echo " ✓ Nginx 配置已生成: $PROXY_CONF"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -430,6 +477,72 @@ install_docker() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ==================== 配置 HTTP Nginx 入口 ====================
|
||||||
|
configure_http_nginx_proxy() {
|
||||||
|
case "$DEPLOY_MODE" in
|
||||||
|
ip|domain) ;;
|
||||||
|
*) return 0 ;;
|
||||||
|
esac
|
||||||
|
# HTTPS 域名由 https.sh 安装配置并签发证书;IP 与纯 HTTP 域名走这里。
|
||||||
|
if [ "$DEPLOY_MODE" = "domain" ] && [ "$DEPLOY_HTTPS" = true ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ -z "$PROXY_CONF" ] || [ ! -f "$PROXY_CONF" ]; then
|
||||||
|
echo "❌ 缺少 Nginx 配置文件: ${PROXY_CONF:-未生成}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🌐 配置 Nginx 统一入口(HTTP 80)..."
|
||||||
|
if ! command -v nginx &>/dev/null; then
|
||||||
|
if command -v apt-get &>/dev/null; then
|
||||||
|
sudo apt-get update -y
|
||||||
|
sudo apt-get install -y nginx
|
||||||
|
elif command -v dnf &>/dev/null; then
|
||||||
|
sudo dnf install -y nginx
|
||||||
|
elif command -v yum &>/dev/null; then
|
||||||
|
sudo yum install -y nginx
|
||||||
|
else
|
||||||
|
echo "❌ 未找到受支持的 Nginx 包管理器(apt-get/dnf/yum)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
local nginx_conf_dir="/etc/nginx/conf.d"
|
||||||
|
local nginx_target="${nginx_conf_dir}/easyai-proxy.conf"
|
||||||
|
local nginx_backup=""
|
||||||
|
sudo install -d -m 0755 "$nginx_conf_dir"
|
||||||
|
if sudo test -f "$nginx_target"; then
|
||||||
|
nginx_backup="${nginx_target}.pre-ip-proxy-$(date +%Y%m%dT%H%M%S)"
|
||||||
|
sudo cp -a "$nginx_target" "$nginx_backup"
|
||||||
|
echo " ✓ 已备份现有配置: $nginx_backup"
|
||||||
|
fi
|
||||||
|
sudo install -m 0644 "$PROXY_CONF" "$nginx_target"
|
||||||
|
|
||||||
|
if ! sudo nginx -t; then
|
||||||
|
echo "❌ Nginx 配置校验失败,正在恢复原配置"
|
||||||
|
if [ -n "$nginx_backup" ]; then
|
||||||
|
sudo cp -a "$nginx_backup" "$nginx_target"
|
||||||
|
else
|
||||||
|
sudo rm -f "$nginx_target"
|
||||||
|
fi
|
||||||
|
sudo nginx -t || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v systemctl &>/dev/null; then
|
||||||
|
sudo systemctl enable nginx >/dev/null
|
||||||
|
if sudo systemctl is-active --quiet nginx; then
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
else
|
||||||
|
sudo systemctl start nginx
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
sudo nginx -s reload 2>/dev/null || sudo nginx
|
||||||
|
fi
|
||||||
|
echo " ✓ Nginx 已监听 80,统一代理前端、API、WebSocket 与 ASG"
|
||||||
|
}
|
||||||
|
|
||||||
# ==================== 启动服务 ====================
|
# ==================== 启动服务 ====================
|
||||||
start_services() {
|
start_services() {
|
||||||
echo ""
|
echo ""
|
||||||
@@ -521,17 +634,14 @@ main() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 域名和本机部署默认仅绑定回环;只有明确的 IP/LAN 直连模式才开放后端宿主机端口。
|
# Linux 的 IP/域名模式都经由同机 Nginx 代理,后端宿主机端口保持回环绑定。
|
||||||
# shellcheck source=scripts/init-server-http-bind-ip.sh
|
# shellcheck source=scripts/init-server-http-bind-ip.sh
|
||||||
. ./scripts/init-server-http-bind-ip.sh
|
. ./scripts/init-server-http-bind-ip.sh
|
||||||
local server_bind_override=""
|
local server_bind_override=""
|
||||||
if [ "$DEPLOY_MODE" = "domain" ]; then
|
if [ "$DEPLOY_MODE" = "domain" ]; then
|
||||||
server_bind_override="127.0.0.1"
|
server_bind_override="127.0.0.1"
|
||||||
elif [ "$DEPLOY_MODE" = "ip" ]; then
|
elif [ "$DEPLOY_MODE" = "ip" ]; then
|
||||||
case "$DEPLOY_IP" in
|
server_bind_override="127.0.0.1"
|
||||||
127.0.0.1|localhost) server_bind_override="127.0.0.1" ;;
|
|
||||||
*) server_bind_override="0.0.0.0" ;;
|
|
||||||
esac
|
|
||||||
fi
|
fi
|
||||||
init_server_http_bind_ip .env "$server_bind_override"
|
init_server_http_bind_ip .env "$server_bind_override"
|
||||||
|
|
||||||
@@ -557,6 +667,7 @@ main() {
|
|||||||
else
|
else
|
||||||
install_docker
|
install_docker
|
||||||
start_services
|
start_services
|
||||||
|
configure_http_nginx_proxy
|
||||||
export DEPLOY_DOMAIN
|
export DEPLOY_DOMAIN
|
||||||
run_https_setup
|
run_https_setup
|
||||||
fi
|
fi
|
||||||
@@ -566,12 +677,12 @@ main() {
|
|||||||
echo " 部署完成"
|
echo " 部署完成"
|
||||||
echo "================================"
|
echo "================================"
|
||||||
if [ "$DEPLOY_MODE" = "ip" ] && [ -n "$DEPLOY_IP" ]; then
|
if [ "$DEPLOY_MODE" = "ip" ] && [ -n "$DEPLOY_IP" ]; then
|
||||||
echo "访问地址: http://${DEPLOY_IP}:3010"
|
echo "访问地址: http://${DEPLOY_IP}"
|
||||||
elif [ "$DEPLOY_MODE" = "domain" ] && [ -n "$DEPLOY_DOMAIN" ]; then
|
elif [ "$DEPLOY_MODE" = "domain" ] && [ -n "$DEPLOY_DOMAIN" ]; then
|
||||||
if [ "$DEPLOY_HTTPS" = true ]; then
|
if [ "$DEPLOY_HTTPS" = true ]; then
|
||||||
echo "访问地址: https://${DEPLOY_DOMAIN}"
|
echo "访问地址: https://${DEPLOY_DOMAIN}"
|
||||||
else
|
else
|
||||||
echo "访问地址: http://${DEPLOY_DOMAIN} (配置 Nginx 后)"
|
echo "访问地址: http://${DEPLOY_DOMAIN}"
|
||||||
fi
|
fi
|
||||||
if [ "$DEPLOY_HTTPS" = true ]; then
|
if [ "$DEPLOY_HTTPS" = true ]; then
|
||||||
echo "HTTPS 已启用"
|
echo "HTTPS 已启用"
|
||||||
|
|||||||
Reference in New Issue
Block a user