diff --git a/.env.sample b/.env.sample index 6e1627b..84eb585 100644 --- a/.env.sample +++ b/.env.sample @@ -30,6 +30,9 @@ WEB_PORT=3010 SERVER_HTTP_PORT=3001 # 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 # easyai-server 的 Node.js V8 old-space 堆上限,单位 MiB @@ -66,14 +69,11 @@ MEMORY_TCP_PORT=4004 # 记忆整理相关参数在 .env.AMS 中配置(MEMORY_MAINTENANCE_*) # ========== 4. 沙箱环境 ========== -SANDBOX_PORT=8081 -# 不建议对外暴露 - -SANDBOX_JUPYTERLAB_PORT=8888 SANDBOX_JUPYTER_TOKEN=easyaiisbest -# 建议设置 token +# Sandbox API 与 JupyterLab 默认不映射宿主机端口;token 仅用于容器内 Jupyter 认证 SANDBOX_SERVICE_BASE_URL= +# 留空时 easyai-server 通过 Docker 内网 http://sandbox:8000 访问 # ========== 5. Redis ========== REDIS_PORT= diff --git a/.github/workflows/test-deployment-public-url.yml b/.github/workflows/test-deployment-public-url.yml index c170067..d78cd84 100644 --- a/.github/workflows/test-deployment-public-url.yml +++ b/.github/workflows/test-deployment-public-url.yml @@ -10,6 +10,8 @@ on: - '.env.sample' - 'scripts/init-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' - '.github/workflows/test-deployment-public-url.yml' pull_request: @@ -21,6 +23,8 @@ on: - '.env.sample' - 'scripts/init-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' - '.github/workflows/test-deployment-public-url.yml' workflow_dispatch: @@ -33,10 +37,13 @@ jobs: - name: Check shell syntax 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 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 run: bash scripts/test-deployment-public-url.sh diff --git a/.github/workflows/test-start-ps1.yml b/.github/workflows/test-start-ps1.yml index 353131b..39eb0f1 100644 --- a/.github/workflows/test-start-ps1.yml +++ b/.github/workflows/test-start-ps1.yml @@ -8,6 +8,8 @@ on: - 'update.ps1' - 'scripts/Initialize-PublicApiBaseUrl.ps1' - 'scripts/Test-PublicApiBaseUrl.ps1' + - 'scripts/Initialize-ServerHttpBindIp.ps1' + - 'scripts/Test-ServerHttpBindIp.ps1' - '.env.sample' - '.github/workflows/test-start-ps1.yml' pull_request: @@ -16,6 +18,8 @@ on: - 'update.ps1' - 'scripts/Initialize-PublicApiBaseUrl.ps1' - 'scripts/Test-PublicApiBaseUrl.ps1' + - 'scripts/Initialize-ServerHttpBindIp.ps1' + - 'scripts/Test-ServerHttpBindIp.ps1' - '.env.sample' - '.github/workflows/test-start-ps1.yml' workflow_dispatch: @@ -40,12 +44,18 @@ jobs: $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 } $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 ($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 ($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 配置正确" - name: Test public API URL migration helper run: | powershell -ExecutionPolicy Bypass -File .\scripts\Test-PublicApiBaseUrl.ps1 + + - name: Test server HTTP bind migration helper + run: | + powershell -ExecutionPolicy Bypass -File .\scripts\Test-ServerHttpBindIp.ps1 diff --git a/README.md b/README.md index 04b61db..4200c21 100644 --- a/README.md +++ b/README.md @@ -455,10 +455,11 @@ curl https://<你的域名>/asg-api/health 1. 增加脚本沙箱环境容器,需要更新`docker-compose.yml`,用以支持SKILL中的脚本运行 2. 增加环境变量配置 ```dotenv -SANDBOX_PORT=8081 #对外暴露沙箱环境的端口,不建议暴露,权限较高 -SANDBOX_SERVICE_BASE_URL= #脚本运行环境使用的服务地址,默认为通过内网直接访问http://sandbox:8000,不需要配置。当将sandbox部署在其他外部网络才需要配置 +SANDBOX_SERVICE_BASE_URL= #脚本运行环境使用的服务地址,默认通过 Docker 内网访问 http://sandbox:8000,不需要配置 ``` +> 安全默认值:Sandbox API 与 JupyterLab 不映射宿主机端口,Nginx 模板对 `/api/sandbox`、`/sandbox`、`/jupyterlab` 返回 404。不要在公网重新开放这些高权限入口。 + ### 2025.1.29 @@ -582,6 +583,7 @@ cd easyai ```bash 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. [首次执行,后续无需重复执行]添加脚本的执行权限,命令: @@ -619,6 +621,7 @@ powershell.exe -NoProfile -ExecutionPolicy Bypass -File ".\start.ps1" ```bash NUXT_PUBLIC_BASE_APIURL=/api NUXT_PUBLIC_BASE_SOCKETURL=wss://<你的域名>/socket.io +SERVER_HTTP_BIND_IP=127.0.0.1 ``` 3. [首次执行,后续无需重复执行]添加执行权限,命令: ```bash diff --git a/docker-compose.yml b/docker-compose.yml index b15cb08..70e78fc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -55,7 +55,7 @@ services: labels: - "com.centurylinklabs.watchtower.enable=true" 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 networks: easyai: @@ -341,10 +341,7 @@ services: container_name: sandbox networks: - easyai - #沙箱环境默认不对外暴露 - ports: -# - "${SANDBOX_PORT}:8000" - - "${SANDBOX_JUPYTERLAB_PORT:-8888}:8888" + # 不映射宿主机端口,仅允许 Docker 内网中的 easyai-server 访问 labels: - "com.centurylinklabs.watchtower.enable=true" volumes: diff --git a/easyai-proxy.conf.sample b/easyai-proxy.conf.sample index 5997790..f9b3482 100644 --- a/easyai-proxy.conf.sample +++ b/easyai-proxy.conf.sample @@ -95,6 +95,15 @@ server { return 308 /api/; } + # Sandbox 只能由 easyai-server 通过 Docker 内网访问,禁止从公网 API 入口绕过。 + location = /api/sandbox { + return 404; + } + + location ^~ /api/sandbox/ { + return 404; + } + location /api/ { proxy_pass http://127.0.0.1:3001/; proxy_read_timeout 1200s; # 您已设置,适合长连接 @@ -160,14 +169,12 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $host; } - location /jupyterlab/ { - proxy_pass http://127.0.0.1:8888/lab/; - proxy_redirect off; - proxy_set_header X-Original-Prefix '/lab'; - 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 = /jupyterlab { + return 404; + } + + location ^~ /jupyterlab/ { + return 404; } location /socket.io { proxy_pass http://127.0.0.1:3002; @@ -197,17 +204,12 @@ server { proxy_buffering off; # 对于 WebSocket 连接禁用缓冲 } - # 沙箱环境 API(脚本执行、下载、安装依赖等),需在 docker-compose 中取消 SANDBOX_PORT 映射 - location /sandbox/ { - proxy_pass http://127.0.0.1:8081/; - proxy_read_timeout 300s; - client_max_body_size 50M; - proxy_redirect off; - 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; + location = /sandbox { + return 404; + } + + location ^~ /sandbox/ { + return 404; } } diff --git a/scripts/Initialize-ServerHttpBindIp.ps1 b/scripts/Initialize-ServerHttpBindIp.ps1 new file mode 100644 index 0000000..821d626 --- /dev/null +++ b/scripts/Initialize-ServerHttpBindIp.ps1 @@ -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" + } +} diff --git a/scripts/Test-ServerHttpBindIp.ps1 b/scripts/Test-ServerHttpBindIp.ps1 new file mode 100644 index 0000000..e6accc8 --- /dev/null +++ b/scripts/Test-ServerHttpBindIp.ps1 @@ -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 +} diff --git a/scripts/init-server-http-bind-ip.sh b/scripts/init-server-http-bind-ip.sh new file mode 100755 index 0000000..d5892a8 --- /dev/null +++ b/scripts/init-server-http-bind-ip.sh @@ -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 diff --git a/scripts/test-deployment-public-url.sh b/scripts/test-deployment-public-url.sh index 298c102..f77d9ac 100755 --- a/scripts/test-deployment-public-url.sh +++ b/scripts/test-deployment-public-url.sh @@ -20,6 +20,7 @@ mkdir -p "$TMP_DIR/scripts" cp \ "$REPO_ROOT/scripts/init-security-env.sh" \ "$REPO_ROOT/scripts/init-public-api-base-url.sh" \ + "$REPO_ROOT/scripts/init-server-http-bind-ip.sh" \ "$TMP_DIR/scripts/" cd "$TMP_DIR" @@ -48,6 +49,34 @@ assert_compression_config() { 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 rm -f .env.sample.bak DEPLOY_NON_INTERACTIVE=1 \ @@ -57,8 +86,9 @@ DEPLOY_NON_INTERACTIVE=1 \ bash start.sh >/dev/null 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 '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 - docker compose config --quiet + assert_compose_exposure 0.0.0.0 fi 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 'location = /api {' 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。 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 'proxy_set_header X-Forwarded-Host $easyai_forwarded_host;' easyai-proxy.conf assert_compression_config easyai-proxy.conf +assert_sandbox_public_access_disabled easyai-proxy.conf reset_case DEPLOY_NON_INTERACTIVE=1 \ diff --git a/scripts/test-server-http-bind-ip.sh b/scripts/test-server-http-bind-ip.sh new file mode 100755 index 0000000..bceb24a --- /dev/null +++ b/scripts/test-server-http-bind-ip.sh @@ -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' diff --git a/start.ps1 b/start.ps1 index 2edec15..6b96d9c 100644 --- a/start.ps1 +++ b/start.ps1 @@ -173,6 +173,8 @@ function Setup-EnvFiles { $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_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+)$') $webPort = if ($webPortMatch.Success) { $webPortMatch.Groups[1].Value } else { "3010" } $content = Upsert-Env $content "CONFIG_SECURITY_ORIGIN" "http://$($script:DeployIP):$webPort" @@ -386,6 +388,18 @@ function Main { 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") Initialize-SecurityEnv -Path (Join-Path $script:Root ".env") -Mode $script:SecurityEnvMode diff --git a/start.sh b/start.sh index 8c3683b..ca95591 100755 --- a/start.sh +++ b/start.sh @@ -495,6 +495,20 @@ main() { 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,后续启动保持稳定。 # 数据库中的后台 JWT 轮转配置由后端优先使用,不受此处影响。 # shellcheck source=scripts/init-security-env.sh diff --git a/update.ps1 b/update.ps1 index 98dad21..3647e8b 100644 --- a/update.ps1 +++ b/update.ps1 @@ -174,6 +174,9 @@ if (-not $skipRepoUpdate) { # 空值或历史默认值只初始化一次并写回 .env;已有自定义值及数据库轮转配置不受影响。 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") Initialize-SecurityEnv -Path (Join-Path $scriptDir ".env") -Mode "upgrade" diff --git a/update.sh b/update.sh index 5f3dc02..4803860 100755 --- a/update.sh +++ b/update.sh @@ -128,6 +128,12 @@ if [ ! -f .env ]; then echo "❌ 未找到 .env,请先执行 start.sh 完成初始化" exit 1 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 . ./scripts/init-security-env.sh init_security_env .env upgrade