nixos/frp: support multiple instances

This commit is contained in:
DCsunset
2025-12-09 22:05:38 +01:00
committed by Weijia Wang
parent 9da06d4665
commit 67d9b4b947
3 changed files with 104 additions and 76 deletions
@@ -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`.
@@ -26,4 +26,6 @@
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
- `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.
+101 -75
View File
@@ -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 ];
}