1fdfa1ca13
Signed-off-by: Austin Seipp <aseipp@pobox.com>
417 lines
14 KiB
Nix
417 lines
14 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.services.foundationdb;
|
|
pkg = cfg.package;
|
|
|
|
# used for initial cluster configuration
|
|
initialIpAddr = if (cfg.publicAddress != "auto") then cfg.publicAddress else "127.0.0.1";
|
|
|
|
fdbServers = n:
|
|
concatStringsSep "\n" (map (x: "[fdbserver.${toString (x+cfg.listenPortStart)}]") (range 0 (n - 1)));
|
|
|
|
backupAgents = n:
|
|
concatStringsSep "\n" (map (x: "[backup_agent.${toString x}]") (range 1 n));
|
|
|
|
configFile = pkgs.writeText "foundationdb.conf" ''
|
|
[general]
|
|
cluster_file = /etc/foundationdb/fdb.cluster
|
|
|
|
[fdbmonitor]
|
|
restart_delay = ${toString cfg.restartDelay}
|
|
user = ${cfg.user}
|
|
group = ${cfg.group}
|
|
|
|
[fdbserver]
|
|
command = ${pkg}/bin/fdbserver
|
|
public_address = ${cfg.publicAddress}:$ID
|
|
listen_address = ${cfg.listenAddress}
|
|
datadir = ${cfg.dataDir}/$ID
|
|
logdir = ${cfg.logDir}
|
|
logsize = ${cfg.logSize}
|
|
maxlogssize = ${cfg.maxLogSize}
|
|
${optionalString (cfg.class != null) "class = ${cfg.class}"}
|
|
memory = ${cfg.memory}
|
|
storage_memory = ${cfg.storageMemory}
|
|
|
|
${optionalString (cfg.tls != null) ''
|
|
tls_plugin = ${pkg}/libexec/plugins/FDBLibTLS.so
|
|
tls_certificate_file = ${cfg.tls.certificate}
|
|
tls_key_file = ${cfg.tls.key}
|
|
tls_verify_peers = ${cfg.tls.allowedPeers}
|
|
''}
|
|
|
|
${optionalString (cfg.locality.machineId != null) "locality_machineid=${cfg.locality.machineId}"}
|
|
${optionalString (cfg.locality.zoneId != null) "locality_zoneid=${cfg.locality.zoneId}"}
|
|
${optionalString (cfg.locality.datacenterId != null) "locality_dcid=${cfg.locality.datacenterId}"}
|
|
${optionalString (cfg.locality.dataHall != null) "locality_data_hall=${cfg.locality.dataHall}"}
|
|
|
|
${fdbServers cfg.serverProcesses}
|
|
|
|
[backup_agent]
|
|
command = ${pkg}/libexec/backup_agent
|
|
${backupAgents cfg.backupProcesses}
|
|
'';
|
|
in
|
|
{
|
|
options.services.foundationdb = {
|
|
|
|
enable = mkEnableOption "FoundationDB Server";
|
|
|
|
package = mkOption {
|
|
type = types.package;
|
|
description = ''
|
|
The FoundationDB package to use for this server. This must be specified by the user
|
|
in order to ensure migrations and upgrades are controlled appropriately.
|
|
'';
|
|
};
|
|
|
|
publicAddress = mkOption {
|
|
type = types.str;
|
|
default = "auto";
|
|
description = "Publicly visible IP address of the process. Port is determined by process ID";
|
|
};
|
|
|
|
listenAddress = mkOption {
|
|
type = types.str;
|
|
default = "public";
|
|
description = "Publicly visible IP address of the process. Port is determined by process ID";
|
|
};
|
|
|
|
listenPortStart = mkOption {
|
|
type = types.int;
|
|
default = 4500;
|
|
description = ''
|
|
Starting port number for database listening sockets. Every FDB process binds to a
|
|
subsequent port, to this number reflects the start of the overall range. e.g. having
|
|
8 server processes will use all ports between 4500 and 4507.
|
|
'';
|
|
};
|
|
|
|
openFirewall = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Open the firewall ports corresponding to FoundationDB processes and coordinators
|
|
using <option>config.networking.firewall.*</option>.
|
|
'';
|
|
};
|
|
|
|
dataDir = mkOption {
|
|
type = types.path;
|
|
default = "/var/lib/foundationdb";
|
|
description = "Data directory. All cluster data will be put under here.";
|
|
};
|
|
|
|
logDir = mkOption {
|
|
type = types.path;
|
|
default = "/var/log/foundationdb";
|
|
description = "Log directory.";
|
|
};
|
|
|
|
user = mkOption {
|
|
type = types.str;
|
|
default = "foundationdb";
|
|
description = "User account under which FoundationDB runs.";
|
|
};
|
|
|
|
group = mkOption {
|
|
type = types.str;
|
|
default = "foundationdb";
|
|
description = "Group account under which FoundationDB runs.";
|
|
};
|
|
|
|
class = mkOption {
|
|
type = types.nullOr (types.enum [ "storage" "transaction" "stateless" ]);
|
|
default = null;
|
|
description = "Process class";
|
|
};
|
|
|
|
restartDelay = mkOption {
|
|
type = types.int;
|
|
default = 10;
|
|
description = "Number of seconds to wait before restarting servers.";
|
|
};
|
|
|
|
logSize = mkOption {
|
|
type = types.string;
|
|
default = "10MiB";
|
|
description = ''
|
|
Roll over to a new log file after the current log file
|
|
reaches the specified size.
|
|
'';
|
|
};
|
|
|
|
maxLogSize = mkOption {
|
|
type = types.string;
|
|
default = "100MiB";
|
|
description = ''
|
|
Delete the oldest log file when the total size of all log
|
|
files exceeds the specified size. If set to 0, old log files
|
|
will not be deleted.
|
|
'';
|
|
};
|
|
|
|
serverProcesses = mkOption {
|
|
type = types.int;
|
|
default = 1;
|
|
description = "Number of fdbserver processes to run.";
|
|
};
|
|
|
|
backupProcesses = mkOption {
|
|
type = types.int;
|
|
default = 1;
|
|
description = "Number of backup_agent processes to run for snapshots.";
|
|
};
|
|
|
|
memory = mkOption {
|
|
type = types.string;
|
|
default = "8GiB";
|
|
description = ''
|
|
Maximum memory used by the process. The default value is
|
|
<literal>8GiB</literal>. When specified without a unit,
|
|
<literal>MiB</literal> is assumed. This parameter does not
|
|
change the memory allocation of the program. Rather, it sets
|
|
a hard limit beyond which the process will kill itself and
|
|
be restarted. The default value of <literal>8GiB</literal>
|
|
is double the intended memory usage in the default
|
|
configuration (providing an emergency buffer to deal with
|
|
memory leaks or similar problems). It is not recommended to
|
|
decrease the value of this parameter below its default
|
|
value. It may be increased if you wish to allocate a very
|
|
large amount of storage engine memory or cache. In
|
|
particular, when the <literal>storageMemory</literal>
|
|
parameter is increased, the <literal>memory</literal>
|
|
parameter should be increased by an equal amount.
|
|
'';
|
|
};
|
|
|
|
storageMemory = mkOption {
|
|
type = types.string;
|
|
default = "1GiB";
|
|
description = ''
|
|
Maximum memory used for data storage. The default value is
|
|
<literal>1GiB</literal>. When specified without a unit,
|
|
<literal>MB</literal> is assumed. Clusters using the memory
|
|
storage engine will be restricted to using this amount of
|
|
memory per process for purposes of data storage. Memory
|
|
overhead associated with storing the data is counted against
|
|
this total. If you increase the
|
|
<literal>storageMemory</literal>, you should also increase
|
|
the <literal>memory</literal> parameter by the same amount.
|
|
'';
|
|
};
|
|
|
|
tls = mkOption {
|
|
default = null;
|
|
description = ''
|
|
FoundationDB Transport Security Layer (TLS) settings.
|
|
'';
|
|
|
|
type = types.nullOr (types.submodule ({
|
|
options = {
|
|
certificate = mkOption {
|
|
type = types.str;
|
|
description = ''
|
|
Path to the TLS certificate file. This certificate will
|
|
be offered to, and may be verified by, clients.
|
|
'';
|
|
};
|
|
|
|
key = mkOption {
|
|
type = types.str;
|
|
description = "Private key file for the certificate.";
|
|
};
|
|
|
|
allowedPeers = mkOption {
|
|
type = types.str;
|
|
default = "Check.Valid=1,Check.Unexpired=1";
|
|
description = ''
|
|
"Peer verification string". This may be used to adjust which TLS
|
|
client certificates a server will accept, as a form of user
|
|
authorization; for example, it may only accept TLS clients who
|
|
offer a certificate abiding by some locality or organization name.
|
|
|
|
For more information, please see the FoundationDB documentation.
|
|
'';
|
|
};
|
|
};
|
|
}));
|
|
};
|
|
|
|
locality = mkOption {
|
|
default = {
|
|
machineId = null;
|
|
zoneId = null;
|
|
datacenterId = null;
|
|
dataHall = null;
|
|
};
|
|
|
|
description = ''
|
|
FoundationDB locality settings.
|
|
'';
|
|
|
|
type = types.submodule ({
|
|
options = {
|
|
machineId = mkOption {
|
|
default = null;
|
|
type = types.nullOr types.str;
|
|
description = ''
|
|
Machine identifier key. All processes on a machine should share a
|
|
unique id. By default, processes on a machine determine a unique id to share.
|
|
This does not generally need to be set.
|
|
'';
|
|
};
|
|
|
|
zoneId = mkOption {
|
|
default = null;
|
|
type = types.nullOr types.str;
|
|
description = ''
|
|
Zone identifier key. Processes that share a zone id are
|
|
considered non-unique for the purposes of data replication.
|
|
If unset, defaults to machine id.
|
|
'';
|
|
};
|
|
|
|
datacenterId = mkOption {
|
|
default = null;
|
|
type = types.nullOr types.str;
|
|
description = ''
|
|
Data center identifier key. All processes physically located in a
|
|
data center should share the id. If you are depending on data
|
|
center based replication this must be set on all processes.
|
|
'';
|
|
};
|
|
|
|
dataHall = mkOption {
|
|
default = null;
|
|
type = types.nullOr types.str;
|
|
description = ''
|
|
Data hall identifier key. All processes physically located in a
|
|
data hall should share the id. If you are depending on data
|
|
hall based replication this must be set on all processes.
|
|
'';
|
|
};
|
|
};
|
|
});
|
|
};
|
|
|
|
extraReadWritePaths = mkOption {
|
|
default = [ ];
|
|
type = types.listOf types.path;
|
|
description = ''
|
|
An extra set of filesystem paths that FoundationDB can read to
|
|
and write from. By default, FoundationDB runs under a heavily
|
|
namespaced systemd environment without write access to most of
|
|
the filesystem outside of its data and log directories. By
|
|
adding paths to this list, the set of writeable paths will be
|
|
expanded. This is useful for allowing e.g. backups to local files,
|
|
which must be performed on behalf of the foundationdb service.
|
|
'';
|
|
};
|
|
|
|
pidfile = mkOption {
|
|
type = types.path;
|
|
default = "/run/foundationdb.pid";
|
|
description = "Path to pidfile for fdbmonitor.";
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
environment.systemPackages = [ pkg ];
|
|
|
|
users.users = optionalAttrs (cfg.user == "foundationdb") (singleton
|
|
{ name = "foundationdb";
|
|
description = "FoundationDB User";
|
|
uid = config.ids.uids.foundationdb;
|
|
group = cfg.group;
|
|
});
|
|
|
|
users.groups = optionalAttrs (cfg.group == "foundationdb") (singleton
|
|
{ name = "foundationdb";
|
|
gid = config.ids.gids.foundationdb;
|
|
});
|
|
|
|
networking.firewall.allowedTCPPortRanges = mkIf cfg.openFirewall
|
|
[ { from = cfg.listenPortStart;
|
|
to = (cfg.listenPortStart + cfg.serverProcesses) - 1;
|
|
}
|
|
];
|
|
|
|
systemd.services.foundationdb = {
|
|
description = "FoundationDB Service";
|
|
|
|
after = [ "network.target" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
unitConfig =
|
|
{ RequiresMountsFor = "${cfg.dataDir} ${cfg.logDir}";
|
|
};
|
|
|
|
serviceConfig =
|
|
let rwpaths = [ cfg.dataDir cfg.logDir cfg.pidfile "/etc/foundationdb" ]
|
|
++ cfg.extraReadWritePaths;
|
|
in
|
|
{ Type = "simple";
|
|
Restart = "always";
|
|
RestartSec = 5;
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
PIDFile = "${cfg.pidfile}";
|
|
|
|
PermissionsStartOnly = true; # setup needs root perms
|
|
TimeoutSec = 120; # give reasonable time to shut down
|
|
|
|
# Security options
|
|
NoNewPrivileges = true;
|
|
ProtectHome = true;
|
|
ProtectSystem = "strict";
|
|
ProtectKernelTunables = true;
|
|
ProtectControlGroups = true;
|
|
PrivateTmp = true;
|
|
PrivateDevices = true;
|
|
ReadWritePaths = lib.concatStringsSep " " (map (x: "-" + x) rwpaths);
|
|
};
|
|
|
|
path = [ pkg pkgs.coreutils ];
|
|
|
|
preStart = ''
|
|
rm -f ${cfg.pidfile} && \
|
|
touch ${cfg.pidfile} && \
|
|
chown -R ${cfg.user}:${cfg.group} ${cfg.pidfile}
|
|
|
|
for x in "${cfg.logDir}" "${cfg.dataDir}"; do
|
|
[ ! -d "$x" ] && mkdir -m 0700 -vp "$x";
|
|
chown -R ${cfg.user}:${cfg.group} "$x";
|
|
done
|
|
|
|
[ ! -d /etc/foundationdb ] && \
|
|
mkdir -m 0775 -vp /etc/foundationdb && \
|
|
chown -R ${cfg.user}:${cfg.group} "/etc/foundationdb"
|
|
|
|
if [ ! -f /etc/foundationdb/fdb.cluster ]; then
|
|
cf=/etc/foundationdb/fdb.cluster
|
|
desc=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c8)
|
|
rand=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c8)
|
|
echo ''${desc}:''${rand}@${initialIpAddr}:${builtins.toString cfg.listenPortStart} > $cf
|
|
chmod 0664 $cf && chown -R ${cfg.user}:${cfg.group} $cf
|
|
touch "${cfg.dataDir}/.first_startup"
|
|
fi
|
|
'';
|
|
|
|
script = "exec fdbmonitor --lockfile ${cfg.pidfile} --conffile ${configFile}";
|
|
|
|
postStart = ''
|
|
if [ -e "${cfg.dataDir}/.first_startup" ]; then
|
|
fdbcli --exec "configure new single memory"
|
|
rm -f "${cfg.dataDir}/.first_startup";
|
|
fi
|
|
'';
|
|
};
|
|
};
|
|
|
|
meta.doc = ./foundationdb.xml;
|
|
meta.maintainers = with lib.maintainers; [ thoughtpolice ];
|
|
}
|