Setting up tetra and mogma
This commit is contained in:
parent
0812b82c46
commit
34a686c562
12 changed files with 454 additions and 8 deletions
|
|
@ -6,6 +6,7 @@
|
|||
- `link` : Kanidm (`auth.lyes.eu`)
|
||||
- `maistro` : Incus
|
||||
- `mikau` : Jellyfin (`media.lyes.eu`)
|
||||
- `mogma` : VPN NetNS Configuration
|
||||
- `nayru` : Komga/Manga (`manga.lyes.eu`)
|
||||
- `taf` : Mail (`taf.lyes.eu`/`mail.lyes.eu`)
|
||||
- `tetra` : Torrent (`torrent.lyes.eu`)
|
||||
|
|
|
|||
26
modules/server/mogma/README.md
Normal file
26
modules/server/mogma/README.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
= VPN NetNS
|
||||
|
||||
== Credit
|
||||
|
||||
The modules here come from https://codeberg.org/loutr/dotfiles, and is thus
|
||||
licensed under the AGPLv3.
|
||||
|
||||
== License
|
||||
|
||||
A collection of NixOS modules for various apps and services,
|
||||
as well as their uses in host configurations.
|
||||
Copyright (C) 2016-2021 Henrik Lissner
|
||||
Copyright (C) 2021-2024 Lucas Tabary-Maujean
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
128
modules/server/mogma/default.nix
Normal file
128
modules/server/mogma/default.nix
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
options,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.networking.vpn-netns;
|
||||
in
|
||||
|
||||
{
|
||||
imports = [
|
||||
./encapsulation.nix
|
||||
./forwarding.nix
|
||||
];
|
||||
|
||||
options.networking.vpn-netns = with lib; {
|
||||
restrictedServices = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = "A list of valid systemd service names to be encapsulated in the vpn netns.";
|
||||
};
|
||||
|
||||
nameserver = mkOption {
|
||||
type = types.singleLineStr;
|
||||
description = "The DNS server associated with the wireguard connection.";
|
||||
};
|
||||
|
||||
wireguardInterface = mkOption {
|
||||
type = types.str;
|
||||
description = "The name of the wireguard interface.";
|
||||
};
|
||||
|
||||
wireguardOptions = mkOption {
|
||||
type = with types; submodule { freeformType = attrsOf anything; };
|
||||
description = "Regular wireguard settings used to setup interface ${wgInterface}.";
|
||||
};
|
||||
|
||||
interfaceNamespace = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "vpn";
|
||||
description = "The name of the encapsulating netns.";
|
||||
};
|
||||
|
||||
vethInterfaceName = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "vethvpn";
|
||||
description = "The name of the veth interface accross netns.";
|
||||
};
|
||||
|
||||
vethIP = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "10.0.0.1";
|
||||
description = "The veth IP address of encapsulated services";
|
||||
};
|
||||
|
||||
vethOuterIP = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "10.0.0.2";
|
||||
description = "The veth IP address of non-encapsulated services.";
|
||||
};
|
||||
|
||||
portForwarding = {
|
||||
enable = mkEnableOption "a port forwarding service.";
|
||||
|
||||
leaseDuration = mkOption {
|
||||
type = types.int;
|
||||
default = 60;
|
||||
description = "The NATPMP lease duration in seconds.";
|
||||
};
|
||||
|
||||
updateDuration = mkOption {
|
||||
type = types.int;
|
||||
default = 2;
|
||||
description = ''
|
||||
How long the update script takes (in seconds).
|
||||
This is substracted from the timer, so that leases do not get
|
||||
interrupted. Tweak this based on your hardware performance etc.
|
||||
'';
|
||||
};
|
||||
|
||||
endpoint = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = cfg.nameserver;
|
||||
description = "The VPN endpoint (with which to negotiate the lease).";
|
||||
};
|
||||
|
||||
temporaryPortRange = mkOption {
|
||||
type = options.networking.firewall.allowedUDPPortRanges.type.nestedTypes.elemType;
|
||||
default = {
|
||||
from = 30000;
|
||||
to = 30010;
|
||||
};
|
||||
description = ''
|
||||
The port range used for local port redirection. Make sure it doesn't
|
||||
interfere with other services, including the assignable port from your
|
||||
VPN provider.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
encapsulatedServices = mkOption {
|
||||
default = { };
|
||||
type =
|
||||
with types;
|
||||
attrsOf (submodule {
|
||||
options.enable = mkEnableOption "network namespace encapsulation for this service.";
|
||||
|
||||
options.portForwarding = {
|
||||
enable = mkEnableOption "port forwarding for this service.";
|
||||
|
||||
updateScript = mkOption {
|
||||
type = str;
|
||||
example = ''
|
||||
echo listenPort=$PORT > /var/lib/service/config.conf
|
||||
'';
|
||||
description = ''
|
||||
The script to apply everytime the forwarded port changes.
|
||||
The shell has access to the `$PORT` variable with the corresponding
|
||||
port. Be cautious, this script can perform arbitrary commands.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
77
modules/server/mogma/encapsulation.nix
Normal file
77
modules/server/mogma/encapsulation.nix
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.networking.vpn-netns;
|
||||
|
||||
netnsName = cfg.interfaceNamespace;
|
||||
vethOuterName = cfg.vethInterfaceName;
|
||||
vethEncapsulatedName = vethOuterName + "0";
|
||||
|
||||
outerIP = cfg.vethOuterIP + "/32";
|
||||
encapsulatedIP = cfg.vethIP + "/32";
|
||||
in
|
||||
|
||||
{
|
||||
config = lib.mkIf (cfg.restrictedServices != [ ]) {
|
||||
systemd.services = builtins.listToAttrs (
|
||||
builtins.map (name: {
|
||||
inherit name;
|
||||
value = {
|
||||
after = [ "wireguard.target" ];
|
||||
# preStart = "sleep 3";
|
||||
serviceConfig.NetworkNamespacePath = "/var/run/netns/${netnsName}";
|
||||
};
|
||||
}) cfg.restrictedServices
|
||||
);
|
||||
|
||||
networking = {
|
||||
wireguard.interfaces.${cfg.wireguardInterface} =
|
||||
let
|
||||
ip = "${pkgs.iproute2}/bin/ip";
|
||||
in
|
||||
{
|
||||
preSetup = ''
|
||||
# clean interfaces
|
||||
${ip} netns delete "${netnsName}" 2> /dev/null || true
|
||||
${ip} link delete ${vethOuterName} 2> /dev/null || true
|
||||
rm -rf /etc/netns/${netnsName}
|
||||
|
||||
# add a namespace-specific resolv.conf
|
||||
mkdir -p "/etc/netns/${netnsName}"
|
||||
sed '/nameserver /d' /etc/resolv.conf > "/etc/netns/${netnsName}/resolv.conf"
|
||||
echo "nameserver ${cfg.nameserver}" >> "/etc/netns/${netnsName}/resolv.conf"
|
||||
|
||||
# create the network namespace
|
||||
${ip} netns add "${netnsName}"
|
||||
${ip} -n "${netnsName}" link set lo up
|
||||
|
||||
# Add a custom link between netns
|
||||
${ip} link add ${vethOuterName} type veth peer name ${vethEncapsulatedName}
|
||||
${ip} link set ${vethEncapsulatedName} netns ${netnsName}
|
||||
${ip} addr add ${outerIP} dev ${vethOuterName}
|
||||
${ip} link set ${vethOuterName} up
|
||||
${ip} route add ${encapsulatedIP} dev ${vethOuterName}
|
||||
${ip} -n ${netnsName} addr add ${encapsulatedIP} dev ${vethEncapsulatedName}
|
||||
${ip} -n ${netnsName} link set ${vethEncapsulatedName} up
|
||||
${ip} -n ${netnsName} route add ${outerIP} dev ${vethEncapsulatedName}
|
||||
'';
|
||||
|
||||
postShutdown = ''
|
||||
${ip} netns delete "${netnsName}" || true
|
||||
${ip} link delete ${vethOuterName} || true
|
||||
rm -rf /etc/netns/${netnsName}
|
||||
'';
|
||||
|
||||
inherit (cfg) interfaceNamespace;
|
||||
}
|
||||
// cfg.wireguardOptions;
|
||||
|
||||
firewall.trustedInterfaces = [ cfg.vethInterfaceName ];
|
||||
};
|
||||
};
|
||||
}
|
||||
103
modules/server/mogma/forwarding.nix
Normal file
103
modules/server/mogma/forwarding.nix
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.networking.vpn-netns;
|
||||
|
||||
inherit (cfg.portForwarding) leaseDuration updateDuration endpoint;
|
||||
|
||||
forwardedServices = lib.filterAttrs (
|
||||
_: value: value.enable && value.portForwarding.enable
|
||||
) cfg.encapsulatedServices;
|
||||
|
||||
assignCount =
|
||||
{ acc, temporaryPort }:
|
||||
name: value: {
|
||||
acc = acc ++ [
|
||||
{
|
||||
inherit name temporaryPort;
|
||||
inherit (value.portForwarding) updateScript;
|
||||
}
|
||||
];
|
||||
temporaryPort = temporaryPort + 1;
|
||||
};
|
||||
|
||||
forwardedServicesWithTmpPort = lib.foldlAttrs assignCount {
|
||||
acc = [ ];
|
||||
temporaryPort = cfg.portForwarding.temporaryPortRange.from;
|
||||
} forwardedServices;
|
||||
|
||||
serviceList = lib.mapAttrsToList (name: _: name + ".service") forwardedServices;
|
||||
in
|
||||
lib.mkIf (forwardedServices != { } && cfg.portForwarding.enable) {
|
||||
assertions = [
|
||||
{
|
||||
assertion =
|
||||
forwardedServicesWithTmpPort.temporaryPort <= cfg.portForwarding.temporaryPortRange.to + 1;
|
||||
message = ''
|
||||
vpn forwarding: not enough temporary ports.
|
||||
Increase the range of vpn.endpoint.temporaryPortRange.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
systemd = {
|
||||
services.natpmpc-lease = {
|
||||
description = "Request VPN port forwarding leases.";
|
||||
|
||||
wantedBy = serviceList;
|
||||
after = [ "wireguard.target" ];
|
||||
wants = [ "wireguard.target" ];
|
||||
|
||||
# preStart = "sleep 3";
|
||||
|
||||
path = with pkgs; [
|
||||
libnatpmp
|
||||
iptables
|
||||
];
|
||||
|
||||
script = lib.concatMapStrings (
|
||||
{
|
||||
temporaryPort,
|
||||
name,
|
||||
updateScript,
|
||||
}:
|
||||
let
|
||||
temporaryPort' = toString temporaryPort;
|
||||
leaseDuration' = toString leaseDuration;
|
||||
in
|
||||
''
|
||||
natpmpc -g ${endpoint} -a 1 ${temporaryPort'} udp ${leaseDuration'} > /dev/null
|
||||
PORT=$(natpmpc -g ${endpoint} -a 1 ${temporaryPort'} tcp ${leaseDuration'} |
|
||||
grep -oP "Mapped public port \K\d+")
|
||||
|
||||
iptables -t nat -C PREROUTING -p udp --dport ${temporaryPort'} -j REDIRECT --to-port "$PORT" 2>/dev/null || (
|
||||
iptables -t nat -A PREROUTING -p tcp --dport ${temporaryPort'} -j REDIRECT --to-port "$PORT";
|
||||
iptables -t nat -A PREROUTING -p udp --dport ${temporaryPort'} -j REDIRECT --to-port "$PORT"
|
||||
)
|
||||
|
||||
echo "Changing settings for service ${name} with port $PORT, if needed"
|
||||
${updateScript}
|
||||
''
|
||||
) forwardedServicesWithTmpPort.acc;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
TimeoutStartSec = 60; # toString (2 * updateDuration);
|
||||
NetworkNamespacePath = "/var/run/netns/${cfg.interfaceNamespace}";
|
||||
};
|
||||
};
|
||||
|
||||
timers.natpmpc-lease = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnUnitActiveSec = toString (leaseDuration - updateDuration);
|
||||
Persistent = "yes";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
{ ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
libnatpmp
|
||||
];
|
||||
|
||||
services.qbittorrent = {
|
||||
enable = false;
|
||||
enable = true;
|
||||
user = "qbittorrent";
|
||||
group = "media";
|
||||
|
||||
|
|
@ -33,13 +37,47 @@
|
|||
Username = "lyes";
|
||||
Password_PBKDF2 = "@ByteArray(5UU0KdjkWdtIdml1aQVDOQ==:qs0cVTkuQzbHA3EmF9++MK9eJstbx95hIR52amh2PSSgmQxrXavu0oxUZdUMWnaIRKkUuq18o9GV+DMb7T99NA==)";
|
||||
AuthSubnetWhitelistEnabled = true;
|
||||
# AuthSubnetWhitelist = "192.168.2.2/32";
|
||||
AuthSubnetWhitelist = "192.168.2.2/32";
|
||||
StatusbarExternalIPDisplayed = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
networking.vpn-netns.encapsulatedServices.qbittorrent = {
|
||||
enable = true;
|
||||
|
||||
portForwarding = {
|
||||
enable = true;
|
||||
|
||||
updateScript =
|
||||
let
|
||||
configFile = "/var/lib/qbittorrent/qBittorrent/config/qBittorrent.conf";
|
||||
passwordFile = config.age.secrets.tetra-pass.path;
|
||||
apiSetPreferenceUrl = "http://${config.networking.vpn-netns.vethIP}:${toString config.services.qbittorrent.webuiPort}/api/v2/app/setPreferences";
|
||||
curl = lib.getExe pkgs.curl;
|
||||
ip = "${pkgs.iproute2}/bin/ip";
|
||||
in
|
||||
''
|
||||
CURRENT_PORT=$(cat ${configFile} | grep 'Session\\Port' | cut -d '=' -f 2)
|
||||
PASS=$(cat ${passwordFile})
|
||||
test "$PORT" -eq "$CURRENT_PORT" || (
|
||||
${ip} netns exec netns-mogma ${curl} -i -X POST -d "json={\"random_port\": false}" "${apiSetPreferenceUrl}"
|
||||
${ip} netns exec netns-mogma ${curl} -i -X POST -d "json={\"listen_port\": $PORT}" "${apiSetPreferenceUrl}"
|
||||
)
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
age.secrets = {
|
||||
tetra-pass = {
|
||||
file = ../../../secrets/zora/services/tetra-pass.age;
|
||||
mode = "770";
|
||||
owner = "qbittorrent";
|
||||
group = "media";
|
||||
};
|
||||
};
|
||||
|
||||
# users.users.qbittorrent.extraGroups = [ "media" ];
|
||||
users.users.qbittorrent.isSystemUser = true;
|
||||
users.users.qbittorrent.group = "media";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue