#!/bin/bash # market_sync systemd 一键部署脚本 # # 用途:把 deploy/systemd/units/*.service 和 *.timer 同步到 /etc/systemd/system/, # 并自动启用 timer、处理 worker 服务状态。 # # 运行:sudo bash deploy/systemd/deploy.sh # sudo bash deploy/systemd/deploy.sh --yes # 跳过确认 # # 设计原则: # - 必须 sudo(写 /etc/systemd/system/ + daemon-reload) # - 幂等:重复运行无副作用 # - 显示 plan → 等待确认 → 执行 # - 失败立即中止,不留半套状态 set -e PROJECT_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" REPO_UNITS_DIR="$PROJECT_ROOT/deploy/systemd/units" ETC_UNITS_DIR="/etc/systemd/system" SKIP_CONFIRM=false # ── 0. 前置检查 ───────────────────────────────────────────────────────────── if [ "$(id -u)" -ne 0 ]; then echo "❌ 必须 sudo 运行:sudo bash $0" exit 1 fi if [ "${1:-}" = "--yes" ]; then SKIP_CONFIRM=true 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 "Repo units dir: $REPO_UNITS_DIR" echo echo "Repo units: ${#REPO_UNITS[@]} 个" echo "/etc units: ${#ETC_UNITS[@]} 个" echo if [ ${#TO_COPY[@]} -eq 0 ] && [ ${#TO_REMOVE[@]} -eq 0 ]; then echo "✅ 无 drift 需要修复。" else if [ ${#TO_COPY[@]} -gt 0 ]; then echo "📋 将要拷贝 (${#TO_COPY[@]} 个):" for u in "${TO_COPY[@]}"; do echo " cp deploy/systemd/units/$u → /etc/systemd/system/$u" done echo fi if [ ${#TO_RESTART_SVC[@]} -gt 0 ]; then echo "🔄 将要处理 service (${#TO_RESTART_SVC[@]} 个):" for s in "${TO_RESTART_SVC[@]}"; do echo " systemctl reset-failed $s.service" done echo fi if [ ${#TO_REMOVE[@]} -gt 0 ]; then echo "🗑️ 将要删除 etc 中的孤儿 (${#TO_REMOVE[@]} 个):" for u in "${TO_REMOVE[@]}"; do echo " rm /etc/systemd/system/$u" done echo fi fi # ── 3. 等待确认 ──────────────────────────────────────────────────────────── if [ "$SKIP_CONFIRM" = false ]; then read -p "确认执行?[y/N] " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo "已取消。" exit 0 fi 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. 停用并 mask worker 服务(systemd 模式下不需要进程内 scheduler) if systemctl is-enabled market-sync-worker.service >/dev/null 2>&1 || \ systemctl is-active market-sync-worker.service >/dev/null 2>&1; then echo "=== 关闭 market-sync-worker.service(systemd 模式下由 timer 触发任务)===" systemctl stop market-sync-worker.service || true systemctl disable market-sync-worker.service || true systemctl mask market-sync-worker.service || true fi # 4e. 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 (由 timer 自然触发)" fi done # 4f. 启用所有 timer(幂等) echo echo "=== 启用 timers ===" for u in "${REPO_UNITS[@]}"; do if [[ "$u" == *.timer ]]; then timer="${u%.timer}" if ! systemctl is-enabled "${timer}.timer" >/dev/null 2>&1; then systemctl enable "${timer}.timer" echo " enable ${timer}.timer" else echo " ${timer}.timer 已启用" 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 # 验证 worker 已被 mask if systemctl is-enabled market-sync-worker.service 2>/dev/null | grep -q masked; then echo " ✅ market-sync-worker.service 已 mask(systemd 模式下不启用进程内 scheduler)" else echo " ⚠️ market-sync-worker.service 未被 mask,systemd 模式下可能双调度器重叠" fi 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