What: - 新增 tools/schedule.sh,三個子命令:install 安裝、remove 移除、status 查現況。兩筆工作分別是每分鐘的心跳與每十五分鐘的巡檢。 - 助理主體的 start 接上安裝、stop 接上移除,行為清單與 README 跟著更新。 Why: - 心跳要有人定時寫,不然啟動之後過幾分鐘就自己過期,助理看起來像沒在跑。 - 排程這件事有標準的輸入與輸出,準則要求下放腳本,技能只描述何時呼叫。 How: - 條目行尾帶固定標記,安裝先濾掉自己的舊條目再追加。絕不覆寫整份排程,別人的條目一行都不碰;寫完回讀驗證,出現兩筆或別人的行數對不上就報錯。 - 裝完檢查排程服務在不在跑。這台機器是 WSL,預設不啟動 cron,裝了條目卻一次都不會執行——不檢查就會宣稱一件沒發生的事。 - 條目一律帶 </dev/null。排程是非互動環境,任何等輸入的東西都會把整筆卡死。 - 輸出寫到 JSC_HOME 底下,不寫進任何專案目錄,免得多出未追蹤檔污染別人的變更盤點。 - 巡檢那一筆預設不裝,要明著指定才會裝。巡檢本體還沒實作,裝了只會每十五分鐘失敗一次;巡檢指令也不猜 CLI,判不出來就停下,猜錯的代價是每十五分鐘跑一支不存在的執行檔。 - stop 先移除排程再清心跳,順序不能反。反過來的話清完下一分鐘排程就補寫一次,stop 等於騙人。 Who: 助理落地的第三塊。這裡留下一個要在巡檢那一輪解掉的問題:心跳目前由排程直接寫,所以心跳新鮮只證明排程活著,不證明助理做了事。往後應該改由巡檢跑完那一輪去寫,心跳才等於工作訊號。限制已寫進技能內文與說明文件。
447 lines
16 KiB
Bash
Executable File
447 lines
16 KiB
Bash
Executable File
#!/usr/bin/env sh
|
||
# schedule.sh — 助理系統排程的安裝、移除與查現況(供 jsc-assist:assistant 呼叫)。
|
||
#
|
||
# 用法:
|
||
# schedule.sh install [heartbeat|patrol|all] [--dry-run] [--cli {代號}] [--patrol-cmd {指令}]
|
||
# schedule.sh remove [heartbeat|patrol|all] [--dry-run]
|
||
# schedule.sh status [--dry-run]
|
||
#
|
||
# 工作代號省略時一律是 heartbeat。心跳每 60 秒寫一次,巡檢每 15 分鐘跑一輪。
|
||
# 巡檢那一筆要自己指名(`patrol` 或 `all`)才會裝——巡檢本體還沒實作,裝了每一輪都會失敗。
|
||
#
|
||
# 結束碼:
|
||
# 0 成功:install 條目寫進去也回讀得到、排程服務在跑;remove 移除完成,或本來就沒裝;
|
||
# status 印完現況(有沒有裝都算成功,看 installed 欄)
|
||
# 1 install 寫進去了,但排程服務沒在跑——條目不會被執行。WSL 預設不啟動 cron,這一碼
|
||
# 多半就是它。呼叫端一律照實講「排程裝了但不會執行」,不可以宣稱會定時執行
|
||
# 2 找不到 jsc-hooks 的 hooks/heartbeat.sh。心跳沒有東西可跑,整支停下
|
||
# 3 這台機器沒有可用的排程機制:認不得作業系統,或 crontab 與 schtasks 都找不到
|
||
# 4 排程操作失敗:讀不到現有排程(且失敗原因不是「沒有排程」)、寫入或刪除回非 0
|
||
# 5 回讀驗證失敗:寫入回 0 但條目不在,或移除回 0 但條目還在,又或其他人的條目數量對不上
|
||
# 6 用法錯誤:不認得的子命令、不認得的工作代號、缺參數,或判不出要用哪一支 CLI 跑巡檢
|
||
#
|
||
# --- 只動自己那一筆 ---
|
||
#
|
||
# 每一筆條目行尾都帶固定標記「# jsc-assist:assistant {工作代號}」,安裝與移除都靠它比對。
|
||
# 安裝先用 `grep -vF` 濾掉自己這幾個工作的舊條目,再把新條目追加上去,整份寫回;**絕不**
|
||
# 把 crontab 當空的重寫。`crontab -l` 在沒有任何排程時會回非 0,錯誤訊息才分得出是「沒有
|
||
# 排程」還是「權限不足」——後者當成空的寫回去,會把使用者整份排程刪光,所以這裡讀不懂
|
||
# 錯誤訊息就直接回 4,不猜。
|
||
# 寫回之後還會數行數:其他人的條目一行都不能少,少了就回 5。
|
||
#
|
||
# --- 排程服務沒在跑,等於沒裝 ---
|
||
#
|
||
# 條目寫進去不代表會被執行。WSL 預設不啟動 cron,要 `sudo service cron start`,而且重開
|
||
# WSL 之後要再啟動一次。安裝完一律檢查服務在不在跑,沒跑就回 1 並講清楚,不可以只回報
|
||
# 「排程已建立」。
|
||
#
|
||
# --- 排程是非互動環境 ---
|
||
#
|
||
# 條目一律接 `</dev/null`,跑的東西讀不到標準輸入,卡不住。heartbeat.sh 本來就不讀 stdin,
|
||
# 這一條是給巡檢那一筆用的:助理跳出權限詢問就會整輪卡死,等於排程沒跑。
|
||
#
|
||
# --- log 放哪裡 ---
|
||
#
|
||
# $JSC_HOME/assistant/schedule.log(JSC_HOME 未設定時為 ~/.jsc)。刻意放在專案外面:寫進
|
||
# 任何存取庫都會多出未追蹤檔,污染別人的變更盤點。
|
||
#
|
||
# 環境變數:
|
||
# JSC_HOME 助理狀態檔的根目錄,預設 ~/.jsc
|
||
# JSC_ASSIST_CRONTAB_CMD crontab 執行檔,預設 crontab。crontab 不在標準路徑,或要用
|
||
# 假的 crontab 驗濾除邏輯時才設
|
||
# JSC_ASSIST_PATROL_CMD 巡檢要跑的指令,優先於 --patrol-cmd 以外的所有推斷
|
||
# JSC_CLI 目前是哪一支 CLI,決定巡檢預設指令
|
||
set -u
|
||
|
||
MARK_PREFIX='# jsc-assist:assistant'
|
||
JSC_HOME="${JSC_HOME:-$HOME/.jsc}"
|
||
STATE_DIR="$JSC_HOME/assistant"
|
||
LOG="$STATE_DIR/schedule.log"
|
||
CRONTAB_CMD="${JSC_ASSIST_CRONTAB_CMD:-crontab}"
|
||
TASK_PREFIX='jsc-assist-assistant'
|
||
|
||
DRYRUN=0
|
||
CLI=''
|
||
PATROL_CMD="${JSC_ASSIST_PATROL_CMD:-}"
|
||
|
||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" 2>/dev/null && pwd)
|
||
SCRIPT_DIR="${SCRIPT_DIR:-.}"
|
||
|
||
usage() {
|
||
cat >&2 <<'EOF'
|
||
usage: schedule.sh install [heartbeat|patrol|all] [--dry-run] [--cli 代號] [--patrol-cmd 指令]
|
||
schedule.sh remove [heartbeat|patrol|all] [--dry-run]
|
||
schedule.sh status [--dry-run]
|
||
EOF
|
||
exit 6
|
||
}
|
||
|
||
die() { # $1=結束碼 $2=訊息
|
||
printf '[jsc][助理排程][ERR]:%s\n' "$2" >&2
|
||
exit "$1"
|
||
}
|
||
|
||
note() { printf '[jsc][助理排程]:%s\n' "$1" >&2; }
|
||
|
||
# 找出 jsc-hooks 的 hooks/heartbeat.sh 絕對路徑。搜尋順序比照 jsc-hooks lib.sh 的
|
||
# jsc_gitea_sh():先環境變數,再開發用的並排存取庫版面,最後已安裝的 plugin 快取版面。
|
||
heartbeat_sh() {
|
||
if [ -n "${JSC_HOOKS_HOOKS:-}" ] && [ -f "$JSC_HOOKS_HOOKS/heartbeat.sh" ]; then
|
||
printf '%s\n' "$JSC_HOOKS_HOOKS/heartbeat.sh"; return 0
|
||
fi
|
||
_root="${CLAUDE_PLUGIN_ROOT:-$SCRIPT_DIR/..}"
|
||
for _c in "$_root/../hooks/hooks/heartbeat.sh" "$_root/../jsc-hooks/hooks/heartbeat.sh"; do
|
||
[ -f "$_c" ] && { (CDPATH= cd -- "$(dirname -- "$_c")" && printf '%s/heartbeat.sh\n' "$(pwd)"); return 0; }
|
||
done
|
||
_c=$(ls -d "$_root"/../../jsc-hooks/*/hooks/heartbeat.sh \
|
||
"$_root"/../../hooks/*/hooks/heartbeat.sh \
|
||
"$HOME"/.claude/plugins/cache/*/jsc-hooks/*/hooks/heartbeat.sh 2>/dev/null \
|
||
| sort | tail -n1)
|
||
[ -n "$_c" ] && [ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
|
||
_c=$(command -v heartbeat.sh 2>/dev/null || true)
|
||
[ -n "$_c" ] && { printf '%s\n' "$_c"; return 0; }
|
||
return 1
|
||
}
|
||
|
||
os_kind() {
|
||
case "$(uname -s 2>/dev/null)" in
|
||
Linux) printf 'linux' ;;
|
||
Darwin) printf 'darwin' ;;
|
||
MINGW*|MSYS*|CYGWIN*|Windows_NT) printf 'windows' ;;
|
||
*) printf 'unknown' ;;
|
||
esac
|
||
}
|
||
|
||
# 這台機器要用哪一種排程機制。Linux、WSL 與 macOS 都用 crontab;macOS 若要改用 launchd
|
||
# 請自行改寫,本腳本不代為產生 plist。
|
||
mechanism() {
|
||
case "$(os_kind)" in
|
||
windows) command -v schtasks >/dev/null 2>&1 && { printf 'schtasks'; return 0; } ;;
|
||
linux|darwin) command -v "$CRONTAB_CMD" >/dev/null 2>&1 && { printf 'crontab'; return 0; } ;;
|
||
esac
|
||
return 1
|
||
}
|
||
|
||
# 排程服務在不在跑。回 running、stopped 或 unknown。
|
||
service_state() {
|
||
case "$(os_kind)" in
|
||
darwin)
|
||
# macOS 的 cron 由 launchd 隨用隨起,查不到行程不代表沒在跑,一律回 unknown。
|
||
printf 'unknown'; return 0 ;;
|
||
windows)
|
||
printf 'unknown'; return 0 ;;
|
||
esac
|
||
if command -v pgrep >/dev/null 2>&1; then
|
||
if pgrep -x cron >/dev/null 2>&1 || pgrep -x crond >/dev/null 2>&1; then
|
||
printf 'running'
|
||
else
|
||
printf 'stopped'
|
||
fi
|
||
return 0
|
||
fi
|
||
if command -v ps >/dev/null 2>&1; then
|
||
if ps -e 2>/dev/null | grep -qE '[ /](cron|crond)$'; then printf 'running'; else printf 'stopped'; fi
|
||
return 0
|
||
fi
|
||
printf 'unknown'
|
||
}
|
||
|
||
marker_of() { printf '%s %s' "$MARK_PREFIX" "$1"; }
|
||
|
||
# 數行數。用 grep -c '' 不用 wc -l:最後一行沒有換行時 wc -l 會少數一行。
|
||
# grep 數到 0 會回非 0,數字照樣印得出來,所以只在完全沒有輸出時才補 0。
|
||
count_lines() { _n=$(grep -c '' "$1" 2>/dev/null); [ -n "$_n" ] || _n=0; printf '%s' "$_n"; }
|
||
|
||
# 數管線進來的行數,語意同 count_lines。
|
||
count_stdin() { _n=$(grep -c '' 2>/dev/null); [ -n "$_n" ] || _n=0; printf '%s' "$_n"; }
|
||
|
||
spec_of() {
|
||
case "$1" in
|
||
heartbeat) printf '* * * * *' ;;
|
||
patrol) printf '*/15 * * * *' ;;
|
||
esac
|
||
}
|
||
|
||
# 巡檢要跑的指令。優先序:--patrol-cmd 或 JSC_ASSIST_PATROL_CMD > 依 CLI 代號推斷。
|
||
# 判不出 CLI 就回非 0,由主流程回 6,不猜——猜錯會每 15 分鐘跑一支不存在的執行檔。
|
||
patrol_command() {
|
||
[ -n "$PATROL_CMD" ] && { printf '%s' "$PATROL_CMD"; return 0; }
|
||
_cli="$CLI"
|
||
[ -n "$_cli" ] || _cli="${JSC_CLI:-}"
|
||
[ -n "$_cli" ] || { [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && _cli=claude; }
|
||
case "$_cli" in
|
||
claude) printf 'claude -p "/jsc-assist:patrol"' ;;
|
||
codex) printf "codex exec '\$patrol'" ;;
|
||
copilot) printf 'copilot -p "跑一輪助理巡檢"' ;;
|
||
antigravity) printf 'agy -p "/jsc-assist:patrol"' ;;
|
||
kiro) printf 'kiro-cli -p "跑一輪助理巡檢"' ;;
|
||
*) return 1 ;;
|
||
esac
|
||
}
|
||
|
||
# 組出一筆 crontab 條目。`%` 在 crontab 是換行符號,一律跳脫。
|
||
cron_entry() { # $1=工作代號
|
||
_spec=$(spec_of "$1")
|
||
case "$1" in
|
||
heartbeat) _cmd="JSC_CLI=cron JSC_SESSION_ID=schedule '$HEARTBEAT' write" ;;
|
||
patrol) _cmd="JSC_CLI=cron JSC_SESSION_ID=schedule $PATROL_RESOLVED" ;;
|
||
esac
|
||
printf '%s %s </dev/null >>%s 2>&1 %s' \
|
||
"$_spec" "$_cmd" "'$LOG'" "$(marker_of "$1")" | sed 's/%/\\%/g'
|
||
}
|
||
|
||
task_name() { printf '%s-%s' "$TASK_PREFIX" "$1"; }
|
||
|
||
# 讀現有的 crontab 到 $1。沒有任何排程時 crontab -l 會回非 0,那算正常;讀不懂的錯誤
|
||
# 一律回 4,不當成空的——當成空的寫回去會把使用者整份排程刪光。
|
||
cron_read() { # $1=輸出檔
|
||
_err="$TMPD/err"
|
||
if "$CRONTAB_CMD" -l >"$1" 2>"$_err"; then return 0; fi
|
||
if [ ! -s "$_err" ] || grep -qiE 'no crontab|沒有 crontab' "$_err"; then
|
||
: >"$1"; return 0
|
||
fi
|
||
die 4 "讀不到現有排程,原因不是「沒有排程」:$(tr '\n' ' ' <"$_err")"
|
||
}
|
||
|
||
cron_lines_for() { # $1=crontab 檔 $2=工作代號;印出該工作的條目
|
||
grep -F "$(marker_of "$2")" "$1" 2>/dev/null || true
|
||
}
|
||
|
||
# --- 參數解析 ---
|
||
|
||
CMD="${1:-}"
|
||
[ -n "$CMD" ] || usage
|
||
shift
|
||
case "$CMD" in install|remove|status) ;; *) usage ;; esac
|
||
|
||
JOBS='heartbeat'
|
||
if [ "$#" -gt 0 ]; then
|
||
case "$1" in
|
||
heartbeat) JOBS='heartbeat'; shift ;;
|
||
patrol) JOBS='patrol'; shift ;;
|
||
all) JOBS='heartbeat patrol'; shift ;;
|
||
esac
|
||
fi
|
||
|
||
while [ "$#" -gt 0 ]; do
|
||
case "$1" in
|
||
--dry-run) DRYRUN=1; shift ;;
|
||
--cli) [ "$#" -ge 2 ] || usage; CLI="$2"; shift 2 ;;
|
||
--patrol-cmd) [ "$#" -ge 2 ] || usage; PATROL_CMD="$2"; shift 2 ;;
|
||
*) usage ;;
|
||
esac
|
||
done
|
||
|
||
HEARTBEAT=$(heartbeat_sh) \
|
||
|| die 2 '找不到 jsc-hooks 的 hooks/heartbeat.sh,心跳沒有東西可跑。請先安裝 jsc-hooks 0.3.7 以上。'
|
||
MECH=$(mechanism) \
|
||
|| die 3 "這台機器沒有可用的排程機制(作業系統:$(os_kind),找不到 $CRONTAB_CMD 或 schtasks)。"
|
||
|
||
# 巡檢指令在這裡就解出來。放進 cron_entry 再解的話,那支是在命令替換的子行程裡跑,
|
||
# 判不出 CLI 時 die 只結束子行程,主流程會帶著空指令繼續往下裝。
|
||
PATROL_RESOLVED=''
|
||
case "$CMD:$JOBS" in
|
||
install:*patrol*)
|
||
PATROL_RESOLVED=$(patrol_command) \
|
||
|| die 6 '判不出要用哪一支 CLI 跑巡檢,請帶 --cli {claude|codex|copilot|antigravity|kiro} 或 --patrol-cmd「指令」。' ;;
|
||
esac
|
||
|
||
TMPD=$(mktemp -d) || die 4 '建不出暫存目錄。'
|
||
trap 'rm -rf "$TMPD"' EXIT
|
||
|
||
# --- schtasks(Windows)---
|
||
|
||
schtasks_install() {
|
||
_rc=0
|
||
for _job in $JOBS; do
|
||
_tn=$(task_name "$_job")
|
||
case "$_job" in
|
||
heartbeat) _mo=1; _run="sh \"$HEARTBEAT\" write" ;;
|
||
patrol) _mo=15; _run=$(patrol_command) || die 6 '判不出要用哪一支 CLI 跑巡檢,請帶 --cli 或 --patrol-cmd。' ;;
|
||
esac
|
||
_tr="cmd /c $_run <NUL >> \"$LOG\" 2>&1"
|
||
if [ "$DRYRUN" -eq 1 ]; then
|
||
printf 'dryrun=schtasks job=%s task=%s interval=%s cmd=%s\n' "$_job" "$_tn" "$_mo" "$_tr"
|
||
continue
|
||
fi
|
||
# /F 只覆蓋同名任務,也就是本腳本自己那一筆,不影響別人的排程。
|
||
schtasks /Create /TN "$_tn" /SC MINUTE /MO "$_mo" /F /TR "$_tr" >/dev/null 2>&1 \
|
||
|| die 4 "schtasks 建立 $_tn 失敗。"
|
||
schtasks /Query /TN "$_tn" >/dev/null 2>&1 || die 5 "schtasks 建立 $_tn 回 0,卻查不到這個任務。"
|
||
_rc=0
|
||
done
|
||
return "$_rc"
|
||
}
|
||
|
||
schtasks_remove() {
|
||
for _job in $JOBS; do
|
||
_tn=$(task_name "$_job")
|
||
if [ "$DRYRUN" -eq 1 ]; then
|
||
printf 'dryrun=schtasks job=%s task=%s action=delete\n' "$_job" "$_tn"
|
||
continue
|
||
fi
|
||
if ! schtasks /Query /TN "$_tn" >/dev/null 2>&1; then
|
||
note "$_job 本來就沒有排程,不用移除。"
|
||
continue
|
||
fi
|
||
schtasks /Delete /TN "$_tn" /F >/dev/null 2>&1 || die 4 "schtasks 刪除 $_tn 失敗。"
|
||
schtasks /Query /TN "$_tn" >/dev/null 2>&1 && die 5 "schtasks 刪除 $_tn 回 0,任務卻還在。"
|
||
done
|
||
return 0
|
||
}
|
||
|
||
schtasks_status() {
|
||
printf 'mechanism=schtasks service=%s log=%s\n' "$(service_state)" "$LOG"
|
||
for _job in heartbeat patrol; do
|
||
_tn=$(task_name "$_job")
|
||
if schtasks /Query /TN "$_tn" >/dev/null 2>&1; then
|
||
printf 'job=%s installed=yes task=%s\n' "$_job" "$_tn"
|
||
else
|
||
printf 'job=%s installed=no task=%s\n' "$_job" "$_tn"
|
||
fi
|
||
done
|
||
return 0
|
||
}
|
||
|
||
# --- crontab(Linux、WSL、macOS)---
|
||
|
||
crontab_install() {
|
||
_cur="$TMPD/cur"; _new="$TMPD/new"
|
||
cron_read "$_cur"
|
||
|
||
cp "$_cur" "$_new"
|
||
# 先濾掉自己這幾個工作的舊條目,再追加新的。重跑不會疊成兩筆,別人的條目原樣留著。
|
||
for _job in $JOBS; do
|
||
grep -vF "$(marker_of "$_job")" "$_new" >"$TMPD/f" 2>/dev/null || true
|
||
mv "$TMPD/f" "$_new"
|
||
done
|
||
_others=$(count_lines "$_new")
|
||
for _job in $JOBS; do
|
||
cron_entry "$_job" >>"$_new"
|
||
printf '\n' >>"$_new"
|
||
done
|
||
|
||
if [ "$DRYRUN" -eq 1 ]; then
|
||
for _job in $JOBS; do
|
||
printf 'dryrun=crontab job=%s entry=%s\n' "$_job" "$(cron_entry "$_job")"
|
||
done
|
||
printf 'dryrun=crontab action=write others_kept=%s total_lines=%s\n' \
|
||
"$_others" "$(count_lines "$_new")"
|
||
printf -- '--- 寫回後的 crontab ---\n'
|
||
cat "$_new"
|
||
return 0
|
||
fi
|
||
|
||
mkdir -p "$STATE_DIR" 2>/dev/null || true
|
||
"$CRONTAB_CMD" "$_new" >/dev/null 2>"$TMPD/err" \
|
||
|| die 4 "crontab 寫入失敗:$(tr '\n' ' ' <"$TMPD/err")"
|
||
|
||
_chk="$TMPD/chk"; cron_read "$_chk"
|
||
for _job in $JOBS; do
|
||
[ -n "$(cron_lines_for "$_chk" "$_job")" ] \
|
||
|| die 5 "crontab 寫入回 0,卻讀不到 $_job 的條目。"
|
||
[ "$(cron_lines_for "$_chk" "$_job" | count_stdin)" -eq 1 ] \
|
||
|| die 5 "$_job 的條目不只一筆,排程會重複執行。"
|
||
done
|
||
_kept=$(grep -vF "$MARK_PREFIX" "$_chk" 2>/dev/null | count_stdin)
|
||
[ "$_kept" -eq "$_others" ] \
|
||
|| die 5 "別人的排程條目從 $_others 筆變成 $_kept 筆,寫回不完整。"
|
||
|
||
for _job in $JOBS; do
|
||
printf 'installed=%s entry=%s\n' "$_job" "$(cron_lines_for "$_chk" "$_job")"
|
||
done
|
||
printf 'others_kept=%s log=%s\n' "$_kept" "$LOG"
|
||
return 0
|
||
}
|
||
|
||
crontab_remove() {
|
||
_cur="$TMPD/cur"; _new="$TMPD/new"
|
||
cron_read "$_cur"
|
||
|
||
_hit=0
|
||
cp "$_cur" "$_new"
|
||
for _job in $JOBS; do
|
||
if [ -n "$(cron_lines_for "$_new" "$_job")" ]; then
|
||
_hit=$((_hit + 1))
|
||
else
|
||
note "$_job 本來就沒有排程,不用移除。"
|
||
continue
|
||
fi
|
||
grep -vF "$(marker_of "$_job")" "$_new" >"$TMPD/f" 2>/dev/null || true
|
||
mv "$TMPD/f" "$_new"
|
||
done
|
||
|
||
if [ "$_hit" -eq 0 ]; then
|
||
printf 'removed=0 others_kept=%s\n' "$(count_lines "$_cur")"
|
||
return 0
|
||
fi
|
||
|
||
if [ "$DRYRUN" -eq 1 ]; then
|
||
printf 'dryrun=crontab action=remove jobs=%s removed=%s\n' "$JOBS" "$_hit"
|
||
printf -- '--- 寫回後的 crontab ---\n'
|
||
cat "$_new"
|
||
return 0
|
||
fi
|
||
|
||
"$CRONTAB_CMD" "$_new" >/dev/null 2>"$TMPD/err" \
|
||
|| die 4 "crontab 寫入失敗:$(tr '\n' ' ' <"$TMPD/err")"
|
||
|
||
_chk="$TMPD/chk"; cron_read "$_chk"
|
||
for _job in $JOBS; do
|
||
[ -z "$(cron_lines_for "$_chk" "$_job")" ] \
|
||
|| die 5 "crontab 刪除回 0,$_job 的條目卻還在。"
|
||
done
|
||
_before=$(grep -vF "$MARK_PREFIX" "$_cur" 2>/dev/null | count_stdin)
|
||
_after=$(grep -vF "$MARK_PREFIX" "$_chk" 2>/dev/null | count_stdin)
|
||
[ "$_before" -eq "$_after" ] \
|
||
|| die 5 "別人的排程條目從 $_before 筆變成 $_after 筆,移除動到了不該動的東西。"
|
||
|
||
printf 'removed=%s others_kept=%s\n' "$_hit" "$_after"
|
||
return 0
|
||
}
|
||
|
||
crontab_status() {
|
||
_cur="$TMPD/cur"; cron_read "$_cur"
|
||
printf 'mechanism=crontab service=%s log=%s\n' "$(service_state)" "$LOG"
|
||
for _job in heartbeat patrol; do
|
||
_line=$(cron_lines_for "$_cur" "$_job")
|
||
if [ -n "$_line" ]; then
|
||
printf 'job=%s installed=yes entry=%s\n' "$_job" "$_line"
|
||
else
|
||
printf 'job=%s installed=no entry=\n' "$_job"
|
||
fi
|
||
done
|
||
return 0
|
||
}
|
||
|
||
# --- 主流程 ---
|
||
|
||
case "$CMD" in
|
||
install)
|
||
case "$MECH" in
|
||
crontab) crontab_install ;;
|
||
schtasks) schtasks_install ;;
|
||
esac
|
||
[ "$DRYRUN" -eq 1 ] && exit 0
|
||
_svc=$(service_state)
|
||
if [ "$_svc" = stopped ]; then
|
||
printf 'service=stopped\n'
|
||
die 1 '排程條目寫進去了,但 cron 服務沒在跑,條目一次都不會被執行。WSL 預設不啟動 cron:請跑 `sudo service cron start`,而且重開 WSL 之後要再啟動一次。'
|
||
fi
|
||
printf 'service=%s\n' "$_svc"
|
||
[ "$_svc" = unknown ] && note '判不出排程服務在不在跑,請自行確認條目真的會被執行。'
|
||
exit 0 ;;
|
||
remove)
|
||
case "$MECH" in
|
||
crontab) crontab_remove ;;
|
||
schtasks) schtasks_remove ;;
|
||
esac
|
||
exit 0 ;;
|
||
status)
|
||
case "$MECH" in
|
||
crontab) crontab_status ;;
|
||
schtasks) schtasks_status ;;
|
||
esac
|
||
exit 0 ;;
|
||
esac
|