Files

255 lines
6.8 KiB
C++
Raw Permalink Normal View History

2026-10-14 09:18:19 +03:00
/*
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 <atomic>
#include <chrono>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
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<int>(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<std::string> 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<bool> 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;
}