diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md index 2ff367f20ebd..d7fe84cbf70e 100644 --- a/nixos/doc/manual/release-notes/rl-2311.section.md +++ b/nixos/doc/manual/release-notes/rl-2311.section.md @@ -922,7 +922,7 @@ Make sure to also check the many updates in the [Nixpkgs library](#sec-release-2 - [frp](https://github.com/fatedier/frp), a fast reverse proxy to help you expose a local server behind a NAT or firewall to the Internet. Available as - [services.frp](#opt-services.frp.enable). + `services.frp`. - [river](https://github.com/riverwm/river), A dynamic tiling wayland compositor. Available as `programs.river`. diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 4fee9fb92e5e..706b4839a640 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -26,4 +26,6 @@ +- `services.frp` now supports multiple instances through `services.frp.instances` to make it possible to run multiple frp clients or servers at the same time. + - `services.openssh` now supports generating host SSH keys by setting `services.openssh.generateHostKeys = true` while leaving `services.openssh.enable` disabled. This is particularly useful for systems that have no need of an SSH daemon but want SSH host keys for other purposes such as using agenix or sops-nix. diff --git a/nixos/modules/services/networking/frp.nix b/nixos/modules/services/networking/frp.nix index f59074688b6c..d592e89b7201 100644 --- a/nixos/modules/services/networking/frp.nix +++ b/nixos/modules/services/networking/frp.nix @@ -7,95 +7,121 @@ let cfg = config.services.frp; settingsFormat = pkgs.formats.toml { }; - configFile = settingsFormat.generate "frp.toml" cfg.settings; - isClient = (cfg.role == "client"); - isServer = (cfg.role == "server"); + enabledInstances = lib.filterAttrs (name: conf: conf.enable) cfg.instances; in { + imports = [ + (lib.mkRenamedOptionModule + [ "services" "frp" "enable" ] + [ "services" "frp" "instances" "" "enable" ] + ) + (lib.mkRenamedOptionModule [ "services" "frp" "role" ] [ "services" "frp" "instances" "" "role" ]) + (lib.mkRenamedOptionModule + [ "services" "frp" "settings" ] + [ "services" "frp" "instances" "" "settings" ] + ) + ]; + options = { services.frp = { - enable = lib.mkEnableOption "frp"; + instances = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule { + options = { + enable = lib.mkEnableOption "frp"; - package = lib.mkPackageOption pkgs "frp" { }; + role = lib.mkOption { + type = lib.types.enum [ + "server" + "client" + ]; + description = '' + The frp consists of `client` and `server`. The server is usually + deployed on the machine with a public IP address, and + the client is usually deployed on the machine + where the Intranet service to be penetrated resides. + ''; + }; - role = lib.mkOption { - type = lib.types.enum [ - "server" - "client" - ]; - description = '' - The frp consists of `client` and `server`. The server is usually - deployed on the machine with a public IP address, and - the client is usually deployed on the machine - where the Intranet service to be penetrated resides. - ''; - }; - - settings = lib.mkOption { - type = settingsFormat.type; + settings = lib.mkOption { + type = settingsFormat.type; + default = { }; + description = '' + Frp configuration, for configuration options + see the example of [client](https://github.com/fatedier/frp/blob/dev/conf/frpc_full_example.toml) + or [server](https://github.com/fatedier/frp/blob/dev/conf/frps_full_example.toml) on github. + ''; + example = { + serverAddr = "x.x.x.x"; + serverPort = 7000; + }; + }; + }; + } + ); default = { }; description = '' - Frp configuration, for configuration options - see the example of [client](https://github.com/fatedier/frp/blob/dev/conf/frpc_full_example.toml) - or [server](https://github.com/fatedier/frp/blob/dev/conf/frps_full_example.toml) on github. + Frp instances. ''; - example = { - serverAddr = "x.x.x.x"; - serverPort = 7000; - }; }; + + package = lib.mkPackageOption pkgs "frp" { }; }; }; - config = - let - serviceCapability = lib.optionals isServer [ "CAP_NET_BIND_SERVICE" ]; - executableFile = if isClient then "frpc" else "frps"; - in - lib.mkIf cfg.enable { - systemd.services = { - frp = { - wants = lib.optionals isClient [ "network-online.target" ]; - after = if isClient then [ "network-online.target" ] else [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - description = "A fast reverse proxy frp ${cfg.role}"; - serviceConfig = { - Type = "simple"; - Restart = "on-failure"; - RestartSec = 15; - ExecStart = "${cfg.package}/bin/${executableFile} --strict_config -c ${configFile}"; - DynamicUser = true; - # Hardening - CapabilityBoundingSet = serviceCapability; - AmbientCapabilities = serviceCapability; - PrivateDevices = true; - ProtectHostname = true; - ProtectClock = true; - ProtectKernelTunables = true; - ProtectKernelModules = true; - ProtectKernelLogs = true; - ProtectControlGroups = true; - RestrictAddressFamilies = [ - "AF_INET" - "AF_INET6" - ] - ++ lib.optionals isClient [ "AF_UNIX" ]; - LockPersonality = true; - MemoryDenyWriteExecute = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - PrivateMounts = true; - SystemCallArchitectures = "native"; - SystemCallFilter = [ "@system-service" ]; - } - // lib.optionalAttrs isServer { - StateDirectory = "frp"; - StateDirectoryMode = "0700"; - UMask = "0007"; - }; + config = lib.mkIf (enabledInstances != { }) { + systemd.services = lib.mapAttrs' ( + instance: options: + let + serviceName = "frp" + lib.optionalString (instance != "") ("-" + instance); + configFile = settingsFormat.generate "${serviceName}.toml" options.settings; + isClient = (options.role == "client"); + isServer = (options.role == "server"); + serviceCapability = lib.optionals isServer [ "CAP_NET_BIND_SERVICE" ]; + executableFile = if isClient then "frpc" else "frps"; + in + lib.nameValuePair serviceName { + wants = lib.optionals isClient [ "network-online.target" ]; + after = if isClient then [ "network-online.target" ] else [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + description = "A fast reverse proxy frp ${options.role} for instance ${instance}"; + serviceConfig = { + Type = "simple"; + Restart = "on-failure"; + RestartSec = 15; + ExecStart = "${cfg.package}/bin/${executableFile} --strict_config -c ${configFile}"; + DynamicUser = true; + # Hardening + CapabilityBoundingSet = serviceCapability; + AmbientCapabilities = serviceCapability; + PrivateDevices = true; + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + ] + ++ lib.optionals isClient [ "AF_UNIX" ]; + LockPersonality = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + PrivateMounts = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ "@system-service" ]; + } + // lib.optionalAttrs isServer { + StateDirectory = "frp"; + StateDirectoryMode = "0700"; + UMask = "0007"; }; - }; - }; + } + ) enabledInstances; + }; meta.maintainers = with lib.maintainers; [ zaldnoay ]; }