主机管理
平台支持把构建好的 VitePress / VuePress 站点 一键发布到远程 Linux 主机。所有要发布的目标主机都集中在「主机管理」面板统一管理,SSH 凭据用 AES-256-GCM 加密存储,任何明文都不落盘。
核心特性
- 🔒 AES-256-GCM 加密:SSH 密码 / 私钥 / passphrase 全部加密后存
host表,平台只持有THINK_HOST_CRYPTO_KEY这个 256 bit 主密钥 - ⚡ 一键初始化 nginx:SSH 进主机 → 装包 → 启服务 → 校验 → 平台主动 HTTP 探测 → 防火墙放行,9 步流水线
- 🚀 一键发布站点:选站点 + 选主机 → 后端 build + SCP + 解压 + reload nginx,全异步 SSE 实时进度
- :terminal: Web 终端:xterm.js + WebSocket + PTY 直连主机 shell,排查问题不用 ssh 进堡垒机
- 🔄 版本回滚:每次发布独立 build task,一键回到任意历史版本
主机实体
每台主机在 host 表里一行,关键字段:
| 字段 | 说明 |
|---|---|
un_code | 业务主键 UUID,平台内唯一标识 |
name | 主机别名(运维侧自定义,如 prod-web-01) |
host | SSH 目标 IP 或域名,也是平台 HTTP 探测的目标 |
port | SSH 端口(默认 22) |
username | SSH 登录用户名 |
auth_type | PASSWORD / PUBLIC_KEY 两种认证方式 |
os_type | 自动探测:UBUNTU / CENTOS / ROCKY / UNKNOWN |
os_version | /etc/os-release 的 VERSION_ID 字段 |
nginx_status | nginx 当前状态:NOT_INSTALLED / INSTALLED_ACTIVE / INSTALLED_INACTIVE / INIT_FAILED |
nginx_web_root | 站点根目录,默认 /var/www/html |
nginx_config_dir | nginx 配置目录,默认 /etc/nginx |
nginx_conf_d_dir | conf.d 目录,默认 /etc/nginx/conf.d |
keep_versions | 每个站点在该主机上保留的历史版本数(默认 3) |
last_check_at / last_check_success / last_check_message | 最近一次 SSH 连通性探测结果 |
last_init_at / last_init_success / last_init_message | 最近一次 nginx 初始化结果 |
凭据加密
HostCryptoService 用 THINK_HOST_CRYPTO_KEY 这个 32 字节 hex 主密钥(从环境变量读),把凭据加密成 ciphertext 存 host 表。
加密算法:AES-256-GCM
key=THINK_HOST_CRYPTO_KEY(env,hex 编码,32 字节)IV= 12 字节随机,每条记录独立- 密文格式:
base64(IV 12B || ciphertext || GCM tag 16B)(共 ≥ IV+ciphertext+tag 三段,直接拼接后整体 base64)
启动校验: AdminApplication 启动时会检查 THINK_HOST_CRYPTO_KEY 是否已设置,缺失直接 fail-fast 退出(防止用空 key 启动后给空加密)。
使用流程: 任何需要 SSH 的接口都走 HostService.requireWithSecret(unCode) → 取出记录 → AES-GCM 解密 → 返回带明文密码的 DTO(内存用,绝不写日志/响应)。
# application.yml
host-crypto:
key: ${THINK_HOST_CRYPTO_KEY} # 必须设,32 字节 hex,启动校验# 生成强 key
openssl rand -hex 32
# 输出:9f3a1b... 64 字符 hex
# 设到环境变量 / .env / K8s Secret
export THINK_HOST_CRYPTO_KEY=9f3a1b...连通性探测
点击主机行的「探测」按钮 → HostProbeService 异步 SSH 登录 + 解析 /etc/os-release + systemctl is-active nginx + 探测公网 HTTP 80 是否通。
自动识别 的 OS(读取 /etc/os-release 的 ID 字段,fallback 到 ID_LIKE):
ID | 映射到 | 备注 |
|---|---|---|
ubuntu / linuxmint / pop | UBUNTU | Debian 系 |
centos / rhel / ol | CENTOS | RHEL 7 / 8 系 |
rocky / almalinux / alma | ROCKY | RHEL 8+ 系 |
fedora / amzn / amazoneuler / tencentos / opencloudos | CENTOS | dnf 系 |
debian | 暂不支持 | 提示用户升级到 Ubuntu 或手动适配 |
| 其它 | UNKNOWN | 提示用户手动确认是否兼容 |
探测结果回写 last_check_* 字段,前端实时刷新。
Web 终端
主机行的「终端」按钮 → xterm.js + WebSocket 直连主机 PTY。
链路:
xterm.js (browser)
→ WebSocket /api/ws/host/shell?hostUnCode=...&token=...
→ Umi dev 代理 /ws
→ Spring WebSocket / HostShellWebSocketHandler
→ SshConnection.openShellChannel() 拿到 PtyChannel
→ stdin/stdout 双向透传到 xterm鉴权: WebSocket upgrade 不支持 header,token 走 query ?token=xxx,WebSocket handler 内部用 AuthHelper.authenticate(request, token) 手动验,失败立即 close session。
安全限制:
- 仅 admin 角色可连接
- 单个主机同时只允许一个终端 session(后续接了会收到 "session busy" 关闭帧)
下一步
- 准备好主机后,去 一键初始化 nginx 完成 nginx 部署
- 之后到 站点发布到主机 把站点推上线