fix(deploy): 明确提示初始管理员密码

This commit is contained in:
2026-08-25 16:22:46 +08:00
parent 8cd1fb137d
commit 7dc4313de6
8 changed files with 114 additions and 12 deletions
+3 -2
View File
@@ -144,8 +144,9 @@ CONFIG_TRUSTED_PROXY=
# 完成身份冲突扫描和人工处置后才能启用手机号、邮箱和微信身份唯一索引。 # 完成身份冲突扫描和人工处置后才能启用手机号、邮箱和微信身份唯一索引。
CONFIG_AUTH_UNIQUE_INDEXES_ENABLED=false CONFIG_AUTH_UNIQUE_INDEXES_ENABLED=false
# 仅在首次创建 admin 账号时使用;启动脚本会生成 20 位字母数字随机值并持久化到 .env。 # 仅在数据库首次创建 admin 账号时使用;启动脚本会生成 20 位字母数字随机值并持久化到 .env。
# 已存在 admin 的升级环境不会使用或修改现有管理员密码 # 首次部署完成页会明确显示登录账号和密码;初始密码没有独立过期时间,首次登录后请立即修改
# admin 创建后不再使用该值;修改 .env 或执行升级不会重置现有管理员密码。
CONFIG_INITIAL_ADMIN_PASSWORD= CONFIG_INITIAL_ADMIN_PASSWORD=
CONFIG_TOKEN_SIGN_SK=easyai2025easyai CONFIG_TOKEN_SIGN_SK=easyai2025easyai
@@ -12,6 +12,7 @@ on:
- 'scripts/test-public-api-base-url.sh' - 'scripts/test-public-api-base-url.sh'
- 'scripts/init-server-http-bind-ip.sh' - 'scripts/init-server-http-bind-ip.sh'
- 'scripts/test-server-http-bind-ip.sh' - 'scripts/test-server-http-bind-ip.sh'
- 'scripts/init-security-env.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:
@@ -25,6 +26,7 @@ on:
- 'scripts/test-public-api-base-url.sh' - 'scripts/test-public-api-base-url.sh'
- 'scripts/init-server-http-bind-ip.sh' - 'scripts/init-server-http-bind-ip.sh'
- 'scripts/test-server-http-bind-ip.sh' - 'scripts/test-server-http-bind-ip.sh'
- 'scripts/init-security-env.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:
@@ -37,7 +39,7 @@ 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/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 bash -n start.sh update.sh scripts/init-security-env.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
+11 -1
View File
@@ -36,7 +36,17 @@ jobs:
DEPLOY_IP: "192.168.1.100" DEPLOY_IP: "192.168.1.100"
run: | run: |
(Get-Content .env.sample) -replace '^SERVER_HTTP_PORT=.*$', 'SERVER_HTTP_PORT=4100' | Set-Content .env.sample -Encoding UTF8 (Get-Content .env.sample) -replace '^SERVER_HTTP_PORT=.*$', 'SERVER_HTTP_PORT=4100' | Set-Content .env.sample -Encoding UTF8
powershell -ExecutionPolicy Bypass -File .\start.ps1 $output = powershell -ExecutionPolicy Bypass -File .\start.ps1 2>&1 | Out-String
if ($LASTEXITCODE -ne 0) { Write-Host $output; exit $LASTEXITCODE }
$adminPassword = (Select-String -Path .env -Pattern '^CONFIG_INITIAL_ADMIN_PASSWORD=' | ForEach-Object { $_.Line.Substring('CONFIG_INITIAL_ADMIN_PASSWORD='.Length) })
if ([string]::IsNullOrWhiteSpace($adminPassword)) { throw 'Initial admin password was not generated' }
if (-not $output.Contains('登录账号: admin')) { throw 'Deployment output does not show the admin username' }
if (-not $output.Contains("登录密码: $adminPassword")) { throw 'Deployment output does not show the generated admin password' }
if (-not $output.Contains('初始密码没有独立过期时间')) { throw 'Deployment output does not explain the initial password lifetime' }
$existingOutput = powershell -ExecutionPolicy Bypass -File .\start.ps1 2>&1 | Out-String
if ($LASTEXITCODE -ne 0) { Write-Host $existingOutput; exit $LASTEXITCODE }
if (-not $existingOutput.Contains('本次部署不会生成或重置已有密码')) { throw 'Existing-env output does not explain password preservation' }
if ($existingOutput.Contains("登录密码: $adminPassword")) { throw 'Existing-env output must not report the env value as the current password' }
- name: Verify .env - name: Verify .env
run: | run: |
+14 -1
View File
@@ -38,7 +38,20 @@ DEPLOY_ACCESS=domain DEPLOY_DOMAIN=demo.example.com DEPLOY_HTTPS=true CERTBOT_EM
`start.sh` / `start.ps1``update.sh` / `update.ps1` 会自动初始化并持久化鉴权安全密钥。首次安装或存量升级发现 `WS_AUTH_WS_TICKET_SECRET` 缺失、过短时,会生成新的高强度随机值,并确保 `WS_AUTH_METHODS` 包含 `ws_ticket`;Compose 会把同一密钥同时注入 `easyai-server``ws-gateway`。已有合格密钥会原样保留,重复启动或升级不会轮换,用户无需手动填写或复制密钥。 `start.sh` / `start.ps1``update.sh` / `update.ps1` 会自动初始化并持久化鉴权安全密钥。首次安装或存量升级发现 `WS_AUTH_WS_TICKET_SECRET` 缺失、过短时,会生成新的高强度随机值,并确保 `WS_AUTH_METHODS` 包含 `ws_ticket`;Compose 会把同一密钥同时注入 `easyai-server``ws-gateway`。已有合格密钥会原样保留,重复启动或升级不会轮换,用户无需手动填写或复制密钥。
生成结果仅保存在本机 `.env`,脚本日志只显示变量名而不会输出密钥内容。不要把 `.env` 提交到 Git;集群部署时,各 `easyai-server` / `ws-gateway` 实例必须使用同一份持久化配置。 生成结果仅保存在本机 `.env`。除首次安装完成页会明确显示登录用的初始管理员密码外,脚本日志对其他安全密钥只显示变量名,不输出密钥内容。不要把 `.env` 提交到 Git;集群部署时,各 `easyai-server` / `ws-gateway` 实例必须使用同一份持久化配置。
#### 初始管理员密码生命周期
首次安装时,部署脚本会生成 20 位随机密码,写入 `.env``CONFIG_INITIAL_ADMIN_PASSWORD`,并在部署完成页直接显示:
```text
登录账号: admin
登录密码: <本次部署生成的随机密码>
```
后端只在数据库中不存在 `admin` 时读取该值并创建管理员。创建成功后,`.env` 中的值不再用于登录校验,修改该变量或执行升级都不会重置现有管理员密码。初始密码没有独立的自动过期时间,在管理员主动修改前持续有效,因此首次登录后应立即修改密码。
如果部署脚本检测到已有 `.env`,完成页不会把其中的值误报成当前密码:数据库已有 `admin` 时继续使用现有密码;数据库尚未创建 `admin` 时,可查看 `.env -> CONFIG_INITIAL_ADMIN_PASSWORD`
#### 后端公开 API 地址 #### 后端公开 API 地址
+9
View File
@@ -156,6 +156,15 @@ powershell -ExecutionPolicy Bypass -File .\start.ps1
- 本地访问:`http://127.0.0.1:3010` - 本地访问:`http://127.0.0.1:3010`
- 局域网访问:`http://<LAN_IP>:3010` - 局域网访问:`http://<LAN_IP>:3010`
首次安装还会在完成页直接显示管理员登录信息:
```text
登录账号: admin
登录密码: <本次部署生成的 20 位随机密码>
```
该密码只在数据库首次创建 `admin` 时使用,创建后修改 `.env` 不会重置现有密码。初始密码没有独立过期时间,首次登录后应立即修改。已有 `.env` 的部署只提示密码沿用规则,不会把可能已经失效的环境变量值显示成当前登录密码。
--- ---
## 7. 实现清单 ## 7. 实现清单
+20 -1
View File
@@ -98,10 +98,29 @@ DEPLOY_NON_INTERACTIVE=1 \
DEPLOY_DRY_RUN=1 \ DEPLOY_DRY_RUN=1 \
DEPLOY_ACCESS=ip \ DEPLOY_ACCESS=ip \
DEPLOY_IP=10.0.0.8 \ DEPLOY_IP=10.0.0.8 \
bash start.sh >/dev/null 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=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 grep -qx 'SERVER_HTTP_BIND_IP=0.0.0.0' .env
initial_admin_password="$(awk -F= '$1 == "CONFIG_INITIAL_ADMIN_PASSWORD" { print $2; exit }' .env)"
[ "${#initial_admin_password}" -ge 12 ] || {
echo "Initial admin password was not generated" >&2
exit 1
}
grep -Fq '登录账号: admin' "$TMP_DIR/first-install.log"
grep -Fq "登录密码: ${initial_admin_password}" "$TMP_DIR/first-install.log"
grep -Fq '生命周期: 仅在数据库首次创建 admin 时使用;创建后修改 .env 不会重置密码。' "$TMP_DIR/first-install.log"
grep -Fq '有效期: 初始密码没有独立过期时间,在管理员修改密码前持续有效。' "$TMP_DIR/first-install.log"
DEPLOY_NON_INTERACTIVE=1 \
DEPLOY_DRY_RUN=1 \
DEPLOY_ACCESS=ip \
DEPLOY_IP=10.0.0.8 \
bash start.sh > "$TMP_DIR/existing-env.log"
grep -Fq '管理员账号: admin(本次部署不会生成或重置已有密码)' "$TMP_DIR/existing-env.log"
if grep -Fq "登录密码: ${initial_admin_password}" "$TMP_DIR/existing-env.log"; then
echo "Existing-env deployment must not report CONFIG_INITIAL_ADMIN_PASSWORD as the current login password" >&2
exit 1
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 0.0.0.0
assert_canvas_ws_auth_config assert_canvas_ws_auth_config
+27 -6
View File
@@ -358,6 +358,32 @@ function Start-Services {
Ok "EasyAI started" Ok "EasyAI started"
} }
function Show-AdminLoginInfo {
Write-Host ""
if ($script:SecurityEnvMode -ne "new") {
Write-Host " 管理员账号: admin(本次部署不会生成或重置已有密码)" -ForegroundColor Yellow
Write-Host " 登录提示: 若数据库尚未创建 admin,请查看 .env -> CONFIG_INITIAL_ADMIN_PASSWORD;若 admin 已存在,请使用当前密码。" -ForegroundColor DarkGray
return
}
$content = Get-Content (Join-Path $script:Root ".env") -Raw -Encoding UTF8
$initialAdminPassword = Get-EnvValue $content "CONFIG_INITIAL_ADMIN_PASSWORD"
if ([string]::IsNullOrWhiteSpace($initialAdminPassword)) {
Fail "Cannot read initial admin password from .env -> CONFIG_INITIAL_ADMIN_PASSWORD"
}
$heading = if ($script:DeployDryRun) {
" -------- 初始管理员(Docker 首次启动后生效)--------"
} else {
" -------- 初始管理员(首次登录后请立即修改密码)--------"
}
Write-Host $heading -ForegroundColor Yellow
Write-Host " 登录账号: " -NoNewline; Write-Host "admin" -ForegroundColor White
Write-Host " 登录密码: " -NoNewline; Write-Host $initialAdminPassword -ForegroundColor White
Write-Host " 生命周期: 仅在数据库首次创建 admin 时使用;创建后修改 .env 不会重置密码。" -ForegroundColor DarkGray
Write-Host " 有效期: 初始密码没有独立过期时间,在管理员修改密码前持续有效。" -ForegroundColor DarkGray
}
function Main { function Main {
Init-ProjectDir Init-ProjectDir
@@ -425,19 +451,14 @@ function Main {
Write-Host "================================" -ForegroundColor Yellow Write-Host "================================" -ForegroundColor Yellow
Write-Host "" Write-Host ""
Write-Host " 预期访问: " -NoNewline; Write-Host "http://${ip}:3010" -ForegroundColor Cyan Write-Host " 预期访问: " -NoNewline; Write-Host "http://${ip}:3010" -ForegroundColor Cyan
Write-Host ""
Write-Host " 初始管理员: admin;随机密码保存在 .env 的 CONFIG_INITIAL_ADMIN_PASSWORD" -ForegroundColor DarkGray
} else { } else {
Write-Host "================================" -ForegroundColor Green Write-Host "================================" -ForegroundColor Green
Write-Host " 部署成功" -ForegroundColor Green Write-Host " 部署成功" -ForegroundColor Green
Write-Host "================================" -ForegroundColor Green Write-Host "================================" -ForegroundColor Green
Write-Host "" Write-Host ""
Write-Host " 访问地址: " -NoNewline; Write-Host "http://${ip}:3010" -ForegroundColor Cyan Write-Host " 访问地址: " -NoNewline; Write-Host "http://${ip}:3010" -ForegroundColor Cyan
Write-Host ""
Write-Host " -------- 初始管理员(首次登录后请修改密码)--------" -ForegroundColor Yellow
Write-Host " 账号: " -NoNewline; Write-Host "admin" -ForegroundColor White
Write-Host " 密码位置: " -NoNewline; Write-Host ".env -> CONFIG_INITIAL_ADMIN_PASSWORD" -ForegroundColor White
} }
Show-AdminLoginInfo
Write-Host "" Write-Host ""
Wait-ForExit Wait-ForExit
} }
+27
View File
@@ -457,6 +457,32 @@ run_https_setup() {
fi fi
} }
show_admin_login_info() {
echo ""
if [ "$SECURITY_ENV_MODE" != "new" ]; then
echo "管理员账号: admin(本次部署不会生成或重置已有密码)"
echo "登录提示: 若数据库尚未创建 admin,请查看 .env -> CONFIG_INITIAL_ADMIN_PASSWORD;若 admin 已存在,请使用当前密码。"
return 0
fi
local initial_admin_password
initial_admin_password="$(read_env_value .env "CONFIG_INITIAL_ADMIN_PASSWORD")"
if [ -z "$initial_admin_password" ]; then
echo "❌ 未能读取初始管理员密码: .env -> CONFIG_INITIAL_ADMIN_PASSWORD" >&2
return 1
fi
if [ "$DEPLOY_DRY_RUN" = "1" ]; then
echo "-------- 初始管理员(Docker 首次启动后生效)--------"
else
echo "-------- 初始管理员(首次登录后请立即修改密码)--------"
fi
echo "登录账号: admin"
echo "登录密码: ${initial_admin_password}"
echo "生命周期: 仅在数据库首次创建 admin 时使用;创建后修改 .env 不会重置密码。"
echo "有效期: 初始密码没有独立过期时间,在管理员修改密码前持续有效。"
}
# ==================== 主流程 ==================== # ==================== 主流程 ====================
main() { main() {
init_project_dir init_project_dir
@@ -551,6 +577,7 @@ main() {
echo "HTTPS 已启用" echo "HTTPS 已启用"
fi fi
fi fi
show_admin_login_info
echo "" echo ""
} }