Setting up tetra and mogma
This commit is contained in:
parent
0812b82c46
commit
34a686c562
12 changed files with 454 additions and 8 deletions
|
|
@ -15,6 +15,6 @@ This repo uses flakes, which can construct the following targets:
|
|||
- `zora` : For my servers hosted at the [Crans](https://crans.org/)
|
||||
- `triforce` : For my custom ISO creation
|
||||
|
||||
Unless indicated otherwise by a comment (when code is taken from elsewhere),
|
||||
everything in this repo is under the MIT-0 LICENSE. Meaning, you can copy
|
||||
everything here without any restriction (not even attribution).
|
||||
Unless indicated otherwise by a comment or file (when the code is taken from
|
||||
elsewhere), everything in this repo is under the MIT-0 LICENSE. Meaning, you
|
||||
can copy everything here without any restriction (not even attribution).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
{ ... }:
|
||||
{ config, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
../../modules/server/mogma
|
||||
];
|
||||
|
||||
# Networking
|
||||
networking = {
|
||||
hostName = "zora";
|
||||
|
|
@ -51,6 +55,51 @@
|
|||
};
|
||||
};
|
||||
|
||||
# VPN
|
||||
networking.vpn-netns = {
|
||||
wireguardInterface = "mogma";
|
||||
nameserver = "10.2.0.1";
|
||||
|
||||
interfaceNamespace = "netns-mogma";
|
||||
vethInterfaceName = "veth-mogma";
|
||||
|
||||
vethIP = "192.168.2.2";
|
||||
vethOuterIP = "192.168.2.1";
|
||||
|
||||
wireguardOptions = {
|
||||
privateKeyFile = config.age.secrets.mogma-privatekey.path;
|
||||
ips = [ "10.2.0.2/32" ];
|
||||
|
||||
peers = [
|
||||
{
|
||||
publicKey = "W4XqVNXMdnhtiRxWNzWThy3f7hRoT9NTx/HYu/jTaRU=";
|
||||
allowedIPs = [
|
||||
"0.0.0.0/0"
|
||||
"::/0"
|
||||
];
|
||||
endpoint = "79.127.169.89:51820";
|
||||
persistentKeepalive = 25;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
restrictedServices = [
|
||||
"qbittorrent"
|
||||
# "suwayomi-server"
|
||||
];
|
||||
|
||||
portForwarding = {
|
||||
enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
age.secrets = {
|
||||
mogma-privatekey = {
|
||||
file = ../../secrets/zora/services/mogma-privatekey.age;
|
||||
mode = "755";
|
||||
};
|
||||
};
|
||||
|
||||
# Imposing a bandwidth limit to avoid Aurore/Crans disruptions
|
||||
# networking.nftables = {
|
||||
# tables.rate_limit = {
|
||||
|
|
|
|||
|
|
@ -83,7 +83,15 @@
|
|||
"torrent.lyes.eu" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/".proxyPass = "http://localhost:${toString config.services.qbittorrent.webuiPort}";
|
||||
locations."/" = {
|
||||
proxyPass = "http://${config.networking.vpn-netns.vethIP}:${toString config.services.qbittorrent.webuiPort}";
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
# 9980
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -22,4 +22,6 @@ in
|
|||
"secrets/zora/services/biggoron-db-pass.age".publicKeys = all;
|
||||
"secrets/zora/services/biggoron-admin-pass.age".publicKeys = all;
|
||||
"secrets/zora/services/ptigoron-token.age".publicKeys = all;
|
||||
"secrets/zora/services/mogma-privatekey.age".publicKeys = all;
|
||||
"secrets/zora/services/tetra-pass.age".publicKeys = all;
|
||||
}
|
||||
|
|
|
|||
7
secrets/zora/services/mogma-privatekey.age
Normal file
7
secrets/zora/services/mogma-privatekey.age
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 whuRpQ UsWzTO1dXbNc3QQiQ3ucPSE+1tBSdYyLmxKH/wqOXUU
|
||||
l1iyFn+HmgihWdv6/4pwnR/7tpRRZE2WyD9NOBYpqX8
|
||||
-> ssh-ed25519 TFqgIg i7JnezH1VCIoZ689NVYTHsmFZHM6At3OADAg0a7hq0A
|
||||
w8hxox6Ti+wY6mniY/sjjoOLTuI61TFXWsrHOB/vlkE
|
||||
--- ckQG+2v4dN1rrpaNPci9mYjHl3Z7Boz4JAPB7c6EeBA
|
||||
äõ%ld§ÞC»ûÎB¬OÎÝx¢—ÓVöqhÙ™rîîTÓRÁ±/9<!7¨á…7›YÑA„« ÑÑ3ÙÉÜ>køîºà,k{É¿
|
||||
7
secrets/zora/services/tetra-pass.age
Normal file
7
secrets/zora/services/tetra-pass.age
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 whuRpQ 3poc5ONc1KrUFEJ5b+JJOl5DCJuKIqsCDgXsWJkTtU8
|
||||
WRr4d9BwyPiY8iVZORHRhcwVPbBqASRMRDvxNfPewiY
|
||||
-> ssh-ed25519 TFqgIg pb5iRBHs9Sru1cPtmZCAbZiRQFF+EnHdJKF3bnjNmwM
|
||||
Ap7yRwNQDoY2XDGdruH08qTDU3iGCa+Dk71+d8nnPE8
|
||||
--- srqiR5By2Q7OgTr9ciw10FFoD5pgISNc67O8wiNfTLc
|
||||
ûq§!è?ÛË%$KüG<>쬯 b>wGÉÕÜ*‘uÛZ§Aƒ²yÿ-Ú>t|<7C>ª«<C2AA>/C<>ÙÚ»œ
|
||||
Loading…
Add table
Add a link
Reference in a new issue