mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-10 19:55:41 +03:00

After final improvements to the official formatter implementation, this commit now performs the first treewide reformat of Nix files using it. This is part of the implementation of RFC 166. Only "inactive" files are reformatted, meaning only files that aren't being touched by any PR with activity in the past 2 months. This is to avoid conflicts for PRs that might soon be merged. Later we can do a full treewide reformat to get the rest, which should not cause as many conflicts. A CI check has already been running for some time to ensure that new and already-formatted files are formatted, so the files being reformatted here should also stay formatted. This commit was automatically created and can be verified using nix-builda08b3a4d19
.tar.gz \ --argstr baseRevb32a094368
result/bin/apply-formatting $NIXPKGS_PATH
562 lines
19 KiB
Nix
562 lines
19 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
let
|
|
inherit (lib)
|
|
any
|
|
attrNames
|
|
attrValues
|
|
count
|
|
escapeShellArg
|
|
filterAttrs
|
|
flatten
|
|
flip
|
|
getExe
|
|
hasAttr
|
|
hasInfix
|
|
listToAttrs
|
|
literalExpression
|
|
mapAttrsToList
|
|
mkEnableOption
|
|
mkPackageOption
|
|
mkIf
|
|
mkOption
|
|
nameValuePair
|
|
optional
|
|
subtractLists
|
|
types
|
|
unique
|
|
;
|
|
|
|
format = pkgs.formats.json { };
|
|
cfg = config.services.influxdb2;
|
|
configFile = format.generate "config.json" cfg.settings;
|
|
|
|
validPermissions = [
|
|
"authorizations"
|
|
"buckets"
|
|
"dashboards"
|
|
"orgs"
|
|
"tasks"
|
|
"telegrafs"
|
|
"users"
|
|
"variables"
|
|
"secrets"
|
|
"labels"
|
|
"views"
|
|
"documents"
|
|
"notificationRules"
|
|
"notificationEndpoints"
|
|
"checks"
|
|
"dbrp"
|
|
"annotations"
|
|
"sources"
|
|
"scrapers"
|
|
"notebooks"
|
|
"remotes"
|
|
"replications"
|
|
];
|
|
|
|
# Determines whether at least one active api token is defined
|
|
anyAuthDefined = flip any (attrValues cfg.provision.organizations) (
|
|
o: o.present && flip any (attrValues o.auths) (a: a.present && a.tokenFile != null)
|
|
);
|
|
|
|
provisionState = pkgs.writeText "provision_state.json" (
|
|
builtins.toJSON {
|
|
inherit (cfg.provision) organizations users;
|
|
}
|
|
);
|
|
|
|
influxHost = "http://${
|
|
escapeShellArg (
|
|
if
|
|
!hasAttr "http-bind-address" cfg.settings || hasInfix "0.0.0.0" cfg.settings.http-bind-address
|
|
then
|
|
"localhost:8086"
|
|
else
|
|
cfg.settings.http-bind-address
|
|
)
|
|
}";
|
|
|
|
waitUntilServiceIsReady = pkgs.writeShellScript "wait-until-service-is-ready" ''
|
|
set -euo pipefail
|
|
export INFLUX_HOST=${influxHost}
|
|
count=0
|
|
while ! influx ping &>/dev/null; do
|
|
if [ "$count" -eq 300 ]; then
|
|
echo "Tried for 30 seconds, giving up..."
|
|
exit 1
|
|
fi
|
|
|
|
if ! kill -0 "$MAINPID"; then
|
|
echo "Main server died, giving up..."
|
|
exit 1
|
|
fi
|
|
|
|
sleep 0.1
|
|
count=$((count++))
|
|
done
|
|
'';
|
|
|
|
provisioningScript = pkgs.writeShellScript "post-start-provision" ''
|
|
set -euo pipefail
|
|
export INFLUX_HOST=${influxHost}
|
|
|
|
# Do the initial database setup. Pass /dev/null as configs-path to
|
|
# avoid saving the token as the active config.
|
|
if test -e "$STATE_DIRECTORY/.first_startup"; then
|
|
influx setup \
|
|
--configs-path /dev/null \
|
|
--org ${escapeShellArg cfg.provision.initialSetup.organization} \
|
|
--bucket ${escapeShellArg cfg.provision.initialSetup.bucket} \
|
|
--username ${escapeShellArg cfg.provision.initialSetup.username} \
|
|
--password "$(< "$CREDENTIALS_DIRECTORY/admin-password")" \
|
|
--token "$(< "$CREDENTIALS_DIRECTORY/admin-token")" \
|
|
--retention ${toString cfg.provision.initialSetup.retention}s \
|
|
--force >/dev/null
|
|
|
|
rm -f "$STATE_DIRECTORY/.first_startup"
|
|
fi
|
|
|
|
provision_result=$(${getExe pkgs.influxdb2-provision} ${provisionState} "$INFLUX_HOST" "$(< "$CREDENTIALS_DIRECTORY/admin-token")")
|
|
if [[ "$(jq '[.auths[] | select(.action == "created")] | length' <<< "$provision_result")" -gt 0 ]]; then
|
|
echo "Created at least one new token, queueing service restart so we can manipulate secrets"
|
|
touch "$STATE_DIRECTORY/.needs_restart"
|
|
fi
|
|
'';
|
|
|
|
restarterScript = pkgs.writeShellScript "post-start-restarter" ''
|
|
set -euo pipefail
|
|
if test -e "$STATE_DIRECTORY/.needs_restart"; then
|
|
rm -f "$STATE_DIRECTORY/.needs_restart"
|
|
/run/current-system/systemd/bin/systemctl restart influxdb2
|
|
fi
|
|
'';
|
|
|
|
organizationSubmodule = types.submodule (
|
|
organizationSubmod:
|
|
let
|
|
org = organizationSubmod.config._module.args.name;
|
|
in
|
|
{
|
|
options = {
|
|
present = mkOption {
|
|
description = "Whether to ensure that this organization is present or absent.";
|
|
type = types.bool;
|
|
default = true;
|
|
};
|
|
|
|
description = mkOption {
|
|
description = "Optional description for the organization.";
|
|
default = null;
|
|
type = types.nullOr types.str;
|
|
};
|
|
|
|
buckets = mkOption {
|
|
description = "Buckets to provision in this organization.";
|
|
default = { };
|
|
type = types.attrsOf (
|
|
types.submodule (
|
|
bucketSubmod:
|
|
let
|
|
bucket = bucketSubmod.config._module.args.name;
|
|
in
|
|
{
|
|
options = {
|
|
present = mkOption {
|
|
description = "Whether to ensure that this bucket is present or absent.";
|
|
type = types.bool;
|
|
default = true;
|
|
};
|
|
|
|
description = mkOption {
|
|
description = "Optional description for the bucket.";
|
|
default = null;
|
|
type = types.nullOr types.str;
|
|
};
|
|
|
|
retention = mkOption {
|
|
type = types.ints.unsigned;
|
|
default = 0;
|
|
description = "The duration in seconds for which the bucket will retain data (0 is infinite).";
|
|
};
|
|
};
|
|
}
|
|
)
|
|
);
|
|
};
|
|
|
|
auths = mkOption {
|
|
description = "API tokens to provision for the user in this organization.";
|
|
default = { };
|
|
type = types.attrsOf (
|
|
types.submodule (
|
|
authSubmod:
|
|
let
|
|
auth = authSubmod.config._module.args.name;
|
|
in
|
|
{
|
|
options = {
|
|
id = mkOption {
|
|
description = "A unique identifier for this authentication token. Since influx doesn't store names for tokens, this will be hashed and appended to the description to identify the token.";
|
|
readOnly = true;
|
|
default = builtins.substring 0 32 (builtins.hashString "sha256" "${org}:${auth}");
|
|
defaultText = "<a hash derived from org and name>";
|
|
type = types.str;
|
|
};
|
|
|
|
present = mkOption {
|
|
description = "Whether to ensure that this user is present or absent.";
|
|
type = types.bool;
|
|
default = true;
|
|
};
|
|
|
|
description = mkOption {
|
|
description = ''
|
|
Optional description for the API token.
|
|
Note that the actual token will always be created with a descriptionregardless
|
|
of whether this is given or not. The name is always added plus a unique suffix
|
|
to later identify the token to track whether it has already been created.
|
|
'';
|
|
default = null;
|
|
type = types.nullOr types.str;
|
|
};
|
|
|
|
tokenFile = mkOption {
|
|
type = types.nullOr types.path;
|
|
default = null;
|
|
description = "The token value. If not given, influx will automatically generate one.";
|
|
};
|
|
|
|
operator = mkOption {
|
|
description = "Grants all permissions in all organizations.";
|
|
default = false;
|
|
type = types.bool;
|
|
};
|
|
|
|
allAccess = mkOption {
|
|
description = "Grants all permissions in the associated organization.";
|
|
default = false;
|
|
type = types.bool;
|
|
};
|
|
|
|
readPermissions = mkOption {
|
|
description = ''
|
|
The read permissions to include for this token. Access is usually granted only
|
|
for resources in the associated organization.
|
|
|
|
Available permissions are `authorizations`, `buckets`, `dashboards`,
|
|
`orgs`, `tasks`, `telegrafs`, `users`, `variables`, `secrets`, `labels`, `views`,
|
|
`documents`, `notificationRules`, `notificationEndpoints`, `checks`, `dbrp`,
|
|
`annotations`, `sources`, `scrapers`, `notebooks`, `remotes`, `replications`.
|
|
|
|
Refer to `influx auth create --help` for a full list with descriptions.
|
|
|
|
`buckets` grants read access to all associated buckets. Use `readBuckets` to define
|
|
more granular access permissions.
|
|
'';
|
|
default = [ ];
|
|
type = types.listOf (types.enum validPermissions);
|
|
};
|
|
|
|
writePermissions = mkOption {
|
|
description = ''
|
|
The read permissions to include for this token. Access is usually granted only
|
|
for resources in the associated organization.
|
|
|
|
Available permissions are `authorizations`, `buckets`, `dashboards`,
|
|
`orgs`, `tasks`, `telegrafs`, `users`, `variables`, `secrets`, `labels`, `views`,
|
|
`documents`, `notificationRules`, `notificationEndpoints`, `checks`, `dbrp`,
|
|
`annotations`, `sources`, `scrapers`, `notebooks`, `remotes`, `replications`.
|
|
|
|
Refer to `influx auth create --help` for a full list with descriptions.
|
|
|
|
`buckets` grants write access to all associated buckets. Use `writeBuckets` to define
|
|
more granular access permissions.
|
|
'';
|
|
default = [ ];
|
|
type = types.listOf (types.enum validPermissions);
|
|
};
|
|
|
|
readBuckets = mkOption {
|
|
description = "The organization's buckets which should be allowed to be read";
|
|
default = [ ];
|
|
type = types.listOf types.str;
|
|
};
|
|
|
|
writeBuckets = mkOption {
|
|
description = "The organization's buckets which should be allowed to be written";
|
|
default = [ ];
|
|
type = types.listOf types.str;
|
|
};
|
|
};
|
|
}
|
|
)
|
|
);
|
|
};
|
|
};
|
|
}
|
|
);
|
|
in
|
|
{
|
|
options = {
|
|
services.influxdb2 = {
|
|
enable = mkEnableOption "the influxdb2 server";
|
|
|
|
package = mkPackageOption pkgs "influxdb2" { };
|
|
|
|
settings = mkOption {
|
|
default = { };
|
|
description = ''configuration options for influxdb2, see <https://docs.influxdata.com/influxdb/v2.0/reference/config-options> for details.'';
|
|
type = format.type;
|
|
};
|
|
|
|
provision = {
|
|
enable = mkEnableOption "initial database setup and provisioning";
|
|
|
|
initialSetup = {
|
|
organization = mkOption {
|
|
type = types.str;
|
|
example = "main";
|
|
description = "Primary organization name";
|
|
};
|
|
|
|
bucket = mkOption {
|
|
type = types.str;
|
|
example = "example";
|
|
description = "Primary bucket name";
|
|
};
|
|
|
|
username = mkOption {
|
|
type = types.str;
|
|
default = "admin";
|
|
description = "Primary username";
|
|
};
|
|
|
|
retention = mkOption {
|
|
type = types.ints.unsigned;
|
|
default = 0;
|
|
description = "The duration in seconds for which the bucket will retain data (0 is infinite).";
|
|
};
|
|
|
|
passwordFile = mkOption {
|
|
type = types.path;
|
|
description = "Password for primary user. Don't use a file from the nix store!";
|
|
};
|
|
|
|
tokenFile = mkOption {
|
|
type = types.path;
|
|
description = "API Token to set for the admin user. Don't use a file from the nix store!";
|
|
};
|
|
};
|
|
|
|
organizations = mkOption {
|
|
description = "Organizations to provision.";
|
|
example = literalExpression ''
|
|
{
|
|
myorg = {
|
|
description = "My organization";
|
|
buckets.mybucket = {
|
|
description = "My bucket";
|
|
retention = 31536000; # 1 year
|
|
};
|
|
auths.mytoken = {
|
|
readBuckets = ["mybucket"];
|
|
tokenFile = "/run/secrets/mytoken";
|
|
};
|
|
};
|
|
}
|
|
'';
|
|
default = { };
|
|
type = types.attrsOf organizationSubmodule;
|
|
};
|
|
|
|
users = mkOption {
|
|
description = "Users to provision.";
|
|
default = { };
|
|
example = literalExpression ''
|
|
{
|
|
# admin = {}; /* The initialSetup.username will automatically be added. */
|
|
myuser.passwordFile = "/run/secrets/myuser_password";
|
|
}
|
|
'';
|
|
type = types.attrsOf (
|
|
types.submodule (
|
|
userSubmod:
|
|
let
|
|
user = userSubmod.config._module.args.name;
|
|
org = userSubmod.config.org;
|
|
in
|
|
{
|
|
options = {
|
|
present = mkOption {
|
|
description = "Whether to ensure that this user is present or absent.";
|
|
type = types.bool;
|
|
default = true;
|
|
};
|
|
|
|
passwordFile = mkOption {
|
|
description = "Password for the user. If unset, the user will not be able to log in until a password is set by an operator! Don't use a file from the nix store!";
|
|
default = null;
|
|
type = types.nullOr types.path;
|
|
};
|
|
};
|
|
}
|
|
)
|
|
);
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
assertions =
|
|
[
|
|
{
|
|
assertion = !(hasAttr "bolt-path" cfg.settings) && !(hasAttr "engine-path" cfg.settings);
|
|
message = "services.influxdb2.config: bolt-path and engine-path should not be set as they are managed by systemd";
|
|
}
|
|
]
|
|
++ flatten (
|
|
flip mapAttrsToList cfg.provision.organizations (
|
|
orgName: org:
|
|
flip mapAttrsToList org.auths (
|
|
authName: auth: [
|
|
{
|
|
assertion =
|
|
1 == count (x: x) [
|
|
auth.operator
|
|
auth.allAccess
|
|
(
|
|
auth.readPermissions != [ ]
|
|
|| auth.writePermissions != [ ]
|
|
|| auth.readBuckets != [ ]
|
|
|| auth.writeBuckets != [ ]
|
|
)
|
|
];
|
|
message = "influxdb2: provision.organizations.${orgName}.auths.${authName}: The `operator` and `allAccess` options are mutually exclusive with each other and the granular permission settings.";
|
|
}
|
|
(
|
|
let
|
|
unknownBuckets = subtractLists (attrNames org.buckets) auth.readBuckets;
|
|
in
|
|
{
|
|
assertion = unknownBuckets == [ ];
|
|
message = "influxdb2: provision.organizations.${orgName}.auths.${authName}: Refers to invalid buckets in readBuckets: ${toString unknownBuckets}";
|
|
}
|
|
)
|
|
(
|
|
let
|
|
unknownBuckets = subtractLists (attrNames org.buckets) auth.writeBuckets;
|
|
in
|
|
{
|
|
assertion = unknownBuckets == [ ];
|
|
message = "influxdb2: provision.organizations.${orgName}.auths.${authName}: Refers to invalid buckets in writeBuckets: ${toString unknownBuckets}";
|
|
}
|
|
)
|
|
]
|
|
)
|
|
)
|
|
);
|
|
|
|
services.influxdb2.provision = mkIf cfg.provision.enable {
|
|
organizations.${cfg.provision.initialSetup.organization} = {
|
|
buckets.${cfg.provision.initialSetup.bucket} = {
|
|
inherit (cfg.provision.initialSetup) retention;
|
|
};
|
|
};
|
|
users.${cfg.provision.initialSetup.username} = {
|
|
inherit (cfg.provision.initialSetup) passwordFile;
|
|
};
|
|
};
|
|
|
|
systemd.services.influxdb2 = {
|
|
description = "InfluxDB is an open-source, distributed, time series database";
|
|
documentation = [ "https://docs.influxdata.com/influxdb/" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "network.target" ];
|
|
environment = {
|
|
INFLUXD_CONFIG_PATH = configFile;
|
|
ZONEINFO = "${pkgs.tzdata}/share/zoneinfo";
|
|
};
|
|
serviceConfig = {
|
|
Type = "exec"; # When credentials are used with systemd before v257 this is necessary to make the service start reliably (see systemd/systemd#33953)
|
|
ExecStart = "${cfg.package}/bin/influxd --bolt-path \${STATE_DIRECTORY}/influxd.bolt --engine-path \${STATE_DIRECTORY}/engine";
|
|
StateDirectory = "influxdb2";
|
|
User = "influxdb2";
|
|
Group = "influxdb2";
|
|
CapabilityBoundingSet = "";
|
|
SystemCallFilter = "@system-service";
|
|
LimitNOFILE = 65536;
|
|
KillMode = "control-group";
|
|
Restart = "on-failure";
|
|
LoadCredential = mkIf cfg.provision.enable [
|
|
"admin-password:${cfg.provision.initialSetup.passwordFile}"
|
|
"admin-token:${cfg.provision.initialSetup.tokenFile}"
|
|
];
|
|
|
|
ExecStartPost =
|
|
[
|
|
waitUntilServiceIsReady
|
|
]
|
|
++ (lib.optionals cfg.provision.enable (
|
|
[ provisioningScript ]
|
|
++
|
|
# Only the restarter runs with elevated privileges
|
|
optional anyAuthDefined "+${restarterScript}"
|
|
));
|
|
};
|
|
|
|
path = [
|
|
pkgs.influxdb2-cli
|
|
pkgs.jq
|
|
];
|
|
|
|
# Mark if this is the first startup so postStart can do the initial setup.
|
|
# Also extract any token secret mappings and apply them if this isn't the first start.
|
|
preStart =
|
|
let
|
|
tokenPaths = listToAttrs (
|
|
flatten
|
|
# For all organizations
|
|
(
|
|
flip mapAttrsToList cfg.provision.organizations
|
|
# For each contained token that has a token file
|
|
(
|
|
_: org:
|
|
flip mapAttrsToList (filterAttrs (_: x: x.tokenFile != null) org.auths)
|
|
# Collect id -> tokenFile for the mapping
|
|
(_: auth: nameValuePair auth.id auth.tokenFile)
|
|
)
|
|
)
|
|
);
|
|
tokenMappings = pkgs.writeText "token_mappings.json" (builtins.toJSON tokenPaths);
|
|
in
|
|
mkIf cfg.provision.enable ''
|
|
if ! test -e "$STATE_DIRECTORY/influxd.bolt"; then
|
|
touch "$STATE_DIRECTORY/.first_startup"
|
|
else
|
|
# Manipulate provisioned api tokens if necessary
|
|
${getExe pkgs.influxdb2-token-manipulator} "$STATE_DIRECTORY/influxd.bolt" ${tokenMappings}
|
|
fi
|
|
'';
|
|
};
|
|
|
|
users.extraUsers.influxdb2 = {
|
|
isSystemUser = true;
|
|
group = "influxdb2";
|
|
};
|
|
|
|
users.extraGroups.influxdb2 = { };
|
|
};
|
|
|
|
meta.maintainers = with lib.maintainers; [
|
|
nickcao
|
|
oddlama
|
|
];
|
|
}
|