| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- #include "smartbotic/database/config.hpp"
- #include <nlohmann/json.hpp>
- #include <spdlog/spdlog.h>
- #include <algorithm>
- #include <cctype>
- #include <cstdlib>
- #include <filesystem>
- #include <fstream>
- namespace smartbotic::database {
- auto LogLevelToString(LogLevel level) -> std::string {
- switch (level) {
- case LogLevel::kTrace:
- return "trace";
- case LogLevel::kDebug:
- return "debug";
- case LogLevel::kInfo:
- return "info";
- case LogLevel::kWarn:
- return "warn";
- case LogLevel::kError:
- return "error";
- case LogLevel::kCritical:
- return "critical";
- case LogLevel::kOff:
- return "off";
- }
- return "info";
- }
- auto ParseLogLevel(const std::string& str) -> std::optional<LogLevel> {
- std::string lower;
- lower.reserve(str.size());
- std::transform(str.begin(), str.end(), std::back_inserter(lower),
- [](unsigned char c) { return std::tolower(c); });
- if (lower == "trace") return LogLevel::kTrace;
- if (lower == "debug") return LogLevel::kDebug;
- if (lower == "info") return LogLevel::kInfo;
- if (lower == "warn" || lower == "warning") return LogLevel::kWarn;
- if (lower == "error" || lower == "err") return LogLevel::kError;
- if (lower == "critical" || lower == "crit" || lower == "fatal") return LogLevel::kCritical;
- if (lower == "off" || lower == "none") return LogLevel::kOff;
- return std::nullopt;
- }
- namespace {
- auto GetEnvOrDefault(const char* name, const std::string& default_value) -> std::string {
- const char* value = std::getenv(name);
- return value != nullptr ? std::string(value) : default_value;
- }
- auto GetEnvOrDefault(const char* name, uint16_t default_value) -> uint16_t {
- const char* value = std::getenv(name);
- if (value == nullptr) {
- return default_value;
- }
- try {
- return static_cast<uint16_t>(std::stoi(value));
- } catch (...) {
- return default_value;
- }
- }
- auto GetEnvOrDefault(const char* name, int64_t default_value) -> int64_t {
- const char* value = std::getenv(name);
- if (value == nullptr) {
- return default_value;
- }
- try {
- return std::stoll(value);
- } catch (...) {
- return default_value;
- }
- }
- } // namespace
- auto ConfigLoader::GetDefaultConfigPaths() -> std::vector<std::string> {
- std::vector<std::string> paths;
- // Current directory
- paths.emplace_back("./smartbotic-db.json");
- // Config subdirectory
- paths.emplace_back("./config/smartbotic-db.json");
- // /etc for system-wide config
- paths.emplace_back("/etc/smartbotic/smartbotic-db.json");
- // Home directory
- const char* home = std::getenv("HOME");
- if (home != nullptr) {
- paths.push_back(std::string(home) + "/.config/smartbotic/smartbotic-db.json");
- }
- return paths;
- }
- auto ConfigLoader::FindConfigFile() -> std::optional<std::string> {
- // Check environment variable for explicit config path
- const char* config_path_env = std::getenv("SMARTBOTIC_CRM_DB_CONFIG");
- if (config_path_env != nullptr && std::filesystem::exists(config_path_env)) {
- return std::string(config_path_env);
- }
- // Search default paths
- for (const auto& path : GetDefaultConfigPaths()) {
- if (std::filesystem::exists(path)) {
- return path;
- }
- }
- return std::nullopt;
- }
- auto ConfigLoader::LoadFromFile(const std::string& path) -> std::optional<DatabaseConfig> {
- try {
- if (!std::filesystem::exists(path)) {
- spdlog::warn("Config file not found: {}", path);
- return std::nullopt;
- }
- std::ifstream file(path);
- if (!file.is_open()) {
- spdlog::error("Failed to open config file: {}", path);
- return std::nullopt;
- }
- nlohmann::json json = nlohmann::json::parse(file);
- DatabaseConfig config;
- // Server section
- if (json.contains("server")) {
- const auto& server = json["server"];
- if (server.contains("address")) {
- config.address = server["address"].get<std::string>();
- }
- if (server.contains("port")) {
- config.port = server["port"].get<uint16_t>();
- }
- }
- // Storage section
- if (json.contains("storage")) {
- const auto& storage = json["storage"];
- if (storage.contains("data_dir")) {
- config.data_dir = storage["data_dir"].get<std::string>();
- }
- }
- // Snapshot section
- if (json.contains("snapshot")) {
- const auto& snapshot = json["snapshot"];
- if (snapshot.contains("enabled")) {
- config.snapshot_enabled = snapshot["enabled"].get<bool>();
- }
- if (snapshot.contains("interval_seconds")) {
- config.snapshot_interval_seconds = snapshot["interval_seconds"].get<int64_t>();
- }
- if (snapshot.contains("change_threshold")) {
- config.snapshot_change_threshold = snapshot["change_threshold"].get<uint64_t>();
- }
- }
- // Logging section
- if (json.contains("logging")) {
- const auto& logging = json["logging"];
- if (logging.contains("level")) {
- auto level_str = logging["level"].get<std::string>();
- auto level = ParseLogLevel(level_str);
- if (level) {
- config.log_level = *level;
- } else {
- spdlog::warn("Invalid log level '{}', using default", level_str);
- }
- }
- }
- // Security section
- if (json.contains("security")) {
- const auto& security = json["security"];
- if (security.contains("encryption_key_path")) {
- config.encryption_key_path = security["encryption_key_path"].get<std::string>();
- }
- }
- spdlog::info("Loaded configuration from: {}", path);
- return config;
- } catch (const nlohmann::json::exception& e) {
- spdlog::error("Failed to parse config file {}: {}", path, e.what());
- return std::nullopt;
- } catch (const std::exception& e) {
- spdlog::error("Error loading config file {}: {}", path, e.what());
- return std::nullopt;
- }
- }
- void ConfigLoader::ApplyEnvironment(DatabaseConfig& config) {
- // Server settings from environment (can override config file)
- config.address = GetEnvOrDefault("SMARTBOTIC_CRM_DB_ADDRESS", config.address);
- config.port = GetEnvOrDefault("SMARTBOTIC_CRM_DB_PORT", config.port);
- config.data_dir = GetEnvOrDefault("SMARTBOTIC_CRM_DB_DATA_DIR", config.data_dir);
- // Snapshot settings from environment
- config.snapshot_interval_seconds =
- GetEnvOrDefault("SMARTBOTIC_CRM_DB_SNAPSHOT_INTERVAL", config.snapshot_interval_seconds);
- // Log level from environment
- const char* log_level_env = std::getenv("SMARTBOTIC_CRM_DB_LOG_LEVEL");
- if (log_level_env != nullptr) {
- auto level = ParseLogLevel(log_level_env);
- if (level) {
- config.log_level = *level;
- }
- }
- // Encryption key path from environment (for secrets)
- const char* key_path_env = std::getenv("SMARTBOTIC_CRM_DB_ENCRYPTION_KEY_PATH");
- if (key_path_env != nullptr) {
- config.encryption_key_path = key_path_env;
- }
- }
- auto ConfigLoader::ParseCommandLine(int argc, char* argv[], DatabaseConfig& config)
- -> std::optional<bool> {
- std::string config_file_override;
- for (int i = 1; i < argc; ++i) {
- std::string arg = argv[i];
- if (arg == "--help" || arg == "-h") {
- PrintHelp(argv[0]);
- return std::nullopt; // Help requested, exit
- }
- if (arg == "--version" || arg == "-v") {
- spdlog::info("SmartBotic Database v0.1.0");
- return std::nullopt; // Version requested, exit
- }
- if (arg == "--config" || arg == "-c") {
- if (i + 1 < argc) {
- config_file_override = argv[++i];
- } else {
- spdlog::error("--config requires a path argument");
- return false;
- }
- continue;
- }
- if (arg == "--port" || arg == "-p") {
- if (i + 1 < argc) {
- try {
- config.port = static_cast<uint16_t>(std::stoi(argv[++i]));
- } catch (...) {
- spdlog::error("Invalid port number: {}", argv[i]);
- return false;
- }
- } else {
- spdlog::error("--port requires a number argument");
- return false;
- }
- continue;
- }
- if (arg == "--address" || arg == "-a") {
- if (i + 1 < argc) {
- config.address = argv[++i];
- } else {
- spdlog::error("--address requires an address argument");
- return false;
- }
- continue;
- }
- if (arg == "--data-dir" || arg == "-d") {
- if (i + 1 < argc) {
- config.data_dir = argv[++i];
- } else {
- spdlog::error("--data-dir requires a path argument");
- return false;
- }
- continue;
- }
- if (arg == "--snapshot-interval") {
- if (i + 1 < argc) {
- try {
- config.snapshot_interval_seconds = std::stoll(argv[++i]);
- } catch (...) {
- spdlog::error("Invalid snapshot interval: {}", argv[i]);
- return false;
- }
- } else {
- spdlog::error("--snapshot-interval requires a number argument");
- return false;
- }
- continue;
- }
- if (arg == "--log-level" || arg == "-l") {
- if (i + 1 < argc) {
- auto level = ParseLogLevel(argv[++i]);
- if (level) {
- config.log_level = *level;
- } else {
- spdlog::error("Invalid log level: {}", argv[i]);
- return false;
- }
- } else {
- spdlog::error("--log-level requires a level argument");
- return false;
- }
- continue;
- }
- if (arg == "--encryption-key-path") {
- if (i + 1 < argc) {
- config.encryption_key_path = argv[++i];
- } else {
- spdlog::error("--encryption-key-path requires a path argument");
- return false;
- }
- continue;
- }
- spdlog::error("Unknown argument: {}", arg);
- PrintHelp(argv[0]);
- return false;
- }
- // If config file was specified on command line, load it
- if (!config_file_override.empty()) {
- auto file_config = LoadFromFile(config_file_override);
- if (!file_config) {
- spdlog::error("Failed to load specified config file: {}", config_file_override);
- return false;
- }
- // Start with file config, then re-apply command line args below
- config = *file_config;
- // Re-parse command line to override file settings
- // (Skip the config file argument this time)
- for (int i = 1; i < argc; ++i) {
- std::string arg = argv[i];
- if (arg == "--config" || arg == "-c") {
- ++i; // Skip the config path
- continue;
- }
- if ((arg == "--port" || arg == "-p") && i + 1 < argc) {
- config.port = static_cast<uint16_t>(std::stoi(argv[++i]));
- } else if ((arg == "--address" || arg == "-a") && i + 1 < argc) {
- config.address = argv[++i];
- } else if ((arg == "--data-dir" || arg == "-d") && i + 1 < argc) {
- config.data_dir = argv[++i];
- } else if (arg == "--snapshot-interval" && i + 1 < argc) {
- config.snapshot_interval_seconds = std::stoll(argv[++i]);
- } else if ((arg == "--log-level" || arg == "-l") && i + 1 < argc) {
- auto level = ParseLogLevel(argv[++i]);
- if (level) config.log_level = *level;
- } else if (arg == "--encryption-key-path" && i + 1 < argc) {
- config.encryption_key_path = argv[++i];
- }
- }
- }
- return true;
- }
- auto ConfigLoader::Load(int argc, char* argv[]) -> DatabaseConfig {
- DatabaseConfig config = DatabaseConfig::Default();
- // 1. Try to find and load config file (lowest priority for non-secrets)
- auto config_file = FindConfigFile();
- if (config_file) {
- auto file_config = LoadFromFile(*config_file);
- if (file_config) {
- config = *file_config;
- }
- }
- // 2. Apply environment variables (can override config file)
- ApplyEnvironment(config);
- // 3. Parse command-line arguments (highest priority)
- auto result = ParseCommandLine(argc, argv, config);
- if (!result.has_value()) {
- // Help or version was requested, exit cleanly
- std::exit(0);
- }
- if (!result.value()) {
- // Error parsing arguments
- std::exit(1);
- }
- return config;
- }
- void ConfigLoader::PrintHelp(const char* program_name) {
- spdlog::info("Usage: {} [options]", program_name);
- spdlog::info("");
- spdlog::info("SmartBotic Database Service");
- spdlog::info("");
- spdlog::info("Options:");
- spdlog::info(" -h, --help Show this help message");
- spdlog::info(" -v, --version Show version information");
- spdlog::info(" -c, --config <path> Path to config file (default: smartbotic-db.json)");
- spdlog::info(" -p, --port <port> Port to listen on (default: 50051)");
- spdlog::info(" -a, --address <addr> Address to bind to (default: 0.0.0.0)");
- spdlog::info(" -d, --data-dir <path> Data directory (default: ./data)");
- spdlog::info(" --snapshot-interval <sec> Snapshot interval in seconds (default: 300)");
- spdlog::info(" -l, --log-level <level> Log level: trace, debug, info, warn, error, critical, off");
- spdlog::info(" --encryption-key-path <p> Path to encryption key file");
- spdlog::info("");
- spdlog::info("Environment Variables:");
- spdlog::info(" SMARTBOTIC_CRM_DB_CONFIG Path to config file");
- spdlog::info(" SMARTBOTIC_CRM_DB_ADDRESS Address to bind to");
- spdlog::info(" SMARTBOTIC_CRM_DB_PORT Port to listen on");
- spdlog::info(" SMARTBOTIC_CRM_DB_DATA_DIR Data directory");
- spdlog::info(" SMARTBOTIC_CRM_DB_SNAPSHOT_INTERVAL Snapshot interval in seconds");
- spdlog::info(" SMARTBOTIC_CRM_DB_LOG_LEVEL Log level");
- spdlog::info(" SMARTBOTIC_CRM_DB_ENCRYPTION_KEY_PATH Path to encryption key file");
- spdlog::info(" SMARTBOTIC_CRM_DB_ENCRYPTION_KEY Encryption key (base64 encoded) - for secrets");
- spdlog::info("");
- spdlog::info("Config File Search Order:");
- for (const auto& path : GetDefaultConfigPaths()) {
- spdlog::info(" {}", path);
- }
- }
- void ConfigLoader::PrintConfig(const DatabaseConfig& config) {
- spdlog::info("Configuration:");
- spdlog::info(" Address: {}", config.address);
- spdlog::info(" Port: {}", config.port);
- spdlog::info(" Data Directory: {}", config.data_dir);
- spdlog::info(" Snapshot Interval: {} seconds", config.snapshot_interval_seconds);
- spdlog::info(" Snapshot Enabled: {}", config.snapshot_enabled ? "yes" : "no");
- spdlog::info(" Log Level: {}", LogLevelToString(config.log_level));
- if (!config.encryption_key_path.empty()) {
- spdlog::info(" Encryption Key: {} (path configured)", config.encryption_key_path);
- } else {
- const char* key_env = std::getenv("SMARTBOTIC_CRM_DB_ENCRYPTION_KEY");
- if (key_env != nullptr) {
- spdlog::info(" Encryption Key: (from environment variable)");
- } else {
- spdlog::info(" Encryption Key: (not configured)");
- }
- }
- }
- } // namespace smartbotic::database
|