- InitDatabaseStep: create DbPath parent directory so custom paths work - RegisterServiceStep: pass obj= argument so ServiceAccount is honoured - StartServiceStep: poll for RUNNING state so downstream steps don't race
39 lines
1.6 KiB
C#
39 lines
1.6 KiB
C#
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<StepResult> ExecuteAsync(InstallContext ctx, IProgress<string> progress, CancellationToken ct)
|
|
{
|
|
progress.Report($"Starting {ServiceName}...");
|
|
|
|
var (exit, _) = await ProcessRunner.RunAsync("sc.exe", $"start {ServiceName}", null, progress, ct);
|
|
// 1056 = ERROR_SERVICE_ALREADY_RUNNING — fine, fall through to the readiness poll.
|
|
if (exit != 0 && exit != 1056)
|
|
return StepResult.Fail($"sc.exe start failed with exit code {exit}");
|
|
|
|
if (exit == 1056)
|
|
progress.Report("Service was already running.");
|
|
|
|
// sc.exe start returns as soon as SCM accepts the command. Poll until the
|
|
// service actually reports RUNNING so downstream steps and SignalR clients
|
|
// don't race the worker's startup.
|
|
progress.Report("Waiting for service to reach RUNNING state...");
|
|
for (var i = 0; i < 30; i++)
|
|
{
|
|
ct.ThrowIfCancellationRequested();
|
|
var (q, output) = await ProcessRunner.RunAsync("sc.exe", $"query {ServiceName}", null, progress, ct);
|
|
if (q == 0 && output.Contains("RUNNING", StringComparison.OrdinalIgnoreCase))
|
|
return StepResult.Ok();
|
|
await Task.Delay(1000, ct);
|
|
}
|
|
|
|
return StepResult.Fail("Service did not reach RUNNING state within 30 seconds.");
|
|
}
|
|
}
|