using System;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Threading;
using IvfTl.Watchdog.Core;
namespace IvfTl.Watchdog
{
///
/// control 崩溃看门狗(D3-05)。常驻探活 control /ping,崩溃自动重拉(读 DPAPI 缓存凭据)。
/// 不依赖 operate 是否在跑。可 --pause/--resume(维护让路)、--stop(停这次)、--install/--uninstall(开机自启)。
///
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;
}
}
/// 常驻探活循环。
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; }
}
}
}
}
/// 探活:control /ping 是否可达。
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; }
}
/// 读缓存凭据,提权拉起 control(与 operate ControlProcessLauncher 同形态)。
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; }
}
/// control 可执行路径:默认与看门狗同目录(部署在同一 control\ 子目录)。
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;
}
/// 给常驻看门狗发停止信号(打开已存在的命名事件并置位);无常驻实例则静默。
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 { }
}
}
}