攀枝花测试环境 —— 部署台账

目标机:攀枝花政务云(Ubuntu 26.04.1),公网 117.172.73.106,入向只开 8081/8082。
2026-09-22 建成。**这是开发测试环境,不是生产**(无域名/备案/TLS;生产验收清单见
docs/huifu/08-上线与环境.md §4)。

布局

路径 内容
/data/talent-card/app.jar 后端 fat jar(103 MiB)
/data/talent-card/admin/ 后台管理端 dist(nginx 托管)
/data/talent-card/uploads/ 上传落盘目录(对应 UPLOAD_DIR)
/data/talent-card/src/ 从 Gitblit 克隆的源码,服务器上构建 admin 用
/data/talent-card/redeploy-admin.sh admin 重部署脚本(见下)
/data/talent-card/app-flyway-verify.log 2026-09-22 空库跑 148 个迁移的原始证据
/etc/talent-card/env 唯一的部署变量来源(600),systemd 的 EnvironmentFile
/etc/systemd/system/talent-card.service 后端单元(active + enabled)
/etc/nginx/sites-available/talent-card 8081 站点:/api/ /uploads/ 反代,/ 托管 dist
/data/mysql MySQL datadir(从 /var/lib/mysql 迁入,AppArmor 走 local/usr.sbin.mysqld 覆盖)
/var/lib/mysql 迁移前的原始副本,**回滚用**,确认稳定后可删

环境变量(/etc/talent-card/env,10 个键)

改配置只改这个文件,不要在单元里写 Environment= —— systemd 里 EnvironmentFile 会覆盖同名
Environment=,变量散两处迟早踩坑。改完 systemctl restart talent-card。

键 为什么必须有
MYSQL_URL 默认值指向办公室开发库(192.168.110.13)。这里指向 127.0.0.1。**必须带 rewriteBatchedStatements=true**:不带时 batchUpdate 在驱动层退化成逐条发送,实测同一批 15.4 万行从 28.5 秒变成 986 秒(约 100 倍)。
MYSQL_USERNAME / MYSQL_PASSWORD 业务账号 talent_card@localhost(caching_sha2_password)。密码只在这个文件里。
REDIS_PORT / REDIS_PASSWORD 默认值是 6368 + 一个弱口令,与实际装的 Redis 不符 → Actuator 的 Redis 健康指示器会让 /actuator/health 永远 503。业务代码零调用 Redis,**但健康探针会用到**。
SPRING_FLYWAY_ENABLED application.yml 里 flyway.enabled: false 是硬编码的,测试环境要跟着迁移走,所以常开。
SERVER_ADDRESS 后端只绑 127.0.0.1,对外一律经 nginx。
UPLOAD_DIR 上传目录指到 /data(系统盘只有 40G)。
APP_PRIVACY_ENCRYPTION_KEY 默认值是**仓库里公开的开发 key**。SensitiveDataCrypto 是 SHA-256(该字符串) 派生 AES key,所以任意字符串合法。**必须在写入任何隐私数据之前设**——事后换会让旧数据解不开。
CORS_ALLOWED_ORIGINS 必须包含浏览器实际访问的 origin,哪怕是同源。 浏览器对同源的非简单请求(POST + JSON)也会带 Origin 头,而 ApiCorsConfig 只要看到 Origin 就按白名单校验,不在名单里直接 403 Invalid CORS request。漏了它的症状就是「登录 403」。

allowed-origin-patterns(http://localhost:[*])没被覆盖,所以本地 vite / HBuilderX 调试来源照旧放行。

重部署 admin

bash /data/talent-card/redeploy-admin.sh          # 默认拉 main,可传分支

它做:git fetch → npm ci → npm run build → 替换 /data/talent-card/admin/。
高德 key 等构建期变量放 /etc/talent-card/admin-build.env(600),脚本会自动拷成构建用的 .env。
不要把 key 放进仓库:talent-card-admin/.gitignore 里只忽略了 *.local,**没有忽略 .env**。

之所以能在服务器上构建:Gitblit git://120.76.84.145/java/talent-card.git 匿名可读(已实测
ls-remote / clone 都不需要凭据),package-lock.json 的 resolved 指向 registry.npmmirror.com
(可达)。服务器上装了 git 2.53.0 / nodejs 22.22.1 / npm 9.2.0。

命令通道单次只能带约 4KB,所以 dist(4.4MB)走不了命令通道——这是当初需要人工 SFTP 传包的原因,
现在用「服务器自建」绕过去了。

已知边界

  • 收不到汇付回调:非公网环境的既定事实与既定设计(docs/adr/0004-active-query-is-authoritative.md、
    .scratch/pending-order-payment-flow/README.md 的 F1)。收敛靠读接口惰性查询 + 60 秒一轮的对账快车道,
    订单不会卡住。要收回调得等备案域名。
  • 8081 对全公网开放且是纯 HTTP(无 TLS),token 明文传输。已确认接受(测试环境)。
  • 数据库:talent_card,字符集 utf8mb4 / utf8mb4_0900_ai_ci(与测试库同口径,
    照 ResetTestDatabase.java 只写 charset 不写 collate;**不要**照抄 init-local-mysql.sql 的 utf8mb4_unicode_ci,那是 5.7 开发库用的)。
  • MySQL 8.4 上迁移里的 7 条 VALUES() is deprecated(1287)仍可运行,但该语法未来版本会被移除。

⚠️ 用空库部署后,第一件事:处置引导账号 admin

V76__admin_operator_authentication.sql:44-47 会种一个账号,其口令哈希**经实测就是 123456**
(用 node 按 V76 的参数算 PBKDF2-HMAC-SHA256 / 210000 轮 / 盐 AQIDBAUG…,结果与迁移里写死的值逐字符相同)。
where not exists 的写法意味着**每一个新库都会得到这个已知口令**。

所以每次用空库部署(包括将来的生产)之后,必须立刻把 admin 的口令换掉,否则服务商的安全扫描会
再次报「Web弱口令」,而且这次是真的能被登进去。

根治要改代码/加迁移(引导口令从 ADMIN_BOOTSTRAP_PASSWORD 注入 + 首登强制改密),
属后台账号体系、不属支付链路 —— 已按 AGENTS.md 写成
.scratch/weak-credentials-hardening/issues/01-bootstrap-admin-and-password-policy.md。

在这台机器上,快速自查有没有遗留的弱口令账号:

python3 /root/check-weak-passwords.py     # 拿 69 个常见弱口令撞库里所有 password_hash,只打印命中项

凭据现状(2026-09-22 自查,因服务商「Web弱口令」告警)

告警内容:扫描器抓到一次成功登录 {"account":"admin","password":"123456"}。根因与仓库侧的修法见
.scratch/weak-credentials-hardening/issues/01-bootstrap-admin-and-password-policy.md(引导账号 + 无强度策略)。

这台机器上的现状:

项 现状 结论
后台 admin 账号 已重置;用 /root/check-weak-passwords.py 拿 69 个常见弱口令撞 PBKDF2 哈希,**0 命中** ✓
MySQL root / debian-sys-maint auth_socket,**不启用密码认证**(只能以本机 root 身份连) ✓
MySQL talent_card 32 位随机、四类字符齐全,仅 localhost,caching_sha2_password ✓
Redis 32 位随机、四类字符齐全,仅监听 127.0.0.1 ✓
/etc/mysql/** 里存的明文口令 无 ✓
/etc/talent-card/env 600 root ✓
OS 账号 root / ubuntu 两个都设了口令,且 ubuntu 在 sudo 组 ⚠️ 见下
sshd PermitRootLogin yes + PasswordAuthentication yes ⚠️ 见下

⚠️ 两条必须由人自己处理的:

  1. OS 口令在技能包的 credentials.txt 里(按约定不读进上下文),所以 root / ubuntu 的口令强度
    必须由用户自己核。若还是回执单那个初始口令,**必须换**(ubuntu 在 sudo 组,等于第二个 root)。
  2. 千万不要为了"整改弱口令"去关 PermitRootLogin —— 堡垒机就是用 root + 口令登录的,
    关掉等于切断唯一管理通道(AGENTS.md 记的那次失联事故同类)。正确做法是把口令换强,而不是关入口。

口令已按四类字符口径轮换(2026-09-22):MySQL 与 Redis 的口令都重生成过,现为 32 位、含大小写/数字/
特殊符号(符号只用 @%^*+_.-,避开会破坏 systemd EnvironmentFile 解析与 shell 展开的 $ " ' \ ! 和空格)。
验证方式是 /actuator/health 为 UP —— 这一条同时证明新 MySQL 口令(Hikari 建连 + Flyway 校验)
与新 Redis 口令都对,另外用新口令走 TCP 登录 MySQL、redis-cli -a 取到 PONG。

轮换流程(已实跑):

  1. mysql -e "alter user \"talent_card\"@\"localhost\" identified by \"<新>\""
  2. 改 /etc/talent-card/env 的 MYSQL_PASSWORD / REDIS_PASSWORD
  3. Redis:改 /etc/redis/redis.conf 的 requirepass → systemctl restart redis-server
  4. systemctl restart talent-card → 验 health

三个坑(都踩过):

  • 纯随机生成不保证四类字符齐全。 第一次生成 32 位随机的 MySQL 口令,结果**一个特殊符号都没有**;
    生成时要**循环到四类都出现**再落库(重生成到第 5 次才齐)。
  • redis-cli config set requirepass 会报 NOAUTH。 库上已经设过口令时,未认证不许改。
    要么先 redis-cli -a <旧> config set ...,要么**直接改配置文件再重启**(更省事,本次就是靠这一步生效的)。
  • 轮换后旧口令仍留在备份文件里(已失效但还在磁盘上)。已收拢为一份当前状态的
    /root/talent-card-env.bak,旧的 .bak.2/.bak.3/.bak.4 与 env-before-rotate.bak 都删了。
  • 不必担心把自己锁死:MySQL 的 root@localhost 走 auth_socket,口令改坏了也能用 root 身份重设。

另一个角度:告警详情页能看到完整请求体(含口令),是因为 8081 是纯 HTTP。口令换强了,
但**传输仍是明文**(浏览器→服务器、以及服务商的安全设备都能看到)。根治要 HTTPS(域名+备案)。
在此之前可选:后台管理端不放在公网端口上暴露。这条只是提醒,已决定接受公网暴露。