using ShrinkNetwork; namespace ShrinkNetwork.ServerHost.Framework; public sealed class ServerHostOptions { public int Port { get; set; } = 17777; public string UnityAssetsPath { get; set; } = ResolveDefaultUnityAssetsPath(); public string ScanOutputDirectory { get; set; } = ResolveDefaultScanOutputDirectory(); public string? SharedAuthToken { get; set; } public bool AllowAnonymousWhenAuthTokenMissing { get; set; } public bool EnableSessionTokens { get; set; } = true; public int SessionTokenTtlSeconds { get; set; } = 1800; public int SessionTokenRefreshWindowSeconds { get; set; } = 300; public bool DisconnectOnInvalidSessionToken { get; set; } = true; public bool EnableUnityCodeScan { get; set; } = true; public int MinProtocolVersion { get; set; } = ShrinkNetworkProtocol.CurrentProtocolVersion; public int MaxProtocolVersion { get; set; } = ShrinkNetworkProtocol.CurrentProtocolVersion; public int MinSchemaVersion { get; set; } = ShrinkNetworkProtocol.CurrentSchemaVersion; public int MaxSchemaVersion { get; set; } = ShrinkNetworkProtocol.CurrentSchemaVersion; public bool DisconnectOnProtocolViolation { get; set; } = true; public int DiagnosticsLogIntervalSeconds { get; set; } = 60; public bool EnableTcpTls { get; set; } public string? TcpTlsTargetHost { get; set; } public string? TcpTlsCertificatePath { get; set; } public string? TcpTlsCertificatePassword { get; set; } public bool TcpTlsCheckCertificateRevocation { get; set; } public void ApplyEnvironmentOverrides() { SharedAuthToken = ReadOptionalStringFromEnvironment("SHRINK_SERVER_AUTH_TOKEN", SharedAuthToken); AllowAnonymousWhenAuthTokenMissing = ReadBoolFromEnvironment( "SHRINK_SERVER_ALLOW_ANONYMOUS_WHEN_AUTH_TOKEN_MISSING", AllowAnonymousWhenAuthTokenMissing); EnableSessionTokens = ReadBoolFromEnvironment("SHRINK_SERVER_ENABLE_SESSION_TOKENS", EnableSessionTokens); SessionTokenTtlSeconds = ReadIntFromEnvironment("SHRINK_SERVER_SESSION_TOKEN_TTL_SECONDS", SessionTokenTtlSeconds, 60); SessionTokenRefreshWindowSeconds = ReadIntFromEnvironment( "SHRINK_SERVER_SESSION_TOKEN_REFRESH_WINDOW_SECONDS", SessionTokenRefreshWindowSeconds, 0); DisconnectOnInvalidSessionToken = ReadBoolFromEnvironment( "SHRINK_SERVER_DISCONNECT_ON_INVALID_SESSION_TOKEN", DisconnectOnInvalidSessionToken); EnableUnityCodeScan = ReadBoolFromEnvironment("SHRINK_SERVER_ENABLE_UNITY_CODE_SCAN", EnableUnityCodeScan); MinProtocolVersion = ReadIntFromEnvironment("SHRINK_SERVER_MIN_PROTOCOL_VERSION", MinProtocolVersion, 1); MaxProtocolVersion = ReadIntFromEnvironment("SHRINK_SERVER_MAX_PROTOCOL_VERSION", MaxProtocolVersion, 1); MinSchemaVersion = ReadIntFromEnvironment("SHRINK_SERVER_MIN_SCHEMA_VERSION", MinSchemaVersion, 1); MaxSchemaVersion = ReadIntFromEnvironment("SHRINK_SERVER_MAX_SCHEMA_VERSION", MaxSchemaVersion, 1); DisconnectOnProtocolViolation = ReadBoolFromEnvironment( "SHRINK_SERVER_DISCONNECT_ON_PROTOCOL_VIOLATION", DisconnectOnProtocolViolation); DiagnosticsLogIntervalSeconds = ReadIntFromEnvironment( "SHRINK_SERVER_DIAGNOSTICS_LOG_INTERVAL_SECONDS", DiagnosticsLogIntervalSeconds, 0); EnableTcpTls = ReadBoolFromEnvironment("SHRINK_SERVER_ENABLE_TCP_TLS", EnableTcpTls); TcpTlsTargetHost = ReadOptionalStringFromEnvironment("SHRINK_SERVER_TLS_TARGET_HOST", TcpTlsTargetHost); TcpTlsCertificatePath = ReadOptionalStringFromEnvironment("SHRINK_SERVER_TLS_CERT_PATH", TcpTlsCertificatePath); TcpTlsCertificatePassword = ReadOptionalStringFromEnvironment( "SHRINK_SERVER_TLS_CERT_PASSWORD", TcpTlsCertificatePassword); TcpTlsCheckCertificateRevocation = ReadBoolFromEnvironment( "SHRINK_SERVER_TLS_CHECK_CERTIFICATE_REVOCATION", TcpTlsCheckCertificateRevocation); } public static string ResolveHostProjectDirectory() { var resolved = TryFindProjectDirectory(AppContext.BaseDirectory); if (!string.IsNullOrWhiteSpace(resolved)) return resolved; resolved = TryFindProjectDirectory(Environment.CurrentDirectory); if (!string.IsNullOrWhiteSpace(resolved)) return resolved; return Environment.CurrentDirectory; } public static string ResolveDefaultConfigFilePath() { return Path.Combine(ResolveHostProjectDirectory(), ServerHostProperties.DefaultFileName); } public static string ResolveDefaultUnityAssetsPath() { return Path.GetFullPath(Path.Combine(ResolveHostProjectDirectory(), "..", "..", "Assets")); } public static string ResolveDefaultScanOutputDirectory() { return Path.GetFullPath(Path.Combine(ResolveHostProjectDirectory(), "Generated")); } private static string? TryFindProjectDirectory(string startDirectory) { if (string.IsNullOrWhiteSpace(startDirectory) || !Directory.Exists(startDirectory)) return null; var directory = new DirectoryInfo(Path.GetFullPath(startDirectory)); for (var depth = 0; directory != null && depth < 8; depth++, directory = directory.Parent) { var candidate = Path.Combine(directory.FullName, "ShrinkNetwork.ServerHost.csproj"); if (File.Exists(candidate)) return directory.FullName; } return null; } private static int ReadIntFromEnvironment(string name, int fallback, int minValue) { var raw = Environment.GetEnvironmentVariable(name); if (!int.TryParse(raw, out var value)) return fallback; return Math.Max(minValue, value); } private static bool ReadBoolFromEnvironment(string name, bool fallback) { var raw = Environment.GetEnvironmentVariable(name); if (string.IsNullOrWhiteSpace(raw)) return fallback; if (bool.TryParse(raw, out var parsed)) return parsed; return raw switch { "1" => true, "0" => false, _ => fallback }; } private static string? ReadOptionalStringFromEnvironment(string name, string? fallback) { var raw = Environment.GetEnvironmentVariable(name); if (raw == null) return fallback; return string.IsNullOrWhiteSpace(raw) ? string.Empty : raw.Trim(); } }