using System.Text.Json; namespace ScadaLink.CLI; /// /// Resolved CLI configuration combining config file values, environment variable overrides, and per-invocation credentials. /// public class CliConfig { /// Base URL of the ScadaLink Management API (e.g. http://localhost:9000). public string? ManagementUrl { get; set; } /// Default output format for CLI commands; defaults to "json". public string DefaultFormat { get; set; } = "json"; /// /// LDAP username from the SCADALINK_USERNAME environment variable, if set. /// Credentials are intentionally only sourced from environment variables (or the /// command line) — never from the config file — so they are not persisted to disk. /// public string? Username { get; set; } /// /// LDAP password from the SCADALINK_PASSWORD environment variable, if set. /// Provides a safer alternative to --password, which leaks into process /// listings and shell history. /// public string? Password { get; set; } /// /// Loads CLI configuration by merging the config file, environment variables, and credential env vars. /// /// A populated instance. public static CliConfig Load() { var config = new CliConfig(); // Load from config file var configPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".scadalink", "config.json"); if (File.Exists(configPath)) { // CLI-021: a malformed (`JsonException`), unreadable // (`UnauthorizedAccessException`), or otherwise faulted // (`IOException`) config file must not crash the CLI before any // command runs — even a command that supplies everything via // --url/--username/--password/--format on the command line still // calls Load() and would otherwise inherit the fault. Warn once on // stderr and fall through to the env-var + command-line precedence // chain with default settings. try { var json = File.ReadAllText(configPath); var fileConfig = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); if (fileConfig != null) { if (!string.IsNullOrEmpty(fileConfig.ManagementUrl)) config.ManagementUrl = fileConfig.ManagementUrl; if (!string.IsNullOrEmpty(fileConfig.DefaultFormat)) config.DefaultFormat = fileConfig.DefaultFormat; } } catch (Exception ex) when (ex is JsonException || ex is IOException || ex is UnauthorizedAccessException) { Console.Error.WriteLine( $"warning: ignoring malformed or unreadable {configPath}: {ex.Message}"); } } // Override from environment variables var envUrl = Environment.GetEnvironmentVariable("SCADALINK_MANAGEMENT_URL"); if (!string.IsNullOrEmpty(envUrl)) config.ManagementUrl = envUrl; var envFormat = Environment.GetEnvironmentVariable("SCADALINK_FORMAT"); if (!string.IsNullOrEmpty(envFormat)) config.DefaultFormat = envFormat; // Credentials from environment variables only (never the config file). var envUsername = Environment.GetEnvironmentVariable("SCADALINK_USERNAME"); if (!string.IsNullOrEmpty(envUsername)) config.Username = envUsername; var envPassword = Environment.GetEnvironmentVariable("SCADALINK_PASSWORD"); if (!string.IsNullOrEmpty(envPassword)) config.Password = envPassword; return config; } private class CliConfigFile { /// Management API URL from the config file. public string? ManagementUrl { get; set; } /// Default output format from the config file. public string? DefaultFormat { get; set; } } }