diff --git a/scripts/daemon.sh b/scripts/daemon.sh old mode 100755 new mode 100644 index acb05fe..5556012 --- a/scripts/daemon.sh +++ b/scripts/daemon.sh @@ -3,7 +3,7 @@ set -euo pipefail # ============================================================================= # wechat-claude-code cross-platform daemon manager -# Supports: macOS (launchd) / Linux (systemd + nohup fallback) +# Supports: macOS (launchd) / Linux (systemd + nohup fallback) / Windows (Git Bash) # ============================================================================= DATA_DIR="${HOME}/.wechat-claude-code" @@ -375,6 +375,139 @@ linux_logs() { fi } +# ============================================================================= +# Windows (Git Bash / MSYS / Cygwin) functions +# No systemd/launchd here. We launch a fully detached process via PowerShell +# Start-Process so the daemon survives the parent shell exiting (avoids the +# EPIPE crash you get from a plain `nohup ... &` whose stdio is tied to bash). +# ============================================================================= + +win_pid_file() { echo "${DATA_DIR}/${SERVICE_NAME}.pid"; } + +# Convert a possible MSYS path (/c/...) to a Windows path (C:\...) for node.exe. +win_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -w "$1" + else + echo "$1" + fi +} + +# Liveness check for a NATIVE Windows PID. MSYS `kill -0` only knows processes +# it spawned itself, so it returns false for our PowerShell-launched daemon. +# tasklist sees every process on the machine. +win_pid_alive() { + local pid="$1" + [ -n "$pid" ] || return 1 + tasklist //FI "PID eq $pid" //NH 2>/dev/null | grep -q "$pid" +} + +win_start() { + local pid_file="$(win_pid_file)" + + if [ -f "$pid_file" ]; then + local old_pid=$(cat "$pid_file" 2>/dev/null | tr -d '[:space:]') + if [ -n "$old_pid" ] && win_pid_alive "$old_pid"; then + echo "Already running (PID: $old_pid)" + exit 0 + fi + rm -f "$pid_file" + fi + + mkdir -p "$DATA_DIR/logs" + + local node_bin="$(command -v node || echo node)" + local main_js_win="$(win_path "${PROJECT_DIR}/dist/main.js")" + local out_win="$(win_path "${DATA_DIR}/logs/stdout.log")" + local err_win="$(win_path "${DATA_DIR}/logs/stderr.log")" + + echo "Starting wechat-claude-code daemon (Windows detached mode)..." + + local wd_win="$(win_path "$PROJECT_DIR")" + local pid_file_win="$(win_path "$pid_file")" + + # IMPORTANT: do not capture PowerShell's stdout via $(...). The detached node + # child inherits the pipe handle, so bash never sees EOF and hangs forever. + # Instead PowerShell writes the child PID straight into the pidfile, and we + # fully detach the launcher's stdio from this shell. + local ps_cmd="\$p = Start-Process -FilePath 'node' -ArgumentList @('${main_js_win}','start') -WorkingDirectory '${wd_win}' -WindowStyle Hidden -RedirectStandardOutput '${out_win}' -RedirectStandardError '${err_win}' -PassThru; Set-Content -Path '${pid_file_win}' -Value \$p.Id -NoNewline" + powershell -NoProfile -Command "$ps_cmd" /dev/null 2>&1 + + # Give PowerShell a moment to spawn the child and write the pidfile. + local tries=0 + while [ ! -s "$pid_file" ] && [ $tries -lt 20 ]; do + sleep 0.3 + tries=$((tries + 1)) + done + + local pid="$(cat "$pid_file" 2>/dev/null | tr -d '[:space:]')" + if [ -z "$pid" ]; then + echo "Failed to start (no PID written). Check $err_win" + exit 1 + fi + + echo "Started (PID: $pid)" + echo "Logs: ${DATA_DIR}/logs/stdout.log" +} + +win_stop() { + local pid_file="$(win_pid_file)" + + if [ ! -f "$pid_file" ]; then + echo "Not running (no PID file)" + exit 0 + fi + + local pid=$(cat "$pid_file" 2>/dev/null) + rm -f "$pid_file" + + if [ -z "$pid" ]; then + echo "Stopped" + exit 0 + fi + + # taskkill /T also kills any child processes (e.g. spawned claude CLI). + taskkill //PID "$pid" //T //F >/dev/null 2>&1 && echo "Stopped (PID: $pid)" || echo "Process not running (cleaned up PID file)" +} + +win_restart() { win_stop; sleep 1; win_start; } + +win_status() { + local pid_file="$(win_pid_file)" + + if [ ! -f "$pid_file" ]; then + echo "Not running" + exit 0 + fi + + local pid=$(cat "$pid_file" 2>/dev/null | tr -d '[:space:]') + if [ -z "$pid" ]; then + echo "Not running (invalid PID file)" + exit 0 + fi + + if win_pid_alive "$pid"; then + echo "Running (PID: $pid)" + else + echo "Not running (stale PID file)" + fi +} + +win_logs() { + local log_dir="${DATA_DIR}/logs" + if [ -d "$log_dir" ]; then + for f in "${log_dir}"/stdout.log "${log_dir}"/stderr.log; do + if [ -f "$f" ]; then + echo "=== $(basename "$f") ===" + tail -50 "$f" + echo "" + fi + done + else + echo "No logs found" + fi +} + # ============================================================================= # Main dispatcher # ============================================================================= @@ -383,6 +516,20 @@ main() { local command="${1:-}" case "$OS_TYPE" in + MINGW*|MSYS*|CYGWIN*) + case "$command" in + start) win_start ;; + stop) win_stop ;; + restart) win_restart ;; + status) win_status ;; + logs) win_logs ;; + *) + echo "Usage: daemon.sh {start|stop|restart|status|logs}" + echo "Platform: Windows (Git Bash direct mode)" + exit 1 + ;; + esac + ;; Darwin) case "$command" in start) macos_start ;;