grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / misc / geoipupdate.nix
blobf46bf7b394feaf0cbd06b5b6b3f398b37033c7ae
1 { config, lib, pkgs, ... }:
3 let
4   cfg = config.services.geoipupdate;
5   inherit (builtins) isAttrs isString isInt isList typeOf hashString;
6 in
8   imports = [
9     (lib.mkRemovedOptionModule [ "services" "geoip-updater" ] "services.geoip-updater has been removed, use services.geoipupdate instead.")
10   ];
12   options = {
13     services.geoipupdate = {
14       enable = lib.mkEnableOption ''
15         periodic downloading of GeoIP databases using geoipupdate
16       '';
18       interval = lib.mkOption {
19         type = lib.types.str;
20         default = "weekly";
21         description = ''
22           Update the GeoIP databases at this time / interval.
23           The format is described in
24           {manpage}`systemd.time(7)`.
25         '';
26       };
28       settings = lib.mkOption {
29         example = lib.literalExpression ''
30           {
31             AccountID = 200001;
32             DatabaseDirectory = "/var/lib/GeoIP";
33             LicenseKey = { _secret = "/run/keys/maxmind_license_key"; };
34             Proxy = "10.0.0.10:8888";
35             ProxyUserPassword = { _secret = "/run/keys/proxy_pass"; };
36           }
37         '';
38         description = ''
39           geoipupdate configuration options. See
40           <https://github.com/maxmind/geoipupdate/blob/main/doc/GeoIP.conf.md>
41           for a full list of available options.
43           Settings containing secret data should be set to an
44           attribute set containing the attribute
45           `_secret` - a string pointing to a file
46           containing the value the option should be set to. See the
47           example to get a better picture of this: in the resulting
48           {file}`GeoIP.conf` file, the
49           `ProxyUserPassword` key will be set to the
50           contents of the
51           {file}`/run/keys/proxy_pass` file.
52         '';
53         type = lib.types.submodule {
54           freeformType =
55             with lib.types;
56             let
57               type = oneOf [str int bool];
58             in
59               attrsOf (either type (listOf type));
61           options = {
63             AccountID = lib.mkOption {
64               type = lib.types.int;
65               description = ''
66                 Your MaxMind account ID.
67               '';
68             };
70             EditionIDs = lib.mkOption {
71               type = with lib.types; listOf (either str int);
72               example = [
73                 "GeoLite2-ASN"
74                 "GeoLite2-City"
75                 "GeoLite2-Country"
76               ];
77               description = ''
78                 List of database edition IDs. This includes new string
79                 IDs like `GeoIP2-City` and old
80                 numeric IDs like `106`.
81               '';
82             };
84             LicenseKey = lib.mkOption {
85               type = with lib.types; either path (attrsOf path);
86               description = ''
87                 A file containing the MaxMind license key.
89                 Always handled as a secret whether the value is
90                 wrapped in a `{ _secret = ...; }`
91                 attrset or not (refer to [](#opt-services.geoipupdate.settings) for
92                 details).
93               '';
94               apply = x: if isAttrs x then x else { _secret = x; };
95             };
97             DatabaseDirectory = lib.mkOption {
98               type = lib.types.path;
99               default = "/var/lib/GeoIP";
100               example = "/run/GeoIP";
101               description = ''
102                 The directory to store the database files in. The
103                 directory will be automatically created, the owner
104                 changed to `geoip` and permissions
105                 set to world readable. This applies if the directory
106                 already exists as well, so don't use a directory with
107                 sensitive contents.
108               '';
109             };
111           };
112         };
113       };
114     };
116   };
118   config = lib.mkIf cfg.enable {
120     services.geoipupdate.settings = {
121       LockFile = "/run/geoipupdate/.lock";
122     };
124     systemd.services.geoipupdate-create-db-dir = {
125       serviceConfig.Type = "oneshot";
126       script = ''
127         set -o errexit -o pipefail -o nounset -o errtrace
128         shopt -s inherit_errexit
130         mkdir -p ${cfg.settings.DatabaseDirectory}
131         chmod 0755 ${cfg.settings.DatabaseDirectory}
132       '';
133     };
135     systemd.services.geoipupdate = {
136       description = "GeoIP Updater";
137       requires = [ "geoipupdate-create-db-dir.service" ];
138       after = [
139         "geoipupdate-create-db-dir.service"
140         "network-online.target"
141         "nss-lookup.target"
142       ];
143       path = [ pkgs.replace-secret ];
144       wants = [ "network-online.target" ];
145       startAt = cfg.interval;
146       serviceConfig = {
147         ExecStartPre =
148           let
149             isSecret = v: isAttrs v && v ? _secret && isString v._secret;
150             geoipupdateKeyValue = lib.generators.toKeyValue {
151               mkKeyValue = lib.flip lib.generators.mkKeyValueDefault " " rec {
152                 mkValueString = v:
153                   if isInt           v then toString v
154                   else if isString   v then v
155                   else if true  ==   v then "1"
156                   else if false ==   v then "0"
157                   else if isList     v then lib.concatMapStringsSep " " mkValueString v
158                   else if isSecret   v then hashString "sha256" v._secret
159                   else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
160               };
161             };
162             secretPaths = lib.catAttrs "_secret" (lib.collect isSecret cfg.settings);
163             mkSecretReplacement = file: ''
164               replace-secret ${lib.escapeShellArgs [ (hashString "sha256" file) file "/run/geoipupdate/GeoIP.conf" ]}
165             '';
166             secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
168             geoipupdateConf = pkgs.writeText "geoipupdate.conf" (geoipupdateKeyValue cfg.settings);
170             script = ''
171               set -o errexit -o pipefail -o nounset -o errtrace
172               shopt -s inherit_errexit
174               chown geoip "${cfg.settings.DatabaseDirectory}"
176               cp ${geoipupdateConf} /run/geoipupdate/GeoIP.conf
177               ${secretReplacements}
178             '';
179           in
180             "+${pkgs.writeShellScript "start-pre-full-privileges" script}";
181         ExecStart = "${pkgs.geoipupdate}/bin/geoipupdate -f /run/geoipupdate/GeoIP.conf";
182         User = "geoip";
183         DynamicUser = true;
184         ReadWritePaths = cfg.settings.DatabaseDirectory;
185         RuntimeDirectory = "geoipupdate";
186         RuntimeDirectoryMode = "0700";
187         CapabilityBoundingSet = "";
188         PrivateDevices = true;
189         PrivateMounts = true;
190         PrivateUsers = true;
191         ProtectClock = true;
192         ProtectControlGroups = true;
193         ProtectHome = true;
194         ProtectHostname = true;
195         ProtectKernelLogs = true;
196         ProtectKernelModules = true;
197         ProtectKernelTunables = true;
198         ProtectProc = "invisible";
199         ProcSubset = "pid";
200         SystemCallFilter = [ "@system-service" "~@privileged" ];
201         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
202         RestrictRealtime = true;
203         RestrictNamespaces = true;
204         MemoryDenyWriteExecute = true;
205         LockPersonality = true;
206         SystemCallArchitectures = "native";
207       };
208     };
210     systemd.timers.geoipupdate-initial-run = {
211       wantedBy = [ "timers.target" ];
212       unitConfig.ConditionPathExists = "!${cfg.settings.DatabaseDirectory}";
213       timerConfig = {
214         Unit = "geoipupdate.service";
215         OnActiveSec = 0;
216       };
217     };
218   };
220   meta.maintainers = [ lib.maintainers.talyz ];