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