/* Dynagate LLM Gateway: dynamically change models on the fly (C++20 port) Created by Luxferre in 2026, released into the public domain */ #ifndef _DARWIN_C_SOURCE #define _DARWIN_C_SOURCE #endif #include "dynagate.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace { volatile std::sig_atomic_t g_shutdown_requested = 0; void handle_signal(int /*sig*/) { g_shutdown_requested = 1; } void print_usage(const char *prog) { std::fprintf(stderr, "Dynagate LLM gateway by Luxferre (https://luxferre.top)\n\n" "Usage of %s:\n" " -csv string\n" " \tPath to the CSV configuration file (default \"models.csv\")\n" " -csv-update-interval int\n" " \tInterval in minutes with which to run the CSV updater command (default 10)\n" " -csv-updater string\n" " \tCommand to run to update the configuration CSV file\n" " -key string\n" " \tPath to plaintext file containing expected auth token\n" " -port int\n" " \tPort to listen on (default 8080)\n" " -retry-delay int\n" " \tBase delay in milliseconds for Fibonacci failover retries (0 to disable) (default 100)\n", prog); } bool parse_int_arg(const std::string &val, int &out) { if (val.empty()) { return false; } char *endptr = nullptr; long parsed = std::strtol(val.c_str(), &endptr, 10); if (endptr == val.c_str() || *endptr != '\0') { return false; } out = static_cast(parsed); return true; } } // namespace int main(int argc, char **argv) { int port = 8080; std::string csv_path = "models.csv"; std::string key_path; std::string csv_updater; int csv_update_interval = 10; int retry_delay_ms = 100; for (int i = 1; i < argc; ++i) { std::string arg = argv[i]; if (arg == "-h" || arg == "-help" || arg == "--help") { print_usage(argv[0]); return 0; } std::string flag_name = arg; std::string flag_val; bool has_eq = false; if (flag_name.rfind("--", 0) == 0) { flag_name.erase(0, 1); } size_t eq_pos = flag_name.find('='); if (eq_pos != std::string::npos) { flag_val = flag_name.substr(eq_pos + 1); flag_name.resize(eq_pos); has_eq = true; } auto next_val = [&](std::string &out) -> bool { if (has_eq) { out = flag_val; return true; } if (i + 1 >= argc) { std::fprintf(stderr, "flag needs an argument: %s\n", arg.c_str()); print_usage(argv[0]); return false; } out = argv[++i]; return true; }; if (flag_name == "-port") { std::string v; if (!next_val(v) || !parse_int_arg(v, port)) { std::fprintf(stderr, "invalid value for -port\n"); print_usage(argv[0]); return 2; } } else if (flag_name == "-csv") { if (!next_val(csv_path)) { return 2; } } else if (flag_name == "-key") { if (!next_val(key_path)) { return 2; } } else if (flag_name == "-csv-updater") { if (!next_val(csv_updater)) { return 2; } } else if (flag_name == "-csv-update-interval") { std::string v; if (!next_val(v) || !parse_int_arg(v, csv_update_interval)) { std::fprintf(stderr, "invalid value for -csv-update-interval\n"); print_usage(argv[0]); return 2; } } else if (flag_name == "-retry-delay") { std::string v; if (!next_val(v) || !parse_int_arg(v, retry_delay_ms)) { std::fprintf(stderr, "invalid value for -retry-delay\n"); print_usage(argv[0]); return 2; } } else { std::fprintf(stderr, "flag provided but not defined: %s\n", arg.c_str()); print_usage(argv[0]); return 2; } } std::signal(SIGPIPE, SIG_IGN); ensure_curl_initialized(); auto retry_delay = std::chrono::milliseconds(retry_delay_ms); std::vector auth_tokens; if (!key_path.empty()) { std::ifstream ifs(key_path, std::ios::binary); if (!ifs.is_open()) { log_msg("Failed to read auth token file: %s", key_path.c_str()); return 1; } std::ostringstream oss; oss << ifs.rdbuf(); auto tokens = parse_auth_tokens(oss.str()); if (!tokens.empty()) { auth_tokens = std::move(tokens); log_msg( "Starting Dynagate LLM Gateway. Config: %s, Port: %d, Auth: Required (%zu tokens loaded)", csv_path.c_str(), port, auth_tokens.size()); } else { log_msg( "Starting Dynagate LLM Gateway. Config: %s, Port: %d, Auth: None (empty key file)", csv_path.c_str(), port); } } else { log_msg("Starting Dynagate LLM Gateway. Config: %s, Port: %d, Auth: None", csv_path.c_str(), port); } // Initialize configuration manager ConfigManager cm(csv_path); std::atomic stop_flag{false}; // Start background config watcher std::thread watcher_thread([&]() { cm.watch(stop_flag); }); // Start background CSV updater if specified std::thread updater_thread; if (!csv_updater.empty()) { int mins = csv_update_interval; if (mins <= 0) { log_msg( "Warning: csv-update-interval must be positive, defaulting to 10 minutes"); mins = 10; } auto interval = std::chrono::minutes(mins); log_msg("Starting background CSV updater: command=\"%s\", interval=%dm0s", csv_updater.c_str(), mins); updater_thread = std::thread([&, interval]() { start_csv_updater(stop_flag, csv_updater, interval); }); } GatewayServer server(port, cm, auth_tokens, retry_delay); std::string listen_err; if (!server.start(&listen_err)) { log_msg("Server listen failed: %s", listen_err.c_str()); stop_flag.store(true); if (watcher_thread.joinable()) { watcher_thread.join(); } if (updater_thread.joinable()) { updater_thread.join(); } return 1; } struct sigaction sa; std::memset(&sa, 0, sizeof(sa)); sa.sa_handler = handle_signal; sigemptyset(&sa.sa_mask); sigaction(SIGINT, &sa, nullptr); sigaction(SIGTERM, &sa, nullptr); std::thread server_thread([&]() { log_msg("Server listening on port %d", server.bound_port()); server.serve(); }); while (!g_shutdown_requested) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); } log_msg("Shutting down Dynagate server..."); stop_flag.store(true); server.stop(); if (server_thread.joinable()) { server_thread.join(); } if (watcher_thread.joinable()) { watcher_thread.join(); } if (updater_thread.joinable()) { updater_thread.join(); } log_msg("Server exited cleanly"); return 0; }