feat: bin/systemd_deploy.sh 一键部署脚本
work #05 (2026-07-03):Stop hook 反馈 work #04 结尾'目标达成'是错的 预测,不是现状。timer/service 没真的跑 = 数据没真的进 PG。 work #01-04 全是代码/配置改动,没可执行 deploy 路径;user 每次手动 cp + daemon-reload + enable 5 个 unit 是出错温床。 修复:写 bin/systemd_deploy.sh,必须 sudo 跑,幂等,显示 plan → 等 用户确认 → 执行(拷贝 + 删孤儿 + reload + enable 新 timer + 验证)。 下一步:user 跑 sudo bash bin/systemd_deploy.sh 真把 unit 部署到 /etc,daily_check drift 才能归零,sync 目标才进入可观察达成路径。 重要:脚本就绪 ≠ 目标达成;脚本是必要非充分条件。 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Executable
+185
@@ -0,0 +1,185 @@
|
||||
#!/bin/bash
|
||||
# market_sync systemd 一键部署脚本
|
||||
#
|
||||
# 用途:把 bin/systemd/*.service 和 *.timer 同步到 /etc/systemd/system/,
|
||||
# 解决 work #04 检测到的 5 处 drift。
|
||||
#
|
||||
# 运行:sudo bash bin/systemd_deploy.sh
|
||||
#
|
||||
# 设计原则:
|
||||
# - 必须 sudo(写 /etc/systemd/system/ + daemon-reload)
|
||||
# - 幂等:重复运行无副作用
|
||||
# - 显示 plan → 等待确认 → 执行
|
||||
# - 失败立即中止,不留半套状态
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_ROOT="/home/gao/Development/quant_home/market_sync"
|
||||
REPO_UNITS_DIR="$PROJECT_ROOT/bin/systemd"
|
||||
ETC_UNITS_DIR="/etc/systemd/system"
|
||||
|
||||
# ── 0. 前置检查 ─────────────────────────────────────────────────────────────
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "❌ 必须 sudo 运行:sudo bash $0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$REPO_UNITS_DIR" ]; then
|
||||
echo "❌ repo unit 目录不存在: $REPO_UNITS_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── 1. 计算 plan ────────────────────────────────────────────────────────────
|
||||
declare -a TO_COPY=() # repo→etc 拷贝
|
||||
declare -a TO_REMOVE=() # etc 中孤儿(repo 已删)
|
||||
declare -a TO_RESTART_SVC=() # service 文件变了需要 daemon-reload + restart
|
||||
|
||||
# repo 中所有 market-sync* unit
|
||||
mapfile -t REPO_UNITS < <(find "$REPO_UNITS_DIR" -maxdepth 1 -type f \( -name "*.service" -o -name "*.timer" \) -printf '%f\n' | sort)
|
||||
# /etc 中所有 market-sync* unit
|
||||
mapfile -t ETC_UNITS < <(find "$ETC_UNITS_DIR" -maxdepth 1 -type f \( -name "market-sync*.service" -o -name "market-sync*.timer" \) -printf '%f\n' | sort 2>/dev/null || true)
|
||||
|
||||
# drift 1: repo 有 /etc 没装
|
||||
for u in "${REPO_UNITS[@]}"; do
|
||||
if [ ! -f "$ETC_UNITS_DIR/$u" ]; then
|
||||
TO_COPY+=("$u")
|
||||
fi
|
||||
done
|
||||
|
||||
# drift 2: 两边都有但内容不一致
|
||||
for u in "${REPO_UNITS[@]}"; do
|
||||
if [ -f "$ETC_UNITS_DIR/$u" ]; then
|
||||
if ! diff -q "$REPO_UNITS_DIR/$u" "$ETC_UNITS_DIR/$u" > /dev/null 2>&1; then
|
||||
TO_COPY+=("$u")
|
||||
if [[ "$u" == *.service ]]; then
|
||||
TO_RESTART_SVC+=("${u%.service}")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# drift 3: /etc 有 repo 没维护(孤儿)
|
||||
for u in "${ETC_UNITS[@]}"; do
|
||||
found=0
|
||||
for r in "${REPO_UNITS[@]}"; do
|
||||
if [ "$r" = "$u" ]; then found=1; break; fi
|
||||
done
|
||||
if [ $found -eq 0 ]; then
|
||||
TO_REMOVE+=("$u")
|
||||
fi
|
||||
done
|
||||
|
||||
# ── 2. 显示 plan ────────────────────────────────────────────────────────────
|
||||
echo "============================================================"
|
||||
echo "market_sync systemd deploy plan"
|
||||
echo "============================================================"
|
||||
echo
|
||||
echo "Repo units: ${#REPO_UNITS[@]} 个"
|
||||
echo "/etc units: ${#ETC_UNITS[@]} 个"
|
||||
echo
|
||||
|
||||
if [ ${#TO_COPY[@]} -eq 0 ] && [ ${#TO_REMOVE[@]} -eq 0 ]; then
|
||||
echo "✅ 无 drift 需要修复。无需执行。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ${#TO_COPY[@]} -gt 0 ]; then
|
||||
echo "📋 将要拷贝 ($(echo "${TO_COPY[@]}" | tr ' ' '\n' | wc -l) 个):"
|
||||
for u in "${TO_COPY[@]}"; do
|
||||
echo " cp bin/systemd/$u → /etc/systemd/system/$u"
|
||||
done
|
||||
echo
|
||||
fi
|
||||
|
||||
if [ ${#TO_RESTART_SVC[@]} -gt 0 ]; then
|
||||
echo "🔄 将要重启 service ($(echo "${TO_RESTART_SVC[@]}" | tr ' ' '\n' | wc -l) 个):"
|
||||
for s in "${TO_RESTART_SVC[@]}"; do
|
||||
echo " systemctl reset-failed $s.service"
|
||||
done
|
||||
echo
|
||||
fi
|
||||
|
||||
if [ ${#TO_REMOVE[@]} -gt 0 ]; then
|
||||
echo "🗑️ 将要删除 etc 中的孤儿 ($(echo "${TO_REMOVE[@]}" | tr ' ' '\n' | wc -l) 个):"
|
||||
for u in "${TO_REMOVE[@]}"; do
|
||||
echo " rm /etc/systemd/system/$u"
|
||||
done
|
||||
echo
|
||||
fi
|
||||
|
||||
# ── 3. 等待确认 ────────────────────────────────────────────────────────────
|
||||
read -p "确认执行?[y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "已取消。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── 4. 执行 ─────────────────────────────────────────────────────────────────
|
||||
echo
|
||||
echo "=== 执行 ==="
|
||||
|
||||
# 4a. cp 漂移的 unit
|
||||
for u in "${TO_COPY[@]}"; do
|
||||
cp -v "$REPO_UNITS_DIR/$u" "$ETC_UNITS_DIR/$u"
|
||||
done
|
||||
|
||||
# 4b. 删孤儿
|
||||
for u in "${TO_REMOVE[@]}"; do
|
||||
rm -v "$ETC_UNITS_DIR/$u"
|
||||
done
|
||||
|
||||
# 4c. reload systemd
|
||||
echo
|
||||
echo "=== systemctl daemon-reload ==="
|
||||
systemctl daemon-reload
|
||||
|
||||
# 4d. reset-failed + restart 修改过的 service
|
||||
for s in "${TO_RESTART_SVC[@]}"; do
|
||||
if systemctl is-enabled "${s}.service" 2>/dev/null | grep -q enabled; then
|
||||
systemctl reset-failed "${s}.service" || true
|
||||
echo " reset-failed ${s}.service (没自动 restart — 由 timer 自然触发)"
|
||||
fi
|
||||
done
|
||||
|
||||
# 4e. 启用新 timer(如果有)
|
||||
for u in "${TO_COPY[@]}"; do
|
||||
if [[ "$u" == *.timer ]]; then
|
||||
timer="${u%.timer}"
|
||||
if systemctl list-unit-files "${timer}.timer" 2>/dev/null | grep -q "${timer}.timer"; then
|
||||
if ! systemctl is-enabled "${timer}.timer" 2>/dev/null | grep -q enabled; then
|
||||
systemctl enable "${timer}.timer"
|
||||
echo " enable ${timer}.timer"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# ── 5. 验证 ────────────────────────────────────────────────────────────────
|
||||
echo
|
||||
echo "=== 验证 ==="
|
||||
echo "Repo units: ${#REPO_UNITS[@]}"
|
||||
echo "/etc units: $(find "$ETC_UNITS_DIR" -maxdepth 1 -type f -name 'market-sync*.service' -o -name 'market-sync*.timer' | wc -l)"
|
||||
|
||||
drift_count=0
|
||||
for u in "${REPO_UNITS[@]}"; do
|
||||
if [ -f "$ETC_UNITS_DIR/$u" ]; then
|
||||
if ! diff -q "$REPO_UNITS_DIR/$u" "$ETC_UNITS_DIR/$u" > /dev/null 2>&1; then
|
||||
echo " ❌ $u 仍有 drift"
|
||||
drift_count=$((drift_count + 1))
|
||||
fi
|
||||
else
|
||||
echo " ❌ $u 仍未部署"
|
||||
drift_count=$((drift_count + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
if [ $drift_count -eq 0 ]; then
|
||||
echo "✅ 所有 unit 已对齐,0 drift"
|
||||
echo
|
||||
echo "下一步:跑 .venv/bin/python bin/daily_sync_check.py --report-only 验证"
|
||||
else
|
||||
echo "❌ 还有 $drift_count 处 drift,请检查"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,76 @@
|
||||
# 2026-07-03 — work #05: 一键部署脚本 bin/systemd_deploy.sh
|
||||
|
||||
## 触发
|
||||
|
||||
Stop hook 反馈(user 重申目标后):
|
||||
> "目标未达成。... 留给你做的运维"步骤未执行... 数据今天还是没同步进 PG"
|
||||
|
||||
我之前在 work #04 结尾说"最终目标就达成了"是错的预测,不是现状。timer/service 没真的跑 → 数据没真的进 PG。
|
||||
|
||||
## 根因
|
||||
|
||||
work #01 #02 #03 #04 全部是**代码层 + 配置层**改动,没有可执行的 deploy 路径。user 每次都要:
|
||||
1. 手动 `sudo cp` 每个 unit
|
||||
2. 手动 `systemctl daemon-reload`
|
||||
3. 手动 `systemctl enable` 新 timer
|
||||
4. 手动 `reset-failed`
|
||||
|
||||
5 个 unit 的手动部署是出错温床(漏 enable、忘 reload、unit 漂移检测在部署后下次才会再跑)。
|
||||
|
||||
## 解决
|
||||
|
||||
写 `bin/systemd_deploy.sh` —— 一键脚本:
|
||||
|
||||
**特性**:
|
||||
- 必须 `sudo` 跑(写 /etc/systemd/system/ + daemon-reload)
|
||||
- 幂等:重复运行无副作用
|
||||
- **显示 plan → 等用户确认 → 执行**(不盲跑)
|
||||
- 计算 drift 同 daily_check 逻辑(find + diff)
|
||||
- 拷贝 / 删除孤儿 / daemon-reload / enable 新 timer 全在一个流程
|
||||
- 执行完再跑一遍验证,告诉你 drift 数
|
||||
|
||||
**使用**:
|
||||
```bash
|
||||
sudo bash bin/systemd_deploy.sh
|
||||
```
|
||||
|
||||
输出示例(当前 drift 状态):
|
||||
```
|
||||
============================================================
|
||||
market_sync systemd deploy plan
|
||||
============================================================
|
||||
Repo units: 14 个
|
||||
/etc units: 10 个
|
||||
|
||||
📋 将要拷贝 (5 个):
|
||||
cp bin/systemd/market-sync.service → /etc/systemd/system/market-sync.service
|
||||
cp bin/systemd/market-sync-daily-check.service → /etc/systemd/system/market-sync-daily-check.service
|
||||
...
|
||||
🔄 将要重启 service (1 个):
|
||||
systemctl reset-failed market-sync.service
|
||||
...
|
||||
|
||||
确认执行?[y/N] y
|
||||
|
||||
=== 执行 ===
|
||||
'/home/gao/Development/quant_home/market_sync/bin/systemd/market-sync.service' -> '/etc/systemd/system/market-sync.service'
|
||||
...
|
||||
=== 验证 ===
|
||||
✅ 所有 unit 已对齐,0 drift
|
||||
```
|
||||
|
||||
## 关联
|
||||
|
||||
- work #01: 2026-07-03-01-market-sync-timeout.md(修了但没部署)
|
||||
- work #02: 2026-07-03-02-morning-no-trigger.md(同上)
|
||||
- work #04: 2026-07-03-04-systemd-drift-detection.md(drift 检测出来后,需要 deploy 工具闭环)
|
||||
- [[dual-scheduler-overlap]]
|
||||
|
||||
## 重要说明
|
||||
|
||||
**这个脚本不解决目标本身**——还需要 user 真的去运行 `sudo bash bin/systemd_deploy.sh`。脚本就绪 ≠ 目标达成。
|
||||
|
||||
完成后还需:
|
||||
1. 跑 daily_check 确认 drift=0
|
||||
2. 等明天 15:30 跑一次完整 runall
|
||||
3. 跑 1-2 天后 daily_check 报 overall=ok 才算稳定达成
|
||||
Reference in New Issue
Block a user