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; }
}
}