aboutsummaryrefslogtreecommitdiff
path: root/nixpkgs/nixos/modules/services/misc/geoip-updater.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/modules/services/misc/geoip-updater.nix')
-rw-r--r--nixpkgs/nixos/modules/services/misc/geoip-updater.nix306
1 files changed, 306 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/services/misc/geoip-updater.nix b/nixpkgs/nixos/modules/services/misc/geoip-updater.nix
new file mode 100644
index 00000000000..baf0a8d73d1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/geoip-updater.nix
@@ -0,0 +1,306 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.geoip-updater;
+
+ dbBaseUrl = "https://geolite.maxmind.com/download/geoip/database";
+
+ randomizedTimerDelaySec = "3600";
+
+ # Use writeScriptBin instead of writeScript, so that argv[0] (logged to the
+ # journal) doesn't include the long nix store path hash. (Prefixing the
+ # ExecStart= command with '@' doesn't work because we start a shell (new
+ # process) that creates a new argv[0].)
+ geoip-updater = pkgs.writeScriptBin "geoip-updater" ''
+ #!${pkgs.runtimeShell}
+ skipExisting=0
+ debug()
+ {
+ echo "<7>$@"
+ }
+ info()
+ {
+ echo "<6>$@"
+ }
+ error()
+ {
+ echo "<3>$@"
+ }
+ die()
+ {
+ error "$@"
+ exit 1
+ }
+ waitNetworkOnline()
+ {
+ ret=1
+ for i in $(seq 6); do
+ curl_out=$("${pkgs.curl.bin}/bin/curl" \
+ --silent --fail --show-error --max-time 60 "${dbBaseUrl}" 2>&1)
+ if [ $? -eq 0 ]; then
+ debug "Server is reachable (try $i)"
+ ret=0
+ break
+ else
+ debug "Server is unreachable (try $i): $curl_out"
+ sleep 10
+ fi
+ done
+ return $ret
+ }
+ dbFnameTmp()
+ {
+ dburl=$1
+ echo "${cfg.databaseDir}/.$(basename "$dburl")"
+ }
+ dbFnameTmpDecompressed()
+ {
+ dburl=$1
+ echo "${cfg.databaseDir}/.$(basename "$dburl")" | sed 's/\.\(gz\|xz\)$//'
+ }
+ dbFname()
+ {
+ dburl=$1
+ echo "${cfg.databaseDir}/$(basename "$dburl")" | sed 's/\.\(gz\|xz\)$//'
+ }
+ downloadDb()
+ {
+ dburl=$1
+ curl_out=$("${pkgs.curl.bin}/bin/curl" \
+ --silent --fail --show-error --max-time 900 -L -o "$(dbFnameTmp "$dburl")" "$dburl" 2>&1)
+ if [ $? -ne 0 ]; then
+ error "Failed to download $dburl: $curl_out"
+ return 1
+ fi
+ }
+ decompressDb()
+ {
+ fn=$(dbFnameTmp "$1")
+ ret=0
+ case "$fn" in
+ *.gz)
+ cmd_out=$("${pkgs.gzip}/bin/gzip" --decompress --force "$fn" 2>&1)
+ ;;
+ *.xz)
+ cmd_out=$("${pkgs.xz.bin}/bin/xz" --decompress --force "$fn" 2>&1)
+ ;;
+ *)
+ cmd_out=$(echo "File \"$fn\" is neither a .gz nor .xz file")
+ false
+ ;;
+ esac
+ if [ $? -ne 0 ]; then
+ error "$cmd_out"
+ ret=1
+ fi
+ }
+ atomicRename()
+ {
+ dburl=$1
+ mv "$(dbFnameTmpDecompressed "$dburl")" "$(dbFname "$dburl")"
+ }
+ removeIfNotInConfig()
+ {
+ # Arg 1 is the full path of an installed DB.
+ # If the corresponding database is not specified in the NixOS config we
+ # remove it.
+ db=$1
+ for cdb in ${lib.concatStringsSep " " cfg.databases}; do
+ confDb=$(echo "$cdb" | sed 's/\.\(gz\|xz\)$//')
+ if [ "$(basename "$db")" = "$(basename "$confDb")" ]; then
+ return 0
+ fi
+ done
+ rm "$db"
+ if [ $? -eq 0 ]; then
+ debug "Removed $(basename "$db") (not listed in services.geoip-updater.databases)"
+ else
+ error "Failed to remove $db"
+ fi
+ }
+ removeUnspecifiedDbs()
+ {
+ for f in "${cfg.databaseDir}/"*; do
+ test -f "$f" || continue
+ case "$f" in
+ *.dat|*.mmdb|*.csv)
+ removeIfNotInConfig "$f"
+ ;;
+ *)
+ debug "Not removing \"$f\" (unknown file extension)"
+ ;;
+ esac
+ done
+ }
+ downloadAndInstall()
+ {
+ dburl=$1
+ if [ "$skipExisting" -eq 1 -a -f "$(dbFname "$dburl")" ]; then
+ debug "Skipping existing file: $(dbFname "$dburl")"
+ return 0
+ fi
+ downloadDb "$dburl" || return 1
+ decompressDb "$dburl" || return 1
+ atomicRename "$dburl" || return 1
+ info "Updated $(basename "$(dbFname "$dburl")")"
+ }
+ for arg in "$@"; do
+ case "$arg" in
+ --skip-existing)
+ skipExisting=1
+ info "Option --skip-existing is set: not updating existing databases"
+ ;;
+ *)
+ error "Unknown argument: $arg";;
+ esac
+ done
+ waitNetworkOnline || die "Network is down (${dbBaseUrl} is unreachable)"
+ test -d "${cfg.databaseDir}" || die "Database directory (${cfg.databaseDir}) doesn't exist"
+ debug "Starting update of GeoIP databases in ${cfg.databaseDir}"
+ all_ret=0
+ for db in ${lib.concatStringsSep " \\\n " cfg.databases}; do
+ downloadAndInstall "${dbBaseUrl}/$db" || all_ret=1
+ done
+ removeUnspecifiedDbs || all_ret=1
+ if [ $all_ret -eq 0 ]; then
+ info "Completed GeoIP database update in ${cfg.databaseDir}"
+ else
+ error "Completed GeoIP database update in ${cfg.databaseDir}, with error(s)"
+ fi
+ # Hack to work around systemd journal race:
+ # https://github.com/systemd/systemd/issues/2913
+ sleep 2
+ exit $all_ret
+ '';
+
+in
+
+{
+ options = {
+ services.geoip-updater = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable periodic downloading of GeoIP databases from
+ maxmind.com. You might want to enable this if you, for instance, use
+ ntopng or Wireshark.
+ '';
+ };
+
+ interval = mkOption {
+ type = 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>.
+ To prevent load spikes on maxmind.com, the timer interval is
+ randomized by an additional delay of ${randomizedTimerDelaySec}
+ seconds. Setting a shorter interval than this is not recommended.
+ '';
+ };
+
+ databaseDir = mkOption {
+ type = types.path;
+ default = "/var/lib/geoip-databases";
+ description = ''
+ Directory that will contain GeoIP databases.
+ '';
+ };
+
+ databases = mkOption {
+ type = types.listOf types.str;
+ default = [
+ "GeoLiteCountry/GeoIP.dat.gz"
+ "GeoIPv6.dat.gz"
+ "GeoLiteCity.dat.xz"
+ "GeoLiteCityv6-beta/GeoLiteCityv6.dat.gz"
+ "asnum/GeoIPASNum.dat.gz"
+ "asnum/GeoIPASNumv6.dat.gz"
+ "GeoLite2-Country.mmdb.gz"
+ "GeoLite2-City.mmdb.gz"
+ ];
+ description = ''
+ Which GeoIP databases to update. The full URL is ${dbBaseUrl}/ +
+ <literal>the_database</literal>.
+ '';
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = (builtins.filter
+ (x: builtins.match ".*\\.(gz|xz)$" x == null) cfg.databases) == [];
+ message = ''
+ services.geoip-updater.databases supports only .gz and .xz databases.
+
+ Current value:
+ ${toString cfg.databases}
+
+ Offending element(s):
+ ${toString (builtins.filter (x: builtins.match ".*\\.(gz|xz)$" x == null) cfg.databases)};
+ '';
+ }
+ ];
+
+ users.users.geoip = {
+ group = "root";
+ description = "GeoIP database updater";
+ uid = config.ids.uids.geoip;
+ };
+
+ systemd.timers.geoip-updater =
+ { description = "GeoIP Updater Timer";
+ partOf = [ "geoip-updater.service" ];
+ wantedBy = [ "timers.target" ];
+ timerConfig.OnCalendar = cfg.interval;
+ timerConfig.Persistent = "true";
+ timerConfig.RandomizedDelaySec = randomizedTimerDelaySec;
+ };
+
+ systemd.services.geoip-updater = {
+ description = "GeoIP Updater";
+ after = [ "network-online.target" "nss-lookup.target" ];
+ wants = [ "network-online.target" ];
+ preStart = ''
+ mkdir -p "${cfg.databaseDir}"
+ chmod 755 "${cfg.databaseDir}"
+ chown geoip:root "${cfg.databaseDir}"
+ '';
+ serviceConfig = {
+ ExecStart = "${geoip-updater}/bin/geoip-updater";
+ User = "geoip";
+ PermissionsStartOnly = true;
+ };
+ };
+
+ systemd.services.geoip-updater-setup = {
+ description = "GeoIP Updater Setup";
+ after = [ "network-online.target" "nss-lookup.target" ];
+ wants = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+ conflicts = [ "geoip-updater.service" ];
+ preStart = ''
+ mkdir -p "${cfg.databaseDir}"
+ chmod 755 "${cfg.databaseDir}"
+ chown geoip:root "${cfg.databaseDir}"
+ '';
+ serviceConfig = {
+ ExecStart = "${geoip-updater}/bin/geoip-updater --skip-existing";
+ User = "geoip";
+ PermissionsStartOnly = true;
+ # So it won't be (needlessly) restarted:
+ RemainAfterExit = true;
+ };
+ };
+
+ };
+}