{ pkgs, lib, config, ... }: with lib; let inherit (config.users) users; cfg = config.services.upnpc; getInfo = '' while IFS=: read -r k v; do k=$(printf %s "$k" | sed -e 's/^\s*//' -e 's/\s*$//') v=$(printf %s "$v" | sed -e 's/^\s*//' -e 's/\s*$//') case $k in (desc) desc=$v;; ("Local LAN ip address") localIP=$v;; esac done < 0 then config.duration / 2 else null; defaultText = "if duration > 0 then duration / 2 else null"; }; options.override = mkOption { description = "Try to override the redirection in case of conflict in mapping entry."; type = types.bool; default = true; }; options.service = mkOption { description = "Configuration specific to the systemd service handling this UPnP redirecting."; type = types.attrs; default = {}; }; })); }; }; config = { systemd.services = listToAttrs (map (r: nameValuePair "upnpc-${toString r.internalPort}" (mkMerge [ { description = "UPnP ${toString r.internalPort}"; after = [ "network-online.target" ]; #wantedBy = [ "multi-user.target" ]; path = [ pkgs.miniupnpc ]; serviceConfig = { Type = if r.maintainPeriod == null then "oneshot" else "simple"; RemainAfterExit = r.maintainPeriod == null; ExecStart = pkgs.writeShellScript "upnpc-start-${toString r.internalPort}" '' set -eu redirect () { result= while IFS= read -r line; do echo >&2 -E "$line" case $line in (*" is redirected to internal $localIP:${toString r.internalPort}"*) result=ok ;; (*ConflictInMappingEntry*) result=conflict ;; esac done <&1) EOF } while true; do ${getInfo} redirect ${optionalString r.override '' test "$result" != conflict || { upnpc -u "$desc" -d ${toString r.externalPort} ${r.protocol} redirect } ''} case $result in (ok) ${if r.maintainPeriod == null then "break" else "sleep " + toString r.maintainPeriod} ;; (*) exit 1 ;; esac done ''; ExecStop = "${pkgs.miniupnpc}/bin/upnpc -d ${toString r.externalPort} ${r.protocol}"; Restart = "on-failure"; RestartSec = mkDefault r.maintainPeriod; DynamicUser = true; User = users."upnpc".name; }; } r.service ]) ) cfg.redirections); # This enables to match on the uid in the firewall. users.users."upnpc".isSystemUser = true; }; meta.maintainers = with maintainers; [ julm ]; }