-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdeploy.sh
More file actions
154 lines (138 loc) · 6.33 KB
/
Copy pathdeploy.sh
File metadata and controls
154 lines (138 loc) · 6.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#!/usr/bin/env bash
#
# 一键部署 GPU 风扇控制台到目标机
#
# ./deploy.sh 全量(后端 + 前端)→ 覆盖 → 重启 → 自检
# ./deploy.sh --static-only 只更新前端(不改后端,不重启,静态文件即时生效)
# ./deploy.sh --no-build 跳过前端构建(复用 app/static 里已有的产物)
# ./deploy.sh --no-restart 传完不重启(全量模式下慎用)
#
# 可用环境变量(CONN 必填,其余有默认值):
# CONN=<ssh连接名> REMOTE_DIR=/opt/gpu-fan-console SERVICE=gpu-fan-console URL=http://<目标机>:8765
#
# 为什么不用 agentsshcli upload:它现在报「创建远端续传元数据失败」,虽然数据传完了
# 但整体返回失败(--no-cache 直连模式也无效,已实测)。所以走分片 base64,见 DEPLOY.md 坑 ②。
#
set -euo pipefail
CONN="${CONN:?用法: CONN=<ssh连接名> ./deploy.sh}"
REMOTE_DIR="${REMOTE_DIR:-/opt/gpu-fan-console}"
SERVICE="${SERVICE:-gpu-fan-console}"
URL="${URL:-http://127.0.0.1:8765}"
# 单次命令行上限 32767 字符(Windows),留出余量
CHUNK_SIZE="${CHUNK_SIZE:-30000}"
STATIC_ONLY=0
DO_BUILD=1
DO_RESTART=1
for arg in "$@"; do
case "$arg" in
--static-only) STATIC_ONLY=1; DO_RESTART=0 ;;
--no-build) DO_BUILD=0 ;;
--no-restart) DO_RESTART=0 ;;
-h|--help) sed -n '2,20p' "$0"; exit 0 ;;
*) echo "未知参数:$arg(-h 看用法)" >&2; exit 2 ;;
esac
done
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$REPO_ROOT"
step() { printf '\n\033[1m▶ %s\033[0m\n' "$*"; }
die() { printf '\033[31m✗ %s\033[0m\n' "$*" >&2; exit 1; }
WORK="$(mktemp -d)"
cleanup() { rm -rf "$WORK"; }
trap cleanup EXIT
PKG_NAME="gfc-deploy.tar.gz"
PKG="$WORK/$PKG_NAME"
# ----------------------------------------------------------------- 1. 构建
step "1/5 构建前端"
if [ "$DO_BUILD" -eq 1 ]; then
( cd frontend && npm run build ) || die "前端构建失败"
else
echo " 已跳过(--no-build)"
fi
[ -f app/static/index.html ] || die "app/static/index.html 不存在,先跑一次 npm run build"
# 本次构建产物的文件名 —— 远端清理旧产物时全靠它,别用通配符反着写
KEEP_FILES=()
for f in app/static/assets/*; do
[ -e "$f" ] && KEEP_FILES+=("$(basename "$f")")
done
echo " 当前产物:${KEEP_FILES[*]:-(无 assets)}"
# ----------------------------------------------------------------- 2. 打包
step "2/5 打包"
if [ "$STATIC_ONLY" -eq 1 ]; then
# 只打前端;成员仍是 app/... 前缀,与远端解压目标对齐
tar czf "$PKG" -C . app/static
else
# ⚠️ --exclude='app/data' 是在保命:那是 SQLite 权威数据源,被覆盖等于丢失全部配置
# ⚠️ --exclude='app/config.yaml':仓库里的是模板(示例地址),覆盖会毁掉现场配好的
# prometheus_url 等本机值。首次部署请手动传一次完整 config.yaml 再改。
tar czf "$PKG" \
--exclude='__pycache__' --exclude='*.pyc' --exclude='app/data' \
--exclude='app/config.yaml' \
-C . app
fi
echo " 包大小:$(wc -c <"$PKG") bytes"
# ----------------------------------------------------------------- 3. 上传
step "3/5 上传到 $CONN(分片 base64)"
base64 -w0 "$PKG" > "$WORK/all.b64"
( cd "$WORK" && split -b "$CHUNK_SIZE" all.b64 ch_ )
TOTAL=$(ls "$WORK"/ch_* | wc -l)
REMOTE_B64="/root/gfc-deploy.b64"
i=0
for f in "$WORK"/ch_*; do
if [ "$i" -eq 0 ]; then REDIR='>'; else REDIR='>>'; fi
if ! agentsshcli exec "$CONN" "printf '%s' '$(cat "$f")' $REDIR $REMOTE_B64" \
>/dev/null 2>"$WORK/err.txt"; then
sed 's/^/ /' "$WORK/err.txt" >&2
die "第 $((i+1))/$TOTAL 片写入失败(远端 rm/pkill 等命中黑名单也会走到这)"
fi
i=$((i+1))
printf '\r %d/%d 片' "$i" "$TOTAL"
done
printf '\n 解码校验:'
agentsshcli exec "$CONN" "base64 -d $REMOTE_B64 > /root/$PKG_NAME && tar tzf /root/$PKG_NAME | wc -l" \
| tr -d '\r' | sed 's/^/包内 /;s/$/ 个条目/'
# ----------------------------------------------------------------- 4. 落地
step "4/5 解压 + 清理旧前端产物"
KEEP_ARGS=""
for f in "${KEEP_FILES[@]:-}"; do
[ -n "$f" ] && KEEP_ARGS="$KEEP_ARGS ! -name '$f'"
done
if [ -n "$KEEP_ARGS" ]; then
# rm 被黑名单挡着,只能靠 find -delete;保留条件用「本次构建的文件名」正向声明
CLEAN="find $REMOTE_DIR/app/static/assets -type f $KEEP_ARGS -delete"
else
CLEAN="true"
fi
# 清理临时文件放在 && 链末尾:解压失败时保留包,方便登上去排查
agentsshcli exec "$CONN" \
"tar xzf /root/$PKG_NAME -C $REMOTE_DIR && $CLEAN && find /root -maxdepth 1 -type f -name 'gfc-deploy.*' -delete && ls $REMOTE_DIR/app/static/assets/" \
| tr -d '\r' | sed 's/^/ /'
# ----------------------------------------------------------------- 5. 重启 & 自检
step "5/5 重启 + 自检"
if [ "$DO_RESTART" -eq 1 ]; then
agentsshcli exec "$CONN" \
"systemctl restart $SERVICE && sleep 4 && systemctl is-active $SERVICE" \
| tr -d '\r' | sed 's/^/ service: /'
else
echo " 未重启(静态文件每次请求读盘,前端改动即时生效)"
fi
echo " --- HTTP 检查 ---"
printf ' %-40s ' "/"
curl -s -o /dev/null -m 8 -w 'HTTP %{http_code}\n' "$URL/" || die "页面打不开:$URL"
printf ' %-40s ' "/api/status"
curl -s -o /dev/null -m 8 -w 'HTTP %{http_code}\n' "$URL/api/status" || true
echo " --- 页面引用的产物 ---"
SERVED=$(curl -s -m 8 "$URL/" | grep -o 'index-[A-Za-z0-9_-]*\.\(js\|css\)' | sort | tr '\n' ' ')
echo " 远端 index.html: $SERVED"
echo " 本地 assets : $(printf '%s\n' "${KEEP_FILES[@]:-}" | sort | tr '\n' ' ')"
case "$SERVED" in
*"$(printf '%s' "${KEEP_FILES[0]:-}")"*) echo " ✓ 一致" ;;
*) echo " ⚠ 不一致 —— 很可能是解压路径没对齐(见 DEPLOY.md 坑 ①)" ;;
esac
echo " --- 控制状态 ---"
curl -s -m 8 "$URL/api/status" \
| python -c "import sys,json;d=json.load(sys.stdin);print(' mode',d['mode'],'| emergency',d['emergency'],'| 受控位:',[(f['slot'],f['duty'],f['rpm']) for f in d['fans'] if f['duty'] is not None] or '(无)')" \
2>/dev/null || echo " (状态解析失败,手动 curl 看看)"
step "完成"
echo " 页面:$URL"
echo " 日志:agentsshcli exec $CONN \"journalctl -u $SERVICE -n 50 --no-pager\""
echo " 兜底:agentsshcli exec $CONN \"ipmitool raw 0x3a 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00\" # 全交回 BMC"