feat(worker): run worker as per-user logon task instead of Windows service
A LocalSystem Windows service can't see the logged-in user's Claude CLI authentication, so the worker now runs as the current user via a hidden per-user logon Scheduled Task with restart-on-failure. - Worker is WinExe (no console window) with a Serilog rolling file sink and a single-instance mutex so the logon task, app ensure-running, and Restart button can't fight over the SignalR port. - Installer replaces the service steps (register/start/stop) with autostart task steps, migrates the legacy ClaudeDoWorker service away on update, and removes the task on uninstall. ServicePage drops the service-account UI. - UI gains a WorkerLocator; the app ensures the worker is running at startup and the Restart button kills+relaunches this install's worker process. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
48
src/ClaudeDo.Installer/Steps/StopWorkerStep.cs
Normal file
48
src/ClaudeDo.Installer/Steps/StopWorkerStep.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using ClaudeDo.Installer.Core;
|
||||
|
||||
namespace ClaudeDo.Installer.Steps;
|
||||
|
||||
public sealed class StopWorkerStep : IInstallStep
|
||||
{
|
||||
public const string TaskName = "ClaudeDoWorker";
|
||||
public const string ProcessName = "ClaudeDo.Worker";
|
||||
|
||||
public string Name => "Stop Worker";
|
||||
|
||||
public async Task<StepResult> ExecuteAsync(InstallContext ctx, IProgress<string> progress, CancellationToken ct)
|
||||
{
|
||||
progress.Report("Stopping worker task (if running)...");
|
||||
await ProcessRunner.RunAsync("schtasks.exe", $"/End /TN \"{TaskName}\"", null, progress, ct);
|
||||
|
||||
progress.Report("Stopping worker process (if running)...");
|
||||
var installDir = ctx.InstallDirectory;
|
||||
foreach (var p in Process.GetProcessesByName(ProcessName))
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = p.MainModule?.FileName;
|
||||
if (path is not null && !IsUnder(path, installDir)) continue;
|
||||
p.Kill(entireProcessTree: true);
|
||||
p.WaitForExit(10000);
|
||||
}
|
||||
catch { /* process may have exited or be inaccessible */ }
|
||||
finally { p.Dispose(); }
|
||||
}
|
||||
await Task.CompletedTask;
|
||||
return StepResult.Ok();
|
||||
}
|
||||
|
||||
private static bool IsUnder(string filePath, string dir)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dir)) return true; // can't scope — be permissive
|
||||
var full = Path.GetFullPath(filePath);
|
||||
var root = Path.GetFullPath(dir).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||
return full.StartsWith(root, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user