Standalone WPF app (ClaudeDo.Installer) that handles full installation and ongoing configuration of ClaudeDo. Two modes: wizard for first run, tabbed settings panel for subsequent launches. Page-based extensibility via IInstallerPage interface — adding new config sections requires only one new class. Install pipeline: dotnet publish, deploy binaries, write configs, init DB (via SchemaInitializer from ClaudeDo.Data), register Windows Service, create shortcuts. Dark theme matching the Avalonia app (forest teal accent). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
69 lines
2.8 KiB
C#
69 lines
2.8 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);
|
|
|
|
// Create service
|
|
var startType = ctx.AutoStart ? "auto" : "demand";
|
|
var createArgs = $"create {ServiceName} binPath= \"{workerExe}\" start= {startType}";
|
|
|
|
if (ctx.ServiceAccount == "CurrentUser")
|
|
{
|
|
var username = Environment.UserName;
|
|
createArgs += $" obj= \".\\{username}\"";
|
|
}
|
|
|
|
progress.Report("Creating service...");
|
|
var (exitCode, output) = await RunSc(createArgs, ctx, progress, ct);
|
|
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})");
|
|
|
|
// Start service if auto-start
|
|
if (ctx.AutoStart)
|
|
{
|
|
progress.Report("Starting service...");
|
|
var (startExit, _) = await RunSc($"start {ServiceName}", ctx, progress, ct);
|
|
if (startExit != 0)
|
|
progress.Report("Warning: service created but failed to start. You may need to start it manually.");
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|