{ config, pkgs, lib, options, ... }: let inherit (lib) mkOption types flip mapAttrs' nameValuePair concatStringsSep concatMapStringsSep pipe optional mapAttrsToList filterAttrs elem mkPackageOption attrNames singleton optionalString ; cfg = config.services.pppoe-server; in { options.services.pppoe-server = mkOption { default = {}; type = types.attrsOf (types.submodule ({ name, config, ... }: { freeformType = with types; oneOf [int str]; options = { package = mkPackageOption pkgs "rp-pppoe" {}; interface = mkOption { type = types.str; description = '' Which interface should `pppoe-server' bind to, this interface must be an L2 interface and pass PPPoE packets through unchanged. `pppoe-server' initially broadcasts a PADI packet. ''; }; localAddress = mkOption { type = types.str; description = '' Which address should be assigned to the local PPP interface. Only one such IP address per daemon is possible. ''; }; startingRemoteAddress = mkOption { type = types.nullOr types.str; description = '' Which starting address to utilize, `pppoe-server' will keep track of assigned addresses and automatically reclaim them as clients disconnect. ''; default = null; }; maxNumberOfConnections = mkOption { type = types.int; description = '' The maximum number of concurrent connections to allow, also limits the number of IP addresses and therefore the range, as a side effect. ''; default = 64; }; remoteAddressFile = mkOption { type = types.nullOr types.path; description = '' Reads the specified file fname which is a text file consisting of one IP address per line. These IP addresses will be assigned to clients. The number of sessions allowed will equal the number of addresses found in the file. The `remoteAddressFile' option overrides both `startingRemoteAddress' and `maxNumberOfConnections'. In addition to containing IP addresses, the pool file can contain lines of the form: - `a.b.c.d-e` which includes all IP addresses from`a.b.c.d` to `a.b.c.e`. For example, the line: - `1.2.3.4-7` is equivalent to: ``` 1.2.3.4 1.2.3.5 1.2.3.6 1.2.3.7 ``` ''; default = null; }; ifUpScript = mkOption { type = with types; nullOr path; description = '' Script to run, when the `ppp` interface goes up. ''; default = null; }; ifDownScript = mkOption { type = with types; nullOr path; description = '' Script to run, when the `ppp` interface goes down. ''; default = null; }; pppdSettings = mkOption { type = with types; attrsOf (listOf (oneOf [str int path])); default = {}; description = '' Settings passed to PPPD after it is started by `pppoe-server`. ''; }; }; config = { pppdSettings = { ip-up-script = singleton (pkgs.writeShellScript "ip-up-${name}" '' { echo "Signalling ready to systemd" systemd-notify --ready ${optionalString (config.ifUpScript != null) config.ifUpScript} } | logger -t pppd-ip-up ''); ip-down-script = singleton (pkgs.writeShellScript "ip-down-${name}" '' { echo "Signalling stopping to systemd" systemd-notify --stopping ${optionalString (config.ifDownScript != null) config.ifDownScript} } | logger -t pppd-ip-down ''); }; }; })); }; config.systemd.services = flip mapAttrs' cfg ( n: v: let pppdSettingsFile = pkgs.writeText "pppd-${n}.conf" ((pipe v.pppdSettings [ (mapAttrsToList (n: v: n + " " + concatMapStringsSep " " toString v)) (concatStringsSep "\n") ]) + "\n"); in nameValuePair "pppoe-server-${n}" { before = ["network.target"]; wants = ["network.target"]; after = ["network-pre.target" "ifstate.service"]; wantedBy = ["multi-user.target"]; path = [ pkgs.util-linux ]; serviceConfig = { Type = "notify"; NotifyAccess = "all"; ExecStart = "${v.package}/bin/pppoe-server " + concatStringsSep " " ( [ "-F" "-k" "-I ${v.interface}" "-L ${v.localAddress}" "-g ${v.package}/etc/ppp/plugins/rp-pppoe.so" "-O ${pppdSettingsFile}" ] ++ optional (v.startingRemoteAddress != null) "-R ${v.startingRemoteAddress}" ++ optional (v.maxNumberOfConnections != null) "-N ${toString v.maxNumberOfConnections}" ++ optional (v.remoteAddressFile != null) "-p ${v.remoteAddressFile}" ++ (flip mapAttrsToList (filterAttrs ( n: _: !(elem n (attrNames (options.services.pppoe-server.type.getSubOptions []))) ) v) (n: v: "-${n} ${toString v}")) ); }; } ); }