feat(host): wire UseWindowsService so sc.exe-installed service runs cleanly
Some checks failed
v2-ci / build (push) Failing after 45s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Some checks failed
v2-ci / build (push) Failing after 45s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
The v2 plan's blessed install path (scripts/install/Install-Services.ps1)
registers the host via `sc.exe create binPath=...OtOpcUa.Host.exe`, but the
binary never called `UseWindowsService`. Without it, the Service Control
Manager waits ~30s for the process to call SetServiceStatus(Running) and
then kills it — the install script's design was incomplete.
Two changes:
- Host.csproj: drop the `IsOSPlatform('Windows')` condition on the
Microsoft.Extensions.Hosting.WindowsServices package reference so the
package is always available. The runtime helper used by
UseWindowsService gates on WindowsServiceHelpers.IsWindowsService()
internally, so it's a no-op when running as a console app or under
Linux/macOS — the binary stays cross-platform-buildable.
- Program.cs: call builder.Host.UseWindowsService(options =>
options.ServiceName = "OtOpcUaHost") immediately after CreateBuilder.
When the host is launched by SCM, WindowsServiceLifetime takes over
the IHostLifetime slot and reports START/STOP correctly. When launched
by `dotnet run` or `OtOpcUa.Host.exe` from a console, it's a no-op.
Verified end-to-end on wonder-app-vd03.zmr.zimmer.com: `sc.exe create`
followed by `sc.exe start OtOpcUaHost` transitions from START_PENDING to
RUNNING; /login + /health/ready + /health/active all return 200; service
survives SSH session close and auto-starts on boot per the AUTO_START
flag set by the installer script.
This commit is contained in:
@@ -33,6 +33,12 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
// regardless of ASPNETCORE_ENVIRONMENT.
|
||||
builder.WebHost.UseStaticWebAssets();
|
||||
|
||||
// Windows Service support: when the EXE is started by Service Control Manager (sc.exe),
|
||||
// the host needs to call SetServiceStatus to keep the SCM happy. UseWindowsService()
|
||||
// installs the WindowsServiceLifetime IFF WindowsServiceHelpers.IsWindowsService() is
|
||||
// true at runtime — so it's safely a no-op when running as a console app or on Linux.
|
||||
builder.Host.UseWindowsService(options => options.ServiceName = "OtOpcUaHost");
|
||||
|
||||
// Per-role appsettings overlay: appsettings.{role}.json (single role) or appsettings.admin-driver.json
|
||||
// (both). Optional — base appsettings.json carries enough to boot if these don't exist.
|
||||
var roleSuffix = roles.Length == 0 ? null : string.Join('-', roles.OrderBy(r => r, StringComparer.Ordinal));
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Akka.Hosting"/>
|
||||
<PackageReference Include="Serilog.AspNetCore"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Condition="$([MSBuild]::IsOSPlatform('Windows'))"/>
|
||||
<!-- Always referenced (drops earlier IsOSPlatform condition) so Program.cs can
|
||||
call builder.Host.UseWindowsService() unconditionally; the runtime helper
|
||||
only activates when actually running as a Windows Service, so this is a
|
||||
no-op on macOS/Linux. -->
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
Reference in New Issue
Block a user