{ config, lib, pkgs, ... }: let cfg = config.services.flaredb; tomlFormat = pkgs.formats.toml { }; stripLeadingZeros = digits: if digits == "" then "" else if lib.hasPrefix "0" digits then stripLeadingZeros (lib.removePrefix "0" digits) else digits; numericIdString = value: let captures = builtins.match ".*?([0-9]+)$" value; digits = if captures == null then throw "services.flaredb.nodeId must end with digits (got '${value}')" else builtins.elemAt captures 0; normalized = stripLeadingZeros digits; in if normalized == "" then "0" else normalized; apiAddrArg = if cfg.apiAddr != null then cfg.apiAddr else "0.0.0.0:${toString cfg.port}"; peerArgs = map (peer: let parts = lib.splitString "=" peer; rawId = if builtins.length parts == 2 then builtins.elemAt parts 0 else throw "services.flaredb.initialPeers entries must be 'storeId=host:port' (got '${peer}')"; peerAddr = builtins.elemAt parts 1; in "--peer ${numericIdString rawId}=${peerAddr}") cfg.initialPeers; chainfirePeerApiEndpoints = if (config.services ? chainfire) && config.services.chainfire.enable then map (peer: let parts = lib.splitString "=" peer; raftAddr = if builtins.length parts == 2 then builtins.elemAt parts 1 else throw "services.chainfire.initialPeers entries must be 'nodeId=host:port' (got '${peer}')"; captures = builtins.match "(.+):[0-9]+" raftAddr; host = if captures == null then throw "services.chainfire.initialPeers raft address must be host:port (got '${raftAddr}')" else builtins.elemAt captures 0; in "${host}:${toString config.services.chainfire.port}") config.services.chainfire.initialPeers else []; defaultPdAddr = if cfg.pdAddr != null then cfg.pdAddr else if (config.services ? chainfire) && config.services.chainfire.enable then if config.services.chainfire.apiAddr != null then config.services.chainfire.apiAddr else "127.0.0.1:${toString config.services.chainfire.port}" else "127.0.0.1:2379"; pdAddrMatch = builtins.match "(.*):([0-9]+)" defaultPdAddr; pdHost = if pdAddrMatch == null then throw "services.flaredb.pdAddr must be host:port (got '${defaultPdAddr}')" else builtins.elemAt pdAddrMatch 0; pdPort = if pdAddrMatch == null then throw "services.flaredb.pdAddr must be host:port (got '${defaultPdAddr}')" else builtins.elemAt pdAddrMatch 1; derivedPdEndpoints = lib.unique ([ defaultPdAddr ] ++ chainfirePeerApiEndpoints); localDependencies = lib.optionals ((config.services ? chainfire) && config.services.chainfire.enable) [ "chainfire.service" ]; flaredbConfigFile = tomlFormat.generate "flaredb.toml" (lib.recursiveUpdate { addr = apiAddrArg; http_addr = "0.0.0.0:${toString cfg.httpPort}"; data_dir = toString cfg.dataDir; pd_addr = defaultPdAddr; pd_endpoints = derivedPdEndpoints; log_level = "info"; namespace_modes = { default = "strong"; validation = "eventual"; plasmavmc = "strong"; lightningstor = "eventual"; prismnet = "eventual"; flashdns = "eventual"; fiberlb = "eventual"; creditservice = "strong"; k8shost = "eventual"; }; } cfg.settings); in { options.services.flaredb = { enable = lib.mkEnableOption "flaredb distributed SQL/KV service"; nodeId = lib.mkOption { type = lib.types.str; default = config.networking.hostName; description = "Unique node identifier for the Raft cluster"; }; port = lib.mkOption { type = lib.types.port; default = 2479; description = "Port for flaredb API"; }; raftPort = lib.mkOption { type = lib.types.port; default = 2480; description = "Port for flaredb Raft protocol"; }; raftAddr = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "Full address for Raft (host:port). If null, uses 0.0.0.0:raftPort"; }; apiAddr = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "Full address for API (host:port). If null, uses 0.0.0.0:port"; }; initialPeers = lib.mkOption { type = lib.types.listOf lib.types.str; default = []; description = "Initial Raft peers for cluster bootstrap"; example = [ "node01=10.0.0.1:2480" "node02=10.0.0.2:2480" ]; }; httpPort = lib.mkOption { type = lib.types.port; default = 8082; description = "Port for flaredb HTTP/admin API (used for cluster join)"; }; pdAddr = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "ChainFire placement-driver endpoint (host:port). Defaults to the local ChainFire API when enabled."; example = "10.0.0.1:2379"; }; dataDir = lib.mkOption { type = lib.types.path; default = "/var/lib/flaredb"; description = "Data directory for flaredb"; }; dbaasEnabled = lib.mkOption { type = lib.types.bool; default = true; description = "Expose FlareDB as the managed DBaaS data plane"; }; settings = lib.mkOption { type = lib.types.attrs; default = {}; description = "Additional configuration settings"; }; package = lib.mkOption { type = lib.types.package; default = pkgs.flaredb-server or (throw "flaredb-server package not found"); description = "Package to use for flaredb"; }; }; config = lib.mkIf cfg.enable { # Create system user users.users.flaredb = { isSystemUser = true; group = "flaredb"; description = "FlareDB service user"; home = cfg.dataDir; }; users.groups.flaredb = {}; # Create systemd service systemd.services.flaredb = { description = "FlareDB Distributed Database Service"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ] ++ localDependencies; requires = localDependencies; environment = { FLAREDB_DBAAS_ENABLED = if cfg.dbaasEnabled then "true" else "false"; }; serviceConfig = { Type = "simple"; User = "flaredb"; Group = "flaredb"; Restart = "on-failure"; RestartSec = "10s"; # State directory management StateDirectory = "flaredb"; StateDirectoryMode = "0750"; # Security hardening NoNewPrivileges = true; PrivateTmp = true; ProtectSystem = "strict"; ProtectHome = true; ReadWritePaths = [ cfg.dataDir ]; ExecStartPre = "${pkgs.bash}/bin/bash -lc 'for i in $(seq 1 60); do ${pkgs.netcat}/bin/nc -z ${lib.escapeShellArg pdHost} ${lib.escapeShellArg pdPort} && exit 0; sleep 1; done; echo \"timed out waiting for FlareDB PD ${defaultPdAddr}\" >&2; exit 1'"; ExecStart = lib.concatStringsSep " " ([ "${cfg.package}/bin/flaredb-server" "--config ${flaredbConfigFile}" "--store-id ${numericIdString cfg.nodeId}" ] ++ peerArgs); }; }; }; }