fix(deploy): 默认关闭沙箱公网访问
This commit is contained in:
+5
-5
@@ -30,6 +30,9 @@ WEB_PORT=3010
|
|||||||
SERVER_HTTP_PORT=3001
|
SERVER_HTTP_PORT=3001
|
||||||
# easyai-server 后端 HTTP 端口
|
# easyai-server 后端 HTTP 端口
|
||||||
|
|
||||||
|
SERVER_HTTP_BIND_IP=127.0.0.1
|
||||||
|
# easyai-server 宿主机绑定地址。域名/本机部署保持 127.0.0.1;仅 IP/LAN 直连模式使用 0.0.0.0
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -66,14 +69,11 @@ MEMORY_TCP_PORT=4004
|
|||||||
# 记忆整理相关参数在 .env.AMS 中配置(MEMORY_MAINTENANCE_*)
|
# 记忆整理相关参数在 .env.AMS 中配置(MEMORY_MAINTENANCE_*)
|
||||||
|
|
||||||
# ========== 4. 沙箱环境 ==========
|
# ========== 4. 沙箱环境 ==========
|
||||||
SANDBOX_PORT=8081
|
|
||||||
# 不建议对外暴露
|
|
||||||
|
|
||||||
SANDBOX_JUPYTERLAB_PORT=8888
|
|
||||||
SANDBOX_JUPYTER_TOKEN=easyaiisbest
|
SANDBOX_JUPYTER_TOKEN=easyaiisbest
|
||||||
# 建议设置 token
|
# Sandbox API 与 JupyterLab 默认不映射宿主机端口;token 仅用于容器内 Jupyter 认证
|
||||||
|
|
||||||
SANDBOX_SERVICE_BASE_URL=
|
SANDBOX_SERVICE_BASE_URL=
|
||||||
|
# 留空时 easyai-server 通过 Docker 内网 http://sandbox:8000 访问
|
||||||
|
|
||||||
# ========== 5. Redis ==========
|
# ========== 5. Redis ==========
|
||||||
REDIS_PORT=
|
REDIS_PORT=
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ on:
|
|||||||
- '.env.sample'
|
- '.env.sample'
|
||||||
- 'scripts/init-public-api-base-url.sh'
|
- 'scripts/init-public-api-base-url.sh'
|
||||||
- 'scripts/test-public-api-base-url.sh'
|
- 'scripts/test-public-api-base-url.sh'
|
||||||
|
- 'scripts/init-server-http-bind-ip.sh'
|
||||||
|
- 'scripts/test-server-http-bind-ip.sh'
|
||||||
- 'scripts/test-deployment-public-url.sh'
|
- 'scripts/test-deployment-public-url.sh'
|
||||||
- '.github/workflows/test-deployment-public-url.yml'
|
- '.github/workflows/test-deployment-public-url.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -21,6 +23,8 @@ on:
|
|||||||
- '.env.sample'
|
- '.env.sample'
|
||||||
- 'scripts/init-public-api-base-url.sh'
|
- 'scripts/init-public-api-base-url.sh'
|
||||||
- 'scripts/test-public-api-base-url.sh'
|
- 'scripts/test-public-api-base-url.sh'
|
||||||
|
- 'scripts/init-server-http-bind-ip.sh'
|
||||||
|
- 'scripts/test-server-http-bind-ip.sh'
|
||||||
- 'scripts/test-deployment-public-url.sh'
|
- 'scripts/test-deployment-public-url.sh'
|
||||||
- '.github/workflows/test-deployment-public-url.yml'
|
- '.github/workflows/test-deployment-public-url.yml'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -33,10 +37,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Check shell syntax
|
- name: Check shell syntax
|
||||||
run: |
|
run: |
|
||||||
bash -n start.sh update.sh scripts/init-public-api-base-url.sh scripts/test-public-api-base-url.sh scripts/test-deployment-public-url.sh
|
bash -n start.sh update.sh scripts/init-public-api-base-url.sh scripts/init-server-http-bind-ip.sh scripts/test-public-api-base-url.sh scripts/test-server-http-bind-ip.sh scripts/test-deployment-public-url.sh
|
||||||
|
|
||||||
- name: Test migration helper
|
- name: Test migration helper
|
||||||
run: bash scripts/test-public-api-base-url.sh
|
run: bash scripts/test-public-api-base-url.sh
|
||||||
|
|
||||||
|
- name: Test server HTTP bind migration
|
||||||
|
run: bash scripts/test-server-http-bind-ip.sh
|
||||||
|
|
||||||
- name: Test deployment dry-run matrix
|
- name: Test deployment dry-run matrix
|
||||||
run: bash scripts/test-deployment-public-url.sh
|
run: bash scripts/test-deployment-public-url.sh
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ on:
|
|||||||
- 'update.ps1'
|
- 'update.ps1'
|
||||||
- 'scripts/Initialize-PublicApiBaseUrl.ps1'
|
- 'scripts/Initialize-PublicApiBaseUrl.ps1'
|
||||||
- 'scripts/Test-PublicApiBaseUrl.ps1'
|
- 'scripts/Test-PublicApiBaseUrl.ps1'
|
||||||
|
- 'scripts/Initialize-ServerHttpBindIp.ps1'
|
||||||
|
- 'scripts/Test-ServerHttpBindIp.ps1'
|
||||||
- '.env.sample'
|
- '.env.sample'
|
||||||
- '.github/workflows/test-start-ps1.yml'
|
- '.github/workflows/test-start-ps1.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -16,6 +18,8 @@ on:
|
|||||||
- 'update.ps1'
|
- 'update.ps1'
|
||||||
- 'scripts/Initialize-PublicApiBaseUrl.ps1'
|
- 'scripts/Initialize-PublicApiBaseUrl.ps1'
|
||||||
- 'scripts/Test-PublicApiBaseUrl.ps1'
|
- 'scripts/Test-PublicApiBaseUrl.ps1'
|
||||||
|
- 'scripts/Initialize-ServerHttpBindIp.ps1'
|
||||||
|
- 'scripts/Test-ServerHttpBindIp.ps1'
|
||||||
- '.env.sample'
|
- '.env.sample'
|
||||||
- '.github/workflows/test-start-ps1.yml'
|
- '.github/workflows/test-start-ps1.yml'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -40,12 +44,18 @@ jobs:
|
|||||||
$socket = Select-String -Path .env -Pattern '^NUXT_PUBLIC_BASE_SOCKETURL=' | ForEach-Object { $_.Line }
|
$socket = Select-String -Path .env -Pattern '^NUXT_PUBLIC_BASE_SOCKETURL=' | ForEach-Object { $_.Line }
|
||||||
$sg = Select-String -Path .env -Pattern '^NUXT_PUBLIC_SG_APIURL=' | ForEach-Object { $_.Line }
|
$sg = Select-String -Path .env -Pattern '^NUXT_PUBLIC_SG_APIURL=' | ForEach-Object { $_.Line }
|
||||||
$publicApi = Select-String -Path .env -Pattern '^CONFIG_PUBLIC_API_BASE_URL=' | ForEach-Object { $_.Line }
|
$publicApi = Select-String -Path .env -Pattern '^CONFIG_PUBLIC_API_BASE_URL=' | ForEach-Object { $_.Line }
|
||||||
|
$serverBindIp = Select-String -Path .env -Pattern '^SERVER_HTTP_BIND_IP=' | ForEach-Object { $_.Line }
|
||||||
if ($api -ne 'NUXT_PUBLIC_BASE_APIURL=http://192.168.1.100:4100') { exit 1 }
|
if ($api -ne 'NUXT_PUBLIC_BASE_APIURL=http://192.168.1.100:4100') { exit 1 }
|
||||||
if ($socket -ne 'NUXT_PUBLIC_BASE_SOCKETURL=ws://192.168.1.100:3002') { exit 1 }
|
if ($socket -ne 'NUXT_PUBLIC_BASE_SOCKETURL=ws://192.168.1.100:3002') { exit 1 }
|
||||||
if ($sg -ne 'NUXT_PUBLIC_SG_APIURL=http://192.168.1.100:3003') { exit 1 }
|
if ($sg -ne 'NUXT_PUBLIC_SG_APIURL=http://192.168.1.100:3003') { exit 1 }
|
||||||
if ($publicApi -ne 'CONFIG_PUBLIC_API_BASE_URL=http://192.168.1.100:4100') { exit 1 }
|
if ($publicApi -ne 'CONFIG_PUBLIC_API_BASE_URL=http://192.168.1.100:4100') { exit 1 }
|
||||||
|
if ($serverBindIp -ne 'SERVER_HTTP_BIND_IP=0.0.0.0') { exit 1 }
|
||||||
Write-Host "OK: .env 配置正确"
|
Write-Host "OK: .env 配置正确"
|
||||||
|
|
||||||
- name: Test public API URL migration helper
|
- name: Test public API URL migration helper
|
||||||
run: |
|
run: |
|
||||||
powershell -ExecutionPolicy Bypass -File .\scripts\Test-PublicApiBaseUrl.ps1
|
powershell -ExecutionPolicy Bypass -File .\scripts\Test-PublicApiBaseUrl.ps1
|
||||||
|
|
||||||
|
- name: Test server HTTP bind migration helper
|
||||||
|
run: |
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\scripts\Test-ServerHttpBindIp.ps1
|
||||||
|
|||||||
@@ -455,10 +455,11 @@ curl https://<你的域名>/asg-api/health
|
|||||||
1. 增加脚本沙箱环境容器,需要更新`docker-compose.yml`,用以支持SKILL中的脚本运行
|
1. 增加脚本沙箱环境容器,需要更新`docker-compose.yml`,用以支持SKILL中的脚本运行
|
||||||
2. 增加环境变量配置
|
2. 增加环境变量配置
|
||||||
```dotenv
|
```dotenv
|
||||||
SANDBOX_PORT=8081 #对外暴露沙箱环境的端口,不建议暴露,权限较高
|
SANDBOX_SERVICE_BASE_URL= #脚本运行环境使用的服务地址,默认通过 Docker 内网访问 http://sandbox:8000,不需要配置
|
||||||
SANDBOX_SERVICE_BASE_URL= #脚本运行环境使用的服务地址,默认为通过内网直接访问http://sandbox:8000,不需要配置。当将sandbox部署在其他外部网络才需要配置
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> 安全默认值:Sandbox API 与 JupyterLab 不映射宿主机端口,Nginx 模板对 `/api/sandbox`、`/sandbox`、`/jupyterlab` 返回 404。不要在公网重新开放这些高权限入口。
|
||||||
|
|
||||||
|
|
||||||
### 2025.1.29
|
### 2025.1.29
|
||||||
|
|
||||||
@@ -582,6 +583,7 @@ cd easyai
|
|||||||
```bash
|
```bash
|
||||||
NUXT_PUBLIC_BASE_APIURL=http://<你的服务器公网IP或者局域网地址>:3001
|
NUXT_PUBLIC_BASE_APIURL=http://<你的服务器公网IP或者局域网地址>:3001
|
||||||
NUXT_PUBLIC_BASE_SOCKETURL=ws://<你的服务器公网IP或者局域网地址>:3002
|
NUXT_PUBLIC_BASE_SOCKETURL=ws://<你的服务器公网IP或者局域网地址>:3002
|
||||||
|
SERVER_HTTP_BIND_IP=0.0.0.0
|
||||||
```
|
```
|
||||||
3. 开放服务器的3001、3002和3010端口
|
3. 开放服务器的3001、3002和3010端口
|
||||||
4. [首次执行,后续无需重复执行]添加脚本的执行权限,命令:
|
4. [首次执行,后续无需重复执行]添加脚本的执行权限,命令:
|
||||||
@@ -619,6 +621,7 @@ powershell.exe -NoProfile -ExecutionPolicy Bypass -File ".\start.ps1"
|
|||||||
```bash
|
```bash
|
||||||
NUXT_PUBLIC_BASE_APIURL=/api
|
NUXT_PUBLIC_BASE_APIURL=/api
|
||||||
NUXT_PUBLIC_BASE_SOCKETURL=wss://<你的域名>/socket.io
|
NUXT_PUBLIC_BASE_SOCKETURL=wss://<你的域名>/socket.io
|
||||||
|
SERVER_HTTP_BIND_IP=127.0.0.1
|
||||||
```
|
```
|
||||||
3. [首次执行,后续无需重复执行]添加执行权限,命令:
|
3. [首次执行,后续无需重复执行]添加执行权限,命令:
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+2
-5
@@ -55,7 +55,7 @@ services:
|
|||||||
labels:
|
labels:
|
||||||
- "com.centurylinklabs.watchtower.enable=true"
|
- "com.centurylinklabs.watchtower.enable=true"
|
||||||
ports:
|
ports:
|
||||||
- "${SERVER_HTTP_PORT}:3001" #http端口
|
- "${SERVER_HTTP_BIND_IP:-127.0.0.1}:${SERVER_HTTP_PORT}:3001" # 域名/本机部署默认仅回环;IP/LAN 模式由启动脚本显式设为 0.0.0.0
|
||||||
# read_only: true
|
# read_only: true
|
||||||
networks:
|
networks:
|
||||||
easyai:
|
easyai:
|
||||||
@@ -341,10 +341,7 @@ services:
|
|||||||
container_name: sandbox
|
container_name: sandbox
|
||||||
networks:
|
networks:
|
||||||
- easyai
|
- easyai
|
||||||
#沙箱环境默认不对外暴露
|
# 不映射宿主机端口,仅允许 Docker 内网中的 easyai-server 访问
|
||||||
ports:
|
|
||||||
# - "${SANDBOX_PORT}:8000"
|
|
||||||
- "${SANDBOX_JUPYTERLAB_PORT:-8888}:8888"
|
|
||||||
labels:
|
labels:
|
||||||
- "com.centurylinklabs.watchtower.enable=true"
|
- "com.centurylinklabs.watchtower.enable=true"
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
+21
-19
@@ -95,6 +95,15 @@ server {
|
|||||||
return 308 /api/;
|
return 308 /api/;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Sandbox 只能由 easyai-server 通过 Docker 内网访问,禁止从公网 API 入口绕过。
|
||||||
|
location = /api/sandbox {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /api/sandbox/ {
|
||||||
|
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 +169,12 @@ 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 /jupyterlab/ {
|
location = /jupyterlab {
|
||||||
proxy_pass http://127.0.0.1:8888/lab/;
|
return 404;
|
||||||
proxy_redirect off;
|
}
|
||||||
proxy_set_header X-Original-Prefix '/lab';
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
location ^~ /jupyterlab/ {
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
return 404;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
}
|
}
|
||||||
location /socket.io {
|
location /socket.io {
|
||||||
proxy_pass http://127.0.0.1:3002;
|
proxy_pass http://127.0.0.1:3002;
|
||||||
@@ -197,17 +204,12 @@ server {
|
|||||||
proxy_buffering off; # 对于 WebSocket 连接禁用缓冲
|
proxy_buffering off; # 对于 WebSocket 连接禁用缓冲
|
||||||
}
|
}
|
||||||
|
|
||||||
# 沙箱环境 API(脚本执行、下载、安装依赖等),需在 docker-compose 中取消 SANDBOX_PORT 映射
|
location = /sandbox {
|
||||||
location /sandbox/ {
|
return 404;
|
||||||
proxy_pass http://127.0.0.1:8081/;
|
}
|
||||||
proxy_read_timeout 300s;
|
|
||||||
client_max_body_size 50M;
|
location ^~ /sandbox/ {
|
||||||
proxy_redirect off;
|
return 404;
|
||||||
proxy_set_header X-Original-Prefix '/sandbox';
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
function Get-ServerHttpBindEnvValue {
|
||||||
|
param([string]$Content, [string]$Key)
|
||||||
|
$match = [regex]::Match($Content, "(?m)^$([regex]::Escape($Key))=([^\r\n]*)$")
|
||||||
|
if ($match.Success) { return $match.Groups[1].Value.Trim() }
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function Set-ServerHttpBindEnvValue {
|
||||||
|
param([string]$Content, [string]$Key, [string]$Value)
|
||||||
|
$line = "$Key=$Value"
|
||||||
|
$pattern = "(?m)^$([regex]::Escape($Key))=.*$"
|
||||||
|
if ($Content -match $pattern) { return ($Content -replace $pattern, $line) }
|
||||||
|
if ($Content -and -not $Content.EndsWith("`n")) { $Content += "`n" }
|
||||||
|
return ($Content + $line + "`n")
|
||||||
|
}
|
||||||
|
|
||||||
|
function Initialize-ServerHttpBindIp {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)][string]$Path,
|
||||||
|
[string]$Override = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not (Test-Path $Path)) { throw "Environment file not found: $Path" }
|
||||||
|
$content = Get-Content $Path -Raw -Encoding UTF8
|
||||||
|
if (-not $content) { $content = "" }
|
||||||
|
|
||||||
|
$current = Get-ServerHttpBindEnvValue $content "SERVER_HTTP_BIND_IP"
|
||||||
|
$target = if (-not [string]::IsNullOrWhiteSpace($Override)) { $Override.Trim() } else { $current }
|
||||||
|
if ([string]::IsNullOrWhiteSpace($target)) {
|
||||||
|
$publicApiUrl = Get-ServerHttpBindEnvValue $content "NUXT_PUBLIC_BASE_APIURL"
|
||||||
|
$target = if (
|
||||||
|
$publicApiUrl -eq "/api" -or
|
||||||
|
$publicApiUrl.StartsWith("/api/") -or
|
||||||
|
$publicApiUrl -match '^https?://(127\.0\.0\.1|localhost)(:\d+)?(?:/|$)'
|
||||||
|
) { "127.0.0.1" } elseif ($publicApiUrl -match '^https?://') { "0.0.0.0" } else { "127.0.0.1" }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($target -notin @("127.0.0.1", "0.0.0.0")) {
|
||||||
|
throw "SERVER_HTTP_BIND_IP only supports 127.0.0.1 or 0.0.0.0, current value: $target"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($current -ne $target) {
|
||||||
|
$content = Set-ServerHttpBindEnvValue $content "SERVER_HTTP_BIND_IP" $target
|
||||||
|
[System.IO.File]::WriteAllText($Path, $content, [System.Text.UTF8Encoding]::new($false))
|
||||||
|
Write-Host " ✓ SERVER_HTTP_BIND_IP=$target"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
. (Join-Path $PSScriptRoot "Initialize-ServerHttpBindIp.ps1")
|
||||||
|
|
||||||
|
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("easyai-bind-test-" + [guid]::NewGuid().ToString("N"))
|
||||||
|
New-Item -ItemType Directory -Path $tempDir | Out-Null
|
||||||
|
$envPath = Join-Path $tempDir ".env"
|
||||||
|
|
||||||
|
function Assert-BindIp {
|
||||||
|
param([string]$PublicApiUrl, [string]$Expected)
|
||||||
|
[System.IO.File]::WriteAllText($envPath, "NUXT_PUBLIC_BASE_APIURL=$PublicApiUrl`n", [System.Text.UTF8Encoding]::new($false))
|
||||||
|
Initialize-ServerHttpBindIp -Path $envPath
|
||||||
|
$content = Get-Content $envPath -Raw -Encoding UTF8
|
||||||
|
if ($content -notmatch "(?m)^SERVER_HTTP_BIND_IP=$([regex]::Escape($Expected))$") {
|
||||||
|
throw "Expected SERVER_HTTP_BIND_IP=$Expected for $PublicApiUrl"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Assert-BindIp "/api" "127.0.0.1"
|
||||||
|
Assert-BindIp "http://10.0.0.8:3001" "0.0.0.0"
|
||||||
|
Assert-BindIp "http://127.0.0.1:3001" "127.0.0.1"
|
||||||
|
|
||||||
|
[System.IO.File]::WriteAllText($envPath, "NUXT_PUBLIC_BASE_APIURL=/api`nSERVER_HTTP_BIND_IP=0.0.0.0`n", [System.Text.UTF8Encoding]::new($false))
|
||||||
|
Initialize-ServerHttpBindIp -Path $envPath
|
||||||
|
if ((Get-Content $envPath -Raw -Encoding UTF8) -notmatch '(?m)^SERVER_HTTP_BIND_IP=0\.0\.0\.0$') {
|
||||||
|
throw "Existing explicit bind value was not preserved"
|
||||||
|
}
|
||||||
|
|
||||||
|
$invalidAccepted = $false
|
||||||
|
try {
|
||||||
|
Initialize-ServerHttpBindIp -Path $envPath -Override "192.168.1.8"
|
||||||
|
$invalidAccepted = $true
|
||||||
|
} catch { }
|
||||||
|
if ($invalidAccepted) { throw "Invalid SERVER_HTTP_BIND_IP unexpectedly accepted" }
|
||||||
|
|
||||||
|
Write-Host "Server HTTP bind IP PowerShell tests passed"
|
||||||
|
} finally {
|
||||||
|
Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
Executable
+83
@@ -0,0 +1,83 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
server_bind_read_env_value() {
|
||||||
|
local file="$1"
|
||||||
|
local key="$2"
|
||||||
|
awk -F= -v key="$key" '$1 == key { sub(/^[^=]*=/, ""); print; exit }' "$file" | tr -d '\r'
|
||||||
|
}
|
||||||
|
|
||||||
|
server_bind_write_env_value() {
|
||||||
|
local file="$1"
|
||||||
|
local key="$2"
|
||||||
|
local value="$3"
|
||||||
|
local tmp_file
|
||||||
|
tmp_file="$(mktemp "${file}.XXXXXX")"
|
||||||
|
awk -v key="$key" -v value="$value" '
|
||||||
|
BEGIN { replaced = 0 }
|
||||||
|
$0 ~ "^" key "=" {
|
||||||
|
if (!replaced) {
|
||||||
|
print key "=" value
|
||||||
|
replaced = 1
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
END {
|
||||||
|
if (!replaced) print key "=" value
|
||||||
|
}
|
||||||
|
' "$file" > "$tmp_file"
|
||||||
|
chmod --reference="$file" "$tmp_file" 2>/dev/null || true
|
||||||
|
mv "$tmp_file" "$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
infer_server_http_bind_ip() {
|
||||||
|
local file="$1"
|
||||||
|
local public_api_url
|
||||||
|
public_api_url="$(server_bind_read_env_value "$file" "NUXT_PUBLIC_BASE_APIURL")"
|
||||||
|
|
||||||
|
case "$public_api_url" in
|
||||||
|
/api|/api/*|http://127.0.0.1:*|https://127.0.0.1:*|http://localhost:*|https://localhost:*)
|
||||||
|
printf '%s\n' "127.0.0.1"
|
||||||
|
;;
|
||||||
|
http://*|https://*)
|
||||||
|
printf '%s\n' "0.0.0.0"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf '%s\n' "127.0.0.1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
init_server_http_bind_ip() {
|
||||||
|
local file="${1:-.env}"
|
||||||
|
local override="${2:-}"
|
||||||
|
local current target
|
||||||
|
|
||||||
|
if [ ! -f "$file" ]; then
|
||||||
|
echo "❌ 未找到环境文件: $file" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
current="$(server_bind_read_env_value "$file" "SERVER_HTTP_BIND_IP")"
|
||||||
|
target="${override:-$current}"
|
||||||
|
if [ -z "$target" ]; then
|
||||||
|
target="$(infer_server_http_bind_ip "$file")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$target" in
|
||||||
|
127.0.0.1|0.0.0.0) ;;
|
||||||
|
*)
|
||||||
|
echo "❌ SERVER_HTTP_BIND_IP 仅支持 127.0.0.1 或 0.0.0.0,当前为: $target" >&2
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$current" != "$target" ]; then
|
||||||
|
server_bind_write_env_value "$file" "SERVER_HTTP_BIND_IP" "$target"
|
||||||
|
echo " ✓ SERVER_HTTP_BIND_IP=$target"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
||||||
|
init_server_http_bind_ip "${1:-.env}" "${2:-}"
|
||||||
|
fi
|
||||||
@@ -20,6 +20,7 @@ mkdir -p "$TMP_DIR/scripts"
|
|||||||
cp \
|
cp \
|
||||||
"$REPO_ROOT/scripts/init-security-env.sh" \
|
"$REPO_ROOT/scripts/init-security-env.sh" \
|
||||||
"$REPO_ROOT/scripts/init-public-api-base-url.sh" \
|
"$REPO_ROOT/scripts/init-public-api-base-url.sh" \
|
||||||
|
"$REPO_ROOT/scripts/init-server-http-bind-ip.sh" \
|
||||||
"$TMP_DIR/scripts/"
|
"$TMP_DIR/scripts/"
|
||||||
|
|
||||||
cd "$TMP_DIR"
|
cd "$TMP_DIR"
|
||||||
@@ -48,6 +49,34 @@ assert_compression_config() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert_sandbox_public_access_disabled() {
|
||||||
|
local config_file="$1"
|
||||||
|
grep -q '^ location = /api/sandbox {' "$config_file"
|
||||||
|
grep -q '^ location \^~ /api/sandbox/ {' "$config_file"
|
||||||
|
grep -q '^ location = /jupyterlab {' "$config_file"
|
||||||
|
grep -q '^ location \^~ /jupyterlab/ {' "$config_file"
|
||||||
|
grep -q '^ location = /sandbox {' "$config_file"
|
||||||
|
grep -q '^ location \^~ /sandbox/ {' "$config_file"
|
||||||
|
if grep -Eq 'proxy_pass http://127\.0\.0\.1:(8081|8888)' "$config_file"; then
|
||||||
|
echo "Unexpected public Sandbox/Jupyter proxy in $config_file" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_compose_exposure() {
|
||||||
|
local expected_server_host_ip="$1"
|
||||||
|
docker compose config --format json | python3 -c '
|
||||||
|
import json, sys
|
||||||
|
expected = sys.argv[1]
|
||||||
|
config = json.load(sys.stdin)
|
||||||
|
sandbox_ports = config["services"]["sandbox"].get("ports") or []
|
||||||
|
assert sandbox_ports == [], f"sandbox ports published: {sandbox_ports}"
|
||||||
|
server_ports = config["services"]["easyai-server"].get("ports") or []
|
||||||
|
assert len(server_ports) == 1, server_ports
|
||||||
|
assert server_ports[0].get("host_ip") == expected, server_ports
|
||||||
|
' "$expected_server_host_ip"
|
||||||
|
}
|
||||||
|
|
||||||
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 \
|
||||||
@@ -57,8 +86,9 @@ DEPLOY_NON_INTERACTIVE=1 \
|
|||||||
bash start.sh >/dev/null
|
bash start.sh >/dev/null
|
||||||
grep -qx 'NUXT_PUBLIC_BASE_APIURL=http://10.0.0.8:4100' .env
|
grep -qx 'NUXT_PUBLIC_BASE_APIURL=http://10.0.0.8:4100' .env
|
||||||
grep -qx 'CONFIG_PUBLIC_API_BASE_URL=http://10.0.0.8:4100' .env
|
grep -qx 'CONFIG_PUBLIC_API_BASE_URL=http://10.0.0.8:4100' .env
|
||||||
|
grep -qx 'SERVER_HTTP_BIND_IP=0.0.0.0' .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
|
||||||
docker compose config --quiet
|
assert_compose_exposure 0.0.0.0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
reset_case
|
reset_case
|
||||||
@@ -75,6 +105,11 @@ grep -q 'proxy_set_header X-Forwarded-Port $server_port;' demo.example.com.conf
|
|||||||
grep -q "proxy_set_header X-Original-Prefix '/api';" demo.example.com.conf
|
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
|
||||||
|
grep -qx 'SERVER_HTTP_BIND_IP=127.0.0.1' .env
|
||||||
|
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
|
||||||
|
assert_compose_exposure 127.0.0.1
|
||||||
|
fi
|
||||||
|
|
||||||
# 直接验证 https.sh 的缺省配置生成函数会采用完整模板,而不是只代理 3010。
|
# 直接验证 https.sh 的缺省配置生成函数会采用完整模板,而不是只代理 3010。
|
||||||
eval "$(awk '
|
eval "$(awk '
|
||||||
@@ -90,6 +125,7 @@ grep -q 'proxy_pass http://127.0.0.1:3001/;' easyai-proxy.conf
|
|||||||
grep -q 'location /socket.io {' easyai-proxy.conf
|
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
|
||||||
|
|
||||||
reset_case
|
reset_case
|
||||||
DEPLOY_NON_INTERACTIVE=1 \
|
DEPLOY_NON_INTERACTIVE=1 \
|
||||||
|
|||||||
Executable
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
TMP_DIR="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||||
|
|
||||||
|
# shellcheck source=init-server-http-bind-ip.sh
|
||||||
|
. "$REPO_ROOT/scripts/init-server-http-bind-ip.sh"
|
||||||
|
|
||||||
|
write_env() {
|
||||||
|
printf '%s\n' "$1" > "$TMP_DIR/.env"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_env 'NUXT_PUBLIC_BASE_APIURL=/api'
|
||||||
|
init_server_http_bind_ip "$TMP_DIR/.env" >/dev/null
|
||||||
|
grep -qx 'SERVER_HTTP_BIND_IP=127.0.0.1' "$TMP_DIR/.env"
|
||||||
|
|
||||||
|
write_env 'NUXT_PUBLIC_BASE_APIURL=http://10.0.0.8:3001'
|
||||||
|
init_server_http_bind_ip "$TMP_DIR/.env" >/dev/null
|
||||||
|
grep -qx 'SERVER_HTTP_BIND_IP=0.0.0.0' "$TMP_DIR/.env"
|
||||||
|
|
||||||
|
write_env 'NUXT_PUBLIC_BASE_APIURL=http://127.0.0.1:3001'
|
||||||
|
init_server_http_bind_ip "$TMP_DIR/.env" >/dev/null
|
||||||
|
grep -qx 'SERVER_HTTP_BIND_IP=127.0.0.1' "$TMP_DIR/.env"
|
||||||
|
|
||||||
|
write_env $'NUXT_PUBLIC_BASE_APIURL=/api\nSERVER_HTTP_BIND_IP=0.0.0.0'
|
||||||
|
init_server_http_bind_ip "$TMP_DIR/.env" >/dev/null
|
||||||
|
grep -qx 'SERVER_HTTP_BIND_IP=0.0.0.0' "$TMP_DIR/.env"
|
||||||
|
|
||||||
|
if init_server_http_bind_ip "$TMP_DIR/.env" '192.168.1.8' >/dev/null 2>&1; then
|
||||||
|
echo 'Invalid SERVER_HTTP_BIND_IP unexpectedly accepted' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo 'Server HTTP bind IP shell tests passed'
|
||||||
@@ -173,6 +173,8 @@ function Setup-EnvFiles {
|
|||||||
$content = Upsert-Env $content "NUXT_PUBLIC_BASE_APIURL" $publicApiBaseUrl
|
$content = Upsert-Env $content "NUXT_PUBLIC_BASE_APIURL" $publicApiBaseUrl
|
||||||
$content = Upsert-Env $content "NUXT_PUBLIC_BASE_SOCKETURL" "ws://$($script:DeployIP):3002"
|
$content = Upsert-Env $content "NUXT_PUBLIC_BASE_SOCKETURL" "ws://$($script:DeployIP):3002"
|
||||||
$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" }
|
||||||
|
$content = Upsert-Env $content "SERVER_HTTP_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"
|
||||||
@@ -386,6 +388,18 @@ function Main {
|
|||||||
Ensure-FileFromSample ".env.AMS" ".env.AMS.sample"
|
Ensure-FileFromSample ".env.AMS" ".env.AMS.sample"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
. (Join-Path $script:Root "scripts\Initialize-ServerHttpBindIp.ps1")
|
||||||
|
$serverBindOverride = if ($script:SkipDeployQuestions) {
|
||||||
|
""
|
||||||
|
} elseif ($script:DeployIP -eq "127.0.0.1" -or $script:DeployIP -eq "localhost") {
|
||||||
|
"127.0.0.1"
|
||||||
|
} else {
|
||||||
|
"0.0.0.0"
|
||||||
|
}
|
||||||
|
Initialize-ServerHttpBindIp `
|
||||||
|
-Path (Join-Path $script:Root ".env") `
|
||||||
|
-Override $serverBindOverride
|
||||||
|
|
||||||
. (Join-Path $script:Root "scripts\Initialize-SecurityEnv.ps1")
|
. (Join-Path $script:Root "scripts\Initialize-SecurityEnv.ps1")
|
||||||
Initialize-SecurityEnv -Path (Join-Path $script:Root ".env") -Mode $script:SecurityEnvMode
|
Initialize-SecurityEnv -Path (Join-Path $script:Root ".env") -Mode $script:SecurityEnvMode
|
||||||
|
|
||||||
|
|||||||
@@ -495,6 +495,20 @@ main() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 域名和本机部署默认仅绑定回环;只有明确的 IP/LAN 直连模式才开放后端宿主机端口。
|
||||||
|
# shellcheck source=scripts/init-server-http-bind-ip.sh
|
||||||
|
. ./scripts/init-server-http-bind-ip.sh
|
||||||
|
local server_bind_override=""
|
||||||
|
if [ "$DEPLOY_MODE" = "domain" ]; then
|
||||||
|
server_bind_override="127.0.0.1"
|
||||||
|
elif [ "$DEPLOY_MODE" = "ip" ]; then
|
||||||
|
case "$DEPLOY_IP" in
|
||||||
|
127.0.0.1|localhost) server_bind_override="127.0.0.1" ;;
|
||||||
|
*) server_bind_override="0.0.0.0" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
init_server_http_bind_ip .env "$server_bind_override"
|
||||||
|
|
||||||
# 空值或历史默认值只在首次发现时生成并写回 .env,后续启动保持稳定。
|
# 空值或历史默认值只在首次发现时生成并写回 .env,后续启动保持稳定。
|
||||||
# 数据库中的后台 JWT 轮转配置由后端优先使用,不受此处影响。
|
# 数据库中的后台 JWT 轮转配置由后端优先使用,不受此处影响。
|
||||||
# shellcheck source=scripts/init-security-env.sh
|
# shellcheck source=scripts/init-security-env.sh
|
||||||
|
|||||||
@@ -174,6 +174,9 @@ if (-not $skipRepoUpdate) {
|
|||||||
|
|
||||||
# 空值或历史默认值只初始化一次并写回 .env;已有自定义值及数据库轮转配置不受影响。
|
# 空值或历史默认值只初始化一次并写回 .env;已有自定义值及数据库轮转配置不受影响。
|
||||||
if (-not (Test-Path ".env")) { Write-Err "未找到 .env,请先执行 start.ps1 完成初始化" }
|
if (-not (Test-Path ".env")) { Write-Err "未找到 .env,请先执行 start.ps1 完成初始化" }
|
||||||
|
. (Join-Path $scriptDir "scripts\Initialize-ServerHttpBindIp.ps1")
|
||||||
|
Initialize-ServerHttpBindIp -Path (Join-Path $scriptDir ".env")
|
||||||
|
|
||||||
. (Join-Path $scriptDir "scripts\Initialize-SecurityEnv.ps1")
|
. (Join-Path $scriptDir "scripts\Initialize-SecurityEnv.ps1")
|
||||||
Initialize-SecurityEnv -Path (Join-Path $scriptDir ".env") -Mode "upgrade"
|
Initialize-SecurityEnv -Path (Join-Path $scriptDir ".env") -Mode "upgrade"
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,12 @@ if [ ! -f .env ]; then
|
|||||||
echo "❌ 未找到 .env,请先执行 start.sh 完成初始化"
|
echo "❌ 未找到 .env,请先执行 start.sh 完成初始化"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 老部署缺少绑定配置时,根据现有公开 API 地址迁移:域名/本机保持回环,IP 直连保持可用。
|
||||||
|
# shellcheck source=scripts/init-server-http-bind-ip.sh
|
||||||
|
. ./scripts/init-server-http-bind-ip.sh
|
||||||
|
init_server_http_bind_ip .env
|
||||||
|
|
||||||
# shellcheck source=scripts/init-security-env.sh
|
# shellcheck source=scripts/init-security-env.sh
|
||||||
. ./scripts/init-security-env.sh
|
. ./scripts/init-security-env.sh
|
||||||
init_security_env .env upgrade
|
init_security_env .env upgrade
|
||||||
|
|||||||
Reference in New Issue
Block a user