|
@@ -1,4 +1,5 @@
|
|
|
#include "database_service.hpp"
|
|
#include "database_service.hpp"
|
|
|
|
|
+#include "persistence/persistence_manager.hpp"
|
|
|
|
|
|
|
|
#include <spdlog/spdlog.h>
|
|
#include <spdlog/spdlog.h>
|
|
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
|
@@ -7,6 +8,8 @@
|
|
|
#include <cstdlib>
|
|
#include <cstdlib>
|
|
|
#include <filesystem>
|
|
#include <filesystem>
|
|
|
#include <iostream>
|
|
#include <iostream>
|
|
|
|
|
+#include <optional>
|
|
|
|
|
+#include <string>
|
|
|
|
|
|
|
|
#ifdef HAVE_SYSTEMD
|
|
#ifdef HAVE_SYSTEMD
|
|
|
#include <systemd/sd-daemon.h>
|
|
#include <systemd/sd-daemon.h>
|
|
@@ -30,6 +33,11 @@ void printUsage(const char* programName) {
|
|
|
<< " --config, -c PATH Configuration file path\n"
|
|
<< " --config, -c PATH Configuration file path\n"
|
|
|
<< " --help, -h Show this help message\n"
|
|
<< " --help, -h Show this help message\n"
|
|
|
<< " --version, -v Show version information\n"
|
|
<< " --version, -v Show version information\n"
|
|
|
|
|
+ << " --read-only Start in read-only mode (reject all writes)\n"
|
|
|
|
|
+ << " --force-readwrite Accept non-trivial recovery state; start writable\n"
|
|
|
|
|
+ << " --recovery-mode=MODE Override recovery mode for this boot\n"
|
|
|
|
|
+ << " Values: normal, snapshot_fallback, wal_only,\n"
|
|
|
|
|
+ << " best_effort, force_empty\n"
|
|
|
<< "\n"
|
|
<< "\n"
|
|
|
<< "Environment variables:\n"
|
|
<< "Environment variables:\n"
|
|
|
<< " DATABASE_NODE_ID Override node ID from config\n"
|
|
<< " DATABASE_NODE_ID Override node ID from config\n"
|
|
@@ -71,6 +79,33 @@ void initLogging(const std::string& serviceName) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+struct CliOverrides {
|
|
|
|
|
+ bool readOnly = false;
|
|
|
|
|
+ bool forceReadwrite = false;
|
|
|
|
|
+ std::optional<smartbotic::database::RecoveryMode> recoveryMode;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+CliOverrides parseCliOverrides(int argc, char* argv[]) {
|
|
|
|
|
+ CliOverrides ov;
|
|
|
|
|
+ for (int i = 1; i < argc; i++) {
|
|
|
|
|
+ std::string arg = argv[i];
|
|
|
|
|
+ if (arg == "--read-only") {
|
|
|
|
|
+ ov.readOnly = true;
|
|
|
|
|
+ } else if (arg == "--force-readwrite") {
|
|
|
|
|
+ ov.forceReadwrite = true;
|
|
|
|
|
+ } else if (arg.rfind("--recovery-mode=", 0) == 0) {
|
|
|
|
|
+ std::string mode = arg.substr(std::string("--recovery-mode=").size());
|
|
|
|
|
+ try {
|
|
|
|
|
+ ov.recoveryMode = smartbotic::database::recoveryModeFromString(mode);
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ std::cerr << "ERROR: " << e.what() << std::endl;
|
|
|
|
|
+ std::exit(11);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return ov;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
std::filesystem::path findConfigFile(int argc, char* argv[]) {
|
|
std::filesystem::path findConfigFile(int argc, char* argv[]) {
|
|
|
// Check command line arguments
|
|
// Check command line arguments
|
|
|
for (int i = 1; i < argc; ++i) {
|
|
for (int i = 1; i < argc; ++i) {
|
|
@@ -149,20 +184,44 @@ int main(int argc, char* argv[]) {
|
|
|
config.nodeId = nodeId;
|
|
config.nodeId = nodeId;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Parse CLI overrides (--read-only, --force-readwrite, --recovery-mode=...)
|
|
|
|
|
+ auto cli = parseCliOverrides(argc, argv);
|
|
|
|
|
+
|
|
|
|
|
+ // Apply --recovery-mode to persistence config before service construction
|
|
|
|
|
+ if (cli.recoveryMode) {
|
|
|
|
|
+ config.persistenceConfig.recoveryMode = *cli.recoveryMode;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Create service
|
|
// Create service
|
|
|
smartbotic::database::DatabaseService service(config);
|
|
smartbotic::database::DatabaseService service(config);
|
|
|
g_service = &service;
|
|
g_service = &service;
|
|
|
|
|
|
|
|
|
|
+ // Apply --force-readwrite BEFORE initialize() so auto-readonly check respects it
|
|
|
|
|
+ if (cli.forceReadwrite) {
|
|
|
|
|
+ service.setForceReadwrite(true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Set up signal handlers
|
|
// Set up signal handlers
|
|
|
std::signal(SIGINT, signalHandler);
|
|
std::signal(SIGINT, signalHandler);
|
|
|
std::signal(SIGTERM, signalHandler);
|
|
std::signal(SIGTERM, signalHandler);
|
|
|
|
|
|
|
|
- // Initialize service
|
|
|
|
|
|
|
+ // Initialize service. Recovery-refused is reported via the
|
|
|
|
|
+ // recovery_outcome_ state (banner printed by DatabaseService) and
|
|
|
|
|
+ // maps to exit code 10.
|
|
|
if (!service.initialize()) {
|
|
if (!service.initialize()) {
|
|
|
|
|
+ if (service.recoveryOutcome().isFailure()) {
|
|
|
|
|
+ // Banner was already printed by DatabaseService; just exit with code 10.
|
|
|
|
|
+ return 10;
|
|
|
|
|
+ }
|
|
|
spdlog::error("Failed to initialize database service");
|
|
spdlog::error("Failed to initialize database service");
|
|
|
return 1;
|
|
return 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Apply --read-only after initialize() succeeds, before start() accepts connections
|
|
|
|
|
+ if (cli.readOnly) {
|
|
|
|
|
+ service.setReadOnly(true, "started with --read-only flag");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Start service
|
|
// Start service
|
|
|
service.start();
|
|
service.start();
|
|
|
|
|
|