at 23.11-pre 7.7 kB view raw
1{ config, lib, pkgs, ... }: 2 3let 4 cfg = config.services.geoipupdate; 5 inherit (builtins) isAttrs isString isInt isList typeOf hashString; 6in 7{ 8 imports = [ 9 (lib.mkRemovedOptionModule [ "services" "geoip-updater" ] "services.geoip-updater has been removed, use services.geoipupdate instead.") 10 ]; 11 12 options = { 13 services.geoipupdate = { 14 enable = lib.mkEnableOption (lib.mdDoc '' 15 periodic downloading of GeoIP databases using geoipupdate. 16 ''); 17 18 interval = lib.mkOption { 19 type = lib.types.str; 20 default = "weekly"; 21 description = lib.mdDoc '' 22 Update the GeoIP databases at this time / interval. 23 The format is described in 24 {manpage}`systemd.time(7)`. 25 ''; 26 }; 27 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 = lib.mdDoc '' 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. 42 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)); 60 61 options = { 62 63 AccountID = lib.mkOption { 64 type = lib.types.int; 65 description = lib.mdDoc '' 66 Your MaxMind account ID. 67 ''; 68 }; 69 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 = lib.mdDoc '' 78 List of database edition IDs. This includes new string 79 IDs like `GeoIP2-City` and old 80 numeric IDs like `106`. 81 ''; 82 }; 83 84 LicenseKey = lib.mkOption { 85 type = with lib.types; either path (attrsOf path); 86 description = lib.mdDoc '' 87 A file containing the MaxMind license key. 88 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 }; 96 97 DatabaseDirectory = lib.mkOption { 98 type = lib.types.path; 99 default = "/var/lib/GeoIP"; 100 example = "/run/GeoIP"; 101 description = lib.mdDoc '' 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 }; 110 111 }; 112 }; 113 }; 114 }; 115 116 }; 117 118 config = lib.mkIf cfg.enable { 119 120 services.geoipupdate.settings = { 121 LockFile = "/run/geoipupdate/.lock"; 122 }; 123 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 129 130 mkdir -p ${cfg.settings.DatabaseDirectory} 131 chmod 0755 ${cfg.settings.DatabaseDirectory} 132 ''; 133 }; 134 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; 167 168 geoipupdateConf = pkgs.writeText "geoipupdate.conf" (geoipupdateKeyValue cfg.settings); 169 170 script = '' 171 set -o errexit -o pipefail -o nounset -o errtrace 172 shopt -s inherit_errexit 173 174 chown geoip "${cfg.settings.DatabaseDirectory}" 175 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 }; 209 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 }; 219 220 meta.maintainers = [ lib.maintainers.talyz ]; 221}