diff --git a/src/ClaudeDo.Installer/Steps/StartServiceStep.cs b/src/ClaudeDo.Installer/Steps/StartServiceStep.cs new file mode 100644 index 0000000..270c179 --- /dev/null +++ b/src/ClaudeDo.Installer/Steps/StartServiceStep.cs @@ -0,0 +1,27 @@ +using ClaudeDo.Installer.Core; + +namespace ClaudeDo.Installer.Steps; + +public sealed class StartServiceStep : IInstallStep +{ + private const string ServiceName = StopServiceStep.ServiceName; + + public string Name => "Start Worker Service"; + + public async Task ExecuteAsync(InstallContext ctx, IProgress progress, CancellationToken ct) + { + progress.Report($"Starting {ServiceName}..."); + + var (exit, output) = await ProcessRunner.RunAsync("sc.exe", $"start {ServiceName}", null, progress, ct); + if (exit == 0) return StepResult.Ok(); + + // Exit 1056 = already running — that's fine too. + if (output.Contains("1056", StringComparison.OrdinalIgnoreCase)) + { + progress.Report("Service was already running."); + return StepResult.Ok(); + } + + return StepResult.Fail($"sc.exe start failed with exit code {exit}"); + } +} diff --git a/src/ClaudeDo.Installer/Steps/StopServiceStep.cs b/src/ClaudeDo.Installer/Steps/StopServiceStep.cs new file mode 100644 index 0000000..be3c271 --- /dev/null +++ b/src/ClaudeDo.Installer/Steps/StopServiceStep.cs @@ -0,0 +1,48 @@ +using ClaudeDo.Installer.Core; + +namespace ClaudeDo.Installer.Steps; + +public sealed class StopServiceStep : IInstallStep +{ + public const string ServiceName = "ClaudeDoWorker"; + + public string Name => "Stop Worker Service"; + + public async Task ExecuteAsync(InstallContext ctx, IProgress progress, CancellationToken ct) + { + progress.Report($"Stopping {ServiceName} (if running)..."); + + // sc.exe query -> returns non-zero if the service does not exist; that's fine. + var (queryExit, queryOutput) = await ProcessRunner.RunAsync("sc.exe", $"query {ServiceName}", null, progress, ct); + if (queryExit != 0) + { + progress.Report("Service is not registered — nothing to stop."); + return StepResult.Ok(); + } + + if (queryOutput.Contains("STOPPED", StringComparison.OrdinalIgnoreCase)) + { + progress.Report("Service is already stopped."); + return StepResult.Ok(); + } + + var (stopExit, _) = await ProcessRunner.RunAsync("sc.exe", $"stop {ServiceName}", null, progress, ct); + if (stopExit != 0) + return StepResult.Fail($"sc.exe stop failed with exit code {stopExit}"); + + // Poll until stopped or timeout (up to 30s). + for (var i = 0; i < 30; i++) + { + ct.ThrowIfCancellationRequested(); + await Task.Delay(1000, ct); + var (e, o) = await ProcessRunner.RunAsync("sc.exe", $"query {ServiceName}", null, progress, ct); + if (e != 0 || o.Contains("STOPPED", StringComparison.OrdinalIgnoreCase)) + { + progress.Report("Service stopped."); + return StepResult.Ok(); + } + } + + return StepResult.Fail("Service did not stop within 30 seconds."); + } +}