diff --git a/bin/systemd_deploy.sh b/bin/systemd_deploy.sh new file mode 100755 index 0000000..9b3bc48 --- /dev/null +++ b/bin/systemd_deploy.sh @@ -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 \ No newline at end of file diff --git a/docs/works/2026-07-03-05-systemd-deploy-script.md b/docs/works/2026-07-03-05-systemd-deploy-script.md new file mode 100644 index 0000000..b27a693 --- /dev/null +++ b/docs/works/2026-07-03-05-systemd-deploy-script.md @@ -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 才算稳定达成 \ No newline at end of file