| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- using System;
- using System.Diagnostics;
- using System.IO;
- using System.Net.Http;
- using System.Threading;
- using IvfTl.Watchdog.Core;
- namespace IvfTl.Watchdog
- {
- /// <summary>
- /// control 崩溃看门狗(D3-05)。常驻探活 control /ping,崩溃自动重拉(读 DPAPI 缓存凭据)。
- /// 不依赖 operate 是否在跑。可 --pause/--resume(维护让路)、--stop(停这次)、--install/--uninstall(开机自启)。
- /// </summary>
- public static class Program
- {
- private const string MutexName = @"Global\ivf_tl_watchdog_singleton";
- private const string StopEventName = @"Global\ivf_tl_watchdog_stop";
- private const string RunKey = @"HKCU\Software\Microsoft\Windows\CurrentVersion\Run";
- private const string RunValueName = "ivf_tl_watchdog";
- private const int RelaunchCooldownSec = 30; // 两次重拉最小间隔(防崩溃风暴)
- private const int MaxConsecutiveFailures = 5; // 连续失败超此数 → 拉长冷却 + 告警
- private const int LongCooldownSec = 300;
- private static readonly HttpClient _http = new HttpClient { Timeout = TimeSpan.FromSeconds(2) };
- [STAThread]
- public static int Main(string[] args)
- {
- var a = WatchdogArgs.Parse(args);
- try
- {
- switch (a.Command)
- {
- case WatchdogCommand.Install: return DoInstall(a);
- case WatchdogCommand.Uninstall: return DoUninstall();
- case WatchdogCommand.Pause: WatchdogPaths.SetPaused(true); Log("已暂停看门狗(只探活不重拉);--resume 恢复"); return 0;
- case WatchdogCommand.Resume: WatchdogPaths.SetPaused(false); Log("已恢复看门狗守护"); return 0;
- case WatchdogCommand.Stop: SignalStop(); Log("已向常驻看门狗发送停止信号"); return 0;
- default: return RunLoop(a);
- }
- }
- catch (Exception ex)
- {
- Log("看门狗致命异常:" + ex);
- return 1;
- }
- }
- /// <summary>常驻探活循环。</summary>
- private static int RunLoop(WatchdogArgs a)
- {
- bool isNew;
- using (var mutex = new Mutex(true, MutexName, out isNew))
- {
- if (!isNew) { Log("已有看门狗实例在运行,本进程退出"); return 0; }
- using (var stopEvent = new EventWaitHandle(false, EventResetMode.ManualReset, StopEventName))
- {
- Log($"看门狗启动 port={a.Port} interval={a.IntervalSec}s controlExe={ResolveControlExe()}");
- int intervalMs = Math.Max(1, a.IntervalSec) * 1000;
- DateTime lastRelaunchUtc = DateTime.MinValue;
- int consecutiveFailures = 0;
- bool? lastAlive = null; // 状态变化才记日志,避免刷屏
- while (true)
- {
- // 先判停止信号(--stop / --uninstall 触发):立刻优雅退出。
- if (stopEvent.WaitOne(0)) { Log("收到停止信号,看门狗退出"); return 0; }
- bool alive = IsControlAlive(a.Port);
- if (alive != lastAlive) { Log(alive ? "control 存活(探活正常)" : "control 探活失败(不在)"); lastAlive = alive; }
- if (alive)
- {
- consecutiveFailures = 0;
- }
- else
- {
- bool paused = WatchdogPaths.IsPaused();
- bool stopped = WatchdogPaths.IsDeliberatelyStopped();
- int cooldown = consecutiveFailures >= MaxConsecutiveFailures ? LongCooldownSec : RelaunchCooldownSec;
- bool cooldownActive = (DateTime.UtcNow - lastRelaunchUtc).TotalSeconds < cooldown;
- if (RelaunchDecision.ShouldRelaunch(alive, paused, stopped, cooldownActive))
- {
- var creds = CredentialStore.Load();
- if (creds == null)
- {
- Log($"control 不在,但无可用缓存凭据(creds.dat 存在={File.Exists(WatchdogPaths.CredsFile)});等 operate 首次拉起 control 后再守护");
- }
- else
- {
- lastRelaunchUtc = DateTime.UtcNow;
- consecutiveFailures++;
- bool ok = Relaunch(creds, a.Port);
- if (ok && consecutiveFailures >= MaxConsecutiveFailures)
- Log($"⚠ control 已连续 {consecutiveFailures} 次重拉仍未稳定,冷却拉长至 {LongCooldownSec}s,请人工检查");
- }
- }
- else if (paused) Log("control 不在,但看门狗处于暂停(--resume 恢复),不重拉");
- else if (stopped) Log("control 不在,但为故意停机(受护栏 /shutdown),不重拉");
- // cooldown 中静默等待,不刷屏
- }
- // 等一个周期;期间若收到停止信号则立刻醒来退出。
- if (stopEvent.WaitOne(intervalMs)) { Log("收到停止信号,看门狗退出"); return 0; }
- }
- }
- }
- }
- /// <summary>探活:control /ping 是否可达。</summary>
- private static bool IsControlAlive(int port)
- {
- try
- {
- var resp = _http.GetAsync($"http://127.0.0.1:{port}/ping").GetAwaiter().GetResult();
- return resp.IsSuccessStatusCode;
- }
- catch { return false; }
- }
- /// <summary>读缓存凭据,提权拉起 control(与 operate ControlProcessLauncher 同形态)。</summary>
- private static bool Relaunch(Credentials c, int port)
- {
- string exe = ResolveControlExe();
- if (!File.Exists(exe)) { Log("找不到 control.exe:" + exe); return false; }
- try
- {
- var psi = new ProcessStartInfo
- {
- FileName = exe,
- Arguments = $"--account={c.Account} --password={c.Password} --cacheDisk={c.CacheDisk} --port={port}",
- UseShellExecute = true, // requireAdministrator 子进程需 ShellExecute(看门狗已是管理员,不弹 UAC)
- WindowStyle = ProcessWindowStyle.Hidden
- };
- Process.Start(psi);
- Log($"control 不在 → 已重拉 control.exe(port={port})");
- return true;
- }
- catch (Exception ex) { Log("重拉 control 失败:" + ex.Message); return false; }
- }
- /// <summary>control 可执行路径:默认与看门狗同目录(部署在同一 control\ 子目录)。</summary>
- private static string ResolveControlExe() =>
- Path.Combine(AppContext.BaseDirectory, "ivf_tl_ControlHost.exe");
- // ── 控制面 ──────────────────────────────────────────────
- private static int DoInstall(WatchdogArgs a)
- {
- string exe = Process.GetCurrentProcess().MainModule.FileName;
- string cmd = $"\"{exe}\" --port={a.Port} --interval={a.IntervalSec}";
- int rc = RunReg($"add \"{RunKey}\" /v {RunValueName} /t REG_SZ /d \"{cmd}\" /f");
- Log(rc == 0 ? $"已写开机自启:{cmd}" : "写开机自启失败 rc=" + rc);
- return rc;
- }
- private static int DoUninstall()
- {
- // 先停常驻实例,再删自启项 = 一条命令彻底卸载、无残留。
- SignalStop();
- int rc = RunReg($"delete \"{RunKey}\" /v {RunValueName} /f");
- Log(rc == 0 ? "已删除开机自启项,看门狗已卸载" : "删除开机自启项(可能本就不存在) rc=" + rc);
- return 0;
- }
- /// <summary>给常驻看门狗发停止信号(打开已存在的命名事件并置位);无常驻实例则静默。</summary>
- private static void SignalStop()
- {
- try
- {
- using (var ev = EventWaitHandle.OpenExisting(StopEventName)) ev.Set();
- }
- catch (WaitHandleCannotBeOpenedException) { Log("无常驻看门狗在运行(无需停止)"); }
- catch (Exception ex) { Log("发送停止信号异常:" + ex.Message); }
- }
- private static int RunReg(string arguments)
- {
- try
- {
- var psi = new ProcessStartInfo("reg.exe", arguments) { UseShellExecute = false, CreateNoWindow = true };
- var p = Process.Start(psi);
- p.WaitForExit();
- return p.ExitCode;
- }
- catch (Exception ex) { Log("reg.exe 执行异常:" + ex.Message); return -1; }
- }
- // ── 日志 ────────────────────────────────────────────────
- private static readonly object _logLock = new object();
- private static void Log(string msg)
- {
- string line = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} {msg}";
- try
- {
- lock (_logLock)
- {
- WatchdogPaths.EnsureDataDir();
- File.AppendAllText(Path.Combine(WatchdogPaths.DataDir, "watchdog.log"), line + Environment.NewLine);
- }
- }
- catch { }
- try { Console.WriteLine(line); } catch { }
- }
- }
- }
|