| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- #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 <svapi/version.hpp>
- #include <spdlog/spdlog.h>
- #ifdef HAVE_SYSTEMD
- #include <systemd/sd-daemon.h>
- #else
- #define sd_notify(u, s) do {} while (0)
- #endif
- #include <atomic>
- #include <chrono>
- #include <csignal>
- #include <cstdlib>
- #include <cstring>
- #include <iostream>
- #include <thread>
- namespace {
- std::atomic<bool> 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;
- }
|