- 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
89 lines
4.1 KiB
C#
89 lines
4.1 KiB
C#
using System.IO;
|
|
using ClaudeDo.Installer.Core;
|
|
|
|
namespace ClaudeDo.Installer.Steps;
|
|
|
|
public sealed class RegisterServiceStep : IInstallStep
|
|
{
|
|
private const string ServiceName = "ClaudeDoWorker";
|
|
|
|
public string Name => "Register Windows Service";
|
|
|
|
public async Task<StepResult> ExecuteAsync(InstallContext ctx, IProgress<string> progress, CancellationToken ct)
|
|
{
|
|
var workerExe = Path.Combine(ctx.InstallDirectory, "worker", "ClaudeDo.Worker.exe");
|
|
if (!File.Exists(workerExe))
|
|
return StepResult.Fail($"Worker executable not found: {workerExe}");
|
|
|
|
// Stop existing service (ignore errors — may not exist)
|
|
progress.Report("Stopping existing service (if any)...");
|
|
await RunSc($"stop {ServiceName}", ctx, progress, ct, ignoreErrors: true);
|
|
|
|
// Delete existing service (ignore errors)
|
|
progress.Report("Removing existing service registration (if any)...");
|
|
await RunSc($"delete {ServiceName}", ctx, progress, ct, ignoreErrors: true);
|
|
|
|
// Wait for the service to actually disappear from SCM. `sc delete` returns
|
|
// immediately but the service stays "marked for deletion" until every open
|
|
// handle (services.msc, Task Manager, a prior sc query process) is closed.
|
|
// Poll up to 30s — then fail with actionable guidance if it's still there.
|
|
progress.Report("Waiting for prior service registration to clear...");
|
|
for (var i = 0; i < 30; i++)
|
|
{
|
|
ct.ThrowIfCancellationRequested();
|
|
var (queryExit, _) = await RunSc($"query {ServiceName}", ctx, progress, ct, ignoreErrors: true);
|
|
if (queryExit != 0) break; // service no longer registered — good
|
|
if (i == 29)
|
|
return StepResult.Fail(
|
|
$"Service '{ServiceName}' is marked for deletion but hasn't cleared after 30s. " +
|
|
"Close any open Services console (services.msc), Task Manager Services tab, or " +
|
|
"Event Viewer showing the service, then retry. A reboot will also clear it.");
|
|
await Task.Delay(1000, ct);
|
|
}
|
|
|
|
// Create service
|
|
var startType = ctx.AutoStart ? "auto" : "demand";
|
|
|
|
if (ctx.ServiceAccount == "CurrentUser")
|
|
return StepResult.Fail(
|
|
"Service cannot run as Current User without a password. " +
|
|
"Select 'Local System' or extend ServicePage to capture a password.");
|
|
|
|
var objArg = ctx.ServiceAccount switch
|
|
{
|
|
"LocalSystem" => " obj= LocalSystem",
|
|
"NetworkService" => " obj= \"NT AUTHORITY\\NetworkService\"",
|
|
"LocalService" => " obj= \"NT AUTHORITY\\LocalService\"",
|
|
_ => "",
|
|
};
|
|
var createArgs = $"create {ServiceName} binPath= \"{workerExe}\" start= {startType}{objArg}";
|
|
|
|
progress.Report("Creating service...");
|
|
var (exitCode, output) = await RunSc(createArgs, ctx, progress, ct);
|
|
if (exitCode == 1072)
|
|
return StepResult.Fail(
|
|
$"Service '{ServiceName}' is still marked for deletion. " +
|
|
"Close services.msc / Task Manager / Event Viewer and retry, or reboot.");
|
|
if (exitCode != 0)
|
|
return StepResult.Fail($"sc.exe create failed (exit {exitCode}): {output}");
|
|
|
|
// Configure restart policy
|
|
var delay = ctx.RestartDelayMs;
|
|
var failureArgs = $"failure {ServiceName} reset= 86400 actions= restart/{delay}/restart/{delay}/restart/{delay}";
|
|
progress.Report("Configuring restart policy...");
|
|
var (failExit, failOutput) = await RunSc(failureArgs, ctx, progress, ct);
|
|
if (failExit != 0)
|
|
progress.Report($"Warning: failed to set restart policy (exit {failExit})");
|
|
|
|
return StepResult.Ok();
|
|
}
|
|
|
|
private static async Task<(int ExitCode, string Output)> RunSc(
|
|
string arguments, InstallContext ctx, IProgress<string> progress,
|
|
CancellationToken ct, bool ignoreErrors = false)
|
|
{
|
|
var result = await ProcessRunner.RunAsync("sc.exe", arguments, null, progress, ct);
|
|
return result;
|
|
}
|
|
}
|