feat: scaffold windows agent host

This commit is contained in:
sladro 2026-03-27 10:49:57 +08:00
parent 0bf6185f90
commit b825249a7e
7 changed files with 123 additions and 7 deletions

View File

@ -0,0 +1,20 @@
using System.IO;
using Microsoft.Extensions.Configuration;
namespace TermRemoteCtl.Agent;
internal static class AppPaths
{
public static string ResolveDataRoot(IConfiguration configuration)
{
var configuredPath = configuration["Agent:DataRoot"];
if (!string.IsNullOrWhiteSpace(configuredPath))
{
return configuredPath;
}
return Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
"TermRemoteCtl");
}
}

View File

@ -0,0 +1,16 @@
namespace TermRemoteCtl.Agent.Configuration;
public sealed class AgentOptions
{
public const string SectionName = "Agent";
public string DataRoot { get; set; } = string.Empty;
public string BindAddress { get; set; } = "0.0.0.0";
public int HttpsPort { get; set; }
public int WebSocketFrameFlushMilliseconds { get; set; }
public int RingBufferLineLimit { get; set; }
}

View File

@ -0,0 +1,30 @@
using Microsoft.Extensions.Options;
namespace TermRemoteCtl.Agent.Configuration;
public sealed class AgentOptionsValidator : IValidateOptions<AgentOptions>
{
public ValidateOptionsResult Validate(string? name, AgentOptions options)
{
var failures = new List<string>();
if (string.IsNullOrWhiteSpace(options.DataRoot))
{
failures.Add("Agent:DataRoot must be configured.");
}
if (options.HttpsPort is < 1 or > 65535)
{
failures.Add("Agent:HttpsPort must be between 1 and 65535.");
}
if (options.WebSocketFrameFlushMilliseconds <= 0)
{
failures.Add("Agent:WebSocketFrameFlushMilliseconds must be positive.");
}
return failures.Count > 0
? ValidateOptionsResult.Fail(failures)
: ValidateOptionsResult.Success;
}
}

View File

@ -1,4 +1,26 @@
using Microsoft.Extensions.Options;
using TermRemoteCtl.Agent;
using TermRemoteCtl.Agent.Configuration;
var builder = WebApplication.CreateBuilder(args);
var agentSection = builder.Configuration.GetSection(AgentOptions.SectionName);
var bindAddress = agentSection[nameof(AgentOptions.BindAddress)] ?? "0.0.0.0";
var httpsPort = agentSection.GetValue<int?>(nameof(AgentOptions.HttpsPort)) ?? 9443;
builder.WebHost.UseUrls($"http://{bindAddress}:{httpsPort}");
builder.Services.AddSingleton<IValidateOptions<AgentOptions>, AgentOptionsValidator>();
builder.Services
.AddOptions<AgentOptions>()
.Bind(agentSection)
.PostConfigure(options => options.DataRoot = AppPaths.ResolveDataRoot(builder.Configuration))
.ValidateOnStart();
var app = builder.Build();
var agentOptions = app.Services.GetRequiredService<IOptions<AgentOptions>>().Value;
Directory.CreateDirectory(agentOptions.DataRoot);
app.MapGet("/health", () => Results.Json(new { status = "ok" }));
app.Run();

View File

@ -1,9 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"Agent": {
"DataRoot": "C:\\ProgramData\\TermRemoteCtl",
"BindAddress": "0.0.0.0",
"HttpsPort": 9443,
"WebSocketFrameFlushMilliseconds": 33,
"RingBufferLineLimit": 4000
}
}

View File

@ -0,0 +1,24 @@
using TermRemoteCtl.Agent.Configuration;
namespace TermRemoteCtl.Agent.Tests.Configuration;
public class AgentOptionsValidatorTests
{
[Fact]
public void Validate_Fails_When_DataRoot_Is_Missing()
{
var options = new AgentOptions
{
DataRoot = "",
BindAddress = "0.0.0.0",
HttpsPort = 9443
};
var validator = new AgentOptionsValidator();
var result = validator.Validate("Agent", options);
Assert.True(result.Failed);
Assert.Contains(result.Failures, failure => failure.Contains("DataRoot"));
}
}

View File

@ -20,4 +20,8 @@
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\\..\\src\\TermRemoteCtl.Agent\\TermRemoteCtl.Agent.csproj" />
</ItemGroup>
</Project>