photoncloud-monorepo/nix/modules/flaredb.nix

225 lines
7.2 KiB
Nix

{ 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);
};
};
};
}