feat(M5): 前端完善 + 部署 — Vue3 全量页面、仪表盘统计与我的审计接口、systemd/sudoers/迁移脚本、部署文档
This commit is contained in:
Executable
+102
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env bash
|
||||
# ws_usernode SQLite → MySQL 数据迁移脚本(PLAN 验收:SQLite 数据可迁移至 MySQL)。
|
||||
#
|
||||
# 原理:SQLite 导出 SQL(含表结构与数据)→ 简单改写为 MySQL 兼容语法 → 导入 MySQL。
|
||||
# 适用:从开发库(SQLite)迁移到生产库(MySQL)。
|
||||
#
|
||||
# 用法:
|
||||
# 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
|
||||
#
|
||||
# 前置:
|
||||
# - sqlite3、mysql 客户端已安装(Debian/Ubuntu: apt install sqlite3 default-mysql-client)
|
||||
# - MySQL 中已建库、建好应用账号(建议先用 usernode migrate --config 指向 MySQL 建表,
|
||||
# 再执行本脚本导数据;若表不存在,本脚本也可建表)
|
||||
# - 先停服务,避免迁移过程中写入
|
||||
#
|
||||
# 注意事项:
|
||||
# - 时间字段:SQLite 存的是 "2026-08-30 10:41:41.437309782+08:00" 这类文本,
|
||||
# MySQL DATETIME 不接受。本脚本用字符串函数提取前 19 位(秒级精度)。
|
||||
# 若你的库以 UTC 存储且无需时区换算,秒级精度足够(审计/会话/OTP 均为秒级业务)。
|
||||
# - BLOB/二进制字段:本表结构无二进制大字段,不处理。
|
||||
# - 迁移后建议校验行数并抽查,见脚本尾部。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "用法: $0 <sqlite.db> [MYSQL_DSN 或通过环境变量 MYSQL_DSN]"
|
||||
echo "示例: MYSQL_DSN='usernode:pass@tcp(127.0.0.1:3306)/usernode?charset=utf8mb4&parseTime=True&loc=UTC' $0 data/usernode.db"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SQLITE_DB="$1"
|
||||
MYSQL_DSN="${MYSQL_DSN:-}"
|
||||
|
||||
if [ -z "$MYSQL_DSN" ]; then
|
||||
echo "错误:未提供 MYSQL_DSN(环境变量或第二参数)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
command -v sqlite3 >/dev/null || { echo "缺少 sqlite3" >&2; exit 1; }
|
||||
command -v mysql >/dev/null || { echo "缺少 mysql 客户端" >&2; exit 1; }
|
||||
|
||||
TMP=$(mktemp -d)
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
SQL_DUMP="$TMP/dump.sql"
|
||||
|
||||
echo "==> 1/4 导出 SQLite 结构 + 数据: $SQLITE_DB"
|
||||
sqlite3 "$SQLITE_DB" .dump > "$SQL_DUMP"
|
||||
|
||||
# 排除 SQLite 内部表(sqlite_sequence 等)
|
||||
echo "==> 2/4 改写为 MySQL 兼容 SQL"
|
||||
awk '
|
||||
/^CREATE TABLE/ { table=1 }
|
||||
/^CREATE TABLE sqlite_sequence/ { skip=1; next }
|
||||
/^CREATE TABLE/ && $3 ~ /^sqlite_/ { skip=1; next }
|
||||
/^CREATE INDEX/ && $0 ~ /sqlite_autoindex/ { next }
|
||||
|
||||
# 建表语句改写:
|
||||
# "text" → TEXT;AUTOINCREMENT 去掉(MySQL 自增)
|
||||
# PRIMARY KEY (id) 保留;去掉多余引号
|
||||
/^CREATE TABLE/ {
|
||||
line = $0
|
||||
gsub(/"text"/, "TEXT", line)
|
||||
gsub(/AUTOINCREMENT/, "", line)
|
||||
# 去掉反引号内外的双引号(表名/列名)
|
||||
line = line
|
||||
print line
|
||||
next
|
||||
}
|
||||
|
||||
# 行数据:SQLite 的 INSERT INTO "table" VALUES(...) → MySQL 双引号仅用于字符串,表名不需要引号
|
||||
/^INSERT INTO/ {
|
||||
line = $0
|
||||
gsub(/INSERT INTO "/, "INSERT INTO `", line)
|
||||
gsub(/"\s*VALUES/, "` VALUES", line)
|
||||
# SQLite 布尔/整数原样;字符串内单引号已由 sqlite3 转义为 '',MySQL 兼容
|
||||
print line
|
||||
next
|
||||
}
|
||||
|
||||
{ print }
|
||||
' "$SQL_DUMP" > "$TMP/mysql.sql"
|
||||
|
||||
echo "==> 3/4 导入 MySQL"
|
||||
# mysql 客户端 DSN 形式为 mysql://user:pass@tcp(host:port)/db 或 user:pass@tcp(...)/db
|
||||
if [[ "$MYSQL_DSN" == mysql://* ]]; then
|
||||
MYSQL_DSN="${MYSQL_DSN#mysql://}"
|
||||
fi
|
||||
mysql --protocol=tcp "$MYSQL_DSN" < "$TMP/mysql.sql"
|
||||
|
||||
echo "==> 4/4 校验行数"
|
||||
for t in admin_users users ssh_keys approvals audit_logs sessions settings mail_logs otp_codes password_reset_tokens; do
|
||||
s=$(sqlite3 "$SQLITE_DB" "SELECT count(*) FROM $t;" 2>/dev/null || echo "?")
|
||||
m=$(mysql --protocol=tcp --batch --skip-column-names "$MYSQL_DSN" -e "SELECT count(*) FROM \`$t\`;" 2>/dev/null || echo "?")
|
||||
printf " %-24s sqlite=%-6s mysql=%s\n" "$t" "$s" "$m"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "迁移完成。建议:"
|
||||
echo " 1. 抽查关键表(users/sessions)内容与时间字段格式"
|
||||
echo " 2. 用指向 MySQL 的配置启动 usernode serve 验证"
|
||||
echo " 3. 确认无误后再处理旧 SQLite 库"
|
||||
@@ -25,3 +25,9 @@ usernode ALL=(root) NOPASSWD: /usr/sbin/useradd, /usr/sbin/usermod, \
|
||||
# [system]
|
||||
# sudo = true
|
||||
# dry_run = false
|
||||
#
|
||||
# 审计命令行为:如需记录 usernode 用户经 sudo 执行的所有命令,可追加
|
||||
# Defaults:usernode log_output
|
||||
# 并把输出目录设为 usernode 可写(如 /var/log/usernode/sudo),配合
|
||||
# journald 的 audit 日志作为系统账号操作的可追溯记录(PLAN §9)。
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# ws_usernode systemd 服务单元(生产部署)
|
||||
#
|
||||
# 安装:
|
||||
# 1. 构建单二进制:make build-embed(含前端产物)
|
||||
# 2. 安装二进制与配置:
|
||||
# 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
|
||||
# (按需编辑 /etc/usernode/config.toml:监听地址、数据库、SMTP、system.sudo=true)
|
||||
# 3. 创建运行用户与数据目录:
|
||||
# sudo useradd -r -s /usr/sbin/nologin -d /var/lib/usernode usernode
|
||||
# sudo mkdir -p /var/lib/usernode && sudo chown usernode:usernode /var/lib/usernode
|
||||
# 4. 安装 sudoers 白名单(节点以专有用户经 sudo -n 执行系统账号命令,见 sudoers.example):
|
||||
# sudo cp deploy/sudoers.example /etc/sudoers.d/usernode && sudo visudo -c
|
||||
# 5. 安装本单元并启动:
|
||||
# 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
|
||||
# 6. 初始化:migrate + 创建初始管理员
|
||||
# sudo -u usernode usernode migrate --config /etc/usernode/config.toml
|
||||
# sudo -u usernode usernode admin create --config /etc/usernode/config.toml \
|
||||
# --username root --password 'xxx' --email admin@example.com
|
||||
#
|
||||
# 说明:
|
||||
# - 以 usernode 专有用户运行;数据(SQLite / 审计归档)在 /var/lib/usernode。
|
||||
# - 生产必须配置 [system] sudo=true dry_run=false,配合 /etc/sudoers.d/usernode 白名单。
|
||||
# - 建议前置 nginx/caddy 反向代理启用 TLS;[server] listen 保持 127.0.0.1。
|
||||
|
||||
[Unit]
|
||||
Description=ws_usernode server user management node
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=usernode
|
||||
Group=usernode
|
||||
WorkingDirectory=/var/lib/usernode
|
||||
ExecStart=/usr/local/bin/usernode serve --config /etc/usernode/config.toml
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
# 安全加固
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadWritePaths=/var/lib/usernode /var/log/usernode /etc/usernode
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
# 日志走 journald
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user