# 部署指南(M5) ws_usernode 交付形态:**单 Go 二进制**(前端已 go:embed 嵌入),配 systemd 服务 + sudoers 白名单。可选 podman 容器镜像。本文件覆盖完整生产部署步骤与验证。 --- ## 1. 构建 前置:Go ≥ 1.22、podman(构建前端需要容器,宿主机 Node 版本过旧无法直接构建)。 ```bash # 1) 构建前端并嵌入 go:embed(内部自动 podman 构建 dev 镜像 + npm install + vite build) make web-build # 2) 编译含前端产物的单二进制 make build-embed # 产物 bin/usernode ``` > 代理环境:容器构建无法继承 proxychains,Makefile 通过 `--build-arg` 传递 > `HTTP_PROXY/HTTPS_PROXY/ALL_PROXY/NO_PROXY`。若宿主机 LD_PRELOAD 了 > proxychains(本环境如此),podman 拉取镜像会失败,需: > `env -u LD_PRELOAD -u PROXYCHAINS_CONF_FILE make web-build` 验证二进制:`./bin/usernode --help`;健康检查 `curl localhost:8080/healthz`。 --- ## 2. systemd 部署(推荐) ### 2.1 运行用户与目录 ```bash sudo useradd -r -s /usr/sbin/nologin -d /var/lib/usernode usernode sudo mkdir -p /var/lib/usernode /var/log/usernode /etc/usernode sudo chown usernode:usernode /var/lib/usernode /var/log/usernode ``` ### 2.2 安装二进制与配置 ```bash sudo install -o usernode -g usernode -m 0755 bin/usernode /usr/local/bin/usernode sudo install -o root -g root -m 0644 config.example.toml /etc/usernode/config.toml sudoedit /etc/usernode/config.toml ``` 配置要点(生产): ```toml [app] env = "production" base_url = "https://usernode.example.com" # 对外地址(邮件重置链接) [server] listen = "127.0.0.1:8080" # 置于反向代理后;或直接 0.0.0.0 trusted_proxies = ["127.0.0.1", "::1"] [database] driver = "mysql" # 生产 MySQL;小规模可 sqlite dsn = "usernode:pass@tcp(127.0.0.1:3306)/usernode?charset=utf8mb4&parseTime=True&loc=UTC" [smtp] host = "smtp.example.com" # 邮件必配(OTP/审批/到期通知) port = 587 username = "usernode" password = "..." # 或 USERNODE_SMTP_PASSWORD 环境变量 from = "usernode@example.com" [system] sudo = true # 必须 true:经 sudo -n 白名单执行 dry_run = false # 必须 false [audit] archive_dir = "/var/lib/usernode/audit_archive" # 审计每日归档;留空不清理 ``` ### 2.3 sudoers 白名单 ```bash sudo cp deploy/sudoers.example /etc/sudoers.d/usernode sudo visudo -c ``` 白名单仅允许 `useradd/usermod/userdel/passwd/mkdir/chmod/chown/install` (禁任意 shell),命令参数由程序内强校验。 ### 2.4 初始化数据库与管理员 ```bash sudo -u usernode usermode migrate --config /etc/usernode/config.toml # 建表(先于 admin create) sudo -u usernode usernode admin create --config /etc/usernode/config.toml \ --username root --password '强密码' --email admin@example.com ``` ### 2.5 安装服务并启动 ```bash sudo install -o root -g root -m 0644 deploy/usernode.service /etc/systemd/system/usernode.service sudo systemctl daemon-reload sudo systemctl enable --now usernode sudo systemctl status usernode journalctl -u usernode -f # 结构化日志(slog) ``` ### 2.6 反向代理(TLS 可选但建议) nginx 示例: ```nginx server { listen 443 ssl; server_name usernode.example.com; # ssl_certificate / ssl_certificate_key ... location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; 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; } } ``` --- ## 3. podman 容器(可选) ```bash make image # 多阶段构建:node 构建前端 → Go 编译 → alpine 运行镜像 podman run -d --name usernode \ -p 8080:8080 \ -v usernode-data:/data \ -v /etc/usernode:/etc/usernode:ro \ -e USERNODE_SYSTEM_SUDO=true \ -e USERNODE_SYSTEM_DRY_RUN=false \ ws-usernode:0.3.0-m5 ``` > 容器内系统账号操作:容器以 usernode 用户运行,需在宿主机(或特权容器内) > 配置对应 sudoers;默认 `dry_run=true` 只打印计划命令,用于容器内演练。 > 容器内用户目录/系统账号与宿主机共享时,请自行评估 sudo 与挂载边界。 --- ## 4. SQLite → MySQL 迁移 ```bash # 前置:MySQL 已建库、建账号;可先用指向 MySQL 的配置跑 usernode migrate 建表 MYSQL_DSN='usernode:pass@tcp(127.0.0.1:3306)/usernode?charset=utf8mb4&parseTime=True&loc=UTC' \ ./deploy/migrate-sqlite2mysql.sh /path/to/usernode.db ``` 脚本会导出 SQLite(结构+数据)、改写为 MySQL 兼容语法并导入,最后逐表比对行数。 详见脚本头部注释(含时间字段精度说明)。 --- ## 5. 验收清单(对照 PLAN M5 验收标准) - [ ] 管理员创建用户 → 外部用户图形验证码 + OTP(邮件或 CLI)登录 → 上传公钥 → SSH 登录服务器 - [ ] 吊销密钥立即失效;到期自动禁用并通知;回收期内可延期,超期自动回收(保留审计) - [ ] 所有管理操作可审计、可查询、可 CSV 导出;每日归档(配置 archive_dir 后) - [ ] SQLite 数据可迁移至 MySQL(deploy/migrate-sqlite2mysql.sh) - [ ] 单二进制部署(systemd + sudoers 白名单)通过 - [ ] podman 容器构建通过 ## 6. 运维速查 | 场景 | 操作 | |---|---| | 重置管理员密码 | `sudo -u usernode usernode admin reset-password --config /etc/usernode/config.toml --username root --password 新密码` | | 获取外部用户 OTP | `usernode user otp --config ... --username ext_xxx`(与邮件同码同效期) | | 查看审计归档 | `ls /var/lib/usernode/audit_archive/` | | 查看邮件失败记录 | 数据库 `mail_logs` 表 status='failed'(每 5 分钟自动重试) | | 手动触发维护 | 重启服务触发 `@daily` 维护;或等待 `@every 6h` 补扫 | | 备份 | SQLite:停服拷贝 db 文件;MySQL:mysqldump |