JJServiceManagerRelay/server/Program.cs

93 lines
3.5 KiB
C#

using System.Text.Json.Nodes;
using JJServiceManagerRelay;
var config = ServerConfig.FromEnvironment();
var log = new ServerLog(config.LogDir);
var db = new Db(config.ConnectionString, log);
var hub = new Hub(db, config, log);
log.Info($"JJServiceManagerRelay {hub.Version} 시작 (포트 {config.Port})");
using var startCts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
await db.InitializeAsync(startCts.Token);
await db.CloseDanglingConnectionsAsync();
// 티켓 서명 키 (처음 한 번 만들고 DB에 보관)
hub.Signer = new TicketSigner(await db.GetOrCreateSettingAsync("ticket_private_key", TicketSigner.NewPrivateKey));
// 첫 관리자: 사용자가 하나도 없을 때만 환경 변수로 만듦
if (await db.CountUsersAsync() == 0)
{
if (!string.IsNullOrWhiteSpace(config.AdminUser) && !string.IsNullOrEmpty(config.AdminPassword))
{
if (config.AdminPassword.Length < 8)
{
log.Error("JJSM_ADMIN_PASSWORD는 8자 이상이어야 합니다. 관리자를 만들지 않았습니다.");
}
else
{
await db.CreateUserAsync(config.AdminUser.Trim(), Secrets.HashPassword(config.AdminPassword), "admin", true, "첫 관리자 (환경 변수)");
await db.AuditAsync(null, "system", null, "", "userCreate", config.AdminUser.Trim(), "ok", "첫 관리자 (환경 변수)", "");
log.Info($"첫 관리자 계정을 만들었습니다: {config.AdminUser.Trim()} (첫 로그인 때 비밀번호를 바꿔야 합니다)");
}
}
else
{
log.Warn("사용자가 없습니다. JJSM_ADMIN_USER / JJSM_ADMIN_PASSWORD 환경 변수를 넣고 다시 시작하세요.");
}
}
var builder = WebApplication.CreateBuilder(new WebApplicationOptions { Args = args });
builder.Logging.ClearProviders();
builder.Logging.AddSimpleConsole(o => o.SingleLine = true);
builder.Logging.SetMinimumLevel(LogLevel.Warning);
builder.WebHost.UseUrls($"http://0.0.0.0:{config.Port}");
builder.WebHost.ConfigureKestrel(k => k.AddServerHeader = false);
var app = builder.Build();
app.UseWebSockets(new WebSocketOptions { KeepAliveInterval = TimeSpan.FromSeconds(30) });
var agentHandler = new AgentHandler(hub);
var managerHandler = new ManagerHandler(hub);
var relayHandler = new RelayHandler(hub);
app.MapGet("/", () => Results.Text("JJServiceManagerRelay\n", "text/plain; charset=utf-8"));
app.MapGet("/health", async () =>
{
bool dbOk = await db.PingAsync();
var o = new JsonObject
{
["status"] = dbOk ? "ok" : "db-error",
["version"] = hub.Version,
["db"] = dbOk,
["onlineDevices"] = hub.Agents.Count,
["onlineManagers"] = hub.Managers.Count
};
return Results.Text(o.ToJsonString(), "application/json; charset=utf-8", statusCode: dbOk ? 200 : 503);
});
app.Map("/ws/agent", async ctx =>
{
if (!ctx.WebSockets.IsWebSocketRequest) { ctx.Response.StatusCode = 400; return; }
await agentHandler.RunAsync(ctx);
});
app.Map("/ws/manager", async ctx =>
{
if (!ctx.WebSockets.IsWebSocketRequest) { ctx.Response.StatusCode = 400; return; }
await managerHandler.RunAsync(ctx);
});
app.Map("/relay/{sid}", async (HttpContext ctx, string sid) =>
{
if (!ctx.WebSockets.IsWebSocketRequest) { ctx.Response.StatusCode = 400; return; }
await relayHandler.RunAsync(ctx, sid);
});
var stop = app.Lifetime.ApplicationStopping;
_ = Task.Run(() => hub.HousekeepingLoopAsync(stop));
app.Lifetime.ApplicationStopping.Register(() => log.Info("서버 종료 중"));
await app.RunAsync();