uboot: (firmwareOdroidC2/C4) don't invoke patch tool, use patches = [] instead
https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/setup.sh#L948 this can do it nicely. Signed-off-by: Anton Arapov <anton@deadbeef.mx>
This commit is contained in:
commit
56de2bcd43
30691 changed files with 3076956 additions and 0 deletions
413
nixos/modules/services/networking/3proxy.nix
Normal file
413
nixos/modules/services/networking/3proxy.nix
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
with lib;
|
||||
let
|
||||
pkg = pkgs._3proxy;
|
||||
cfg = config.services._3proxy;
|
||||
optionalList = list: if list == [ ] then "*" else concatMapStringsSep "," toString list;
|
||||
in {
|
||||
options.services._3proxy = {
|
||||
enable = mkEnableOption "3proxy";
|
||||
confFile = mkOption {
|
||||
type = types.path;
|
||||
example = "/var/lib/3proxy/3proxy.conf";
|
||||
description = ''
|
||||
Ignore all other 3proxy options and load configuration from this file.
|
||||
'';
|
||||
};
|
||||
usersFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/var/lib/3proxy/3proxy.passwd";
|
||||
description = ''
|
||||
Load users and passwords from this file.
|
||||
|
||||
Example users file with plain-text passwords:
|
||||
|
||||
<literal>
|
||||
test1:CL:password1
|
||||
test2:CL:password2
|
||||
</literal>
|
||||
|
||||
Example users file with md5-crypted passwords:
|
||||
|
||||
<literal>
|
||||
test1:CR:$1$tFkisVd2$1GA8JXkRmTXdLDytM/i3a1
|
||||
test2:CR:$1$rkpibm5J$Aq1.9VtYAn0JrqZ8M.1ME.
|
||||
</literal>
|
||||
|
||||
You can generate md5-crypted passwords via https://unix4lyfe.org/crypt/
|
||||
Note that htpasswd tool generates incompatible md5-crypted passwords.
|
||||
Consult <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/How-To-(incomplete)#USERS">documentation</link> for more information.
|
||||
'';
|
||||
};
|
||||
services = mkOption {
|
||||
type = types.listOf (types.submodule {
|
||||
options = {
|
||||
type = mkOption {
|
||||
type = types.enum [
|
||||
"proxy"
|
||||
"socks"
|
||||
"pop3p"
|
||||
"ftppr"
|
||||
"admin"
|
||||
"dnspr"
|
||||
"tcppm"
|
||||
"udppm"
|
||||
];
|
||||
example = "proxy";
|
||||
description = ''
|
||||
Service type. The following values are valid:
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>
|
||||
<literal>"proxy"</literal>: HTTP/HTTPS proxy (default port 3128).
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<literal>"socks"</literal>: SOCKS 4/4.5/5 proxy (default port 1080).
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<literal>"pop3p"</literal>: POP3 proxy (default port 110).
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<literal>"ftppr"</literal>: FTP proxy (default port 21).
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<literal>"admin"</literal>: Web interface (default port 80).
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<literal>"dnspr"</literal>: Caching DNS proxy (default port 53).
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<literal>"tcppm"</literal>: TCP portmapper.
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<literal>"udppm"</literal>: UDP portmapper.
|
||||
</para></listitem>
|
||||
</itemizedlist>
|
||||
'';
|
||||
};
|
||||
bindAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "[::]";
|
||||
example = "127.0.0.1";
|
||||
description = ''
|
||||
Address used for service.
|
||||
'';
|
||||
};
|
||||
bindPort = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
example = 3128;
|
||||
description = ''
|
||||
Override default port used for service.
|
||||
'';
|
||||
};
|
||||
maxConnections = mkOption {
|
||||
type = types.int;
|
||||
default = 100;
|
||||
example = 1000;
|
||||
description = ''
|
||||
Maximum number of simulationeous connections to this service.
|
||||
'';
|
||||
};
|
||||
auth = mkOption {
|
||||
type = types.listOf (types.enum [ "none" "iponly" "strong" ]);
|
||||
example = [ "iponly" "strong" ];
|
||||
description = ''
|
||||
Authentication type. The following values are valid:
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>
|
||||
<literal>"none"</literal>: disables both authentication and authorization. You can not use ACLs.
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<literal>"iponly"</literal>: specifies no authentication. ACLs authorization is used.
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<literal>"strong"</literal>: authentication by username/password. If user is not registered their access is denied regardless of ACLs.
|
||||
</para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
Double authentication is possible, e.g.
|
||||
|
||||
<literal>
|
||||
{
|
||||
auth = [ "iponly" "strong" ];
|
||||
acl = [
|
||||
{
|
||||
rule = "allow";
|
||||
targets = [ "192.168.0.0/16" ];
|
||||
}
|
||||
{
|
||||
rule = "allow"
|
||||
users = [ "user1" "user2" ];
|
||||
}
|
||||
];
|
||||
}
|
||||
</literal>
|
||||
In this example strong username authentication is not required to access 192.168.0.0/16.
|
||||
'';
|
||||
};
|
||||
acl = mkOption {
|
||||
type = types.listOf (types.submodule {
|
||||
options = {
|
||||
rule = mkOption {
|
||||
type = types.enum [ "allow" "deny" ];
|
||||
example = "allow";
|
||||
description = ''
|
||||
ACL rule. The following values are valid:
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>
|
||||
<literal>"allow"</literal>: connections allowed.
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<literal>"deny"</literal>: connections not allowed.
|
||||
</para></listitem>
|
||||
</itemizedlist>
|
||||
'';
|
||||
};
|
||||
users = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "user1" "user2" "user3" ];
|
||||
description = ''
|
||||
List of users, use empty list for any.
|
||||
'';
|
||||
};
|
||||
sources = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "127.0.0.1" "192.168.1.0/24" ];
|
||||
description = ''
|
||||
List of source IP range, use empty list for any.
|
||||
'';
|
||||
};
|
||||
targets = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "127.0.0.1" "192.168.1.0/24" ];
|
||||
description = ''
|
||||
List of target IP ranges, use empty list for any.
|
||||
May also contain host names instead of addresses.
|
||||
It's possible to use wildmask in the begginning and in the the end of hostname, e.g. *badsite.com or *badcontent*.
|
||||
Hostname is only checked if hostname presents in request.
|
||||
'';
|
||||
};
|
||||
targetPorts = mkOption {
|
||||
type = types.listOf types.int;
|
||||
default = [ ];
|
||||
example = [ 80 443 ];
|
||||
description = ''
|
||||
List of target ports, use empty list for any.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [ ];
|
||||
example = literalExpression ''
|
||||
[
|
||||
{
|
||||
rule = "allow";
|
||||
users = [ "user1" ];
|
||||
}
|
||||
{
|
||||
rule = "allow";
|
||||
sources = [ "192.168.1.0/24" ];
|
||||
}
|
||||
{
|
||||
rule = "deny";
|
||||
}
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
Use this option to limit user access to resources.
|
||||
'';
|
||||
};
|
||||
extraArguments = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "-46";
|
||||
description = ''
|
||||
Extra arguments for service.
|
||||
Consult "Options" section in <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg">documentation</link> for available arguments.
|
||||
'';
|
||||
};
|
||||
extraConfig = mkOption {
|
||||
type = types.nullOr types.lines;
|
||||
default = null;
|
||||
description = ''
|
||||
Extra configuration for service. Use this to configure things like bandwidth limiter or ACL-based redirection.
|
||||
Consult <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg">documentation</link> for available options.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [ ];
|
||||
example = literalExpression ''
|
||||
[
|
||||
{
|
||||
type = "proxy";
|
||||
bindAddress = "192.168.1.24";
|
||||
bindPort = 3128;
|
||||
auth = [ "none" ];
|
||||
}
|
||||
{
|
||||
type = "proxy";
|
||||
bindAddress = "10.10.1.20";
|
||||
bindPort = 3128;
|
||||
auth = [ "iponly" ];
|
||||
}
|
||||
{
|
||||
type = "socks";
|
||||
bindAddress = "172.17.0.1";
|
||||
bindPort = 1080;
|
||||
auth = [ "strong" ];
|
||||
}
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
Use this option to define 3proxy services.
|
||||
'';
|
||||
};
|
||||
denyPrivate = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to deny access to private IP ranges including loopback.
|
||||
'';
|
||||
};
|
||||
privateRanges = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [
|
||||
"0.0.0.0/8"
|
||||
"127.0.0.0/8"
|
||||
"10.0.0.0/8"
|
||||
"100.64.0.0/10"
|
||||
"172.16.0.0/12"
|
||||
"192.168.0.0/16"
|
||||
"::"
|
||||
"::1"
|
||||
"fc00::/7"
|
||||
];
|
||||
description = ''
|
||||
What IP ranges to deny access when denyPrivate is set tu true.
|
||||
'';
|
||||
};
|
||||
resolution = mkOption {
|
||||
type = types.submodule {
|
||||
options = {
|
||||
nserver = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "127.0.0.53" "192.168.1.3:5353/tcp" ];
|
||||
description = ''
|
||||
List of nameservers to use.
|
||||
|
||||
Up to 5 nservers may be specified. If no nserver is configured,
|
||||
default system name resolution functions are used.
|
||||
'';
|
||||
};
|
||||
nscache = mkOption {
|
||||
type = types.int;
|
||||
default = 65535;
|
||||
description = "Set name cache size for IPv4.";
|
||||
};
|
||||
nscache6 = mkOption {
|
||||
type = types.int;
|
||||
default = 65535;
|
||||
description = "Set name cache size for IPv6.";
|
||||
};
|
||||
nsrecord = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
"files.local" = "192.168.1.12";
|
||||
"site.local" = "192.168.1.43";
|
||||
}
|
||||
'';
|
||||
description = "Adds static nsrecords.";
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
description = ''
|
||||
Use this option to configure name resolution and DNS caching.
|
||||
'';
|
||||
};
|
||||
extraConfig = mkOption {
|
||||
type = types.nullOr types.lines;
|
||||
default = null;
|
||||
description = ''
|
||||
Extra configuration, appended to the 3proxy configuration file.
|
||||
Consult <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg">documentation</link> for available options.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services._3proxy.confFile = mkDefault (pkgs.writeText "3proxy.conf" ''
|
||||
# log to stdout
|
||||
log
|
||||
|
||||
${concatMapStringsSep "\n" (x: "nserver " + x) cfg.resolution.nserver}
|
||||
|
||||
nscache ${toString cfg.resolution.nscache}
|
||||
nscache6 ${toString cfg.resolution.nscache6}
|
||||
|
||||
${concatMapStringsSep "\n" (x: "nsrecord " + x)
|
||||
(mapAttrsToList (name: value: "${name} ${value}")
|
||||
cfg.resolution.nsrecord)}
|
||||
|
||||
${optionalString (cfg.usersFile != null)
|
||||
''users $"${cfg.usersFile}"''
|
||||
}
|
||||
|
||||
${concatMapStringsSep "\n" (service: ''
|
||||
auth ${concatStringsSep " " service.auth}
|
||||
|
||||
${optionalString (cfg.denyPrivate)
|
||||
"deny * * ${optionalList cfg.privateRanges}"}
|
||||
|
||||
${concatMapStringsSep "\n" (acl:
|
||||
"${acl.rule} ${
|
||||
concatMapStringsSep " " optionalList [
|
||||
acl.users
|
||||
acl.sources
|
||||
acl.targets
|
||||
acl.targetPorts
|
||||
]
|
||||
}") service.acl}
|
||||
|
||||
maxconn ${toString service.maxConnections}
|
||||
|
||||
${optionalString (service.extraConfig != null) service.extraConfig}
|
||||
|
||||
${service.type} -i${toString service.bindAddress} ${
|
||||
optionalString (service.bindPort != null)
|
||||
"-p${toString service.bindPort}"
|
||||
} ${
|
||||
optionalString (service.extraArguments != null) service.extraArguments
|
||||
}
|
||||
|
||||
flush
|
||||
'') cfg.services}
|
||||
${optionalString (cfg.extraConfig != null) cfg.extraConfig}
|
||||
'');
|
||||
systemd.services."3proxy" = {
|
||||
description = "Tiny free proxy server";
|
||||
documentation = [ "https://github.com/z3APA3A/3proxy/wiki" ];
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
StateDirectory = "3proxy";
|
||||
ExecStart = "${pkg}/bin/3proxy ${cfg.confFile}";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ misuzu ];
|
||||
}
|
||||
140
nixos/modules/services/networking/adguardhome.nix
Normal file
140
nixos/modules/services/networking/adguardhome.nix
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.adguardhome;
|
||||
|
||||
args = concatStringsSep " " ([
|
||||
"--no-check-update"
|
||||
"--pidfile /run/AdGuardHome/AdGuardHome.pid"
|
||||
"--work-dir /var/lib/AdGuardHome/"
|
||||
"--config /var/lib/AdGuardHome/AdGuardHome.yaml"
|
||||
] ++ cfg.extraArgs);
|
||||
|
||||
baseConfig = {
|
||||
bind_host = cfg.host;
|
||||
bind_port = cfg.port;
|
||||
};
|
||||
|
||||
configFile = pkgs.writeTextFile {
|
||||
name = "AdGuardHome.yaml";
|
||||
text = builtins.toJSON (recursiveUpdate cfg.settings baseConfig);
|
||||
checkPhase = "${pkgs.adguardhome}/bin/adguardhome -c $out --check-config";
|
||||
};
|
||||
|
||||
in {
|
||||
options.services.adguardhome = with types; {
|
||||
enable = mkEnableOption "AdGuard Home network-wide ad blocker";
|
||||
|
||||
host = mkOption {
|
||||
default = "0.0.0.0";
|
||||
type = str;
|
||||
description = ''
|
||||
Host address to bind HTTP server to.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
default = 3000;
|
||||
type = port;
|
||||
description = ''
|
||||
Port to serve HTTP pages on.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
default = false;
|
||||
type = bool;
|
||||
description = ''
|
||||
Open ports in the firewall for the AdGuard Home web interface. Does not
|
||||
open the port needed to access the DNS resolver.
|
||||
'';
|
||||
};
|
||||
|
||||
mutableSettings = mkOption {
|
||||
default = true;
|
||||
type = bool;
|
||||
description = ''
|
||||
Allow changes made on the AdGuard Home web interface to persist between
|
||||
service restarts.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = (pkgs.formats.yaml { }).type;
|
||||
default = { };
|
||||
description = ''
|
||||
AdGuard Home configuration. Refer to
|
||||
<link xlink:href="https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#configuration-file"/>
|
||||
for details on supported values.
|
||||
|
||||
<note><para>
|
||||
On start and if <option>mutableSettings</option> is <literal>true</literal>,
|
||||
these options are merged into the configuration file on start, taking
|
||||
precedence over configuration changes made on the web interface.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
|
||||
extraArgs = mkOption {
|
||||
default = [ ];
|
||||
type = listOf str;
|
||||
description = ''
|
||||
Extra command line parameters to be passed to the adguardhome binary.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.settings != { }
|
||||
-> (hasAttrByPath [ "dns" "bind_host" ] cfg.settings)
|
||||
|| (hasAttrByPath [ "dns" "bind_hosts" ] cfg.settings);
|
||||
message =
|
||||
"AdGuard setting dns.bind_host or dns.bind_hosts needs to be configured for a minimal working configuration";
|
||||
}
|
||||
{
|
||||
assertion = cfg.settings != { }
|
||||
-> hasAttrByPath [ "dns" "bootstrap_dns" ] cfg.settings;
|
||||
message =
|
||||
"AdGuard setting dns.bootstrap_dns needs to be configured for a minimal working configuration";
|
||||
}
|
||||
];
|
||||
|
||||
systemd.services.adguardhome = {
|
||||
description = "AdGuard Home: Network-level blocker";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
unitConfig = {
|
||||
StartLimitIntervalSec = 5;
|
||||
StartLimitBurst = 10;
|
||||
};
|
||||
|
||||
preStart = optionalString (cfg.settings != { }) ''
|
||||
if [ -e "$STATE_DIRECTORY/AdGuardHome.yaml" ] \
|
||||
&& [ "${toString cfg.mutableSettings}" = "1" ]; then
|
||||
# Writing directly to AdGuardHome.yaml results in empty file
|
||||
${pkgs.yaml-merge}/bin/yaml-merge "$STATE_DIRECTORY/AdGuardHome.yaml" "${configFile}" > "$STATE_DIRECTORY/AdGuardHome.yaml.tmp"
|
||||
mv "$STATE_DIRECTORY/AdGuardHome.yaml.tmp" "$STATE_DIRECTORY/AdGuardHome.yaml"
|
||||
else
|
||||
cp --force "${configFile}" "$STATE_DIRECTORY/AdGuardHome.yaml"
|
||||
chmod 600 "$STATE_DIRECTORY/AdGuardHome.yaml"
|
||||
fi
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
ExecStart = "${pkgs.adguardhome}/bin/adguardhome ${args}";
|
||||
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||
Restart = "always";
|
||||
RestartSec = 10;
|
||||
RuntimeDirectory = "AdGuardHome";
|
||||
StateDirectory = "AdGuardHome";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
|
||||
};
|
||||
}
|
||||
83
nixos/modules/services/networking/amuled.nix
Normal file
83
nixos/modules/services/networking/amuled.nix
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.amule;
|
||||
opt = options.services.amule;
|
||||
user = if cfg.user != null then cfg.user else "amule";
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.amule = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to run the AMule daemon. You need to manually run "amuled --ec-config" to configure the service for the first time.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/home/${user}/";
|
||||
defaultText = literalExpression ''
|
||||
"/home/''${config.${opt.user}}/"
|
||||
'';
|
||||
description = ''
|
||||
The directory holding configuration, incoming and temporary files.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
The user the AMule daemon should run as.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.users = mkIf (cfg.user == null) [
|
||||
{ name = "amule";
|
||||
description = "AMule daemon";
|
||||
group = "amule";
|
||||
uid = config.ids.uids.amule;
|
||||
} ];
|
||||
|
||||
users.groups = mkIf (cfg.user == null) [
|
||||
{ name = "amule";
|
||||
gid = config.ids.gids.amule;
|
||||
} ];
|
||||
|
||||
systemd.services.amuled = {
|
||||
description = "AMule daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
preStart = ''
|
||||
mkdir -p ${cfg.dataDir}
|
||||
chown ${user} ${cfg.dataDir}
|
||||
'';
|
||||
|
||||
script = ''
|
||||
${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${user} \
|
||||
-c 'HOME="${cfg.dataDir}" ${pkgs.amule-daemon}/bin/amuled'
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
80
nixos/modules/services/networking/antennas.nix
Normal file
80
nixos/modules/services/networking/antennas.nix
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let cfg = config.services.antennas;
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
services.antennas = {
|
||||
enable = mkEnableOption "Antennas";
|
||||
|
||||
tvheadendUrl = mkOption {
|
||||
type = types.str;
|
||||
default = "http://localhost:9981";
|
||||
description = "URL of Tvheadend.";
|
||||
};
|
||||
|
||||
antennasUrl = mkOption {
|
||||
type = types.str;
|
||||
default = "http://127.0.0.1:5004";
|
||||
description = "URL of Antennas.";
|
||||
};
|
||||
|
||||
tunerCount = mkOption {
|
||||
type = types.int;
|
||||
default = 6;
|
||||
description = "Numbers of tuners in tvheadend.";
|
||||
};
|
||||
|
||||
deviceUUID = mkOption {
|
||||
type = types.str;
|
||||
default = "2f70c0d7-90a3-4429-8275-cbeeee9cd605";
|
||||
description = "Device tuner UUID. Change this if you are running multiple instances.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.antennas = {
|
||||
description = "Antennas HDHomeRun emulator for Tvheadend. ";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
# Config
|
||||
environment = {
|
||||
TVHEADEND_URL = cfg.tvheadendUrl;
|
||||
ANTENNAS_URL = cfg.antennasUrl;
|
||||
TUNER_COUNT = toString cfg.tunerCount;
|
||||
DEVICE_UUID = cfg.deviceUUID;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.antennas}/bin/antennas";
|
||||
|
||||
# Antennas expects all resources like html and config to be relative to it's working directory
|
||||
WorkingDirectory = "${pkgs.antennas}/libexec/antennas/deps/antennas/";
|
||||
|
||||
# Hardening
|
||||
CapabilityBoundingSet = [ "" ];
|
||||
DynamicUser = true;
|
||||
LockPersonality = true;
|
||||
ProcSubset = "pid";
|
||||
PrivateDevices = true;
|
||||
PrivateUsers = true;
|
||||
PrivateTmp = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
ProtectSystem = "strict";
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
131
nixos/modules/services/networking/aria2.nix
Normal file
131
nixos/modules/services/networking/aria2.nix
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.aria2;
|
||||
|
||||
homeDir = "/var/lib/aria2";
|
||||
|
||||
settingsDir = "${homeDir}";
|
||||
sessionFile = "${homeDir}/aria2.session";
|
||||
downloadDir = "${homeDir}/Downloads";
|
||||
|
||||
rangesToStringList = map (x: builtins.toString x.from +"-"+ builtins.toString x.to);
|
||||
|
||||
settingsFile = pkgs.writeText "aria2.conf"
|
||||
''
|
||||
dir=${cfg.downloadDir}
|
||||
listen-port=${concatStringsSep "," (rangesToStringList cfg.listenPortRange)}
|
||||
rpc-listen-port=${toString cfg.rpcListenPort}
|
||||
rpc-secret=${cfg.rpcSecret}
|
||||
'';
|
||||
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.aria2 = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether or not to enable the headless Aria2 daemon service.
|
||||
|
||||
Aria2 daemon can be controlled via the RPC interface using
|
||||
one of many WebUI (http://localhost:6800/ by default).
|
||||
|
||||
Targets are downloaded to ${downloadDir} by default and are
|
||||
accessible to users in the "aria2" group.
|
||||
'';
|
||||
};
|
||||
openPorts = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Open listen and RPC ports found in listenPortRange and rpcListenPort
|
||||
options in the firewall.
|
||||
'';
|
||||
};
|
||||
downloadDir = mkOption {
|
||||
type = types.path;
|
||||
default = downloadDir;
|
||||
description = ''
|
||||
Directory to store downloaded files.
|
||||
'';
|
||||
};
|
||||
listenPortRange = mkOption {
|
||||
type = types.listOf types.attrs;
|
||||
default = [ { from = 6881; to = 6999; } ];
|
||||
description = ''
|
||||
Set UDP listening port range used by DHT(IPv4, IPv6) and UDP tracker.
|
||||
'';
|
||||
};
|
||||
rpcListenPort = mkOption {
|
||||
type = types.int;
|
||||
default = 6800;
|
||||
description = "Specify a port number for JSON-RPC/XML-RPC server to listen to. Possible Values: 1024-65535";
|
||||
};
|
||||
rpcSecret = mkOption {
|
||||
type = types.str;
|
||||
default = "aria2rpc";
|
||||
description = ''
|
||||
Set RPC secret authorization token.
|
||||
Read https://aria2.github.io/manual/en/html/aria2c.html#rpc-auth to know how this option value is used.
|
||||
'';
|
||||
};
|
||||
extraArguments = mkOption {
|
||||
type = types.separatedString " ";
|
||||
example = "--rpc-listen-all --remote-time=true";
|
||||
default = "";
|
||||
description = ''
|
||||
Additional arguments to be passed to Aria2.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
# Need to open ports for proper functioning
|
||||
networking.firewall = mkIf cfg.openPorts {
|
||||
allowedUDPPortRanges = config.services.aria2.listenPortRange;
|
||||
allowedTCPPorts = [ config.services.aria2.rpcListenPort ];
|
||||
};
|
||||
|
||||
users.users.aria2 = {
|
||||
group = "aria2";
|
||||
uid = config.ids.uids.aria2;
|
||||
description = "aria2 user";
|
||||
home = homeDir;
|
||||
createHome = false;
|
||||
};
|
||||
|
||||
users.groups.aria2.gid = config.ids.gids.aria2;
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${homeDir}' 0770 aria2 aria2 - -"
|
||||
"d '${config.services.aria2.downloadDir}' 0770 aria2 aria2 - -"
|
||||
];
|
||||
|
||||
systemd.services.aria2 = {
|
||||
description = "aria2 Service";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
preStart = ''
|
||||
if [[ ! -e "${sessionFile}" ]]
|
||||
then
|
||||
touch "${sessionFile}"
|
||||
fi
|
||||
cp -f "${settingsFile}" "${settingsDir}/aria2.conf"
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Restart = "on-abort";
|
||||
ExecStart = "${pkgs.aria2}/bin/aria2c --enable-rpc --conf-path=${settingsDir}/aria2.conf ${config.services.aria2.extraArguments} --save-session=${sessionFile}";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
User = "aria2";
|
||||
Group = "aria2";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
232
nixos/modules/services/networking/asterisk.nix
Normal file
232
nixos/modules/services/networking/asterisk.nix
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.asterisk;
|
||||
|
||||
asteriskUser = "asterisk";
|
||||
asteriskGroup = "asterisk";
|
||||
|
||||
varlibdir = "/var/lib/asterisk";
|
||||
spooldir = "/var/spool/asterisk";
|
||||
logdir = "/var/log/asterisk";
|
||||
|
||||
# Add filecontents from files of useTheseDefaultConfFiles to confFiles, do not override
|
||||
defaultConfFiles = subtractLists (attrNames cfg.confFiles) cfg.useTheseDefaultConfFiles;
|
||||
allConfFiles = {
|
||||
# Default asterisk.conf file
|
||||
"asterisk.conf".text = ''
|
||||
[directories]
|
||||
astetcdir => /etc/asterisk
|
||||
astmoddir => ${cfg.package}/lib/asterisk/modules
|
||||
astvarlibdir => /var/lib/asterisk
|
||||
astdbdir => /var/lib/asterisk
|
||||
astkeydir => /var/lib/asterisk
|
||||
astdatadir => /var/lib/asterisk
|
||||
astagidir => /var/lib/asterisk/agi-bin
|
||||
astspooldir => /var/spool/asterisk
|
||||
astrundir => /run/asterisk
|
||||
astlogdir => /var/log/asterisk
|
||||
astsbindir => ${cfg.package}/sbin
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
|
||||
# Loading all modules by default is considered sensible by the authors of
|
||||
# "Asterisk: The Definitive Guide". Secure sites will likely want to
|
||||
# specify their own "modules.conf" in the confFiles option.
|
||||
"modules.conf".text = ''
|
||||
[modules]
|
||||
autoload=yes
|
||||
'';
|
||||
|
||||
# Use syslog for logging so logs can be viewed with journalctl
|
||||
"logger.conf".text = ''
|
||||
[general]
|
||||
|
||||
[logfiles]
|
||||
syslog.local0 => notice,warning,error
|
||||
'';
|
||||
} //
|
||||
mapAttrs (name: text: { inherit text; }) cfg.confFiles //
|
||||
listToAttrs (map (x: nameValuePair x { source = cfg.package + "/etc/asterisk/" + x; }) defaultConfFiles);
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
services.asterisk = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable the Asterisk PBX server.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
example = ''
|
||||
[options]
|
||||
verbose=3
|
||||
debug=3
|
||||
'';
|
||||
description = ''
|
||||
Extra configuration options appended to the default
|
||||
<literal>asterisk.conf</literal> file.
|
||||
'';
|
||||
};
|
||||
|
||||
confFiles = mkOption {
|
||||
default = {};
|
||||
type = types.attrsOf types.str;
|
||||
example = literalExpression
|
||||
''
|
||||
{
|
||||
"extensions.conf" = '''
|
||||
[tests]
|
||||
; Dial 100 for "hello, world"
|
||||
exten => 100,1,Answer()
|
||||
same => n,Wait(1)
|
||||
same => n,Playback(hello-world)
|
||||
same => n,Hangup()
|
||||
|
||||
[softphones]
|
||||
include => tests
|
||||
|
||||
[unauthorized]
|
||||
''';
|
||||
"sip.conf" = '''
|
||||
[general]
|
||||
allowguest=no ; Require authentication
|
||||
context=unauthorized ; Send unauthorized users to /dev/null
|
||||
srvlookup=no ; Don't do DNS lookup
|
||||
udpbindaddr=0.0.0.0 ; Listen on all interfaces
|
||||
nat=force_rport,comedia ; Assume device is behind NAT
|
||||
|
||||
[softphone](!)
|
||||
type=friend ; Match on username first, IP second
|
||||
context=softphones ; Send to softphones context in
|
||||
; extensions.conf file
|
||||
host=dynamic ; Device will register with asterisk
|
||||
disallow=all ; Manually specify codecs to allow
|
||||
allow=g722
|
||||
allow=ulaw
|
||||
allow=alaw
|
||||
|
||||
[myphone](softphone)
|
||||
secret=GhoshevFew ; Change this password!
|
||||
''';
|
||||
"logger.conf" = '''
|
||||
[general]
|
||||
|
||||
[logfiles]
|
||||
; Add debug output to log
|
||||
syslog.local0 => notice,warning,error,debug
|
||||
''';
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Sets the content of config files (typically ending with
|
||||
<literal>.conf</literal>) in the Asterisk configuration directory.
|
||||
|
||||
Note that if you want to change <literal>asterisk.conf</literal>, it
|
||||
is preferable to use the <option>services.asterisk.extraConfig</option>
|
||||
option over this option. If <literal>"asterisk.conf"</literal> is
|
||||
specified with the <option>confFiles</option> option (not recommended),
|
||||
you must be prepared to set your own <literal>astetcdir</literal>
|
||||
path.
|
||||
|
||||
See
|
||||
<link xlink:href="http://www.asterisk.org/community/documentation"/>
|
||||
for more examples of what is possible here.
|
||||
'';
|
||||
};
|
||||
|
||||
useTheseDefaultConfFiles = mkOption {
|
||||
default = [ "ari.conf" "acl.conf" "agents.conf" "amd.conf" "calendar.conf" "cdr.conf" "cdr_syslog.conf" "cdr_custom.conf" "cel.conf" "cel_custom.conf" "cli_aliases.conf" "confbridge.conf" "dundi.conf" "features.conf" "hep.conf" "iax.conf" "pjsip.conf" "pjsip_wizard.conf" "phone.conf" "phoneprov.conf" "queues.conf" "res_config_sqlite3.conf" "res_parking.conf" "statsd.conf" "udptl.conf" "unistim.conf" ];
|
||||
type = types.listOf types.str;
|
||||
example = [ "sip.conf" "dundi.conf" ];
|
||||
description = ''Sets these config files to the default content. The default value for
|
||||
this option contains all necesscary files to avoid errors at startup.
|
||||
This does not override settings via <option>services.asterisk.confFiles</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
extraArguments = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.str;
|
||||
example =
|
||||
[ "-vvvddd" "-e" "1024" ];
|
||||
description = ''
|
||||
Additional command line arguments to pass to Asterisk.
|
||||
'';
|
||||
};
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.asterisk;
|
||||
defaultText = literalExpression "pkgs.asterisk";
|
||||
description = "The Asterisk package to use.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
environment.etc = mapAttrs' (name: value:
|
||||
nameValuePair "asterisk/${name}" value
|
||||
) allConfFiles;
|
||||
|
||||
users.users.asterisk =
|
||||
{ name = asteriskUser;
|
||||
group = asteriskGroup;
|
||||
uid = config.ids.uids.asterisk;
|
||||
description = "Asterisk daemon user";
|
||||
home = varlibdir;
|
||||
};
|
||||
|
||||
users.groups.asterisk =
|
||||
{ name = asteriskGroup;
|
||||
gid = config.ids.gids.asterisk;
|
||||
};
|
||||
|
||||
systemd.services.asterisk = {
|
||||
description = ''
|
||||
Asterisk PBX server
|
||||
'';
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
# Do not restart, to avoid disruption of running calls. Restart unit by yourself!
|
||||
restartIfChanged = false;
|
||||
|
||||
preStart = ''
|
||||
# Copy skeleton directory tree to /var
|
||||
for d in '${varlibdir}' '${spooldir}' '${logdir}'; do
|
||||
# TODO: Make exceptions for /var directories that likely should be updated
|
||||
if [ ! -e "$d" ]; then
|
||||
mkdir -p "$d"
|
||||
cp --recursive ${cfg.package}/"$d"/* "$d"/
|
||||
chown --recursive ${asteriskUser}:${asteriskGroup} "$d"
|
||||
find "$d" -type d | xargs chmod 0755
|
||||
fi
|
||||
done
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart =
|
||||
let
|
||||
# FIXME: This doesn't account for arguments with spaces
|
||||
argString = concatStringsSep " " cfg.extraArguments;
|
||||
in
|
||||
"${cfg.package}/bin/asterisk -U ${asteriskUser} -C /etc/asterisk/asterisk.conf ${argString} -F";
|
||||
ExecReload = ''${cfg.package}/bin/asterisk -x "core reload"
|
||||
'';
|
||||
Type = "forking";
|
||||
PIDFile = "/run/asterisk/asterisk.pid";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
65
nixos/modules/services/networking/atftpd.nix
Normal file
65
nixos/modules/services/networking/atftpd.nix
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# NixOS module for atftpd TFTP server
|
||||
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.atftpd;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
options = {
|
||||
|
||||
services.atftpd = {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to enable the atftpd TFTP server. By default, the server
|
||||
binds to address 0.0.0.0.
|
||||
'';
|
||||
};
|
||||
|
||||
extraOptions = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.str;
|
||||
example = literalExpression ''
|
||||
[ "--bind-address 192.168.9.1"
|
||||
"--verbose=7"
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
Extra command line arguments to pass to atftp.
|
||||
'';
|
||||
};
|
||||
|
||||
root = mkOption {
|
||||
default = "/srv/tftp";
|
||||
type = types.path;
|
||||
description = ''
|
||||
Document root directory for the atftpd.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.services.atftpd = {
|
||||
description = "TFTP Server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
# runs as nobody
|
||||
serviceConfig.ExecStart = "${pkgs.atftp}/sbin/atftpd --daemon --no-fork ${lib.concatStringsSep " " cfg.extraOptions} ${cfg.root}";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
113
nixos/modules/services/networking/autossh.nix
Normal file
113
nixos/modules/services/networking/autossh.nix
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.autossh;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.autossh = {
|
||||
|
||||
sessions = mkOption {
|
||||
type = types.listOf (types.submodule {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
example = "socks-peer";
|
||||
description = "Name of the local AutoSSH session";
|
||||
};
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
example = "bill";
|
||||
description = "Name of the user the AutoSSH session should run as";
|
||||
};
|
||||
monitoringPort = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
example = 20000;
|
||||
description = ''
|
||||
Port to be used by AutoSSH for peer monitoring. Note, that
|
||||
AutoSSH also uses mport+1. Value of 0 disables the keep-alive
|
||||
style monitoring
|
||||
'';
|
||||
};
|
||||
extraArguments = mkOption {
|
||||
type = types.separatedString " ";
|
||||
example = "-N -D4343 bill@socks.example.net";
|
||||
description = ''
|
||||
Arguments to be passed to AutoSSH and retransmitted to SSH
|
||||
process. Some meaningful options include -N (don't run remote
|
||||
command), -D (open SOCKS proxy on local port), -R (forward
|
||||
remote port), -L (forward local port), -v (Enable debug). Check
|
||||
ssh manual for the complete list.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
default = [];
|
||||
description = ''
|
||||
List of AutoSSH sessions to start as systemd services. Each service is
|
||||
named 'autossh-{session.name}'.
|
||||
'';
|
||||
|
||||
example = [
|
||||
{
|
||||
name="socks-peer";
|
||||
user="bill";
|
||||
monitoringPort = 20000;
|
||||
extraArguments="-N -D4343 billremote@socks.host.net";
|
||||
}
|
||||
];
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf (cfg.sessions != []) {
|
||||
|
||||
systemd.services =
|
||||
|
||||
lib.foldr ( s : acc : acc //
|
||||
{
|
||||
"autossh-${s.name}" =
|
||||
let
|
||||
mport = if s ? monitoringPort then s.monitoringPort else 0;
|
||||
in
|
||||
{
|
||||
description = "AutoSSH session (" + s.name + ")";
|
||||
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
# To be able to start the service with no network connection
|
||||
environment.AUTOSSH_GATETIME="0";
|
||||
|
||||
# How often AutoSSH checks the network, in seconds
|
||||
environment.AUTOSSH_POLL="30";
|
||||
|
||||
serviceConfig = {
|
||||
User = "${s.user}";
|
||||
# AutoSSH may exit with 0 code if the SSH session was
|
||||
# gracefully terminated by either local or remote side.
|
||||
Restart = "on-success";
|
||||
ExecStart = "${pkgs.autossh}/bin/autossh -M ${toString mport} ${s.extraArguments}";
|
||||
};
|
||||
};
|
||||
}) {} cfg.sessions;
|
||||
|
||||
environment.systemPackages = [ pkgs.autossh ];
|
||||
|
||||
};
|
||||
}
|
||||
286
nixos/modules/services/networking/avahi-daemon.nix
Normal file
286
nixos/modules/services/networking/avahi-daemon.nix
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.avahi;
|
||||
|
||||
yesNo = yes : if yes then "yes" else "no";
|
||||
|
||||
avahiDaemonConf = with cfg; pkgs.writeText "avahi-daemon.conf" ''
|
||||
[server]
|
||||
${# Users can set `networking.hostName' to the empty string, when getting
|
||||
# a host name from DHCP. In that case, let Avahi take whatever the
|
||||
# current host name is; setting `host-name' to the empty string in
|
||||
# `avahi-daemon.conf' would be invalid.
|
||||
optionalString (hostName != "") "host-name=${hostName}"}
|
||||
browse-domains=${concatStringsSep ", " browseDomains}
|
||||
use-ipv4=${yesNo ipv4}
|
||||
use-ipv6=${yesNo ipv6}
|
||||
${optionalString (interfaces!=null) "allow-interfaces=${concatStringsSep "," interfaces}"}
|
||||
${optionalString (domainName!=null) "domain-name=${domainName}"}
|
||||
allow-point-to-point=${yesNo allowPointToPoint}
|
||||
${optionalString (cacheEntriesMax!=null) "cache-entries-max=${toString cacheEntriesMax}"}
|
||||
|
||||
[wide-area]
|
||||
enable-wide-area=${yesNo wideArea}
|
||||
|
||||
[publish]
|
||||
disable-publishing=${yesNo (!publish.enable)}
|
||||
disable-user-service-publishing=${yesNo (!publish.userServices)}
|
||||
publish-addresses=${yesNo (publish.userServices || publish.addresses)}
|
||||
publish-hinfo=${yesNo publish.hinfo}
|
||||
publish-workstation=${yesNo publish.workstation}
|
||||
publish-domain=${yesNo publish.domain}
|
||||
|
||||
[reflector]
|
||||
enable-reflector=${yesNo reflector}
|
||||
${extraConfig}
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.services.avahi = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to run the Avahi daemon, which allows Avahi clients
|
||||
to use Avahi's service discovery facilities and also allows
|
||||
the local machine to advertise its presence and services
|
||||
(through the mDNS responder implemented by `avahi-daemon').
|
||||
'';
|
||||
};
|
||||
|
||||
hostName = mkOption {
|
||||
type = types.str;
|
||||
default = config.networking.hostName;
|
||||
defaultText = literalExpression "config.networking.hostName";
|
||||
description = ''
|
||||
Host name advertised on the LAN. If not set, avahi will use the value
|
||||
of <option>config.networking.hostName</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
domainName = mkOption {
|
||||
type = types.str;
|
||||
default = "local";
|
||||
description = ''
|
||||
Domain name for all advertisements.
|
||||
'';
|
||||
};
|
||||
|
||||
browseDomains = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "0pointer.de" "zeroconf.org" ];
|
||||
description = ''
|
||||
List of non-local DNS domains to be browsed.
|
||||
'';
|
||||
};
|
||||
|
||||
ipv4 = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether to use IPv4.";
|
||||
};
|
||||
|
||||
ipv6 = mkOption {
|
||||
type = types.bool;
|
||||
default = config.networking.enableIPv6;
|
||||
defaultText = literalExpression "config.networking.enableIPv6";
|
||||
description = "Whether to use IPv6.";
|
||||
};
|
||||
|
||||
interfaces = mkOption {
|
||||
type = types.nullOr (types.listOf types.str);
|
||||
default = null;
|
||||
description = ''
|
||||
List of network interfaces that should be used by the <command>avahi-daemon</command>.
|
||||
Other interfaces will be ignored. If <literal>null</literal>, all local interfaces
|
||||
except loopback and point-to-point will be used.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to open the firewall for UDP port 5353.
|
||||
'';
|
||||
};
|
||||
|
||||
allowPointToPoint = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description= ''
|
||||
Whether to use POINTTOPOINT interfaces. Might make mDNS unreliable due to usually large
|
||||
latencies with such links and opens a potential security hole by allowing mDNS access from Internet
|
||||
connections.
|
||||
'';
|
||||
};
|
||||
|
||||
wideArea = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether to enable wide-area service discovery.";
|
||||
};
|
||||
|
||||
reflector = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Reflect incoming mDNS requests to all allowed network interfaces.";
|
||||
};
|
||||
|
||||
extraServiceFiles = mkOption {
|
||||
type = with types; attrsOf (either str path);
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{
|
||||
ssh = "''${pkgs.avahi}/etc/avahi/services/ssh.service";
|
||||
smb = '''
|
||||
<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
|
||||
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
|
||||
<service-group>
|
||||
<name replace-wildcards="yes">%h</name>
|
||||
<service>
|
||||
<type>_smb._tcp</type>
|
||||
<port>445</port>
|
||||
</service>
|
||||
</service-group>
|
||||
''';
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Specify custom service definitions which are placed in the avahi service directory.
|
||||
See the <citerefentry><refentrytitle>avahi.service</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry> manpage for detailed information.
|
||||
'';
|
||||
};
|
||||
|
||||
publish = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to allow publishing in general.";
|
||||
};
|
||||
|
||||
userServices = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to publish user services. Will set <literal>addresses=true</literal>.";
|
||||
};
|
||||
|
||||
addresses = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to register mDNS address records for all local IP addresses.";
|
||||
};
|
||||
|
||||
hinfo = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to register a mDNS HINFO record which contains information about the
|
||||
local operating system and CPU.
|
||||
'';
|
||||
};
|
||||
|
||||
workstation = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to register a service of type "_workstation._tcp" on the local LAN.
|
||||
'';
|
||||
};
|
||||
|
||||
domain = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to announce the locally used domain name for browsing by other hosts.";
|
||||
};
|
||||
};
|
||||
|
||||
nssmdns = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable the mDNS NSS (Name Service Switch) plug-in.
|
||||
Enabling it allows applications to resolve names in the `.local'
|
||||
domain by transparently querying the Avahi daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
cacheEntriesMax = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
Number of resource records to be cached per interface. Use 0 to
|
||||
disable caching. Avahi daemon defaults to 4096 if not set.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Extra config to append to avahi-daemon.conf.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
users.users.avahi = {
|
||||
description = "avahi-daemon privilege separation user";
|
||||
home = "/var/empty";
|
||||
group = "avahi";
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
users.groups.avahi = {};
|
||||
|
||||
system.nssModules = optional cfg.nssmdns pkgs.nssmdns;
|
||||
system.nssDatabases.hosts = optionals cfg.nssmdns (mkMerge [
|
||||
(mkBefore [ "mdns_minimal [NOTFOUND=return]" ]) # before resolve
|
||||
(mkAfter [ "mdns" ]) # after dns
|
||||
]);
|
||||
|
||||
environment.systemPackages = [ pkgs.avahi ];
|
||||
|
||||
environment.etc = (mapAttrs' (n: v: nameValuePair
|
||||
"avahi/services/${n}.service"
|
||||
{ ${if types.path.check v then "source" else "text"} = v; }
|
||||
) cfg.extraServiceFiles);
|
||||
|
||||
systemd.sockets.avahi-daemon = {
|
||||
description = "Avahi mDNS/DNS-SD Stack Activation Socket";
|
||||
listenStreams = [ "/run/avahi-daemon/socket" ];
|
||||
wantedBy = [ "sockets.target" ];
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [ "d /run/avahi-daemon - avahi avahi -" ];
|
||||
|
||||
systemd.services.avahi-daemon = {
|
||||
description = "Avahi mDNS/DNS-SD Stack";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
requires = [ "avahi-daemon.socket" ];
|
||||
|
||||
# Make NSS modules visible so that `avahi_nss_support ()' can
|
||||
# return a sensible value.
|
||||
environment.LD_LIBRARY_PATH = config.system.nssModules.path;
|
||||
|
||||
path = [ pkgs.coreutils pkgs.avahi ];
|
||||
|
||||
serviceConfig = {
|
||||
NotifyAccess = "main";
|
||||
BusName = "org.freedesktop.Avahi";
|
||||
Type = "dbus";
|
||||
ExecStart = "${pkgs.avahi}/sbin/avahi-daemon --syslog -f ${avahiDaemonConf}";
|
||||
};
|
||||
};
|
||||
|
||||
services.dbus.enable = true;
|
||||
services.dbus.packages = [ pkgs.avahi ];
|
||||
|
||||
networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall [ 5353 ];
|
||||
};
|
||||
}
|
||||
144
nixos/modules/services/networking/babeld.nix
Normal file
144
nixos/modules/services/networking/babeld.nix
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.babeld;
|
||||
|
||||
conditionalBoolToString = value: if (isBool value) then (boolToString value) else (toString value);
|
||||
|
||||
paramsString = params:
|
||||
concatMapStringsSep " " (name: "${name} ${conditionalBoolToString (getAttr name params)}")
|
||||
(attrNames params);
|
||||
|
||||
interfaceConfig = name:
|
||||
let
|
||||
interface = getAttr name cfg.interfaces;
|
||||
in
|
||||
"interface ${name} ${paramsString interface}\n";
|
||||
|
||||
configFile = with cfg; pkgs.writeText "babeld.conf" (
|
||||
''
|
||||
skip-kernel-setup true
|
||||
''
|
||||
+ (optionalString (cfg.interfaceDefaults != null) ''
|
||||
default ${paramsString cfg.interfaceDefaults}
|
||||
'')
|
||||
+ (concatMapStrings interfaceConfig (attrNames cfg.interfaces))
|
||||
+ extraConfig);
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
meta.maintainers = with maintainers; [ hexa ];
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.babeld = {
|
||||
|
||||
enable = mkEnableOption "the babeld network routing daemon";
|
||||
|
||||
interfaceDefaults = mkOption {
|
||||
default = null;
|
||||
description = ''
|
||||
A set describing default parameters for babeld interfaces.
|
||||
See <citerefentry><refentrytitle>babeld</refentrytitle><manvolnum>8</manvolnum></citerefentry> for options.
|
||||
'';
|
||||
type = types.nullOr (types.attrsOf types.unspecified);
|
||||
example =
|
||||
{
|
||||
type = "tunnel";
|
||||
split-horizon = true;
|
||||
};
|
||||
};
|
||||
|
||||
interfaces = mkOption {
|
||||
default = {};
|
||||
description = ''
|
||||
A set describing babeld interfaces.
|
||||
See <citerefentry><refentrytitle>babeld</refentrytitle><manvolnum>8</manvolnum></citerefentry> for options.
|
||||
'';
|
||||
type = types.attrsOf (types.attrsOf types.unspecified);
|
||||
example =
|
||||
{ enp0s2 =
|
||||
{ type = "wired";
|
||||
hello-interval = 5;
|
||||
split-horizon = "auto";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Options that will be copied to babeld.conf.
|
||||
See <citerefentry><refentrytitle>babeld</refentrytitle><manvolnum>8</manvolnum></citerefentry> for details.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf config.services.babeld.enable {
|
||||
|
||||
boot.kernel.sysctl = {
|
||||
"net.ipv6.conf.all.forwarding" = 1;
|
||||
"net.ipv6.conf.all.accept_redirects" = 0;
|
||||
"net.ipv4.conf.all.forwarding" = 1;
|
||||
"net.ipv4.conf.all.rp_filter" = 0;
|
||||
} // lib.mapAttrs' (ifname: _: lib.nameValuePair "net.ipv4.conf.${ifname}.rp_filter" (lib.mkDefault 0)) config.services.babeld.interfaces;
|
||||
|
||||
systemd.services.babeld = {
|
||||
description = "Babel routing daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.babeld}/bin/babeld -c ${configFile} -I /run/babeld/babeld.pid -S /var/lib/babeld/state";
|
||||
AmbientCapabilities = [ "CAP_NET_ADMIN" ];
|
||||
CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
|
||||
DevicePolicy = "closed";
|
||||
DynamicUser = true;
|
||||
IPAddressAllow = [ "fe80::/64" "ff00::/8" "::1/128" "127.0.0.0/8" ];
|
||||
IPAddressDeny = "any";
|
||||
LockPersonality = true;
|
||||
NoNewPrivileges = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectClock = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictAddressFamilies = [ "AF_NETLINK" "AF_INET6" "AF_INET" ];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
RemoveIPC = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectProc = "invisible";
|
||||
PrivateMounts = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
PrivateUsers = false; # kernel_route(ADD): Operation not permitted
|
||||
ProcSubset = "pid";
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@privileged @resources"
|
||||
];
|
||||
UMask = "0177";
|
||||
RuntimeDirectory = "babeld";
|
||||
StateDirectory = "babeld";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
107
nixos/modules/services/networking/bee-clef.nix
Normal file
107
nixos/modules/services/networking/bee-clef.nix
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# NOTE for now nothing is installed into /etc/bee-clef/. the config files are used as read-only from the nix store.
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.bee-clef;
|
||||
in {
|
||||
meta = {
|
||||
maintainers = with maintainers; [ attila-lendvai ];
|
||||
};
|
||||
|
||||
### interface
|
||||
|
||||
options = {
|
||||
services.bee-clef = {
|
||||
enable = mkEnableOption "clef external signer instance for Ethereum Swarm Bee";
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = "/var/lib/bee-clef";
|
||||
description = ''
|
||||
Data dir for bee-clef. Beware that some helper scripts may not work when changed!
|
||||
The service itself should work fine, though.
|
||||
'';
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = "/var/lib/bee-clef/password";
|
||||
description = "Password file for bee-clef.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "bee-clef";
|
||||
description = ''
|
||||
User the bee-clef daemon should execute under.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "bee-clef";
|
||||
description = ''
|
||||
Group the bee-clef daemon should execute under.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# if we ever want to have rules.js under /etc/bee-clef/
|
||||
# environment.etc."bee-clef/rules.js".source = ${pkgs.bee-clef}/rules.js
|
||||
|
||||
systemd.packages = [ pkgs.bee-clef ]; # include the upstream bee-clef.service file
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dataDir}/' 0750 ${cfg.user} ${cfg.group}"
|
||||
"d '${cfg.dataDir}/keystore' 0700 ${cfg.user} ${cfg.group}"
|
||||
];
|
||||
|
||||
systemd.services.bee-clef = {
|
||||
path = [
|
||||
# these are needed for the ensure-clef-account script
|
||||
pkgs.coreutils
|
||||
pkgs.gnused
|
||||
pkgs.gawk
|
||||
];
|
||||
|
||||
wantedBy = [ "bee.service" "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStartPre = ''${pkgs.bee-clef}/share/bee-clef/ensure-clef-account "${cfg.dataDir}" "${pkgs.bee-clef}/share/bee-clef/"'';
|
||||
ExecStart = [
|
||||
"" # this hides/overrides what's in the original entry
|
||||
"${pkgs.bee-clef}/share/bee-clef/bee-clef-service start"
|
||||
];
|
||||
ExecStop = [
|
||||
"" # this hides/overrides what's in the original entry
|
||||
"${pkgs.bee-clef}/share/bee-clef/bee-clef-service stop"
|
||||
];
|
||||
Environment = [
|
||||
"CONFIGDIR=${cfg.dataDir}"
|
||||
"PASSWORD_FILE=${cfg.passwordFile}"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "bee-clef") {
|
||||
bee-clef = {
|
||||
group = cfg.group;
|
||||
home = cfg.dataDir;
|
||||
isSystemUser = true;
|
||||
description = "Daemon user for the bee-clef service";
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == "bee-clef") {
|
||||
bee-clef = {};
|
||||
};
|
||||
};
|
||||
}
|
||||
149
nixos/modules/services/networking/bee.nix
Normal file
149
nixos/modules/services/networking/bee.nix
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.bee;
|
||||
format = pkgs.formats.yaml {};
|
||||
configFile = format.generate "bee.yaml" cfg.settings;
|
||||
in {
|
||||
meta = {
|
||||
# doc = ./bee.xml;
|
||||
maintainers = with maintainers; [ attila-lendvai ];
|
||||
};
|
||||
|
||||
### interface
|
||||
|
||||
options = {
|
||||
services.bee = {
|
||||
enable = mkEnableOption "Ethereum Swarm Bee";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.bee;
|
||||
defaultText = literalExpression "pkgs.bee";
|
||||
example = literalExpression "pkgs.bee-unstable";
|
||||
description = "The package providing the bee binary for the service.";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = format.type;
|
||||
description = ''
|
||||
Ethereum Swarm Bee configuration. Refer to
|
||||
<link xlink:href="https://gateway.ethswarm.org/bzz/docs.swarm.eth/docs/installation/configuration/"/>
|
||||
for details on supported values.
|
||||
'';
|
||||
};
|
||||
|
||||
daemonNiceLevel = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = ''
|
||||
Daemon process priority for bee.
|
||||
0 is the default Unix process priority, 19 is the lowest.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "bee";
|
||||
description = ''
|
||||
User the bee binary should execute under.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "bee";
|
||||
description = ''
|
||||
Group the bee binary should execute under.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{ assertion = (hasAttr "password" cfg.settings) != true;
|
||||
message = ''
|
||||
`services.bee.settings.password` is insecure. Use `services.bee.settings.password-file` or `systemd.services.bee.serviceConfig.EnvironmentFile` instead.
|
||||
'';
|
||||
}
|
||||
{ assertion = (hasAttr "swap-endpoint" cfg.settings) || (cfg.settings.swap-enable or true == false);
|
||||
message = ''
|
||||
In a swap-enabled network a working Ethereum blockchain node is required. You must specify one using `services.bee.settings.swap-endpoint`, or disable `services.bee.settings.swap-enable` = false.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
warnings = optional (! config.services.bee-clef.enable) "The bee service requires an external signer. Consider setting `config.services.bee-clef.enable` = true";
|
||||
|
||||
services.bee.settings = {
|
||||
data-dir = lib.mkDefault "/var/lib/bee";
|
||||
password-file = lib.mkDefault "/var/lib/bee/password";
|
||||
clef-signer-enable = lib.mkDefault true;
|
||||
clef-signer-endpoint = lib.mkDefault "/var/lib/bee-clef/clef.ipc";
|
||||
swap-endpoint = lib.mkDefault "https://rpc.slock.it/goerli";
|
||||
};
|
||||
|
||||
systemd.packages = [ cfg.package ]; # include the upstream bee.service file
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.settings.data-dir}' 0750 ${cfg.user} ${cfg.group}"
|
||||
];
|
||||
|
||||
systemd.services.bee = {
|
||||
requires = optional config.services.bee-clef.enable
|
||||
"bee-clef.service";
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Nice = cfg.daemonNiceLevel;
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStart = [
|
||||
"" # this hides/overrides what's in the original entry
|
||||
"${cfg.package}/bin/bee --config=${configFile} start"
|
||||
];
|
||||
};
|
||||
|
||||
preStart = with cfg.settings; ''
|
||||
if ! test -f ${password-file}; then
|
||||
< /dev/urandom tr -dc _A-Z-a-z-0-9 2> /dev/null | head -c32 > ${password-file}
|
||||
chmod 0600 ${password-file}
|
||||
echo "Initialized ${password-file} from /dev/urandom"
|
||||
fi
|
||||
if [ ! -f ${data-dir}/keys/libp2p.key ]; then
|
||||
${cfg.package}/bin/bee init --config=${configFile} >/dev/null
|
||||
echo "
|
||||
Logs: journalctl -f -u bee.service
|
||||
|
||||
Bee has SWAP enabled by default and it needs ethereum endpoint to operate.
|
||||
It is recommended to use external signer with bee.
|
||||
Check documentation for more info:
|
||||
- SWAP https://docs.ethswarm.org/docs/installation/manual#swap-bandwidth-incentives
|
||||
- External signer https://docs.ethswarm.org/docs/installation/bee-clef
|
||||
|
||||
After you finish configuration run 'sudo bee-get-addr'."
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "bee") {
|
||||
bee = {
|
||||
group = cfg.group;
|
||||
home = cfg.settings.data-dir;
|
||||
isSystemUser = true;
|
||||
description = "Daemon user for Ethereum Swarm Bee";
|
||||
extraGroups = optional config.services.bee-clef.enable
|
||||
config.services.bee-clef.group;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == "bee") {
|
||||
bee = {};
|
||||
};
|
||||
};
|
||||
}
|
||||
270
nixos/modules/services/networking/biboumi.nix
Normal file
270
nixos/modules/services/networking/biboumi.nix
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
{ config, lib, pkgs, options, ... }:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.biboumi;
|
||||
inherit (config.environment) etc;
|
||||
rootDir = "/run/biboumi/mnt-root";
|
||||
stateDir = "/var/lib/biboumi";
|
||||
settingsFile = pkgs.writeText "biboumi.cfg" (
|
||||
generators.toKeyValue {
|
||||
mkKeyValue = k: v:
|
||||
if v == null then ""
|
||||
else generators.mkKeyValueDefault {} "=" k v;
|
||||
} cfg.settings);
|
||||
need_CAP_NET_BIND_SERVICE = cfg.settings.identd_port != 0 && cfg.settings.identd_port < 1024;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.biboumi = {
|
||||
enable = mkEnableOption "the Biboumi XMPP gateway to IRC";
|
||||
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
See <link xlink:href="https://lab.louiz.org/louiz/biboumi/blob/8.5/doc/biboumi.1.rst">biboumi 8.5</link>
|
||||
for documentation.
|
||||
'';
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
freeformType = with types;
|
||||
(attrsOf (nullOr (oneOf [str int bool]))) // {
|
||||
description = "settings option";
|
||||
};
|
||||
options.admin = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [];
|
||||
example = ["admin@example.org"];
|
||||
apply = concatStringsSep ":";
|
||||
description = ''
|
||||
The bare JID of the gateway administrator. This JID will have more
|
||||
privileges than other standard users, for example some administration
|
||||
ad-hoc commands will only be available to that JID.
|
||||
'';
|
||||
};
|
||||
options.ca_file = mkOption {
|
||||
type = types.path;
|
||||
default = "/etc/ssl/certs/ca-certificates.crt";
|
||||
description = ''
|
||||
Specifies which file should be used as the list of trusted CA
|
||||
when negociating a TLS session.
|
||||
'';
|
||||
};
|
||||
options.db_name = mkOption {
|
||||
type = with types; either path str;
|
||||
default = "${stateDir}/biboumi.sqlite";
|
||||
description = ''
|
||||
The name of the database to use.
|
||||
'';
|
||||
example = "postgresql://user:secret@localhost";
|
||||
};
|
||||
options.hostname = mkOption {
|
||||
type = types.str;
|
||||
example = "biboumi.example.org";
|
||||
description = ''
|
||||
The hostname served by the XMPP gateway.
|
||||
This domain must be configured in the XMPP server
|
||||
as an external component.
|
||||
'';
|
||||
};
|
||||
options.identd_port = mkOption {
|
||||
type = types.port;
|
||||
default = 113;
|
||||
example = 0;
|
||||
description = ''
|
||||
The TCP port on which to listen for identd queries.
|
||||
'';
|
||||
};
|
||||
options.log_level = mkOption {
|
||||
type = types.ints.between 0 3;
|
||||
default = 1;
|
||||
description = ''
|
||||
Indicate what type of log messages to write in the logs.
|
||||
0 is debug, 1 is info, 2 is warning, 3 is error.
|
||||
'';
|
||||
};
|
||||
options.password = mkOption {
|
||||
type = with types; nullOr str;
|
||||
description = ''
|
||||
The password used to authenticate the XMPP component to your XMPP server.
|
||||
This password must be configured in the XMPP server,
|
||||
associated with the external component on
|
||||
<link linkend="opt-services.biboumi.settings.hostname">hostname</link>.
|
||||
|
||||
Set it to null and use <link linkend="opt-services.biboumi.credentialsFile">credentialsFile</link>
|
||||
if you do not want this password to go into the Nix store.
|
||||
'';
|
||||
};
|
||||
options.persistent_by_default = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether all rooms will be persistent by default:
|
||||
the value of the “persistent” option in the global configuration of each
|
||||
user will be “true”, but the value of each individual room will still
|
||||
default to false. This means that a user just needs to change the global
|
||||
“persistent” configuration option to false in order to override this.
|
||||
'';
|
||||
};
|
||||
options.policy_directory = mkOption {
|
||||
type = types.path;
|
||||
default = "${pkgs.biboumi}/etc/biboumi";
|
||||
defaultText = literalExpression ''"''${pkgs.biboumi}/etc/biboumi"'';
|
||||
description = ''
|
||||
A directory that should contain the policy files,
|
||||
used to customize Botan’s behaviour
|
||||
when negociating the TLS connections with the IRC servers.
|
||||
'';
|
||||
};
|
||||
options.port = mkOption {
|
||||
type = types.port;
|
||||
default = 5347;
|
||||
description = ''
|
||||
The TCP port to use to connect to the local XMPP component.
|
||||
'';
|
||||
};
|
||||
options.realname_customization = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether the users will be able to use
|
||||
the ad-hoc commands that lets them configure
|
||||
their realname and username.
|
||||
'';
|
||||
};
|
||||
options.realname_from_jid = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether the realname and username of each biboumi
|
||||
user will be extracted from their JID.
|
||||
Otherwise they will be set to the nick
|
||||
they used to connect to the IRC server.
|
||||
'';
|
||||
};
|
||||
options.xmpp_server_ip = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
description = ''
|
||||
The IP address to connect to the XMPP server on.
|
||||
The connection to the XMPP server is unencrypted,
|
||||
so the biboumi instance and the server should
|
||||
normally be on the same host.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
credentialsFile = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
Path to a configuration file to be merged with the settings.
|
||||
Beware not to surround "=" with spaces when setting biboumi's options in this file.
|
||||
Useful to merge a file which is better kept out of the Nix store
|
||||
because it contains sensible data like
|
||||
<link linkend="opt-services.biboumi.settings.password">password</link>.
|
||||
'';
|
||||
default = "/dev/null";
|
||||
example = "/run/keys/biboumi.cfg";
|
||||
};
|
||||
|
||||
openFirewall = mkEnableOption "opening of the identd port in the firewall";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
networking.firewall = mkIf (cfg.openFirewall && cfg.settings.identd_port != 0)
|
||||
{ allowedTCPPorts = [ cfg.settings.identd_port ]; };
|
||||
|
||||
systemd.services.biboumi = {
|
||||
description = "Biboumi, XMPP to IRC gateway";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "notify";
|
||||
# Biboumi supports systemd's watchdog.
|
||||
WatchdogSec = 20;
|
||||
Restart = "always";
|
||||
# Use "+" because credentialsFile may not be accessible to User= or Group=.
|
||||
ExecStartPre = [("+" + pkgs.writeShellScript "biboumi-prestart" ''
|
||||
set -eux
|
||||
cat ${settingsFile} '${cfg.credentialsFile}' |
|
||||
install -m 644 /dev/stdin /run/biboumi/biboumi.cfg
|
||||
'')];
|
||||
ExecStart = "${pkgs.biboumi}/bin/biboumi /run/biboumi/biboumi.cfg";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -USR1 $MAINPID";
|
||||
# Firewalls needing opening for output connections can still do that
|
||||
# selectively for biboumi with:
|
||||
# users.users.biboumi.isSystemUser = true;
|
||||
# and, for example:
|
||||
# networking.nftables.ruleset = ''
|
||||
# add rule inet filter output meta skuid biboumi tcp accept
|
||||
# '';
|
||||
DynamicUser = true;
|
||||
RootDirectory = rootDir;
|
||||
RootDirectoryStartOnly = true;
|
||||
InaccessiblePaths = [ "-+${rootDir}" ];
|
||||
RuntimeDirectory = [ "biboumi" (removePrefix "/run/" rootDir) ];
|
||||
RuntimeDirectoryMode = "700";
|
||||
StateDirectory = "biboumi";
|
||||
StateDirectoryMode = "700";
|
||||
MountAPIVFS = true;
|
||||
UMask = "0066";
|
||||
BindPaths = [
|
||||
stateDir
|
||||
# This is for Type="notify"
|
||||
# See https://github.com/systemd/systemd/issues/3544
|
||||
"/run/systemd/notify"
|
||||
"/run/systemd/journal/socket"
|
||||
];
|
||||
BindReadOnlyPaths = [
|
||||
builtins.storeDir
|
||||
"/etc"
|
||||
];
|
||||
# The following options are only for optimizing:
|
||||
# systemd-analyze security biboumi
|
||||
AmbientCapabilities = [ (optionalString need_CAP_NET_BIND_SERVICE "CAP_NET_BIND_SERVICE") ];
|
||||
CapabilityBoundingSet = [ (optionalString need_CAP_NET_BIND_SERVICE "CAP_NET_BIND_SERVICE") ];
|
||||
# ProtectClock= adds DeviceAllow=char-rtc r
|
||||
DeviceAllow = "";
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = true;
|
||||
PrivateMounts = true;
|
||||
PrivateNetwork = mkDefault false;
|
||||
PrivateTmp = true;
|
||||
# PrivateUsers=true breaks AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
# See https://bugs.archlinux.org/task/65921
|
||||
PrivateUsers = !need_CAP_NET_BIND_SERVICE;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectSystem = "strict";
|
||||
RemoveIPC = true;
|
||||
# AF_UNIX is for /run/systemd/notify
|
||||
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
# Groups in @system-service which do not contain a syscall
|
||||
# listed by perf stat -e 'syscalls:sys_enter_*' biboumi biboumi.cfg
|
||||
# in tests, and seem likely not necessary for biboumi.
|
||||
# To run such a perf in ExecStart=, you have to:
|
||||
# - AmbientCapabilities="CAP_SYS_ADMIN"
|
||||
# - mount -o remount,mode=755 /sys/kernel/debug/{,tracing}
|
||||
"~@aio" "~@chown" "~@ipc" "~@keyring" "~@resources" "~@setuid" "~@timer"
|
||||
];
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallErrorNumber = "EPERM";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ julm ];
|
||||
}
|
||||
274
nixos/modules/services/networking/bind.nix
Normal file
274
nixos/modules/services/networking/bind.nix
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.bind;
|
||||
|
||||
bindPkg = config.services.bind.package;
|
||||
|
||||
bindUser = "named";
|
||||
|
||||
bindZoneCoerce = list: builtins.listToAttrs (lib.forEach list (zone: { name = zone.name; value = zone; }));
|
||||
|
||||
bindZoneOptions = { name, config, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
description = "Name of the zone.";
|
||||
};
|
||||
master = mkOption {
|
||||
description = "Master=false means slave server";
|
||||
type = types.bool;
|
||||
};
|
||||
file = mkOption {
|
||||
type = types.either types.str types.path;
|
||||
description = "Zone file resource records contain columns of data, separated by whitespace, that define the record.";
|
||||
};
|
||||
masters = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = "List of servers for inclusion in stub and secondary zones.";
|
||||
};
|
||||
slaves = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = "Addresses who may request zone transfers.";
|
||||
default = [ ];
|
||||
};
|
||||
extraConfig = mkOption {
|
||||
type = types.str;
|
||||
description = "Extra zone config to be appended at the end of the zone section.";
|
||||
default = "";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
confFile = pkgs.writeText "named.conf"
|
||||
''
|
||||
include "/etc/bind/rndc.key";
|
||||
controls {
|
||||
inet 127.0.0.1 allow {localhost;} keys {"rndc-key";};
|
||||
};
|
||||
|
||||
acl cachenetworks { ${concatMapStrings (entry: " ${entry}; ") cfg.cacheNetworks} };
|
||||
acl badnetworks { ${concatMapStrings (entry: " ${entry}; ") cfg.blockedNetworks} };
|
||||
|
||||
options {
|
||||
listen-on { ${concatMapStrings (entry: " ${entry}; ") cfg.listenOn} };
|
||||
listen-on-v6 { ${concatMapStrings (entry: " ${entry}; ") cfg.listenOnIpv6} };
|
||||
allow-query { cachenetworks; };
|
||||
blackhole { badnetworks; };
|
||||
forward ${cfg.forward};
|
||||
forwarders { ${concatMapStrings (entry: " ${entry}; ") cfg.forwarders} };
|
||||
directory "${cfg.directory}";
|
||||
pid-file "/run/named/named.pid";
|
||||
${cfg.extraOptions}
|
||||
};
|
||||
|
||||
${cfg.extraConfig}
|
||||
|
||||
${ concatMapStrings
|
||||
({ name, file, master ? true, slaves ? [], masters ? [], extraConfig ? "" }:
|
||||
''
|
||||
zone "${name}" {
|
||||
type ${if master then "master" else "slave"};
|
||||
file "${file}";
|
||||
${ if master then
|
||||
''
|
||||
allow-transfer {
|
||||
${concatMapStrings (ip: "${ip};\n") slaves}
|
||||
};
|
||||
''
|
||||
else
|
||||
''
|
||||
masters {
|
||||
${concatMapStrings (ip: "${ip};\n") masters}
|
||||
};
|
||||
''
|
||||
}
|
||||
allow-query { any; };
|
||||
${extraConfig}
|
||||
};
|
||||
'')
|
||||
(attrValues cfg.zones) }
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.bind = {
|
||||
|
||||
enable = mkEnableOption "BIND domain name server";
|
||||
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.bind;
|
||||
defaultText = literalExpression "pkgs.bind";
|
||||
description = "The BIND package to use.";
|
||||
};
|
||||
|
||||
cacheNetworks = mkOption {
|
||||
default = [ "127.0.0.0/24" ];
|
||||
type = types.listOf types.str;
|
||||
description = "
|
||||
What networks are allowed to use us as a resolver. Note
|
||||
that this is for recursive queries -- all networks are
|
||||
allowed to query zones configured with the `zones` option.
|
||||
It is recommended that you limit cacheNetworks to avoid your
|
||||
server being used for DNS amplification attacks.
|
||||
";
|
||||
};
|
||||
|
||||
blockedNetworks = mkOption {
|
||||
default = [ ];
|
||||
type = types.listOf types.str;
|
||||
description = "
|
||||
What networks are just blocked.
|
||||
";
|
||||
};
|
||||
|
||||
ipv4Only = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "
|
||||
Only use ipv4, even if the host supports ipv6.
|
||||
";
|
||||
};
|
||||
|
||||
forwarders = mkOption {
|
||||
default = config.networking.nameservers;
|
||||
defaultText = literalExpression "config.networking.nameservers";
|
||||
type = types.listOf types.str;
|
||||
description = "
|
||||
List of servers we should forward requests to.
|
||||
";
|
||||
};
|
||||
|
||||
forward = mkOption {
|
||||
default = "first";
|
||||
type = types.enum ["first" "only"];
|
||||
description = "
|
||||
Whether to forward 'first' (try forwarding but lookup directly if forwarding fails) or 'only'.
|
||||
";
|
||||
};
|
||||
|
||||
listenOn = mkOption {
|
||||
default = [ "any" ];
|
||||
type = types.listOf types.str;
|
||||
description = "
|
||||
Interfaces to listen on.
|
||||
";
|
||||
};
|
||||
|
||||
listenOnIpv6 = mkOption {
|
||||
default = [ "any" ];
|
||||
type = types.listOf types.str;
|
||||
description = "
|
||||
Ipv6 interfaces to listen on.
|
||||
";
|
||||
};
|
||||
|
||||
directory = mkOption {
|
||||
type = types.str;
|
||||
default = "/run/named";
|
||||
description = "Working directory of BIND.";
|
||||
};
|
||||
|
||||
zones = mkOption {
|
||||
default = [ ];
|
||||
type = with types; coercedTo (listOf attrs) bindZoneCoerce (attrsOf (types.submodule bindZoneOptions));
|
||||
description = "
|
||||
List of zones we claim authority over.
|
||||
";
|
||||
example = {
|
||||
"example.com" = {
|
||||
master = false;
|
||||
file = "/var/dns/example.com";
|
||||
masters = [ "192.168.0.1" ];
|
||||
slaves = [ ];
|
||||
extraConfig = "";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "
|
||||
Extra lines to be added verbatim to the generated named configuration file.
|
||||
";
|
||||
};
|
||||
|
||||
extraOptions = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Extra lines to be added verbatim to the options section of the
|
||||
generated named configuration file.
|
||||
'';
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
type = types.path;
|
||||
default = confFile;
|
||||
defaultText = literalExpression "confFile";
|
||||
description = "
|
||||
Overridable config file to use for named. By default, that
|
||||
generated by nixos.
|
||||
";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
networking.resolvconf.useLocalResolver = mkDefault true;
|
||||
|
||||
users.users.${bindUser} =
|
||||
{
|
||||
group = bindUser;
|
||||
description = "BIND daemon user";
|
||||
isSystemUser = true;
|
||||
};
|
||||
users.groups.${bindUser} = {};
|
||||
|
||||
systemd.services.bind = {
|
||||
description = "BIND Domain Name Server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
preStart = ''
|
||||
mkdir -m 0755 -p /etc/bind
|
||||
if ! [ -f "/etc/bind/rndc.key" ]; then
|
||||
${bindPkg.out}/sbin/rndc-confgen -c /etc/bind/rndc.key -u ${bindUser} -a -A hmac-sha256 2>/dev/null
|
||||
fi
|
||||
|
||||
${pkgs.coreutils}/bin/mkdir -p /run/named
|
||||
chown ${bindUser} /run/named
|
||||
|
||||
${pkgs.coreutils}/bin/mkdir -p ${cfg.directory}
|
||||
chown ${bindUser} ${cfg.directory}
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${bindPkg.out}/sbin/named -u ${bindUser} ${optionalString cfg.ipv4Only "-4"} -c ${cfg.configFile} -f";
|
||||
ExecReload = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' reload";
|
||||
ExecStop = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' stop";
|
||||
};
|
||||
|
||||
unitConfig.Documentation = "man:named(8)";
|
||||
};
|
||||
};
|
||||
}
|
||||
269
nixos/modules/services/networking/bird-lg.nix
Normal file
269
nixos/modules/services/networking/bird-lg.nix
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.bird-lg;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.bird-lg = {
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.bird-lg;
|
||||
defaultText = literalExpression "pkgs.bird-lg";
|
||||
description = "The Bird Looking Glass package to use.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "bird-lg";
|
||||
description = "User to run the service.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "bird-lg";
|
||||
description = "Group to run the service.";
|
||||
};
|
||||
|
||||
frontend = {
|
||||
enable = mkEnableOption "Bird Looking Glass Frontend Webserver";
|
||||
|
||||
listenAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1:5000";
|
||||
description = "Address to listen on.";
|
||||
};
|
||||
|
||||
proxyPort = mkOption {
|
||||
type = types.port;
|
||||
default = 8000;
|
||||
description = "Port bird-lg-proxy is running on.";
|
||||
};
|
||||
|
||||
domain = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "dn42.lantian.pub";
|
||||
description = "Server name domain suffixes.";
|
||||
};
|
||||
|
||||
servers = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "gigsgigscloud" "hostdare" ];
|
||||
description = "Server name prefixes.";
|
||||
};
|
||||
|
||||
whois = mkOption {
|
||||
type = types.str;
|
||||
default = "whois.verisign-grs.com";
|
||||
description = "Whois server for queries.";
|
||||
};
|
||||
|
||||
dnsInterface = mkOption {
|
||||
type = types.str;
|
||||
default = "asn.cymru.com";
|
||||
description = "DNS zone to query ASN information.";
|
||||
};
|
||||
|
||||
bgpMapInfo = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "asn" "as-name" "ASName" "descr" ];
|
||||
description = "Information displayed in bgpmap.";
|
||||
};
|
||||
|
||||
titleBrand = mkOption {
|
||||
type = types.str;
|
||||
default = "Bird-lg Go";
|
||||
description = "Prefix of page titles in browser tabs.";
|
||||
};
|
||||
|
||||
netSpecificMode = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "dn42";
|
||||
description = "Apply network-specific changes for some networks.";
|
||||
};
|
||||
|
||||
protocolFilter = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "ospf" ];
|
||||
description = "Information displayed in bgpmap.";
|
||||
};
|
||||
|
||||
nameFilter = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "^ospf";
|
||||
description = "Protocol names to hide in summary tables (RE2 syntax),";
|
||||
};
|
||||
|
||||
timeout = mkOption {
|
||||
type = types.int;
|
||||
default = 120;
|
||||
description = "Time before request timed out, in seconds.";
|
||||
};
|
||||
|
||||
navbar = {
|
||||
brand = mkOption {
|
||||
type = types.str;
|
||||
default = "Bird-lg Go";
|
||||
description = "Brand to show in the navigation bar .";
|
||||
};
|
||||
|
||||
brandURL = mkOption {
|
||||
type = types.str;
|
||||
default = "/";
|
||||
description = "URL of the brand to show in the navigation bar.";
|
||||
};
|
||||
|
||||
allServers = mkOption {
|
||||
type = types.str;
|
||||
default = "ALL Servers";
|
||||
description = "Text of 'All server' button in the navigation bar.";
|
||||
};
|
||||
|
||||
allServersURL = mkOption {
|
||||
type = types.str;
|
||||
default = "all";
|
||||
description = "URL of 'All servers' button.";
|
||||
};
|
||||
};
|
||||
|
||||
extraArgs = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "
|
||||
Extra parameters documented <link xlink:href=\"https://github.com/xddxdd/bird-lg-go#frontend\">here</link>.
|
||||
";
|
||||
};
|
||||
};
|
||||
|
||||
proxy = {
|
||||
enable = mkEnableOption "Bird Looking Glass Proxy";
|
||||
|
||||
listenAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1:8000";
|
||||
description = "Address to listen on.";
|
||||
};
|
||||
|
||||
allowedIPs = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "192.168.25.52" "192.168.25.53" ];
|
||||
description = "List of IPs to allow (default all allowed).";
|
||||
};
|
||||
|
||||
birdSocket = mkOption {
|
||||
type = types.str;
|
||||
default = "/run/bird.ctl";
|
||||
example = "/var/run/bird/bird.ctl";
|
||||
description = "Bird control socket path.";
|
||||
};
|
||||
|
||||
traceroute = {
|
||||
binary = mkOption {
|
||||
type = types.str;
|
||||
default = "${pkgs.traceroute}/bin/traceroute";
|
||||
defaultText = literalExpression ''"''${pkgs.traceroute}/bin/traceroute"'';
|
||||
description = "Traceroute's binary path.";
|
||||
};
|
||||
|
||||
rawOutput = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Display traceroute output in raw format.";
|
||||
};
|
||||
};
|
||||
|
||||
extraArgs = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "
|
||||
Extra parameters documented <link xlink:href=\"https://github.com/xddxdd/bird-lg-go#proxy\">here</link>.
|
||||
";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = {
|
||||
systemd.services = {
|
||||
bird-lg-frontend = mkIf cfg.frontend.enable {
|
||||
enable = true;
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
description = "Bird Looking Glass Frontend Webserver";
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "on-failure";
|
||||
ProtectSystem = "full";
|
||||
ProtectHome = "yes";
|
||||
MemoryDenyWriteExecute = "yes";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
};
|
||||
script = ''
|
||||
${cfg.package}/bin/frontend \
|
||||
--servers ${concatStringsSep "," cfg.frontend.servers } \
|
||||
--domain ${cfg.frontend.domain} \
|
||||
--listen ${cfg.frontend.listenAddress} \
|
||||
--proxy-port ${toString cfg.frontend.proxyPort} \
|
||||
--whois ${cfg.frontend.whois} \
|
||||
--dns-interface ${cfg.frontend.dnsInterface} \
|
||||
--bgpmap-info ${concatStringsSep "," cfg.frontend.bgpMapInfo } \
|
||||
--title-brand ${cfg.frontend.titleBrand} \
|
||||
--navbar-brand ${cfg.frontend.navbar.brand} \
|
||||
--navbar-brand-url ${cfg.frontend.navbar.brandURL} \
|
||||
--navbar-all-servers ${cfg.frontend.navbar.allServers} \
|
||||
--navbar-all-url ${cfg.frontend.navbar.allServersURL} \
|
||||
--net-specific-mode ${cfg.frontend.netSpecificMode} \
|
||||
--protocol-filter ${concatStringsSep "," cfg.frontend.protocolFilter } \
|
||||
--name-filter ${cfg.frontend.nameFilter} \
|
||||
--time-out ${toString cfg.frontend.timeout} \
|
||||
${cfg.frontend.extraArgs}
|
||||
'';
|
||||
};
|
||||
|
||||
bird-lg-proxy = mkIf cfg.proxy.enable {
|
||||
enable = true;
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
description = "Bird Looking Glass Proxy";
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "on-failure";
|
||||
ProtectSystem = "full";
|
||||
ProtectHome = "yes";
|
||||
MemoryDenyWriteExecute = "yes";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
};
|
||||
script = ''
|
||||
${cfg.package}/bin/proxy \
|
||||
--allowed ${concatStringsSep "," cfg.proxy.allowedIPs } \
|
||||
--bird ${cfg.proxy.birdSocket} \
|
||||
--listen ${cfg.proxy.listenAddress} \
|
||||
--traceroute_bin ${cfg.proxy.traceroute.binary}
|
||||
--traceroute_raw ${boolToString cfg.proxy.traceroute.rawOutput}
|
||||
${cfg.proxy.extraArgs}
|
||||
'';
|
||||
};
|
||||
};
|
||||
users = mkIf (cfg.frontend.enable || cfg.proxy.enable) {
|
||||
groups."bird-lg" = mkIf (cfg.group == "bird-lg") { };
|
||||
users."bird-lg" = mkIf (cfg.user == "bird-lg") {
|
||||
description = "Bird Looking Glass user";
|
||||
extraGroups = lib.optionals (config.services.bird2.enable) [ "bird2" ];
|
||||
group = cfg.group;
|
||||
isSystemUser = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
102
nixos/modules/services/networking/bird.nix
Normal file
102
nixos/modules/services/networking/bird.nix
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkEnableOption mkIf mkOption optionalString types;
|
||||
|
||||
cfg = config.services.bird2;
|
||||
caps = [ "CAP_NET_ADMIN" "CAP_NET_BIND_SERVICE" "CAP_NET_RAW" ];
|
||||
in
|
||||
{
|
||||
###### interface
|
||||
options = {
|
||||
services.bird2 = {
|
||||
enable = mkEnableOption "BIRD Internet Routing Daemon";
|
||||
config = mkOption {
|
||||
type = types.lines;
|
||||
description = ''
|
||||
BIRD Internet Routing Daemon configuration file.
|
||||
<link xlink:href='http://bird.network.cz/'/>
|
||||
'';
|
||||
};
|
||||
checkConfig = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether the config should be checked at build time.
|
||||
When the config can't be checked during build time, for example when it includes
|
||||
other files, either disable this option or use <code>preCheckConfig</code> to create
|
||||
the included files before checking.
|
||||
'';
|
||||
};
|
||||
preCheckConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = ''
|
||||
echo "cost 100;" > include.conf
|
||||
'';
|
||||
description = ''
|
||||
Commands to execute before the config file check. The file to be checked will be
|
||||
available as <code>bird2.conf</code> in the current directory.
|
||||
|
||||
Files created with this option will not be available at service runtime, only during
|
||||
build time checking.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
imports = [
|
||||
(lib.mkRemovedOptionModule [ "services" "bird" ] "Use services.bird2 instead")
|
||||
(lib.mkRemovedOptionModule [ "services" "bird6" ] "Use services.bird2 instead")
|
||||
];
|
||||
|
||||
###### implementation
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = [ pkgs.bird ];
|
||||
|
||||
environment.etc."bird/bird2.conf".source = pkgs.writeTextFile {
|
||||
name = "bird2";
|
||||
text = cfg.config;
|
||||
checkPhase = optionalString cfg.checkConfig ''
|
||||
ln -s $out bird2.conf
|
||||
${cfg.preCheckConfig}
|
||||
${pkgs.bird}/bin/bird -d -p -c bird2.conf
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.bird2 = {
|
||||
description = "BIRD Internet Routing Daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
reloadTriggers = [ config.environment.etc."bird/bird2.conf".source ];
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
Restart = "on-failure";
|
||||
User = "bird2";
|
||||
Group = "bird2";
|
||||
ExecStart = "${pkgs.bird}/bin/bird -c /etc/bird/bird2.conf";
|
||||
ExecReload = "${pkgs.bird}/bin/birdc configure";
|
||||
ExecStop = "${pkgs.bird}/bin/birdc down";
|
||||
RuntimeDirectory = "bird";
|
||||
CapabilityBoundingSet = caps;
|
||||
AmbientCapabilities = caps;
|
||||
ProtectSystem = "full";
|
||||
ProtectHome = "yes";
|
||||
ProtectKernelTunables = true;
|
||||
ProtectControlGroups = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
SystemCallFilter = "~@cpu-emulation @debug @keyring @module @mount @obsolete @raw-io";
|
||||
MemoryDenyWriteExecute = "yes";
|
||||
};
|
||||
};
|
||||
users = {
|
||||
users.bird2 = {
|
||||
description = "BIRD Internet Routing Daemon user";
|
||||
group = "bird2";
|
||||
isSystemUser = true;
|
||||
};
|
||||
groups.bird2 = { };
|
||||
};
|
||||
};
|
||||
}
|
||||
261
nixos/modules/services/networking/bitcoind.nix
Normal file
261
nixos/modules/services/networking/bitcoind.nix
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
eachBitcoind = config.services.bitcoind;
|
||||
|
||||
rpcUserOpts = { name, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
example = "alice";
|
||||
description = ''
|
||||
Username for JSON-RPC connections.
|
||||
'';
|
||||
};
|
||||
passwordHMAC = mkOption {
|
||||
type = types.uniq (types.strMatching "[0-9a-f]+\\$[0-9a-f]{64}");
|
||||
example = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
|
||||
description = ''
|
||||
Password HMAC-SHA-256 for JSON-RPC connections. Must be a string of the
|
||||
format <SALT-HEX>$<HMAC-HEX>.
|
||||
|
||||
Tool (Python script) for HMAC generation is available here:
|
||||
<link xlink:href="https://github.com/bitcoin/bitcoin/blob/master/share/rpcauth/rpcauth.py"/>
|
||||
'';
|
||||
};
|
||||
};
|
||||
config = {
|
||||
name = mkDefault name;
|
||||
};
|
||||
};
|
||||
|
||||
bitcoindOpts = { config, lib, name, ...}: {
|
||||
options = {
|
||||
|
||||
enable = mkEnableOption "Bitcoin daemon";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.bitcoind;
|
||||
defaultText = literalExpression "pkgs.bitcoind";
|
||||
description = "The package providing bitcoin binaries.";
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/var/lib/${name}/bitcoin.conf";
|
||||
description = "The configuration file path to supply bitcoind.";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = ''
|
||||
par=16
|
||||
rpcthreads=16
|
||||
logips=1
|
||||
'';
|
||||
description = "Additional configurations to be appended to <filename>bitcoin.conf</filename>.";
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/bitcoind-${name}";
|
||||
description = "The data directory for bitcoind.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "bitcoind-${name}";
|
||||
description = "The user as which to run bitcoind.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = config.user;
|
||||
description = "The group as which to run bitcoind.";
|
||||
};
|
||||
|
||||
rpc = {
|
||||
port = mkOption {
|
||||
type = types.nullOr types.port;
|
||||
default = null;
|
||||
description = "Override the default port on which to listen for JSON-RPC connections.";
|
||||
};
|
||||
users = mkOption {
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{
|
||||
alice.passwordHMAC = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
|
||||
bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99";
|
||||
}
|
||||
'';
|
||||
type = types.attrsOf (types.submodule rpcUserOpts);
|
||||
description = "RPC user information for JSON-RPC connnections.";
|
||||
};
|
||||
};
|
||||
|
||||
pidFile = mkOption {
|
||||
type = types.path;
|
||||
default = "${config.dataDir}/bitcoind.pid";
|
||||
description = "Location of bitcoind pid file.";
|
||||
};
|
||||
|
||||
testnet = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to use the testnet instead of mainnet.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.nullOr types.port;
|
||||
default = null;
|
||||
description = "Override the default port on which to listen for connections.";
|
||||
};
|
||||
|
||||
dbCache = mkOption {
|
||||
type = types.nullOr (types.ints.between 4 16384);
|
||||
default = null;
|
||||
example = 4000;
|
||||
description = "Override the default database cache size in MiB.";
|
||||
};
|
||||
|
||||
prune = mkOption {
|
||||
type = types.nullOr (types.coercedTo
|
||||
(types.enum [ "disable" "manual" ])
|
||||
(x: if x == "disable" then 0 else 1)
|
||||
types.ints.unsigned
|
||||
);
|
||||
default = null;
|
||||
example = 10000;
|
||||
description = ''
|
||||
Reduce storage requirements by enabling pruning (deleting) of old
|
||||
blocks. This allows the pruneblockchain RPC to be called to delete
|
||||
specific blocks, and enables automatic pruning of old blocks if a
|
||||
target size in MiB is provided. This mode is incompatible with -txindex
|
||||
and -rescan. Warning: Reverting this setting requires re-downloading
|
||||
the entire blockchain. ("disable" = disable pruning blocks, "manual"
|
||||
= allow manual pruning via RPC, >=550 = automatically prune block files
|
||||
to stay under the specified target size in MiB).
|
||||
'';
|
||||
};
|
||||
|
||||
extraCmdlineOptions = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Extra command line options to pass to bitcoind.
|
||||
Run bitcoind --help to list all available options.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
|
||||
options = {
|
||||
services.bitcoind = mkOption {
|
||||
type = types.attrsOf (types.submodule bitcoindOpts);
|
||||
default = {};
|
||||
description = "Specification of one or more bitcoind instances.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (eachBitcoind != {}) {
|
||||
|
||||
assertions = flatten (mapAttrsToList (bitcoindName: cfg: [
|
||||
{
|
||||
assertion = (cfg.prune != null) -> (builtins.elem cfg.prune [ "disable" "manual" 0 1 ] || (builtins.isInt cfg.prune && cfg.prune >= 550));
|
||||
message = ''
|
||||
If set, services.bitcoind.${bitcoindName}.prune has to be "disable", "manual", 0 , 1 or >= 550.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = (cfg.rpc.users != {}) -> (cfg.configFile == null);
|
||||
message = ''
|
||||
You cannot set both services.bitcoind.${bitcoindName}.rpc.users and services.bitcoind.${bitcoindName}.configFile
|
||||
as they are exclusive. RPC user setting would have no effect if custom configFile would be used.
|
||||
'';
|
||||
}
|
||||
]) eachBitcoind);
|
||||
|
||||
environment.systemPackages = flatten (mapAttrsToList (bitcoindName: cfg: [
|
||||
cfg.package
|
||||
]) eachBitcoind);
|
||||
|
||||
systemd.services = mapAttrs' (bitcoindName: cfg: (
|
||||
nameValuePair "bitcoind-${bitcoindName}" (
|
||||
let
|
||||
configFile = pkgs.writeText "bitcoin.conf" ''
|
||||
# If Testnet is enabled, we need to add [test] section
|
||||
# otherwise, some options (e.g.: custom RPC port) will not work
|
||||
${optionalString cfg.testnet "[test]"}
|
||||
# RPC users
|
||||
${concatMapStringsSep "\n"
|
||||
(rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}")
|
||||
(attrValues cfg.rpc.users)
|
||||
}
|
||||
# Extra config options (from bitcoind nixos service)
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
in {
|
||||
description = "Bitcoin daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStart = ''
|
||||
${cfg.package}/bin/bitcoind \
|
||||
${if (cfg.configFile != null) then
|
||||
"-conf=${cfg.configFile}"
|
||||
else
|
||||
"-conf=${configFile}"
|
||||
} \
|
||||
-datadir=${cfg.dataDir} \
|
||||
-pid=${cfg.pidFile} \
|
||||
${optionalString cfg.testnet "-testnet"}\
|
||||
${optionalString (cfg.port != null) "-port=${toString cfg.port}"}\
|
||||
${optionalString (cfg.prune != null) "-prune=${toString cfg.prune}"}\
|
||||
${optionalString (cfg.dbCache != null) "-dbcache=${toString cfg.dbCache}"}\
|
||||
${optionalString (cfg.rpc.port != null) "-rpcport=${toString cfg.rpc.port}"}\
|
||||
${toString cfg.extraCmdlineOptions}
|
||||
'';
|
||||
Restart = "on-failure";
|
||||
|
||||
# Hardening measures
|
||||
PrivateTmp = "true";
|
||||
ProtectSystem = "full";
|
||||
NoNewPrivileges = "true";
|
||||
PrivateDevices = "true";
|
||||
MemoryDenyWriteExecute = "true";
|
||||
};
|
||||
}
|
||||
))) eachBitcoind;
|
||||
|
||||
systemd.tmpfiles.rules = flatten (mapAttrsToList (bitcoindName: cfg: [
|
||||
"d '${cfg.dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
|
||||
]) eachBitcoind);
|
||||
|
||||
users.users = mapAttrs' (bitcoindName: cfg: (
|
||||
nameValuePair "bitcoind-${bitcoindName}" {
|
||||
name = cfg.user;
|
||||
group = cfg.group;
|
||||
description = "Bitcoin daemon user";
|
||||
home = cfg.dataDir;
|
||||
isSystemUser = true;
|
||||
})) eachBitcoind;
|
||||
|
||||
users.groups = mapAttrs' (bitcoindName: cfg: (
|
||||
nameValuePair "${cfg.group}" { }
|
||||
)) eachBitcoind;
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ _1000101 ];
|
||||
|
||||
}
|
||||
189
nixos/modules/services/networking/bitlbee.nix
Normal file
189
nixos/modules/services/networking/bitlbee.nix
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.bitlbee;
|
||||
bitlbeeUid = config.ids.uids.bitlbee;
|
||||
|
||||
bitlbeePkg = pkgs.bitlbee.override {
|
||||
enableLibPurple = cfg.libpurple_plugins != [];
|
||||
enablePam = cfg.authBackend == "pam";
|
||||
};
|
||||
|
||||
bitlbeeConfig = pkgs.writeText "bitlbee.conf"
|
||||
''
|
||||
[settings]
|
||||
RunMode = Daemon
|
||||
ConfigDir = ${cfg.configDir}
|
||||
DaemonInterface = ${cfg.interface}
|
||||
DaemonPort = ${toString cfg.portNumber}
|
||||
AuthMode = ${cfg.authMode}
|
||||
AuthBackend = ${cfg.authBackend}
|
||||
Plugindir = ${pkgs.bitlbee-plugins cfg.plugins}/lib/bitlbee
|
||||
${lib.optionalString (cfg.hostName != "") "HostName = ${cfg.hostName}"}
|
||||
${lib.optionalString (cfg.protocols != "") "Protocols = ${cfg.protocols}"}
|
||||
${cfg.extraSettings}
|
||||
|
||||
[defaults]
|
||||
${cfg.extraDefaults}
|
||||
'';
|
||||
|
||||
purple_plugin_path =
|
||||
lib.concatMapStringsSep ":"
|
||||
(plugin: "${plugin}/lib/pidgin/:${plugin}/lib/purple-2/")
|
||||
cfg.libpurple_plugins
|
||||
;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.bitlbee = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to run the BitlBee IRC to other chat network gateway.
|
||||
Running it allows you to access the MSN, Jabber, Yahoo! and ICQ chat
|
||||
networks via an IRC client.
|
||||
'';
|
||||
};
|
||||
|
||||
interface = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
description = ''
|
||||
The interface the BitlBee deamon will be listening to. If `127.0.0.1',
|
||||
only clients on the local host can connect to it; if `0.0.0.0', clients
|
||||
can access it from any network interface.
|
||||
'';
|
||||
};
|
||||
|
||||
portNumber = mkOption {
|
||||
default = 6667;
|
||||
type = types.int;
|
||||
description = ''
|
||||
Number of the port BitlBee will be listening to.
|
||||
'';
|
||||
};
|
||||
|
||||
authBackend = mkOption {
|
||||
default = "storage";
|
||||
type = types.enum [ "storage" "pam" ];
|
||||
description = ''
|
||||
How users are authenticated
|
||||
storage -- save passwords internally
|
||||
pam -- Linux PAM authentication
|
||||
'';
|
||||
};
|
||||
|
||||
authMode = mkOption {
|
||||
default = "Open";
|
||||
type = types.enum [ "Open" "Closed" "Registered" ];
|
||||
description = ''
|
||||
The following authentication modes are available:
|
||||
Open -- Accept connections from anyone, use NickServ for user authentication.
|
||||
Closed -- Require authorization (using the PASS command during login) before allowing the user to connect at all.
|
||||
Registered -- Only allow registered users to use this server; this disables the register- and the account command until the user identifies himself.
|
||||
'';
|
||||
};
|
||||
|
||||
hostName = mkOption {
|
||||
default = "";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Normally, BitlBee gets a hostname using getsockname(). If you have a nicer
|
||||
alias for your BitlBee daemon, you can set it here and BitlBee will identify
|
||||
itself with that name instead.
|
||||
'';
|
||||
};
|
||||
|
||||
plugins = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [];
|
||||
example = literalExpression "[ pkgs.bitlbee-facebook ]";
|
||||
description = ''
|
||||
The list of bitlbee plugins to install.
|
||||
'';
|
||||
};
|
||||
|
||||
libpurple_plugins = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [];
|
||||
example = literalExpression "[ pkgs.purple-matrix ]";
|
||||
description = ''
|
||||
The list of libpurple plugins to install.
|
||||
'';
|
||||
};
|
||||
|
||||
configDir = mkOption {
|
||||
default = "/var/lib/bitlbee";
|
||||
type = types.path;
|
||||
description = ''
|
||||
Specify an alternative directory to store all the per-user configuration
|
||||
files.
|
||||
'';
|
||||
};
|
||||
|
||||
protocols = mkOption {
|
||||
default = "";
|
||||
type = types.str;
|
||||
description = ''
|
||||
This option allows to remove the support of protocol, even if compiled
|
||||
in. If nothing is given, there are no restrictions.
|
||||
'';
|
||||
};
|
||||
|
||||
extraSettings = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Will be inserted in the Settings section of the config file.
|
||||
'';
|
||||
};
|
||||
|
||||
extraDefaults = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Will be inserted in the Default section of the config file.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkMerge [
|
||||
(mkIf config.services.bitlbee.enable {
|
||||
systemd.services.bitlbee = {
|
||||
environment.PURPLE_PLUGIN_PATH = purple_plugin_path;
|
||||
description = "BitlBee IRC to other chat networks gateway";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
StateDirectory = "bitlbee";
|
||||
ExecStart = "${bitlbeePkg}/sbin/bitlbee -F -n -c ${bitlbeeConfig}";
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [ bitlbeePkg ];
|
||||
|
||||
})
|
||||
(mkIf (config.services.bitlbee.authBackend == "pam") {
|
||||
security.pam.services.bitlbee = {};
|
||||
})
|
||||
];
|
||||
|
||||
}
|
||||
278
nixos/modules/services/networking/blockbook-frontend.nix
Normal file
278
nixos/modules/services/networking/blockbook-frontend.nix
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
eachBlockbook = config.services.blockbook-frontend;
|
||||
|
||||
blockbookOpts = { config, lib, name, ...}: {
|
||||
|
||||
options = {
|
||||
|
||||
enable = mkEnableOption "blockbook-frontend application.";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.blockbook;
|
||||
defaultText = literalExpression "pkgs.blockbook";
|
||||
description = "Which blockbook package to use.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "blockbook-frontend-${name}";
|
||||
description = "The user as which to run blockbook-frontend-${name}.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "${config.user}";
|
||||
description = "The group as which to run blockbook-frontend-${name}.";
|
||||
};
|
||||
|
||||
certFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/etc/secrets/blockbook-frontend-${name}/certFile";
|
||||
description = ''
|
||||
To enable SSL, specify path to the name of certificate files without extension.
|
||||
Expecting <filename>certFile.crt</filename> and <filename>certFile.key</filename>.
|
||||
'';
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
example = "${config.dataDir}/config.json";
|
||||
description = "Location of the blockbook configuration file.";
|
||||
};
|
||||
|
||||
coinName = mkOption {
|
||||
type = types.str;
|
||||
default = "Bitcoin";
|
||||
description = ''
|
||||
See <link xlink:href="https://github.com/trezor/blockbook/blob/master/bchain/coins/blockchain.go#L61"/>
|
||||
for current of coins supported in master (Note: may differ from release).
|
||||
'';
|
||||
};
|
||||
|
||||
cssDir = mkOption {
|
||||
type = types.path;
|
||||
default = "${config.package}/share/css/";
|
||||
defaultText = literalExpression ''"''${package}/share/css/"'';
|
||||
example = literalExpression ''"''${dataDir}/static/css/"'';
|
||||
description = ''
|
||||
Location of the dir with <filename>main.css</filename> CSS file.
|
||||
By default, the one shipped with the package is used.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/blockbook-frontend-${name}";
|
||||
description = "Location of blockbook-frontend-${name} data directory.";
|
||||
};
|
||||
|
||||
debug = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Debug mode, return more verbose errors, reload templates on each request.";
|
||||
};
|
||||
|
||||
internal = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = ":9030";
|
||||
description = "Internal http server binding <literal>[address]:port</literal>.";
|
||||
};
|
||||
|
||||
messageQueueBinding = mkOption {
|
||||
type = types.str;
|
||||
default = "tcp://127.0.0.1:38330";
|
||||
description = "Message Queue Binding <literal>address:port</literal>.";
|
||||
};
|
||||
|
||||
public = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = ":9130";
|
||||
description = "Public http server binding <literal>[address]:port</literal>.";
|
||||
};
|
||||
|
||||
rpc = {
|
||||
url = mkOption {
|
||||
type = types.str;
|
||||
default = "http://127.0.0.1";
|
||||
description = "URL for JSON-RPC connections.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8030;
|
||||
description = "Port for JSON-RPC connections.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "rpc";
|
||||
description = "Username for JSON-RPC connections.";
|
||||
};
|
||||
|
||||
password = mkOption {
|
||||
type = types.str;
|
||||
default = "rpc";
|
||||
description = ''
|
||||
RPC password for JSON-RPC connections.
|
||||
Warning: this is stored in cleartext in the Nix store!!!
|
||||
Use <literal>configFile</literal> or <literal>passwordFile</literal> if needed.
|
||||
'';
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
File containing password of the RPC user.
|
||||
Note: This options is ignored when <literal>configFile</literal> is used.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
sync = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Synchronizes until tip, if together with zeromq, keeps index synchronized.";
|
||||
};
|
||||
|
||||
templateDir = mkOption {
|
||||
type = types.path;
|
||||
default = "${config.package}/share/templates/";
|
||||
defaultText = literalExpression ''"''${package}/share/templates/"'';
|
||||
example = literalExpression ''"''${dataDir}/templates/static/"'';
|
||||
description = "Location of the HTML templates. By default, ones shipped with the package are used.";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
example = literalExpression '' {
|
||||
"alternative_estimate_fee" = "whatthefee-disabled";
|
||||
"alternative_estimate_fee_params" = "{\"url\": \"https://whatthefee.io/data.json\", \"periodSeconds\": 60}";
|
||||
"fiat_rates" = "coingecko";
|
||||
"fiat_rates_params" = "{\"url\": \"https://api.coingecko.com/api/v3\", \"coin\": \"bitcoin\", \"periodSeconds\": 60}";
|
||||
"coin_shortcut" = "BTC";
|
||||
"coin_label" = "Bitcoin";
|
||||
"parse" = true;
|
||||
"subversion" = "";
|
||||
"address_format" = "";
|
||||
"xpub_magic" = 76067358;
|
||||
"xpub_magic_segwit_p2sh" = 77429938;
|
||||
"xpub_magic_segwit_native" = 78792518;
|
||||
"mempool_workers" = 8;
|
||||
"mempool_sub_workers" = 2;
|
||||
"block_addresses_to_keep" = 300;
|
||||
}'';
|
||||
description = ''
|
||||
Additional configurations to be appended to <filename>coin.conf</filename>.
|
||||
Overrides any already defined configuration options.
|
||||
See <link xlink:href="https://github.com/trezor/blockbook/tree/master/configs/coins"/>
|
||||
for current configuration options supported in master (Note: may differ from release).
|
||||
'';
|
||||
};
|
||||
|
||||
extraCmdLineOptions = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "-workers=1" "-dbcache=0" "-logtosderr" ];
|
||||
description = ''
|
||||
Extra command line options to pass to Blockbook.
|
||||
Run blockbook --help to list all available options.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
# interface
|
||||
|
||||
options = {
|
||||
services.blockbook-frontend = mkOption {
|
||||
type = types.attrsOf (types.submodule blockbookOpts);
|
||||
default = {};
|
||||
description = "Specification of one or more blockbook-frontend instances.";
|
||||
};
|
||||
};
|
||||
|
||||
# implementation
|
||||
|
||||
config = mkIf (eachBlockbook != {}) {
|
||||
|
||||
systemd.services = mapAttrs' (blockbookName: cfg: (
|
||||
nameValuePair "blockbook-frontend-${blockbookName}" (
|
||||
let
|
||||
configFile = if cfg.configFile != null then cfg.configFile else
|
||||
pkgs.writeText "config.conf" (builtins.toJSON ( {
|
||||
coin_name = "${cfg.coinName}";
|
||||
rpc_user = "${cfg.rpc.user}";
|
||||
rpc_pass = "${cfg.rpc.password}";
|
||||
rpc_url = "${cfg.rpc.url}:${toString cfg.rpc.port}";
|
||||
message_queue_binding = "${cfg.messageQueueBinding}";
|
||||
} // cfg.extraConfig)
|
||||
);
|
||||
in {
|
||||
description = "blockbook-frontend-${blockbookName} daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
preStart = ''
|
||||
ln -sf ${cfg.templateDir} ${cfg.dataDir}/static/
|
||||
ln -sf ${cfg.cssDir} ${cfg.dataDir}/static/
|
||||
${optionalString (cfg.rpc.passwordFile != null && cfg.configFile == null) ''
|
||||
CONFIGTMP=$(mktemp)
|
||||
${pkgs.jq}/bin/jq ".rpc_pass = \"$(cat ${cfg.rpc.passwordFile})\"" ${configFile} > $CONFIGTMP
|
||||
mv $CONFIGTMP ${cfg.dataDir}/${blockbookName}-config.json
|
||||
''}
|
||||
'';
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStart = ''
|
||||
${cfg.package}/bin/blockbook \
|
||||
${if (cfg.rpc.passwordFile != null && cfg.configFile == null) then
|
||||
"-blockchaincfg=${cfg.dataDir}/${blockbookName}-config.json"
|
||||
else
|
||||
"-blockchaincfg=${configFile}"
|
||||
} \
|
||||
-datadir=${cfg.dataDir} \
|
||||
${optionalString (cfg.sync != false) "-sync"} \
|
||||
${optionalString (cfg.certFile != null) "-certfile=${toString cfg.certFile}"} \
|
||||
${optionalString (cfg.debug != false) "-debug"} \
|
||||
${optionalString (cfg.internal != null) "-internal=${toString cfg.internal}"} \
|
||||
${optionalString (cfg.public != null) "-public=${toString cfg.public}"} \
|
||||
${toString cfg.extraCmdLineOptions}
|
||||
'';
|
||||
Restart = "on-failure";
|
||||
WorkingDirectory = cfg.dataDir;
|
||||
LimitNOFILE = 65536;
|
||||
};
|
||||
}
|
||||
) )) eachBlockbook;
|
||||
|
||||
systemd.tmpfiles.rules = flatten (mapAttrsToList (blockbookName: cfg: [
|
||||
"d ${cfg.dataDir} 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d ${cfg.dataDir}/static 0750 ${cfg.user} ${cfg.group} - -"
|
||||
]) eachBlockbook);
|
||||
|
||||
users.users = mapAttrs' (blockbookName: cfg: (
|
||||
nameValuePair "blockbook-frontend-${blockbookName}" {
|
||||
name = cfg.user;
|
||||
group = cfg.group;
|
||||
home = cfg.dataDir;
|
||||
isSystemUser = true;
|
||||
})) eachBlockbook;
|
||||
|
||||
users.groups = mapAttrs' (instanceName: cfg: (
|
||||
nameValuePair "${cfg.group}" { })) eachBlockbook;
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ _1000101 ];
|
||||
|
||||
}
|
||||
40
nixos/modules/services/networking/blocky.nix
Normal file
40
nixos/modules/services/networking/blocky.nix
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.blocky;
|
||||
|
||||
format = pkgs.formats.yaml { };
|
||||
configFile = format.generate "config.yaml" cfg.settings;
|
||||
in
|
||||
{
|
||||
options.services.blocky = {
|
||||
enable = mkEnableOption "Fast and lightweight DNS proxy as ad-blocker for local network with many features";
|
||||
|
||||
settings = mkOption {
|
||||
type = format.type;
|
||||
default = { };
|
||||
description = ''
|
||||
Blocky configuration. Refer to
|
||||
<link xlink:href="https://0xerr0r.github.io/blocky/configuration/"/>
|
||||
for details on supported values.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.blocky = {
|
||||
description = "A DNS proxy and ad-blocker for the local network";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
ExecStart = "${pkgs.blocky}/bin/blocky --config ${configFile}";
|
||||
|
||||
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
114
nixos/modules/services/networking/charybdis.nix
Normal file
114
nixos/modules/services/networking/charybdis.nix
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkEnableOption mkIf mkOption singleton types;
|
||||
inherit (pkgs) coreutils charybdis;
|
||||
cfg = config.services.charybdis;
|
||||
|
||||
configFile = pkgs.writeText "charybdis.conf" ''
|
||||
${cfg.config}
|
||||
'';
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.charybdis = {
|
||||
|
||||
enable = mkEnableOption "Charybdis IRC daemon";
|
||||
|
||||
config = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
Charybdis IRC daemon configuration file.
|
||||
'';
|
||||
};
|
||||
|
||||
statedir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/charybdis";
|
||||
description = ''
|
||||
Location of the state directory of charybdis.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "ircd";
|
||||
description = ''
|
||||
Charybdis IRC daemon user.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "ircd";
|
||||
description = ''
|
||||
Charybdis IRC daemon group.
|
||||
'';
|
||||
};
|
||||
|
||||
motd = mkOption {
|
||||
type = types.nullOr types.lines;
|
||||
default = null;
|
||||
description = ''
|
||||
Charybdis MOTD text.
|
||||
|
||||
Charybdis will read its MOTD from /etc/charybdis/ircd.motd .
|
||||
If set, the value of this option will be written to this path.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable (lib.mkMerge [
|
||||
{
|
||||
users.users.${cfg.user} = {
|
||||
description = "Charybdis IRC daemon user";
|
||||
uid = config.ids.uids.ircd;
|
||||
group = cfg.group;
|
||||
};
|
||||
|
||||
users.groups.${cfg.group} = {
|
||||
gid = config.ids.gids.ircd;
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.statedir} - ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
|
||||
environment.etc."charybdis/ircd.conf".source = configFile;
|
||||
|
||||
systemd.services.charybdis = {
|
||||
description = "Charybdis IRC daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
reloadIfChanged = true;
|
||||
restartTriggers = [
|
||||
configFile
|
||||
];
|
||||
environment = {
|
||||
BANDB_DBPATH = "${cfg.statedir}/ban.db";
|
||||
};
|
||||
serviceConfig = {
|
||||
ExecStart = "${charybdis}/bin/charybdis -foreground -logfile /dev/stdout -configfile /etc/charybdis/ircd.conf";
|
||||
ExecReload = "${coreutils}/bin/kill -HUP $MAINPID";
|
||||
Group = cfg.group;
|
||||
User = cfg.user;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
(mkIf (cfg.motd != null) {
|
||||
environment.etc."charybdis/ircd.motd".text = cfg.motd;
|
||||
})
|
||||
]);
|
||||
}
|
||||
304
nixos/modules/services/networking/cjdns.nix
Normal file
304
nixos/modules/services/networking/cjdns.nix
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
pkg = pkgs.cjdns;
|
||||
|
||||
cfg = config.services.cjdns;
|
||||
|
||||
connectToSubmodule =
|
||||
{ ... }:
|
||||
{ options =
|
||||
{ password = mkOption {
|
||||
type = types.str;
|
||||
description = "Authorized password to the opposite end of the tunnel.";
|
||||
};
|
||||
login = mkOption {
|
||||
default = "";
|
||||
type = types.str;
|
||||
description = "(optional) name your peer has for you";
|
||||
};
|
||||
peerName = mkOption {
|
||||
default = "";
|
||||
type = types.str;
|
||||
description = "(optional) human-readable name for peer";
|
||||
};
|
||||
publicKey = mkOption {
|
||||
type = types.str;
|
||||
description = "Public key at the opposite end of the tunnel.";
|
||||
};
|
||||
hostname = mkOption {
|
||||
default = "";
|
||||
example = "foobar.hype";
|
||||
type = types.str;
|
||||
description = "Optional hostname to add to /etc/hosts; prevents reverse lookup failures.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Additional /etc/hosts entries for peers with an associated hostname
|
||||
cjdnsExtraHosts = pkgs.runCommand "cjdns-hosts" {} ''
|
||||
exec >$out
|
||||
${concatStringsSep "\n" (mapAttrsToList (k: v:
|
||||
optionalString (v.hostname != "")
|
||||
"echo $(${pkgs.cjdns}/bin/publictoip6 ${v.publicKey}) ${v.hostname}")
|
||||
(cfg.ETHInterface.connectTo // cfg.UDPInterface.connectTo))}
|
||||
'';
|
||||
|
||||
parseModules = x:
|
||||
x // { connectTo = mapAttrs (name: value: { inherit (value) password publicKey; }) x.connectTo; };
|
||||
|
||||
cjdrouteConf = builtins.toJSON ( recursiveUpdate {
|
||||
admin = {
|
||||
bind = cfg.admin.bind;
|
||||
password = "@CJDNS_ADMIN_PASSWORD@";
|
||||
};
|
||||
authorizedPasswords = map (p: { password = p; }) cfg.authorizedPasswords;
|
||||
interfaces = {
|
||||
ETHInterface = if (cfg.ETHInterface.bind != "") then [ (parseModules cfg.ETHInterface) ] else [ ];
|
||||
UDPInterface = if (cfg.UDPInterface.bind != "") then [ (parseModules cfg.UDPInterface) ] else [ ];
|
||||
};
|
||||
|
||||
privateKey = "@CJDNS_PRIVATE_KEY@";
|
||||
|
||||
resetAfterInactivitySeconds = 100;
|
||||
|
||||
router = {
|
||||
interface = { type = "TUNInterface"; };
|
||||
ipTunnel = {
|
||||
allowedConnections = [];
|
||||
outgoingConnections = [];
|
||||
};
|
||||
};
|
||||
|
||||
security = [ { exemptAngel = 1; setuser = "nobody"; } ];
|
||||
|
||||
} cfg.extraConfig);
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
|
||||
services.cjdns = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable the cjdns network encryption
|
||||
and routing engine. A file at /etc/cjdns.keys will
|
||||
be created if it does not exist to contain a random
|
||||
secret key that your IPv6 address will be derived from.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
example = { router.interface.tunDevice = "tun10"; };
|
||||
description = ''
|
||||
Extra configuration, given as attrs, that will be merged recursively
|
||||
with the rest of the JSON generated by this module, at the root node.
|
||||
'';
|
||||
};
|
||||
|
||||
confFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/etc/cjdroute.conf";
|
||||
description = ''
|
||||
Ignore all other cjdns options and load configuration from this file.
|
||||
'';
|
||||
};
|
||||
|
||||
authorizedPasswords = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [
|
||||
"snyrfgkqsc98qh1y4s5hbu0j57xw5s0"
|
||||
"z9md3t4p45mfrjzdjurxn4wuj0d8swv"
|
||||
"49275fut6tmzu354pq70sr5b95qq0vj"
|
||||
];
|
||||
description = ''
|
||||
Any remote cjdns nodes that offer these passwords on
|
||||
connection will be allowed to route through this node.
|
||||
'';
|
||||
};
|
||||
|
||||
admin = {
|
||||
bind = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1:11234";
|
||||
description = ''
|
||||
Bind the administration port to this address and port.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
UDPInterface = {
|
||||
bind = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "192.168.1.32:43211";
|
||||
description = ''
|
||||
Address and port to bind UDP tunnels to.
|
||||
'';
|
||||
};
|
||||
connectTo = mkOption {
|
||||
type = types.attrsOf ( types.submodule ( connectToSubmodule ) );
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
"192.168.1.1:27313" = {
|
||||
hostname = "homer.hype";
|
||||
password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM";
|
||||
publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k";
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Credentials for making UDP tunnels.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
ETHInterface = {
|
||||
bind = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "eth0";
|
||||
description =
|
||||
''
|
||||
Bind to this device for native ethernet operation.
|
||||
<literal>all</literal> is a pseudo-name which will try to connect to all devices.
|
||||
'';
|
||||
};
|
||||
|
||||
beacon = mkOption {
|
||||
type = types.int;
|
||||
default = 2;
|
||||
description = ''
|
||||
Auto-connect to other cjdns nodes on the same network.
|
||||
Options:
|
||||
0: Disabled.
|
||||
1: Accept beacons, this will cause cjdns to accept incoming
|
||||
beacon messages and try connecting to the sender.
|
||||
2: Accept and send beacons, this will cause cjdns to broadcast
|
||||
messages on the local network which contain a randomly
|
||||
generated per-session password, other nodes which have this
|
||||
set to 1 or 2 will hear the beacon messages and connect
|
||||
automatically.
|
||||
'';
|
||||
};
|
||||
|
||||
connectTo = mkOption {
|
||||
type = types.attrsOf ( types.submodule ( connectToSubmodule ) );
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
"01:02:03:04:05:06" = {
|
||||
hostname = "homer.hype";
|
||||
password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM";
|
||||
publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k";
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Credentials for connecting look similar to UDP credientials
|
||||
except they begin with the mac address.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
addExtraHosts = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to add cjdns peers with an associated hostname to
|
||||
<filename>/etc/hosts</filename>. Beware that enabling this
|
||||
incurs heavy eval-time costs.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
boot.kernelModules = [ "tun" ];
|
||||
|
||||
# networking.firewall.allowedUDPPorts = ...
|
||||
|
||||
systemd.services.cjdns = {
|
||||
description = "cjdns: routing engine designed for security, scalability, speed and ease of use";
|
||||
wantedBy = [ "multi-user.target" "sleep.target"];
|
||||
after = [ "network-online.target" ];
|
||||
bindsTo = [ "network-online.target" ];
|
||||
|
||||
preStart = if cfg.confFile != null then "" else ''
|
||||
[ -e /etc/cjdns.keys ] && source /etc/cjdns.keys
|
||||
|
||||
if [ -z "$CJDNS_PRIVATE_KEY" ]; then
|
||||
shopt -s lastpipe
|
||||
${pkg}/bin/makekeys | { read private ipv6 public; }
|
||||
|
||||
umask 0077
|
||||
echo "CJDNS_PRIVATE_KEY=$private" >> /etc/cjdns.keys
|
||||
echo -e "CJDNS_IPV6=$ipv6\nCJDNS_PUBLIC_KEY=$public" > /etc/cjdns.public
|
||||
|
||||
chmod 600 /etc/cjdns.keys
|
||||
chmod 444 /etc/cjdns.public
|
||||
fi
|
||||
|
||||
if [ -z "$CJDNS_ADMIN_PASSWORD" ]; then
|
||||
echo "CJDNS_ADMIN_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)" \
|
||||
>> /etc/cjdns.keys
|
||||
fi
|
||||
'';
|
||||
|
||||
script = (
|
||||
if cfg.confFile != null then "${pkg}/bin/cjdroute < ${cfg.confFile}" else
|
||||
''
|
||||
source /etc/cjdns.keys
|
||||
(cat <<'EOF'
|
||||
${cjdrouteConf}
|
||||
EOF
|
||||
) | sed \
|
||||
-e "s/@CJDNS_ADMIN_PASSWORD@/$CJDNS_ADMIN_PASSWORD/g" \
|
||||
-e "s/@CJDNS_PRIVATE_KEY@/$CJDNS_PRIVATE_KEY/g" \
|
||||
| ${pkg}/bin/cjdroute
|
||||
''
|
||||
);
|
||||
|
||||
startLimitIntervalSec = 0;
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
Restart = "always";
|
||||
RestartSec = 1;
|
||||
CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW CAP_SETUID";
|
||||
ProtectSystem = true;
|
||||
# Doesn't work on i686, causing service to fail
|
||||
MemoryDenyWriteExecute = !pkgs.stdenv.isi686;
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
};
|
||||
};
|
||||
|
||||
networking.hostFiles = mkIf cfg.addExtraHosts [ cjdnsExtraHosts ];
|
||||
|
||||
assertions = [
|
||||
{ assertion = ( cfg.ETHInterface.bind != "" || cfg.UDPInterface.bind != "" || cfg.confFile != null );
|
||||
message = "Neither cjdns.ETHInterface.bind nor cjdns.UDPInterface.bind defined.";
|
||||
}
|
||||
{ assertion = config.networking.enableIPv6;
|
||||
message = "networking.enableIPv6 must be enabled for CJDNS to work";
|
||||
}
|
||||
];
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
93
nixos/modules/services/networking/cloudflare-dyndns.nix
Normal file
93
nixos/modules/services/networking/cloudflare-dyndns.nix
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.cloudflare-dyndns;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.cloudflare-dyndns = {
|
||||
enable = mkEnableOption "Cloudflare Dynamic DNS Client";
|
||||
|
||||
apiTokenFile = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
The path to a file containing the CloudFlare API token.
|
||||
|
||||
The file must have the form `CLOUDFLARE_API_TOKEN=...`
|
||||
'';
|
||||
};
|
||||
|
||||
domains = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = ''
|
||||
List of domain names to update records for.
|
||||
'';
|
||||
};
|
||||
|
||||
proxied = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether this is a DNS-only record, or also being proxied through CloudFlare.
|
||||
'';
|
||||
};
|
||||
|
||||
ipv4 = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to enable setting IPv4 A records.
|
||||
'';
|
||||
};
|
||||
|
||||
ipv6 = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable setting IPv6 AAAA records.
|
||||
'';
|
||||
};
|
||||
|
||||
deleteMissing = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to delete the record when no IP address is found.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.cloudflare-dyndns = {
|
||||
description = "CloudFlare Dynamic DNS Client";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
startAt = "*:0/5";
|
||||
|
||||
environment = {
|
||||
CLOUDFLARE_DOMAINS = toString cfg.domains;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
DynamicUser = true;
|
||||
StateDirectory = "cloudflare-dyndns";
|
||||
EnvironmentFile = cfg.apiTokenFile;
|
||||
ExecStart =
|
||||
let
|
||||
args = [ "--cache-file /var/lib/cloudflare-dyndns/ip.cache" ]
|
||||
++ (if cfg.ipv4 then [ "-4" ] else [ "-no-4" ])
|
||||
++ (if cfg.ipv6 then [ "-6" ] else [ "-no-6" ])
|
||||
++ optional cfg.deleteMissing "--delete-missing"
|
||||
++ optional cfg.proxied "--proxied";
|
||||
in
|
||||
"${pkgs.cloudflare-dyndns}/bin/cloudflare-dyndns ${toString args}";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
126
nixos/modules/services/networking/cntlm.nix
Normal file
126
nixos/modules/services/networking/cntlm.nix
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.cntlm;
|
||||
|
||||
configFile = if cfg.configText != "" then
|
||||
pkgs.writeText "cntlm.conf" ''
|
||||
${cfg.configText}
|
||||
''
|
||||
else
|
||||
pkgs.writeText "lighttpd.conf" ''
|
||||
# Cntlm Authentication Proxy Configuration
|
||||
Username ${cfg.username}
|
||||
Domain ${cfg.domain}
|
||||
Password ${cfg.password}
|
||||
${optionalString (cfg.netbios_hostname != "") "Workstation ${cfg.netbios_hostname}"}
|
||||
${concatMapStrings (entry: "Proxy ${entry}\n") cfg.proxy}
|
||||
${optionalString (cfg.noproxy != []) "NoProxy ${concatStringsSep ", " cfg.noproxy}"}
|
||||
|
||||
${concatMapStrings (port: ''
|
||||
Listen ${toString port}
|
||||
'') cfg.port}
|
||||
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
options.services.cntlm = {
|
||||
|
||||
enable = mkEnableOption "cntlm, which starts a local proxy";
|
||||
|
||||
username = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
Proxy account name, without the possibility to include domain name ('at' sign is interpreted literally).
|
||||
'';
|
||||
};
|
||||
|
||||
domain = mkOption {
|
||||
type = types.str;
|
||||
description = "Proxy account domain/workgroup name.";
|
||||
};
|
||||
|
||||
password = mkOption {
|
||||
default = "/etc/cntlm.password";
|
||||
type = types.str;
|
||||
description = "Proxy account password. Note: use chmod 0600 on /etc/cntlm.password for security.";
|
||||
};
|
||||
|
||||
netbios_hostname = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
The hostname of your machine.
|
||||
'';
|
||||
};
|
||||
|
||||
proxy = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
A list of NTLM/NTLMv2 authenticating HTTP proxies.
|
||||
|
||||
Parent proxy, which requires authentication. The same as proxy on the command-line, can be used more than once to specify unlimited
|
||||
number of proxies. Should one proxy fail, cntlm automatically moves on to the next one. The connect request fails only if the whole
|
||||
list of proxies is scanned and (for each request) and found to be invalid. Command-line takes precedence over the configuration file.
|
||||
'';
|
||||
example = [ "proxy.example.com:81" ];
|
||||
};
|
||||
|
||||
noproxy = mkOption {
|
||||
description = ''
|
||||
A list of domains where the proxy is skipped.
|
||||
'';
|
||||
default = [];
|
||||
type = types.listOf types.str;
|
||||
example = [ "*.example.com" "example.com" ];
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
default = [3128];
|
||||
type = types.listOf types.port;
|
||||
description = "Specifies on which ports the cntlm daemon listens.";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Additional config appended to the end of the generated <filename>cntlm.conf</filename>.";
|
||||
};
|
||||
|
||||
configText = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Verbatim contents of <filename>cntlm.conf</filename>.";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.cntlm = {
|
||||
description = "CNTLM is an NTLM / NTLM Session Response / NTLMv2 authenticating HTTP proxy";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
User = "cntlm";
|
||||
ExecStart = ''
|
||||
${pkgs.cntlm}/bin/cntlm -U cntlm -c ${configFile} -v -f
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
users.users.cntlm = {
|
||||
name = "cntlm";
|
||||
description = "cntlm system-wide daemon";
|
||||
isSystemUser = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
162
nixos/modules/services/networking/connman.nix
Normal file
162
nixos/modules/services/networking/connman.nix
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with pkgs;
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.connman;
|
||||
configFile = pkgs.writeText "connman.conf" ''
|
||||
[General]
|
||||
NetworkInterfaceBlacklist=${concatStringsSep "," cfg.networkInterfaceBlacklist}
|
||||
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
enableIwd = cfg.wifi.backend == "iwd";
|
||||
in {
|
||||
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "networking" "connman" ] [ "services" "connman" ])
|
||||
];
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.connman = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to use ConnMan for managing your network connections.
|
||||
'';
|
||||
};
|
||||
|
||||
enableVPN = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to enable ConnMan VPN service.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Configuration lines appended to the generated connman configuration file.
|
||||
'';
|
||||
};
|
||||
|
||||
networkInterfaceBlacklist = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [ "vmnet" "vboxnet" "virbr" "ifb" "ve" ];
|
||||
description = ''
|
||||
Default blacklisted interfaces, this includes NixOS containers interfaces (ve).
|
||||
'';
|
||||
};
|
||||
|
||||
wifi = {
|
||||
backend = mkOption {
|
||||
type = types.enum [ "wpa_supplicant" "iwd" ];
|
||||
default = "wpa_supplicant";
|
||||
description = ''
|
||||
Specify the Wi-Fi backend used.
|
||||
Currently supported are <option>wpa_supplicant</option> or <option>iwd</option>.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
extraFlags = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [ ];
|
||||
example = [ "--nodnsproxy" ];
|
||||
description = ''
|
||||
Extra flags to pass to connmand
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
description = "The connman package / build flavor";
|
||||
default = connman;
|
||||
defaultText = literalExpression "pkgs.connman";
|
||||
example = literalExpression "pkgs.connmanFull";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
assertions = [{
|
||||
assertion = !config.networking.useDHCP;
|
||||
message = "You can not use services.connman with networking.useDHCP";
|
||||
}{
|
||||
# TODO: connman seemingly can be used along network manager and
|
||||
# connmanFull supports this - so this should be worked out somehow
|
||||
assertion = !config.networking.networkmanager.enable;
|
||||
message = "You can not use services.connman with networking.networkmanager";
|
||||
}];
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
systemd.services.connman = {
|
||||
description = "Connection service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "syslog.target" ] ++ optional enableIwd "iwd.service";
|
||||
requires = optional enableIwd "iwd.service";
|
||||
serviceConfig = {
|
||||
Type = "dbus";
|
||||
BusName = "net.connman";
|
||||
Restart = "on-failure";
|
||||
ExecStart = toString ([
|
||||
"${cfg.package}/sbin/connmand"
|
||||
"--config=${configFile}"
|
||||
"--nodaemon"
|
||||
] ++ optional enableIwd "--wifi=iwd_agent"
|
||||
++ cfg.extraFlags);
|
||||
StandardOutput = "null";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.connman-vpn = mkIf cfg.enableVPN {
|
||||
description = "ConnMan VPN service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "syslog.target" ];
|
||||
before = [ "connman.service" ];
|
||||
serviceConfig = {
|
||||
Type = "dbus";
|
||||
BusName = "net.connman.vpn";
|
||||
ExecStart = "${cfg.package}/sbin/connman-vpnd -n";
|
||||
StandardOutput = "null";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.net-connman-vpn = mkIf cfg.enableVPN {
|
||||
description = "D-BUS Service";
|
||||
serviceConfig = {
|
||||
Name = "net.connman.vpn";
|
||||
before = [ "connman.service" ];
|
||||
ExecStart = "${cfg.package}/sbin/connman-vpnd -n";
|
||||
User = "root";
|
||||
SystemdService = "connman-vpn.service";
|
||||
};
|
||||
};
|
||||
|
||||
networking = {
|
||||
useDHCP = false;
|
||||
wireless = {
|
||||
enable = mkIf (!enableIwd) true;
|
||||
dbusControlled = true;
|
||||
iwd = mkIf enableIwd {
|
||||
enable = true;
|
||||
};
|
||||
};
|
||||
networkmanager.enable = false;
|
||||
};
|
||||
};
|
||||
}
|
||||
285
nixos/modules/services/networking/consul.nix
Normal file
285
nixos/modules/services/networking/consul.nix
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
{ config, lib, pkgs, utils, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
|
||||
dataDir = "/var/lib/consul";
|
||||
cfg = config.services.consul;
|
||||
|
||||
configOptions = {
|
||||
data_dir = dataDir;
|
||||
ui_config = {
|
||||
enabled = cfg.webUi;
|
||||
};
|
||||
} // cfg.extraConfig;
|
||||
|
||||
configFiles = [ "/etc/consul.json" "/etc/consul-addrs.json" ]
|
||||
++ cfg.extraConfigFiles;
|
||||
|
||||
devices = attrValues (filterAttrs (_: i: i != null) cfg.interface);
|
||||
systemdDevices = forEach devices
|
||||
(i: "sys-subsystem-net-devices-${utils.escapeSystemdPath i}.device");
|
||||
in
|
||||
{
|
||||
options = {
|
||||
|
||||
services.consul = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enables the consul daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.consul;
|
||||
defaultText = literalExpression "pkgs.consul";
|
||||
description = ''
|
||||
The package used for the Consul agent and CLI.
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
webUi = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enables the web interface on the consul http port.
|
||||
'';
|
||||
};
|
||||
|
||||
leaveOnStop = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
If enabled, causes a leave action to be sent when closing consul.
|
||||
This allows a clean termination of the node, but permanently removes
|
||||
it from the cluster. You probably don't want this option unless you
|
||||
are running a node which going offline in a permanent / semi-permanent
|
||||
fashion.
|
||||
'';
|
||||
};
|
||||
|
||||
interface = {
|
||||
|
||||
advertise = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
The name of the interface to pull the advertise_addr from.
|
||||
'';
|
||||
};
|
||||
|
||||
bind = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
The name of the interface to pull the bind_addr from.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
forceAddrFamily = mkOption {
|
||||
type = types.enum [ "any" "ipv4" "ipv6" ];
|
||||
default = "any";
|
||||
description = ''
|
||||
Whether to bind ipv4/ipv6 or both kind of addresses.
|
||||
'';
|
||||
};
|
||||
|
||||
forceIpv4 = mkOption {
|
||||
type = types.nullOr types.bool;
|
||||
default = null;
|
||||
description = ''
|
||||
Deprecated: Use consul.forceAddrFamily instead.
|
||||
Whether we should force the interfaces to only pull ipv4 addresses.
|
||||
'';
|
||||
};
|
||||
|
||||
dropPrivileges = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether the consul agent should be run as a non-root consul user.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
default = { };
|
||||
type = types.attrsOf types.anything;
|
||||
description = ''
|
||||
Extra configuration options which are serialized to json and added
|
||||
to the config.json file.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfigFiles = mkOption {
|
||||
default = [ ];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
Additional configuration files to pass to consul
|
||||
NOTE: These will not trigger the service to be restarted when altered.
|
||||
'';
|
||||
};
|
||||
|
||||
alerts = {
|
||||
enable = mkEnableOption "consul-alerts";
|
||||
|
||||
package = mkOption {
|
||||
description = "Package to use for consul-alerts.";
|
||||
default = pkgs.consul-alerts;
|
||||
defaultText = literalExpression "pkgs.consul-alerts";
|
||||
type = types.package;
|
||||
};
|
||||
|
||||
listenAddr = mkOption {
|
||||
description = "Api listening address.";
|
||||
default = "localhost:9000";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
consulAddr = mkOption {
|
||||
description = "Consul api listening adddress";
|
||||
default = "localhost:8500";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
watchChecks = mkOption {
|
||||
description = "Whether to enable check watcher.";
|
||||
default = true;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
watchEvents = mkOption {
|
||||
description = "Whether to enable event watcher.";
|
||||
default = true;
|
||||
type = types.bool;
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable (
|
||||
mkMerge [{
|
||||
|
||||
users.users.consul = {
|
||||
description = "Consul agent daemon user";
|
||||
isSystemUser = true;
|
||||
group = "consul";
|
||||
# The shell is needed for health checks
|
||||
shell = "/run/current-system/sw/bin/bash";
|
||||
};
|
||||
users.groups.consul = {};
|
||||
|
||||
environment = {
|
||||
etc."consul.json".text = builtins.toJSON configOptions;
|
||||
# We need consul.d to exist for consul to start
|
||||
etc."consul.d/dummy.json".text = "{ }";
|
||||
systemPackages = [ cfg.package ];
|
||||
};
|
||||
|
||||
warnings = lib.flatten [
|
||||
(lib.optional (cfg.forceIpv4 != null) ''
|
||||
The option consul.forceIpv4 is deprecated, please use
|
||||
consul.forceAddrFamily instead.
|
||||
'')
|
||||
];
|
||||
|
||||
systemd.services.consul = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ] ++ systemdDevices;
|
||||
bindsTo = systemdDevices;
|
||||
restartTriggers = [ config.environment.etc."consul.json".source ]
|
||||
++ mapAttrsToList (_: d: d.source)
|
||||
(filterAttrs (n: _: hasPrefix "consul.d/" n) config.environment.etc);
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "@${cfg.package}/bin/consul consul agent -config-dir /etc/consul.d"
|
||||
+ concatMapStrings (n: " -config-file ${n}") configFiles;
|
||||
ExecReload = "${cfg.package}/bin/consul reload";
|
||||
PermissionsStartOnly = true;
|
||||
User = if cfg.dropPrivileges then "consul" else null;
|
||||
Restart = "on-failure";
|
||||
TimeoutStartSec = "infinity";
|
||||
} // (optionalAttrs (cfg.leaveOnStop) {
|
||||
ExecStop = "${cfg.package}/bin/consul leave";
|
||||
});
|
||||
|
||||
path = with pkgs; [ iproute2 gnugrep gawk consul ];
|
||||
preStart = let
|
||||
family = if cfg.forceAddrFamily == "ipv6" then
|
||||
"-6"
|
||||
else if cfg.forceAddrFamily == "ipv4" then
|
||||
"-4"
|
||||
else
|
||||
"";
|
||||
in ''
|
||||
mkdir -m 0700 -p ${dataDir}
|
||||
chown -R consul ${dataDir}
|
||||
|
||||
# Determine interface addresses
|
||||
getAddrOnce () {
|
||||
ip ${family} addr show dev "$1" scope global \
|
||||
| awk -F '[ /\t]*' '/inet/ {print $3}' | head -n 1
|
||||
}
|
||||
getAddr () {
|
||||
ADDR="$(getAddrOnce $1)"
|
||||
LEFT=60 # Die after 1 minute
|
||||
while [ -z "$ADDR" ]; do
|
||||
sleep 1
|
||||
LEFT=$(expr $LEFT - 1)
|
||||
if [ "$LEFT" -eq "0" ]; then
|
||||
echo "Address lookup timed out"
|
||||
exit 1
|
||||
fi
|
||||
ADDR="$(getAddrOnce $1)"
|
||||
done
|
||||
echo "$ADDR"
|
||||
}
|
||||
echo "{" > /etc/consul-addrs.json
|
||||
delim=" "
|
||||
''
|
||||
+ concatStrings (flip mapAttrsToList cfg.interface (name: i:
|
||||
optionalString (i != null) ''
|
||||
echo "$delim \"${name}_addr\": \"$(getAddr "${i}")\"" >> /etc/consul-addrs.json
|
||||
delim=","
|
||||
''))
|
||||
+ ''
|
||||
echo "}" >> /etc/consul-addrs.json
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
||||
# deprecated
|
||||
(mkIf (cfg.forceIpv4 != null && cfg.forceIpv4) {
|
||||
services.consul.forceAddrFamily = "ipv4";
|
||||
})
|
||||
|
||||
(mkIf (cfg.alerts.enable) {
|
||||
systemd.services.consul-alerts = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "consul.service" ];
|
||||
|
||||
path = [ cfg.package ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = ''
|
||||
${cfg.alerts.package}/bin/consul-alerts start \
|
||||
--alert-addr=${cfg.alerts.listenAddr} \
|
||||
--consul-addr=${cfg.alerts.consulAddr} \
|
||||
${optionalString cfg.alerts.watchChecks "--watch-checks"} \
|
||||
${optionalString cfg.alerts.watchEvents "--watch-events"}
|
||||
'';
|
||||
User = if cfg.dropPrivileges then "consul" else null;
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
})
|
||||
|
||||
]);
|
||||
}
|
||||
50
nixos/modules/services/networking/coredns.nix
Normal file
50
nixos/modules/services/networking/coredns.nix
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.coredns;
|
||||
configFile = pkgs.writeText "Corefile" cfg.config;
|
||||
in {
|
||||
options.services.coredns = {
|
||||
enable = mkEnableOption "Coredns dns server";
|
||||
|
||||
config = mkOption {
|
||||
default = "";
|
||||
example = ''
|
||||
. {
|
||||
whoami
|
||||
}
|
||||
'';
|
||||
type = types.lines;
|
||||
description = "Verbatim Corefile to use. See <link xlink:href=\"https://coredns.io/manual/toc/#configuration\"/> for details.";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
default = pkgs.coredns;
|
||||
defaultText = literalExpression "pkgs.coredns";
|
||||
type = types.package;
|
||||
description = "Coredns package to use.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.coredns = {
|
||||
description = "Coredns dns server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
PermissionsStartOnly = true;
|
||||
LimitNPROC = 512;
|
||||
LimitNOFILE = 1048576;
|
||||
CapabilityBoundingSet = "cap_net_bind_service";
|
||||
AmbientCapabilities = "cap_net_bind_service";
|
||||
NoNewPrivileges = true;
|
||||
DynamicUser = true;
|
||||
ExecStart = "${getBin cfg.package}/bin/coredns -conf=${configFile}";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -SIGUSR1 $MAINPID";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
82
nixos/modules/services/networking/corerad.nix
Normal file
82
nixos/modules/services/networking/corerad.nix
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.corerad;
|
||||
settingsFormat = pkgs.formats.toml {};
|
||||
|
||||
in {
|
||||
meta.maintainers = with maintainers; [ mdlayher ];
|
||||
|
||||
options.services.corerad = {
|
||||
enable = mkEnableOption "CoreRAD IPv6 NDP RA daemon";
|
||||
|
||||
settings = mkOption {
|
||||
type = settingsFormat.type;
|
||||
example = literalExpression ''
|
||||
{
|
||||
interfaces = [
|
||||
# eth0 is an upstream interface monitoring for IPv6 router advertisements.
|
||||
{
|
||||
name = "eth0";
|
||||
monitor = true;
|
||||
}
|
||||
# eth1 is a downstream interface advertising IPv6 prefixes for SLAAC.
|
||||
{
|
||||
name = "eth1";
|
||||
advertise = true;
|
||||
prefix = [{ prefix = "::/64"; }];
|
||||
}
|
||||
];
|
||||
# Optionally enable Prometheus metrics.
|
||||
debug = {
|
||||
address = "localhost:9430";
|
||||
prometheus = true;
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Configuration for CoreRAD, see <link xlink:href="https://github.com/mdlayher/corerad/blob/main/internal/config/reference.toml"/>
|
||||
for supported values. Ignored if configFile is set.
|
||||
'';
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
type = types.path;
|
||||
example = literalExpression ''"''${pkgs.corerad}/etc/corerad/corerad.toml"'';
|
||||
description = "Path to CoreRAD TOML configuration file.";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
default = pkgs.corerad;
|
||||
defaultText = literalExpression "pkgs.corerad";
|
||||
type = types.package;
|
||||
description = "CoreRAD package to use.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# Prefer the config file over settings if both are set.
|
||||
services.corerad.configFile = mkDefault (settingsFormat.generate "corerad.toml" cfg.settings);
|
||||
|
||||
systemd.services.corerad = {
|
||||
description = "CoreRAD IPv6 NDP RA daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
LimitNPROC = 512;
|
||||
LimitNOFILE = 1048576;
|
||||
CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW";
|
||||
AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_RAW";
|
||||
NoNewPrivileges = true;
|
||||
DynamicUser = true;
|
||||
Type = "notify";
|
||||
NotifyAccess = "main";
|
||||
ExecStart = "${getBin cfg.package}/bin/corerad -c=${cfg.configFile}";
|
||||
Restart = "on-failure";
|
||||
RestartKillSignal = "SIGHUP";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
365
nixos/modules/services/networking/coturn.nix
Normal file
365
nixos/modules/services/networking/coturn.nix
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.coturn;
|
||||
pidfile = "/run/turnserver/turnserver.pid";
|
||||
configFile = pkgs.writeText "turnserver.conf" ''
|
||||
listening-port=${toString cfg.listening-port}
|
||||
tls-listening-port=${toString cfg.tls-listening-port}
|
||||
alt-listening-port=${toString cfg.alt-listening-port}
|
||||
alt-tls-listening-port=${toString cfg.alt-tls-listening-port}
|
||||
${concatStringsSep "\n" (map (x: "listening-ip=${x}") cfg.listening-ips)}
|
||||
${concatStringsSep "\n" (map (x: "relay-ip=${x}") cfg.relay-ips)}
|
||||
min-port=${toString cfg.min-port}
|
||||
max-port=${toString cfg.max-port}
|
||||
${lib.optionalString cfg.lt-cred-mech "lt-cred-mech"}
|
||||
${lib.optionalString cfg.no-auth "no-auth"}
|
||||
${lib.optionalString cfg.use-auth-secret "use-auth-secret"}
|
||||
${lib.optionalString (cfg.static-auth-secret != null) ("static-auth-secret=${cfg.static-auth-secret}")}
|
||||
${lib.optionalString (cfg.static-auth-secret-file != null) ("static-auth-secret=#static-auth-secret#")}
|
||||
realm=${cfg.realm}
|
||||
${lib.optionalString cfg.no-udp "no-udp"}
|
||||
${lib.optionalString cfg.no-tcp "no-tcp"}
|
||||
${lib.optionalString cfg.no-tls "no-tls"}
|
||||
${lib.optionalString cfg.no-dtls "no-dtls"}
|
||||
${lib.optionalString cfg.no-udp-relay "no-udp-relay"}
|
||||
${lib.optionalString cfg.no-tcp-relay "no-tcp-relay"}
|
||||
${lib.optionalString (cfg.cert != null) "cert=${cfg.cert}"}
|
||||
${lib.optionalString (cfg.pkey != null) "pkey=${cfg.pkey}"}
|
||||
${lib.optionalString (cfg.dh-file != null) ("dh-file=${cfg.dh-file}")}
|
||||
no-stdout-log
|
||||
syslog
|
||||
pidfile=${pidfile}
|
||||
${lib.optionalString cfg.secure-stun "secure-stun"}
|
||||
${lib.optionalString cfg.no-cli "no-cli"}
|
||||
cli-ip=${cfg.cli-ip}
|
||||
cli-port=${toString cfg.cli-port}
|
||||
${lib.optionalString (cfg.cli-password != null) ("cli-password=${cfg.cli-password}")}
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
in {
|
||||
options = {
|
||||
services.coturn = {
|
||||
enable = mkEnableOption "coturn TURN server";
|
||||
listening-port = mkOption {
|
||||
type = types.int;
|
||||
default = 3478;
|
||||
description = ''
|
||||
TURN listener port for UDP and TCP.
|
||||
Note: actually, TLS and DTLS sessions can connect to the
|
||||
"plain" TCP and UDP port(s), too - if allowed by configuration.
|
||||
'';
|
||||
};
|
||||
tls-listening-port = mkOption {
|
||||
type = types.int;
|
||||
default = 5349;
|
||||
description = ''
|
||||
TURN listener port for TLS.
|
||||
Note: actually, "plain" TCP and UDP sessions can connect to the TLS and
|
||||
DTLS port(s), too - if allowed by configuration. The TURN server
|
||||
"automatically" recognizes the type of traffic. Actually, two listening
|
||||
endpoints (the "plain" one and the "tls" one) are equivalent in terms of
|
||||
functionality; but we keep both endpoints to satisfy the RFC 5766 specs.
|
||||
For secure TCP connections, we currently support SSL version 3 and
|
||||
TLS version 1.0, 1.1 and 1.2.
|
||||
For secure UDP connections, we support DTLS version 1.
|
||||
'';
|
||||
};
|
||||
alt-listening-port = mkOption {
|
||||
type = types.int;
|
||||
default = cfg.listening-port + 1;
|
||||
defaultText = literalExpression "listening-port + 1";
|
||||
description = ''
|
||||
Alternative listening port for UDP and TCP listeners;
|
||||
default (or zero) value means "listening port plus one".
|
||||
This is needed for RFC 5780 support
|
||||
(STUN extension specs, NAT behavior discovery). The TURN Server
|
||||
supports RFC 5780 only if it is started with more than one
|
||||
listening IP address of the same family (IPv4 or IPv6).
|
||||
RFC 5780 is supported only by UDP protocol, other protocols
|
||||
are listening to that endpoint only for "symmetry".
|
||||
'';
|
||||
};
|
||||
alt-tls-listening-port = mkOption {
|
||||
type = types.int;
|
||||
default = cfg.tls-listening-port + 1;
|
||||
defaultText = literalExpression "tls-listening-port + 1";
|
||||
description = ''
|
||||
Alternative listening port for TLS and DTLS protocols.
|
||||
'';
|
||||
};
|
||||
listening-ips = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "203.0.113.42" "2001:DB8::42" ];
|
||||
description = ''
|
||||
Listener IP addresses of relay server.
|
||||
If no IP(s) specified in the config file or in the command line options,
|
||||
then all IPv4 and IPv6 system IPs will be used for listening.
|
||||
'';
|
||||
};
|
||||
relay-ips = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "203.0.113.42" "2001:DB8::42" ];
|
||||
description = ''
|
||||
Relay address (the local IP address that will be used to relay the
|
||||
packets to the peer).
|
||||
Multiple relay addresses may be used.
|
||||
The same IP(s) can be used as both listening IP(s) and relay IP(s).
|
||||
|
||||
If no relay IP(s) specified, then the turnserver will apply the default
|
||||
policy: it will decide itself which relay addresses to be used, and it
|
||||
will always be using the client socket IP address as the relay IP address
|
||||
of the TURN session (if the requested relay address family is the same
|
||||
as the family of the client socket).
|
||||
'';
|
||||
};
|
||||
min-port = mkOption {
|
||||
type = types.int;
|
||||
default = 49152;
|
||||
description = ''
|
||||
Lower bound of UDP relay endpoints
|
||||
'';
|
||||
};
|
||||
max-port = mkOption {
|
||||
type = types.int;
|
||||
default = 65535;
|
||||
description = ''
|
||||
Upper bound of UDP relay endpoints
|
||||
'';
|
||||
};
|
||||
lt-cred-mech = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Use long-term credential mechanism.
|
||||
'';
|
||||
};
|
||||
no-auth = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
This option is opposite to lt-cred-mech.
|
||||
(TURN Server with no-auth option allows anonymous access).
|
||||
If neither option is defined, and no users are defined,
|
||||
then no-auth is default. If at least one user is defined,
|
||||
in this file or in command line or in usersdb file, then
|
||||
lt-cred-mech is default.
|
||||
'';
|
||||
};
|
||||
use-auth-secret = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
TURN REST API flag.
|
||||
Flag that sets a special authorization option that is based upon authentication secret.
|
||||
This feature can be used with the long-term authentication mechanism, only.
|
||||
This feature purpose is to support "TURN Server REST API", see
|
||||
"TURN REST API" link in the project's page
|
||||
https://github.com/coturn/coturn/
|
||||
|
||||
This option is used with timestamp:
|
||||
|
||||
usercombo -> "timestamp:userid"
|
||||
turn user -> usercombo
|
||||
turn password -> base64(hmac(secret key, usercombo))
|
||||
|
||||
This allows TURN credentials to be accounted for a specific user id.
|
||||
If you don't have a suitable id, the timestamp alone can be used.
|
||||
This option is just turning on secret-based authentication.
|
||||
The actual value of the secret is defined either by option static-auth-secret,
|
||||
or can be found in the turn_secret table in the database.
|
||||
'';
|
||||
};
|
||||
static-auth-secret = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
'Static' authentication secret value (a string) for TURN REST API only.
|
||||
If not set, then the turn server
|
||||
will try to use the 'dynamic' value in turn_secret table
|
||||
in user database (if present). The database-stored value can be changed on-the-fly
|
||||
by a separate program, so this is why that other mode is 'dynamic'.
|
||||
'';
|
||||
};
|
||||
static-auth-secret-file = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to the file containing the static authentication secret.
|
||||
'';
|
||||
};
|
||||
realm = mkOption {
|
||||
type = types.str;
|
||||
default = config.networking.hostName;
|
||||
defaultText = literalExpression "config.networking.hostName";
|
||||
example = "example.com";
|
||||
description = ''
|
||||
The default realm to be used for the users when no explicit
|
||||
origin/realm relationship was found in the database, or if the TURN
|
||||
server is not using any database (just the commands-line settings
|
||||
and the userdb file). Must be used with long-term credentials
|
||||
mechanism or with TURN REST API.
|
||||
'';
|
||||
};
|
||||
cert = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "/var/lib/acme/example.com/fullchain.pem";
|
||||
description = ''
|
||||
Certificate file in PEM format.
|
||||
'';
|
||||
};
|
||||
pkey = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "/var/lib/acme/example.com/key.pem";
|
||||
description = ''
|
||||
Private key file in PEM format.
|
||||
'';
|
||||
};
|
||||
dh-file = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Use custom DH TLS key, stored in PEM format in the file.
|
||||
'';
|
||||
};
|
||||
secure-stun = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Require authentication of the STUN Binding request.
|
||||
By default, the clients are allowed anonymous access to the STUN Binding functionality.
|
||||
'';
|
||||
};
|
||||
no-cli = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Turn OFF the CLI support.
|
||||
'';
|
||||
};
|
||||
cli-ip = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
description = ''
|
||||
Local system IP address to be used for CLI server endpoint.
|
||||
'';
|
||||
};
|
||||
cli-port = mkOption {
|
||||
type = types.int;
|
||||
default = 5766;
|
||||
description = ''
|
||||
CLI server port.
|
||||
'';
|
||||
};
|
||||
cli-password = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
CLI access password.
|
||||
For the security reasons, it is recommended to use the encrypted
|
||||
for of the password (see the -P command in the turnadmin utility).
|
||||
'';
|
||||
};
|
||||
no-udp = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Disable UDP client listener";
|
||||
};
|
||||
no-tcp = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Disable TCP client listener";
|
||||
};
|
||||
no-tls = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Disable TLS client listener";
|
||||
};
|
||||
no-dtls = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Disable DTLS client listener";
|
||||
};
|
||||
no-udp-relay = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Disable UDP relay endpoints";
|
||||
};
|
||||
no-tcp-relay = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Disable TCP relay endpoints";
|
||||
};
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Additional configuration options";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable (mkMerge ([
|
||||
{ assertions = [
|
||||
{ assertion = cfg.static-auth-secret != null -> cfg.static-auth-secret-file == null ;
|
||||
message = "static-auth-secret and static-auth-secret-file cannot be set at the same time";
|
||||
}
|
||||
];}
|
||||
|
||||
{
|
||||
users.users.turnserver =
|
||||
{ uid = config.ids.uids.turnserver;
|
||||
group = "turnserver";
|
||||
description = "coturn TURN server user";
|
||||
};
|
||||
users.groups.turnserver =
|
||||
{ gid = config.ids.gids.turnserver;
|
||||
members = [ "turnserver" ];
|
||||
};
|
||||
|
||||
systemd.services.coturn = let
|
||||
runConfig = "/run/coturn/turnserver.cfg";
|
||||
in {
|
||||
description = "coturn TURN server";
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
unitConfig = {
|
||||
Documentation = "man:coturn(1) man:turnadmin(1) man:turnserver(1)";
|
||||
};
|
||||
|
||||
preStart = ''
|
||||
cat ${configFile} > ${runConfig}
|
||||
${optionalString (cfg.static-auth-secret-file != null) ''
|
||||
STATIC_AUTH_SECRET="$(head -n1 ${cfg.static-auth-secret-file} || :)"
|
||||
sed -e "s,#static-auth-secret#,$STATIC_AUTH_SECRET,g" \
|
||||
-i ${runConfig}
|
||||
'' }
|
||||
chmod 640 ${runConfig}
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${pkgs.coturn}/bin/turnserver -c ${runConfig}";
|
||||
RuntimeDirectory = "turnserver";
|
||||
User = "turnserver";
|
||||
Group = "turnserver";
|
||||
AmbientCapabilities =
|
||||
mkIf (
|
||||
cfg.listening-port < 1024 ||
|
||||
cfg.alt-listening-port < 1024 ||
|
||||
cfg.tls-listening-port < 1024 ||
|
||||
cfg.alt-tls-listening-port < 1024 ||
|
||||
cfg.min-port < 1024
|
||||
) "cap_net_bind_service";
|
||||
Restart = "on-abort";
|
||||
};
|
||||
};
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /run/coturn 0700 turnserver turnserver - -"
|
||||
];
|
||||
}]));
|
||||
}
|
||||
50
nixos/modules/services/networking/create_ap.nix
Normal file
50
nixos/modules/services/networking/create_ap.nix
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.create_ap;
|
||||
configFile = pkgs.writeText "create_ap.conf" (generators.toKeyValue { } cfg.settings);
|
||||
in {
|
||||
options = {
|
||||
services.create_ap = {
|
||||
enable = mkEnableOption "setup wifi hotspots using create_ap";
|
||||
settings = mkOption {
|
||||
type = with types; attrsOf (oneOf [ int bool str ]);
|
||||
default = {};
|
||||
description = ''
|
||||
Configuration for <package>create_ap</package>.
|
||||
See <link xlink:href="https://raw.githubusercontent.com/lakinduakash/linux-wifi-hotspot/master/src/scripts/create_ap.conf">upstream example configuration</link>
|
||||
for supported values.
|
||||
'';
|
||||
example = {
|
||||
INTERNET_IFACE = "eth0";
|
||||
WIFI_IFACE = "wlan0";
|
||||
SSID = "My Wifi Hotspot";
|
||||
PASSPHRASE = "12345678";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd = {
|
||||
services.create_ap = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
description = "Create AP Service";
|
||||
after = [ "network.target" ];
|
||||
restartTriggers = [ configFile ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.linux-wifi-hotspot}/bin/create_ap --config ${configFile}";
|
||||
KillSignal = "SIGINT";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ onny ];
|
||||
|
||||
}
|
||||
86
nixos/modules/services/networking/croc.nix
Normal file
86
nixos/modules/services/networking/croc.nix
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
inherit (lib) types;
|
||||
cfg = config.services.croc;
|
||||
rootDir = "/run/croc";
|
||||
in
|
||||
{
|
||||
options.services.croc = {
|
||||
enable = lib.mkEnableOption "croc relay";
|
||||
ports = lib.mkOption {
|
||||
type = with types; listOf port;
|
||||
default = [9009 9010 9011 9012 9013];
|
||||
description = "Ports of the relay.";
|
||||
};
|
||||
pass = lib.mkOption {
|
||||
type = with types; either path str;
|
||||
default = "pass123";
|
||||
description = "Password or passwordfile for the relay.";
|
||||
};
|
||||
openFirewall = lib.mkEnableOption "opening of the peer port(s) in the firewall";
|
||||
debug = lib.mkEnableOption "debug logs";
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.services.croc = {
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.croc}/bin/croc --pass '${cfg.pass}' ${lib.optionalString cfg.debug "--debug"} relay --ports ${lib.concatMapStringsSep "," toString cfg.ports}";
|
||||
# The following options are only for optimizing:
|
||||
# systemd-analyze security croc
|
||||
AmbientCapabilities = "";
|
||||
CapabilityBoundingSet = "";
|
||||
DynamicUser = true;
|
||||
# ProtectClock= adds DeviceAllow=char-rtc r
|
||||
DeviceAllow = "";
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
MountAPIVFS = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = true;
|
||||
PrivateMounts = true;
|
||||
PrivateNetwork = lib.mkDefault false;
|
||||
PrivateTmp = true;
|
||||
PrivateUsers = true;
|
||||
ProcSubset = "pid";
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
ProtectSystem = "strict";
|
||||
RemoveIPC = true;
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
RootDirectory = rootDir;
|
||||
# Avoid mounting rootDir in the own rootDir of ExecStart='s mount namespace.
|
||||
InaccessiblePaths = [ "-+${rootDir}" ];
|
||||
BindReadOnlyPaths = [
|
||||
builtins.storeDir
|
||||
] ++ lib.optional (types.path.check cfg.pass) cfg.pass;
|
||||
# This is for BindReadOnlyPaths=
|
||||
# to allow traversal of directories they create in RootDirectory=.
|
||||
UMask = "0066";
|
||||
# Create rootDir in the host's mount namespace.
|
||||
RuntimeDirectory = [(baseNameOf rootDir)];
|
||||
RuntimeDirectoryMode = "700";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@aio" "~@keyring" "~@memlock" "~@privileged" "~@resources" "~@setuid" "~@sync" "~@timer"
|
||||
];
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallErrorNumber = "EPERM";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall cfg.ports;
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ hax404 julm ];
|
||||
}
|
||||
62
nixos/modules/services/networking/dante.nix
Normal file
62
nixos/modules/services/networking/dante.nix
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.dante;
|
||||
confFile = pkgs.writeText "dante-sockd.conf" ''
|
||||
user.privileged: root
|
||||
user.unprivileged: dante
|
||||
logoutput: syslog
|
||||
|
||||
${cfg.config}
|
||||
'';
|
||||
in
|
||||
|
||||
{
|
||||
meta = {
|
||||
maintainers = with maintainers; [ arobyn ];
|
||||
};
|
||||
|
||||
options = {
|
||||
services.dante = {
|
||||
enable = mkEnableOption "Dante SOCKS proxy";
|
||||
|
||||
config = mkOption {
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Contents of Dante's configuration file.
|
||||
NOTE: user.privileged, user.unprivileged and logoutput are set by the service.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{ assertion = cfg.config != "";
|
||||
message = "please provide Dante configuration file contents";
|
||||
}
|
||||
];
|
||||
|
||||
users.users.dante = {
|
||||
description = "Dante SOCKS proxy daemon user";
|
||||
isSystemUser = true;
|
||||
group = "dante";
|
||||
};
|
||||
users.groups.dante = {};
|
||||
|
||||
systemd.services.dante = {
|
||||
description = "Dante SOCKS v4 and v5 compatible proxy server";
|
||||
after = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${pkgs.dante}/bin/sockd -f ${confFile}";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
# Can crash sometimes; see https://github.com/NixOS/nixpkgs/pull/39005#issuecomment-381828708
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
239
nixos/modules/services/networking/ddclient.nix
Normal file
239
nixos/modules/services/networking/ddclient.nix
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.ddclient;
|
||||
boolToStr = bool: if bool then "yes" else "no";
|
||||
dataDir = "/var/lib/ddclient";
|
||||
StateDirectory = builtins.baseNameOf dataDir;
|
||||
RuntimeDirectory = StateDirectory;
|
||||
|
||||
configFile' = pkgs.writeText "ddclient.conf" ''
|
||||
# This file can be used as a template for configFile or is automatically generated by Nix options.
|
||||
cache=${dataDir}/ddclient.cache
|
||||
foreground=YES
|
||||
use=${cfg.use}
|
||||
login=${cfg.username}
|
||||
password=${lib.optionalString (cfg.protocol == "nsupdate") "/run/${RuntimeDirectory}/ddclient.key"}
|
||||
protocol=${cfg.protocol}
|
||||
${lib.optionalString (cfg.script != "") "script=${cfg.script}"}
|
||||
${lib.optionalString (cfg.server != "") "server=${cfg.server}"}
|
||||
${lib.optionalString (cfg.zone != "") "zone=${cfg.zone}"}
|
||||
ssl=${boolToStr cfg.ssl}
|
||||
wildcard=YES
|
||||
ipv6=${boolToStr cfg.ipv6}
|
||||
quiet=${boolToStr cfg.quiet}
|
||||
verbose=${boolToStr cfg.verbose}
|
||||
${cfg.extraConfig}
|
||||
${lib.concatStringsSep "," cfg.domains}
|
||||
'';
|
||||
configFile = if (cfg.configFile != null) then cfg.configFile else configFile';
|
||||
|
||||
preStart = ''
|
||||
install ${configFile} /run/${RuntimeDirectory}/ddclient.conf
|
||||
${lib.optionalString (cfg.configFile == null) (if (cfg.protocol == "nsupdate") then ''
|
||||
install ${cfg.passwordFile} /run/${RuntimeDirectory}/ddclient.key
|
||||
'' else if (cfg.passwordFile != null) then ''
|
||||
password=$(printf "%q" "$(head -n 1 "${cfg.passwordFile}")")
|
||||
sed -i "s|^password=$|password=$password|" /run/${RuntimeDirectory}/ddclient.conf
|
||||
'' else ''
|
||||
sed -i '/^password=$/d' /run/${RuntimeDirectory}/ddclient.conf
|
||||
'')}
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
|
||||
imports = [
|
||||
(mkChangedOptionModule [ "services" "ddclient" "domain" ] [ "services" "ddclient" "domains" ]
|
||||
(config:
|
||||
let value = getAttrFromPath [ "services" "ddclient" "domain" ] config;
|
||||
in if value != "" then [ value ] else []))
|
||||
(mkRemovedOptionModule [ "services" "ddclient" "homeDir" ] "")
|
||||
(mkRemovedOptionModule [ "services" "ddclient" "password" ] "Use services.ddclient.passwordFile instead.")
|
||||
];
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.ddclient = with lib.types; {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = bool;
|
||||
description = ''
|
||||
Whether to synchronise your machine's IP address with a dynamic DNS provider (e.g. dyndns.org).
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = package;
|
||||
default = pkgs.ddclient;
|
||||
defaultText = "pkgs.ddclient";
|
||||
description = ''
|
||||
The ddclient executable package run by the service.
|
||||
'';
|
||||
};
|
||||
|
||||
domains = mkOption {
|
||||
default = [ "" ];
|
||||
type = listOf str;
|
||||
description = ''
|
||||
Domain name(s) to synchronize.
|
||||
'';
|
||||
};
|
||||
|
||||
username = mkOption {
|
||||
# For `nsupdate` username contains the path to the nsupdate executable
|
||||
default = lib.optionalString (config.services.ddclient.protocol == "nsupdate") "${pkgs.bind.dnsutils}/bin/nsupdate";
|
||||
defaultText = "";
|
||||
type = str;
|
||||
description = ''
|
||||
User name.
|
||||
'';
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
default = null;
|
||||
type = nullOr str;
|
||||
description = ''
|
||||
A file containing the password or a TSIG key in named format when using the nsupdate protocol.
|
||||
'';
|
||||
};
|
||||
|
||||
interval = mkOption {
|
||||
default = "10min";
|
||||
type = str;
|
||||
description = ''
|
||||
The interval at which to run the check and update.
|
||||
See <command>man 7 systemd.time</command> for the format.
|
||||
'';
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
default = null;
|
||||
type = nullOr path;
|
||||
description = ''
|
||||
Path to configuration file.
|
||||
When set this overrides the generated configuration from module options.
|
||||
'';
|
||||
example = "/root/nixos/secrets/ddclient.conf";
|
||||
};
|
||||
|
||||
protocol = mkOption {
|
||||
default = "dyndns2";
|
||||
type = str;
|
||||
description = ''
|
||||
Protocol to use with dynamic DNS provider (see https://sourceforge.net/p/ddclient/wiki/protocols).
|
||||
'';
|
||||
};
|
||||
|
||||
server = mkOption {
|
||||
default = "";
|
||||
type = str;
|
||||
description = ''
|
||||
Server address.
|
||||
'';
|
||||
};
|
||||
|
||||
ssl = mkOption {
|
||||
default = true;
|
||||
type = bool;
|
||||
description = ''
|
||||
Whether to use SSL/TLS to connect to dynamic DNS provider.
|
||||
'';
|
||||
};
|
||||
|
||||
ipv6 = mkOption {
|
||||
default = false;
|
||||
type = bool;
|
||||
description = ''
|
||||
Whether to use IPv6.
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
quiet = mkOption {
|
||||
default = false;
|
||||
type = bool;
|
||||
description = ''
|
||||
Print no messages for unnecessary updates.
|
||||
'';
|
||||
};
|
||||
|
||||
script = mkOption {
|
||||
default = "";
|
||||
type = str;
|
||||
description = ''
|
||||
script as required by some providers.
|
||||
'';
|
||||
};
|
||||
|
||||
use = mkOption {
|
||||
default = "web, web=checkip.dyndns.com/, web-skip='Current IP Address: '";
|
||||
type = str;
|
||||
description = ''
|
||||
Method to determine the IP address to send to the dynamic DNS provider.
|
||||
'';
|
||||
};
|
||||
|
||||
verbose = mkOption {
|
||||
default = false;
|
||||
type = bool;
|
||||
description = ''
|
||||
Print verbose information.
|
||||
'';
|
||||
};
|
||||
|
||||
zone = mkOption {
|
||||
default = "";
|
||||
type = str;
|
||||
description = ''
|
||||
zone as required by some providers.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
default = "";
|
||||
type = lines;
|
||||
description = ''
|
||||
Extra configuration. Contents will be added verbatim to the configuration file.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf config.services.ddclient.enable {
|
||||
systemd.services.ddclient = {
|
||||
description = "Dynamic DNS Client";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
restartTriggers = optional (cfg.configFile != null) cfg.configFile;
|
||||
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
RuntimeDirectoryMode = "0700";
|
||||
inherit RuntimeDirectory;
|
||||
inherit StateDirectory;
|
||||
Type = "oneshot";
|
||||
ExecStartPre = "!${pkgs.writeShellScript "ddclient-prestart" preStart}";
|
||||
ExecStart = "${lib.getBin cfg.package}/bin/ddclient -file /run/${RuntimeDirectory}/ddclient.conf";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.ddclient = {
|
||||
description = "Run ddclient";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnBootSec = cfg.interval;
|
||||
OnUnitInactiveSec = cfg.interval;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
250
nixos/modules/services/networking/dhcpcd.nix
Normal file
250
nixos/modules/services/networking/dhcpcd.nix
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
dhcpcd = if !config.boot.isContainer then pkgs.dhcpcd else pkgs.dhcpcd.override { udev = null; };
|
||||
|
||||
cfg = config.networking.dhcpcd;
|
||||
|
||||
interfaces = attrValues config.networking.interfaces;
|
||||
|
||||
enableDHCP = config.networking.dhcpcd.enable &&
|
||||
(config.networking.useDHCP || any (i: i.useDHCP == true) interfaces);
|
||||
|
||||
# Don't start dhcpcd on explicitly configured interfaces or on
|
||||
# interfaces that are part of a bridge, bond or sit device.
|
||||
ignoredInterfaces =
|
||||
map (i: i.name) (filter (i: if i.useDHCP != null then !i.useDHCP else i.ipv4.addresses != [ ]) interfaces)
|
||||
++ mapAttrsToList (i: _: i) config.networking.sits
|
||||
++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.bridges))
|
||||
++ flatten (concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues config.networking.vswitches))
|
||||
++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.bonds))
|
||||
++ config.networking.dhcpcd.denyInterfaces;
|
||||
|
||||
arrayAppendOrNull = a1: a2: if a1 == null && a2 == null then null
|
||||
else if a1 == null then a2 else if a2 == null then a1
|
||||
else a1 ++ a2;
|
||||
|
||||
# If dhcp is disabled but explicit interfaces are enabled,
|
||||
# we need to provide dhcp just for those interfaces.
|
||||
allowInterfaces = arrayAppendOrNull cfg.allowInterfaces
|
||||
(if !config.networking.useDHCP && enableDHCP then
|
||||
map (i: i.name) (filter (i: i.useDHCP == true) interfaces) else null);
|
||||
|
||||
# Config file adapted from the one that ships with dhcpcd.
|
||||
dhcpcdConf = pkgs.writeText "dhcpcd.conf"
|
||||
''
|
||||
# Inform the DHCP server of our hostname for DDNS.
|
||||
hostname
|
||||
|
||||
# A list of options to request from the DHCP server.
|
||||
option domain_name_servers, domain_name, domain_search, host_name
|
||||
option classless_static_routes, ntp_servers, interface_mtu
|
||||
|
||||
# A ServerID is required by RFC2131.
|
||||
# Commented out because of many non-compliant DHCP servers in the wild :(
|
||||
#require dhcp_server_identifier
|
||||
|
||||
# A hook script is provided to lookup the hostname if not set by
|
||||
# the DHCP server, but it should not be run by default.
|
||||
nohook lookup-hostname
|
||||
|
||||
# Ignore peth* devices; on Xen, they're renamed physical
|
||||
# Ethernet cards used for bridging. Likewise for vif* and tap*
|
||||
# (Xen) and virbr* and vnet* (libvirt).
|
||||
denyinterfaces ${toString ignoredInterfaces} lo peth* vif* tap* tun* virbr* vnet* vboxnet* sit*
|
||||
|
||||
# Use the list of allowed interfaces if specified
|
||||
${optionalString (allowInterfaces != null) "allowinterfaces ${toString allowInterfaces}"}
|
||||
|
||||
# Immediately fork to background if specified, otherwise wait for IP address to be assigned
|
||||
${{
|
||||
background = "background";
|
||||
any = "waitip";
|
||||
ipv4 = "waitip 4";
|
||||
ipv6 = "waitip 6";
|
||||
both = "waitip 4\nwaitip 6";
|
||||
if-carrier-up = "";
|
||||
}.${cfg.wait}}
|
||||
|
||||
${optionalString (config.networking.enableIPv6 == false) ''
|
||||
# Don't solicit or accept IPv6 Router Advertisements and DHCPv6 if disabled IPv6
|
||||
noipv6
|
||||
''}
|
||||
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
|
||||
exitHook = pkgs.writeText "dhcpcd.exit-hook"
|
||||
''
|
||||
if [ "$reason" = BOUND -o "$reason" = REBOOT ]; then
|
||||
# Restart ntpd. We need to restart it to make sure that it
|
||||
# will actually do something: if ntpd cannot resolve the
|
||||
# server hostnames in its config file, then it will never do
|
||||
# anything ever again ("couldn't resolve ..., giving up on
|
||||
# it"), so we silently lose time synchronisation. This also
|
||||
# applies to openntpd.
|
||||
/run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd.service openntpd.service chronyd.service || true
|
||||
fi
|
||||
|
||||
${cfg.runHook}
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
networking.dhcpcd.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to enable dhcpcd for device configuration. This is mainly to
|
||||
explicitly disable dhcpcd (for example when using networkd).
|
||||
'';
|
||||
};
|
||||
|
||||
networking.dhcpcd.persistent = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whenever to leave interfaces configured on dhcpcd daemon
|
||||
shutdown. Set to true if you have your root or store mounted
|
||||
over the network or this machine accepts SSH connections
|
||||
through DHCP interfaces and clients should be notified when
|
||||
it shuts down.
|
||||
'';
|
||||
};
|
||||
|
||||
networking.dhcpcd.denyInterfaces = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Disable the DHCP client for any interface whose name matches
|
||||
any of the shell glob patterns in this list. The purpose of
|
||||
this option is to blacklist virtual interfaces such as those
|
||||
created by Xen, libvirt, LXC, etc.
|
||||
'';
|
||||
};
|
||||
|
||||
networking.dhcpcd.allowInterfaces = mkOption {
|
||||
type = types.nullOr (types.listOf types.str);
|
||||
default = null;
|
||||
description = ''
|
||||
Enable the DHCP client for any interface whose name matches
|
||||
any of the shell glob patterns in this list. Any interface not
|
||||
explicitly matched by this pattern will be denied. This pattern only
|
||||
applies when non-null.
|
||||
'';
|
||||
};
|
||||
|
||||
networking.dhcpcd.extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Literal string to append to the config file generated for dhcpcd.
|
||||
'';
|
||||
};
|
||||
|
||||
networking.dhcpcd.runHook = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = "if [[ $reason =~ BOUND ]]; then echo $interface: Routers are $new_routers - were $old_routers; fi";
|
||||
description = ''
|
||||
Shell code that will be run after all other hooks. See
|
||||
`man dhcpcd-run-hooks` for details on what is possible.
|
||||
'';
|
||||
};
|
||||
|
||||
networking.dhcpcd.wait = mkOption {
|
||||
type = types.enum [ "background" "any" "ipv4" "ipv6" "both" "if-carrier-up" ];
|
||||
default = "any";
|
||||
description = ''
|
||||
This option specifies when the dhcpcd service will fork to background.
|
||||
If set to "background", dhcpcd will fork to background immediately.
|
||||
If set to "ipv4" or "ipv6", dhcpcd will wait for the corresponding IP
|
||||
address to be assigned. If set to "any", dhcpcd will wait for any type
|
||||
(IPv4 or IPv6) to be assigned. If set to "both", dhcpcd will wait for
|
||||
both an IPv4 and an IPv6 address before forking.
|
||||
The option "if-carrier-up" is equivalent to "any" if either ethernet
|
||||
is plugged nor WiFi is powered, and to "background" otherwise.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf enableDHCP {
|
||||
|
||||
assertions = [ {
|
||||
# dhcpcd doesn't start properly with malloc ∉ [ libc scudo ]
|
||||
# see https://github.com/NixOS/nixpkgs/issues/151696
|
||||
assertion =
|
||||
dhcpcd.enablePrivSep
|
||||
-> elem config.environment.memoryAllocator.provider [ "libc" "scudo" ];
|
||||
message = ''
|
||||
dhcpcd with privilege separation is incompatible with chosen system malloc.
|
||||
Currently only the `libc` and `scudo` allocators are known to work.
|
||||
To disable dhcpcd's privilege separation, overlay Nixpkgs and override dhcpcd
|
||||
to set `enablePrivSep = false`.
|
||||
'';
|
||||
} ];
|
||||
|
||||
systemd.services.dhcpcd = let
|
||||
cfgN = config.networking;
|
||||
hasDefaultGatewaySet = (cfgN.defaultGateway != null && cfgN.defaultGateway.address != "")
|
||||
&& (!cfgN.enableIPv6 || (cfgN.defaultGateway6 != null && cfgN.defaultGateway6.address != ""));
|
||||
in
|
||||
{ description = "DHCP Client";
|
||||
|
||||
wantedBy = [ "multi-user.target" ] ++ optional (!hasDefaultGatewaySet) "network-online.target";
|
||||
wants = [ "network.target" ];
|
||||
before = [ "network-online.target" ];
|
||||
|
||||
restartTriggers = [ exitHook ];
|
||||
|
||||
# Stopping dhcpcd during a reconfiguration is undesirable
|
||||
# because it brings down the network interfaces configured by
|
||||
# dhcpcd. So do a "systemctl restart" instead.
|
||||
stopIfChanged = false;
|
||||
|
||||
path = [ dhcpcd pkgs.nettools pkgs.openresolv ];
|
||||
|
||||
unitConfig.ConditionCapability = "CAP_NET_ADMIN";
|
||||
|
||||
serviceConfig =
|
||||
{ Type = "forking";
|
||||
PIDFile = "/run/dhcpcd/pid";
|
||||
RuntimeDirectory = "dhcpcd";
|
||||
ExecStart = "@${dhcpcd}/sbin/dhcpcd dhcpcd --quiet ${optionalString cfg.persistent "--persistent"} --config ${dhcpcdConf}";
|
||||
ExecReload = "${dhcpcd}/sbin/dhcpcd --rebind";
|
||||
Restart = "always";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.dhcpcd = {
|
||||
isSystemUser = true;
|
||||
group = "dhcpcd";
|
||||
};
|
||||
users.groups.dhcpcd = {};
|
||||
|
||||
environment.systemPackages = [ dhcpcd ];
|
||||
|
||||
environment.etc."dhcpcd.exit-hook".source = exitHook;
|
||||
|
||||
powerManagement.resumeCommands = mkIf config.systemd.services.dhcpcd.enable
|
||||
''
|
||||
# Tell dhcpcd to rebind its interfaces if it's running.
|
||||
/run/current-system/systemd/bin/systemctl reload dhcpcd.service
|
||||
'';
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
223
nixos/modules/services/networking/dhcpd.nix
Normal file
223
nixos/modules/services/networking/dhcpd.nix
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg4 = config.services.dhcpd4;
|
||||
cfg6 = config.services.dhcpd6;
|
||||
|
||||
writeConfig = postfix: cfg: pkgs.writeText "dhcpd.conf"
|
||||
''
|
||||
default-lease-time 600;
|
||||
max-lease-time 7200;
|
||||
${optionalString (!cfg.authoritative) "not "}authoritative;
|
||||
ddns-update-style interim;
|
||||
log-facility local1; # see dhcpd.nix
|
||||
|
||||
${cfg.extraConfig}
|
||||
|
||||
${lib.concatMapStrings
|
||||
(machine: ''
|
||||
host ${machine.hostName} {
|
||||
hardware ethernet ${machine.ethernetAddress};
|
||||
fixed-address${
|
||||
optionalString (postfix == "6") postfix
|
||||
} ${machine.ipAddress};
|
||||
}
|
||||
'')
|
||||
cfg.machines
|
||||
}
|
||||
'';
|
||||
|
||||
dhcpdService = postfix: cfg:
|
||||
let
|
||||
configFile =
|
||||
if cfg.configFile != null
|
||||
then cfg.configFile
|
||||
else writeConfig postfix cfg;
|
||||
leaseFile = "/var/lib/dhcpd${postfix}/dhcpd.leases";
|
||||
args = [
|
||||
"@${pkgs.dhcp}/sbin/dhcpd" "dhcpd${postfix}" "-${postfix}"
|
||||
"-pf" "/run/dhcpd${postfix}/dhcpd.pid"
|
||||
"-cf" configFile
|
||||
"-lf" leaseFile
|
||||
] ++ cfg.extraFlags
|
||||
++ cfg.interfaces;
|
||||
in
|
||||
optionalAttrs cfg.enable {
|
||||
"dhcpd${postfix}" = {
|
||||
description = "DHCPv${postfix} server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
preStart = "touch ${leaseFile}";
|
||||
serviceConfig = {
|
||||
ExecStart = concatMapStringsSep " " escapeShellArg args;
|
||||
Type = "forking";
|
||||
Restart = "always";
|
||||
DynamicUser = true;
|
||||
User = "dhcpd";
|
||||
Group = "dhcpd";
|
||||
AmbientCapabilities = [
|
||||
"CAP_NET_RAW" # to send ICMP messages
|
||||
"CAP_NET_BIND_SERVICE" # to bind on DHCP port (67)
|
||||
];
|
||||
StateDirectory = "dhcpd${postfix}";
|
||||
RuntimeDirectory = "dhcpd${postfix}";
|
||||
PIDFile = "/run/dhcpd${postfix}/dhcpd.pid";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
machineOpts = { ... }: {
|
||||
|
||||
options = {
|
||||
|
||||
hostName = mkOption {
|
||||
type = types.str;
|
||||
example = "foo";
|
||||
description = ''
|
||||
Hostname which is assigned statically to the machine.
|
||||
'';
|
||||
};
|
||||
|
||||
ethernetAddress = mkOption {
|
||||
type = types.str;
|
||||
example = "00:16:76:9a:32:1d";
|
||||
description = ''
|
||||
MAC address of the machine.
|
||||
'';
|
||||
};
|
||||
|
||||
ipAddress = mkOption {
|
||||
type = types.str;
|
||||
example = "192.168.1.10";
|
||||
description = ''
|
||||
IP address of the machine.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
dhcpConfig = postfix: {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable the DHCPv${postfix} server.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = ''
|
||||
option subnet-mask 255.255.255.0;
|
||||
option broadcast-address 192.168.1.255;
|
||||
option routers 192.168.1.5;
|
||||
option domain-name-servers 130.161.158.4, 130.161.33.17, 130.161.180.1;
|
||||
option domain-name "example.org";
|
||||
subnet 192.168.1.0 netmask 255.255.255.0 {
|
||||
range 192.168.1.100 192.168.1.200;
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Extra text to be appended to the DHCP server configuration
|
||||
file. Currently, you almost certainly need to specify something
|
||||
there, such as the options specifying the subnet mask, DNS servers,
|
||||
etc.
|
||||
'';
|
||||
};
|
||||
|
||||
extraFlags = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Additional command line flags to be passed to the dhcpd daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
The path of the DHCP server configuration file. If no file
|
||||
is specified, a file is generated using the other options.
|
||||
'';
|
||||
};
|
||||
|
||||
interfaces = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = ["eth0"];
|
||||
description = ''
|
||||
The interfaces on which the DHCP server should listen.
|
||||
'';
|
||||
};
|
||||
|
||||
machines = mkOption {
|
||||
type = with types; listOf (submodule machineOpts);
|
||||
default = [];
|
||||
example = [
|
||||
{ hostName = "foo";
|
||||
ethernetAddress = "00:16:76:9a:32:1d";
|
||||
ipAddress = "192.168.1.10";
|
||||
}
|
||||
{ hostName = "bar";
|
||||
ethernetAddress = "00:19:d1:1d:c4:9a";
|
||||
ipAddress = "192.168.1.11";
|
||||
}
|
||||
];
|
||||
description = ''
|
||||
A list mapping Ethernet addresses to IPv${postfix} addresses for the
|
||||
DHCP server.
|
||||
'';
|
||||
};
|
||||
|
||||
authoritative = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether the DHCP server shall send DHCPNAK messages to misconfigured
|
||||
clients. If this is not done, clients may be unable to get a correct
|
||||
IP address after changing subnets until their old lease has expired.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "services" "dhcpd" ] [ "services" "dhcpd4" ])
|
||||
] ++ flip map [ "4" "6" ] (postfix:
|
||||
mkRemovedOptionModule [ "services" "dhcpd${postfix}" "stateDir" ] ''
|
||||
The DHCP server state directory is now managed with the systemd's DynamicUser mechanism.
|
||||
This means the directory is named after the service (dhcpd${postfix}), created under
|
||||
/var/lib/private/ and symlinked to /var/lib/.
|
||||
''
|
||||
);
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.dhcpd4 = dhcpConfig "4";
|
||||
services.dhcpd6 = dhcpConfig "6";
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf (cfg4.enable || cfg6.enable) {
|
||||
|
||||
systemd.services = dhcpdService "4" cfg4 // dhcpdService "6" cfg6;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
108
nixos/modules/services/networking/dnscache.nix
Normal file
108
nixos/modules/services/networking/dnscache.nix
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.dnscache;
|
||||
|
||||
dnscache-root = pkgs.runCommand "dnscache-root" { preferLocalBuild = true; } ''
|
||||
mkdir -p $out/{servers,ip}
|
||||
|
||||
${concatMapStrings (ip: ''
|
||||
touch "$out/ip/"${lib.escapeShellArg ip}
|
||||
'') cfg.clientIps}
|
||||
|
||||
${concatStrings (mapAttrsToList (host: ips: ''
|
||||
${concatMapStrings (ip: ''
|
||||
echo ${lib.escapeShellArg ip} >> "$out/servers/"${lib.escapeShellArg host}
|
||||
'') ips}
|
||||
'') cfg.domainServers)}
|
||||
|
||||
# if a list of root servers was not provided in config, copy it
|
||||
# over. (this is also done by dnscache-conf, but we 'rm -rf
|
||||
# /var/lib/dnscache/root' below & replace it wholesale with this,
|
||||
# so we have to ensure servers/@ exists ourselves.)
|
||||
if [ ! -e $out/servers/@ ]; then
|
||||
# symlink does not work here, due chroot
|
||||
cp ${pkgs.djbdns}/etc/dnsroots.global $out/servers/@;
|
||||
fi
|
||||
'';
|
||||
|
||||
in {
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
services.dnscache = {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "Whether to run the dnscache caching dns server.";
|
||||
};
|
||||
|
||||
ip = mkOption {
|
||||
default = "0.0.0.0";
|
||||
type = types.str;
|
||||
description = "IP address on which to listen for connections.";
|
||||
};
|
||||
|
||||
clientIps = mkOption {
|
||||
default = [ "127.0.0.1" ];
|
||||
type = types.listOf types.str;
|
||||
description = "Client IP addresses (or prefixes) from which to accept connections.";
|
||||
example = ["192.168" "172.23.75.82"];
|
||||
};
|
||||
|
||||
domainServers = mkOption {
|
||||
default = { };
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
description = ''
|
||||
Table of {hostname: server} pairs to use as authoritative servers for hosts (and subhosts).
|
||||
If entry for @ is not specified predefined list of root servers is used.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
"@" = ["8.8.8.8" "8.8.4.4"];
|
||||
"example.com" = ["192.168.100.100"];
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
forwardOnly = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to treat root servers (for @) as caching
|
||||
servers, requesting addresses the same way a client does. This is
|
||||
needed if you want to use e.g. Google DNS as your upstream DNS.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf config.services.dnscache.enable {
|
||||
environment.systemPackages = [ pkgs.djbdns ];
|
||||
users.users.dnscache.isSystemUser = true;
|
||||
|
||||
systemd.services.dnscache = {
|
||||
description = "djbdns dnscache server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = with pkgs; [ bash daemontools djbdns ];
|
||||
preStart = ''
|
||||
rm -rf /var/lib/dnscache
|
||||
dnscache-conf dnscache dnscache /var/lib/dnscache ${config.services.dnscache.ip}
|
||||
rm -rf /var/lib/dnscache/root
|
||||
ln -sf ${dnscache-root} /var/lib/dnscache/root
|
||||
'';
|
||||
script = ''
|
||||
cd /var/lib/dnscache/
|
||||
${optionalString cfg.forwardOnly "export FORWARDONLY=1"}
|
||||
exec ./run
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
124
nixos/modules/services/networking/dnscrypt-proxy2.nix
Normal file
124
nixos/modules/services/networking/dnscrypt-proxy2.nix
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
{ config, lib, pkgs, ... }: with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.dnscrypt-proxy2;
|
||||
in
|
||||
|
||||
{
|
||||
options.services.dnscrypt-proxy2 = {
|
||||
enable = mkEnableOption "dnscrypt-proxy2";
|
||||
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
Attrset that is converted and passed as TOML config file.
|
||||
For available params, see: <link xlink:href="https://github.com/DNSCrypt/dnscrypt-proxy/blob/${pkgs.dnscrypt-proxy2.version}/dnscrypt-proxy/example-dnscrypt-proxy.toml"/>
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
sources.public-resolvers = {
|
||||
urls = [ "https://download.dnscrypt.info/resolvers-list/v2/public-resolvers.md" ];
|
||||
cache_file = "public-resolvers.md";
|
||||
minisign_key = "RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3";
|
||||
refresh_delay = 72;
|
||||
};
|
||||
}
|
||||
'';
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
};
|
||||
|
||||
upstreamDefaults = mkOption {
|
||||
description = ''
|
||||
Whether to base the config declared in <option>services.dnscrypt-proxy2.settings</option> on the upstream example config (<link xlink:href="https://github.com/DNSCrypt/dnscrypt-proxy/blob/master/dnscrypt-proxy/example-dnscrypt-proxy.toml"/>)
|
||||
|
||||
Disable this if you want to declare your dnscrypt config from scratch.
|
||||
'';
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
description = ''
|
||||
Path to TOML config file. See: <link xlink:href="https://github.com/DNSCrypt/dnscrypt-proxy/blob/master/dnscrypt-proxy/example-dnscrypt-proxy.toml"/>
|
||||
If this option is set, it will override any configuration done in options.services.dnscrypt-proxy2.settings.
|
||||
'';
|
||||
example = "/etc/dnscrypt-proxy/dnscrypt-proxy.toml";
|
||||
type = types.path;
|
||||
default = pkgs.runCommand "dnscrypt-proxy.toml" {
|
||||
json = builtins.toJSON cfg.settings;
|
||||
passAsFile = [ "json" ];
|
||||
} ''
|
||||
${if cfg.upstreamDefaults then ''
|
||||
${pkgs.remarshal}/bin/toml2json ${pkgs.dnscrypt-proxy2.src}/dnscrypt-proxy/example-dnscrypt-proxy.toml > example.json
|
||||
${pkgs.jq}/bin/jq --slurp add example.json $jsonPath > config.json # merges the two
|
||||
'' else ''
|
||||
cp $jsonPath config.json
|
||||
''}
|
||||
${pkgs.remarshal}/bin/json2toml < config.json > $out
|
||||
'';
|
||||
defaultText = literalDocBook "TOML file generated from <option>services.dnscrypt-proxy2.settings</option>";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
networking.nameservers = lib.mkDefault [ "127.0.0.1" ];
|
||||
|
||||
systemd.services.dnscrypt-proxy2 = {
|
||||
description = "DNSCrypt-proxy client";
|
||||
wants = [
|
||||
"network-online.target"
|
||||
"nss-lookup.target"
|
||||
];
|
||||
before = [
|
||||
"nss-lookup.target"
|
||||
];
|
||||
wantedBy = [
|
||||
"multi-user.target"
|
||||
];
|
||||
serviceConfig = {
|
||||
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
|
||||
CacheDirectory = "dnscrypt-proxy";
|
||||
DynamicUser = true;
|
||||
ExecStart = "${pkgs.dnscrypt-proxy2}/bin/dnscrypt-proxy -config ${cfg.configFile}";
|
||||
LockPersonality = true;
|
||||
LogsDirectory = "dnscrypt-proxy";
|
||||
MemoryDenyWriteExecute = true;
|
||||
NoNewPrivileges = true;
|
||||
NonBlocking = true;
|
||||
PrivateDevices = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectSystem = "strict";
|
||||
Restart = "always";
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RuntimeDirectory = "dnscrypt-proxy";
|
||||
StateDirectory = "dnscrypt-proxy";
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"@chown"
|
||||
"~@aio"
|
||||
"~@keyring"
|
||||
"~@memlock"
|
||||
"~@resources"
|
||||
"~@setuid"
|
||||
"~@timer"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# uses attributes of the linked package
|
||||
meta.buildDocsInSandbox = false;
|
||||
}
|
||||
286
nixos/modules/services/networking/dnscrypt-wrapper.nix
Normal file
286
nixos/modules/services/networking/dnscrypt-wrapper.nix
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.dnscrypt-wrapper;
|
||||
dataDir = "/var/lib/dnscrypt-wrapper";
|
||||
|
||||
mkPath = path: default:
|
||||
if path != null
|
||||
then toString path
|
||||
else default;
|
||||
|
||||
publicKey = mkPath cfg.providerKey.public "${dataDir}/public.key";
|
||||
secretKey = mkPath cfg.providerKey.secret "${dataDir}/secret.key";
|
||||
|
||||
daemonArgs = with cfg; [
|
||||
"--listen-address=${address}:${toString port}"
|
||||
"--resolver-address=${upstream.address}:${toString upstream.port}"
|
||||
"--provider-name=${providerName}"
|
||||
"--provider-publickey-file=${publicKey}"
|
||||
"--provider-secretkey-file=${secretKey}"
|
||||
"--provider-cert-file=${providerName}.crt"
|
||||
"--crypt-secretkey-file=${providerName}.key"
|
||||
];
|
||||
|
||||
genKeys = ''
|
||||
# generates time-limited keypairs
|
||||
keyGen() {
|
||||
dnscrypt-wrapper --gen-crypt-keypair \
|
||||
--crypt-secretkey-file=${cfg.providerName}.key
|
||||
|
||||
dnscrypt-wrapper --gen-cert-file \
|
||||
--crypt-secretkey-file=${cfg.providerName}.key \
|
||||
--provider-cert-file=${cfg.providerName}.crt \
|
||||
--provider-publickey-file=${publicKey} \
|
||||
--provider-secretkey-file=${secretKey} \
|
||||
--cert-file-expire-days=${toString cfg.keys.expiration}
|
||||
}
|
||||
|
||||
cd ${dataDir}
|
||||
|
||||
# generate provider keypair (first run only)
|
||||
${optionalString (cfg.providerKey.public == null || cfg.providerKey.secret == null) ''
|
||||
if [ ! -f ${publicKey} ] || [ ! -f ${secretKey} ]; then
|
||||
dnscrypt-wrapper --gen-provider-keypair
|
||||
fi
|
||||
''}
|
||||
|
||||
# generate new keys for rotation
|
||||
if [ ! -f ${cfg.providerName}.key ] || [ ! -f ${cfg.providerName}.crt ]; then
|
||||
keyGen
|
||||
fi
|
||||
'';
|
||||
|
||||
rotateKeys = ''
|
||||
# check if keys are not expired
|
||||
keyValid() {
|
||||
fingerprint=$(dnscrypt-wrapper \
|
||||
--show-provider-publickey \
|
||||
--provider-publickey-file=${publicKey} \
|
||||
| awk '{print $(NF)}')
|
||||
dnscrypt-proxy --test=${toString (cfg.keys.checkInterval + 1)} \
|
||||
--resolver-address=127.0.0.1:${toString cfg.port} \
|
||||
--provider-name=${cfg.providerName} \
|
||||
--provider-key=$fingerprint
|
||||
}
|
||||
|
||||
cd ${dataDir}
|
||||
|
||||
# archive old keys and restart the service
|
||||
if ! keyValid; then
|
||||
echo "certificate soon to become invalid; backing up old cert"
|
||||
mkdir -p oldkeys
|
||||
mv -v ${cfg.providerName}.key oldkeys/${cfg.providerName}-$(date +%F-%T).key
|
||||
mv -v ${cfg.providerName}.crt oldkeys/${cfg.providerName}-$(date +%F-%T).crt
|
||||
systemctl restart dnscrypt-wrapper
|
||||
fi
|
||||
'';
|
||||
|
||||
|
||||
# This is the fork of the original dnscrypt-proxy maintained by Dyne.org.
|
||||
# dnscrypt-proxy2 doesn't provide the `--test` feature that is needed to
|
||||
# correctly implement key rotation of dnscrypt-wrapper ephemeral keys.
|
||||
dnscrypt-proxy1 = pkgs.callPackage
|
||||
({ stdenv, fetchFromGitHub, autoreconfHook
|
||||
, pkg-config, libsodium, ldns, openssl, systemd }:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "dnscrypt-proxy";
|
||||
version = "2019-08-20";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "dyne";
|
||||
repo = "dnscrypt-proxy";
|
||||
rev = "07ac3825b5069adc28e2547c16b1d983a8ed8d80";
|
||||
sha256 = "0c4mq741q4rpmdn09agwmxap32kf0vgfz7pkhcdc5h54chc3g3xy";
|
||||
};
|
||||
|
||||
configureFlags = optional stdenv.isLinux "--with-systemd";
|
||||
|
||||
nativeBuildInputs = [ autoreconfHook pkg-config ];
|
||||
|
||||
# <ldns/ldns.h> depends on <openssl/ssl.h>
|
||||
buildInputs = [ libsodium openssl.dev ldns ] ++ optional stdenv.isLinux systemd;
|
||||
|
||||
postInstall = ''
|
||||
# Previous versions required libtool files to load plugins; they are
|
||||
# now strictly optional.
|
||||
rm $out/lib/dnscrypt-proxy/*.la
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "A tool for securing communications between a client and a DNS resolver";
|
||||
homepage = "https://github.com/dyne/dnscrypt-proxy";
|
||||
license = licenses.isc;
|
||||
maintainers = with maintainers; [ rnhmjoj ];
|
||||
platforms = platforms.linux;
|
||||
};
|
||||
}) { };
|
||||
|
||||
in {
|
||||
|
||||
|
||||
###### interface
|
||||
|
||||
options.services.dnscrypt-wrapper = {
|
||||
enable = mkEnableOption "DNSCrypt wrapper";
|
||||
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
description = ''
|
||||
The DNSCrypt wrapper will bind to this IP address.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 5353;
|
||||
description = ''
|
||||
The DNSCrypt wrapper will listen for DNS queries on this port.
|
||||
'';
|
||||
};
|
||||
|
||||
providerName = mkOption {
|
||||
type = types.str;
|
||||
default = "2.dnscrypt-cert.${config.networking.hostName}";
|
||||
defaultText = literalExpression ''"2.dnscrypt-cert.''${config.networking.hostName}"'';
|
||||
example = "2.dnscrypt-cert.myresolver";
|
||||
description = ''
|
||||
The name that will be given to this DNSCrypt resolver.
|
||||
Note: the resolver name must start with <literal>2.dnscrypt-cert.</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
providerKey.public = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/etc/secrets/public.key";
|
||||
description = ''
|
||||
The filepath to the provider public key. If not given a new
|
||||
provider key pair will be generated on the first run.
|
||||
'';
|
||||
};
|
||||
|
||||
providerKey.secret = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/etc/secrets/secret.key";
|
||||
description = ''
|
||||
The filepath to the provider secret key. If not given a new
|
||||
provider key pair will be generated on the first run.
|
||||
'';
|
||||
};
|
||||
|
||||
upstream.address = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
description = ''
|
||||
The IP address of the upstream DNS server DNSCrypt will "wrap".
|
||||
'';
|
||||
};
|
||||
|
||||
upstream.port = mkOption {
|
||||
type = types.int;
|
||||
default = 53;
|
||||
description = ''
|
||||
The port of the upstream DNS server DNSCrypt will "wrap".
|
||||
'';
|
||||
};
|
||||
|
||||
keys.expiration = mkOption {
|
||||
type = types.int;
|
||||
default = 30;
|
||||
description = ''
|
||||
The duration (in days) of the time-limited secret key.
|
||||
This will be automatically rotated before expiration.
|
||||
'';
|
||||
};
|
||||
|
||||
keys.checkInterval = mkOption {
|
||||
type = types.int;
|
||||
default = 1440;
|
||||
description = ''
|
||||
The time interval (in minutes) between key expiration checks.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.users.dnscrypt-wrapper = {
|
||||
description = "dnscrypt-wrapper daemon user";
|
||||
home = "${dataDir}";
|
||||
createHome = true;
|
||||
isSystemUser = true;
|
||||
group = "dnscrypt-wrapper";
|
||||
};
|
||||
users.groups.dnscrypt-wrapper = { };
|
||||
|
||||
security.polkit.extraConfig = ''
|
||||
// Allow dnscrypt-wrapper user to restart dnscrypt-wrapper.service
|
||||
polkit.addRule(function(action, subject) {
|
||||
if (action.id == "org.freedesktop.systemd1.manage-units" &&
|
||||
action.lookup("unit") == "dnscrypt-wrapper.service" &&
|
||||
subject.user == "dnscrypt-wrapper") {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
||||
'';
|
||||
|
||||
systemd.services.dnscrypt-wrapper = {
|
||||
description = "dnscrypt-wrapper daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ pkgs.dnscrypt-wrapper ];
|
||||
|
||||
serviceConfig = {
|
||||
User = "dnscrypt-wrapper";
|
||||
WorkingDirectory = dataDir;
|
||||
Restart = "on-failure";
|
||||
ExecStart = "${pkgs.dnscrypt-wrapper}/bin/dnscrypt-wrapper ${toString daemonArgs}";
|
||||
};
|
||||
|
||||
preStart = genKeys;
|
||||
};
|
||||
|
||||
|
||||
systemd.services.dnscrypt-wrapper-rotate = {
|
||||
after = [ "network.target" ];
|
||||
requires = [ "dnscrypt-wrapper.service" ];
|
||||
description = "Rotates DNSCrypt wrapper keys if soon to expire";
|
||||
|
||||
path = with pkgs; [ dnscrypt-wrapper dnscrypt-proxy1 gawk ];
|
||||
script = rotateKeys;
|
||||
serviceConfig.User = "dnscrypt-wrapper";
|
||||
};
|
||||
|
||||
|
||||
systemd.timers.dnscrypt-wrapper-rotate = {
|
||||
description = "Periodically check DNSCrypt wrapper keys for expiration";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
timerConfig = {
|
||||
Unit = "dnscrypt-wrapper-rotate.service";
|
||||
OnBootSec = "1min";
|
||||
OnUnitActiveSec = cfg.keys.checkInterval * 60;
|
||||
};
|
||||
};
|
||||
|
||||
assertions = with cfg; [
|
||||
{ assertion = (providerKey.public == null && providerKey.secret == null) ||
|
||||
(providerKey.secret != null && providerKey.public != null);
|
||||
message = "The secret and public provider key must be set together.";
|
||||
}
|
||||
];
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ rnhmjoj ];
|
||||
|
||||
}
|
||||
53
nixos/modules/services/networking/dnsdist.nix
Normal file
53
nixos/modules/services/networking/dnsdist.nix
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.dnsdist;
|
||||
configFile = pkgs.writeText "dnsdist.conf" ''
|
||||
setLocal('${cfg.listenAddress}:${toString cfg.listenPort}')
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
in {
|
||||
options = {
|
||||
services.dnsdist = {
|
||||
enable = mkEnableOption "dnsdist domain name server";
|
||||
|
||||
listenAddress = mkOption {
|
||||
type = types.str;
|
||||
description = "Listen IP Address";
|
||||
default = "0.0.0.0";
|
||||
};
|
||||
listenPort = mkOption {
|
||||
type = types.int;
|
||||
description = "Listen port";
|
||||
default = 53;
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Extra lines to be added verbatim to dnsdist.conf.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.packages = [ pkgs.dnsdist ];
|
||||
|
||||
systemd.services.dnsdist = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
startLimitIntervalSec = 0;
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
|
||||
# upstream overrides for better nixos compatibility
|
||||
ExecStartPre = [ "" "${pkgs.dnsdist}/bin/dnsdist --check-config --config ${configFile}" ];
|
||||
ExecStart = [ "" "${pkgs.dnsdist}/bin/dnsdist --supervised --disable-syslog --config ${configFile}" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
130
nixos/modules/services/networking/dnsmasq.nix
Normal file
130
nixos/modules/services/networking/dnsmasq.nix
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.dnsmasq;
|
||||
dnsmasq = pkgs.dnsmasq;
|
||||
stateDir = "/var/lib/dnsmasq";
|
||||
|
||||
dnsmasqConf = pkgs.writeText "dnsmasq.conf" ''
|
||||
dhcp-leasefile=${stateDir}/dnsmasq.leases
|
||||
${optionalString cfg.resolveLocalQueries ''
|
||||
conf-file=/etc/dnsmasq-conf.conf
|
||||
resolv-file=/etc/dnsmasq-resolv.conf
|
||||
''}
|
||||
${flip concatMapStrings cfg.servers (server: ''
|
||||
server=${server}
|
||||
'')}
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.dnsmasq = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to run dnsmasq.
|
||||
'';
|
||||
};
|
||||
|
||||
resolveLocalQueries = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether dnsmasq should resolve local queries (i.e. add 127.0.0.1 to
|
||||
/etc/resolv.conf).
|
||||
'';
|
||||
};
|
||||
|
||||
servers = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "8.8.8.8" "8.8.4.4" ];
|
||||
description = ''
|
||||
The DNS servers which dnsmasq should query.
|
||||
'';
|
||||
};
|
||||
|
||||
alwaysKeepRunning = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
If enabled, systemd will always respawn dnsmasq even if shut down manually. The default, disabled, will only restart it on error.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Extra configuration directives that should be added to
|
||||
<literal>dnsmasq.conf</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
networking.nameservers =
|
||||
optional cfg.resolveLocalQueries "127.0.0.1";
|
||||
|
||||
services.dbus.packages = [ dnsmasq ];
|
||||
|
||||
users.users.dnsmasq = {
|
||||
isSystemUser = true;
|
||||
group = "dnsmasq";
|
||||
description = "Dnsmasq daemon user";
|
||||
};
|
||||
users.groups.dnsmasq = {};
|
||||
|
||||
networking.resolvconf = mkIf cfg.resolveLocalQueries {
|
||||
useLocalResolver = mkDefault true;
|
||||
|
||||
extraConfig = ''
|
||||
dnsmasq_conf=/etc/dnsmasq-conf.conf
|
||||
dnsmasq_resolv=/etc/dnsmasq-resolv.conf
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.dnsmasq = {
|
||||
description = "Dnsmasq Daemon";
|
||||
after = [ "network.target" "systemd-resolved.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ dnsmasq ];
|
||||
preStart = ''
|
||||
mkdir -m 755 -p ${stateDir}
|
||||
touch ${stateDir}/dnsmasq.leases
|
||||
chown -R dnsmasq ${stateDir}
|
||||
touch /etc/dnsmasq-{conf,resolv}.conf
|
||||
dnsmasq --test
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "dbus";
|
||||
BusName = "uk.org.thekelleys.dnsmasq";
|
||||
ExecStart = "${dnsmasq}/bin/dnsmasq -k --enable-dbus --user=dnsmasq -C ${dnsmasqConf}";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = true;
|
||||
ProtectHome = true;
|
||||
Restart = if cfg.alwaysKeepRunning then "always" else "on-failure";
|
||||
};
|
||||
restartTriggers = [ config.environment.etc.hosts.source ];
|
||||
};
|
||||
};
|
||||
}
|
||||
60
nixos/modules/services/networking/doh-proxy-rust.nix
Normal file
60
nixos/modules/services/networking/doh-proxy-rust.nix
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.doh-proxy-rust;
|
||||
|
||||
in {
|
||||
|
||||
options.services.doh-proxy-rust = {
|
||||
|
||||
enable = mkEnableOption "doh-proxy-rust";
|
||||
|
||||
flags = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "--server-address=9.9.9.9:53" ];
|
||||
description = ''
|
||||
A list of command-line flags to pass to doh-proxy. For details on the
|
||||
available options, see <link xlink:href="https://github.com/jedisct1/doh-server#usage"/>.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.doh-proxy-rust = {
|
||||
description = "doh-proxy-rust";
|
||||
after = [ "network.target" "nss-lookup.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.doh-proxy-rust}/bin/doh-proxy ${escapeShellArgs cfg.flags}";
|
||||
Restart = "always";
|
||||
RestartSec = 10;
|
||||
DynamicUser = true;
|
||||
|
||||
CapabilityBoundingSet = "";
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
NoNewPrivileges = true;
|
||||
ProtectClock = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
RemoveIPC = true;
|
||||
RestrictAddressFamilies = "AF_INET AF_INET6";
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallErrorNumber = "EPERM";
|
||||
SystemCallFilter = [ "@system-service" "~@privileged @resources" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ stephank ];
|
||||
|
||||
}
|
||||
157
nixos/modules/services/networking/ejabberd.nix
Normal file
157
nixos/modules/services/networking/ejabberd.nix
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.ejabberd;
|
||||
|
||||
ctlcfg = pkgs.writeText "ejabberdctl.cfg" ''
|
||||
ERL_EPMD_ADDRESS=127.0.0.1
|
||||
${cfg.ctlConfig}
|
||||
'';
|
||||
|
||||
ectl = ''${cfg.package}/bin/ejabberdctl ${optionalString (cfg.configFile != null) "--config ${cfg.configFile}"} --ctl-config "${ctlcfg}" --spool "${cfg.spoolDir}" --logs "${cfg.logsDir}"'';
|
||||
|
||||
dumps = lib.escapeShellArgs cfg.loadDumps;
|
||||
|
||||
in {
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.ejabberd = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable ejabberd server";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.ejabberd;
|
||||
defaultText = literalExpression "pkgs.ejabberd";
|
||||
description = "ejabberd server package to use";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "ejabberd";
|
||||
description = "User under which ejabberd is ran";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "ejabberd";
|
||||
description = "Group under which ejabberd is ran";
|
||||
};
|
||||
|
||||
spoolDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/ejabberd";
|
||||
description = "Location of the spooldir of ejabberd";
|
||||
};
|
||||
|
||||
logsDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/log/ejabberd";
|
||||
description = "Location of the logfile directory of ejabberd";
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
description = "Configuration file for ejabberd in YAML format";
|
||||
default = null;
|
||||
};
|
||||
|
||||
ctlConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Configuration of ejabberdctl";
|
||||
};
|
||||
|
||||
loadDumps = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = [];
|
||||
description = "Configuration dumps that should be loaded on the first startup";
|
||||
example = literalExpression "[ ./myejabberd.dump ]";
|
||||
};
|
||||
|
||||
imagemagick = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Add ImageMagick to server's path; allows for image thumbnailing";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "ejabberd") {
|
||||
ejabberd = {
|
||||
group = cfg.group;
|
||||
home = cfg.spoolDir;
|
||||
createHome = true;
|
||||
uid = config.ids.uids.ejabberd;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == "ejabberd") {
|
||||
ejabberd.gid = config.ids.gids.ejabberd;
|
||||
};
|
||||
|
||||
systemd.services.ejabberd = {
|
||||
description = "ejabberd server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
path = [ pkgs.findutils pkgs.coreutils ] ++ lib.optional cfg.imagemagick pkgs.imagemagick;
|
||||
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStart = "${ectl} foreground";
|
||||
ExecStop = "${ectl} stop";
|
||||
ExecReload = "${ectl} reload_config";
|
||||
};
|
||||
|
||||
preStart = ''
|
||||
if [ -z "$(ls -A '${cfg.spoolDir}')" ]; then
|
||||
touch "${cfg.spoolDir}/.firstRun"
|
||||
fi
|
||||
'';
|
||||
|
||||
postStart = ''
|
||||
while ! ${ectl} status >/dev/null 2>&1; do
|
||||
if ! kill -0 "$MAINPID"; then exit 1; fi
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
if [ -e "${cfg.spoolDir}/.firstRun" ]; then
|
||||
rm "${cfg.spoolDir}/.firstRun"
|
||||
for src in ${dumps}; do
|
||||
find "$src" -type f | while read dump; do
|
||||
echo "Loading configuration dump at $dump"
|
||||
${ectl} load "$dump"
|
||||
done
|
||||
done
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.logsDir}' 0750 ${cfg.user} ${cfg.group} -"
|
||||
"d '${cfg.spoolDir}' 0700 ${cfg.user} ${cfg.group} -"
|
||||
];
|
||||
|
||||
security.pam.services.ejabberd = {};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
84
nixos/modules/services/networking/envoy.nix
Normal file
84
nixos/modules/services/networking/envoy.nix
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.envoy;
|
||||
format = pkgs.formats.json { };
|
||||
conf = format.generate "envoy.json" cfg.settings;
|
||||
validateConfig = file:
|
||||
pkgs.runCommand "validate-envoy-conf" { } ''
|
||||
${pkgs.envoy}/bin/envoy --log-level error --mode validate -c "${file}"
|
||||
cp "${file}" "$out"
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options.services.envoy = {
|
||||
enable = mkEnableOption "Envoy reverse proxy";
|
||||
|
||||
settings = mkOption {
|
||||
type = format.type;
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
admin = {
|
||||
access_log_path = "/dev/null";
|
||||
address = {
|
||||
socket_address = {
|
||||
protocol = "TCP";
|
||||
address = "127.0.0.1";
|
||||
port_value = 9901;
|
||||
};
|
||||
};
|
||||
};
|
||||
static_resources = {
|
||||
listeners = [];
|
||||
clusters = [];
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Specify the configuration for Envoy in Nix.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = [ pkgs.envoy ];
|
||||
systemd.services.envoy = {
|
||||
description = "Envoy reverse proxy";
|
||||
after = [ "network-online.target" ];
|
||||
requires = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.envoy}/bin/envoy -c ${validateConfig conf}";
|
||||
DynamicUser = true;
|
||||
Restart = "no";
|
||||
CacheDirectory = "envoy";
|
||||
LogsDirectory = "envoy";
|
||||
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
|
||||
CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
|
||||
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK AF_XDP";
|
||||
SystemCallArchitectures = "native";
|
||||
LockPersonality = true;
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
PrivateUsers = false; # breaks CAP_NET_BIND_SERVICE
|
||||
PrivateDevices = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "ptraceable";
|
||||
ProtectHostname = true;
|
||||
ProtectSystem = "strict";
|
||||
UMask = "0066";
|
||||
SystemCallFilter = "~@clock @module @mount @reboot @swap @obsolete @cpu-emulation";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
72
nixos/modules/services/networking/epmd.nix
Normal file
72
nixos/modules/services/networking/epmd.nix
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.epmd;
|
||||
in
|
||||
{
|
||||
###### interface
|
||||
options.services.epmd = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable socket activation for Erlang Port Mapper Daemon (epmd),
|
||||
which acts as a name server on all hosts involved in distributed
|
||||
Erlang computations.
|
||||
'';
|
||||
};
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.erlang;
|
||||
defaultText = literalExpression "pkgs.erlang";
|
||||
description = ''
|
||||
The Erlang package to use to get epmd binary. That way you can re-use
|
||||
an Erlang runtime that is already installed for other purposes.
|
||||
'';
|
||||
};
|
||||
listenStream = mkOption
|
||||
{
|
||||
type = types.str;
|
||||
default = "[::]:4369";
|
||||
description = ''
|
||||
the listenStream used by the systemd socket.
|
||||
see https://www.freedesktop.org/software/systemd/man/systemd.socket.html#ListenStream= for more informations.
|
||||
use this to change the port epmd will run on.
|
||||
if not defined, epmd will use "[::]:4369"
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [{
|
||||
assertion = cfg.listenStream == "[::]:4369" -> config.networking.enableIPv6;
|
||||
message = "epmd listens by default on ipv6, enable ipv6 or change config.services.epmd.listenStream";
|
||||
}];
|
||||
systemd.sockets.epmd = rec {
|
||||
description = "Erlang Port Mapper Daemon Activation Socket";
|
||||
wantedBy = [ "sockets.target" ];
|
||||
before = wantedBy;
|
||||
socketConfig = {
|
||||
ListenStream = cfg.listenStream;
|
||||
Accept = "false";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.epmd = {
|
||||
description = "Erlang Port Mapper Daemon";
|
||||
after = [ "network.target" ];
|
||||
requires = [ "epmd.socket" ];
|
||||
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
ExecStart = "${cfg.package}/bin/epmd -systemd";
|
||||
Type = "notify";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = teams.beam.members;
|
||||
}
|
||||
143
nixos/modules/services/networking/ergo.nix
Normal file
143
nixos/modules/services/networking/ergo.nix
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.ergo;
|
||||
opt = options.services.ergo;
|
||||
|
||||
inherit (lib) literalExpression mkEnableOption mkIf mkOption optionalString types;
|
||||
|
||||
configFile = pkgs.writeText "ergo.conf" (''
|
||||
ergo {
|
||||
directory = "${cfg.dataDir}"
|
||||
node {
|
||||
mining = false
|
||||
}
|
||||
wallet.secretStorage.secretDir = "${cfg.dataDir}/wallet/keystore"
|
||||
}
|
||||
|
||||
scorex {
|
||||
network {
|
||||
bindAddress = "${cfg.listen.ip}:${toString cfg.listen.port}"
|
||||
}
|
||||
'' + optionalString (cfg.api.keyHash != null) ''
|
||||
restApi {
|
||||
apiKeyHash = "${cfg.api.keyHash}"
|
||||
bindAddress = "${cfg.api.listen.ip}:${toString cfg.api.listen.port}"
|
||||
}
|
||||
'' + ''
|
||||
}
|
||||
'');
|
||||
|
||||
in {
|
||||
|
||||
options = {
|
||||
|
||||
services.ergo = {
|
||||
enable = mkEnableOption "Ergo service";
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/ergo";
|
||||
description = "The data directory for the Ergo node.";
|
||||
};
|
||||
|
||||
listen = {
|
||||
ip = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
description = "IP address on which the Ergo node should listen.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 9006;
|
||||
description = "Listen port for the Ergo node.";
|
||||
};
|
||||
};
|
||||
|
||||
api = {
|
||||
keyHash = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "324dcf027dd4a30a932c441f365a25e86b173defa4b8e58948253471b81b72cf";
|
||||
description = "Hex-encoded Blake2b256 hash of an API key as a 64-chars long Base16 string.";
|
||||
};
|
||||
|
||||
listen = {
|
||||
ip = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
description = "IP address that the Ergo node API should listen on if <option>api.keyHash</option> is defined.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 9052;
|
||||
description = "Listen port for the API endpoint if <option>api.keyHash</option> is defined.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testnet = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Connect to testnet network instead of the default mainnet.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "ergo";
|
||||
description = "The user as which to run the Ergo node.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = cfg.user;
|
||||
defaultText = literalExpression "config.${opt.user}";
|
||||
description = "The group as which to run the Ergo node.";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Open ports in the firewall for the Ergo node as well as the API.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
|
||||
];
|
||||
|
||||
systemd.services.ergo = {
|
||||
description = "ergo server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStart = ''${pkgs.ergo}/bin/ergo \
|
||||
${optionalString (!cfg.testnet)
|
||||
"--mainnet"} \
|
||||
-c ${configFile}'';
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.listen.port ] ++ [ cfg.api.listen.port ];
|
||||
};
|
||||
|
||||
users.users.${cfg.user} = {
|
||||
name = cfg.user;
|
||||
group = cfg.group;
|
||||
description = "Ergo daemon user";
|
||||
home = cfg.dataDir;
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
users.groups.${cfg.group} = {};
|
||||
|
||||
};
|
||||
}
|
||||
155
nixos/modules/services/networking/ergochat.nix
Normal file
155
nixos/modules/services/networking/ergochat.nix
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
{ config, lib, options, pkgs, ... }: let
|
||||
cfg = config.services.ergochat;
|
||||
in {
|
||||
options = {
|
||||
services.ergochat = {
|
||||
|
||||
enable = lib.mkEnableOption "Ergo IRC daemon";
|
||||
|
||||
openFilesLimit = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 1024;
|
||||
description = ''
|
||||
Maximum number of open files. Limits the clients and server connections.
|
||||
'';
|
||||
};
|
||||
|
||||
configFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = (pkgs.formats.yaml {}).generate "ergo.conf" cfg.settings;
|
||||
defaultText = "generated config file from <literal>.settings</literal>";
|
||||
description = ''
|
||||
Path to configuration file.
|
||||
Setting this will skip any configuration done via <literal>.settings</literal>
|
||||
'';
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = (pkgs.formats.yaml {}).type;
|
||||
description = ''
|
||||
Ergo IRC daemon configuration file.
|
||||
https://raw.githubusercontent.com/ergochat/ergo/master/default.yaml
|
||||
'';
|
||||
default = {
|
||||
network = {
|
||||
name = "testnetwork";
|
||||
};
|
||||
server = {
|
||||
name = "example.com";
|
||||
listeners = {
|
||||
":6667" = {};
|
||||
};
|
||||
casemapping = "permissive";
|
||||
enforce-utf = true;
|
||||
lookup-hostnames = false;
|
||||
ip-cloaking = {
|
||||
enabled = false;
|
||||
};
|
||||
forward-confirm-hostnames = false;
|
||||
check-ident = false;
|
||||
relaymsg = {
|
||||
enabled = false;
|
||||
};
|
||||
max-sendq = "1M";
|
||||
ip-limits = {
|
||||
count = false;
|
||||
throttle = false;
|
||||
};
|
||||
};
|
||||
datastore = {
|
||||
autoupgrade = true;
|
||||
# this points to the StateDirectory of the systemd service
|
||||
path = "/var/lib/ergo/ircd.db";
|
||||
};
|
||||
accounts = {
|
||||
authentication-enabled = true;
|
||||
registration = {
|
||||
enabled = true;
|
||||
allow-before-connect = true;
|
||||
throttling = {
|
||||
enabled = true;
|
||||
duration = "10m";
|
||||
max-attempts = 30;
|
||||
};
|
||||
bcrypt-cost = 4;
|
||||
email-verification.enabled = false;
|
||||
};
|
||||
multiclient = {
|
||||
enabled = true;
|
||||
allowed-by-default = true;
|
||||
always-on = "opt-out";
|
||||
auto-away = "opt-out";
|
||||
};
|
||||
};
|
||||
channels = {
|
||||
default-modes = "+ntC";
|
||||
registration = {
|
||||
enabled = true;
|
||||
};
|
||||
};
|
||||
limits = {
|
||||
nicklen = 32;
|
||||
identlen = 20;
|
||||
channellen = 64;
|
||||
awaylen = 390;
|
||||
kicklen = 390;
|
||||
topiclen = 390;
|
||||
};
|
||||
history = {
|
||||
enabled = true;
|
||||
channel-length = 2048;
|
||||
client-length = 256;
|
||||
autoresize-window = "3d";
|
||||
autoreplay-on-join = 0;
|
||||
chathistory-maxmessages = 100;
|
||||
znc-maxmessages = 2048;
|
||||
restrictions = {
|
||||
expire-time = "1w";
|
||||
query-cutoff = "none";
|
||||
grace-period = "1h";
|
||||
};
|
||||
retention = {
|
||||
allow-individual-delete = false;
|
||||
enable-account-indexing = false;
|
||||
};
|
||||
tagmsg-storage = {
|
||||
default = false;
|
||||
whitelist = [
|
||||
"+draft/react"
|
||||
"+react"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
config = lib.mkIf cfg.enable {
|
||||
|
||||
environment.etc."ergo.yaml".source = cfg.configFile;
|
||||
|
||||
# merge configured values with default values
|
||||
services.ergochat.settings =
|
||||
lib.mapAttrsRecursive (_: lib.mkDefault) options.services.ergochat.settings.default;
|
||||
|
||||
systemd.services.ergochat = {
|
||||
description = "Ergo IRC daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
# reload is not applying the changed config. further investigation is needed
|
||||
# at some point this should be enabled, since we don't want to restart for
|
||||
# every config change
|
||||
# reloadIfChanged = true;
|
||||
restartTriggers = [ cfg.configFile ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.ergochat}/bin/ergo run --conf /etc/ergo.yaml";
|
||||
ExecReload = "${pkgs.util-linux}/bin/kill -HUP $MAINPID";
|
||||
DynamicUser = true;
|
||||
StateDirectory = "ergo";
|
||||
LimitNOFILE = toString cfg.openFilesLimit;
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
meta.maintainers = with lib.maintainers; [ lassulus tv ];
|
||||
}
|
||||
95
nixos/modules/services/networking/eternal-terminal.nix
Normal file
95
nixos/modules/services/networking/eternal-terminal.nix
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.eternal-terminal;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.eternal-terminal = {
|
||||
|
||||
enable = mkEnableOption "Eternal Terminal server";
|
||||
|
||||
port = mkOption {
|
||||
default = 2022;
|
||||
type = types.int;
|
||||
description = ''
|
||||
The port the server should listen on. Will use the server's default (2022) if not specified.
|
||||
|
||||
Make sure to open this port in the firewall if necessary.
|
||||
'';
|
||||
};
|
||||
|
||||
verbosity = mkOption {
|
||||
default = 0;
|
||||
type = types.enum (lib.range 0 9);
|
||||
description = ''
|
||||
The verbosity level (0-9).
|
||||
'';
|
||||
};
|
||||
|
||||
silent = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
If enabled, disables all logging.
|
||||
'';
|
||||
};
|
||||
|
||||
logSize = mkOption {
|
||||
default = 20971520;
|
||||
type = types.int;
|
||||
description = ''
|
||||
The maximum log size.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
# We need to ensure the et package is fully installed because
|
||||
# the (remote) et client runs the `etterminal` binary when it
|
||||
# connects.
|
||||
environment.systemPackages = [ pkgs.eternal-terminal ];
|
||||
|
||||
systemd.services = {
|
||||
eternal-terminal = {
|
||||
description = "Eternal Terminal server.";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
ExecStart = "${pkgs.eternal-terminal}/bin/etserver --daemon --cfgfile=${pkgs.writeText "et.cfg" ''
|
||||
; et.cfg : Config file for Eternal Terminal
|
||||
;
|
||||
|
||||
[Networking]
|
||||
port = ${toString cfg.port}
|
||||
|
||||
[Debug]
|
||||
verbose = ${toString cfg.verbosity}
|
||||
silent = ${if cfg.silent then "1" else "0"}
|
||||
logsize = ${toString cfg.logSize}
|
||||
''}";
|
||||
Restart = "on-failure";
|
||||
KillMode = "process";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ ];
|
||||
};
|
||||
}
|
||||
65
nixos/modules/services/networking/fakeroute.nix
Normal file
65
nixos/modules/services/networking/fakeroute.nix
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.fakeroute;
|
||||
routeConf = pkgs.writeText "route.conf" (concatStringsSep "\n" cfg.route);
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.fakeroute = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable the fakeroute service.
|
||||
'';
|
||||
};
|
||||
|
||||
route = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [
|
||||
"216.102.187.130"
|
||||
"4.0.1.122"
|
||||
"198.116.142.34"
|
||||
"63.199.8.242"
|
||||
];
|
||||
description = ''
|
||||
Fake route that will appear after the real
|
||||
one to any host running a traceroute.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.fakeroute = {
|
||||
description = "Fakeroute Daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
User = "root";
|
||||
ExecStart = "${pkgs.fakeroute}/bin/fakeroute -f ${routeConf}";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ rnhmjoj ];
|
||||
|
||||
}
|
||||
63
nixos/modules/services/networking/ferm.nix
Normal file
63
nixos/modules/services/networking/ferm.nix
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.ferm;
|
||||
|
||||
configFile = pkgs.stdenv.mkDerivation {
|
||||
name = "ferm.conf";
|
||||
text = cfg.config;
|
||||
preferLocalBuild = true;
|
||||
buildCommand = ''
|
||||
echo -n "$text" > $out
|
||||
${cfg.package}/bin/ferm --noexec $out
|
||||
'';
|
||||
};
|
||||
in {
|
||||
options = {
|
||||
services.ferm = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to enable Ferm Firewall.
|
||||
*Warning*: Enabling this service WILL disable the existing NixOS
|
||||
firewall! Default firewall rules provided by packages are not
|
||||
considered at the moment.
|
||||
'';
|
||||
};
|
||||
config = mkOption {
|
||||
description = "Verbatim ferm.conf configuration.";
|
||||
default = "";
|
||||
defaultText = literalDocBook "empty firewall, allows any traffic";
|
||||
type = types.lines;
|
||||
};
|
||||
package = mkOption {
|
||||
description = "The ferm package.";
|
||||
type = types.package;
|
||||
default = pkgs.ferm;
|
||||
defaultText = literalExpression "pkgs.ferm";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.firewall.enable = false;
|
||||
systemd.services.ferm = {
|
||||
description = "Ferm Firewall";
|
||||
after = [ "ipset.target" ];
|
||||
before = [ "network-pre.target" ];
|
||||
wants = [ "network-pre.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
reloadIfChanged = true;
|
||||
serviceConfig = {
|
||||
Type="oneshot";
|
||||
RemainAfterExit = "yes";
|
||||
ExecStart = "${cfg.package}/bin/ferm ${configFile}";
|
||||
ExecReload = "${cfg.package}/bin/ferm ${configFile}";
|
||||
ExecStop = "${cfg.package}/bin/ferm -F ${configFile}";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
52
nixos/modules/services/networking/fireqos.nix
Normal file
52
nixos/modules/services/networking/fireqos.nix
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.fireqos;
|
||||
fireqosConfig = pkgs.writeText "fireqos.conf" "${cfg.config}";
|
||||
in {
|
||||
options.services.fireqos = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
If enabled, FireQOS will be launched with the specified
|
||||
configuration given in `config`.
|
||||
'';
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = ''
|
||||
interface wlp3s0 world-in input rate 10mbit ethernet
|
||||
class web commit 50kbit
|
||||
match tcp ports 80,443
|
||||
|
||||
interface wlp3s0 world-out input rate 10mbit ethernet
|
||||
class web commit 50kbit
|
||||
match tcp ports 80,443
|
||||
'';
|
||||
description = ''
|
||||
The FireQOS configuration goes here.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.fireqos = {
|
||||
description = "FireQOS";
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "${pkgs.firehol}/bin/fireqos start ${fireqosConfig}";
|
||||
ExecStop = [
|
||||
"${pkgs.firehol}/bin/fireqos stop"
|
||||
"${pkgs.firehol}/bin/fireqos clear_all_qos"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
584
nixos/modules/services/networking/firewall.nix
Normal file
584
nixos/modules/services/networking/firewall.nix
Normal file
|
|
@ -0,0 +1,584 @@
|
|||
/* This module enables a simple firewall.
|
||||
|
||||
The firewall can be customised in arbitrary ways by setting
|
||||
‘networking.firewall.extraCommands’. For modularity, the firewall
|
||||
uses several chains:
|
||||
|
||||
- ‘nixos-fw’ is the main chain for input packet processing.
|
||||
|
||||
- ‘nixos-fw-accept’ is called for accepted packets. If you want
|
||||
additional logging, or want to reject certain packets anyway, you
|
||||
can insert rules at the start of this chain.
|
||||
|
||||
- ‘nixos-fw-log-refuse’ and ‘nixos-fw-refuse’ are called for
|
||||
refused packets. (The former jumps to the latter after logging
|
||||
the packet.) If you want additional logging, or want to accept
|
||||
certain packets anyway, you can insert rules at the start of
|
||||
this chain.
|
||||
|
||||
- ‘nixos-fw-rpfilter’ is used as the main chain in the raw table,
|
||||
called from the built-in ‘PREROUTING’ chain. If the kernel
|
||||
supports it and `cfg.checkReversePath` is set this chain will
|
||||
perform a reverse path filter test.
|
||||
|
||||
- ‘nixos-drop’ is used while reloading the firewall in order to drop
|
||||
all traffic. Since reloading isn't implemented in an atomic way
|
||||
this'll prevent any traffic from leaking through while reloading
|
||||
the firewall. However, if the reloading fails, the ‘firewall-stop’
|
||||
script will be called which in return will effectively disable the
|
||||
complete firewall (in the default configuration).
|
||||
|
||||
*/
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.networking.firewall;
|
||||
|
||||
inherit (config.boot.kernelPackages) kernel;
|
||||
|
||||
kernelHasRPFilter = ((kernel.config.isEnabled or (x: false)) "IP_NF_MATCH_RPFILTER") || (kernel.features.netfilterRPFilter or false);
|
||||
|
||||
helpers = import ./helpers.nix { inherit config lib; };
|
||||
|
||||
writeShScript = name: text: let dir = pkgs.writeScriptBin name ''
|
||||
#! ${pkgs.runtimeShell} -e
|
||||
${text}
|
||||
''; in "${dir}/bin/${name}";
|
||||
|
||||
defaultInterface = { default = mapAttrs (name: value: cfg.${name}) commonOptions; };
|
||||
allInterfaces = defaultInterface // cfg.interfaces;
|
||||
|
||||
startScript = writeShScript "firewall-start" ''
|
||||
${helpers}
|
||||
|
||||
# Flush the old firewall rules. !!! Ideally, updating the
|
||||
# firewall would be atomic. Apparently that's possible
|
||||
# with iptables-restore.
|
||||
ip46tables -D INPUT -j nixos-fw 2> /dev/null || true
|
||||
for chain in nixos-fw nixos-fw-accept nixos-fw-log-refuse nixos-fw-refuse; do
|
||||
ip46tables -F "$chain" 2> /dev/null || true
|
||||
ip46tables -X "$chain" 2> /dev/null || true
|
||||
done
|
||||
|
||||
|
||||
# The "nixos-fw-accept" chain just accepts packets.
|
||||
ip46tables -N nixos-fw-accept
|
||||
ip46tables -A nixos-fw-accept -j ACCEPT
|
||||
|
||||
|
||||
# The "nixos-fw-refuse" chain rejects or drops packets.
|
||||
ip46tables -N nixos-fw-refuse
|
||||
|
||||
${if cfg.rejectPackets then ''
|
||||
# Send a reset for existing TCP connections that we've
|
||||
# somehow forgotten about. Send ICMP "port unreachable"
|
||||
# for everything else.
|
||||
ip46tables -A nixos-fw-refuse -p tcp ! --syn -j REJECT --reject-with tcp-reset
|
||||
ip46tables -A nixos-fw-refuse -j REJECT
|
||||
'' else ''
|
||||
ip46tables -A nixos-fw-refuse -j DROP
|
||||
''}
|
||||
|
||||
|
||||
# The "nixos-fw-log-refuse" chain performs logging, then
|
||||
# jumps to the "nixos-fw-refuse" chain.
|
||||
ip46tables -N nixos-fw-log-refuse
|
||||
|
||||
${optionalString cfg.logRefusedConnections ''
|
||||
ip46tables -A nixos-fw-log-refuse -p tcp --syn -j LOG --log-level info --log-prefix "refused connection: "
|
||||
''}
|
||||
${optionalString (cfg.logRefusedPackets && !cfg.logRefusedUnicastsOnly) ''
|
||||
ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type broadcast \
|
||||
-j LOG --log-level info --log-prefix "refused broadcast: "
|
||||
ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type multicast \
|
||||
-j LOG --log-level info --log-prefix "refused multicast: "
|
||||
''}
|
||||
ip46tables -A nixos-fw-log-refuse -m pkttype ! --pkt-type unicast -j nixos-fw-refuse
|
||||
${optionalString cfg.logRefusedPackets ''
|
||||
ip46tables -A nixos-fw-log-refuse \
|
||||
-j LOG --log-level info --log-prefix "refused packet: "
|
||||
''}
|
||||
ip46tables -A nixos-fw-log-refuse -j nixos-fw-refuse
|
||||
|
||||
|
||||
# The "nixos-fw" chain does the actual work.
|
||||
ip46tables -N nixos-fw
|
||||
|
||||
# Clean up rpfilter rules
|
||||
ip46tables -t raw -D PREROUTING -j nixos-fw-rpfilter 2> /dev/null || true
|
||||
ip46tables -t raw -F nixos-fw-rpfilter 2> /dev/null || true
|
||||
ip46tables -t raw -X nixos-fw-rpfilter 2> /dev/null || true
|
||||
|
||||
${optionalString (kernelHasRPFilter && (cfg.checkReversePath != false)) ''
|
||||
# Perform a reverse-path test to refuse spoofers
|
||||
# For now, we just drop, as the raw table doesn't have a log-refuse yet
|
||||
ip46tables -t raw -N nixos-fw-rpfilter 2> /dev/null || true
|
||||
ip46tables -t raw -A nixos-fw-rpfilter -m rpfilter --validmark ${optionalString (cfg.checkReversePath == "loose") "--loose"} -j RETURN
|
||||
|
||||
# Allows this host to act as a DHCP4 client without first having to use APIPA
|
||||
iptables -t raw -A nixos-fw-rpfilter -p udp --sport 67 --dport 68 -j RETURN
|
||||
|
||||
# Allows this host to act as a DHCPv4 server
|
||||
iptables -t raw -A nixos-fw-rpfilter -s 0.0.0.0 -d 255.255.255.255 -p udp --sport 68 --dport 67 -j RETURN
|
||||
|
||||
${optionalString cfg.logReversePathDrops ''
|
||||
ip46tables -t raw -A nixos-fw-rpfilter -j LOG --log-level info --log-prefix "rpfilter drop: "
|
||||
''}
|
||||
ip46tables -t raw -A nixos-fw-rpfilter -j DROP
|
||||
|
||||
ip46tables -t raw -A PREROUTING -j nixos-fw-rpfilter
|
||||
''}
|
||||
|
||||
# Accept all traffic on the trusted interfaces.
|
||||
${flip concatMapStrings cfg.trustedInterfaces (iface: ''
|
||||
ip46tables -A nixos-fw -i ${iface} -j nixos-fw-accept
|
||||
'')}
|
||||
|
||||
# Accept packets from established or related connections.
|
||||
ip46tables -A nixos-fw -m conntrack --ctstate ESTABLISHED,RELATED -j nixos-fw-accept
|
||||
|
||||
# Accept connections to the allowed TCP ports.
|
||||
${concatStrings (mapAttrsToList (iface: cfg:
|
||||
concatMapStrings (port:
|
||||
''
|
||||
ip46tables -A nixos-fw -p tcp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
|
||||
''
|
||||
) cfg.allowedTCPPorts
|
||||
) allInterfaces)}
|
||||
|
||||
# Accept connections to the allowed TCP port ranges.
|
||||
${concatStrings (mapAttrsToList (iface: cfg:
|
||||
concatMapStrings (rangeAttr:
|
||||
let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
|
||||
''
|
||||
ip46tables -A nixos-fw -p tcp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
|
||||
''
|
||||
) cfg.allowedTCPPortRanges
|
||||
) allInterfaces)}
|
||||
|
||||
# Accept packets on the allowed UDP ports.
|
||||
${concatStrings (mapAttrsToList (iface: cfg:
|
||||
concatMapStrings (port:
|
||||
''
|
||||
ip46tables -A nixos-fw -p udp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
|
||||
''
|
||||
) cfg.allowedUDPPorts
|
||||
) allInterfaces)}
|
||||
|
||||
# Accept packets on the allowed UDP port ranges.
|
||||
${concatStrings (mapAttrsToList (iface: cfg:
|
||||
concatMapStrings (rangeAttr:
|
||||
let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
|
||||
''
|
||||
ip46tables -A nixos-fw -p udp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
|
||||
''
|
||||
) cfg.allowedUDPPortRanges
|
||||
) allInterfaces)}
|
||||
|
||||
# Optionally respond to ICMPv4 pings.
|
||||
${optionalString cfg.allowPing ''
|
||||
iptables -w -A nixos-fw -p icmp --icmp-type echo-request ${optionalString (cfg.pingLimit != null)
|
||||
"-m limit ${cfg.pingLimit} "
|
||||
}-j nixos-fw-accept
|
||||
''}
|
||||
|
||||
${optionalString config.networking.enableIPv6 ''
|
||||
# Accept all ICMPv6 messages except redirects and node
|
||||
# information queries (type 139). See RFC 4890, section
|
||||
# 4.4.
|
||||
ip6tables -A nixos-fw -p icmpv6 --icmpv6-type redirect -j DROP
|
||||
ip6tables -A nixos-fw -p icmpv6 --icmpv6-type 139 -j DROP
|
||||
ip6tables -A nixos-fw -p icmpv6 -j nixos-fw-accept
|
||||
|
||||
# Allow this host to act as a DHCPv6 client
|
||||
ip6tables -A nixos-fw -d fe80::/64 -p udp --dport 546 -j nixos-fw-accept
|
||||
''}
|
||||
|
||||
${cfg.extraCommands}
|
||||
|
||||
# Reject/drop everything else.
|
||||
ip46tables -A nixos-fw -j nixos-fw-log-refuse
|
||||
|
||||
|
||||
# Enable the firewall.
|
||||
ip46tables -A INPUT -j nixos-fw
|
||||
'';
|
||||
|
||||
stopScript = writeShScript "firewall-stop" ''
|
||||
${helpers}
|
||||
|
||||
# Clean up in case reload fails
|
||||
ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
|
||||
|
||||
# Clean up after added ruleset
|
||||
ip46tables -D INPUT -j nixos-fw 2>/dev/null || true
|
||||
|
||||
${optionalString (kernelHasRPFilter && (cfg.checkReversePath != false)) ''
|
||||
ip46tables -t raw -D PREROUTING -j nixos-fw-rpfilter 2>/dev/null || true
|
||||
''}
|
||||
|
||||
${cfg.extraStopCommands}
|
||||
'';
|
||||
|
||||
reloadScript = writeShScript "firewall-reload" ''
|
||||
${helpers}
|
||||
|
||||
# Create a unique drop rule
|
||||
ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
|
||||
ip46tables -F nixos-drop 2>/dev/null || true
|
||||
ip46tables -X nixos-drop 2>/dev/null || true
|
||||
ip46tables -N nixos-drop
|
||||
ip46tables -A nixos-drop -j DROP
|
||||
|
||||
# Don't allow traffic to leak out until the script has completed
|
||||
ip46tables -A INPUT -j nixos-drop
|
||||
|
||||
${cfg.extraStopCommands}
|
||||
|
||||
if ${startScript}; then
|
||||
ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
|
||||
else
|
||||
echo "Failed to reload firewall... Stopping"
|
||||
${stopScript}
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
|
||||
canonicalizePortList =
|
||||
ports: lib.unique (builtins.sort builtins.lessThan ports);
|
||||
|
||||
commonOptions = {
|
||||
allowedTCPPorts = mkOption {
|
||||
type = types.listOf types.port;
|
||||
default = [ ];
|
||||
apply = canonicalizePortList;
|
||||
example = [ 22 80 ];
|
||||
description =
|
||||
''
|
||||
List of TCP ports on which incoming connections are
|
||||
accepted.
|
||||
'';
|
||||
};
|
||||
|
||||
allowedTCPPortRanges = mkOption {
|
||||
type = types.listOf (types.attrsOf types.port);
|
||||
default = [ ];
|
||||
example = [ { from = 8999; to = 9003; } ];
|
||||
description =
|
||||
''
|
||||
A range of TCP ports on which incoming connections are
|
||||
accepted.
|
||||
'';
|
||||
};
|
||||
|
||||
allowedUDPPorts = mkOption {
|
||||
type = types.listOf types.port;
|
||||
default = [ ];
|
||||
apply = canonicalizePortList;
|
||||
example = [ 53 ];
|
||||
description =
|
||||
''
|
||||
List of open UDP ports.
|
||||
'';
|
||||
};
|
||||
|
||||
allowedUDPPortRanges = mkOption {
|
||||
type = types.listOf (types.attrsOf types.port);
|
||||
default = [ ];
|
||||
example = [ { from = 60000; to = 61000; } ];
|
||||
description =
|
||||
''
|
||||
Range of open UDP ports.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
networking.firewall = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description =
|
||||
''
|
||||
Whether to enable the firewall. This is a simple stateful
|
||||
firewall that blocks connection attempts to unauthorised TCP
|
||||
or UDP ports on this machine. It does not affect packet
|
||||
forwarding.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.iptables;
|
||||
defaultText = literalExpression "pkgs.iptables";
|
||||
example = literalExpression "pkgs.iptables-legacy";
|
||||
description =
|
||||
''
|
||||
The iptables package to use for running the firewall service."
|
||||
'';
|
||||
};
|
||||
|
||||
logRefusedConnections = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description =
|
||||
''
|
||||
Whether to log rejected or dropped incoming connections.
|
||||
Note: The logs are found in the kernel logs, i.e. dmesg
|
||||
or journalctl -k.
|
||||
'';
|
||||
};
|
||||
|
||||
logRefusedPackets = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description =
|
||||
''
|
||||
Whether to log all rejected or dropped incoming packets.
|
||||
This tends to give a lot of log messages, so it's mostly
|
||||
useful for debugging.
|
||||
Note: The logs are found in the kernel logs, i.e. dmesg
|
||||
or journalctl -k.
|
||||
'';
|
||||
};
|
||||
|
||||
logRefusedUnicastsOnly = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description =
|
||||
''
|
||||
If <option>networking.firewall.logRefusedPackets</option>
|
||||
and this option are enabled, then only log packets
|
||||
specifically directed at this machine, i.e., not broadcasts
|
||||
or multicasts.
|
||||
'';
|
||||
};
|
||||
|
||||
rejectPackets = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description =
|
||||
''
|
||||
If set, refused packets are rejected rather than dropped
|
||||
(ignored). This means that an ICMP "port unreachable" error
|
||||
message is sent back to the client (or a TCP RST packet in
|
||||
case of an existing connection). Rejecting packets makes
|
||||
port scanning somewhat easier.
|
||||
'';
|
||||
};
|
||||
|
||||
trustedInterfaces = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "enp0s2" ];
|
||||
description =
|
||||
''
|
||||
Traffic coming in from these interfaces will be accepted
|
||||
unconditionally. Traffic from the loopback (lo) interface
|
||||
will always be accepted.
|
||||
'';
|
||||
};
|
||||
|
||||
allowPing = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description =
|
||||
''
|
||||
Whether to respond to incoming ICMPv4 echo requests
|
||||
("pings"). ICMPv6 pings are always allowed because the
|
||||
larger address space of IPv6 makes network scanning much
|
||||
less effective.
|
||||
'';
|
||||
};
|
||||
|
||||
pingLimit = mkOption {
|
||||
type = types.nullOr (types.separatedString " ");
|
||||
default = null;
|
||||
example = "--limit 1/minute --limit-burst 5";
|
||||
description =
|
||||
''
|
||||
If pings are allowed, this allows setting rate limits
|
||||
on them. If non-null, this option should be in the form of
|
||||
flags like "--limit 1/minute --limit-burst 5"
|
||||
'';
|
||||
};
|
||||
|
||||
checkReversePath = mkOption {
|
||||
type = types.either types.bool (types.enum ["strict" "loose"]);
|
||||
default = kernelHasRPFilter;
|
||||
defaultText = literalDocBook "<literal>true</literal> if supported by the chosen kernel";
|
||||
example = "loose";
|
||||
description =
|
||||
''
|
||||
Performs a reverse path filter test on a packet. If a reply
|
||||
to the packet would not be sent via the same interface that
|
||||
the packet arrived on, it is refused.
|
||||
|
||||
If using asymmetric routing or other complicated routing, set
|
||||
this option to loose mode or disable it and setup your own
|
||||
counter-measures.
|
||||
|
||||
This option can be either true (or "strict"), "loose" (only
|
||||
drop the packet if the source address is not reachable via any
|
||||
interface) or false. Defaults to the value of
|
||||
kernelHasRPFilter.
|
||||
'';
|
||||
};
|
||||
|
||||
logReversePathDrops = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description =
|
||||
''
|
||||
Logs dropped packets failing the reverse path filter test if
|
||||
the option networking.firewall.checkReversePath is enabled.
|
||||
'';
|
||||
};
|
||||
|
||||
connectionTrackingModules = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "ftp" "irc" "sane" "sip" "tftp" "amanda" "h323" "netbios_sn" "pptp" "snmp" ];
|
||||
description =
|
||||
''
|
||||
List of connection-tracking helpers that are auto-loaded.
|
||||
The complete list of possible values is given in the example.
|
||||
|
||||
As helpers can pose as a security risk, it is advised to
|
||||
set this to an empty list and disable the setting
|
||||
networking.firewall.autoLoadConntrackHelpers unless you
|
||||
know what you are doing. Connection tracking is disabled
|
||||
by default.
|
||||
|
||||
Loading of helpers is recommended to be done through the
|
||||
CT target. More info:
|
||||
https://home.regit.org/netfilter-en/secure-use-of-helpers/
|
||||
'';
|
||||
};
|
||||
|
||||
autoLoadConntrackHelpers = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description =
|
||||
''
|
||||
Whether to auto-load connection-tracking helpers.
|
||||
See the description at networking.firewall.connectionTrackingModules
|
||||
|
||||
(needs kernel 3.5+)
|
||||
'';
|
||||
};
|
||||
|
||||
extraCommands = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = "iptables -A INPUT -p icmp -j ACCEPT";
|
||||
description =
|
||||
''
|
||||
Additional shell commands executed as part of the firewall
|
||||
initialisation script. These are executed just before the
|
||||
final "reject" firewall rule is added, so they can be used
|
||||
to allow packets that would otherwise be refused.
|
||||
'';
|
||||
};
|
||||
|
||||
extraPackages = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [ ];
|
||||
example = literalExpression "[ pkgs.ipset ]";
|
||||
description =
|
||||
''
|
||||
Additional packages to be included in the environment of the system
|
||||
as well as the path of networking.firewall.extraCommands.
|
||||
'';
|
||||
};
|
||||
|
||||
extraStopCommands = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = "iptables -P INPUT ACCEPT";
|
||||
description =
|
||||
''
|
||||
Additional shell commands executed as part of the firewall
|
||||
shutdown script. These are executed just after the removal
|
||||
of the NixOS input rule, or if the service enters a failed
|
||||
state.
|
||||
'';
|
||||
};
|
||||
|
||||
interfaces = mkOption {
|
||||
default = { };
|
||||
type = with types; attrsOf (submodule [ { options = commonOptions; } ]);
|
||||
description =
|
||||
''
|
||||
Interface-specific open ports.
|
||||
'';
|
||||
};
|
||||
} // commonOptions;
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
# FIXME: Maybe if `enable' is false, the firewall should still be
|
||||
# built but not started by default?
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
networking.firewall.trustedInterfaces = [ "lo" ];
|
||||
|
||||
environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
|
||||
|
||||
boot.kernelModules = (optional cfg.autoLoadConntrackHelpers "nf_conntrack")
|
||||
++ map (x: "nf_conntrack_${x}") cfg.connectionTrackingModules;
|
||||
boot.extraModprobeConfig = optionalString cfg.autoLoadConntrackHelpers ''
|
||||
options nf_conntrack nf_conntrack_helper=1
|
||||
'';
|
||||
|
||||
assertions = [
|
||||
# This is approximately "checkReversePath -> kernelHasRPFilter",
|
||||
# but the checkReversePath option can include non-boolean
|
||||
# values.
|
||||
{ assertion = cfg.checkReversePath == false || kernelHasRPFilter;
|
||||
message = "This kernel does not support rpfilter"; }
|
||||
];
|
||||
|
||||
systemd.services.firewall = {
|
||||
description = "Firewall";
|
||||
wantedBy = [ "sysinit.target" ];
|
||||
wants = [ "network-pre.target" ];
|
||||
before = [ "network-pre.target" ];
|
||||
after = [ "systemd-modules-load.service" ];
|
||||
|
||||
path = [ cfg.package ] ++ cfg.extraPackages;
|
||||
|
||||
# FIXME: this module may also try to load kernel modules, but
|
||||
# containers don't have CAP_SYS_MODULE. So the host system had
|
||||
# better have all necessary modules already loaded.
|
||||
unitConfig.ConditionCapability = "CAP_NET_ADMIN";
|
||||
unitConfig.DefaultDependencies = false;
|
||||
|
||||
reloadIfChanged = true;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "@${startScript} firewall-start";
|
||||
ExecReload = "@${reloadScript} firewall-reload";
|
||||
ExecStop = "@${stopScript} firewall-stop";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
192
nixos/modules/services/networking/flannel.nix
Normal file
192
nixos/modules/services/networking/flannel.nix
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.flannel;
|
||||
|
||||
networkConfig = filterAttrs (n: v: v != null) {
|
||||
Network = cfg.network;
|
||||
SubnetLen = cfg.subnetLen;
|
||||
SubnetMin = cfg.subnetMin;
|
||||
SubnetMax = cfg.subnetMax;
|
||||
Backend = cfg.backend;
|
||||
};
|
||||
in {
|
||||
options.services.flannel = {
|
||||
enable = mkEnableOption "flannel";
|
||||
|
||||
package = mkOption {
|
||||
description = "Package to use for flannel";
|
||||
type = types.package;
|
||||
default = pkgs.flannel;
|
||||
defaultText = literalExpression "pkgs.flannel";
|
||||
};
|
||||
|
||||
publicIp = mkOption {
|
||||
description = ''
|
||||
IP accessible by other nodes for inter-host communication.
|
||||
Defaults to the IP of the interface being used for communication.
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
iface = mkOption {
|
||||
description = ''
|
||||
Interface to use (IP or name) for inter-host communication.
|
||||
Defaults to the interface for the default route on the machine.
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
etcd = {
|
||||
endpoints = mkOption {
|
||||
description = "Etcd endpoints";
|
||||
type = types.listOf types.str;
|
||||
default = ["http://127.0.0.1:2379"];
|
||||
};
|
||||
|
||||
prefix = mkOption {
|
||||
description = "Etcd key prefix";
|
||||
type = types.str;
|
||||
default = "/coreos.com/network";
|
||||
};
|
||||
|
||||
caFile = mkOption {
|
||||
description = "Etcd certificate authority file";
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
};
|
||||
|
||||
certFile = mkOption {
|
||||
description = "Etcd cert file";
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
};
|
||||
|
||||
keyFile = mkOption {
|
||||
description = "Etcd key file";
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
|
||||
kubeconfig = mkOption {
|
||||
description = ''
|
||||
Path to kubeconfig to use for storing flannel config using the
|
||||
Kubernetes API
|
||||
'';
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
};
|
||||
|
||||
network = mkOption {
|
||||
description = " IPv4 network in CIDR format to use for the entire flannel network.";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
nodeName = mkOption {
|
||||
description = ''
|
||||
Needed when running with Kubernetes as backend as this cannot be auto-detected";
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
default = with config.networking; (hostName + optionalString (domain != null) ".${domain}");
|
||||
defaultText = literalExpression ''
|
||||
with config.networking; (hostName + optionalString (domain != null) ".''${domain}")
|
||||
'';
|
||||
example = "node1.example.com";
|
||||
};
|
||||
|
||||
storageBackend = mkOption {
|
||||
description = "Determines where flannel stores its configuration at runtime";
|
||||
type = types.enum ["etcd" "kubernetes"];
|
||||
default = "etcd";
|
||||
};
|
||||
|
||||
subnetLen = mkOption {
|
||||
description = ''
|
||||
The size of the subnet allocated to each host. Defaults to 24 (i.e. /24)
|
||||
unless the Network was configured to be smaller than a /24 in which case
|
||||
it is one less than the network.
|
||||
'';
|
||||
type = types.int;
|
||||
default = 24;
|
||||
};
|
||||
|
||||
subnetMin = mkOption {
|
||||
description = ''
|
||||
The beginning of IP range which the subnet allocation should start with.
|
||||
Defaults to the first subnet of Network.
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
subnetMax = mkOption {
|
||||
description = ''
|
||||
The end of IP range which the subnet allocation should start with.
|
||||
Defaults to the last subnet of Network.
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
backend = mkOption {
|
||||
description = "Type of backend to use and specific configurations for that backend.";
|
||||
type = types.attrs;
|
||||
default = {
|
||||
Type = "vxlan";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.flannel = {
|
||||
description = "Flannel Service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
environment = {
|
||||
FLANNELD_PUBLIC_IP = cfg.publicIp;
|
||||
FLANNELD_IFACE = cfg.iface;
|
||||
} // optionalAttrs (cfg.storageBackend == "etcd") {
|
||||
FLANNELD_ETCD_ENDPOINTS = concatStringsSep "," cfg.etcd.endpoints;
|
||||
FLANNELD_ETCD_KEYFILE = cfg.etcd.keyFile;
|
||||
FLANNELD_ETCD_CERTFILE = cfg.etcd.certFile;
|
||||
FLANNELD_ETCD_CAFILE = cfg.etcd.caFile;
|
||||
ETCDCTL_CERT_FILE = cfg.etcd.certFile;
|
||||
ETCDCTL_KEY_FILE = cfg.etcd.keyFile;
|
||||
ETCDCTL_CA_FILE = cfg.etcd.caFile;
|
||||
ETCDCTL_PEERS = concatStringsSep "," cfg.etcd.endpoints;
|
||||
} // optionalAttrs (cfg.storageBackend == "kubernetes") {
|
||||
FLANNELD_KUBE_SUBNET_MGR = "true";
|
||||
FLANNELD_KUBECONFIG_FILE = cfg.kubeconfig;
|
||||
NODE_NAME = cfg.nodeName;
|
||||
};
|
||||
path = [ pkgs.iptables ];
|
||||
preStart = optionalString (cfg.storageBackend == "etcd") ''
|
||||
echo "setting network configuration"
|
||||
until ${pkgs.etcd}/bin/etcdctl set /coreos.com/network/config '${builtins.toJSON networkConfig}'
|
||||
do
|
||||
echo "setting network configuration, retry"
|
||||
sleep 1
|
||||
done
|
||||
'';
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/flannel";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
RuntimeDirectory = "flannel";
|
||||
};
|
||||
};
|
||||
|
||||
services.etcd.enable = mkDefault (cfg.storageBackend == "etcd" && cfg.etcd.endpoints == ["http://127.0.0.1:2379"]);
|
||||
|
||||
# for some reason, flannel doesn't let you configure this path
|
||||
# see: https://github.com/coreos/flannel/blob/master/Documentation/configuration.md#configuration
|
||||
environment.etc."kube-flannel/net-conf.json" = mkIf (cfg.storageBackend == "kubernetes") {
|
||||
source = pkgs.writeText "net-conf.json" (builtins.toJSON networkConfig);
|
||||
};
|
||||
};
|
||||
}
|
||||
64
nixos/modules/services/networking/freenet.nix
Normal file
64
nixos/modules/services/networking/freenet.nix
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# NixOS module for Freenet daemon
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.freenet;
|
||||
varDir = "/var/lib/freenet";
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
### configuration
|
||||
|
||||
options = {
|
||||
|
||||
services.freenet = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable the Freenet daemon";
|
||||
};
|
||||
|
||||
nice = mkOption {
|
||||
type = types.int;
|
||||
default = 10;
|
||||
description = "Set the nice level for the Freenet daemon";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.services.freenet = {
|
||||
description = "Freenet daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig.ExecStart = "${pkgs.freenet}/bin/freenet";
|
||||
serviceConfig.User = "freenet";
|
||||
serviceConfig.UMask = "0007";
|
||||
serviceConfig.WorkingDirectory = varDir;
|
||||
serviceConfig.Nice = cfg.nice;
|
||||
};
|
||||
|
||||
users.users.freenet = {
|
||||
group = "freenet";
|
||||
description = "Freenet daemon user";
|
||||
home = varDir;
|
||||
createHome = true;
|
||||
uid = config.ids.uids.freenet;
|
||||
};
|
||||
|
||||
users.groups.freenet.gid = config.ids.gids.freenet;
|
||||
};
|
||||
|
||||
}
|
||||
86
nixos/modules/services/networking/freeradius.nix
Normal file
86
nixos/modules/services/networking/freeradius.nix
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.freeradius;
|
||||
|
||||
freeradiusService = cfg:
|
||||
{
|
||||
description = "FreeRadius server";
|
||||
wantedBy = ["multi-user.target"];
|
||||
after = ["network.target"];
|
||||
wants = ["network.target"];
|
||||
preStart = ''
|
||||
${pkgs.freeradius}/bin/radiusd -C -d ${cfg.configDir} -l stdout
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.freeradius}/bin/radiusd -f -d ${cfg.configDir} -l stdout" +
|
||||
optionalString cfg.debug " -xx";
|
||||
ExecReload = [
|
||||
"${pkgs.freeradius}/bin/radiusd -C -d ${cfg.configDir} -l stdout"
|
||||
"${pkgs.coreutils}/bin/kill -HUP $MAINPID"
|
||||
];
|
||||
User = "radius";
|
||||
ProtectSystem = "full";
|
||||
ProtectHome = "on";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 2;
|
||||
LogsDirectory = "radius";
|
||||
};
|
||||
};
|
||||
|
||||
freeradiusConfig = {
|
||||
enable = mkEnableOption "the freeradius server";
|
||||
|
||||
configDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/etc/raddb";
|
||||
description = ''
|
||||
The path of the freeradius server configuration directory.
|
||||
'';
|
||||
};
|
||||
|
||||
debug = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable debug logging for freeradius (-xx
|
||||
option). This should not be left on, since it includes
|
||||
sensitive data such as passwords in the logs.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
services.freeradius = freeradiusConfig;
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf (cfg.enable) {
|
||||
|
||||
users = {
|
||||
users.radius = {
|
||||
/*uid = config.ids.uids.radius;*/
|
||||
description = "Radius daemon user";
|
||||
isSystemUser = true;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.freeradius = freeradiusService cfg;
|
||||
warnings = optional cfg.debug "Freeradius debug logging is enabled. This will log passwords in plaintext to the journal!";
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
220
nixos/modules/services/networking/frr.nix
Normal file
220
nixos/modules/services/networking/frr.nix
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.frr;
|
||||
|
||||
services = [
|
||||
"static"
|
||||
"bgp"
|
||||
"ospf"
|
||||
"ospf6"
|
||||
"rip"
|
||||
"ripng"
|
||||
"isis"
|
||||
"pim"
|
||||
"ldp"
|
||||
"nhrp"
|
||||
"eigrp"
|
||||
"babel"
|
||||
"sharp"
|
||||
"pbr"
|
||||
"bfd"
|
||||
"fabric"
|
||||
];
|
||||
|
||||
allServices = services ++ [ "zebra" ];
|
||||
|
||||
isEnabled = service: cfg.${service}.enable;
|
||||
|
||||
daemonName = service: if service == "zebra" then service else "${service}d";
|
||||
|
||||
configFile = service:
|
||||
let
|
||||
scfg = cfg.${service};
|
||||
in
|
||||
if scfg.configFile != null then scfg.configFile
|
||||
else pkgs.writeText "${daemonName service}.conf"
|
||||
''
|
||||
! FRR ${daemonName service} configuration
|
||||
!
|
||||
hostname ${config.networking.hostName}
|
||||
log syslog
|
||||
service password-encryption
|
||||
!
|
||||
${scfg.config}
|
||||
!
|
||||
end
|
||||
'';
|
||||
|
||||
serviceOptions = service:
|
||||
{
|
||||
enable = mkEnableOption "the FRR ${toUpper service} routing protocol";
|
||||
|
||||
configFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/etc/frr/${daemonName service}.conf";
|
||||
description = ''
|
||||
Configuration file to use for FRR ${daemonName service}.
|
||||
By default the NixOS generated files are used.
|
||||
'';
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example =
|
||||
let
|
||||
examples = {
|
||||
rip = ''
|
||||
router rip
|
||||
network 10.0.0.0/8
|
||||
'';
|
||||
|
||||
ospf = ''
|
||||
router ospf
|
||||
network 10.0.0.0/8 area 0
|
||||
'';
|
||||
|
||||
bgp = ''
|
||||
router bgp 65001
|
||||
neighbor 10.0.0.1 remote-as 65001
|
||||
'';
|
||||
};
|
||||
in
|
||||
examples.${service} or "";
|
||||
description = ''
|
||||
${daemonName service} configuration statements.
|
||||
'';
|
||||
};
|
||||
|
||||
vtyListenAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = ''
|
||||
Address to bind to for the VTY interface.
|
||||
'';
|
||||
};
|
||||
|
||||
vtyListenPort = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
TCP Port to bind to for the VTY interface.
|
||||
'';
|
||||
};
|
||||
|
||||
extraOptions = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Extra options for the daemon.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
imports = [
|
||||
{
|
||||
options.services.frr = {
|
||||
zebra = (serviceOptions "zebra") // {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = any isEnabled services;
|
||||
description = ''
|
||||
Whether to enable the Zebra routing manager.
|
||||
|
||||
The Zebra routing manager is automatically enabled
|
||||
if any routing protocols are configured.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
{ options.services.frr = (genAttrs services serviceOptions); }
|
||||
];
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf (any isEnabled allServices) {
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.frr # for the vtysh tool
|
||||
];
|
||||
|
||||
users.users.frr = {
|
||||
description = "FRR daemon user";
|
||||
isSystemUser = true;
|
||||
group = "frr";
|
||||
};
|
||||
|
||||
users.groups = {
|
||||
frr = {};
|
||||
# Members of the frrvty group can use vtysh to inspect the FRR daemons
|
||||
frrvty = { members = [ "frr" ]; };
|
||||
};
|
||||
|
||||
environment.etc = let
|
||||
mkEtcLink = service: {
|
||||
name = "frr/${service}.conf";
|
||||
value.source = configFile service;
|
||||
};
|
||||
in
|
||||
(builtins.listToAttrs
|
||||
(map mkEtcLink (filter isEnabled allServices))) // {
|
||||
"frr/vtysh.conf".text = "";
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /run/frr 0750 frr frr -"
|
||||
];
|
||||
|
||||
systemd.services =
|
||||
let
|
||||
frrService = service:
|
||||
let
|
||||
scfg = cfg.${service};
|
||||
daemon = daemonName service;
|
||||
in
|
||||
nameValuePair daemon ({
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-pre.target" "systemd-sysctl.service" ] ++ lib.optionals (service != "zebra") [ "zebra.service" ];
|
||||
bindsTo = lib.optionals (service != "zebra") [ "zebra.service" ];
|
||||
wants = [ "network.target" ];
|
||||
|
||||
description = if service == "zebra" then "FRR Zebra routing manager"
|
||||
else "FRR ${toUpper service} routing daemon";
|
||||
|
||||
unitConfig.Documentation = if service == "zebra" then "man:zebra(8)"
|
||||
else "man:${daemon}(8) man:zebra(8)";
|
||||
|
||||
restartTriggers = [
|
||||
(configFile service)
|
||||
];
|
||||
reloadIfChanged = true;
|
||||
|
||||
serviceConfig = {
|
||||
PIDFile = "frr/${daemon}.pid";
|
||||
ExecStart = "${pkgs.frr}/libexec/frr/${daemon} -f /etc/frr/${service}.conf"
|
||||
+ optionalString (scfg.vtyListenAddress != "") " -A ${scfg.vtyListenAddress}"
|
||||
+ optionalString (scfg.vtyListenPort != null) " -P ${toString scfg.vtyListenPort}"
|
||||
+ " " + (concatStringsSep " " scfg.extraOptions);
|
||||
ExecReload = "${pkgs.python3.interpreter} ${pkgs.frr}/libexec/frr/frr-reload.py --reload --daemon ${daemonName service} --bindir ${pkgs.frr}/bin --rundir /run/frr /etc/frr/${service}.conf";
|
||||
Restart = "on-abnormal";
|
||||
};
|
||||
});
|
||||
in
|
||||
listToAttrs (map frrService (filter isEnabled allServices));
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ woffs ];
|
||||
|
||||
}
|
||||
59
nixos/modules/services/networking/gateone.nix
Normal file
59
nixos/modules/services/networking/gateone.nix
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
{ config, lib, pkgs, ...}:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.gateone;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.gateone = {
|
||||
enable = mkEnableOption "GateOne server";
|
||||
pidDir = mkOption {
|
||||
default = "/run/gateone";
|
||||
type = types.path;
|
||||
description = "Path of pid files for GateOne.";
|
||||
};
|
||||
settingsDir = mkOption {
|
||||
default = "/var/lib/gateone";
|
||||
type = types.path;
|
||||
description = "Path of configuration files for GateOne.";
|
||||
};
|
||||
};
|
||||
};
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = with pkgs.pythonPackages; [
|
||||
gateone pkgs.openssh pkgs.procps pkgs.coreutils pkgs.cacert];
|
||||
|
||||
users.users.gateone = {
|
||||
description = "GateOne privilege separation user";
|
||||
uid = config.ids.uids.gateone;
|
||||
home = cfg.settingsDir;
|
||||
};
|
||||
users.groups.gateone.gid = config.ids.gids.gateone;
|
||||
|
||||
systemd.services.gateone = with pkgs; {
|
||||
description = "GateOne web-based terminal";
|
||||
path = [ pythonPackages.gateone nix openssh procps coreutils ];
|
||||
preStart = ''
|
||||
if [ ! -d ${cfg.settingsDir} ] ; then
|
||||
mkdir -m 0750 -p ${cfg.settingsDir}
|
||||
chown -R gateone:gateone ${cfg.settingsDir}
|
||||
fi
|
||||
if [ ! -d ${cfg.pidDir} ] ; then
|
||||
mkdir -m 0750 -p ${cfg.pidDir}
|
||||
chown -R gateone:gateone ${cfg.pidDir}
|
||||
fi
|
||||
'';
|
||||
#unitConfig.RequiresMountsFor = "${cfg.settingsDir}";
|
||||
serviceConfig = {
|
||||
ExecStart = ''${pythonPackages.gateone}/bin/gateone --settings_dir=${cfg.settingsDir} --pid_file=${cfg.pidDir}/gateone.pid --gid=${toString config.ids.gids.gateone} --uid=${toString config.ids.uids.gateone}'';
|
||||
User = "gateone";
|
||||
Group = "gateone";
|
||||
WorkingDirectory = cfg.settingsDir;
|
||||
};
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
requires = [ "network.target" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
29
nixos/modules/services/networking/gdomap.nix
Normal file
29
nixos/modules/services/networking/gdomap.nix
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
#
|
||||
# interface
|
||||
#
|
||||
options = {
|
||||
services.gdomap = {
|
||||
enable = mkEnableOption "GNUstep Distributed Objects name server";
|
||||
};
|
||||
};
|
||||
|
||||
#
|
||||
# implementation
|
||||
#
|
||||
config = mkIf config.services.gdomap.enable {
|
||||
# NOTE: gdomap runs as root
|
||||
# TODO: extra user for gdomap?
|
||||
systemd.services.gdomap = {
|
||||
description = "gdomap server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
path = [ pkgs.gnustep.base ];
|
||||
serviceConfig.ExecStart = "${pkgs.gnustep.base}/bin/gdomap -f";
|
||||
};
|
||||
};
|
||||
}
|
||||
242
nixos/modules/services/networking/ghostunnel.nix
Normal file
242
nixos/modules/services/networking/ghostunnel.nix
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
inherit (lib)
|
||||
attrValues
|
||||
concatMap
|
||||
concatStringsSep
|
||||
escapeShellArg
|
||||
literalExpression
|
||||
mapAttrs'
|
||||
mkDefault
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
nameValuePair
|
||||
optional
|
||||
types
|
||||
;
|
||||
|
||||
mainCfg = config.services.ghostunnel;
|
||||
|
||||
module = { config, name, ... }:
|
||||
{
|
||||
options = {
|
||||
|
||||
listen = mkOption {
|
||||
description = ''
|
||||
Address and port to listen on (can be HOST:PORT, unix:PATH).
|
||||
'';
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
target = mkOption {
|
||||
description = ''
|
||||
Address to forward connections to (can be HOST:PORT or unix:PATH).
|
||||
'';
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
keystore = mkOption {
|
||||
description = ''
|
||||
Path to keystore (combined PEM with cert/key, or PKCS12 keystore).
|
||||
|
||||
NB: storepass is not supported because it would expose credentials via <code>/proc/*/cmdline</code>.
|
||||
|
||||
Specify this or <code>cert</code> and <code>key</code>.
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
cert = mkOption {
|
||||
description = ''
|
||||
Path to certificate (PEM with certificate chain).
|
||||
|
||||
Not required if <code>keystore</code> is set.
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
key = mkOption {
|
||||
description = ''
|
||||
Path to certificate private key (PEM with private key).
|
||||
|
||||
Not required if <code>keystore</code> is set.
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
cacert = mkOption {
|
||||
description = ''
|
||||
Path to CA bundle file (PEM/X509). Uses system trust store if <code>null</code>.
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
};
|
||||
|
||||
disableAuthentication = mkOption {
|
||||
description = ''
|
||||
Disable client authentication, no client certificate will be required.
|
||||
'';
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
allowAll = mkOption {
|
||||
description = ''
|
||||
If true, allow all clients, do not check client cert subject.
|
||||
'';
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
allowCN = mkOption {
|
||||
description = ''
|
||||
Allow client if common name appears in the list.
|
||||
'';
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
|
||||
allowOU = mkOption {
|
||||
description = ''
|
||||
Allow client if organizational unit name appears in the list.
|
||||
'';
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
|
||||
allowDNS = mkOption {
|
||||
description = ''
|
||||
Allow client if DNS subject alternative name appears in the list.
|
||||
'';
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
|
||||
allowURI = mkOption {
|
||||
description = ''
|
||||
Allow client if URI subject alternative name appears in the list.
|
||||
'';
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
|
||||
extraArguments = mkOption {
|
||||
description = "Extra arguments to pass to <code>ghostunnel server</code>";
|
||||
type = types.separatedString " ";
|
||||
default = "";
|
||||
};
|
||||
|
||||
unsafeTarget = mkOption {
|
||||
description = ''
|
||||
If set, does not limit target to localhost, 127.0.0.1, [::1], or UNIX sockets.
|
||||
|
||||
This is meant to protect against accidental unencrypted traffic on
|
||||
untrusted networks.
|
||||
'';
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
# Definitions to apply at the root of the NixOS configuration.
|
||||
atRoot = mkOption {
|
||||
internal = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Clients should not be authenticated with the public root certificates
|
||||
# (afaict, it doesn't make sense), so we only provide that default when
|
||||
# client cert auth is disabled.
|
||||
config.cacert = mkIf config.disableAuthentication (mkDefault null);
|
||||
|
||||
config.atRoot = {
|
||||
assertions = [
|
||||
{ message = ''
|
||||
services.ghostunnel.servers.${name}: At least one access control flag is required.
|
||||
Set at least one of:
|
||||
- services.ghostunnel.servers.${name}.disableAuthentication
|
||||
- services.ghostunnel.servers.${name}.allowAll
|
||||
- services.ghostunnel.servers.${name}.allowCN
|
||||
- services.ghostunnel.servers.${name}.allowOU
|
||||
- services.ghostunnel.servers.${name}.allowDNS
|
||||
- services.ghostunnel.servers.${name}.allowURI
|
||||
'';
|
||||
assertion = config.disableAuthentication
|
||||
|| config.allowAll
|
||||
|| config.allowCN != []
|
||||
|| config.allowOU != []
|
||||
|| config.allowDNS != []
|
||||
|| config.allowURI != []
|
||||
;
|
||||
}
|
||||
];
|
||||
|
||||
systemd.services."ghostunnel-server-${name}" = {
|
||||
after = [ "network.target" ];
|
||||
wants = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Restart = "always";
|
||||
AmbientCapabilities = ["CAP_NET_BIND_SERVICE"];
|
||||
DynamicUser = true;
|
||||
LoadCredential = optional (config.keystore != null) "keystore:${config.keystore}"
|
||||
++ optional (config.cert != null) "cert:${config.cert}"
|
||||
++ optional (config.key != null) "key:${config.key}"
|
||||
++ optional (config.cacert != null) "cacert:${config.cacert}";
|
||||
};
|
||||
script = concatStringsSep " " (
|
||||
[ "${mainCfg.package}/bin/ghostunnel" ]
|
||||
++ optional (config.keystore != null) "--keystore=$CREDENTIALS_DIRECTORY/keystore"
|
||||
++ optional (config.cert != null) "--cert=$CREDENTIALS_DIRECTORY/cert"
|
||||
++ optional (config.key != null) "--key=$CREDENTIALS_DIRECTORY/key"
|
||||
++ optional (config.cacert != null) "--cacert=$CREDENTIALS_DIRECTORY/cacert"
|
||||
++ [
|
||||
"server"
|
||||
"--listen ${config.listen}"
|
||||
"--target ${config.target}"
|
||||
] ++ optional config.allowAll "--allow-all"
|
||||
++ map (v: "--allow-cn=${escapeShellArg v}") config.allowCN
|
||||
++ map (v: "--allow-ou=${escapeShellArg v}") config.allowOU
|
||||
++ map (v: "--allow-dns=${escapeShellArg v}") config.allowDNS
|
||||
++ map (v: "--allow-uri=${escapeShellArg v}") config.allowURI
|
||||
++ optional config.disableAuthentication "--disable-authentication"
|
||||
++ optional config.unsafeTarget "--unsafe-target"
|
||||
++ [ config.extraArguments ]
|
||||
);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
options = {
|
||||
services.ghostunnel.enable = mkEnableOption "ghostunnel";
|
||||
|
||||
services.ghostunnel.package = mkOption {
|
||||
description = "The ghostunnel package to use.";
|
||||
type = types.package;
|
||||
default = pkgs.ghostunnel;
|
||||
defaultText = literalExpression "pkgs.ghostunnel";
|
||||
};
|
||||
|
||||
services.ghostunnel.servers = mkOption {
|
||||
description = ''
|
||||
Server mode ghostunnels (TLS listener -> plain TCP/UNIX target)
|
||||
'';
|
||||
type = types.attrsOf (types.submodule module);
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf mainCfg.enable {
|
||||
assertions = lib.mkMerge (map (v: v.atRoot.assertions) (attrValues mainCfg.servers));
|
||||
systemd = lib.mkMerge (map (v: v.atRoot.systemd) (attrValues mainCfg.servers));
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [
|
||||
roberth
|
||||
];
|
||||
}
|
||||
131
nixos/modules/services/networking/git-daemon.nix
Normal file
131
nixos/modules/services/networking/git-daemon.nix
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
with lib;
|
||||
let
|
||||
|
||||
cfg = config.services.gitDaemon;
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
services.gitDaemon = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enable Git daemon, which allows public hosting of git repositories
|
||||
without any access controls. This is mostly intended for read-only access.
|
||||
|
||||
You can allow write access by setting daemon.receivepack configuration
|
||||
item of the repository to true. This is solely meant for a closed LAN setting
|
||||
where everybody is friendly.
|
||||
|
||||
If you need any access controls, use something else.
|
||||
'';
|
||||
};
|
||||
|
||||
basePath = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "/srv/git/";
|
||||
description = ''
|
||||
Remap all the path requests as relative to the given path. For example,
|
||||
if you set base-path to /srv/git, then if you later try to pull
|
||||
git://example.com/hello.git, Git daemon will interpret the path as /srv/git/hello.git.
|
||||
'';
|
||||
};
|
||||
|
||||
exportAll = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Publish all directories that look like Git repositories (have the objects
|
||||
and refs subdirectories), even if they do not have the git-daemon-export-ok file.
|
||||
|
||||
If disabled, you need to touch .git/git-daemon-export-ok in each repository
|
||||
you want the daemon to publish.
|
||||
|
||||
Warning: enabling this without a repository whitelist or basePath
|
||||
publishes every git repository you have.
|
||||
'';
|
||||
};
|
||||
|
||||
repositories = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "/srv/git" "/home/user/git/repo2" ];
|
||||
description = ''
|
||||
A whitelist of paths of git repositories, or directories containing repositories
|
||||
all of which would be published. Paths must not end in "/".
|
||||
|
||||
Warning: leaving this empty and enabling exportAll publishes all
|
||||
repositories in your filesystem or basePath if specified.
|
||||
'';
|
||||
};
|
||||
|
||||
listenAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "example.com";
|
||||
description = "Listen on a specific IP address or hostname.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 9418;
|
||||
description = "Port to listen on.";
|
||||
};
|
||||
|
||||
options = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Extra configuration options to be passed to Git daemon.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "git";
|
||||
description = "User under which Git daemon would be running.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "git";
|
||||
description = "Group under which Git daemon would be running.";
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "git") {
|
||||
git = {
|
||||
uid = config.ids.uids.git;
|
||||
group = "git";
|
||||
description = "Git daemon user";
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == "git") {
|
||||
git.gid = config.ids.gids.git;
|
||||
};
|
||||
|
||||
systemd.services.git-daemon = {
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
script = "${pkgs.git}/bin/git daemon --reuseaddr "
|
||||
+ (optionalString (cfg.basePath != "") "--base-path=${cfg.basePath} ")
|
||||
+ (optionalString (cfg.listenAddress != "") "--listen=${cfg.listenAddress} ")
|
||||
+ "--port=${toString cfg.port} --user=${cfg.user} --group=${cfg.group} ${cfg.options} "
|
||||
+ "--verbose " + (optionalString cfg.exportAll "--export-all ") + concatStringsSep " " cfg.repositories;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
43
nixos/modules/services/networking/globalprotect-vpn.nix
Normal file
43
nixos/modules/services/networking/globalprotect-vpn.nix
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.globalprotect;
|
||||
|
||||
execStart = if cfg.csdWrapper == null then
|
||||
"${pkgs.globalprotect-openconnect}/bin/gpservice"
|
||||
else
|
||||
"${pkgs.globalprotect-openconnect}/bin/gpservice --csd-wrapper=${cfg.csdWrapper}";
|
||||
in
|
||||
|
||||
{
|
||||
options.services.globalprotect = {
|
||||
enable = mkEnableOption "globalprotect";
|
||||
|
||||
csdWrapper = mkOption {
|
||||
description = ''
|
||||
A script that will produce a Host Integrity Protection (HIP) report,
|
||||
as described at <link xlink:href="https://www.infradead.org/openconnect/hip.html" />
|
||||
'';
|
||||
default = null;
|
||||
example = literalExpression ''"''${pkgs.openconnect}/libexec/openconnect/hipreport.sh"'';
|
||||
type = types.nullOr types.path;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.dbus.packages = [ pkgs.globalprotect-openconnect ];
|
||||
|
||||
systemd.services.gpservice = {
|
||||
description = "GlobalProtect openconnect DBus service";
|
||||
serviceConfig = {
|
||||
Type="dbus";
|
||||
BusName="com.yuezk.qt.GPService";
|
||||
ExecStart=execStart;
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
170
nixos/modules/services/networking/gnunet.nix
Normal file
170
nixos/modules/services/networking/gnunet.nix
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.gnunet;
|
||||
|
||||
stateDir = "/var/lib/gnunet";
|
||||
|
||||
configFile = with cfg;
|
||||
''
|
||||
[PATHS]
|
||||
GNUNET_HOME = ${stateDir}
|
||||
GNUNET_RUNTIME_DIR = /run/gnunet
|
||||
GNUNET_USER_RUNTIME_DIR = /run/gnunet
|
||||
GNUNET_DATA_HOME = ${stateDir}/data
|
||||
|
||||
[ats]
|
||||
WAN_QUOTA_IN = ${toString load.maxNetDownBandwidth} b
|
||||
WAN_QUOTA_OUT = ${toString load.maxNetUpBandwidth} b
|
||||
|
||||
[datastore]
|
||||
QUOTA = ${toString fileSharing.quota} MB
|
||||
|
||||
[transport-udp]
|
||||
PORT = ${toString udp.port}
|
||||
ADVERTISED_PORT = ${toString udp.port}
|
||||
|
||||
[transport-tcp]
|
||||
PORT = ${toString tcp.port}
|
||||
ADVERTISED_PORT = ${toString tcp.port}
|
||||
|
||||
${extraOptions}
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.gnunet = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to run the GNUnet daemon. GNUnet is GNU's anonymous
|
||||
peer-to-peer communication and file sharing framework.
|
||||
'';
|
||||
};
|
||||
|
||||
fileSharing = {
|
||||
quota = mkOption {
|
||||
type = types.int;
|
||||
default = 1024;
|
||||
description = ''
|
||||
Maximum file system usage (in MiB) for file sharing.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
udp = {
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 2086; # assigned by IANA
|
||||
description = ''
|
||||
The UDP port for use by GNUnet.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
tcp = {
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 2086; # assigned by IANA
|
||||
description = ''
|
||||
The TCP port for use by GNUnet.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
load = {
|
||||
maxNetDownBandwidth = mkOption {
|
||||
type = types.int;
|
||||
default = 50000;
|
||||
description = ''
|
||||
Maximum bandwidth usage (in bits per second) for GNUnet
|
||||
when downloading data.
|
||||
'';
|
||||
};
|
||||
|
||||
maxNetUpBandwidth = mkOption {
|
||||
type = types.int;
|
||||
default = 50000;
|
||||
description = ''
|
||||
Maximum bandwidth usage (in bits per second) for GNUnet
|
||||
when downloading data.
|
||||
'';
|
||||
};
|
||||
|
||||
hardNetUpBandwidth = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = ''
|
||||
Hard bandwidth limit (in bits per second) when uploading
|
||||
data.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.gnunet;
|
||||
defaultText = literalExpression "pkgs.gnunet";
|
||||
description = "Overridable attribute of the gnunet package to use.";
|
||||
example = literalExpression "pkgs.gnunet_git";
|
||||
};
|
||||
|
||||
extraOptions = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Additional options that will be copied verbatim in `gnunet.conf'.
|
||||
See `gnunet.conf(5)' for details.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf config.services.gnunet.enable {
|
||||
|
||||
users.users.gnunet = {
|
||||
group = "gnunet";
|
||||
description = "GNUnet User";
|
||||
uid = config.ids.uids.gnunet;
|
||||
};
|
||||
|
||||
users.groups.gnunet.gid = config.ids.gids.gnunet;
|
||||
|
||||
# The user tools that talk to `gnunetd' should come from the same source,
|
||||
# so install them globally.
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
environment.etc."gnunet.conf".text = configFile;
|
||||
|
||||
systemd.services.gnunet = {
|
||||
description = "GNUnet";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
restartTriggers = [ configFile ];
|
||||
path = [ cfg.package pkgs.miniupnpc ];
|
||||
serviceConfig.ExecStart = "${cfg.package}/lib/gnunet/libexec/gnunet-service-arm -c /etc/gnunet.conf";
|
||||
serviceConfig.User = "gnunet";
|
||||
serviceConfig.UMask = "0007";
|
||||
serviceConfig.WorkingDirectory = stateDir;
|
||||
serviceConfig.RuntimeDirectory = "gnunet";
|
||||
serviceConfig.StateDirectory = "gnunet";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
79
nixos/modules/services/networking/go-neb.nix
Normal file
79
nixos/modules/services/networking/go-neb.nix
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.go-neb;
|
||||
|
||||
settingsFormat = pkgs.formats.yaml {};
|
||||
configFile = settingsFormat.generate "config.yaml" cfg.config;
|
||||
in {
|
||||
options.services.go-neb = {
|
||||
enable = mkEnableOption "Extensible matrix bot written in Go";
|
||||
|
||||
bindAddress = mkOption {
|
||||
type = types.str;
|
||||
description = "Port (and optionally address) to listen on.";
|
||||
default = ":4050";
|
||||
};
|
||||
|
||||
secretFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/run/keys/go-neb.env";
|
||||
description = ''
|
||||
Environment variables from this file will be interpolated into the
|
||||
final config file using envsubst with this syntax: <literal>$ENVIRONMENT</literal>
|
||||
or <literal>''${VARIABLE}</literal>.
|
||||
The file should contain lines formatted as <literal>SECRET_VAR=SECRET_VALUE</literal>.
|
||||
This is useful to avoid putting secrets into the nix store.
|
||||
'';
|
||||
};
|
||||
|
||||
baseUrl = mkOption {
|
||||
type = types.str;
|
||||
description = "Public-facing endpoint that can receive webhooks.";
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
inherit (settingsFormat) type;
|
||||
description = ''
|
||||
Your <filename>config.yaml</filename> as a Nix attribute set.
|
||||
See <link xlink:href="https://github.com/matrix-org/go-neb/blob/master/config.sample.yaml">config.sample.yaml</link>
|
||||
for possible options.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.go-neb = let
|
||||
finalConfigFile = if cfg.secretFile == null then configFile else "/var/run/go-neb/config.yaml";
|
||||
in {
|
||||
description = "Extensible matrix bot written in Go";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = {
|
||||
BASE_URL = cfg.baseUrl;
|
||||
BIND_ADDRESS = cfg.bindAddress;
|
||||
CONFIG_FILE = finalConfigFile;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
ExecStartPre = lib.optional (cfg.secretFile != null)
|
||||
(pkgs.writeShellScript "pre-start" ''
|
||||
umask 077
|
||||
export $(xargs < ${cfg.secretFile})
|
||||
${pkgs.envsubst}/bin/envsubst -i "${configFile}" > ${finalConfigFile}
|
||||
chown go-neb ${finalConfigFile}
|
||||
'');
|
||||
PermissionsStartOnly = true;
|
||||
RuntimeDirectory = "go-neb";
|
||||
ExecStart = "${pkgs.go-neb}/bin/go-neb";
|
||||
User = "go-neb";
|
||||
DynamicUser = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ hexa maralorn ];
|
||||
}
|
||||
30
nixos/modules/services/networking/go-shadowsocks2.nix
Normal file
30
nixos/modules/services/networking/go-shadowsocks2.nix
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.go-shadowsocks2.server;
|
||||
in {
|
||||
options.services.go-shadowsocks2.server = {
|
||||
enable = mkEnableOption "go-shadowsocks2 server";
|
||||
|
||||
listenAddress = mkOption {
|
||||
type = types.str;
|
||||
description = "Server listen address or URL";
|
||||
example = "ss://AEAD_CHACHA20_POLY1305:your-password@:8488";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.go-shadowsocks2-server = {
|
||||
description = "go-shadowsocks2 server";
|
||||
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.go-shadowsocks2}/bin/go-shadowsocks2 -s '${cfg.listenAddress}'";
|
||||
DynamicUser = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
64
nixos/modules/services/networking/gobgpd.nix
Normal file
64
nixos/modules/services/networking/gobgpd.nix
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.gobgpd;
|
||||
format = pkgs.formats.toml { };
|
||||
confFile = format.generate "gobgpd.conf" cfg.settings;
|
||||
in {
|
||||
options.services.gobgpd = {
|
||||
enable = mkEnableOption "GoBGP Routing Daemon";
|
||||
|
||||
settings = mkOption {
|
||||
type = format.type;
|
||||
default = { };
|
||||
description = ''
|
||||
GoBGP configuration. Refer to
|
||||
<link xlink:href="https://github.com/osrg/gobgp#documentation"/>
|
||||
for details on supported values.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
global = {
|
||||
config = {
|
||||
as = 64512;
|
||||
router-id = "192.168.255.1";
|
||||
};
|
||||
};
|
||||
neighbors = [
|
||||
{
|
||||
config = {
|
||||
neighbor-address = "10.0.255.1";
|
||||
peer-as = 65001;
|
||||
};
|
||||
}
|
||||
{
|
||||
config = {
|
||||
neighbor-address = "10.0.255.2";
|
||||
peer-as = 65002;
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = [ pkgs.gobgpd ];
|
||||
systemd.services.gobgpd = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
description = "GoBGP Routing Daemon";
|
||||
serviceConfig = {
|
||||
Type = "notify";
|
||||
ExecStartPre = "${pkgs.gobgpd}/bin/gobgpd -f ${confFile} -d";
|
||||
ExecStart = "${pkgs.gobgpd}/bin/gobgpd -f ${confFile} --sdnotify";
|
||||
ExecReload = "${pkgs.gobgpd}/bin/gobgpd -r";
|
||||
DynamicUser = true;
|
||||
AmbientCapabilities = "cap_net_bind_service";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
130
nixos/modules/services/networking/gvpe.nix
Normal file
130
nixos/modules/services/networking/gvpe.nix
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# GNU Virtual Private Ethernet
|
||||
|
||||
{config, pkgs, lib, ...}:
|
||||
|
||||
let
|
||||
inherit (lib) mkOption mkIf types;
|
||||
|
||||
cfg = config.services.gvpe;
|
||||
|
||||
finalConfig = if cfg.configFile != null then
|
||||
cfg.configFile
|
||||
else if cfg.configText != null then
|
||||
pkgs.writeTextFile {
|
||||
name = "gvpe.conf";
|
||||
text = cfg.configText;
|
||||
}
|
||||
else
|
||||
throw "You must either specify contents of the config file or the config file itself for GVPE";
|
||||
|
||||
ifupScript = if cfg.ipAddress == null || cfg.subnet == null then
|
||||
throw "Specify IP address and subnet (with mask) for GVPE"
|
||||
else if cfg.nodename == null then
|
||||
throw "You must set node name for GVPE"
|
||||
else
|
||||
(pkgs.writeTextFile {
|
||||
name = "gvpe-if-up";
|
||||
text = ''
|
||||
#! /bin/sh
|
||||
|
||||
export PATH=$PATH:${pkgs.iproute2}/sbin
|
||||
|
||||
ip link set $IFNAME up
|
||||
ip address add ${cfg.ipAddress} dev $IFNAME
|
||||
ip route add ${cfg.subnet} dev $IFNAME
|
||||
|
||||
${cfg.customIFSetup}
|
||||
'';
|
||||
executable = true;
|
||||
});
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
services.gvpe = {
|
||||
enable = lib.mkEnableOption "gvpe";
|
||||
|
||||
nodename = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
description =''
|
||||
GVPE node name
|
||||
'';
|
||||
};
|
||||
configText = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.lines;
|
||||
example = ''
|
||||
tcp-port = 655
|
||||
udp-port = 655
|
||||
mtu = 1480
|
||||
ifname = vpn0
|
||||
|
||||
node = alpha
|
||||
hostname = alpha.example.org
|
||||
connect = always
|
||||
enable-udp = true
|
||||
enable-tcp = true
|
||||
on alpha if-up = if-up-0
|
||||
on alpha pid-file = /var/gvpe/gvpe.pid
|
||||
'';
|
||||
description = ''
|
||||
GVPE config contents
|
||||
'';
|
||||
};
|
||||
configFile = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
example = "/root/my-gvpe-conf";
|
||||
description = ''
|
||||
GVPE config file, if already present
|
||||
'';
|
||||
};
|
||||
ipAddress = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
IP address to assign to GVPE interface
|
||||
'';
|
||||
};
|
||||
subnet = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
example = "10.0.0.0/8";
|
||||
description = ''
|
||||
IP subnet assigned to GVPE network
|
||||
'';
|
||||
};
|
||||
customIFSetup = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Additional commands to apply in ifup script
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.gvpe = {
|
||||
description = "GNU Virtual Private Ethernet node";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
preStart = ''
|
||||
mkdir -p /var/gvpe
|
||||
mkdir -p /var/gvpe/pubkey
|
||||
chown root /var/gvpe
|
||||
chmod 700 /var/gvpe
|
||||
cp ${finalConfig} /var/gvpe/gvpe.conf
|
||||
cp ${ifupScript} /var/gvpe/if-up
|
||||
'';
|
||||
|
||||
script = "${pkgs.gvpe}/sbin/gvpe -c /var/gvpe -D ${cfg.nodename} "
|
||||
+ " ${cfg.nodename}.pid-file=/var/gvpe/gvpe.pid"
|
||||
+ " ${cfg.nodename}.if-up=if-up"
|
||||
+ " &> /var/log/gvpe";
|
||||
|
||||
serviceConfig.Restart = "always";
|
||||
};
|
||||
};
|
||||
}
|
||||
145
nixos/modules/services/networking/hans.nix
Normal file
145
nixos/modules/services/networking/hans.nix
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
# NixOS module for hans, ip over icmp daemon
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.hans;
|
||||
|
||||
hansUser = "hans";
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
### configuration
|
||||
|
||||
options = {
|
||||
|
||||
services.hans = {
|
||||
clients = mkOption {
|
||||
default = {};
|
||||
description = ''
|
||||
Each attribute of this option defines a systemd service that
|
||||
runs hans. Many or none may be defined.
|
||||
The name of each service is
|
||||
<literal>hans-<replaceable>name</replaceable></literal>
|
||||
where <replaceable>name</replaceable> is the name of the
|
||||
corresponding attribute name.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
foo = {
|
||||
server = "192.0.2.1";
|
||||
extraConfig = "-v";
|
||||
}
|
||||
}
|
||||
'';
|
||||
type = types.attrsOf (types.submodule (
|
||||
{
|
||||
options = {
|
||||
server = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "IP address of server running hans";
|
||||
example = "192.0.2.1";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Additional command line parameters";
|
||||
example = "-v";
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "File that containts password";
|
||||
};
|
||||
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
server = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "enable hans server";
|
||||
};
|
||||
|
||||
ip = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "The assigned ip range";
|
||||
example = "198.51.100.0";
|
||||
};
|
||||
|
||||
respondToSystemPings = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Force hans respond to ordinary pings";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Additional command line parameters";
|
||||
example = "-v";
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "File that containts password";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
### implementation
|
||||
|
||||
config = mkIf (cfg.server.enable || cfg.clients != {}) {
|
||||
boot.kernel.sysctl = optionalAttrs cfg.server.respondToSystemPings {
|
||||
"net.ipv4.icmp_echo_ignore_all" = 1;
|
||||
};
|
||||
|
||||
boot.kernelModules = [ "tun" ];
|
||||
|
||||
systemd.services =
|
||||
let
|
||||
createHansClientService = name: cfg:
|
||||
{
|
||||
description = "hans client - ${name}";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
script = "${pkgs.hans}/bin/hans -f -u ${hansUser} ${cfg.extraConfig} -c ${cfg.server} ${optionalString (cfg.passwordFile != "") "-p $(cat \"${cfg.passwordFile}\")"}";
|
||||
serviceConfig = {
|
||||
RestartSec = "30s";
|
||||
Restart = "always";
|
||||
};
|
||||
};
|
||||
in
|
||||
listToAttrs (
|
||||
mapAttrsToList
|
||||
(name: value: nameValuePair "hans-${name}" (createHansClientService name value))
|
||||
cfg.clients
|
||||
) // {
|
||||
hans = mkIf (cfg.server.enable) {
|
||||
description = "hans, ip over icmp server daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
script = "${pkgs.hans}/bin/hans -f -u ${hansUser} ${cfg.server.extraConfig} -s ${cfg.server.ip} ${optionalString cfg.server.respondToSystemPings "-r"} ${optionalString (cfg.server.passwordFile != "") "-p $(cat \"${cfg.server.passwordFile}\")"}";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.${hansUser} = {
|
||||
description = "Hans daemon user";
|
||||
isSystemUser = true;
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ ];
|
||||
}
|
||||
112
nixos/modules/services/networking/haproxy.nix
Normal file
112
nixos/modules/services/networking/haproxy.nix
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.haproxy;
|
||||
|
||||
haproxyCfg = pkgs.writeText "haproxy.conf" ''
|
||||
global
|
||||
# needed for hot-reload to work without dropping packets in multi-worker mode
|
||||
stats socket /run/haproxy/haproxy.sock mode 600 expose-fd listeners level user
|
||||
|
||||
${cfg.config}
|
||||
'';
|
||||
|
||||
in
|
||||
with lib;
|
||||
{
|
||||
options = {
|
||||
services.haproxy = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable HAProxy, the reliable, high performance TCP/HTTP
|
||||
load balancer.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "haproxy";
|
||||
description = "User account under which haproxy runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "haproxy";
|
||||
description = "Group account under which haproxy runs.";
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
type = types.nullOr types.lines;
|
||||
default = null;
|
||||
description = ''
|
||||
Contents of the HAProxy configuration file,
|
||||
<filename>haproxy.conf</filename>.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
assertions = [{
|
||||
assertion = cfg.config != null;
|
||||
message = "You must provide services.haproxy.config.";
|
||||
}];
|
||||
|
||||
# configuration file indirection is needed to support reloading
|
||||
environment.etc."haproxy.cfg".source = haproxyCfg;
|
||||
|
||||
systemd.services.haproxy = {
|
||||
description = "HAProxy";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
Type = "notify";
|
||||
ExecStartPre = [
|
||||
# when the master process receives USR2, it reloads itself using exec(argv[0]),
|
||||
# so we create a symlink there and update it before reloading
|
||||
"${pkgs.coreutils}/bin/ln -sf ${pkgs.haproxy}/sbin/haproxy /run/haproxy/haproxy"
|
||||
# when running the config test, don't be quiet so we can see what goes wrong
|
||||
"/run/haproxy/haproxy -c -f ${haproxyCfg}"
|
||||
];
|
||||
ExecStart = "/run/haproxy/haproxy -Ws -f /etc/haproxy.cfg -p /run/haproxy/haproxy.pid";
|
||||
# support reloading
|
||||
ExecReload = [
|
||||
"${pkgs.haproxy}/sbin/haproxy -c -f ${haproxyCfg}"
|
||||
"${pkgs.coreutils}/bin/ln -sf ${pkgs.haproxy}/sbin/haproxy /run/haproxy/haproxy"
|
||||
"${pkgs.coreutils}/bin/kill -USR2 $MAINPID"
|
||||
];
|
||||
KillMode = "mixed";
|
||||
SuccessExitStatus = "143";
|
||||
Restart = "always";
|
||||
RuntimeDirectory = "haproxy";
|
||||
# upstream hardening options
|
||||
NoNewPrivileges = true;
|
||||
ProtectHome = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
SystemCallFilter= "~@cpu-emulation @keyring @module @obsolete @raw-io @reboot @swap @sync";
|
||||
# needed in case we bind to port < 1024
|
||||
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
|
||||
};
|
||||
};
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "haproxy") {
|
||||
haproxy = {
|
||||
group = cfg.group;
|
||||
isSystemUser = true;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == "haproxy") {
|
||||
haproxy = {};
|
||||
};
|
||||
};
|
||||
}
|
||||
490
nixos/modules/services/networking/headscale.nix
Normal file
490
nixos/modules/services/networking/headscale.nix
Normal file
|
|
@ -0,0 +1,490 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.headscale;
|
||||
|
||||
dataDir = "/var/lib/headscale";
|
||||
runDir = "/run/headscale";
|
||||
|
||||
settingsFormat = pkgs.formats.yaml { };
|
||||
configFile = settingsFormat.generate "headscale.yaml" cfg.settings;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.headscale = {
|
||||
enable = mkEnableOption "headscale, Open Source coordination server for Tailscale";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.headscale;
|
||||
defaultText = literalExpression "pkgs.headscale";
|
||||
description = ''
|
||||
Which headscale package to use for the running server.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
default = "headscale";
|
||||
type = types.str;
|
||||
description = ''
|
||||
User account under which headscale runs.
|
||||
<note><para>
|
||||
If left as the default value this user will automatically be created
|
||||
on system activation, otherwise you are responsible for
|
||||
ensuring the user exists before the headscale service starts.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
default = "headscale";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Group under which headscale runs.
|
||||
<note><para>
|
||||
If left as the default value this group will automatically be created
|
||||
on system activation, otherwise you are responsible for
|
||||
ensuring the user exists before the headscale service starts.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
|
||||
serverUrl = mkOption {
|
||||
type = types.str;
|
||||
default = "http://127.0.0.1:8080";
|
||||
description = ''
|
||||
The url clients will connect to.
|
||||
'';
|
||||
example = "https://myheadscale.example.com:443";
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
description = ''
|
||||
Listening address of headscale.
|
||||
'';
|
||||
example = "0.0.0.0";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8080;
|
||||
description = ''
|
||||
Listening port of headscale.
|
||||
'';
|
||||
example = 443;
|
||||
};
|
||||
|
||||
privateKeyFile = mkOption {
|
||||
type = types.path;
|
||||
default = "${dataDir}/private.key";
|
||||
description = ''
|
||||
Path to private key file, generated automatically if it does not exist.
|
||||
'';
|
||||
};
|
||||
|
||||
derp = {
|
||||
urls = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "https://controlplane.tailscale.com/derpmap/default" ];
|
||||
description = ''
|
||||
List of urls containing DERP maps.
|
||||
See <link xlink:href="https://tailscale.com/blog/how-tailscale-works/">How Tailscale works</link> for more information on DERP maps.
|
||||
'';
|
||||
};
|
||||
|
||||
paths = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = [ ];
|
||||
description = ''
|
||||
List of file paths containing DERP maps.
|
||||
See <link xlink:href="https://tailscale.com/blog/how-tailscale-works/">How Tailscale works</link> for more information on DERP maps.
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
autoUpdate = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to automatically update DERP maps on a set frequency.
|
||||
'';
|
||||
example = false;
|
||||
};
|
||||
|
||||
updateFrequency = mkOption {
|
||||
type = types.str;
|
||||
default = "24h";
|
||||
description = ''
|
||||
Frequency to update DERP maps.
|
||||
'';
|
||||
example = "5m";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
ephemeralNodeInactivityTimeout = mkOption {
|
||||
type = types.str;
|
||||
default = "30m";
|
||||
description = ''
|
||||
Time before an inactive ephemeral node is deleted.
|
||||
'';
|
||||
example = "5m";
|
||||
};
|
||||
|
||||
database = {
|
||||
type = mkOption {
|
||||
type = types.enum [ "sqlite3" "postgres" ];
|
||||
example = "postgres";
|
||||
default = "sqlite3";
|
||||
description = "Database engine to use.";
|
||||
};
|
||||
|
||||
host = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "127.0.0.1";
|
||||
description = "Database host address.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.nullOr types.port;
|
||||
default = null;
|
||||
example = 3306;
|
||||
description = "Database host port.";
|
||||
};
|
||||
|
||||
name = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "headscale";
|
||||
description = "Database name.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "headscale";
|
||||
description = "Database user.";
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/run/keys/headscale-dbpassword";
|
||||
description = ''
|
||||
A file containing the password corresponding to
|
||||
<option>database.user</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
path = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = "${dataDir}/db.sqlite";
|
||||
description = "Path to the sqlite3 database file.";
|
||||
};
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
type = types.str;
|
||||
default = "info";
|
||||
description = ''
|
||||
headscale log level.
|
||||
'';
|
||||
example = "debug";
|
||||
};
|
||||
|
||||
dns = {
|
||||
nameservers = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "1.1.1.1" ];
|
||||
description = ''
|
||||
List of nameservers to pass to Tailscale clients.
|
||||
'';
|
||||
};
|
||||
|
||||
domains = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = ''
|
||||
Search domains to inject to Tailscale clients.
|
||||
'';
|
||||
example = [ "mydomain.internal" ];
|
||||
};
|
||||
|
||||
magicDns = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/).
|
||||
Only works if there is at least a nameserver defined.
|
||||
'';
|
||||
example = false;
|
||||
};
|
||||
|
||||
baseDomain = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Defines the base domain to create the hostnames for MagicDNS.
|
||||
<option>baseDomain</option> must be a FQDNs, without the trailing dot.
|
||||
The FQDN of the hosts will be
|
||||
<literal>hostname.namespace.base_domain</literal> (e.g.
|
||||
<literal>myhost.mynamespace.example.com</literal>).
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
openIdConnect = {
|
||||
issuer = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
URL to OpenID issuer.
|
||||
'';
|
||||
example = "https://openid.example.com";
|
||||
};
|
||||
|
||||
clientId = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
OpenID Connect client ID.
|
||||
'';
|
||||
};
|
||||
|
||||
clientSecretFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to OpenID Connect client secret file.
|
||||
'';
|
||||
};
|
||||
|
||||
domainMap = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = { };
|
||||
description = ''
|
||||
Domain map is used to map incomming users (by their email) to
|
||||
a namespace. The key can be a string, or regex.
|
||||
'';
|
||||
example = {
|
||||
".*" = "default-namespace";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
tls = {
|
||||
letsencrypt = {
|
||||
hostname = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Domain name to request a TLS certificate for.
|
||||
'';
|
||||
};
|
||||
challengeType = mkOption {
|
||||
type = types.enum [ "TLS_ALPN-01" "HTTP-01" ];
|
||||
default = "HTTP-01";
|
||||
description = ''
|
||||
Type of ACME challenge to use, currently supported types:
|
||||
<literal>HTTP-01</literal> or <literal>TLS_ALPN-01</literal>.
|
||||
'';
|
||||
};
|
||||
httpListen = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = ":http";
|
||||
description = ''
|
||||
When HTTP-01 challenge is chosen, letsencrypt must set up a
|
||||
verification endpoint, and it will be listening on:
|
||||
<literal>:http = port 80</literal>.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
certFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to already created certificate.
|
||||
'';
|
||||
};
|
||||
keyFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to key for already created certificate.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
aclPolicyFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to a file containg ACL policies.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = settingsFormat.type;
|
||||
default = { };
|
||||
description = ''
|
||||
Overrides to <filename>config.yaml</filename> as a Nix attribute set.
|
||||
This option is ideal for overriding settings not exposed as Nix options.
|
||||
Check the <link xlink:href="https://github.com/juanfont/headscale/blob/main/config-example.yaml">example config</link>
|
||||
for possible options.
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
services.headscale.settings = {
|
||||
server_url = mkDefault cfg.serverUrl;
|
||||
listen_addr = mkDefault "${cfg.address}:${toString cfg.port}";
|
||||
|
||||
private_key_path = mkDefault cfg.privateKeyFile;
|
||||
|
||||
derp = {
|
||||
urls = mkDefault cfg.derp.urls;
|
||||
paths = mkDefault cfg.derp.paths;
|
||||
auto_update_enable = mkDefault cfg.derp.autoUpdate;
|
||||
update_frequency = mkDefault cfg.derp.updateFrequency;
|
||||
};
|
||||
|
||||
# Turn off update checks since the origin of our package
|
||||
# is nixpkgs and not Github.
|
||||
disable_check_updates = true;
|
||||
|
||||
ephemeral_node_inactivity_timeout = mkDefault cfg.ephemeralNodeInactivityTimeout;
|
||||
|
||||
db_type = mkDefault cfg.database.type;
|
||||
db_path = mkDefault cfg.database.path;
|
||||
|
||||
log_level = mkDefault cfg.logLevel;
|
||||
|
||||
dns_config = {
|
||||
nameservers = mkDefault cfg.dns.nameservers;
|
||||
domains = mkDefault cfg.dns.domains;
|
||||
magic_dns = mkDefault cfg.dns.magicDns;
|
||||
base_domain = mkDefault cfg.dns.baseDomain;
|
||||
};
|
||||
|
||||
unix_socket = "${runDir}/headscale.sock";
|
||||
|
||||
# OpenID Connect
|
||||
oidc = {
|
||||
issuer = mkDefault cfg.openIdConnect.issuer;
|
||||
client_id = mkDefault cfg.openIdConnect.clientId;
|
||||
domain_map = mkDefault cfg.openIdConnect.domainMap;
|
||||
};
|
||||
|
||||
tls_letsencrypt_cache_dir = "${dataDir}/.cache";
|
||||
|
||||
} // optionalAttrs (cfg.database.host != null) {
|
||||
db_host = mkDefault cfg.database.host;
|
||||
} // optionalAttrs (cfg.database.port != null) {
|
||||
db_port = mkDefault cfg.database.port;
|
||||
} // optionalAttrs (cfg.database.name != null) {
|
||||
db_name = mkDefault cfg.database.name;
|
||||
} // optionalAttrs (cfg.database.user != null) {
|
||||
db_user = mkDefault cfg.database.user;
|
||||
} // optionalAttrs (cfg.tls.letsencrypt.hostname != null) {
|
||||
tls_letsencrypt_hostname = mkDefault cfg.tls.letsencrypt.hostname;
|
||||
} // optionalAttrs (cfg.tls.letsencrypt.challengeType != null) {
|
||||
tls_letsencrypt_challenge_type = mkDefault cfg.tls.letsencrypt.challengeType;
|
||||
} // optionalAttrs (cfg.tls.letsencrypt.httpListen != null) {
|
||||
tls_letsencrypt_listen = mkDefault cfg.tls.letsencrypt.httpListen;
|
||||
} // optionalAttrs (cfg.tls.certFile != null) {
|
||||
tls_cert_path = mkDefault cfg.tls.certFile;
|
||||
} // optionalAttrs (cfg.tls.keyFile != null) {
|
||||
tls_key_path = mkDefault cfg.tls.keyFile;
|
||||
} // optionalAttrs (cfg.aclPolicyFile != null) {
|
||||
acl_policy_path = mkDefault cfg.aclPolicyFile;
|
||||
};
|
||||
|
||||
# Setup the headscale configuration in a known path in /etc to
|
||||
# allow both the Server and the Client use it to find the socket
|
||||
# for communication.
|
||||
environment.etc."headscale/config.yaml".source = configFile;
|
||||
|
||||
users.groups.headscale = mkIf (cfg.group == "headscale") { };
|
||||
|
||||
users.users.headscale = mkIf (cfg.user == "headscale") {
|
||||
description = "headscale user";
|
||||
home = dataDir;
|
||||
group = cfg.group;
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
systemd.services.headscale = {
|
||||
description = "headscale coordination server for Tailscale";
|
||||
after = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
restartTriggers = [ configFile ];
|
||||
|
||||
script = ''
|
||||
${optionalString (cfg.database.passwordFile != null) ''
|
||||
export HEADSCALE_DB_PASS="$(head -n1 ${escapeShellArg cfg.database.passwordFile})"
|
||||
''}
|
||||
|
||||
export HEADSCALE_OIDC_CLIENT_SECRET="$(head -n1 ${escapeShellArg cfg.openIdConnect.clientSecretFile})"
|
||||
exec ${cfg.package}/bin/headscale serve
|
||||
'';
|
||||
|
||||
serviceConfig =
|
||||
let
|
||||
capabilityBoundingSet = [ "CAP_CHOWN" ] ++ optional (cfg.port < 1024) "CAP_NET_BIND_SERVICE";
|
||||
in
|
||||
{
|
||||
Restart = "always";
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
|
||||
# Hardening options
|
||||
RuntimeDirectory = "headscale";
|
||||
# Allow headscale group access so users can be added and use the CLI.
|
||||
RuntimeDirectoryMode = "0750";
|
||||
|
||||
StateDirectory = "headscale";
|
||||
StateDirectoryMode = "0750";
|
||||
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictSUIDSGID = true;
|
||||
PrivateMounts = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectHostname = true;
|
||||
ProtectClock = true;
|
||||
ProtectProc = "invisible";
|
||||
ProcSubset = "pid";
|
||||
RestrictNamespaces = true;
|
||||
RemoveIPC = true;
|
||||
UMask = "0077";
|
||||
|
||||
CapabilityBoundingSet = capabilityBoundingSet;
|
||||
AmbientCapabilities = capabilityBoundingSet;
|
||||
NoNewPrivileges = true;
|
||||
LockPersonality = true;
|
||||
RestrictRealtime = true;
|
||||
SystemCallFilter = [ "@system-service" "~@privileged" "@chown" ];
|
||||
SystemCallArchitectures = "native";
|
||||
RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ kradalby ];
|
||||
}
|
||||
11
nixos/modules/services/networking/helpers.nix
Normal file
11
nixos/modules/services/networking/helpers.nix
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{ config, lib, ... }: ''
|
||||
# Helper command to manipulate both the IPv4 and IPv6 tables.
|
||||
ip46tables() {
|
||||
iptables -w "$@"
|
||||
${
|
||||
lib.optionalString config.networking.enableIPv6 ''
|
||||
ip6tables -w "$@"
|
||||
''
|
||||
}
|
||||
}
|
||||
''
|
||||
219
nixos/modules/services/networking/hostapd.nix
Normal file
219
nixos/modules/services/networking/hostapd.nix
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
{ config, lib, pkgs, utils, ... }:
|
||||
|
||||
# TODO:
|
||||
#
|
||||
# asserts
|
||||
# ensure that the nl80211 module is loaded/compiled in the kernel
|
||||
# wpa_supplicant and hostapd on the same wireless interface doesn't make any sense
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.hostapd;
|
||||
|
||||
escapedInterface = utils.escapeSystemdPath cfg.interface;
|
||||
|
||||
configFile = pkgs.writeText "hostapd.conf" ''
|
||||
interface=${cfg.interface}
|
||||
driver=${cfg.driver}
|
||||
ssid=${cfg.ssid}
|
||||
hw_mode=${cfg.hwMode}
|
||||
channel=${toString cfg.channel}
|
||||
${optionalString (cfg.countryCode != null) "country_code=${cfg.countryCode}"}
|
||||
${optionalString (cfg.countryCode != null) "ieee80211d=1"}
|
||||
|
||||
# logging (debug level)
|
||||
logger_syslog=-1
|
||||
logger_syslog_level=${toString cfg.logLevel}
|
||||
logger_stdout=-1
|
||||
logger_stdout_level=${toString cfg.logLevel}
|
||||
|
||||
ctrl_interface=/run/hostapd
|
||||
ctrl_interface_group=${cfg.group}
|
||||
|
||||
${optionalString cfg.wpa ''
|
||||
wpa=2
|
||||
wpa_passphrase=${cfg.wpaPassphrase}
|
||||
''}
|
||||
${optionalString cfg.noScan "noscan=1"}
|
||||
|
||||
${cfg.extraConfig}
|
||||
'' ;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.hostapd = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enable putting a wireless interface into infrastructure mode,
|
||||
allowing other wireless devices to associate with the wireless
|
||||
interface and do wireless networking. A simple access point will
|
||||
<option>enable hostapd.wpa</option>,
|
||||
<option>hostapd.wpaPassphrase</option>, and
|
||||
<option>hostapd.ssid</option>, as well as DHCP on the wireless
|
||||
interface to provide IP addresses to the associated stations, and
|
||||
NAT (from the wireless interface to an upstream interface).
|
||||
'';
|
||||
};
|
||||
|
||||
interface = mkOption {
|
||||
default = "";
|
||||
example = "wlp2s0";
|
||||
type = types.str;
|
||||
description = ''
|
||||
The interfaces <command>hostapd</command> will use.
|
||||
'';
|
||||
};
|
||||
|
||||
noScan = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Do not scan for overlapping BSSs in HT40+/- mode.
|
||||
Caution: turning this on will violate regulatory requirements!
|
||||
'';
|
||||
};
|
||||
|
||||
driver = mkOption {
|
||||
default = "nl80211";
|
||||
example = "hostapd";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Which driver <command>hostapd</command> will use.
|
||||
Most applications will probably use the default.
|
||||
'';
|
||||
};
|
||||
|
||||
ssid = mkOption {
|
||||
default = "nixos";
|
||||
example = "mySpecialSSID";
|
||||
type = types.str;
|
||||
description = "SSID to be used in IEEE 802.11 management frames.";
|
||||
};
|
||||
|
||||
hwMode = mkOption {
|
||||
default = "g";
|
||||
type = types.enum [ "a" "b" "g" ];
|
||||
description = ''
|
||||
Operation mode.
|
||||
(a = IEEE 802.11a, b = IEEE 802.11b, g = IEEE 802.11g).
|
||||
'';
|
||||
};
|
||||
|
||||
channel = mkOption {
|
||||
default = 7;
|
||||
example = 11;
|
||||
type = types.int;
|
||||
description = ''
|
||||
Channel number (IEEE 802.11)
|
||||
Please note that some drivers do not use this value from
|
||||
<command>hostapd</command> and the channel will need to be configured
|
||||
separately with <command>iwconfig</command>.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
default = "wheel";
|
||||
example = "network";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Members of this group can control <command>hostapd</command>.
|
||||
'';
|
||||
};
|
||||
|
||||
wpa = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Enable WPA (IEEE 802.11i/D3.0) to authenticate with the access point.
|
||||
'';
|
||||
};
|
||||
|
||||
wpaPassphrase = mkOption {
|
||||
default = "my_sekret";
|
||||
example = "any_64_char_string";
|
||||
type = types.str;
|
||||
description = ''
|
||||
WPA-PSK (pre-shared-key) passphrase. Clients will need this
|
||||
passphrase to associate with this access point.
|
||||
Warning: This passphrase will get put into a world-readable file in
|
||||
the Nix store!
|
||||
'';
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
default = 2;
|
||||
type = types.int;
|
||||
description = ''
|
||||
Levels (minimum value for logged events):
|
||||
0 = verbose debugging
|
||||
1 = debugging
|
||||
2 = informational messages
|
||||
3 = notification
|
||||
4 = warning
|
||||
'';
|
||||
};
|
||||
|
||||
countryCode = mkOption {
|
||||
default = null;
|
||||
example = "US";
|
||||
type = with types; nullOr str;
|
||||
description = ''
|
||||
Country code (ISO/IEC 3166-1). Used to set regulatory domain.
|
||||
Set as needed to indicate country in which device is operating.
|
||||
This can limit available channels and transmit power.
|
||||
These two octets are used as the first two octets of the Country String
|
||||
(dot11CountryString).
|
||||
If set this enables IEEE 802.11d. This advertises the countryCode and
|
||||
the set of allowed channels and transmit power levels based on the
|
||||
regulatory limits.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
default = "";
|
||||
example = ''
|
||||
auth_algo=0
|
||||
ieee80211n=1
|
||||
ht_capab=[HT40-][SHORT-GI-40][DSSS_CCK-40]
|
||||
'';
|
||||
type = types.lines;
|
||||
description = "Extra configuration options to put in hostapd.conf.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
environment.systemPackages = [ pkgs.hostapd ];
|
||||
|
||||
services.udev.packages = optional (cfg.countryCode != null) [ pkgs.crda ];
|
||||
|
||||
systemd.services.hostapd =
|
||||
{ description = "hostapd wireless AP";
|
||||
|
||||
path = [ pkgs.hostapd ];
|
||||
after = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
|
||||
bindsTo = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
|
||||
requiredBy = [ "network-link-${cfg.interface}.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig =
|
||||
{ ExecStart = "${pkgs.hostapd}/bin/hostapd ${configFile}";
|
||||
Restart = "always";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
80
nixos/modules/services/networking/htpdate.nix
Normal file
80
nixos/modules/services/networking/htpdate.nix
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
inherit (pkgs) htpdate;
|
||||
|
||||
cfg = config.services.htpdate;
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.htpdate = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enable htpdate daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
extraOptions = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Additional command line arguments to pass to htpdate.
|
||||
'';
|
||||
};
|
||||
|
||||
servers = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "www.google.com" ];
|
||||
description = ''
|
||||
HTTP servers to use for time synchronization.
|
||||
'';
|
||||
};
|
||||
|
||||
proxy = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "127.0.0.1:8118";
|
||||
description = ''
|
||||
HTTP proxy used for requests.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.services.htpdate = {
|
||||
description = "htpdate daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
PIDFile = "/run/htpdate.pid";
|
||||
ExecStart = concatStringsSep " " [
|
||||
"${htpdate}/bin/htpdate"
|
||||
"-D -u nobody"
|
||||
"-a -s"
|
||||
"-l"
|
||||
"${optionalString (cfg.proxy != "") "-P ${cfg.proxy}"}"
|
||||
"${cfg.extraOptions}"
|
||||
"${concatStringsSep " " cfg.servers}"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
128
nixos/modules/services/networking/https-dns-proxy.nix
Normal file
128
nixos/modules/services/networking/https-dns-proxy.nix
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (lib)
|
||||
concatStringsSep
|
||||
mkEnableOption mkIf mkOption types;
|
||||
|
||||
cfg = config.services.https-dns-proxy;
|
||||
|
||||
providers = {
|
||||
cloudflare = {
|
||||
ips = [ "1.1.1.1" "1.0.0.1" ];
|
||||
url = "https://cloudflare-dns.com/dns-query";
|
||||
};
|
||||
google = {
|
||||
ips = [ "8.8.8.8" "8.8.4.4" ];
|
||||
url = "https://dns.google/dns-query";
|
||||
};
|
||||
quad9 = {
|
||||
ips = [ "9.9.9.9" "149.112.112.112" ];
|
||||
url = "https://dns.quad9.net/dns-query";
|
||||
};
|
||||
};
|
||||
|
||||
defaultProvider = "quad9";
|
||||
|
||||
providerCfg =
|
||||
let
|
||||
isCustom = cfg.provider.kind == "custom";
|
||||
in
|
||||
lib.concatStringsSep " " [
|
||||
"-b"
|
||||
(concatStringsSep "," (if isCustom then cfg.provider.ips else providers."${cfg.provider.kind}".ips))
|
||||
"-r"
|
||||
(if isCustom then cfg.provider.url else providers."${cfg.provider.kind}".url)
|
||||
];
|
||||
|
||||
in
|
||||
{
|
||||
meta.maintainers = with lib.maintainers; [ peterhoeg ];
|
||||
|
||||
###### interface
|
||||
|
||||
options.services.https-dns-proxy = {
|
||||
enable = mkEnableOption "https-dns-proxy daemon";
|
||||
|
||||
address = mkOption {
|
||||
description = "The address on which to listen";
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
description = "The port on which to listen";
|
||||
type = types.port;
|
||||
default = 5053;
|
||||
};
|
||||
|
||||
provider = {
|
||||
kind = mkOption {
|
||||
description = ''
|
||||
The upstream provider to use or custom in case you do not trust any of
|
||||
the predefined providers or just want to use your own.
|
||||
|
||||
The default is ${defaultProvider} and there are privacy and security trade-offs
|
||||
when using any upstream provider. Please consider that before using any
|
||||
of them.
|
||||
|
||||
If you pick a custom provider, you will need to provide the bootstrap
|
||||
IP addresses as well as the resolver https URL.
|
||||
'';
|
||||
type = types.enum ((builtins.attrNames providers) ++ [ "custom" ]);
|
||||
default = defaultProvider;
|
||||
};
|
||||
|
||||
ips = mkOption {
|
||||
description = "The custom provider IPs";
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
|
||||
url = mkOption {
|
||||
description = "The custom provider URL";
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
preferIPv4 = mkOption {
|
||||
description = ''
|
||||
https_dns_proxy will by default use IPv6 and fail if it is not available.
|
||||
To play it safe, we choose IPv4.
|
||||
'';
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
extraArgs = mkOption {
|
||||
description = "Additional arguments to pass to the process.";
|
||||
type = types.listOf types.str;
|
||||
default = [ "-v" ];
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.services.https-dns-proxy = {
|
||||
description = "DNS to DNS over HTTPS (DoH) proxy";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = rec {
|
||||
Type = "exec";
|
||||
DynamicUser = true;
|
||||
ExecStart = lib.concatStringsSep " " (
|
||||
[
|
||||
"${pkgs.https-dns-proxy}/bin/https_dns_proxy"
|
||||
"-a ${toString cfg.address}"
|
||||
"-p ${toString cfg.port}"
|
||||
"-l -"
|
||||
providerCfg
|
||||
]
|
||||
++ lib.optional cfg.preferIPv4 "-4"
|
||||
++ cfg.extraArgs
|
||||
);
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
31
nixos/modules/services/networking/hylafax/default.nix
Normal file
31
nixos/modules/services/networking/hylafax/default.nix
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
|
||||
imports = [
|
||||
./options.nix
|
||||
./systemd.nix
|
||||
];
|
||||
|
||||
config = lib.modules.mkIf config.services.hylafax.enable {
|
||||
environment.systemPackages = [ pkgs.hylafaxplus ];
|
||||
users.users.uucp = {
|
||||
uid = config.ids.uids.uucp;
|
||||
group = "uucp";
|
||||
description = "Unix-to-Unix CoPy system";
|
||||
isSystemUser = true;
|
||||
inherit (config.users.users.nobody) home;
|
||||
};
|
||||
assertions = [{
|
||||
assertion = config.services.hylafax.modems != {};
|
||||
message = ''
|
||||
HylaFAX cannot be used without modems.
|
||||
Please define at least one modem with
|
||||
<option>config.services.hylafax.modems</option>.
|
||||
'';
|
||||
}];
|
||||
};
|
||||
|
||||
meta.maintainers = [ lib.maintainers.yarny ];
|
||||
|
||||
}
|
||||
12
nixos/modules/services/networking/hylafax/faxq-default.nix
Normal file
12
nixos/modules/services/networking/hylafax/faxq-default.nix
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{ ... }:
|
||||
|
||||
# see man:hylafax-config(5)
|
||||
|
||||
{
|
||||
|
||||
ModemGroup = [ ''"any:0:.*"'' ];
|
||||
ServerTracing = "0x78701";
|
||||
SessionTracing = "0x78701";
|
||||
UUCPLockDir = "/var/lock";
|
||||
|
||||
}
|
||||
29
nixos/modules/services/networking/hylafax/faxq-wait.sh
Executable file
29
nixos/modules/services/networking/hylafax/faxq-wait.sh
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
#! @runtimeShell@ -e
|
||||
|
||||
# skip this if there are no modems at all
|
||||
if ! stat -t "@spoolAreaPath@"/etc/config.* >/dev/null 2>&1
|
||||
then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "faxq started, waiting for modem(s) to initialize..."
|
||||
|
||||
for i in `seq @timeoutSec@0 -1 0` # gracefully timeout
|
||||
do
|
||||
sleep 0.1
|
||||
# done if status files exist, but don't mention initialization
|
||||
if \
|
||||
stat -t "@spoolAreaPath@"/status/* >/dev/null 2>&1 \
|
||||
&& \
|
||||
! grep --silent --ignore-case 'initializing server' \
|
||||
"@spoolAreaPath@"/status/*
|
||||
then
|
||||
echo "modem(s) apparently ready"
|
||||
exit 0
|
||||
fi
|
||||
# if i reached 0, modems probably failed to initialize
|
||||
if test $i -eq 0
|
||||
then
|
||||
echo "warning: modem initialization timed out"
|
||||
fi
|
||||
done
|
||||
10
nixos/modules/services/networking/hylafax/hfaxd-default.nix
Normal file
10
nixos/modules/services/networking/hylafax/hfaxd-default.nix
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{ ... }:
|
||||
|
||||
# see man:hfaxd(8)
|
||||
|
||||
{
|
||||
|
||||
ServerTracing = "0x91";
|
||||
XferLogFile = "/clientlog";
|
||||
|
||||
}
|
||||
22
nixos/modules/services/networking/hylafax/modem-default.nix
Normal file
22
nixos/modules/services/networking/hylafax/modem-default.nix
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
# see man:hylafax-config(5)
|
||||
|
||||
{
|
||||
|
||||
TagLineFont = "etc/LiberationSans-25.pcf";
|
||||
TagLineLocale = "en_US.UTF-8";
|
||||
|
||||
AdminGroup = "root"; # groups that can change server config
|
||||
AnswerRotary = "fax"; # don't accept anything else but faxes
|
||||
LogFileMode = "0640";
|
||||
PriorityScheduling = true;
|
||||
RecvFileMode = "0640";
|
||||
ServerTracing = "0x78701";
|
||||
SessionTracing = "0x78701";
|
||||
UUCPLockDir = "/var/lock";
|
||||
|
||||
SendPageCmd = "${pkgs.coreutils}/bin/false"; # prevent pager transmit
|
||||
SendUUCPCmd = "${pkgs.coreutils}/bin/false"; # prevent UUCP transmit
|
||||
|
||||
}
|
||||
372
nixos/modules/services/networking/hylafax/options.nix
Normal file
372
nixos/modules/services/networking/hylafax/options.nix
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
|
||||
inherit (lib.options) literalExpression mkEnableOption mkOption;
|
||||
inherit (lib.types) bool enum ints lines attrsOf nonEmptyStr nullOr path str submodule;
|
||||
inherit (lib.modules) mkDefault mkIf mkMerge;
|
||||
|
||||
commonDescr = ''
|
||||
Values can be either strings or integers
|
||||
(which will be added to the config file verbatimly)
|
||||
or lists thereof
|
||||
(which will be translated to multiple
|
||||
lines with the same configuration key).
|
||||
Boolean values are translated to "Yes" or "No".
|
||||
The default contains some reasonable
|
||||
configuration to yield an operational system.
|
||||
'';
|
||||
|
||||
configAttrType =
|
||||
# Options in HylaFAX configuration files can be
|
||||
# booleans, strings, integers, or list thereof
|
||||
# representing multiple config directives with the same key.
|
||||
# This type definition resolves all
|
||||
# those types into a list of strings.
|
||||
let
|
||||
inherit (lib.types) attrsOf coercedTo int listOf;
|
||||
innerType = coercedTo bool (x: if x then "Yes" else "No")
|
||||
(coercedTo int (toString) str);
|
||||
in
|
||||
attrsOf (coercedTo innerType lib.singleton (listOf innerType));
|
||||
|
||||
cfg = config.services.hylafax;
|
||||
|
||||
modemConfigOptions = { name, config, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = nonEmptyStr;
|
||||
example = "ttyS1";
|
||||
description = ''
|
||||
Name of modem device,
|
||||
will be searched for in <filename>/dev</filename>.
|
||||
'';
|
||||
};
|
||||
type = mkOption {
|
||||
type = nonEmptyStr;
|
||||
example = "cirrus";
|
||||
description = ''
|
||||
Name of modem configuration file,
|
||||
will be searched for in <filename>config</filename>
|
||||
in the spooling area directory.
|
||||
'';
|
||||
};
|
||||
config = mkOption {
|
||||
type = configAttrType;
|
||||
example = {
|
||||
AreaCode = "49";
|
||||
LocalCode = "30";
|
||||
FAXNumber = "123456";
|
||||
LocalIdentifier = "LostInBerlin";
|
||||
};
|
||||
description = ''
|
||||
Attribute set of values for the given modem.
|
||||
${commonDescr}
|
||||
Options defined here override options in
|
||||
<option>commonModemConfig</option> for this modem.
|
||||
'';
|
||||
};
|
||||
};
|
||||
config.name = mkDefault name;
|
||||
config.config.Include = [ "config/${config.type}" ];
|
||||
};
|
||||
|
||||
defaultConfig =
|
||||
let
|
||||
inherit (config.security) wrapperDir;
|
||||
inherit (config.services.mail.sendmailSetuidWrapper) program;
|
||||
mkIfDefault = cond: value: mkIf cond (mkDefault value);
|
||||
noWrapper = config.services.mail.sendmailSetuidWrapper==null;
|
||||
# If a sendmail setuid wrapper exists,
|
||||
# we add the path to the default configuration file.
|
||||
# Otherwise, we use `false` to provoke
|
||||
# an error if hylafax tries to use it.
|
||||
c.sendmailPath = mkMerge [
|
||||
(mkIfDefault noWrapper "${pkgs.coreutils}/bin/false")
|
||||
(mkIfDefault (!noWrapper) "${wrapperDir}/${program}")
|
||||
];
|
||||
importDefaultConfig = file:
|
||||
lib.attrsets.mapAttrs
|
||||
(lib.trivial.const mkDefault)
|
||||
(import file { inherit pkgs; });
|
||||
c.commonModemConfig = importDefaultConfig ./modem-default.nix;
|
||||
c.faxqConfig = importDefaultConfig ./faxq-default.nix;
|
||||
c.hfaxdConfig = importDefaultConfig ./hfaxd-default.nix;
|
||||
in
|
||||
c;
|
||||
|
||||
localConfig =
|
||||
let
|
||||
c.hfaxdConfig.UserAccessFile = cfg.userAccessFile;
|
||||
c.faxqConfig = lib.attrsets.mapAttrs
|
||||
(lib.trivial.const (v: mkIf (v!=null) v))
|
||||
{
|
||||
AreaCode = cfg.areaCode;
|
||||
CountryCode = cfg.countryCode;
|
||||
LongDistancePrefix = cfg.longDistancePrefix;
|
||||
InternationalPrefix = cfg.internationalPrefix;
|
||||
};
|
||||
c.commonModemConfig = c.faxqConfig;
|
||||
in
|
||||
c;
|
||||
|
||||
in
|
||||
|
||||
|
||||
{
|
||||
|
||||
|
||||
options.services.hylafax = {
|
||||
|
||||
enable = mkEnableOption "HylaFAX server";
|
||||
|
||||
autostart = mkOption {
|
||||
type = bool;
|
||||
default = true;
|
||||
example = false;
|
||||
description = ''
|
||||
Autostart the HylaFAX queue manager at system start.
|
||||
If this is <literal>false</literal>, the queue manager
|
||||
will still be started if there are pending
|
||||
jobs or if a user tries to connect to it.
|
||||
'';
|
||||
};
|
||||
|
||||
countryCode = mkOption {
|
||||
type = nullOr nonEmptyStr;
|
||||
default = null;
|
||||
example = "49";
|
||||
description = "Country code for server and all modems.";
|
||||
};
|
||||
|
||||
areaCode = mkOption {
|
||||
type = nullOr nonEmptyStr;
|
||||
default = null;
|
||||
example = "30";
|
||||
description = "Area code for server and all modems.";
|
||||
};
|
||||
|
||||
longDistancePrefix = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
example = "0";
|
||||
description = "Long distance prefix for server and all modems.";
|
||||
};
|
||||
|
||||
internationalPrefix = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
example = "00";
|
||||
description = "International prefix for server and all modems.";
|
||||
};
|
||||
|
||||
spoolAreaPath = mkOption {
|
||||
type = path;
|
||||
default = "/var/spool/fax";
|
||||
description = ''
|
||||
The spooling area will be created/maintained
|
||||
at the location given here.
|
||||
'';
|
||||
};
|
||||
|
||||
userAccessFile = mkOption {
|
||||
type = path;
|
||||
default = "/etc/hosts.hfaxd";
|
||||
description = ''
|
||||
The <filename>hosts.hfaxd</filename>
|
||||
file entry in the spooling area
|
||||
will be symlinked to the location given here.
|
||||
This file must exist and be
|
||||
readable only by the <literal>uucp</literal> user.
|
||||
See hosts.hfaxd(5) for details.
|
||||
This configuration permits access for all users:
|
||||
<literal>
|
||||
environment.etc."hosts.hfaxd" = {
|
||||
mode = "0600";
|
||||
user = "uucp";
|
||||
text = ".*";
|
||||
};
|
||||
</literal>
|
||||
Note that host-based access can be controlled with
|
||||
<option>config.systemd.sockets.hylafax-hfaxd.listenStreams</option>;
|
||||
by default, only 127.0.0.1 is permitted to connect.
|
||||
'';
|
||||
};
|
||||
|
||||
sendmailPath = mkOption {
|
||||
type = path;
|
||||
example = literalExpression ''"''${pkgs.postfix}/bin/sendmail"'';
|
||||
# '' ; # fix vim
|
||||
description = ''
|
||||
Path to <filename>sendmail</filename> program.
|
||||
The default uses the local sendmail wrapper
|
||||
(see <option>config.services.mail.sendmailSetuidWrapper</option>),
|
||||
otherwise the <filename>false</filename>
|
||||
binary to cause an error if used.
|
||||
'';
|
||||
};
|
||||
|
||||
hfaxdConfig = mkOption {
|
||||
type = configAttrType;
|
||||
example.RecvqProtection = "0400";
|
||||
description = ''
|
||||
Attribute set of lines for the global
|
||||
hfaxd config file <filename>etc/hfaxd.conf</filename>.
|
||||
${commonDescr}
|
||||
'';
|
||||
};
|
||||
|
||||
faxqConfig = mkOption {
|
||||
type = configAttrType;
|
||||
example = {
|
||||
InternationalPrefix = "00";
|
||||
LongDistancePrefix = "0";
|
||||
};
|
||||
description = ''
|
||||
Attribute set of lines for the global
|
||||
faxq config file <filename>etc/config</filename>.
|
||||
${commonDescr}
|
||||
'';
|
||||
};
|
||||
|
||||
commonModemConfig = mkOption {
|
||||
type = configAttrType;
|
||||
example = {
|
||||
InternationalPrefix = "00";
|
||||
LongDistancePrefix = "0";
|
||||
};
|
||||
description = ''
|
||||
Attribute set of default values for
|
||||
modem config files <filename>etc/config.*</filename>.
|
||||
${commonDescr}
|
||||
Think twice before changing
|
||||
paths of fax-processing scripts.
|
||||
'';
|
||||
};
|
||||
|
||||
modems = mkOption {
|
||||
type = attrsOf (submodule [ modemConfigOptions ]);
|
||||
default = {};
|
||||
example.ttyS1 = {
|
||||
type = "cirrus";
|
||||
config = {
|
||||
FAXNumber = "123456";
|
||||
LocalIdentifier = "Smith";
|
||||
};
|
||||
};
|
||||
description = ''
|
||||
Description of installed modems.
|
||||
At least on modem must be defined
|
||||
to enable the HylaFAX server.
|
||||
'';
|
||||
};
|
||||
|
||||
spoolExtraInit = mkOption {
|
||||
type = lines;
|
||||
default = "";
|
||||
example = "chmod 0755 . # everyone may read my faxes";
|
||||
description = ''
|
||||
Additional shell code that is executed within the
|
||||
spooling area directory right after its setup.
|
||||
'';
|
||||
};
|
||||
|
||||
faxcron.enable.spoolInit = mkEnableOption ''
|
||||
Purge old files from the spooling area with
|
||||
<filename>faxcron</filename>
|
||||
each time the spooling area is initialized.
|
||||
'';
|
||||
faxcron.enable.frequency = mkOption {
|
||||
type = nullOr nonEmptyStr;
|
||||
default = null;
|
||||
example = "daily";
|
||||
description = ''
|
||||
Purge old files from the spooling area with
|
||||
<filename>faxcron</filename> with the given frequency
|
||||
(see systemd.time(7)).
|
||||
'';
|
||||
};
|
||||
faxcron.infoDays = mkOption {
|
||||
type = ints.positive;
|
||||
default = 30;
|
||||
description = ''
|
||||
Set the expiration time for data in the
|
||||
remote machine information directory in days.
|
||||
'';
|
||||
};
|
||||
faxcron.logDays = mkOption {
|
||||
type = ints.positive;
|
||||
default = 30;
|
||||
description = ''
|
||||
Set the expiration time for
|
||||
session trace log files in days.
|
||||
'';
|
||||
};
|
||||
faxcron.rcvDays = mkOption {
|
||||
type = ints.positive;
|
||||
default = 7;
|
||||
description = ''
|
||||
Set the expiration time for files in
|
||||
the received facsimile queue in days.
|
||||
'';
|
||||
};
|
||||
|
||||
faxqclean.enable.spoolInit = mkEnableOption ''
|
||||
Purge old files from the spooling area with
|
||||
<filename>faxqclean</filename>
|
||||
each time the spooling area is initialized.
|
||||
'';
|
||||
faxqclean.enable.frequency = mkOption {
|
||||
type = nullOr nonEmptyStr;
|
||||
default = null;
|
||||
example = "daily";
|
||||
description = ''
|
||||
Purge old files from the spooling area with
|
||||
<filename>faxcron</filename> with the given frequency
|
||||
(see systemd.time(7)).
|
||||
'';
|
||||
};
|
||||
faxqclean.archiving = mkOption {
|
||||
type = enum [ "never" "as-flagged" "always" ];
|
||||
default = "as-flagged";
|
||||
example = "always";
|
||||
description = ''
|
||||
Enable or suppress job archiving:
|
||||
<literal>never</literal> disables job archiving,
|
||||
<literal>as-flagged</literal> archives jobs that
|
||||
have been flagged for archiving by sendfax,
|
||||
<literal>always</literal> forces archiving of all jobs.
|
||||
See also sendfax(1) and faxqclean(8).
|
||||
'';
|
||||
};
|
||||
faxqclean.doneqMinutes = mkOption {
|
||||
type = ints.positive;
|
||||
default = 15;
|
||||
example = literalExpression "24*60";
|
||||
description = ''
|
||||
Set the job
|
||||
age threshold (in minutes) that controls how long
|
||||
jobs may reside in the doneq directory.
|
||||
'';
|
||||
};
|
||||
faxqclean.docqMinutes = mkOption {
|
||||
type = ints.positive;
|
||||
default = 60;
|
||||
example = literalExpression "24*60";
|
||||
description = ''
|
||||
Set the document
|
||||
age threshold (in minutes) that controls how long
|
||||
unreferenced files may reside in the docq directory.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
config.services.hylafax =
|
||||
mkIf
|
||||
(config.services.hylafax.enable)
|
||||
(mkMerge [ defaultConfig localConfig ])
|
||||
;
|
||||
|
||||
}
|
||||
111
nixos/modules/services/networking/hylafax/spool.sh
Executable file
111
nixos/modules/services/networking/hylafax/spool.sh
Executable file
|
|
@ -0,0 +1,111 @@
|
|||
#! @runtimeShell@ -e
|
||||
|
||||
# The following lines create/update the HylaFAX spool directory:
|
||||
# Subdirectories/files with persistent data are kept,
|
||||
# other directories/files are removed/recreated,
|
||||
# mostly from the template spool
|
||||
# directory in the HylaFAX package.
|
||||
|
||||
# This block explains how the spool area is
|
||||
# derived from the spool template in the HylaFAX package:
|
||||
#
|
||||
# + capital letter: directory; file otherwise
|
||||
# + P/p: persistent directory
|
||||
# + F/f: directory with symlinks per entry
|
||||
# + T/t: temporary data
|
||||
# + S/s: single symlink into package
|
||||
# |
|
||||
# | + u: change ownership to uucp:uucp
|
||||
# | + U: ..also change access mode to user-only
|
||||
# | |
|
||||
# archive P U
|
||||
# bin S
|
||||
# client T u (client connection info)
|
||||
# config S
|
||||
# COPYRIGHT s
|
||||
# dev T u (maybe some FIFOs)
|
||||
# docq P U
|
||||
# doneq P U
|
||||
# etc F contains customized config files!
|
||||
# etc/hosts.hfaxd f
|
||||
# etc/xferfaxlog f
|
||||
# info P u (database of called devices)
|
||||
# log P u (communication logs)
|
||||
# pollq P U
|
||||
# recvq P u
|
||||
# sendq P U
|
||||
# status T u (modem status info files)
|
||||
# tmp T U
|
||||
|
||||
|
||||
shopt -s dotglob # if bash sees "*", it also includes dot files
|
||||
lnsym () { ln --symbol "$@" ; }
|
||||
lnsymfrc () { ln --symbolic --force "$@" ; }
|
||||
cprd () { cp --remove-destination "$@" ; }
|
||||
update () { install --owner=@faxuser@ --group=@faxgroup@ "$@" ; }
|
||||
|
||||
|
||||
## create/update spooling area
|
||||
|
||||
update --mode=0750 -d "@spoolAreaPath@"
|
||||
cd "@spoolAreaPath@"
|
||||
|
||||
persist=(archive docq doneq info log pollq recvq sendq)
|
||||
|
||||
# remove entries that don't belong here
|
||||
touch dummy # ensure "*" resolves to something
|
||||
for k in *
|
||||
do
|
||||
keep=0
|
||||
for j in "${persist[@]}" xferfaxlog clientlog faxcron.lastrun
|
||||
do
|
||||
if test "$k" == "$j"
|
||||
then
|
||||
keep=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test "$keep" == "0"
|
||||
then
|
||||
rm --recursive "$k"
|
||||
fi
|
||||
done
|
||||
|
||||
# create persistent data directories (unless they exist already)
|
||||
update --mode=0700 -d "${persist[@]}"
|
||||
chmod 0755 info log recvq
|
||||
|
||||
# create ``xferfaxlog``, ``faxcron.lastrun``, ``clientlog``
|
||||
touch clientlog faxcron.lastrun xferfaxlog
|
||||
chown @faxuser@:@faxgroup@ clientlog faxcron.lastrun xferfaxlog
|
||||
|
||||
# create symlinks for frozen directories/files
|
||||
lnsym --target-directory=. "@hylafaxplus@"/spool/{COPYRIGHT,bin,config}
|
||||
|
||||
# create empty temporary directories
|
||||
update --mode=0700 -d client dev status
|
||||
update -d tmp
|
||||
|
||||
|
||||
## create and fill etc
|
||||
|
||||
install -d "@spoolAreaPath@/etc"
|
||||
cd "@spoolAreaPath@/etc"
|
||||
|
||||
# create symlinks to all files in template's etc
|
||||
lnsym --target-directory=. "@hylafaxplus@/spool/etc"/*
|
||||
|
||||
# set LOCKDIR in setup.cache
|
||||
sed --regexp-extended 's|^(UUCP_LOCKDIR=).*$|\1'"'@lockPath@'|g" --in-place setup.cache
|
||||
|
||||
# etc/{xferfaxlog,lastrun} are stored in the spool root
|
||||
lnsymfrc --target-directory=. ../xferfaxlog
|
||||
lnsymfrc --no-target-directory ../faxcron.lastrun lastrun
|
||||
|
||||
# etc/hosts.hfaxd is provided by the NixOS configuration
|
||||
lnsymfrc --no-target-directory "@userAccessFile@" hosts.hfaxd
|
||||
|
||||
# etc/config and etc/config.${DEVID} must be copied:
|
||||
# hfaxd reads these file after locking itself up in a chroot
|
||||
cprd --no-target-directory "@globalConfigPath@" config
|
||||
cprd --target-directory=. "@modemConfigPath@"/*
|
||||
249
nixos/modules/services/networking/hylafax/systemd.nix
Normal file
249
nixos/modules/services/networking/hylafax/systemd.nix
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
|
||||
let
|
||||
|
||||
inherit (lib) mkIf mkMerge;
|
||||
inherit (lib) concatStringsSep optionalString;
|
||||
|
||||
cfg = config.services.hylafax;
|
||||
mapModems = lib.forEach (lib.attrValues cfg.modems);
|
||||
|
||||
mkConfigFile = name: conf:
|
||||
# creates hylafax config file,
|
||||
# makes sure "Include" is listed *first*
|
||||
let
|
||||
mkLines = lib.flip lib.pipe [
|
||||
(lib.mapAttrsToList (key: map (val: "${key}: ${val}")))
|
||||
lib.concatLists
|
||||
];
|
||||
include = mkLines { Include = conf.Include or []; };
|
||||
other = mkLines ( conf // { Include = []; } );
|
||||
in
|
||||
pkgs.writeText "hylafax-config${name}"
|
||||
(concatStringsSep "\n" (include ++ other));
|
||||
|
||||
globalConfigPath = mkConfigFile "" cfg.faxqConfig;
|
||||
|
||||
modemConfigPath =
|
||||
let
|
||||
mkModemConfigFile = { config, name, ... }:
|
||||
mkConfigFile ".${name}"
|
||||
(cfg.commonModemConfig // config);
|
||||
mkLine = { name, type, ... }@modem: ''
|
||||
# check if modem config file exists:
|
||||
test -f "${pkgs.hylafaxplus}/spool/config/${type}"
|
||||
ln \
|
||||
--symbolic \
|
||||
--no-target-directory \
|
||||
"${mkModemConfigFile modem}" \
|
||||
"$out/config.${name}"
|
||||
'';
|
||||
in
|
||||
pkgs.runCommand "hylafax-config-modems" { preferLocalBuild = true; }
|
||||
''mkdir --parents "$out/" ${concatStringsSep "\n" (mapModems mkLine)}'';
|
||||
|
||||
setupSpoolScript = pkgs.substituteAll {
|
||||
name = "hylafax-setup-spool.sh";
|
||||
src = ./spool.sh;
|
||||
isExecutable = true;
|
||||
faxuser = "uucp";
|
||||
faxgroup = "uucp";
|
||||
lockPath = "/var/lock";
|
||||
inherit globalConfigPath modemConfigPath;
|
||||
inherit (cfg) sendmailPath spoolAreaPath userAccessFile;
|
||||
inherit (pkgs) hylafaxplus runtimeShell;
|
||||
};
|
||||
|
||||
waitFaxqScript = pkgs.substituteAll {
|
||||
# This script checks the modems status files
|
||||
# and waits until all modems report readiness.
|
||||
name = "hylafax-faxq-wait-start.sh";
|
||||
src = ./faxq-wait.sh;
|
||||
isExecutable = true;
|
||||
timeoutSec = toString 10;
|
||||
inherit (cfg) spoolAreaPath;
|
||||
inherit (pkgs) runtimeShell;
|
||||
};
|
||||
|
||||
sockets.hylafax-hfaxd = {
|
||||
description = "HylaFAX server socket";
|
||||
documentation = [ "man:hfaxd(8)" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
listenStreams = [ "127.0.0.1:4559" ];
|
||||
socketConfig.FreeBind = true;
|
||||
socketConfig.Accept = true;
|
||||
};
|
||||
|
||||
paths.hylafax-faxq = {
|
||||
description = "HylaFAX queue manager sendq watch";
|
||||
documentation = [ "man:faxq(8)" "man:sendq(5)" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
pathConfig.PathExistsGlob = [ "${cfg.spoolAreaPath}/sendq/q*" ];
|
||||
};
|
||||
|
||||
timers = mkMerge [
|
||||
(
|
||||
mkIf (cfg.faxcron.enable.frequency!=null)
|
||||
{ hylafax-faxcron.timerConfig.Persistent = true; }
|
||||
)
|
||||
(
|
||||
mkIf (cfg.faxqclean.enable.frequency!=null)
|
||||
{ hylafax-faxqclean.timerConfig.Persistent = true; }
|
||||
)
|
||||
];
|
||||
|
||||
hardenService =
|
||||
# Add some common systemd service hardening settings,
|
||||
# but allow each service (here) to override
|
||||
# settings by explicitely setting those to `null`.
|
||||
# More hardening would be nice but makes
|
||||
# customizing hylafax setups very difficult.
|
||||
# If at all, it should only be added along
|
||||
# with some options to customize it.
|
||||
let
|
||||
hardening = {
|
||||
PrivateDevices = true; # breaks /dev/tty...
|
||||
PrivateNetwork = true;
|
||||
PrivateTmp = true;
|
||||
#ProtectClock = true; # breaks /dev/tty... (why?)
|
||||
ProtectControlGroups = true;
|
||||
#ProtectHome = true; # breaks custom spool dirs
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
#ProtectSystem = "strict"; # breaks custom spool dirs
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
};
|
||||
filter = key: value: (value != null) || ! (lib.hasAttr key hardening);
|
||||
apply = service: lib.filterAttrs filter (hardening // (service.serviceConfig or {}));
|
||||
in
|
||||
service: service // { serviceConfig = apply service; };
|
||||
|
||||
services.hylafax-spool = {
|
||||
description = "HylaFAX spool area preparation";
|
||||
documentation = [ "man:hylafax-server(4)" ];
|
||||
script = ''
|
||||
${setupSpoolScript}
|
||||
cd "${cfg.spoolAreaPath}"
|
||||
${cfg.spoolExtraInit}
|
||||
if ! test -f "${cfg.spoolAreaPath}/etc/hosts.hfaxd"
|
||||
then
|
||||
echo hosts.hfaxd is missing
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
serviceConfig.ExecStop = "${setupSpoolScript}";
|
||||
serviceConfig.RemainAfterExit = true;
|
||||
serviceConfig.Type = "oneshot";
|
||||
unitConfig.RequiresMountsFor = [ cfg.spoolAreaPath ];
|
||||
};
|
||||
|
||||
services.hylafax-faxq = {
|
||||
description = "HylaFAX queue manager";
|
||||
documentation = [ "man:faxq(8)" ];
|
||||
requires = [ "hylafax-spool.service" ];
|
||||
after = [ "hylafax-spool.service" ];
|
||||
wants = mapModems ( { name, ... }: "hylafax-faxgetty@${name}.service" );
|
||||
wantedBy = mkIf cfg.autostart [ "multi-user.target" ];
|
||||
serviceConfig.Type = "forking";
|
||||
serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/faxq -q "${cfg.spoolAreaPath}"'';
|
||||
# This delays the "readiness" of this service until
|
||||
# all modems are initialized (or a timeout is reached).
|
||||
# Otherwise, sending a fax with the fax service
|
||||
# stopped will always yield a failed send attempt:
|
||||
# The fax service is started when the job is created with
|
||||
# `sendfax`, but modems need some time to initialize.
|
||||
serviceConfig.ExecStartPost = [ "${waitFaxqScript}" ];
|
||||
# faxquit fails if the pipe is already gone
|
||||
# (e.g. the service is already stopping)
|
||||
serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}"'';
|
||||
# disable some systemd hardening settings
|
||||
serviceConfig.PrivateDevices = null;
|
||||
serviceConfig.RestrictRealtime = null;
|
||||
};
|
||||
|
||||
services."hylafax-hfaxd@" = {
|
||||
description = "HylaFAX server";
|
||||
documentation = [ "man:hfaxd(8)" ];
|
||||
after = [ "hylafax-faxq.service" ];
|
||||
requires = [ "hylafax-faxq.service" ];
|
||||
serviceConfig.StandardInput = "socket";
|
||||
serviceConfig.StandardOutput = "socket";
|
||||
serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/hfaxd -q "${cfg.spoolAreaPath}" -d -I'';
|
||||
unitConfig.RequiresMountsFor = [ cfg.userAccessFile ];
|
||||
# disable some systemd hardening settings
|
||||
serviceConfig.PrivateDevices = null;
|
||||
serviceConfig.PrivateNetwork = null;
|
||||
};
|
||||
|
||||
services.hylafax-faxcron = rec {
|
||||
description = "HylaFAX spool area maintenance";
|
||||
documentation = [ "man:faxcron(8)" ];
|
||||
after = [ "hylafax-spool.service" ];
|
||||
requires = [ "hylafax-spool.service" ];
|
||||
wantedBy = mkIf cfg.faxcron.enable.spoolInit requires;
|
||||
startAt = mkIf (cfg.faxcron.enable.frequency!=null) cfg.faxcron.enable.frequency;
|
||||
serviceConfig.ExecStart = concatStringsSep " " [
|
||||
"${pkgs.hylafaxplus}/spool/bin/faxcron"
|
||||
''-q "${cfg.spoolAreaPath}"''
|
||||
''-info ${toString cfg.faxcron.infoDays}''
|
||||
''-log ${toString cfg.faxcron.logDays}''
|
||||
''-rcv ${toString cfg.faxcron.rcvDays}''
|
||||
];
|
||||
};
|
||||
|
||||
services.hylafax-faxqclean = rec {
|
||||
description = "HylaFAX spool area queue cleaner";
|
||||
documentation = [ "man:faxqclean(8)" ];
|
||||
after = [ "hylafax-spool.service" ];
|
||||
requires = [ "hylafax-spool.service" ];
|
||||
wantedBy = mkIf cfg.faxqclean.enable.spoolInit requires;
|
||||
startAt = mkIf (cfg.faxqclean.enable.frequency!=null) cfg.faxqclean.enable.frequency;
|
||||
serviceConfig.ExecStart = concatStringsSep " " [
|
||||
"${pkgs.hylafaxplus}/spool/bin/faxqclean"
|
||||
''-q "${cfg.spoolAreaPath}"''
|
||||
"-v"
|
||||
(optionalString (cfg.faxqclean.archiving!="never") "-a")
|
||||
(optionalString (cfg.faxqclean.archiving=="always") "-A")
|
||||
''-j ${toString (cfg.faxqclean.doneqMinutes*60)}''
|
||||
''-d ${toString (cfg.faxqclean.docqMinutes*60)}''
|
||||
];
|
||||
};
|
||||
|
||||
mkFaxgettyService = { name, ... }:
|
||||
lib.nameValuePair "hylafax-faxgetty@${name}" rec {
|
||||
description = "HylaFAX faxgetty for %I";
|
||||
documentation = [ "man:faxgetty(8)" ];
|
||||
bindsTo = [ "dev-%i.device" ];
|
||||
requires = [ "hylafax-spool.service" ];
|
||||
after = bindsTo ++ requires;
|
||||
before = [ "hylafax-faxq.service" "getty.target" ];
|
||||
unitConfig.StopWhenUnneeded = true;
|
||||
unitConfig.AssertFileNotEmpty = "${cfg.spoolAreaPath}/etc/config.%I";
|
||||
serviceConfig.UtmpIdentifier = "%I";
|
||||
serviceConfig.TTYPath = "/dev/%I";
|
||||
serviceConfig.Restart = "always";
|
||||
serviceConfig.KillMode = "process";
|
||||
serviceConfig.IgnoreSIGPIPE = false;
|
||||
serviceConfig.ExecStart = ''-${pkgs.hylafaxplus}/spool/bin/faxgetty -q "${cfg.spoolAreaPath}" /dev/%I'';
|
||||
# faxquit fails if the pipe is already gone
|
||||
# (e.g. the service is already stopping)
|
||||
serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}" %I'';
|
||||
# disable some systemd hardening settings
|
||||
serviceConfig.PrivateDevices = null;
|
||||
serviceConfig.RestrictRealtime = null;
|
||||
};
|
||||
|
||||
modemServices =
|
||||
lib.listToAttrs (mapModems mkFaxgettyService);
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
config.systemd = mkIf cfg.enable {
|
||||
inherit sockets timers paths;
|
||||
services = lib.mapAttrs (lib.const hardenService) (services // modemServices);
|
||||
};
|
||||
}
|
||||
34
nixos/modules/services/networking/i2p.nix
Normal file
34
nixos/modules/services/networking/i2p.nix
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.i2p;
|
||||
homeDir = "/var/lib/i2p";
|
||||
in {
|
||||
###### interface
|
||||
options.services.i2p.enable = mkEnableOption "I2P router";
|
||||
|
||||
###### implementation
|
||||
config = mkIf cfg.enable {
|
||||
users.users.i2p = {
|
||||
group = "i2p";
|
||||
description = "i2p User";
|
||||
home = homeDir;
|
||||
createHome = true;
|
||||
uid = config.ids.uids.i2p;
|
||||
};
|
||||
users.groups.i2p.gid = config.ids.gids.i2p;
|
||||
systemd.services.i2p = {
|
||||
description = "I2P router with administration interface for hidden services";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
User = "i2p";
|
||||
WorkingDirectory = homeDir;
|
||||
Restart = "on-abort";
|
||||
ExecStart = "${pkgs.i2p}/bin/i2prouter-plain";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
691
nixos/modules/services/networking/i2pd.nix
Normal file
691
nixos/modules/services/networking/i2pd.nix
Normal file
|
|
@ -0,0 +1,691 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.i2pd;
|
||||
|
||||
homeDir = "/var/lib/i2pd";
|
||||
|
||||
strOpt = k: v: k + " = " + v;
|
||||
boolOpt = k: v: k + " = " + boolToString v;
|
||||
intOpt = k: v: k + " = " + toString v;
|
||||
lstOpt = k: xs: k + " = " + concatStringsSep "," xs;
|
||||
optionalNullString = o: s: optional (s != null) (strOpt o s);
|
||||
optionalNullBool = o: b: optional (b != null) (boolOpt o b);
|
||||
optionalNullInt = o: i: optional (i != null) (intOpt o i);
|
||||
optionalEmptyList = o: l: optional ([] != l) (lstOpt o l);
|
||||
|
||||
mkEnableTrueOption = name: mkEnableOption name // { default = true; };
|
||||
|
||||
mkEndpointOpt = name: addr: port: {
|
||||
enable = mkEnableOption name;
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
description = "The endpoint name.";
|
||||
};
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
default = addr;
|
||||
description = "Bind address for ${name} endpoint.";
|
||||
};
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = port;
|
||||
description = "Bind port for ${name} endpoint.";
|
||||
};
|
||||
};
|
||||
|
||||
i2cpOpts = name: {
|
||||
length = mkOption {
|
||||
type = types.int;
|
||||
description = "Guaranteed minimum hops for ${name} tunnels.";
|
||||
default = 3;
|
||||
};
|
||||
quantity = mkOption {
|
||||
type = types.int;
|
||||
description = "Number of simultaneous ${name} tunnels.";
|
||||
default = 5;
|
||||
};
|
||||
};
|
||||
|
||||
mkKeyedEndpointOpt = name: addr: port: keyloc:
|
||||
(mkEndpointOpt name addr port) // {
|
||||
keys = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = keyloc;
|
||||
description = ''
|
||||
File to persist ${lib.toUpper name} keys.
|
||||
'';
|
||||
};
|
||||
inbound = i2cpOpts name;
|
||||
outbound = i2cpOpts name;
|
||||
latency.min = mkOption {
|
||||
type = with types; nullOr int;
|
||||
description = "Min latency for tunnels.";
|
||||
default = null;
|
||||
};
|
||||
latency.max = mkOption {
|
||||
type = with types; nullOr int;
|
||||
description = "Max latency for tunnels.";
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
|
||||
commonTunOpts = name: {
|
||||
outbound = i2cpOpts name;
|
||||
inbound = i2cpOpts name;
|
||||
crypto.tagsToSend = mkOption {
|
||||
type = types.int;
|
||||
description = "Number of ElGamal/AES tags to send.";
|
||||
default = 40;
|
||||
};
|
||||
destination = mkOption {
|
||||
type = types.str;
|
||||
description = "Remote endpoint, I2P hostname or b32.i2p address.";
|
||||
};
|
||||
keys = mkOption {
|
||||
type = types.str;
|
||||
default = name + "-keys.dat";
|
||||
description = "Keyset used for tunnel identity.";
|
||||
};
|
||||
} // mkEndpointOpt name "127.0.0.1" 0;
|
||||
|
||||
sec = name: "\n[" + name + "]";
|
||||
notice = "# DO NOT EDIT -- this file has been generated automatically.";
|
||||
i2pdConf = let
|
||||
opts = [
|
||||
notice
|
||||
(strOpt "loglevel" cfg.logLevel)
|
||||
(boolOpt "logclftime" cfg.logCLFTime)
|
||||
(boolOpt "ipv4" cfg.enableIPv4)
|
||||
(boolOpt "ipv6" cfg.enableIPv6)
|
||||
(boolOpt "notransit" cfg.notransit)
|
||||
(boolOpt "floodfill" cfg.floodfill)
|
||||
(intOpt "netid" cfg.netid)
|
||||
] ++ (optionalNullInt "bandwidth" cfg.bandwidth)
|
||||
++ (optionalNullInt "port" cfg.port)
|
||||
++ (optionalNullString "family" cfg.family)
|
||||
++ (optionalNullString "datadir" cfg.dataDir)
|
||||
++ (optionalNullInt "share" cfg.share)
|
||||
++ (optionalNullBool "ssu" cfg.ssu)
|
||||
++ (optionalNullBool "ntcp" cfg.ntcp)
|
||||
++ (optionalNullString "ntcpproxy" cfg.ntcpProxy)
|
||||
++ (optionalNullString "ifname" cfg.ifname)
|
||||
++ (optionalNullString "ifname4" cfg.ifname4)
|
||||
++ (optionalNullString "ifname6" cfg.ifname6)
|
||||
++ [
|
||||
(sec "limits")
|
||||
(intOpt "transittunnels" cfg.limits.transittunnels)
|
||||
(intOpt "coresize" cfg.limits.coreSize)
|
||||
(intOpt "openfiles" cfg.limits.openFiles)
|
||||
(intOpt "ntcphard" cfg.limits.ntcpHard)
|
||||
(intOpt "ntcpsoft" cfg.limits.ntcpSoft)
|
||||
(intOpt "ntcpthreads" cfg.limits.ntcpThreads)
|
||||
(sec "upnp")
|
||||
(boolOpt "enabled" cfg.upnp.enable)
|
||||
(sec "precomputation")
|
||||
(boolOpt "elgamal" cfg.precomputation.elgamal)
|
||||
(sec "reseed")
|
||||
(boolOpt "verify" cfg.reseed.verify)
|
||||
] ++ (optionalNullString "file" cfg.reseed.file)
|
||||
++ (optionalEmptyList "urls" cfg.reseed.urls)
|
||||
++ (optionalNullString "floodfill" cfg.reseed.floodfill)
|
||||
++ (optionalNullString "zipfile" cfg.reseed.zipfile)
|
||||
++ (optionalNullString "proxy" cfg.reseed.proxy)
|
||||
++ [
|
||||
(sec "trust")
|
||||
(boolOpt "enabled" cfg.trust.enable)
|
||||
(boolOpt "hidden" cfg.trust.hidden)
|
||||
] ++ (optionalEmptyList "routers" cfg.trust.routers)
|
||||
++ (optionalNullString "family" cfg.trust.family)
|
||||
++ [
|
||||
(sec "websockets")
|
||||
(boolOpt "enabled" cfg.websocket.enable)
|
||||
(strOpt "address" cfg.websocket.address)
|
||||
(intOpt "port" cfg.websocket.port)
|
||||
(sec "exploratory")
|
||||
(intOpt "inbound.length" cfg.exploratory.inbound.length)
|
||||
(intOpt "inbound.quantity" cfg.exploratory.inbound.quantity)
|
||||
(intOpt "outbound.length" cfg.exploratory.outbound.length)
|
||||
(intOpt "outbound.quantity" cfg.exploratory.outbound.quantity)
|
||||
(sec "ntcp2")
|
||||
(boolOpt "enabled" cfg.ntcp2.enable)
|
||||
(boolOpt "published" cfg.ntcp2.published)
|
||||
(intOpt "port" cfg.ntcp2.port)
|
||||
(sec "addressbook")
|
||||
(strOpt "defaulturl" cfg.addressbook.defaulturl)
|
||||
] ++ (optionalEmptyList "subscriptions" cfg.addressbook.subscriptions)
|
||||
++ (flip map
|
||||
(collect (proto: proto ? port && proto ? address) cfg.proto)
|
||||
(proto: let protoOpts = [
|
||||
(sec proto.name)
|
||||
(boolOpt "enabled" proto.enable)
|
||||
(strOpt "address" proto.address)
|
||||
(intOpt "port" proto.port)
|
||||
] ++ (if proto ? keys then optionalNullString "keys" proto.keys else [])
|
||||
++ (if proto ? auth then optionalNullBool "auth" proto.auth else [])
|
||||
++ (if proto ? user then optionalNullString "user" proto.user else [])
|
||||
++ (if proto ? pass then optionalNullString "pass" proto.pass else [])
|
||||
++ (if proto ? strictHeaders then optionalNullBool "strictheaders" proto.strictHeaders else [])
|
||||
++ (if proto ? hostname then optionalNullString "hostname" proto.hostname else [])
|
||||
++ (if proto ? outproxy then optionalNullString "outproxy" proto.outproxy else [])
|
||||
++ (if proto ? outproxyPort then optionalNullInt "outproxyport" proto.outproxyPort else [])
|
||||
++ (if proto ? outproxyEnable then optionalNullBool "outproxy.enabled" proto.outproxyEnable else []);
|
||||
in (concatStringsSep "\n" protoOpts)
|
||||
));
|
||||
in
|
||||
pkgs.writeText "i2pd.conf" (concatStringsSep "\n" opts);
|
||||
|
||||
tunnelConf = let opts = [
|
||||
notice
|
||||
(flip map
|
||||
(collect (tun: tun ? port && tun ? destination) cfg.outTunnels)
|
||||
(tun: let outTunOpts = [
|
||||
(sec tun.name)
|
||||
"type = client"
|
||||
(intOpt "port" tun.port)
|
||||
(strOpt "destination" tun.destination)
|
||||
] ++ (if tun ? destinationPort then optionalNullInt "destinationport" tun.destinationPort else [])
|
||||
++ (if tun ? keys then
|
||||
optionalNullString "keys" tun.keys else [])
|
||||
++ (if tun ? address then
|
||||
optionalNullString "address" tun.address else [])
|
||||
++ (if tun ? inbound.length then
|
||||
optionalNullInt "inbound.length" tun.inbound.length else [])
|
||||
++ (if tun ? inbound.quantity then
|
||||
optionalNullInt "inbound.quantity" tun.inbound.quantity else [])
|
||||
++ (if tun ? outbound.length then
|
||||
optionalNullInt "outbound.length" tun.outbound.length else [])
|
||||
++ (if tun ? outbound.quantity then
|
||||
optionalNullInt "outbound.quantity" tun.outbound.quantity else [])
|
||||
++ (if tun ? crypto.tagsToSend then
|
||||
optionalNullInt "crypto.tagstosend" tun.crypto.tagsToSend else []);
|
||||
in concatStringsSep "\n" outTunOpts))
|
||||
(flip map
|
||||
(collect (tun: tun ? port && tun ? address) cfg.inTunnels)
|
||||
(tun: let inTunOpts = [
|
||||
(sec tun.name)
|
||||
"type = server"
|
||||
(intOpt "port" tun.port)
|
||||
(strOpt "host" tun.address)
|
||||
] ++ (if tun ? destination then
|
||||
optionalNullString "destination" tun.destination else [])
|
||||
++ (if tun ? keys then
|
||||
optionalNullString "keys" tun.keys else [])
|
||||
++ (if tun ? inPort then
|
||||
optionalNullInt "inport" tun.inPort else [])
|
||||
++ (if tun ? accessList then
|
||||
optionalEmptyList "accesslist" tun.accessList else []);
|
||||
in concatStringsSep "\n" inTunOpts))];
|
||||
in pkgs.writeText "i2pd-tunnels.conf" opts;
|
||||
|
||||
i2pdFlags = concatStringsSep " " (
|
||||
optional (cfg.address != null) ("--host=" + cfg.address) ++ [
|
||||
"--service"
|
||||
("--conf=" + i2pdConf)
|
||||
("--tunconf=" + tunnelConf)
|
||||
]);
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "services" "i2pd" "extIp" ] [ "services" "i2pd" "address" ])
|
||||
];
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.i2pd = {
|
||||
|
||||
enable = mkEnableOption "I2Pd daemon" // {
|
||||
description = ''
|
||||
Enables I2Pd as a running service upon activation.
|
||||
Please read http://i2pd.readthedocs.io/en/latest/ for further
|
||||
configuration help.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.i2pd;
|
||||
defaultText = literalExpression "pkgs.i2pd";
|
||||
description = ''
|
||||
i2pd package to use.
|
||||
'';
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
type = types.enum ["debug" "info" "warn" "error"];
|
||||
default = "error";
|
||||
description = ''
|
||||
The log level. <command>i2pd</command> defaults to "info"
|
||||
but that generates copious amounts of log messages.
|
||||
|
||||
We default to "error" which is similar to the default log
|
||||
level of <command>tor</command>.
|
||||
'';
|
||||
};
|
||||
|
||||
logCLFTime = mkEnableOption "Full CLF-formatted date and time to log";
|
||||
|
||||
address = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Your external IP or hostname.
|
||||
'';
|
||||
};
|
||||
|
||||
family = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Specify a family the router belongs to.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Alternative path to storage of i2pd data (RI, keys, peer profiles, ...)
|
||||
'';
|
||||
};
|
||||
|
||||
share = mkOption {
|
||||
type = types.int;
|
||||
default = 100;
|
||||
description = ''
|
||||
Limit of transit traffic from max bandwidth in percents.
|
||||
'';
|
||||
};
|
||||
|
||||
ifname = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Network interface to bind to.
|
||||
'';
|
||||
};
|
||||
|
||||
ifname4 = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
IPv4 interface to bind to.
|
||||
'';
|
||||
};
|
||||
|
||||
ifname6 = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
IPv6 interface to bind to.
|
||||
'';
|
||||
};
|
||||
|
||||
ntcpProxy = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Proxy URL for NTCP transport.
|
||||
'';
|
||||
};
|
||||
|
||||
ntcp = mkEnableTrueOption "ntcp";
|
||||
ssu = mkEnableTrueOption "ssu";
|
||||
|
||||
notransit = mkEnableOption "notransit" // {
|
||||
description = ''
|
||||
Tells the router to not accept transit tunnels during startup.
|
||||
'';
|
||||
};
|
||||
|
||||
floodfill = mkEnableOption "floodfill" // {
|
||||
description = ''
|
||||
If the router is declared to be unreachable and needs introduction nodes.
|
||||
'';
|
||||
};
|
||||
|
||||
netid = mkOption {
|
||||
type = types.int;
|
||||
default = 2;
|
||||
description = ''
|
||||
I2P overlay netid.
|
||||
'';
|
||||
};
|
||||
|
||||
bandwidth = mkOption {
|
||||
type = with types; nullOr int;
|
||||
default = null;
|
||||
description = ''
|
||||
Set a router bandwidth limit integer in KBps.
|
||||
If not set, <command>i2pd</command> defaults to 32KBps.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = with types; nullOr int;
|
||||
default = null;
|
||||
description = ''
|
||||
I2P listen port. If no one is given the router will pick between 9111 and 30777.
|
||||
'';
|
||||
};
|
||||
|
||||
enableIPv4 = mkEnableTrueOption "IPv4 connectivity";
|
||||
enableIPv6 = mkEnableOption "IPv6 connectivity";
|
||||
nat = mkEnableTrueOption "NAT bypass";
|
||||
|
||||
upnp.enable = mkEnableOption "UPnP service discovery";
|
||||
upnp.name = mkOption {
|
||||
type = types.str;
|
||||
default = "I2Pd";
|
||||
description = ''
|
||||
Name i2pd appears in UPnP forwardings list.
|
||||
'';
|
||||
};
|
||||
|
||||
precomputation.elgamal = mkEnableTrueOption "Precomputed ElGamal tables" // {
|
||||
description = ''
|
||||
Whenever to use precomputated tables for ElGamal.
|
||||
<command>i2pd</command> defaults to <literal>false</literal>
|
||||
to save 64M of memory (and looses some performance).
|
||||
|
||||
We default to <literal>true</literal> as that is what most
|
||||
users want anyway.
|
||||
'';
|
||||
};
|
||||
|
||||
reseed.verify = mkEnableOption "SU3 signature verification";
|
||||
|
||||
reseed.file = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Full path to SU3 file to reseed from.
|
||||
'';
|
||||
};
|
||||
|
||||
reseed.urls = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [];
|
||||
description = ''
|
||||
Reseed URLs.
|
||||
'';
|
||||
};
|
||||
|
||||
reseed.floodfill = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to router info of floodfill to reseed from.
|
||||
'';
|
||||
};
|
||||
|
||||
reseed.zipfile = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to local .zip file to reseed from.
|
||||
'';
|
||||
};
|
||||
|
||||
reseed.proxy = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
URL for reseed proxy, supports http/socks.
|
||||
'';
|
||||
};
|
||||
|
||||
addressbook.defaulturl = mkOption {
|
||||
type = types.str;
|
||||
default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt";
|
||||
description = ''
|
||||
AddressBook subscription URL for initial setup
|
||||
'';
|
||||
};
|
||||
addressbook.subscriptions = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [
|
||||
"http://inr.i2p/export/alive-hosts.txt"
|
||||
"http://i2p-projekt.i2p/hosts.txt"
|
||||
"http://stats.i2p/cgi-bin/newhosts.txt"
|
||||
];
|
||||
description = ''
|
||||
AddressBook subscription URLs
|
||||
'';
|
||||
};
|
||||
|
||||
trust.enable = mkEnableOption "Explicit trust options";
|
||||
|
||||
trust.family = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Router Familiy to trust for first hops.
|
||||
'';
|
||||
};
|
||||
|
||||
trust.routers = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [];
|
||||
description = ''
|
||||
Only connect to the listed routers.
|
||||
'';
|
||||
};
|
||||
|
||||
trust.hidden = mkEnableOption "Router concealment";
|
||||
|
||||
websocket = mkEndpointOpt "websockets" "127.0.0.1" 7666;
|
||||
|
||||
exploratory.inbound = i2cpOpts "exploratory";
|
||||
exploratory.outbound = i2cpOpts "exploratory";
|
||||
|
||||
ntcp2.enable = mkEnableTrueOption "NTCP2";
|
||||
ntcp2.published = mkEnableOption "NTCP2 publication";
|
||||
ntcp2.port = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = ''
|
||||
Port to listen for incoming NTCP2 connections (0=auto).
|
||||
'';
|
||||
};
|
||||
|
||||
limits.transittunnels = mkOption {
|
||||
type = types.int;
|
||||
default = 2500;
|
||||
description = ''
|
||||
Maximum number of active transit sessions.
|
||||
'';
|
||||
};
|
||||
|
||||
limits.coreSize = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = ''
|
||||
Maximum size of corefile in Kb (0 - use system limit).
|
||||
'';
|
||||
};
|
||||
|
||||
limits.openFiles = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = ''
|
||||
Maximum number of open files (0 - use system default).
|
||||
'';
|
||||
};
|
||||
|
||||
limits.ntcpHard = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = ''
|
||||
Maximum number of active transit sessions.
|
||||
'';
|
||||
};
|
||||
|
||||
limits.ntcpSoft = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = ''
|
||||
Threshold to start probabalistic backoff with ntcp sessions (default: use system limit).
|
||||
'';
|
||||
};
|
||||
|
||||
limits.ntcpThreads = mkOption {
|
||||
type = types.int;
|
||||
default = 1;
|
||||
description = ''
|
||||
Maximum number of threads used by NTCP DH worker.
|
||||
'';
|
||||
};
|
||||
|
||||
proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // {
|
||||
|
||||
auth = mkEnableOption "Webconsole authentication";
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "i2pd";
|
||||
description = ''
|
||||
Username for webconsole access
|
||||
'';
|
||||
};
|
||||
|
||||
pass = mkOption {
|
||||
type = types.str;
|
||||
default = "i2pd";
|
||||
description = ''
|
||||
Password for webconsole access.
|
||||
'';
|
||||
};
|
||||
|
||||
strictHeaders = mkOption {
|
||||
type = with types; nullOr bool;
|
||||
default = null;
|
||||
description = ''
|
||||
Enable strict host checking on WebUI.
|
||||
'';
|
||||
};
|
||||
|
||||
hostname = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Expected hostname for WebUI.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
proto.httpProxy = (mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 "httpproxy-keys.dat")
|
||||
// {
|
||||
outproxy = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = "Upstream outproxy bind address.";
|
||||
};
|
||||
};
|
||||
proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "socksproxy-keys.dat")
|
||||
// {
|
||||
outproxyEnable = mkEnableOption "SOCKS outproxy";
|
||||
outproxy = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
description = "Upstream outproxy bind address.";
|
||||
};
|
||||
outproxyPort = mkOption {
|
||||
type = types.int;
|
||||
default = 4444;
|
||||
description = "Upstream outproxy bind port.";
|
||||
};
|
||||
};
|
||||
|
||||
proto.sam = mkEndpointOpt "sam" "127.0.0.1" 7656;
|
||||
proto.bob = mkEndpointOpt "bob" "127.0.0.1" 2827;
|
||||
proto.i2cp = mkEndpointOpt "i2cp" "127.0.0.1" 7654;
|
||||
proto.i2pControl = mkEndpointOpt "i2pcontrol" "127.0.0.1" 7650;
|
||||
|
||||
outTunnels = mkOption {
|
||||
default = {};
|
||||
type = with types; attrsOf (submodule (
|
||||
{ name, ... }: {
|
||||
options = {
|
||||
destinationPort = mkOption {
|
||||
type = with types; nullOr int;
|
||||
default = null;
|
||||
description = "Connect to particular port at destination.";
|
||||
};
|
||||
} // commonTunOpts name;
|
||||
config = {
|
||||
name = mkDefault name;
|
||||
};
|
||||
}
|
||||
));
|
||||
description = ''
|
||||
Connect to someone as a client and establish a local accept endpoint
|
||||
'';
|
||||
};
|
||||
|
||||
inTunnels = mkOption {
|
||||
default = {};
|
||||
type = with types; attrsOf (submodule (
|
||||
{ name, ... }: {
|
||||
options = {
|
||||
inPort = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = "Service port. Default to the tunnel's listen port.";
|
||||
};
|
||||
accessList = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [];
|
||||
description = "I2P nodes that are allowed to connect to this service.";
|
||||
};
|
||||
} // commonTunOpts name;
|
||||
config = {
|
||||
name = mkDefault name;
|
||||
};
|
||||
}
|
||||
));
|
||||
description = ''
|
||||
Serve something on I2P network at port and delegate requests to address inPort.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.users.i2pd = {
|
||||
group = "i2pd";
|
||||
description = "I2Pd User";
|
||||
home = homeDir;
|
||||
createHome = true;
|
||||
uid = config.ids.uids.i2pd;
|
||||
};
|
||||
|
||||
users.groups.i2pd.gid = config.ids.gids.i2pd;
|
||||
|
||||
systemd.services.i2pd = {
|
||||
description = "Minimal I2P router";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig =
|
||||
{
|
||||
User = "i2pd";
|
||||
WorkingDirectory = homeDir;
|
||||
Restart = "on-abort";
|
||||
ExecStart = "${cfg.package}/bin/i2pd ${i2pdFlags}";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
155
nixos/modules/services/networking/icecream/daemon.nix
Normal file
155
nixos/modules/services/networking/icecream/daemon.nix
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.icecream.daemon;
|
||||
in {
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.icecream.daemon = {
|
||||
|
||||
enable = mkEnableOption "Icecream Daemon";
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to automatically open receive port in the firewall.
|
||||
'';
|
||||
};
|
||||
|
||||
openBroadcast = mkOption {
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to automatically open the firewall for scheduler discovery.
|
||||
'';
|
||||
};
|
||||
|
||||
cacheLimit = mkOption {
|
||||
type = types.ints.u16;
|
||||
default = 256;
|
||||
description = ''
|
||||
Maximum size in Megabytes of cache used to store compile environments of compile clients.
|
||||
'';
|
||||
};
|
||||
|
||||
netName = mkOption {
|
||||
type = types.str;
|
||||
default = "ICECREAM";
|
||||
description = ''
|
||||
Network name to connect to. A scheduler with the same name needs to be running.
|
||||
'';
|
||||
};
|
||||
|
||||
noRemote = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Prevent jobs from other nodes being scheduled on this daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
schedulerHost = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Explicit scheduler hostname, useful in firewalled environments.
|
||||
|
||||
Uses scheduler autodiscovery via broadcast if set to null.
|
||||
'';
|
||||
};
|
||||
|
||||
maxProcesses = mkOption {
|
||||
type = types.nullOr types.ints.u16;
|
||||
default = null;
|
||||
description = ''
|
||||
Maximum number of compile jobs started in parallel for this daemon.
|
||||
|
||||
Uses the number of CPUs if set to null.
|
||||
'';
|
||||
};
|
||||
|
||||
nice = mkOption {
|
||||
type = types.int;
|
||||
default = 5;
|
||||
description = ''
|
||||
The level of niceness to use.
|
||||
'';
|
||||
};
|
||||
|
||||
hostname = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Hostname of the daemon in the icecream infrastructure.
|
||||
|
||||
Uses the hostname retrieved via uname if set to null.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "icecc";
|
||||
description = ''
|
||||
User to run the icecream daemon as. Set to root to enable receive of
|
||||
remote compile environments.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
default = pkgs.icecream;
|
||||
defaultText = literalExpression "pkgs.icecream";
|
||||
type = types.package;
|
||||
description = "Icecream package to use.";
|
||||
};
|
||||
|
||||
extraArgs = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "Additional command line parameters.";
|
||||
example = [ "-v" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ 10245 ];
|
||||
networking.firewall.allowedUDPPorts = mkIf cfg.openBroadcast [ 8765 ];
|
||||
|
||||
systemd.services.icecc-daemon = {
|
||||
description = "Icecream compile daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = escapeShellArgs ([
|
||||
"${getBin cfg.package}/bin/iceccd"
|
||||
"-b" "$STATE_DIRECTORY"
|
||||
"-u" "icecc"
|
||||
(toString cfg.nice)
|
||||
]
|
||||
++ optionals (cfg.schedulerHost != null) ["-s" cfg.schedulerHost]
|
||||
++ optionals (cfg.netName != null) [ "-n" cfg.netName ]
|
||||
++ optionals (cfg.cacheLimit != null) [ "--cache-limit" (toString cfg.cacheLimit) ]
|
||||
++ optionals (cfg.maxProcesses != null) [ "-m" (toString cfg.maxProcesses) ]
|
||||
++ optionals (cfg.hostname != null) [ "-N" (cfg.hostname) ]
|
||||
++ optional cfg.noRemote "--no-remote"
|
||||
++ cfg.extraArgs);
|
||||
DynamicUser = true;
|
||||
User = "icecc";
|
||||
Group = "icecc";
|
||||
StateDirectory = "icecc";
|
||||
RuntimeDirectory = "icecc";
|
||||
AmbientCapabilities = "CAP_SYS_CHROOT";
|
||||
CapabilityBoundingSet = "CAP_SYS_CHROOT";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ emantor ];
|
||||
}
|
||||
101
nixos/modules/services/networking/icecream/scheduler.nix
Normal file
101
nixos/modules/services/networking/icecream/scheduler.nix
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.icecream.scheduler;
|
||||
in {
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.icecream.scheduler = {
|
||||
enable = mkEnableOption "Icecream Scheduler";
|
||||
|
||||
netName = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Network name for the icecream scheduler.
|
||||
|
||||
Uses the default ICECREAM if null.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8765;
|
||||
description = ''
|
||||
Server port to listen for icecream daemon requests.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to automatically open the daemon port in the firewall.
|
||||
'';
|
||||
};
|
||||
|
||||
openTelnet = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to open the telnet TCP port on 8766.
|
||||
'';
|
||||
};
|
||||
|
||||
persistentClientConnection = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to prevent clients from connecting to a better scheduler.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
default = pkgs.icecream;
|
||||
defaultText = literalExpression "pkgs.icecream";
|
||||
type = types.package;
|
||||
description = "Icecream package to use.";
|
||||
};
|
||||
|
||||
extraArgs = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "Additional command line parameters";
|
||||
example = [ "-v" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
networking.firewall.allowedTCPPorts = mkMerge [
|
||||
(mkIf cfg.openFirewall [ cfg.port ])
|
||||
(mkIf cfg.openTelnet [ 8766 ])
|
||||
];
|
||||
|
||||
systemd.services.icecc-scheduler = {
|
||||
description = "Icecream scheduling server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = escapeShellArgs ([
|
||||
"${getBin cfg.package}/bin/icecc-scheduler"
|
||||
"-p" (toString cfg.port)
|
||||
]
|
||||
++ optionals (cfg.netName != null) [ "-n" (toString cfg.netName) ]
|
||||
++ optional cfg.persistentClientConnection "-r"
|
||||
++ cfg.extraArgs);
|
||||
|
||||
DynamicUser = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ emantor ];
|
||||
}
|
||||
62
nixos/modules/services/networking/inspircd.nix
Normal file
62
nixos/modules/services/networking/inspircd.nix
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.inspircd;
|
||||
|
||||
configFile = pkgs.writeText "inspircd.conf" cfg.config;
|
||||
|
||||
in {
|
||||
meta = {
|
||||
maintainers = [ lib.maintainers.sternenseemann ];
|
||||
};
|
||||
|
||||
options = {
|
||||
services.inspircd = {
|
||||
enable = lib.mkEnableOption "InspIRCd";
|
||||
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = pkgs.inspircd;
|
||||
defaultText = lib.literalExpression "pkgs.inspircd";
|
||||
example = lib.literalExpression "pkgs.inspircdMinimal";
|
||||
description = ''
|
||||
The InspIRCd package to use. This is mainly useful
|
||||
to specify an overridden version of the
|
||||
<literal>pkgs.inspircd</literal> dervivation, for
|
||||
example if you want to use a more minimal InspIRCd
|
||||
distribution with less modules enabled or with
|
||||
modules enabled which can't be distributed in binary
|
||||
form due to licensing issues.
|
||||
'';
|
||||
};
|
||||
|
||||
config = lib.mkOption {
|
||||
type = lib.types.lines;
|
||||
description = ''
|
||||
Verbatim <literal>inspircd.conf</literal> file.
|
||||
For a list of options, consult the
|
||||
<link xlink:href="https://docs.inspircd.org/3/configuration/">InspIRCd documentation</link>, the
|
||||
<link xlink:href="https://docs.inspircd.org/3/modules/">Module documentation</link>
|
||||
and the example configuration files distributed
|
||||
with <literal>pkgs.inspircd.doc</literal>
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.services.inspircd = {
|
||||
description = "InspIRCd - the stable, high-performance and modular Internet Relay Chat Daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
requires = [ "network.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = ''
|
||||
${lib.getBin cfg.package}/bin/inspircd start --config ${configFile} --nofork --nopid
|
||||
'';
|
||||
DynamicUser = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
198
nixos/modules/services/networking/iodine.nix
Normal file
198
nixos/modules/services/networking/iodine.nix
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
# NixOS module for iodine, ip over dns daemon
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.iodine;
|
||||
|
||||
iodinedUser = "iodined";
|
||||
|
||||
/* is this path made unreadable by ProtectHome = true ? */
|
||||
isProtected = x: hasPrefix "/root" x || hasPrefix "/home" x;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "services" "iodined" "enable" ] [ "services" "iodine" "server" "enable" ])
|
||||
(mkRenamedOptionModule [ "services" "iodined" "domain" ] [ "services" "iodine" "server" "domain" ])
|
||||
(mkRenamedOptionModule [ "services" "iodined" "ip" ] [ "services" "iodine" "server" "ip" ])
|
||||
(mkRenamedOptionModule [ "services" "iodined" "extraConfig" ] [ "services" "iodine" "server" "extraConfig" ])
|
||||
(mkRemovedOptionModule [ "services" "iodined" "client" ] "")
|
||||
];
|
||||
|
||||
### configuration
|
||||
|
||||
options = {
|
||||
|
||||
services.iodine = {
|
||||
clients = mkOption {
|
||||
default = {};
|
||||
description = ''
|
||||
Each attribute of this option defines a systemd service that
|
||||
runs iodine. Many or none may be defined.
|
||||
The name of each service is
|
||||
<literal>iodine-<replaceable>name</replaceable></literal>
|
||||
where <replaceable>name</replaceable> is the name of the
|
||||
corresponding attribute name.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
foo = {
|
||||
server = "tunnel.mdomain.com";
|
||||
relay = "8.8.8.8";
|
||||
extraConfig = "-v";
|
||||
}
|
||||
}
|
||||
'';
|
||||
type = types.attrsOf (
|
||||
types.submodule (
|
||||
{
|
||||
options = {
|
||||
server = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Hostname of server running iodined";
|
||||
example = "tunnel.mydomain.com";
|
||||
};
|
||||
|
||||
relay = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "DNS server to use as an intermediate relay to the iodined server";
|
||||
example = "8.8.8.8";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Additional command line parameters";
|
||||
example = "-l 192.168.1.10 -p 23";
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Path to a file containing the password.";
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
server = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "enable iodined server";
|
||||
};
|
||||
|
||||
ip = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "The assigned ip address or ip range";
|
||||
example = "172.16.10.1/24";
|
||||
};
|
||||
|
||||
domain = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Domain or subdomain of which nameservers point to us";
|
||||
example = "tunnel.mydomain.com";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Additional command line parameters";
|
||||
example = "-l 192.168.1.10 -p 23";
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "File that contains password";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
### implementation
|
||||
|
||||
config = mkIf (cfg.server.enable || cfg.clients != {}) {
|
||||
environment.systemPackages = [ pkgs.iodine ];
|
||||
boot.kernelModules = [ "tun" ];
|
||||
|
||||
systemd.services =
|
||||
let
|
||||
createIodineClientService = name: cfg:
|
||||
{
|
||||
description = "iodine client - ${name}";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
script = "exec ${pkgs.iodine}/bin/iodine -f -u ${iodinedUser} ${cfg.extraConfig} ${optionalString (cfg.passwordFile != "") "< \"${builtins.toString cfg.passwordFile}\""} ${cfg.relay} ${cfg.server}";
|
||||
serviceConfig = {
|
||||
RestartSec = "30s";
|
||||
Restart = "always";
|
||||
|
||||
# hardening :
|
||||
# Filesystem access
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = if isProtected cfg.passwordFile then "read-only" else "true" ;
|
||||
PrivateTmp = true;
|
||||
ReadWritePaths = "/dev/net/tun";
|
||||
PrivateDevices = false;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
# Caps
|
||||
NoNewPrivileges = true;
|
||||
# Misc.
|
||||
LockPersonality = true;
|
||||
RestrictRealtime = true;
|
||||
PrivateMounts = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
};
|
||||
};
|
||||
in
|
||||
listToAttrs (
|
||||
mapAttrsToList
|
||||
(name: value: nameValuePair "iodine-${name}" (createIodineClientService name value))
|
||||
cfg.clients
|
||||
) // {
|
||||
iodined = mkIf (cfg.server.enable) {
|
||||
description = "iodine, ip over dns server daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
script = "exec ${pkgs.iodine}/bin/iodined -f -u ${iodinedUser} ${cfg.server.extraConfig} ${optionalString (cfg.server.passwordFile != "") "< \"${builtins.toString cfg.server.passwordFile}\""} ${cfg.server.ip} ${cfg.server.domain}";
|
||||
serviceConfig = {
|
||||
# Filesystem access
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = if isProtected cfg.server.passwordFile then "read-only" else "true" ;
|
||||
PrivateTmp = true;
|
||||
ReadWritePaths = "/dev/net/tun";
|
||||
PrivateDevices = false;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
# Caps
|
||||
NoNewPrivileges = true;
|
||||
# Misc.
|
||||
LockPersonality = true;
|
||||
RestrictRealtime = true;
|
||||
PrivateMounts = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
users.users.${iodinedUser} = {
|
||||
uid = config.ids.uids.iodined;
|
||||
group = "iodined";
|
||||
description = "Iodine daemon user";
|
||||
};
|
||||
users.groups.iodined.gid = config.ids.gids.iodined;
|
||||
};
|
||||
}
|
||||
97
nixos/modules/services/networking/iperf3.nix
Normal file
97
nixos/modules/services/networking/iperf3.nix
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
{ config, lib, pkgs, ... }: with lib;
|
||||
let
|
||||
cfg = config.services.iperf3;
|
||||
|
||||
api = {
|
||||
enable = mkEnableOption "iperf3 network throughput testing server";
|
||||
port = mkOption {
|
||||
type = types.ints.u16;
|
||||
default = 5201;
|
||||
description = "Server port to listen on for iperf3 client requsts.";
|
||||
};
|
||||
affinity = mkOption {
|
||||
type = types.nullOr types.ints.unsigned;
|
||||
default = null;
|
||||
description = "CPU affinity for the process.";
|
||||
};
|
||||
bind = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Bind to the specific interface associated with the given address.";
|
||||
};
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Open ports in the firewall for iperf3.";
|
||||
};
|
||||
verbose = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Give more detailed output.";
|
||||
};
|
||||
forceFlush = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Force flushing output at every interval.";
|
||||
};
|
||||
debug = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Emit debugging output.";
|
||||
};
|
||||
rsaPrivateKey = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = "Path to the RSA private key (not password-protected) used to decrypt authentication credentials from the client.";
|
||||
};
|
||||
authorizedUsersFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = "Path to the configuration file containing authorized users credentials to run iperf tests.";
|
||||
};
|
||||
extraFlags = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = "Extra flags to pass to iperf3(1).";
|
||||
};
|
||||
};
|
||||
|
||||
imp = {
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.port ];
|
||||
};
|
||||
|
||||
systemd.services.iperf3 = {
|
||||
description = "iperf3 daemon";
|
||||
unitConfig.Documentation = "man:iperf3(1) https://iperf.fr/iperf-doc.php";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Restart = "on-failure";
|
||||
RestartSec = 2;
|
||||
DynamicUser = true;
|
||||
PrivateDevices = true;
|
||||
CapabilityBoundingSet = "";
|
||||
NoNewPrivileges = true;
|
||||
ExecStart = ''
|
||||
${pkgs.iperf3}/bin/iperf \
|
||||
--server \
|
||||
--port ${toString cfg.port} \
|
||||
${optionalString (cfg.affinity != null) "--affinity ${toString cfg.affinity}"} \
|
||||
${optionalString (cfg.bind != null) "--bind ${cfg.bind}"} \
|
||||
${optionalString (cfg.rsaPrivateKey != null) "--rsa-private-key-path ${cfg.rsaPrivateKey}"} \
|
||||
${optionalString (cfg.authorizedUsersFile != null) "--authorized-users-path ${cfg.authorizedUsersFile}"} \
|
||||
${optionalString cfg.verbose "--verbose"} \
|
||||
${optionalString cfg.debug "--debug"} \
|
||||
${optionalString cfg.forceFlush "--forceflush"} \
|
||||
${escapeShellArgs cfg.extraFlags}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
in {
|
||||
options.services.iperf3 = api;
|
||||
config = mkIf cfg.enable imp;
|
||||
}
|
||||
31
nixos/modules/services/networking/ircd-hybrid/builder.sh
Normal file
31
nixos/modules/services/networking/ircd-hybrid/builder.sh
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
source $stdenv/setup
|
||||
|
||||
doSub() {
|
||||
local src=$1
|
||||
local dst=$2
|
||||
mkdir -p $(dirname $dst)
|
||||
substituteAll $src $dst
|
||||
}
|
||||
|
||||
subDir=/
|
||||
for i in $scripts; do
|
||||
if test "$(echo $i | cut -c1-2)" = "=>"; then
|
||||
subDir=$(echo $i | cut -c3-)
|
||||
else
|
||||
dst=$out/$subDir/$(stripHash $i | sed 's/\.in//')
|
||||
doSub $i $dst
|
||||
chmod +x $dst # !!!
|
||||
fi
|
||||
done
|
||||
|
||||
subDir=/
|
||||
for i in $substFiles; do
|
||||
if test "$(echo $i | cut -c1-2)" = "=>"; then
|
||||
subDir=$(echo $i | cut -c3-)
|
||||
else
|
||||
dst=$out/$subDir/$(stripHash $i | sed 's/\.in//')
|
||||
doSub $i $dst
|
||||
fi
|
||||
done
|
||||
|
||||
mkdir -p $out/bin
|
||||
26
nixos/modules/services/networking/ircd-hybrid/control.in
Normal file
26
nixos/modules/services/networking/ircd-hybrid/control.in
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#! @shell@ -e
|
||||
|
||||
# Make sure that the environment is deterministic.
|
||||
export PATH=@coreutils@/bin
|
||||
|
||||
if test "$1" = "start"; then
|
||||
if ! @procps@/bin/pgrep ircd; then
|
||||
if @ipv6Enabled@; then
|
||||
while ! @iproute@/sbin/ip addr |
|
||||
@gnugrep@/bin/grep inet6 |
|
||||
@gnugrep@/bin/grep global; do
|
||||
sleep 1;
|
||||
done;
|
||||
fi;
|
||||
rm -rf /home/ircd
|
||||
mkdir -p /home/ircd
|
||||
chown ircd: /home/ircd
|
||||
cd /home/ircd
|
||||
env - HOME=/homeless-shelter $extraEnv \
|
||||
@su@/bin/su ircd --shell=/bin/sh -c ' @ircdHybrid@/bin/ircd -configfile @out@/conf/ircd.conf </dev/null -logfile /home/ircd/ircd.log' 2>&1 >/var/log/ircd-hybrid.out
|
||||
fi;
|
||||
fi
|
||||
|
||||
if test "$1" = "stop" ; then
|
||||
@procps@/bin/pkill ircd;
|
||||
fi;
|
||||
133
nixos/modules/services/networking/ircd-hybrid/default.nix
Normal file
133
nixos/modules/services/networking/ircd-hybrid/default.nix
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.ircdHybrid;
|
||||
|
||||
ircdService = pkgs.stdenv.mkDerivation rec {
|
||||
name = "ircd-hybrid-service";
|
||||
scripts = [ "=>/bin" ./control.in ];
|
||||
substFiles = [ "=>/conf" ./ircd.conf ];
|
||||
inherit (pkgs) ircdHybrid coreutils su iproute2 gnugrep procps;
|
||||
|
||||
ipv6Enabled = boolToString config.networking.enableIPv6;
|
||||
|
||||
inherit (cfg) serverName sid description adminEmail
|
||||
extraPort;
|
||||
|
||||
cryptoSettings =
|
||||
(optionalString (cfg.rsaKey != null) "rsa_private_key_file = \"${cfg.rsaKey}\";\n") +
|
||||
(optionalString (cfg.certificate != null) "ssl_certificate_file = \"${cfg.certificate}\";\n");
|
||||
|
||||
extraListen = map (ip: "host = \""+ip+"\";\nport = 6665 .. 6669, "+extraPort+"; ") cfg.extraIPs;
|
||||
|
||||
builder = ./builder.sh;
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.ircdHybrid = {
|
||||
|
||||
enable = mkEnableOption "IRCD";
|
||||
|
||||
serverName = mkOption {
|
||||
default = "hades.arpa";
|
||||
type = types.str;
|
||||
description = "
|
||||
IRCD server name.
|
||||
";
|
||||
};
|
||||
|
||||
sid = mkOption {
|
||||
default = "0NL";
|
||||
type = types.str;
|
||||
description = "
|
||||
IRCD server unique ID in a net of servers.
|
||||
";
|
||||
};
|
||||
|
||||
description = mkOption {
|
||||
default = "Hybrid-7 IRC server.";
|
||||
type = types.str;
|
||||
description = "
|
||||
IRCD server description.
|
||||
";
|
||||
};
|
||||
|
||||
rsaKey = mkOption {
|
||||
default = null;
|
||||
example = literalExpression "/root/certificates/irc.key";
|
||||
type = types.nullOr types.path;
|
||||
description = "
|
||||
IRCD server RSA key.
|
||||
";
|
||||
};
|
||||
|
||||
certificate = mkOption {
|
||||
default = null;
|
||||
example = literalExpression "/root/certificates/irc.pem";
|
||||
type = types.nullOr types.path;
|
||||
description = "
|
||||
IRCD server SSL certificate. There are some limitations - read manual.
|
||||
";
|
||||
};
|
||||
|
||||
adminEmail = mkOption {
|
||||
default = "<bit-bucket@example.com>";
|
||||
type = types.str;
|
||||
example = "<name@domain.tld>";
|
||||
description = "
|
||||
IRCD server administrator e-mail.
|
||||
";
|
||||
};
|
||||
|
||||
extraIPs = mkOption {
|
||||
default = [];
|
||||
example = ["127.0.0.1"];
|
||||
type = types.listOf types.str;
|
||||
description = "
|
||||
Extra IP's to bind.
|
||||
";
|
||||
};
|
||||
|
||||
extraPort = mkOption {
|
||||
default = "7117";
|
||||
type = types.str;
|
||||
description = "
|
||||
Extra port to avoid filtering.
|
||||
";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf config.services.ircdHybrid.enable {
|
||||
|
||||
users.users.ircd =
|
||||
{ description = "IRCD owner";
|
||||
group = "ircd";
|
||||
uid = config.ids.uids.ircd;
|
||||
};
|
||||
|
||||
users.groups.ircd.gid = config.ids.gids.ircd;
|
||||
|
||||
systemd.services.ircd-hybrid = {
|
||||
description = "IRCD Hybrid server";
|
||||
after = [ "started networking" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
script = "${ircdService}/bin/control start";
|
||||
};
|
||||
};
|
||||
}
|
||||
1051
nixos/modules/services/networking/ircd-hybrid/ircd.conf
Normal file
1051
nixos/modules/services/networking/ircd-hybrid/ircd.conf
Normal file
File diff suppressed because it is too large
Load diff
84
nixos/modules/services/networking/iscsi/initiator.nix
Normal file
84
nixos/modules/services/networking/iscsi/initiator.nix
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
{ config, lib, pkgs, ... }: with lib;
|
||||
let
|
||||
cfg = config.services.openiscsi;
|
||||
in
|
||||
{
|
||||
options.services.openiscsi = with types; {
|
||||
enable = mkEnableOption "the openiscsi iscsi daemon";
|
||||
enableAutoLoginOut = mkEnableOption ''
|
||||
automatic login and logout of all automatic targets.
|
||||
You probably do not want this.
|
||||
'';
|
||||
discoverPortal = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
description = "Portal to discover targets on";
|
||||
};
|
||||
name = mkOption {
|
||||
type = str;
|
||||
description = "Name of this iscsi initiator";
|
||||
example = "iqn.2020-08.org.linux-iscsi.initiatorhost:example";
|
||||
};
|
||||
package = mkOption {
|
||||
type = package;
|
||||
description = "openiscsi package to use";
|
||||
default = pkgs.openiscsi;
|
||||
defaultText = literalExpression "pkgs.openiscsi";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = str;
|
||||
default = "";
|
||||
description = "Lines to append to default iscsid.conf";
|
||||
};
|
||||
|
||||
extraConfigFile = mkOption {
|
||||
description = ''
|
||||
Append an additional file's contents to /etc/iscsid.conf. Use a non-store path
|
||||
and store passwords in this file.
|
||||
'';
|
||||
default = null;
|
||||
type = nullOr str;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.etc."iscsi/iscsid.conf.fragment".source = pkgs.runCommand "iscsid.conf" {} ''
|
||||
cat "${cfg.package}/etc/iscsi/iscsid.conf" > $out
|
||||
cat << 'EOF' >> $out
|
||||
${cfg.extraConfig}
|
||||
${optionalString cfg.enableAutoLoginOut "node.startup = automatic"}
|
||||
EOF
|
||||
'';
|
||||
environment.etc."iscsi/initiatorname.iscsi".text = "InitiatorName=${cfg.name}";
|
||||
|
||||
system.activationScripts.iscsid = let
|
||||
extraCfgDumper = optionalString (cfg.extraConfigFile != null) ''
|
||||
if [ -f "${cfg.extraConfigFile}" ]; then
|
||||
printf "\n# The following is from ${cfg.extraConfigFile}:\n"
|
||||
cat "${cfg.extraConfigFile}"
|
||||
else
|
||||
echo "Warning: services.openiscsi.extraConfigFile ${cfg.extraConfigFile} does not exist!" >&2
|
||||
fi
|
||||
'';
|
||||
in ''
|
||||
(
|
||||
cat ${config.environment.etc."iscsi/iscsid.conf.fragment".source}
|
||||
${extraCfgDumper}
|
||||
) > /etc/iscsi/iscsid.conf
|
||||
'';
|
||||
|
||||
systemd.packages = [ cfg.package ];
|
||||
|
||||
systemd.services."iscsid".wantedBy = [ "multi-user.target" ];
|
||||
systemd.sockets."iscsid".wantedBy = [ "sockets.target" ];
|
||||
|
||||
systemd.services."iscsi" = mkIf cfg.enableAutoLoginOut {
|
||||
wantedBy = [ "remote-fs.target" ];
|
||||
serviceConfig.ExecStartPre = mkIf (cfg.discoverPortal != null) "${cfg.package}/bin/iscsiadm --mode discoverydb --type sendtargets --portal ${escapeShellArg cfg.discoverPortal} --discover";
|
||||
};
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
boot.kernelModules = [ "iscsi_tcp" ];
|
||||
};
|
||||
}
|
||||
190
nixos/modules/services/networking/iscsi/root-initiator.nix
Normal file
190
nixos/modules/services/networking/iscsi/root-initiator.nix
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
{ config, lib, pkgs, ... }: with lib;
|
||||
let
|
||||
cfg = config.boot.iscsi-initiator;
|
||||
in
|
||||
{
|
||||
# If you're booting entirely off another machine you may want to add
|
||||
# this snippet to always boot the latest "system" version. It is not
|
||||
# enabled by default in case you have an initrd on a local disk:
|
||||
#
|
||||
# boot.initrd.postMountCommands = ''
|
||||
# ln -sfn /nix/var/nix/profiles/system/init /mnt-root/init
|
||||
# stage2Init=/init
|
||||
# '';
|
||||
#
|
||||
# Note: Theoretically you might want to connect to multiple portals and
|
||||
# log in to multiple targets, however the authors of this module so far
|
||||
# don't have the need or expertise to reasonably implement it. Also,
|
||||
# consider carefully before making your boot chain depend on multiple
|
||||
# machines to be up.
|
||||
options.boot.iscsi-initiator = with types; {
|
||||
name = mkOption {
|
||||
description = ''
|
||||
Name of the iSCSI initiator to boot from. Note, booting from iscsi
|
||||
requires networkd based networking.
|
||||
'';
|
||||
default = null;
|
||||
example = "iqn.2020-08.org.linux-iscsi.initiatorhost:example";
|
||||
type = nullOr str;
|
||||
};
|
||||
|
||||
discoverPortal = mkOption {
|
||||
description = ''
|
||||
iSCSI portal to boot from.
|
||||
'';
|
||||
default = null;
|
||||
example = "192.168.1.1:3260";
|
||||
type = nullOr str;
|
||||
};
|
||||
|
||||
target = mkOption {
|
||||
description = ''
|
||||
Name of the iSCSI target to boot from.
|
||||
'';
|
||||
default = null;
|
||||
example = "iqn.2020-08.org.linux-iscsi.targethost:example";
|
||||
type = nullOr str;
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
description = ''
|
||||
Higher numbers elicits more logs.
|
||||
'';
|
||||
default = 1;
|
||||
example = 8;
|
||||
type = int;
|
||||
};
|
||||
|
||||
loginAll = mkOption {
|
||||
description = ''
|
||||
Do not log into a specific target on the portal, but to all that we discover.
|
||||
This overrides setting target.
|
||||
'';
|
||||
type = bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
extraIscsiCommands = mkOption {
|
||||
description = "Extra iscsi commands to run in the initrd.";
|
||||
default = "";
|
||||
type = lines;
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
description = "Extra lines to append to /etc/iscsid.conf";
|
||||
default = null;
|
||||
type = nullOr lines;
|
||||
};
|
||||
|
||||
extraConfigFile = mkOption {
|
||||
description = ''
|
||||
Append an additional file's contents to `/etc/iscsid.conf`. Use a non-store path
|
||||
and store passwords in this file. Note: the file specified here must be available
|
||||
in the initrd, see: `boot.initrd.secrets`.
|
||||
'';
|
||||
default = null;
|
||||
type = nullOr str;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (cfg.name != null) {
|
||||
# The "scripted" networking configuration (ie: non-networkd)
|
||||
# doesn't properly order the start and stop of the interfaces, and the
|
||||
# network interfaces are torn down before unmounting disks. Since this
|
||||
# module is specifically for very-early-boot network mounts, we need
|
||||
# the network to stay on.
|
||||
#
|
||||
# We could probably fix the scripted options to properly order, but I'm
|
||||
# not inclined to invest that time today. Hopefully this gets users far
|
||||
# enough along and they can just use networkd.
|
||||
networking.useNetworkd = true;
|
||||
networking.useDHCP = false; # Required to set useNetworkd = true
|
||||
|
||||
boot.initrd = {
|
||||
network.enable = true;
|
||||
|
||||
# By default, the stage-1 disables the network and resets the interfaces
|
||||
# on startup. Since our startup disks are on the network, we can't let
|
||||
# the network not work.
|
||||
network.flushBeforeStage2 = false;
|
||||
|
||||
kernelModules = [ "iscsi_tcp" ];
|
||||
|
||||
extraUtilsCommands = ''
|
||||
copy_bin_and_libs ${pkgs.openiscsi}/bin/iscsid
|
||||
copy_bin_and_libs ${pkgs.openiscsi}/bin/iscsiadm
|
||||
${optionalString (!config.boot.initrd.network.ssh.enable) "cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib"}
|
||||
|
||||
mkdir -p $out/etc/iscsi
|
||||
cp ${config.environment.etc.hosts.source} $out/etc/hosts
|
||||
cp ${pkgs.openiscsi}/etc/iscsi/iscsid.conf $out/etc/iscsi/iscsid.fragment.conf
|
||||
chmod +w $out/etc/iscsi/iscsid.fragment.conf
|
||||
cat << 'EOF' >> $out/etc/iscsi/iscsid.fragment.conf
|
||||
${optionalString (cfg.extraConfig != null) cfg.extraConfig}
|
||||
EOF
|
||||
'';
|
||||
|
||||
extraUtilsCommandsTest = ''
|
||||
$out/bin/iscsiadm --version
|
||||
'';
|
||||
|
||||
preLVMCommands = let
|
||||
extraCfgDumper = optionalString (cfg.extraConfigFile != null) ''
|
||||
if [ -f "${cfg.extraConfigFile}" ]; then
|
||||
printf "\n# The following is from ${cfg.extraConfigFile}:\n"
|
||||
cat "${cfg.extraConfigFile}"
|
||||
else
|
||||
echo "Warning: boot.iscsi-initiator.extraConfigFile ${cfg.extraConfigFile} does not exist!" >&2
|
||||
fi
|
||||
'';
|
||||
in ''
|
||||
${optionalString (!config.boot.initrd.network.ssh.enable) ''
|
||||
# stolen from initrd-ssh.nix
|
||||
echo 'root:x:0:0:root:/root:/bin/ash' > /etc/passwd
|
||||
echo 'passwd: files' > /etc/nsswitch.conf
|
||||
''}
|
||||
|
||||
cp -f $extraUtils/etc/hosts /etc/hosts
|
||||
|
||||
mkdir -p /etc/iscsi /run/lock/iscsi
|
||||
echo "InitiatorName=${cfg.name}" > /etc/iscsi/initiatorname.iscsi
|
||||
|
||||
(
|
||||
cat "$extraUtils/etc/iscsi/iscsid.fragment.conf"
|
||||
printf "\n"
|
||||
${optionalString cfg.loginAll ''echo "node.startup = automatic"''}
|
||||
${extraCfgDumper}
|
||||
) > /etc/iscsi/iscsid.conf
|
||||
|
||||
iscsid --foreground --no-pid-file --debug ${toString cfg.logLevel} &
|
||||
iscsiadm --mode discoverydb \
|
||||
--type sendtargets \
|
||||
--discover \
|
||||
--portal ${escapeShellArg cfg.discoverPortal} \
|
||||
--debug ${toString cfg.logLevel}
|
||||
|
||||
${if cfg.loginAll then ''
|
||||
iscsiadm --mode node --loginall all
|
||||
'' else ''
|
||||
iscsiadm --mode node --targetname ${escapeShellArg cfg.target} --login
|
||||
''}
|
||||
|
||||
${cfg.extraIscsiCommands}
|
||||
|
||||
pkill -9 iscsid
|
||||
'';
|
||||
};
|
||||
|
||||
services.openiscsi = {
|
||||
enable = true;
|
||||
inherit (cfg) name;
|
||||
};
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.loginAll -> cfg.target == null;
|
||||
message = "iSCSI target name is set while login on all portals is enabled.";
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
53
nixos/modules/services/networking/iscsi/target.nix
Normal file
53
nixos/modules/services/networking/iscsi/target.nix
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.target;
|
||||
in
|
||||
{
|
||||
###### interface
|
||||
options = {
|
||||
services.target = with types; {
|
||||
enable = mkEnableOption "the kernel's LIO iscsi target";
|
||||
|
||||
config = mkOption {
|
||||
type = attrs;
|
||||
default = {};
|
||||
description = ''
|
||||
Content of /etc/target/saveconfig.json
|
||||
This file is normally read and written by targetcli
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
config = mkIf cfg.enable {
|
||||
environment.etc."target/saveconfig.json" = {
|
||||
text = builtins.toJSON cfg.config;
|
||||
mode = "0600";
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [ targetcli ];
|
||||
|
||||
boot.kernelModules = [ "configfs" "target_core_mod" "iscsi_target_mod" ];
|
||||
|
||||
systemd.services.iscsi-target = {
|
||||
enable = true;
|
||||
after = [ "network.target" "local-fs.target" ];
|
||||
requires = [ "sys-kernel-config.mount" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${pkgs.python3.pkgs.rtslib}/bin/targetctl restore";
|
||||
ExecStop = "${pkgs.python3.pkgs.rtslib}/bin/targetctl clear";
|
||||
RemainAfterExit = "yes";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /etc/target 0700 root root - -"
|
||||
];
|
||||
};
|
||||
}
|
||||
71
nixos/modules/services/networking/iwd.nix
Normal file
71
nixos/modules/services/networking/iwd.nix
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (lib)
|
||||
mkEnableOption mkIf mkOption types
|
||||
recursiveUpdate;
|
||||
|
||||
cfg = config.networking.wireless.iwd;
|
||||
ini = pkgs.formats.ini { };
|
||||
defaults = {
|
||||
# without UseDefaultInterface, sometimes wlan0 simply goes AWOL with NetworkManager
|
||||
# https://iwd.wiki.kernel.org/interface_lifecycle#interface_management_in_iwd
|
||||
General.UseDefaultInterface = with config.networking.networkmanager; (enable && (wifi.backend == "iwd"));
|
||||
};
|
||||
configFile = ini.generate "main.conf" (recursiveUpdate defaults cfg.settings);
|
||||
|
||||
in
|
||||
{
|
||||
options.networking.wireless.iwd = {
|
||||
enable = mkEnableOption "iwd";
|
||||
|
||||
settings = mkOption {
|
||||
type = ini.type;
|
||||
default = { };
|
||||
|
||||
example = {
|
||||
Settings.AutoConnect = true;
|
||||
|
||||
Network = {
|
||||
EnableIPv6 = true;
|
||||
RoutePriorityOffset = 300;
|
||||
};
|
||||
};
|
||||
|
||||
description = ''
|
||||
Options passed to iwd.
|
||||
See <link xlink:href="https://iwd.wiki.kernel.org/networkconfigurationsettings">here</link> for supported options.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [{
|
||||
assertion = !config.networking.wireless.enable;
|
||||
message = ''
|
||||
Only one wireless daemon is allowed at the time: networking.wireless.enable and networking.wireless.iwd.enable are mutually exclusive.
|
||||
'';
|
||||
}];
|
||||
|
||||
environment.etc."iwd/${configFile.name}".source = configFile;
|
||||
|
||||
# for iwctl
|
||||
environment.systemPackages = [ pkgs.iwd ];
|
||||
|
||||
services.dbus.packages = [ pkgs.iwd ];
|
||||
|
||||
systemd.packages = [ pkgs.iwd ];
|
||||
|
||||
systemd.network.links."80-iwd" = {
|
||||
matchConfig.Type = "wlan";
|
||||
linkConfig.NamePolicy = "keep kernel";
|
||||
};
|
||||
|
||||
systemd.services.iwd = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
restartTriggers = [ configFile ];
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ mic92 dtzWill ];
|
||||
}
|
||||
417
nixos/modules/services/networking/jibri/default.nix
Normal file
417
nixos/modules/services/networking/jibri/default.nix
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.jibri;
|
||||
|
||||
# Copied from the jitsi-videobridge.nix file.
|
||||
toHOCON = x:
|
||||
if isAttrs x && x ? __hocon_envvar then ("\${" + x.__hocon_envvar + "}")
|
||||
else if isAttrs x then "{${ concatStringsSep "," (mapAttrsToList (k: v: ''"${k}":${toHOCON v}'') x) }}"
|
||||
else if isList x then "[${ concatMapStringsSep "," toHOCON x }]"
|
||||
else builtins.toJSON x;
|
||||
|
||||
# We're passing passwords in environment variables that have names generated
|
||||
# from an attribute name, which may not be a valid bash identifier.
|
||||
toVarName = s: "XMPP_PASSWORD_" + stringAsChars (c: if builtins.match "[A-Za-z0-9]" c != null then c else "_") s;
|
||||
|
||||
defaultJibriConfig = {
|
||||
id = "";
|
||||
single-use-mode = false;
|
||||
|
||||
api = {
|
||||
http.external-api-port = 2222;
|
||||
http.internal-api-port = 3333;
|
||||
|
||||
xmpp.environments = flip mapAttrsToList cfg.xmppEnvironments (name: env: {
|
||||
inherit name;
|
||||
|
||||
xmpp-server-hosts = env.xmppServerHosts;
|
||||
xmpp-domain = env.xmppDomain;
|
||||
control-muc = {
|
||||
domain = env.control.muc.domain;
|
||||
room-name = env.control.muc.roomName;
|
||||
nickname = env.control.muc.nickname;
|
||||
};
|
||||
|
||||
control-login = {
|
||||
domain = env.control.login.domain;
|
||||
username = env.control.login.username;
|
||||
password.__hocon_envvar = toVarName "${name}_control";
|
||||
};
|
||||
|
||||
call-login = {
|
||||
domain = env.call.login.domain;
|
||||
username = env.call.login.username;
|
||||
password.__hocon_envvar = toVarName "${name}_call";
|
||||
};
|
||||
|
||||
strip-from-room-domain = env.stripFromRoomDomain;
|
||||
usage-timeout = env.usageTimeout;
|
||||
trust-all-xmpp-certs = env.disableCertificateVerification;
|
||||
});
|
||||
};
|
||||
|
||||
recording = {
|
||||
recordings-directory = "/tmp/recordings";
|
||||
finalize-script = "${cfg.finalizeScript}";
|
||||
};
|
||||
|
||||
streaming.rtmp-allow-list = [ ".*" ];
|
||||
|
||||
chrome.flags = [
|
||||
"--use-fake-ui-for-media-stream"
|
||||
"--start-maximized"
|
||||
"--kiosk"
|
||||
"--enabled"
|
||||
"--disable-infobars"
|
||||
"--autoplay-policy=no-user-gesture-required"
|
||||
]
|
||||
++ lists.optional cfg.ignoreCert
|
||||
"--ignore-certificate-errors";
|
||||
|
||||
|
||||
stats.enable-stats-d = true;
|
||||
webhook.subscribers = [ ];
|
||||
|
||||
jwt-info = { };
|
||||
|
||||
call-status-checks = {
|
||||
no-media-timout = "30 seconds";
|
||||
all-muted-timeout = "10 minutes";
|
||||
default-call-empty-timout = "30 seconds";
|
||||
};
|
||||
};
|
||||
# Allow overriding leaves of the default config despite types.attrs not doing any merging.
|
||||
jibriConfig = recursiveUpdate defaultJibriConfig cfg.config;
|
||||
configFile = pkgs.writeText "jibri.conf" (toHOCON { jibri = jibriConfig; });
|
||||
in
|
||||
{
|
||||
options.services.jibri = with types; {
|
||||
enable = mkEnableOption "Jitsi BRoadcasting Infrastructure. Currently Jibri must be run on a host that is also running <option>services.jitsi-meet.enable</option>, so for most use cases it will be simpler to run <option>services.jitsi-meet.jibri.enable</option>";
|
||||
config = mkOption {
|
||||
type = attrs;
|
||||
default = { };
|
||||
description = ''
|
||||
Jibri configuration.
|
||||
See <link xlink:href="https://github.com/jitsi/jibri/blob/master/src/main/resources/reference.conf" />
|
||||
for default configuration with comments.
|
||||
'';
|
||||
};
|
||||
|
||||
finalizeScript = mkOption {
|
||||
type = types.path;
|
||||
default = pkgs.writeScript "finalize_recording.sh" ''
|
||||
#!/bin/sh
|
||||
|
||||
RECORDINGS_DIR=$1
|
||||
|
||||
echo "This is a dummy finalize script" > /tmp/finalize.out
|
||||
echo "The script was invoked with recordings directory $RECORDINGS_DIR." >> /tmp/finalize.out
|
||||
echo "You should put any finalize logic (renaming, uploading to a service" >> /tmp/finalize.out
|
||||
echo "or storage provider, etc.) in this script" >> /tmp/finalize.out
|
||||
|
||||
exit 0
|
||||
'';
|
||||
defaultText = literalExpression ''
|
||||
pkgs.writeScript "finalize_recording.sh" ''''''
|
||||
#!/bin/sh
|
||||
|
||||
RECORDINGS_DIR=$1
|
||||
|
||||
echo "This is a dummy finalize script" > /tmp/finalize.out
|
||||
echo "The script was invoked with recordings directory $RECORDINGS_DIR." >> /tmp/finalize.out
|
||||
echo "You should put any finalize logic (renaming, uploading to a service" >> /tmp/finalize.out
|
||||
echo "or storage provider, etc.) in this script" >> /tmp/finalize.out
|
||||
|
||||
exit 0
|
||||
'''''';
|
||||
'';
|
||||
example = literalExpression ''
|
||||
pkgs.writeScript "finalize_recording.sh" ''''''
|
||||
#!/bin/sh
|
||||
RECORDINGS_DIR=$1
|
||||
''${pkgs.rclone}/bin/rclone copy $RECORDINGS_DIR RCLONE_REMOTE:jibri-recordings/ -v --log-file=/var/log/jitsi/jibri/recording-upload.txt
|
||||
exit 0
|
||||
'''''';
|
||||
'';
|
||||
description = ''
|
||||
This script runs when jibri finishes recording a video of a conference.
|
||||
'';
|
||||
};
|
||||
|
||||
ignoreCert = mkOption {
|
||||
type = bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
Whether to enable the flag "--ignore-certificate-errors" for the Chromium browser opened by Jibri.
|
||||
Intended for use in automated tests or anywhere else where using a verified cert for Jitsi-Meet is not possible.
|
||||
'';
|
||||
};
|
||||
|
||||
xmppEnvironments = mkOption {
|
||||
description = ''
|
||||
XMPP servers to connect to.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
"jitsi-meet" = {
|
||||
xmppServerHosts = [ "localhost" ];
|
||||
xmppDomain = config.services.jitsi-meet.hostName;
|
||||
|
||||
control.muc = {
|
||||
domain = "internal.''${config.services.jitsi-meet.hostName}";
|
||||
roomName = "JibriBrewery";
|
||||
nickname = "jibri";
|
||||
};
|
||||
|
||||
control.login = {
|
||||
domain = "auth.''${config.services.jitsi-meet.hostName}";
|
||||
username = "jibri";
|
||||
passwordFile = "/var/lib/jitsi-meet/jibri-auth-secret";
|
||||
};
|
||||
|
||||
call.login = {
|
||||
domain = "recorder.''${config.services.jitsi-meet.hostName}";
|
||||
username = "recorder";
|
||||
passwordFile = "/var/lib/jitsi-meet/jibri-recorder-secret";
|
||||
};
|
||||
|
||||
usageTimeout = "0";
|
||||
disableCertificateVerification = true;
|
||||
stripFromRoomDomain = "conference.";
|
||||
};
|
||||
'';
|
||||
default = { };
|
||||
type = attrsOf (submodule ({ name, ... }: {
|
||||
options = {
|
||||
xmppServerHosts = mkOption {
|
||||
type = listOf str;
|
||||
example = [ "xmpp.example.org" ];
|
||||
description = ''
|
||||
Hostnames of the XMPP servers to connect to.
|
||||
'';
|
||||
};
|
||||
xmppDomain = mkOption {
|
||||
type = str;
|
||||
example = "xmpp.example.org";
|
||||
description = ''
|
||||
The base XMPP domain.
|
||||
'';
|
||||
};
|
||||
control.muc.domain = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
The domain part of the MUC to connect to for control.
|
||||
'';
|
||||
};
|
||||
control.muc.roomName = mkOption {
|
||||
type = str;
|
||||
default = "JibriBrewery";
|
||||
description = ''
|
||||
The room name of the MUC to connect to for control.
|
||||
'';
|
||||
};
|
||||
control.muc.nickname = mkOption {
|
||||
type = str;
|
||||
default = "jibri";
|
||||
description = ''
|
||||
The nickname for this Jibri instance in the MUC.
|
||||
'';
|
||||
};
|
||||
control.login.domain = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
The domain part of the JID for this Jibri instance.
|
||||
'';
|
||||
};
|
||||
control.login.username = mkOption {
|
||||
type = str;
|
||||
default = "jvb";
|
||||
description = ''
|
||||
User part of the JID.
|
||||
'';
|
||||
};
|
||||
control.login.passwordFile = mkOption {
|
||||
type = str;
|
||||
example = "/run/keys/jibri-xmpp1";
|
||||
description = ''
|
||||
File containing the password for the user.
|
||||
'';
|
||||
};
|
||||
|
||||
call.login.domain = mkOption {
|
||||
type = str;
|
||||
example = "recorder.xmpp.example.org";
|
||||
description = ''
|
||||
The domain part of the JID for the recorder.
|
||||
'';
|
||||
};
|
||||
call.login.username = mkOption {
|
||||
type = str;
|
||||
default = "recorder";
|
||||
description = ''
|
||||
User part of the JID for the recorder.
|
||||
'';
|
||||
};
|
||||
call.login.passwordFile = mkOption {
|
||||
type = str;
|
||||
example = "/run/keys/jibri-recorder-xmpp1";
|
||||
description = ''
|
||||
File containing the password for the user.
|
||||
'';
|
||||
};
|
||||
disableCertificateVerification = mkOption {
|
||||
type = bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to skip validation of the server's certificate.
|
||||
'';
|
||||
};
|
||||
|
||||
stripFromRoomDomain = mkOption {
|
||||
type = str;
|
||||
default = "0";
|
||||
example = "conference.";
|
||||
description = ''
|
||||
The prefix to strip from the room's JID domain to derive the call URL.
|
||||
'';
|
||||
};
|
||||
usageTimeout = mkOption {
|
||||
type = str;
|
||||
default = "0";
|
||||
example = "1 hour";
|
||||
description = ''
|
||||
The duration that the Jibri session can be.
|
||||
A value of zero means indefinitely.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config =
|
||||
let
|
||||
nick = mkDefault (builtins.replaceStrings [ "." ] [ "-" ] (
|
||||
config.networking.hostName + optionalString (config.networking.domain != null) ".${config.networking.domain}"
|
||||
));
|
||||
in
|
||||
{
|
||||
call.login.username = nick;
|
||||
control.muc.nickname = nick;
|
||||
};
|
||||
}));
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
users.groups.jibri = { };
|
||||
users.groups.plugdev = { };
|
||||
users.users.jibri = {
|
||||
isSystemUser = true;
|
||||
group = "jibri";
|
||||
home = "/var/lib/jibri";
|
||||
extraGroups = [ "jitsi-meet" "adm" "audio" "video" "plugdev" ];
|
||||
};
|
||||
|
||||
systemd.services.jibri-xorg = {
|
||||
description = "Jitsi Xorg Process";
|
||||
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "jibri.service" "jibri-icewm.service" ];
|
||||
|
||||
preStart = ''
|
||||
cp --no-preserve=mode,ownership ${pkgs.jibri}/etc/jitsi/jibri/* /var/lib/jibri
|
||||
mv /var/lib/jibri/{,.}asoundrc
|
||||
'';
|
||||
|
||||
environment.DISPLAY = ":0";
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
|
||||
User = "jibri";
|
||||
Group = "jibri";
|
||||
KillMode = "process";
|
||||
Restart = "on-failure";
|
||||
RestartPreventExitStatus = 255;
|
||||
|
||||
StateDirectory = "jibri";
|
||||
|
||||
ExecStart = "${pkgs.xorg.xorgserver}/bin/Xorg -nocursor -noreset +extension RANDR +extension RENDER -config ${pkgs.jibri}/etc/jitsi/jibri/xorg-video-dummy.conf -logfile /dev/null :0";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.jibri-icewm = {
|
||||
description = "Jitsi Window Manager";
|
||||
|
||||
requires = [ "jibri-xorg.service" ];
|
||||
after = [ "jibri-xorg.service" ];
|
||||
wantedBy = [ "jibri.service" ];
|
||||
|
||||
environment.DISPLAY = ":0";
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
|
||||
User = "jibri";
|
||||
Group = "jibri";
|
||||
Restart = "on-failure";
|
||||
RestartPreventExitStatus = 255;
|
||||
|
||||
StateDirectory = "jibri";
|
||||
|
||||
ExecStart = "${pkgs.icewm}/bin/icewm-session";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.jibri = {
|
||||
description = "Jibri Process";
|
||||
|
||||
requires = [ "jibri-icewm.service" "jibri-xorg.service" ];
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
path = with pkgs; [ chromedriver chromium ffmpeg-full ];
|
||||
|
||||
script = (concatStrings (mapAttrsToList
|
||||
(name: env: ''
|
||||
export ${toVarName "${name}_control"}=$(cat ${env.control.login.passwordFile})
|
||||
export ${toVarName "${name}_call"}=$(cat ${env.call.login.passwordFile})
|
||||
'')
|
||||
cfg.xmppEnvironments))
|
||||
+ ''
|
||||
${pkgs.jre8_headless}/bin/java -Djava.util.logging.config.file=${./logging.properties-journal} -Dconfig.file=${configFile} -jar ${pkgs.jibri}/opt/jitsi/jibri/jibri.jar --config /var/lib/jibri/jibri.json
|
||||
'';
|
||||
|
||||
environment.HOME = "/var/lib/jibri";
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
|
||||
User = "jibri";
|
||||
Group = "jibri";
|
||||
Restart = "always";
|
||||
RestartPreventExitStatus = 255;
|
||||
|
||||
StateDirectory = "jibri";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/log/jitsi/jibri 755 jibri jibri"
|
||||
];
|
||||
|
||||
|
||||
|
||||
# Configure Chromium to not show the "Chrome is being controlled by automatic test software" message.
|
||||
environment.etc."chromium/policies/managed/managed_policies.json".text = builtins.toJSON { CommandLineFlagSecurityWarningsEnabled = false; };
|
||||
warnings = [ "All security warnings for Chromium have been disabled. This is necessary for Jibri, but it also impacts all other uses of Chromium on this system." ];
|
||||
|
||||
boot = {
|
||||
extraModprobeConfig = ''
|
||||
options snd-aloop enable=1,1,1,1,1,1,1,1
|
||||
'';
|
||||
kernelModules = [ "snd-aloop" ];
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = lib.teams.jitsi.members;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
handlers = java.util.logging.FileHandler
|
||||
|
||||
java.util.logging.FileHandler.level = FINE
|
||||
java.util.logging.FileHandler.pattern = /var/log/jitsi/jibri/log.%g.txt
|
||||
java.util.logging.FileHandler.formatter = net.java.sip.communicator.util.ScLogFormatter
|
||||
java.util.logging.FileHandler.count = 10
|
||||
java.util.logging.FileHandler.limit = 10000000
|
||||
|
||||
org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.level = FINE
|
||||
org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.pattern = /var/log/jitsi/jibri/ffmpeg.%g.txt
|
||||
org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.formatter = net.java.sip.communicator.util.ScLogFormatter
|
||||
org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.count = 10
|
||||
org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.limit = 10000000
|
||||
|
||||
org.jitsi.jibri.sipgateway.pjsua.util.PjsuaFileHandler.level = FINE
|
||||
org.jitsi.jibri.sipgateway.pjsua.util.PjsuaFileHandler.pattern = /var/log/jitsi/jibri/pjsua.%g.txt
|
||||
org.jitsi.jibri.sipgateway.pjsua.util.PjsuaFileHandler.formatter = net.java.sip.communicator.util.ScLogFormatter
|
||||
org.jitsi.jibri.sipgateway.pjsua.util.PjsuaFileHandler.count = 10
|
||||
org.jitsi.jibri.sipgateway.pjsua.util.PjsuaFileHandler.limit = 10000000
|
||||
|
||||
org.jitsi.jibri.selenium.util.BrowserFileHandler.level = FINE
|
||||
org.jitsi.jibri.selenium.util.BrowserFileHandler.pattern = /var/log/jitsi/jibri/browser.%g.txt
|
||||
org.jitsi.jibri.selenium.util.BrowserFileHandler.formatter = net.java.sip.communicator.util.ScLogFormatter
|
||||
org.jitsi.jibri.selenium.util.BrowserFileHandler.count = 10
|
||||
org.jitsi.jibri.selenium.util.BrowserFileHandler.limit = 10000000
|
||||
|
||||
org.jitsi.level = FINE
|
||||
org.jitsi.jibri.config.level = INFO
|
||||
|
||||
org.glassfish.level = INFO
|
||||
org.osgi.level = INFO
|
||||
org.jitsi.xmpp.level = INFO
|
||||
152
nixos/modules/services/networking/jicofo.nix
Normal file
152
nixos/modules/services/networking/jicofo.nix
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.jicofo;
|
||||
in
|
||||
{
|
||||
options.services.jicofo = with types; {
|
||||
enable = mkEnableOption "Jitsi Conference Focus - component of Jitsi Meet";
|
||||
|
||||
xmppHost = mkOption {
|
||||
type = str;
|
||||
example = "localhost";
|
||||
description = ''
|
||||
Hostname of the XMPP server to connect to.
|
||||
'';
|
||||
};
|
||||
|
||||
xmppDomain = mkOption {
|
||||
type = nullOr str;
|
||||
example = "meet.example.org";
|
||||
description = ''
|
||||
Domain name of the XMMP server to which to connect as a component.
|
||||
|
||||
If null, <option>xmppHost</option> is used.
|
||||
'';
|
||||
};
|
||||
|
||||
componentPasswordFile = mkOption {
|
||||
type = str;
|
||||
example = "/run/keys/jicofo-component";
|
||||
description = ''
|
||||
Path to file containing component secret.
|
||||
'';
|
||||
};
|
||||
|
||||
userName = mkOption {
|
||||
type = str;
|
||||
default = "focus";
|
||||
description = ''
|
||||
User part of the JID for XMPP user connection.
|
||||
'';
|
||||
};
|
||||
|
||||
userDomain = mkOption {
|
||||
type = str;
|
||||
example = "auth.meet.example.org";
|
||||
description = ''
|
||||
Domain part of the JID for XMPP user connection.
|
||||
'';
|
||||
};
|
||||
|
||||
userPasswordFile = mkOption {
|
||||
type = str;
|
||||
example = "/run/keys/jicofo-user";
|
||||
description = ''
|
||||
Path to file containing password for XMPP user connection.
|
||||
'';
|
||||
};
|
||||
|
||||
bridgeMuc = mkOption {
|
||||
type = str;
|
||||
example = "jvbbrewery@internal.meet.example.org";
|
||||
description = ''
|
||||
JID of the internal MUC used to communicate with Videobridges.
|
||||
'';
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
type = attrsOf str;
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
"org.jitsi.jicofo.auth.URL" = "XMPP:jitsi-meet.example.com";
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Contents of the <filename>sip-communicator.properties</filename> configuration file for jicofo.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.jicofo.config = mapAttrs (_: v: mkDefault v) {
|
||||
"org.jitsi.jicofo.BRIDGE_MUC" = cfg.bridgeMuc;
|
||||
};
|
||||
|
||||
users.groups.jitsi-meet = {};
|
||||
|
||||
systemd.services.jicofo = let
|
||||
jicofoProps = {
|
||||
"-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION" = "/etc/jitsi";
|
||||
"-Dnet.java.sip.communicator.SC_HOME_DIR_NAME" = "jicofo";
|
||||
"-Djava.util.logging.config.file" = "/etc/jitsi/jicofo/logging.properties";
|
||||
};
|
||||
in
|
||||
{
|
||||
description = "JItsi COnference FOcus";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
restartTriggers = [
|
||||
config.environment.etc."jitsi/jicofo/sip-communicator.properties".source
|
||||
];
|
||||
environment.JAVA_SYS_PROPS = concatStringsSep " " (mapAttrsToList (k: v: "${k}=${toString v}") jicofoProps);
|
||||
|
||||
script = ''
|
||||
${pkgs.jicofo}/bin/jicofo \
|
||||
--host=${cfg.xmppHost} \
|
||||
--domain=${if cfg.xmppDomain == null then cfg.xmppHost else cfg.xmppDomain} \
|
||||
--secret=$(cat ${cfg.componentPasswordFile}) \
|
||||
--user_name=${cfg.userName} \
|
||||
--user_domain=${cfg.userDomain} \
|
||||
--user_password=$(cat ${cfg.userPasswordFile})
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "exec";
|
||||
|
||||
DynamicUser = true;
|
||||
User = "jicofo";
|
||||
Group = "jitsi-meet";
|
||||
|
||||
CapabilityBoundingSet = "";
|
||||
NoNewPrivileges = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
|
||||
RestrictNamespaces = true;
|
||||
LockPersonality = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
};
|
||||
};
|
||||
|
||||
environment.etc."jitsi/jicofo/sip-communicator.properties".source =
|
||||
pkgs.writeText "sip-communicator.properties" (
|
||||
generators.toKeyValue {} cfg.config
|
||||
);
|
||||
environment.etc."jitsi/jicofo/logging.properties".source =
|
||||
mkDefault "${pkgs.jicofo}/etc/jitsi/jicofo/logging.properties-journal";
|
||||
};
|
||||
|
||||
meta.maintainers = lib.teams.jitsi.members;
|
||||
}
|
||||
288
nixos/modules/services/networking/jitsi-videobridge.nix
Normal file
288
nixos/modules/services/networking/jitsi-videobridge.nix
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.jitsi-videobridge;
|
||||
attrsToArgs = a: concatStringsSep " " (mapAttrsToList (k: v: "${k}=${toString v}") a);
|
||||
|
||||
# HOCON is a JSON superset that videobridge2 uses for configuration.
|
||||
# It can substitute environment variables which we use for passwords here.
|
||||
# https://github.com/lightbend/config/blob/master/README.md
|
||||
#
|
||||
# Substitution for environment variable FOO is represented as attribute set
|
||||
# { __hocon_envvar = "FOO"; }
|
||||
toHOCON = x: if isAttrs x && x ? __hocon_envvar then ("\${" + x.__hocon_envvar + "}")
|
||||
else if isAttrs x then "{${ concatStringsSep "," (mapAttrsToList (k: v: ''"${k}":${toHOCON v}'') x) }}"
|
||||
else if isList x then "[${ concatMapStringsSep "," toHOCON x }]"
|
||||
else builtins.toJSON x;
|
||||
|
||||
# We're passing passwords in environment variables that have names generated
|
||||
# from an attribute name, which may not be a valid bash identifier.
|
||||
toVarName = s: "XMPP_PASSWORD_" + stringAsChars (c: if builtins.match "[A-Za-z0-9]" c != null then c else "_") s;
|
||||
|
||||
defaultJvbConfig = {
|
||||
videobridge = {
|
||||
ice = {
|
||||
tcp = {
|
||||
enabled = true;
|
||||
port = 4443;
|
||||
};
|
||||
udp.port = 10000;
|
||||
};
|
||||
stats = {
|
||||
enabled = true;
|
||||
transports = [ { type = "muc"; } ];
|
||||
};
|
||||
apis.xmpp-client.configs = flip mapAttrs cfg.xmppConfigs (name: xmppConfig: {
|
||||
hostname = xmppConfig.hostName;
|
||||
domain = xmppConfig.domain;
|
||||
username = xmppConfig.userName;
|
||||
password = { __hocon_envvar = toVarName name; };
|
||||
muc_jids = xmppConfig.mucJids;
|
||||
muc_nickname = xmppConfig.mucNickname;
|
||||
disable_certificate_verification = xmppConfig.disableCertificateVerification;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
# Allow overriding leaves of the default config despite types.attrs not doing any merging.
|
||||
jvbConfig = recursiveUpdate defaultJvbConfig cfg.config;
|
||||
in
|
||||
{
|
||||
options.services.jitsi-videobridge = with types; {
|
||||
enable = mkEnableOption "Jitsi Videobridge, a WebRTC compatible video router";
|
||||
|
||||
config = mkOption {
|
||||
type = attrs;
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
videobridge = {
|
||||
ice.udp.port = 5000;
|
||||
websockets = {
|
||||
enabled = true;
|
||||
server-id = "jvb1";
|
||||
};
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Videobridge configuration.
|
||||
|
||||
See <link xlink:href="https://github.com/jitsi/jitsi-videobridge/blob/master/src/main/resources/reference.conf" />
|
||||
for default configuration with comments.
|
||||
'';
|
||||
};
|
||||
|
||||
xmppConfigs = mkOption {
|
||||
description = ''
|
||||
XMPP servers to connect to.
|
||||
|
||||
See <link xlink:href="https://github.com/jitsi/jitsi-videobridge/blob/master/doc/muc.md" /> for more information.
|
||||
'';
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
"localhost" = {
|
||||
hostName = "localhost";
|
||||
userName = "jvb";
|
||||
domain = "auth.xmpp.example.org";
|
||||
passwordFile = "/var/lib/jitsi-meet/videobridge-secret";
|
||||
mucJids = "jvbbrewery@internal.xmpp.example.org";
|
||||
};
|
||||
}
|
||||
'';
|
||||
type = attrsOf (submodule ({ name, ... }: {
|
||||
options = {
|
||||
hostName = mkOption {
|
||||
type = str;
|
||||
example = "xmpp.example.org";
|
||||
description = ''
|
||||
Hostname of the XMPP server to connect to. Name of the attribute set is used by default.
|
||||
'';
|
||||
};
|
||||
domain = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
example = "auth.xmpp.example.org";
|
||||
description = ''
|
||||
Domain part of JID of the XMPP user, if it is different from hostName.
|
||||
'';
|
||||
};
|
||||
userName = mkOption {
|
||||
type = str;
|
||||
default = "jvb";
|
||||
description = ''
|
||||
User part of the JID.
|
||||
'';
|
||||
};
|
||||
passwordFile = mkOption {
|
||||
type = str;
|
||||
example = "/run/keys/jitsi-videobridge-xmpp1";
|
||||
description = ''
|
||||
File containing the password for the user.
|
||||
'';
|
||||
};
|
||||
mucJids = mkOption {
|
||||
type = str;
|
||||
example = "jvbbrewery@internal.xmpp.example.org";
|
||||
description = ''
|
||||
JID of the MUC to join. JiCoFo needs to be configured to join the same MUC.
|
||||
'';
|
||||
};
|
||||
mucNickname = mkOption {
|
||||
# Upstream DEBs use UUID, let's use hostname instead.
|
||||
type = str;
|
||||
description = ''
|
||||
Videobridges use the same XMPP account and need to be distinguished by the
|
||||
nickname (aka resource part of the JID). By default, system hostname is used.
|
||||
'';
|
||||
};
|
||||
disableCertificateVerification = mkOption {
|
||||
type = bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to skip validation of the server's certificate.
|
||||
'';
|
||||
};
|
||||
};
|
||||
config = {
|
||||
hostName = mkDefault name;
|
||||
mucNickname = mkDefault (builtins.replaceStrings [ "." ] [ "-" ] (
|
||||
config.networking.hostName + optionalString (config.networking.domain != null) ".${config.networking.domain}"
|
||||
));
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
nat = {
|
||||
localAddress = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
example = "192.168.1.42";
|
||||
description = ''
|
||||
Local address when running behind NAT.
|
||||
'';
|
||||
};
|
||||
|
||||
publicAddress = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
example = "1.2.3.4";
|
||||
description = ''
|
||||
Public address when running behind NAT.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
extraProperties = mkOption {
|
||||
type = attrsOf str;
|
||||
default = { };
|
||||
description = ''
|
||||
Additional Java properties passed to jitsi-videobridge.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to open ports in the firewall for the videobridge.
|
||||
'';
|
||||
};
|
||||
|
||||
apis = mkOption {
|
||||
type = with types; listOf str;
|
||||
description = ''
|
||||
What is passed as --apis= parameter. If this is empty, "none" is passed.
|
||||
Needed for monitoring jitsi.
|
||||
'';
|
||||
default = [];
|
||||
example = literalExpression "[ \"colibri\" \"rest\" ]";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
users.groups.jitsi-meet = {};
|
||||
|
||||
services.jitsi-videobridge.extraProperties = optionalAttrs (cfg.nat.localAddress != null) {
|
||||
"org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS" = cfg.nat.localAddress;
|
||||
"org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS" = cfg.nat.publicAddress;
|
||||
};
|
||||
|
||||
systemd.services.jitsi-videobridge2 = let
|
||||
jvbProps = {
|
||||
"-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION" = "/etc/jitsi";
|
||||
"-Dnet.java.sip.communicator.SC_HOME_DIR_NAME" = "videobridge";
|
||||
"-Djava.util.logging.config.file" = "/etc/jitsi/videobridge/logging.properties";
|
||||
"-Dconfig.file" = pkgs.writeText "jvb.conf" (toHOCON jvbConfig);
|
||||
# Mitigate CVE-2021-44228
|
||||
"-Dlog4j2.formatMsgNoLookups" = true;
|
||||
} // (mapAttrs' (k: v: nameValuePair "-D${k}" v) cfg.extraProperties);
|
||||
in
|
||||
{
|
||||
aliases = [ "jitsi-videobridge.service" ];
|
||||
description = "Jitsi Videobridge";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
environment.JAVA_SYS_PROPS = attrsToArgs jvbProps;
|
||||
|
||||
script = (concatStrings (mapAttrsToList (name: xmppConfig:
|
||||
"export ${toVarName name}=$(cat ${xmppConfig.passwordFile})\n"
|
||||
) cfg.xmppConfigs))
|
||||
+ ''
|
||||
${pkgs.jitsi-videobridge}/bin/jitsi-videobridge --apis=${if (cfg.apis == []) then "none" else concatStringsSep "," cfg.apis}
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "exec";
|
||||
|
||||
DynamicUser = true;
|
||||
User = "jitsi-videobridge";
|
||||
Group = "jitsi-meet";
|
||||
|
||||
CapabilityBoundingSet = "";
|
||||
NoNewPrivileges = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
|
||||
RestrictNamespaces = true;
|
||||
LockPersonality = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
|
||||
TasksMax = 65000;
|
||||
LimitNPROC = 65000;
|
||||
LimitNOFILE = 65000;
|
||||
};
|
||||
};
|
||||
|
||||
environment.etc."jitsi/videobridge/logging.properties".source =
|
||||
mkDefault "${pkgs.jitsi-videobridge}/etc/jitsi/videobridge/logging.properties-journal";
|
||||
|
||||
# (from videobridge2 .deb)
|
||||
# this sets the max, so that we can bump the JVB UDP single port buffer size.
|
||||
boot.kernel.sysctl."net.core.rmem_max" = mkDefault 10485760;
|
||||
boot.kernel.sysctl."net.core.netdev_max_backlog" = mkDefault 100000;
|
||||
|
||||
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall
|
||||
[ jvbConfig.videobridge.ice.tcp.port ];
|
||||
networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall
|
||||
[ jvbConfig.videobridge.ice.udp.port ];
|
||||
|
||||
assertions = [{
|
||||
message = "publicAddress must be set if and only if localAddress is set";
|
||||
assertion = (cfg.nat.publicAddress == null) == (cfg.nat.localAddress == null);
|
||||
}];
|
||||
};
|
||||
|
||||
meta.maintainers = lib.teams.jitsi.members;
|
||||
}
|
||||
449
nixos/modules/services/networking/kea.nix
Normal file
449
nixos/modules/services/networking/kea.nix
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
{ config
|
||||
, lib
|
||||
, pkgs
|
||||
, ...
|
||||
}:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.kea;
|
||||
|
||||
xor = x: y: (!x && y) || (x && !y);
|
||||
format = pkgs.formats.json {};
|
||||
|
||||
chooseNotNull = x: y: if x != null then x else y;
|
||||
|
||||
ctrlAgentConfig = chooseNotNull cfg.ctrl-agent.configFile (format.generate "kea-ctrl-agent.conf" {
|
||||
Control-agent = cfg.ctrl-agent.settings;
|
||||
});
|
||||
|
||||
dhcp4Config = chooseNotNull cfg.dhcp4.configFile (format.generate "kea-dhcp4.conf" {
|
||||
Dhcp4 = cfg.dhcp4.settings;
|
||||
});
|
||||
|
||||
dhcp6Config = chooseNotNull cfg.dhcp6.configFile (format.generate "kea-dhcp6.conf" {
|
||||
Dhcp6 = cfg.dhcp6.settings;
|
||||
});
|
||||
|
||||
dhcpDdnsConfig = chooseNotNull cfg.dhcp-ddns.configFile (format.generate "kea-dhcp-ddns.conf" {
|
||||
DhcpDdns = cfg.dhcp-ddns.settings;
|
||||
});
|
||||
|
||||
package = pkgs.kea;
|
||||
in
|
||||
{
|
||||
options.services.kea = with types; {
|
||||
ctrl-agent = mkOption {
|
||||
description = ''
|
||||
Kea Control Agent configuration
|
||||
'';
|
||||
default = {};
|
||||
type = submodule {
|
||||
options = {
|
||||
enable = mkEnableOption "Kea Control Agent";
|
||||
|
||||
extraArgs = mkOption {
|
||||
type = listOf str;
|
||||
default = [];
|
||||
description = ''
|
||||
List of additonal arguments to pass to the daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
type = nullOr path;
|
||||
default = null;
|
||||
description = ''
|
||||
Kea Control Agent configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/agent.html"/>.
|
||||
|
||||
Takes preference over <link linkend="opt-services.kea.ctrl-agent.settings">settings</link>.
|
||||
Most users should prefer using <link linkend="opt-services.kea.ctrl-agent.settings">settings</link> instead.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = format.type;
|
||||
default = null;
|
||||
description = ''
|
||||
Kea Control Agent configuration as an attribute set, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/agent.html"/>.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
dhcp4 = mkOption {
|
||||
description = ''
|
||||
DHCP4 Server configuration
|
||||
'';
|
||||
default = {};
|
||||
type = submodule {
|
||||
options = {
|
||||
enable = mkEnableOption "Kea DHCP4 server";
|
||||
|
||||
extraArgs = mkOption {
|
||||
type = listOf str;
|
||||
default = [];
|
||||
description = ''
|
||||
List of additonal arguments to pass to the daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
type = nullOr path;
|
||||
default = null;
|
||||
description = ''
|
||||
Kea DHCP4 configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp4-srv.html"/>.
|
||||
|
||||
Takes preference over <link linkend="opt-services.kea.dhcp4.settings">settings</link>.
|
||||
Most users should prefer using <link linkend="opt-services.kea.dhcp4.settings">settings</link> instead.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = format.type;
|
||||
default = null;
|
||||
example = {
|
||||
valid-lifetime = 4000;
|
||||
renew-timer = 1000;
|
||||
rebind-timer = 2000;
|
||||
interfaces-config = {
|
||||
interfaces = [
|
||||
"eth0"
|
||||
];
|
||||
};
|
||||
lease-database = {
|
||||
type = "memfile";
|
||||
persist = true;
|
||||
name = "/var/lib/kea/dhcp4.leases";
|
||||
};
|
||||
subnet4 = [ {
|
||||
subnet = "192.0.2.0/24";
|
||||
pools = [ {
|
||||
pool = "192.0.2.100 - 192.0.2.240";
|
||||
} ];
|
||||
} ];
|
||||
};
|
||||
description = ''
|
||||
Kea DHCP4 configuration as an attribute set, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp4-srv.html"/>.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
dhcp6 = mkOption {
|
||||
description = ''
|
||||
DHCP6 Server configuration
|
||||
'';
|
||||
default = {};
|
||||
type = submodule {
|
||||
options = {
|
||||
enable = mkEnableOption "Kea DHCP6 server";
|
||||
|
||||
extraArgs = mkOption {
|
||||
type = listOf str;
|
||||
default = [];
|
||||
description = ''
|
||||
List of additonal arguments to pass to the daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
type = nullOr path;
|
||||
default = null;
|
||||
description = ''
|
||||
Kea DHCP6 configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp6-srv.html"/>.
|
||||
|
||||
Takes preference over <link linkend="opt-services.kea.dhcp6.settings">settings</link>.
|
||||
Most users should prefer using <link linkend="opt-services.kea.dhcp6.settings">settings</link> instead.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = format.type;
|
||||
default = null;
|
||||
example = {
|
||||
valid-lifetime = 4000;
|
||||
renew-timer = 1000;
|
||||
rebind-timer = 2000;
|
||||
preferred-lifetime = 3000;
|
||||
interfaces-config = {
|
||||
interfaces = [
|
||||
"eth0"
|
||||
];
|
||||
};
|
||||
lease-database = {
|
||||
type = "memfile";
|
||||
persist = true;
|
||||
name = "/var/lib/kea/dhcp6.leases";
|
||||
};
|
||||
subnet6 = [ {
|
||||
subnet = "2001:db8:1::/64";
|
||||
pools = [ {
|
||||
pool = "2001:db8:1::1-2001:db8:1::ffff";
|
||||
} ];
|
||||
} ];
|
||||
};
|
||||
description = ''
|
||||
Kea DHCP6 configuration as an attribute set, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp6-srv.html"/>.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
dhcp-ddns = mkOption {
|
||||
description = ''
|
||||
Kea DHCP-DDNS configuration
|
||||
'';
|
||||
default = {};
|
||||
type = submodule {
|
||||
options = {
|
||||
enable = mkEnableOption "Kea DDNS server";
|
||||
|
||||
extraArgs = mkOption {
|
||||
type = listOf str;
|
||||
default = [];
|
||||
description = ''
|
||||
List of additonal arguments to pass to the daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
type = nullOr path;
|
||||
default = null;
|
||||
description = ''
|
||||
Kea DHCP-DDNS configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/ddns.html"/>.
|
||||
|
||||
Takes preference over <link linkend="opt-services.kea.dhcp-ddns.settings">settings</link>.
|
||||
Most users should prefer using <link linkend="opt-services.kea.dhcp-ddns.settings">settings</link> instead.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = format.type;
|
||||
default = null;
|
||||
example = {
|
||||
ip-address = "127.0.0.1";
|
||||
port = 53001;
|
||||
dns-server-timeout = 100;
|
||||
ncr-protocol = "UDP";
|
||||
ncr-format = "JSON";
|
||||
tsig-keys = [ ];
|
||||
forward-ddns = {
|
||||
ddns-domains = [ ];
|
||||
};
|
||||
reverse-ddns = {
|
||||
ddns-domains = [ ];
|
||||
};
|
||||
};
|
||||
description = ''
|
||||
Kea DHCP-DDNS configuration as an attribute set, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/ddns.html"/>.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = let
|
||||
commonServiceConfig = {
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
DynamicUser = true;
|
||||
User = "kea";
|
||||
ConfigurationDirectory = "kea";
|
||||
RuntimeDirectory = "kea";
|
||||
StateDirectory = "kea";
|
||||
UMask = "0077";
|
||||
};
|
||||
in mkIf (cfg.ctrl-agent.enable || cfg.dhcp4.enable || cfg.dhcp6.enable || cfg.dhcp-ddns.enable) (mkMerge [
|
||||
{
|
||||
environment.systemPackages = [ package ];
|
||||
}
|
||||
|
||||
(mkIf cfg.ctrl-agent.enable {
|
||||
assertions = [{
|
||||
assertion = xor (cfg.ctrl-agent.settings == null) (cfg.ctrl-agent.configFile == null);
|
||||
message = "Either services.kea.ctrl-agent.settings or services.kea.ctrl-agent.configFile must be set to a non-null value.";
|
||||
}];
|
||||
|
||||
environment.etc."kea/ctrl-agent.conf".source = ctrlAgentConfig;
|
||||
|
||||
systemd.services.kea-ctrl-agent = {
|
||||
description = "Kea Control Agent";
|
||||
documentation = [
|
||||
"man:kea-ctrl-agent(8)"
|
||||
"https://kea.readthedocs.io/en/kea-${package.version}/arm/agent.html"
|
||||
];
|
||||
|
||||
after = [
|
||||
"network-online.target"
|
||||
"time-sync.target"
|
||||
];
|
||||
wantedBy = [
|
||||
"kea-dhcp4-server.service"
|
||||
"kea-dhcp6-server.service"
|
||||
"kea-dhcp-ddns-server.service"
|
||||
];
|
||||
|
||||
environment = {
|
||||
KEA_PIDFILE_DIR = "/run/kea";
|
||||
KEA_LOCKFILE_DIR = "/run/kea";
|
||||
};
|
||||
|
||||
restartTriggers = [
|
||||
ctrlAgentConfig
|
||||
];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${package}/bin/kea-ctrl-agent -c /etc/kea/ctrl-agent.conf ${lib.escapeShellArgs cfg.dhcp4.extraArgs}";
|
||||
KillMode = "process";
|
||||
Restart = "on-failure";
|
||||
} // commonServiceConfig;
|
||||
};
|
||||
})
|
||||
|
||||
(mkIf cfg.dhcp4.enable {
|
||||
assertions = [{
|
||||
assertion = xor (cfg.dhcp4.settings == null) (cfg.dhcp4.configFile == null);
|
||||
message = "Either services.kea.dhcp4.settings or services.kea.dhcp4.configFile must be set to a non-null value.";
|
||||
}];
|
||||
|
||||
environment.etc."kea/dhcp4-server.conf".source = dhcp4Config;
|
||||
|
||||
systemd.services.kea-dhcp4-server = {
|
||||
description = "Kea DHCP4 Server";
|
||||
documentation = [
|
||||
"man:kea-dhcp4(8)"
|
||||
"https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp4-srv.html"
|
||||
];
|
||||
|
||||
after = [
|
||||
"network-online.target"
|
||||
"time-sync.target"
|
||||
];
|
||||
wantedBy = [
|
||||
"multi-user.target"
|
||||
];
|
||||
|
||||
environment = {
|
||||
KEA_PIDFILE_DIR = "/run/kea";
|
||||
KEA_LOCKFILE_DIR = "/run/kea";
|
||||
};
|
||||
|
||||
restartTriggers = [
|
||||
dhcp4Config
|
||||
];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${package}/bin/kea-dhcp4 -c /etc/kea/dhcp4-server.conf ${lib.escapeShellArgs cfg.dhcp4.extraArgs}";
|
||||
# Kea does not request capabilities by itself
|
||||
AmbientCapabilities = [
|
||||
"CAP_NET_BIND_SERVICE"
|
||||
"CAP_NET_RAW"
|
||||
];
|
||||
CapabilityBoundingSet = [
|
||||
"CAP_NET_BIND_SERVICE"
|
||||
"CAP_NET_RAW"
|
||||
];
|
||||
} // commonServiceConfig;
|
||||
};
|
||||
})
|
||||
|
||||
(mkIf cfg.dhcp6.enable {
|
||||
assertions = [{
|
||||
assertion = xor (cfg.dhcp6.settings == null) (cfg.dhcp6.configFile == null);
|
||||
message = "Either services.kea.dhcp6.settings or services.kea.dhcp6.configFile must be set to a non-null value.";
|
||||
}];
|
||||
|
||||
environment.etc."kea/dhcp6-server.conf".source = dhcp6Config;
|
||||
|
||||
systemd.services.kea-dhcp6-server = {
|
||||
description = "Kea DHCP6 Server";
|
||||
documentation = [
|
||||
"man:kea-dhcp6(8)"
|
||||
"https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp6-srv.html"
|
||||
];
|
||||
|
||||
after = [
|
||||
"network-online.target"
|
||||
"time-sync.target"
|
||||
];
|
||||
wantedBy = [
|
||||
"multi-user.target"
|
||||
];
|
||||
|
||||
environment = {
|
||||
KEA_PIDFILE_DIR = "/run/kea";
|
||||
KEA_LOCKFILE_DIR = "/run/kea";
|
||||
};
|
||||
|
||||
restartTriggers = [
|
||||
dhcp6Config
|
||||
];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${package}/bin/kea-dhcp6 -c /etc/kea/dhcp6-server.conf ${lib.escapeShellArgs cfg.dhcp6.extraArgs}";
|
||||
# Kea does not request capabilities by itself
|
||||
AmbientCapabilities = [
|
||||
"CAP_NET_BIND_SERVICE"
|
||||
];
|
||||
CapabilityBoundingSet = [
|
||||
"CAP_NET_BIND_SERVICE"
|
||||
];
|
||||
} // commonServiceConfig;
|
||||
};
|
||||
})
|
||||
|
||||
(mkIf cfg.dhcp-ddns.enable {
|
||||
assertions = [{
|
||||
assertion = xor (cfg.dhcp-ddns.settings == null) (cfg.dhcp-ddns.configFile == null);
|
||||
message = "Either services.kea.dhcp-ddns.settings or services.kea.dhcp-ddns.configFile must be set to a non-null value.";
|
||||
}];
|
||||
|
||||
environment.etc."kea/dhcp-ddns.conf".source = dhcpDdnsConfig;
|
||||
|
||||
systemd.services.kea-dhcp-ddns-server = {
|
||||
description = "Kea DHCP-DDNS Server";
|
||||
documentation = [
|
||||
"man:kea-dhcp-ddns(8)"
|
||||
"https://kea.readthedocs.io/en/kea-${package.version}/arm/ddns.html"
|
||||
];
|
||||
|
||||
after = [
|
||||
"network-online.target"
|
||||
"time-sync.target"
|
||||
];
|
||||
wantedBy = [
|
||||
"multi-user.target"
|
||||
];
|
||||
|
||||
environment = {
|
||||
KEA_PIDFILE_DIR = "/run/kea";
|
||||
KEA_LOCKFILE_DIR = "/run/kea";
|
||||
};
|
||||
|
||||
restartTriggers = [
|
||||
dhcpDdnsConfig
|
||||
];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${package}/bin/kea-dhcp-ddns -c /etc/kea/dhcp-ddns.conf ${lib.escapeShellArgs cfg.dhcp-ddns.extraArgs}";
|
||||
AmbientCapabilities = [
|
||||
"CAP_NET_BIND_SERVICE"
|
||||
];
|
||||
CapabilityBoundingSet = [
|
||||
"CAP_NET_BIND_SERVICE"
|
||||
];
|
||||
} // commonServiceConfig;
|
||||
};
|
||||
})
|
||||
|
||||
]);
|
||||
|
||||
meta.maintainers = with maintainers; [ hexa ];
|
||||
# uses attributes of the linked package
|
||||
meta.buildDocsInSandbox = false;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue