forked from nix-community/nur-packages-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Moved because of NixOS/nixpkgs#55867
Upstream doesn't care about stableness and reproducibility but this module can still be used as part of another, more declarative process
- Loading branch information
Showing
2 changed files
with
305 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
{ | ||
# Add your NixOS modules here | ||
# | ||
# my-module = ./my-module; | ||
# Moved because of https://github.com/NixOS/nixpkgs/pull/55867 | ||
# Upstream doesn't care about stableness and reproducibility | ||
# but this module can still be used as part of another, more declarative process | ||
geoip-updater = ./geoip-updater; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,301 @@ | ||
{ 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 = [ | ||
"GeoLite2-ASN.tar.gz" | ||
"GeoLite2-Country.tar.gz" | ||
"GeoLite2-City.tar.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; | ||
}; | ||
}; | ||
|
||
}; | ||
} |