#include "auth.hpp" #include "collection_registry.hpp" #include "config.hpp" #include "db_gateway.hpp" #include "key_store.hpp" #include "server.hpp" #include "settings_store.hpp" #include #include #ifdef HAVE_SYSTEMD #include #else #define sd_notify(u, s) do {} while (0) #endif #include #include #include #include #include #include #include namespace { std::atomic g_running{true}; void onSignal(int) { g_running = false; } std::string envOr(const char* k, const std::string& dflt = "") { const char* v = std::getenv(k); return v ? std::string(v) : dflt; } } int main(int argc, char** argv) { std::string configPath = "/etc/smartbotic-vectorapi/config.json"; std::string webuiDirOverride; std::string shareDir = "/usr/share/smartbotic-vectorapi"; for (int i = 1; i < argc; ++i) { if (std::strcmp(argv[i], "--config") == 0 && i + 1 < argc) configPath = argv[++i]; else if (std::strcmp(argv[i], "--webui-dir") == 0 && i + 1 < argc) webuiDirOverride = argv[++i]; else if (std::strcmp(argv[i], "--share-dir") == 0 && i + 1 < argc) shareDir = argv[++i]; else if (std::strcmp(argv[i], "--version") == 0 || std::strcmp(argv[i], "-v") == 0) { std::cout << "smartbotic-vectorapi " << svapi::VERSION << " (" << svapi::GIT_COMMIT << ")\n"; return 0; } } svapi::Config cfg; try { cfg = svapi::Config::load(configPath); } catch (const std::exception& e) { std::cerr << "config error: " << e.what() << "\n"; return 1; } spdlog::set_level(spdlog::level::from_str(cfg.logLevel)); if (!webuiDirOverride.empty()) cfg.webuiDir = webuiDirOverride; std::signal(SIGINT, onSignal); std::signal(SIGTERM, onSignal); std::signal(SIGPIPE, SIG_IGN); svapi::DbGateway db(cfg.dbAddress); spdlog::info("connecting to smartbotic-database at {}", cfg.dbAddress); for (int i = 0; i < 30 && !db.connect(); ++i) std::this_thread::sleep_for(std::chrono::seconds(1)); if (!db.healthy()) { spdlog::error("database not reachable at {}", cfg.dbAddress); return 1; } svapi::SettingsStore settings(db); settings.bootstrap(envOr("OPENAI_API_KEY")); settings.startWatch(); svapi::KeyStore keys(db); keys.bootstrap(envOr("SMARTBOTIC_VECTORAPI_KEY")); keys.startWatch(); svapi::CollectionRegistry registry(db); const std::string defProj = settings.snapshot()->defaultProject; db.ensureProject(defProj); // Re-open EVERY project's collection registry at startup. The DB only // surfaces a collection via find()/list() once it has been opened in the // current process, so without this a restart makes all collections vanish // from the API (GET /collections empty, document ops 404) even though the // data survives on disk. KeyStore::bootstrap already does this for // vectorapi_keys, which is why keys survive but the registry didn't. (issue #1) { auto projects = db.listProjects(); // always includes the default project registry.bootstrap(projects); spdlog::info("opened collection registry for {} project(s) at startup", projects.size()); } svapi::SessionStore sessions(std::chrono::minutes(settings.snapshot()->sessionTtlMinutes)); svapi::ServerDeps deps{db, settings, keys, registry, sessions, cfg.webuiDir, shareDir}; svapi::ApiServer server(std::move(deps)); server.registerRoutes(); std::thread http([&] { spdlog::info("listening on {}:{}", cfg.httpBind, cfg.httpPort); if (!server.listen(cfg.httpBind, cfg.httpPort)) { spdlog::error("bind failed"); g_running = false; } }); sd_notify(0, "READY=1"); while (g_running) { sd_notify(0, "WATCHDOG=1"); std::this_thread::sleep_for(std::chrono::milliseconds(200)); } sd_notify(0, "STOPPING=1"); server.stop(); if (http.joinable()) http.join(); return 0; }