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

目标机:攀枝花政务云(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)。**2026-10-10 13:57 更新到提交 38e39c5**:库版本仍是 156(DbMigrate 报 Schema is up to date. No migration necessary.,150 个迁移校验通过),启动 16.2 s、/actuator/health UP、https://rencaika.dqrcfwpt.com/api/health 与 http://117.172.73.106:8082/api/health 均 200。**这一版起改为「在服务器上从 Gitblit 源码就地构建」,不再上传 103 MiB 的包** —— 做法与由来见下「重建后端」,构建产物 sha256 19efe2b4…ca66。上一版(提交 7a5303f、上传来源文件 sha256 79d63e44…e4554)留在 app.jar.bak-20261010-1341(cp -p 保留原 mtime,sha 已校验 = 79d63e44…e4554),更早两版是 app.jar.bak-20260929-1650(提交 61ba794、库版本 155)和 app.jar.bak-20260929-1326;回滚直接换回即可(本次前后都没有新迁移,旧代码读新库安全)
/data/talent-card/admin/ 后台管理端 dist(nginx 托管)。**最近一次:2026-10-10 13:59 由 redeploy-admin.sh 从 Gitblit main(da90ca8) 重建并替换**(当天与后端 38e39c5 的上线相隔约 2 分钟,两人并行操作,输出留在 admin-redeploy-20261010.log):新增人才卡升级页 TalentUpgradeView.vue。**注意它 git fetch 会把 /data/talent-card/src 硬对齐到 origin/main**(这次就是从 38e39c5 推到 da90ca8),所以此后重建后端会以 da90ca8 为基线。上上次是 2026-09-29 16:54 从 7a5303f 重建(进件详情多了「店铺收银台」一列)。两次构建日志里都有「没有 /etc/talent-card/admin-build.env,本次不带高德 key 构建」——见下文「已知缺口」
/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 迁移前的原始副本,**回滚用**,确认稳定后可删

首页顶部图更新(2026-10-10 16:16)

  • 按用户提供的图片替换线上 /data/talent-card/uploads/home/home-top.png,保留小程序现有圆角和布局。
  • 新图 222141 字节,SHA256:496e4cd33b81ebe89b6d382c3b87f9dfd0964e225c00e2e489137707e1269aa9。
  • 旧图备份:/data/talent-card/backups/home-images/home-top-20261010-161619.png。
  • 使用后台图片上传接口传输,退出此次上传会话;服务器校验后原子替换。公网原地址返回 200,下载文件 SHA256 与附件一致。没有重启后端。
  • 本地开发环境的旧图保留;本次仅更新线上资源。客户端若缓存旧图,需要重新加载或清除图片缓存。

环境变量(/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」。当前值:http://117.172.73.106:8081、http://117.172.73.106:8082、http://www.dqrcfwpt.com:8081、https://www.dqrcfwpt.com(后者为将来上 TLS 预留)。**换域名 / 换端口 / 换协议都会换 origin,都要补** —— 2026-09-22 因为换域名又踩了一次。

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 传包的原因,
现在用「服务器自建」绕过去了。

重建后端(2026-10-10 起:服务器上构建,不再上传 jar)

在此之前后端 jar 是**人工传上去的**。2026-10-10 起改成和 admin 同一套路——**在服务器上从 Gitblit
源码构建**,理由与 admin 一样,而且更硬:本机的**唯一通道是堡垒机**(172.18.105.9,见下「堡垒机」),
它是个**只给交互式菜单的审计系统**,不接受任意远程命令、没有 SFTP 通道;命令通道单次约 4KB,
137MB 的 base64 分片要三万多次往返,且每一步都会被审计系统留档。所以**不要再试图"把 jar 传上去"**。

前置条件(已装好,一次性的):机器原来只有 openjdk-17-jre-headless(**没有 javac**),
2026-10-10 装了 openjdk-17-jdk-headless(javac 17.0.20.1)。java 备选项随之指到 JDK 那份,
与运行时同为 17,不影响已在跑的服务。Maven 不用装:走仓库自带的 mvnw,它把
Maven 3.9.16 下到 /root/.m2/wrapper/dists/,依赖也缓存在 /root/.m2。

SRC=/data/talent-card/src                     # 与 admin 共用同一份 clone(浅克隆)
git -C $SRC fetch --depth 1 origin main
git -C $SRC checkout -B main origin/main      # 与 redeploy-admin.sh 同款:硬对齐 origin/main
cd $SRC/talent-card-server
bash ./mvnw -B -DskipTests package            # 首次 3m52s(要下全部依赖),4 vCPU

替换 jar 一定要用「临时文件 + mv」,不要 cp 原地覆盖:systemd 起的是
java -jar /data/talent-card/app.jar,旧进程 mmap 着那个 inode,cp 会**原地截断同一个 inode**。
原子 rename 则让旧进程继续用旧 inode、重启后用新 inode。现成脚本留在 /data/talent-card/do-deploy.sh
(做:拷 .new → chmod 644 → sha256sum → mv -f → systemctl restart → 等 35 s →
is-active / NRestarts / /actuator/health → tail app.log),全部输出写 /data/talent-card/deploy.log。

这台机器没有 curl(curl: command not found),探健康检查用 python3:

python3 -c "import urllib.request as u;print(u.urlopen('http://127.0.0.1:8080/actuator/health',timeout=8).read().decode())"

堡垒机(唯一的进入通道,2026-10-10 实测)

这台机器没有公网自服务控制台,SSH 也只有这一条路:ssh <账号>@172.18.105.9,它是一台
运维审计系统(VNC/console 之外的唯一入口)。两条硬性质,决定了不能用普通跳板机的姿势:

  • 它不是跳板机、也不接受任意远程命令。 登录后是一个**读 stdin 的交互式菜单**:自动列目录→
    自动选中目标 GT-KC-XXHJSB-DQRCKXM-YYserver (172.192.19.1) → 停在 Input account:。
    用 ssh host '命令' 这种形式会把"命令"当成账号喂进去、连错三次被断开(**症状是 exec 被接受后
    立刻 broken pipe、无任何输出**)。
  • 没有 SFTP / 端口转发通道(子系统与 -W 都过不去),所以**传文件只能靠服务器自己去拉**(见
    「重建后端」)。同理,scp 到这台机器是不通的。

可用的姿势(免交互密码用 SSH_ASKPASS + SSH_ASKPASS_REQUIRE=force,密码通过环境变量传入、
不要写进仓库):按 4 s / 5 s / 6 s 的节奏依次喂 <账号>、<服务器密码>、<远端命令>,会话结束
再喂 exit。推荐**把长任务 nohup ... & 放后台、结果重定向进文件,下一个会话再 cat**——审计系统
有 900 s 自动退出,长任务卡在会话里会被截断(只喂固定等待时长的输出常被切掉)。本机可用的半成品
脚本在 /tmp/sshtest/connect2.sh(临时目录,**每次重启就没了**,照上面节奏重写即可)。

HTTPS(2026-09-29 已上线;2026-10-08 域名变更)

2026-10-08 域名变更:甲方把 www.dqrcfwpt.com 与退域名 dqrcfwpt.com 解析到了他们自己的机器(公司官网),
那两个名字**已不在我们手上**。测试环境改用新建子域 rencaika.dqrcfwpt.com,证书换成
Let's Encrypt(certbot 自动续期)。本节里凡提到 www / :8081 的表述都是**变更前**的历史;
当前口径以下面「端口」与「证书」两行为准。另:旧证书是**腾讯云**签发(不是华为云)。

项 值
证书(两套并存,nginx 按 SNI 选) ① rencaika.dqrcfwpt.com → Let's Encrypt:/etc/letsencrypt/live/rencaika.dqrcfwpt.com/fullchain.pem + privkey.pem;② www/退域名 → 腾讯云签发的 DV:/etc/nginx/ssl/talent-card/server.crt(**含中间链的 bundle**,4451 字节)+ server.key(600)。**之后换腾讯云证书到新子域:只改 443/8081 那两块里的两行路径再 reload,不需要动别的。**
覆盖域名(SAN) ① rencaika.dqrcfwpt.com(LE 那张)② dqrcfwpt.com + www.dqrcfwpt.com(腾讯云那张)
到期 ① 2027-01-06(LE,**certbot.timer 自动续期,不用人管** —— 前提是 80 上的 /.well-known/acme-challenge/ 直读例外别删)② 2027-02-11(腾讯云那张,**人工**:到期前重新下载再传一次)
端口 rencaika.dqrcfwpt.com 就是测试环境:**443**(https://rencaika.dqrcfwpt.com,正式入口、**不带端口**)与 8081(同机兼容)都提供完整功能;**8082 = 明文兜底**(http://117.172.73.106:8082/,给 IP 访问;HSTS 管不到 IP,这条永远可用);**80 只做 301**,且给 /.well-known/acme-challenge/ 留了直读例外(**删了证书就自动续期不了,且是静默失败**)。旧名字 www/退域名的 443 仍挂着生产占位页,但甲方已把那两个名字指走,**那段实际不生效、可删**。
⚠️ 为什么 8081 必须是 TLS、明文要另开 8082 443 上发了 HSTS,而 HSTS 按「主机」生效、覆盖该主机的所有端口(RFC 6797 §8.3),且升级时**显式端口会保留**(http://host:8081 → https://host:8081)。所以 8081 若只说纯 HTTP,浏览器(只要访问过一次域名)一律打不开。**也不能让 nginx 用两个 server 块在 8081 上分别 listen 明文与 ssl** —— 同一个 address:port 的 ssl 标志必须一致,混用直接校验失败,报 no "ssl_certificate" is defined for the "listen ... ssl" directive,而且**错误行号指向那个明文块**,极易看错地方(2026-09-29 就在这里绕了几圈)。所以拆成 8081(TLS) + 8082(明文)。
生产上线时 把 443 块里的 root 与 location 换成 include snippets/talent-card-locations.conf;,80 的 301 不用动。该域名上的 /api 目前返回 503 JSON,避免请求落到静态页被误解为可用
HSTS max-age=86400(1 天),**没有** includeSubDomains / preload
公网实测 链 2 张、Verify return code: 0 (ok)、curl 不带 -k 返回 200、http:// → 301、HSTS 头存在、https:// Origin 的登录请求不再 403

HSTS 为什么先只给 1 天:这张证书是**人工续期**的。若一次给到一年又忘了续,浏览器会把访问者锁在硬错误页上、
连"点继续"的逃生口都没有。**等第一次续期成功之后**再把 max-age 提到 31536000,并考虑 includeSubDomains。
不加 includeSubDomains 的另一个原因:将来若新增子域(例如 OSS 的 img.)而它还没配好 HTTPS,
HSTS 会把那个子域一并锁死;preload 更是单向门,提交后很难退出。

顺带解锁的两件事(都在对方侧操作):
- 汇付的五个回调地址现在可以是真实公网 HTTPS 了,形如
https://www.dqrcfwpt.com/api/payments/huifu/notify——这解掉了 .scratch/pending-order-payment-flow/README.md
里 F1 那个长期项。**要在汇付控台逐条注册**,且与本地 yml 的 HUIFU_*_NOTIFY_URL 保持一致。
- 微信小程序的服务器域名填 https://rencaika.dqrcfwpt.com(request / uploadFile / downloadFile 都要,**不带端口**)。

还没做的:
- ~~退域名 dqrcfwpt.com 没有 A 记录~~ 已作废:2026-10-08 起 dqrcfwpt.com 与 www 都解析到甲方的机器
(公司官网),我们不再持有这两个名字,原来那条「补 A 记录」的待办随之取消。
- 8081 仍是全公网可达的明文端口,上面挂着后台 API。HTTPS 已稳定,建议请云管理方关掉它;
在那之前它仍是唯一的明文入口。
- CORS_ALLOWED_ORIGINS 里没有 http://www.dqrcfwpt.com(80 那条)——**已无关紧要**,因为 80 现在 301 到 https。

小程序体验版接入测试环境(2026-09-29)

三处必须同时对,任一处不一致都表现为「请求失败」:

位置 值
代码 rencaika/config/api.uts 与 rencaika_merchant/config/api.uts:apiEnvironment = 'staging' + stagingBaseUrl = 'https://rencaika.dqrcfwpt.com'
微信后台 → 开发管理 → 服务器域名 request / uploadFile / downloadFile 都填 https://rencaika.dqrcfwpt.com(**不带端口**,与代码里的 stagingBaseUrl 一致);socket / udp / tcp 留空(两个小程序零 WebSocket 用法,已 grep 确认)
体验成员 「成员管理」里逐个加微信号;配额以后台实时提示为准
  • 两个 appid 各配一遍:人才端 wxcfee477acb54d179(后端 app.wechat.mini-program.app-id 与它一致)、商户端 wx550e520ad19458ea。
    ⚠️ rencaika/manifest.json 顶层还有一个 wx6ae2cadae28fce1,与 mp-weixin 段不一致 —— 要确认开发者工具里实际用的是哪个;用错则与后端 appid 对不上,登录必失败。
  • 已验证的服务端前提:后端 → api.weixin.qq.com:443 可达;后端那对 appid/secret 有效(已向微信换到 token);微信 IP 白名单**未挡**本机(否则会报 40164)。
  • 切回 / 切生产:本机联调把 apiEnvironment 改回 'development';生产上线改 'production' 并填 productionBaseUrl,**且那时微信白名单要去掉端口**(微信规则:不配端口时请求 URL 也不能带端口)。
  • 副带效果:切到 staging 后,所有 apiEnvironment == 'development' 的开发期便利(假 talentId、演示数据、预览模式、跳过登录)自动关闭 —— 体验版必须真登录。

已知边界

  • 收不到汇付回调:非公网环境的既定事实与既定设计(docs/adr/0004-active-query-is-authoritative.md、
    .scratch/pending-order-payment-flow/README.md 的 F1)。收敛靠读接口惰性查询 + 60 秒一轮的对账快车道,
    订单不会卡住。要收回调得等备案域名。
  • 8081 对全公网开放且是纯 HTTP(无 TLS),token 明文传输。已确认接受(测试环境)。
  • 域名:www.dqrcfwpt.com → 117.172.73.106(2026-09-22 解析)。**访问必须带端口**:
    http://www.dqrcfwpt.com:8081 —— 80/443 未备案开不出来,实测不带端口是超时。退域名 dqrcfwpt.com
    没有 A 记录(公网解析器确认 ENODATA),要用不带 www 的形式得先补记录。
  • nginx 的 server_name _ 是通配,所以域名访问无需改站点配置(实测 http://www.dqrcfwpt.com:8081/ 返回 200)。
  • 数据库: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)仍可运行,但该语法未来版本会被移除。
  • 后台的「商户创建」页地图没有高德 key:/etc/talent-card/admin-build.env 不存在,redeploy-admin.sh
    每次都会打印「本次不带高德 key 构建(地图相关页面会报未配置)」。**这不是某次重建弄丢的**:本次替换后的
    admin/assets/MerchantCreateView-<hash>.js 与改动前的构建产物**内容哈希完全相同**(D144nYCr,没被本次改动碰到),
    两者都没有内联 key,说明构建期变量一直就没配。要用地图:把 VITE_AMAP_KEY / VITE_AMAP_SECURITY_CODE
    写进 /etc/talent-card/admin-build.env(600,模板见 talent-card-admin/.env.example)再跑一次
    redeploy-admin.sh。别把 key 放进仓库(.gitignore 只忽略 *.local,**没有忽略 .env**)。

⚠️ 用空库部署后,第一件事:处置引导账号 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,只打印命中项

⚠️ docker 的防火墙残留还在内存里 —— 不要去清它

docker 2026-09-21 已彻底卸载(包 0 条、/etc/docker 与 /data/docker 已删、网桥已删、路由干净),
但 iptables/nftables 里 docker 留下的链与规则仍在运行内存中:

链:DOCKER / DOCKER-USER / DOCKER-FORWARD / DOCKER-CT / DOCKER-INTERNAL / DOCKER-BRIDGE
-A POSTROUTING -s 172.18.0.0/16 ! -o br-bced14232775 -j MASQUERADE
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER ! -i br-bced14232775 -o br-bced14232775 -j DROP     (docker0 同款)

已逐条核实为「当前无害」:网卡只剩 ens5 / lo,规则引用的 docker0、br-bced14232775 都不存在,
所以按出接口匹配的那些 DROP/ACCEPT 永不命中;两条 MASQUERADE 只匹配源 172.17/172.18.x,而本机是
172.192.19.1,**不命中**(对堡垒机那条路也无影响:入向包不走 POSTROUTING,我们的回包源也不是 172.18.x)。
另外 iptables -S INPUT 只有 -P INPUT ACCEPT、没有任何规则,ufw 是 inactive ——
本机对入向 80/443 不设限,那是云安全组挡的,与这些残留无关。

不要为了「清理干净」去动防火墙。 收益是零(现在无害),风险是切断唯一入口:这台机器**没有 console/VNC**,
唯一管理通道是堡垒机。AGENTS.md 第一条纪律划的就是这条边界。

而且根本不需要清 —— 它不持久:

检查 结果
iptables-persistent / netfilter-persistent 没装
nftables.service 存在但 disabled → 开机不加载 nft 规则
/etc/nftables.conf 镜像自带的存根;服务 disabled,开机不会读它
开机时间 2026-09-20 17:59(截至 09-22 已连续运行 1 周 21 小时)

下一次重启时内核从空规则表起来,这些就自然消失了。不必为它专门安排重启。

这条也是 AGENTS.md「别再装 docker」的又一实证:包卸了、目录删了、网桥没了,防火墙规则却还在内存里 ——
如果哪天有人重建 docker0 或把 172.17/172.18 网段带回来,这些规则会**立刻开始生效**,
而 172.18.0.0/16 正是堡垒机所在网段(2026-09-21 失联事故的成因)。

如果将来**确实**要清(例如审计明确要求),前提是**先具备带外通道**(请服务商提供 console/VNC 或临时开通),
并挂 systemd-run --on-active=5min /bin/bash -c '<恢复命令>' 自动回滚,清完立刻复检堡垒机通道。
在没有带外通道之前,一律不做。

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

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

这台机器上的现状:

项 现状 结论
后台 admin 账号 2026-09-29 用 /root/reset-admin-password.py 重置为 20 位、四类字符齐全的强口令;明文只写 /root/admin-password.txt(600),脚本输出里只有类别摘要、哈希自检与「真调一次登录接口」的成败(不打印口令与 token)。重置时一并清零失败计数/锁定、并吊销所有在用会话(那次告警报文里带出过 accessToken)。自查用 /root/check-weak-passwords.py ✓
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(域名+备案)。
在此之前可选:后台管理端不放在公网端口上暴露。这条只是提醒,已决定接受公网暴露。