nixpkgs-suyu/nixos/modules/services/misc/geoipupdate.nix
pennae 2e751c0772 treewide: automatically md-convert option descriptions
the conversion procedure is simple:

 - find all things that look like options, ie calls to either `mkOption`
   or `lib.mkOption` that take an attrset. remember the attrset as the
   option
 - for all options, find a `description` attribute who's value is not a
   call to `mdDoc` or `lib.mdDoc`
 - textually convert the entire value of the attribute to MD with a few
   simple regexes (the set from mdize-module.sh)
 - if the change produced a change in the manual output, discard
 - if the change kept the manual unchanged, add some text to the
   description to make sure we've actually found an option. if the
   manual changes this time, keep the converted description

this procedure converts 80% of nixos options to markdown. around 2000
options remain to be inspected, but most of those fail the "does not
change the manual output check": currently the MD conversion process
does not faithfully convert docbook tags like <code> and <package>, so
any option using such tags will not be converted at all.
2022-07-30 15:16:34 +02:00

226 lines
7.8 KiB
Nix

{ config, lib, pkgs, ... }:
let
cfg = config.services.geoipupdate;
inherit (builtins) isAttrs isString isInt isList typeOf hashString;
in
{
imports = [
(lib.mkRemovedOptionModule [ "services" "geoip-updater" ] "services.geoip-updater has been removed, use services.geoipupdate instead.")
];
options = {
services.geoipupdate = {
enable = lib.mkEnableOption ''
periodic downloading of GeoIP databases using
<productname>geoipupdate</productname>.
'';
interval = lib.mkOption {
type = lib.types.str;
default = "weekly";
description = ''
Update the GeoIP databases at this time / interval.
The format is described in
<citerefentry><refentrytitle>systemd.time</refentrytitle>
<manvolnum>7</manvolnum></citerefentry>.
'';
};
settings = lib.mkOption {
example = lib.literalExpression ''
{
AccountID = 200001;
DatabaseDirectory = "/var/lib/GeoIP";
LicenseKey = { _secret = "/run/keys/maxmind_license_key"; };
Proxy = "10.0.0.10:8888";
ProxyUserPassword = { _secret = "/run/keys/proxy_pass"; };
}
'';
description = ''
<productname>geoipupdate</productname> configuration
options. See
<link xlink:href="https://github.com/maxmind/geoipupdate/blob/main/doc/GeoIP.conf.md" />
for a full list of available options.
Settings containing secret data should be set to an
attribute set containing the attribute
<literal>_secret</literal> - a string pointing to a file
containing the value the option should be set to. See the
example to get a better picture of this: in the resulting
<filename>GeoIP.conf</filename> file, the
<literal>ProxyUserPassword</literal> key will be set to the
contents of the
<filename>/run/keys/proxy_pass</filename> file.
'';
type = lib.types.submodule {
freeformType =
with lib.types;
let
type = oneOf [str int bool];
in
attrsOf (either type (listOf type));
options = {
AccountID = lib.mkOption {
type = lib.types.int;
description = lib.mdDoc ''
Your MaxMind account ID.
'';
};
EditionIDs = lib.mkOption {
type = with lib.types; listOf (either str int);
example = [
"GeoLite2-ASN"
"GeoLite2-City"
"GeoLite2-Country"
];
description = lib.mdDoc ''
List of database edition IDs. This includes new string
IDs like `GeoIP2-City` and old
numeric IDs like `106`.
'';
};
LicenseKey = lib.mkOption {
type = with lib.types; either path (attrsOf path);
description = ''
A file containing the
<productname>MaxMind</productname> license key.
Always handled as a secret whether the value is
wrapped in a <literal>{ _secret = ...; }</literal>
attrset or not (refer to <xref
linkend="opt-services.geoipupdate.settings" /> for
details).
'';
apply = x: if isAttrs x then x else { _secret = x; };
};
DatabaseDirectory = lib.mkOption {
type = lib.types.path;
default = "/var/lib/GeoIP";
example = "/run/GeoIP";
description = lib.mdDoc ''
The directory to store the database files in. The
directory will be automatically created, the owner
changed to `geoip` and permissions
set to world readable. This applies if the directory
already exists as well, so don't use a directory with
sensitive contents.
'';
};
};
};
};
};
};
config = lib.mkIf cfg.enable {
services.geoipupdate.settings = {
LockFile = "/run/geoipupdate/.lock";
};
systemd.services.geoipupdate-create-db-dir = {
serviceConfig.Type = "oneshot";
script = ''
set -o errexit -o pipefail -o nounset -o errtrace
shopt -s inherit_errexit
mkdir -p ${cfg.settings.DatabaseDirectory}
chmod 0755 ${cfg.settings.DatabaseDirectory}
'';
};
systemd.services.geoipupdate = {
description = "GeoIP Updater";
requires = [ "geoipupdate-create-db-dir.service" ];
after = [
"geoipupdate-create-db-dir.service"
"network-online.target"
"nss-lookup.target"
];
path = [ pkgs.replace-secret ];
wants = [ "network-online.target" ];
startAt = cfg.interval;
serviceConfig = {
ExecStartPre =
let
isSecret = v: isAttrs v && v ? _secret && isString v._secret;
geoipupdateKeyValue = lib.generators.toKeyValue {
mkKeyValue = lib.flip lib.generators.mkKeyValueDefault " " rec {
mkValueString = v:
if isInt v then toString v
else if isString v then v
else if true == v then "1"
else if false == v then "0"
else if isList v then lib.concatMapStringsSep " " mkValueString v
else if isSecret v then hashString "sha256" v._secret
else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
};
};
secretPaths = lib.catAttrs "_secret" (lib.collect isSecret cfg.settings);
mkSecretReplacement = file: ''
replace-secret ${lib.escapeShellArgs [ (hashString "sha256" file) file "/run/geoipupdate/GeoIP.conf" ]}
'';
secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
geoipupdateConf = pkgs.writeText "geoipupdate.conf" (geoipupdateKeyValue cfg.settings);
script = ''
set -o errexit -o pipefail -o nounset -o errtrace
shopt -s inherit_errexit
chown geoip "${cfg.settings.DatabaseDirectory}"
cp ${geoipupdateConf} /run/geoipupdate/GeoIP.conf
${secretReplacements}
'';
in
"+${pkgs.writeShellScript "start-pre-full-privileges" script}";
ExecStart = "${pkgs.geoipupdate}/bin/geoipupdate -f /run/geoipupdate/GeoIP.conf";
User = "geoip";
DynamicUser = true;
ReadWritePaths = cfg.settings.DatabaseDirectory;
RuntimeDirectory = "geoipupdate";
RuntimeDirectoryMode = 0700;
CapabilityBoundingSet = "";
PrivateDevices = true;
PrivateMounts = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProcSubset = "pid";
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
RestrictRealtime = true;
RestrictNamespaces = true;
MemoryDenyWriteExecute = true;
LockPersonality = true;
SystemCallArchitectures = "native";
};
};
systemd.timers.geoipupdate-initial-run = {
wantedBy = [ "timers.target" ];
unitConfig.ConditionPathExists = "!${cfg.settings.DatabaseDirectory}";
timerConfig = {
Unit = "geoipupdate.service";
OnActiveSec = 0;
};
};
};
meta.maintainers = [ lib.maintainers.talyz ];
}