main.cpp 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. #include "auth.hpp"
  2. #include "collection_registry.hpp"
  3. #include "config.hpp"
  4. #include "db_gateway.hpp"
  5. #include "key_store.hpp"
  6. #include "server.hpp"
  7. #include "settings_store.hpp"
  8. #include <svapi/version.hpp>
  9. #include <spdlog/spdlog.h>
  10. #ifdef HAVE_SYSTEMD
  11. #include <systemd/sd-daemon.h>
  12. #else
  13. #define sd_notify(u, s) do {} while (0)
  14. #endif
  15. #include <atomic>
  16. #include <chrono>
  17. #include <csignal>
  18. #include <cstdlib>
  19. #include <cstring>
  20. #include <iostream>
  21. #include <thread>
  22. namespace {
  23. std::atomic<bool> g_running{true};
  24. void onSignal(int) { g_running = false; }
  25. std::string envOr(const char* k, const std::string& dflt = "") { const char* v = std::getenv(k); return v ? std::string(v) : dflt; }
  26. }
  27. int main(int argc, char** argv) {
  28. std::string configPath = "/etc/smartbotic-vectorapi/config.json";
  29. std::string webuiDirOverride;
  30. std::string shareDir = "/usr/share/smartbotic-vectorapi";
  31. for (int i = 1; i < argc; ++i) {
  32. if (std::strcmp(argv[i], "--config") == 0 && i + 1 < argc) configPath = argv[++i];
  33. else if (std::strcmp(argv[i], "--webui-dir") == 0 && i + 1 < argc) webuiDirOverride = argv[++i];
  34. else if (std::strcmp(argv[i], "--share-dir") == 0 && i + 1 < argc) shareDir = argv[++i];
  35. else if (std::strcmp(argv[i], "--version") == 0 || std::strcmp(argv[i], "-v") == 0) {
  36. std::cout << "smartbotic-vectorapi " << svapi::VERSION << " (" << svapi::GIT_COMMIT << ")\n";
  37. return 0;
  38. }
  39. }
  40. svapi::Config cfg;
  41. try { cfg = svapi::Config::load(configPath); }
  42. catch (const std::exception& e) { std::cerr << "config error: " << e.what() << "\n"; return 1; }
  43. spdlog::set_level(spdlog::level::from_str(cfg.logLevel));
  44. if (!webuiDirOverride.empty()) cfg.webuiDir = webuiDirOverride;
  45. std::signal(SIGINT, onSignal); std::signal(SIGTERM, onSignal); std::signal(SIGPIPE, SIG_IGN);
  46. svapi::DbGateway db(cfg.dbAddress);
  47. spdlog::info("connecting to smartbotic-database at {}", cfg.dbAddress);
  48. for (int i = 0; i < 30 && !db.connect(); ++i) std::this_thread::sleep_for(std::chrono::seconds(1));
  49. if (!db.healthy()) { spdlog::error("database not reachable at {}", cfg.dbAddress); return 1; }
  50. svapi::SettingsStore settings(db);
  51. settings.bootstrap(envOr("OPENAI_API_KEY"));
  52. settings.startWatch();
  53. svapi::KeyStore keys(db);
  54. keys.bootstrap(envOr("SMARTBOTIC_VECTORAPI_KEY"));
  55. keys.startWatch();
  56. svapi::CollectionRegistry registry(db);
  57. const std::string defProj = settings.snapshot()->defaultProject;
  58. db.ensureProject(defProj);
  59. // Re-open EVERY project's collection registry at startup. The DB only
  60. // surfaces a collection via find()/list() once it has been opened in the
  61. // current process, so without this a restart makes all collections vanish
  62. // from the API (GET /collections empty, document ops 404) even though the
  63. // data survives on disk. KeyStore::bootstrap already does this for
  64. // vectorapi_keys, which is why keys survive but the registry didn't. (issue #1)
  65. {
  66. auto projects = db.listProjects(); // always includes the default project
  67. registry.bootstrap(projects);
  68. spdlog::info("opened collection registry for {} project(s) at startup", projects.size());
  69. }
  70. svapi::SessionStore sessions(std::chrono::minutes(settings.snapshot()->sessionTtlMinutes));
  71. svapi::ServerDeps deps{db, settings, keys, registry, sessions, cfg.webuiDir, shareDir};
  72. svapi::ApiServer server(std::move(deps));
  73. server.registerRoutes();
  74. std::thread http([&] {
  75. spdlog::info("listening on {}:{}", cfg.httpBind, cfg.httpPort);
  76. if (!server.listen(cfg.httpBind, cfg.httpPort)) { spdlog::error("bind failed"); g_running = false; }
  77. });
  78. sd_notify(0, "READY=1");
  79. while (g_running) { sd_notify(0, "WATCHDOG=1"); std::this_thread::sleep_for(std::chrono::milliseconds(200)); }
  80. sd_notify(0, "STOPPING=1");
  81. server.stop();
  82. if (http.joinable()) http.join();
  83. return 0;
  84. }