feat[server]: add home proxy

This commit is contained in:
Leon Schwarzäugl 2026-01-04 17:45:53 +01:00 committed by Leon Schwarzäugl
parent 75891c3103
commit c1c7431891
84 changed files with 2961 additions and 1601 deletions

View file

@ -1,7 +1,7 @@
{ config, lib, ... }:
let
mapperTarget = lib.swarselsystems.mkIfElse config.swarselsystems.isCrypted "/dev/mapper/cryptroot" "/dev/disk/by-label/nixos";
inherit (config.swarselsystems) isImpermanence isCrypted;
inherit (config.swarselsystems) isImpermanence isCrypted isBtrfs;
in
{
options.swarselmodules.impermanence = lib.mkEnableOption "impermanence config";
@ -17,7 +17,7 @@ in
# So if it doesn't run, the btrfs system effectively acts like a normal system
# Taken from https://github.com/NotAShelf/nyx/blob/2a8273ed3f11a4b4ca027a68405d9eb35eba567b/modules/core/common/system/impermanence/default.nix
boot.tmp.useTmpfs = lib.mkIf (!isImpermanence) true;
boot.initrd.systemd = lib.mkIf isImpermanence {
boot.initrd.systemd = lib.mkIf (isImpermanence && isBtrfs) {
enable = true;
services.rollback = {
description = "Rollback BTRFS root subvolume to a pristine state";

View file

@ -33,6 +33,7 @@ let
(splitPath "services.kanidm.provision.systems.oauth2")
(splitPath "sops.secrets")
(splitPath "swarselsystems.server.dns")
(splitPath "topology.self.services")
]
++ expandOptions (splitPath "networking.nftables.firewall") [ "zones" "rules" ]
++ expandOptions (splitPath "services.firezone.gateway") [ "enable" "name" "apiUrl" "tokenFile" "package" "logLevel" ]

View file

@ -1,11 +1,10 @@
{ self, inputs, ... }:
{ self, lib, config, inputs, microVMParent, nodes, ... }:
{
imports = [
inputs.disko.nixosModules.disko
inputs.home-manager.nixosModules.home-manager
inputs.impermanence.nixosModules.impermanence
inputs.lanzaboote.nixosModules.lanzaboote
inputs.microvm.nixosModules.host
inputs.microvm.nixosModules.microvm
inputs.nix-index-database.nixosModules.nix-index
inputs.nix-minecraft.nixosModules.minecraft-servers
@ -23,6 +22,51 @@
];
config = {
system.stateVersion = "23.05";
_module.args.dns = inputs.dns;
nix.settings.experimental-features = [
"nix-command"
"flakes"
];
systemd.services."systemd-networkd".environment.SYSTEMD_LOG_LEVEL = "debug";
# NOTE: this is needed, we dont import sevrer network module for microvms
globals.hosts.${config.node.name}.isHome = true;
fileSystems."/persist".neededForBoot = lib.mkForce true;
systemd.network.networks."10-vlan-services" = {
dhcpV6Config = {
WithoutRA = "solicit";
# duid-en is nice in principle, but I already have MAC info anyways for reservations
DUIDType = "link-layer";
};
# networkConfig = {
# IPv6PrivacyExtensions = "no";
# IPv6AcceptRA = false;
# };
ipv6AcceptRAConfig = {
DHCPv6Client = "always";
};
};
microvm = {
shares = [
{
tag = "persist";
source = "${lib.optionalString nodes.${microVMParent}.config.swarselsystems.isImpermanence "/persist"}/microvms/${config.networking.hostName}";
mountPoint = "/persist";
proto = "virtiofs";
}
];
# mount the writeable overlay so that we can use nix shells inside the microvm
volumes = [
{
image = "/tmp/nix-store-overlay-${config.networking.hostName}.img";
autoCreate = true;
mountPoint = config.microvm.writableStoreOverlay;
size = 1024;
}
];
};
};
}

View file

@ -1,13 +1,21 @@
{ config, lib, ... }:
{
# imports = [
# inputs.microvm.nixosModules.host
# ];
config = lib.mkIf (config.guests != { }) {
microvm = {
hypervisor = lib.mkDefault "qemu";
};
systemd.tmpfiles.settings."15-microvms" = builtins.listToAttrs (
map
(path: {
name = "${lib.optionalString config.swarselsystems.isImpermanence "/persist"}/microvms/${path}";
value = {
d = {
group = "kvm";
user = "microvm";
mode = "0750";
};
};
})
(builtins.attrNames config.guests)
);
};
}

View file

@ -1,13 +1,25 @@
{ lib, config, globals, ... }:
{ lib, config, globals, confLib, ... }:
let
inherit (confLib.static) webProxy;
in
{
topology.self = {
icon = lib.mkIf config.swarselsystems.isCloud "devices.cloud-server";
interfaces.wan = lib.mkIf config.swarselsystems.isCloud { };
interfaces.wg = lib.mkIf (config.swarselsystems.server.wireguard.isClient || config.swarselsystems.server.wireguard.isServer) {
addresses = [ globals.networks.twothreetunnel-wg.hosts.${config.node.name}.ipv4 ];
renderer.hidePhysicalConnections = true;
virtual = true;
type = "wireguard";
interfaces = {
wan = lib.mkIf (config.swarselsystems.isCloud && config.swarselsystems.server.localNetwork == "wan") { };
lan = lib.mkIf (config.swarselsystems.isCloud && config.swarselsystems.server.localNetwork == "lan") { };
wgProxy = lib.mkIf (config.swarselsystems.server.wireguard ? wgHome) {
addresses = [ globals.networks."${webProxy}-wg.hosts".${config.node.name}.ipv4 ];
renderer.hidePhysicalConnections = true;
virtual = true;
type = "wireguard";
};
wgHome = lib.mkIf (config.swarselsystems.server.wireguard ? wgHome) {
addresses = [ globals.networks.home-wgHome.hosts.${config.node.name}.ipv4 ];
renderer.hidePhysicalConnections = true;
virtual = true;
type = "wireguard";
};
};
};
}

View file

@ -3,10 +3,10 @@
networking = {
useDHCP = lib.mkForce false;
useNetworkd = true;
dhcpcd.enable = false;
renameInterfacesByMac = lib.mapAttrs (_: v: if (v ? mac) then v.mac else "") (
dhcpcd.enable = lib.mkIf (!config.swarselsystems.isMicroVM) false;
renameInterfacesByMac = lib.mkIf (!config.swarselsystems.isMicroVM) (lib.mapAttrs (_: v: if (v ? mac) then v.mac else "") (
config.repo.secrets.local.networking.networks or { }
);
));
};
systemd.network.enable = true;

View file

@ -0,0 +1,138 @@
{ self, inputs, lib, config, globals, dns, confLib, ... }:
let
inherit (confLib.gen { name = "adguardhome"; port = 3000; }) serviceName servicePort serviceAddress serviceDomain proxyAddress4 proxyAddress6;
inherit (confLib.static) isHome isProxied homeProxy homeProxyIf webProxy webProxyIf homeWebProxy dnsServer homeDnsServer homeServiceAddress nginxAccessRules;
homeServices = lib.attrNames (lib.filterAttrs (_: serviceCfg: serviceCfg.isHome) globals.services);
homeDomains = map (name: globals.services.${name}.domain) homeServices;
in
{
options = {
swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
};
config = lib.mkIf config.swarselmodules.server.${serviceName} {
globals = {
networks = {
${webProxyIf}.hosts = lib.mkIf isProxied {
${config.node.name}.firewallRuleForNode.${webProxy}.allowedTCPPorts = [ servicePort ];
};
${homeProxyIf}.hosts = lib.mkIf isHome {
${config.node.name}.firewallRuleForNode.${homeProxy}.allowedTCPPorts = [ servicePort ];
};
};
services.${serviceName} = {
domain = serviceDomain;
inherit proxyAddress4 proxyAddress6 isHome;
};
};
networking.firewall = {
allowedTCPPorts = [ 53 ];
allowedUDPPorts = [ 53 ];
};
services.adguardhome = {
enable = true;
mutableSettings = false;
host = "0.0.0.0";
port = servicePort;
settings = {
dns = {
bind_hosts = [
globals.networks.home-lan.vlans.services.hosts.${homeDnsServer}.ipv4
globals.networks.home-lan.vlans.services.hosts.${homeDnsServer}.ipv6
];
ratelimit = 300;
upstream_dns = [
"https://dns.cloudflare.com/dns-query"
"https://dns.google/dns-query"
"https://doh.mullvad.net/dns-query"
];
bootstrap_dns = [
"1.1.1.1"
"2606:4700:4700::1111"
"8.8.8.8"
"2001:4860:4860::8844"
];
dhcp.enabled = false;
};
filtering.rewrites = [
]
# Use the local mirror-proxy for some services (not necessary, just for speed)
++
map
(domain: {
inherit domain;
# FIXME: change to homeWebProxy once that is setup
answer = globals.networks.home-lan.vlans.services.hosts.${homeWebProxy}.ipv4;
# answer = globals.hosts.${webProxy}.wanAddress4;
})
homeDomains;
filters = [
{
name = "AdGuard DNS filter";
url = "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt";
enabled = true;
}
{
name = "AdAway Default Blocklist";
url = "https://adaway.org/hosts.txt";
enabled = true;
}
{
name = "OISD (Big)";
url = "https://big.oisd.nl";
enabled = true;
}
];
};
};
environment.persistence."/persist".directories = lib.mkIf config.swarselsystems.isImpermanence [
{
directory = "/var/lib/private/AdGuardHome";
mode = "0700";
}
];
nodes =
let
genNginx = toAddress: extraConfig: {
upstreams = {
${serviceName} = {
servers = {
"${toAddress}:${builtins.toString servicePort}" = { };
};
};
};
virtualHosts = {
"${serviceDomain}" = {
useACMEHost = globals.domains.main;
forceSSL = true;
acmeRoot = null;
oauth2 = {
enable = true;
allowedGroups = [ "adguardhome_access" ];
};
locations = {
"/" = {
proxyPass = "http://${serviceName}";
proxyWebsockets = true;
};
};
extraConfig = lib.mkIf (extraConfig != "") extraConfig;
};
};
};
in
{
${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = {
"${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6;
};
${webProxy}.services.nginx = genNginx serviceAddress "";
${homeWebProxy}.services.nginx = genNginx homeServiceAddress nginxAccessRules;
};
};
}

View file

@ -14,6 +14,12 @@ in
"${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6;
};
topology.self.services.${serviceName} = {
name = lib.swarselsystems.toCapitalized serviceName;
info = "https://${serviceDomain}";
# attic does not have a logo
};
globals = {
networks = {
${webProxyIf}.hosts = lib.mkIf isProxied {

View file

@ -0,0 +1,16 @@
{ lib, config, globals, confLib, ... }:
let
inherit (confLib.gen { name = "dns-home"; }) serviceName homeProxy;
in
{
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
config = lib.mkIf (config.swarselmodules.server.${serviceName}) {
networking.hosts = {
${globals.networks.home-lan.vlans.services.hosts.${homeProxy}.ipv4} = [ "server.${homeProxy}.${globals.domains.main}" ];
${globals.networks.home-lan.vlans.services.hosts.${homeProxy}.ipv6} = [ "server.${homeProxy}.${globals.domains.main}" ];
};
};
}

View file

@ -1,4 +1,4 @@
{ lib, pkgs, config, globals, confLib, dns, nodes, ... }:
{ self, lib, pkgs, config, globals, confLib, dns, nodes, ... }:
let
inherit (confLib.gen { name = "firezone"; dir = "/var/lib/private/firezone"; }) serviceName serviceDir serviceAddress serviceDomain proxyAddress4 proxyAddress6 isHome isProxied homeProxy webProxy homeProxyIf webProxyIf idmServer dnsServer;
inherit (config.swarselsystems) sopsFile;
@ -60,6 +60,12 @@ in
};
};
topology.self.services.${serviceName} = {
name = lib.swarselsystems.toCapitalized serviceName;
info = "https://${serviceDomain}";
icon = "${self}/files/topology-images/${serviceName}.png";
};
sops = {
secrets = {
kanidm-firezone-client = { inherit sopsFile; mode = "0400"; };
@ -314,12 +320,17 @@ in
};
services.firezone.gateway = {
enable = true;
logLevel = "trace";
# logLevel = "trace";
inherit (nodeCfg.node) name;
apiUrl = "wss://${globals.services.firezone.domain}/api/";
tokenFile = nodeCfg.sops.secrets.firezone-gateway-token.path;
package = nodePkgs.stable25_05.firezone-gateway; # newer versions of firezone-gateway are not compatible with server package
};
topology.self.services."${serviceName}-gateway" = {
name = lib.swarselsystems.toCapitalized "${serviceName} Gateway";
icon = "${self}/files/topology-images/${serviceName}.png";
};
};
${idmServer} =
let

View file

@ -1,5 +1,5 @@
# inspired by https://github.com/atropos112/nixos/blob/7fef652006a1c939f4caf9c8a0cb0892d9cdfe21/modules/garage.nix
{ lib, pkgs, config, globals, dns, confLib, ... }:
{ self, lib, pkgs, config, globals, dns, confLib, ... }:
let
inherit (confLib.gen {
name = "garage";
@ -81,6 +81,12 @@ in
"*.${subDomain}-web" = dns.lib.combinators.host proxyAddress4 proxyAddress6;
};
topology.self.services.${serviceName} = {
name = lib.swarselsystems.toCapitalized serviceName;
info = "https://${serviceDomain}";
icon = "${self}/files/topology-images/${serviceName}.png";
};
sops = {
secrets.garage-admin-token = { inherit sopsFile; };
secrets.garage-rpc-secret = { inherit sopsFile; };

View file

@ -1,4 +1,4 @@
{ lib, pkgs, config, globals, dns, confLib, ... }:
{ self, lib, pkgs, config, globals, dns, confLib, ... }:
let
inherit (confLib.gen { name = "homebox"; port = 7745; }) servicePort serviceName serviceDomain serviceAddress proxyAddress4 proxyAddress6 isHome isProxied homeProxy webProxy dnsServer homeProxyIf webProxyIf;
in
@ -10,7 +10,11 @@ in
"${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6;
};
topology.self.services.${serviceName}.info = "https://${serviceDomain}";
topology.self.services.${serviceName} = {
name = "Homebox";
info = "https://${serviceDomain}";
icon = "${self}/files/topology-images/${serviceName}.png";
};
globals = {
networks = {

View file

@ -13,6 +13,8 @@ in
"${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6;
};
topology.self.services.${serviceName}.info = "https://${serviceDomain}";
globals = {
networks = {
${webProxyIf}.hosts = lib.mkIf isProxied {

View file

@ -228,6 +228,7 @@ in
"radicale.access" = { };
"slink.access" = { };
"opkssh.access" = { };
"adguardhome.access" = { };
};
inherit (config.repo.secrets.local) persons;
@ -370,6 +371,11 @@ in
"email"
"profile"
];
"adguardhome.access" = [
"openid"
"email"
"profile"
];
};
preferShortUsername = true;
claimMaps.groups = {
@ -380,6 +386,7 @@ in
"firefly.access" = [ "firefly_access" ];
"radicale.access" = [ "radicale_access" ];
"slink.access" = [ "slink_access" ];
"adguardhome.access" = [ "adguardhome_access" ];
};
};
};

View file

@ -62,7 +62,6 @@ in
virtualHosts = {
"${serviceDomain}" = {
useACMEHost = globals.domains.main;
forceSSL = true;
acmeRoot = null;
locations = {

View file

@ -1,6 +1,6 @@
{ lib, config, globals, confLib, ... }:
{ self, lib, config, globals, confLib, ... }:
let
inherit (confLib.gen { name = "kea"; dir = "/var/lib/private/kea"; }) serviceName serviceDir;
inherit (confLib.gen { name = "kea"; dir = "/var/lib/private/kea"; }) serviceName serviceDir homeDnsServer;
dhcpX = intX:
let
x = builtins.toString intX;
@ -8,6 +8,7 @@ let
{
enable = true;
settings = {
reservations-out-of-pool = true;
lease-database = {
name = "/var/lib/kea/dhcp${x}.leases";
persist = true;
@ -24,37 +25,44 @@ let
inherit (vlanCfg) id;
interface = "me-${vlanName}";
subnet = vlanCfg."cidrv${x}";
rapid-commit = lib.mkIf (intX == 6) true;
pools = [
{
pool = "${lib.net.cidr.host 20 vlanCfg."cidrv${x}"} - ${lib.net.cidr.host (-6) vlanCfg."cidrv${x}"}";
}
];
pd-pools = lib.mkIf (intX == 6) [
{
prefix = builtins.replaceStrings [ "::" ] [ ":0:0:100::" ] (lib.head (lib.splitString "/" vlanCfg.cidrv6));
prefix-len = 56;
delegated-len = 64;
}
];
option-data =
lib.optional (intX == 4)
{
name = "routers";
data = vlanCfg.hosts.hintbooth."ipv${x}"; # FIXME: how to advertise v6 address also?
data = vlanCfg.hosts.hintbooth."ipv${x}";
}
# Advertise DNS server for VLANS that have internet access
++
lib.optional
(lib.elem vlanName globals.general.internetVLANs)
{
name = if (intX == 4) then "domain-name-servers" else "dns-servers";
data = globals.networks.home-lan.vlans.services.hosts.${homeDnsServer}."ipv${x}";
};
# Advertise DNS server for VLANS that have internet access
# ++
# lib.optional
# (lib.elem vlanName [
# "services"
# "home"
# "devices"
# "guests"
# ])
# {
# name = "domain-name-servers";
# data = globals.networks.home-lan.vlans.services.hosts.hintbooth-adguardhome.ipv4;
# };
reservations = lib.concatLists (
lib.forEach (builtins.attrValues vlanCfg.hosts) (
hostCfg:
lib.optional (hostCfg.mac != null) {
hw-address = hostCfg.mac;
hw-address = lib.mkIf (intX == 4) hostCfg.mac;
duid = lib.mkIf (intX == 6) "00:03:00:01:${hostCfg.mac}"; # 00:03 = duid type 3; 00:01 = ethernet
ip-address = lib.mkIf (intX == 4) hostCfg."ipv${x}";
ip-addresses = lib.mkIf (intX == 6) [ hostCfg."ipv${x}" ];
prefixes = lib.mkIf (intX == 6) [
"${builtins.replaceStrings ["::"] [":0:0:${builtins.toString (256 + hostCfg.id)}::"] (lib.head (lib.splitString "/" vlanCfg.cidrv6))}/64"
];
}
)
);
@ -73,6 +81,14 @@ in
{ directory = serviceDir; mode = "0700"; }
];
topology = {
extractors.kea.enable = false;
self.services.${serviceName} = {
name = lib.swarselsystems.toCapitalized serviceName;
icon = "${self}/files/topology-images/${serviceName}.png";
};
};
services.kea = {
dhcp4 = dhcpX 4;
dhcp6 = dhcpX 6;

View file

@ -13,6 +13,10 @@ in
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
config = lib.mkIf config.swarselmodules.server.${serviceName} {
swarselmodules.server = {
podman = true;
postgresql = true;
};
nodes.${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = {
"${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6;

View file

@ -1,4 +1,4 @@
{ lib, config, globals, dns, confLib, ... }:
{ self, lib, config, globals, dns, confLib, ... }:
let
inherit (config.swarselsystems) sopsFile;
inherit (confLib.gen { name = "mailserver"; dir = "/var/lib/dovecot"; user = "virtualMail"; group = "virtualMail"; port = 443; }) serviceName serviceDir servicePort serviceUser serviceGroup serviceAddress serviceDomain proxyAddress4 proxyAddress6 isHome webProxy dnsServer;
@ -32,6 +32,16 @@ in
};
};
topology.self.services = lib.listToAttrs (map
(service:
lib.nameValuePair "${service}" {
name = lib.swarselsystems.toCapitalized service;
info = lib.mkIf (service == "postfix" || service == "roundcube") (if service == "postfix" then "https://${serviceDomain}" else "https://${roundcubeDomain}");
icon = "${self}/files/topology-images/${service}.png";
}
)
[ "postfix" "dovecot" "rspamd" "clamav" "roundcube" ]);
sops.secrets = {
user1-hashed-pw = { inherit sopsFile; owner = serviceUser; };
user2-hashed-pw = { inherit sopsFile; owner = serviceUser; };

View file

@ -1,4 +1,4 @@
{ lib, config, pkgs, globals, dns, confLib, ... }:
{ self, lib, config, pkgs, globals, dns, confLib, ... }:
let
inherit (confLib.gen { name = "minecraft"; port = 25565; dir = "/opt/minecraft"; proxy = config.node.name; }) serviceName servicePort serviceDir serviceDomain proxyAddress4 proxyAddress6 isHome dnsServer;
inherit (config.swarselsystems) mainUser;
@ -12,7 +12,11 @@ in
"${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6;
};
topology.self.services.${serviceName}.info = "https://${serviceDomain}";
topology.self.services.${serviceName} = {
name = "Minecraft";
info = "https://${serviceDomain}";
icon = "${self}/files/topology-images/${serviceName}.png";
};
globals.services.${serviceName} = {
domain = serviceDomain;

View file

@ -16,6 +16,8 @@ in
mpv
];
topology.self.services.${serviceName}.info = "https://${serviceDomain}";
users = {
groups = {
${serviceGroup} = {

View file

@ -40,6 +40,7 @@ in
nnf-drop.enable = true;
nnf-loopback.enable = true;
nnf-ssh.enable = true;
nnf-dhcpv6.enable = true;
};
rules.untrusted-to-local = {

View file

@ -67,7 +67,7 @@ in
};
};
config = {
extraConfig = lib.mkIf topmod.config.defaultStapling (lib.mkAfter ''
extraConfig = lib.mkIf topmod.config.defaultStapling (lib.mkBefore ''
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;

View file

@ -1,4 +1,4 @@
{ lib, config, globals, dns, confLib, ... }:
{ self, lib, config, globals, dns, confLib, ... }:
let
inherit (confLib.gen { name = "nsd"; port = 53; }) serviceName servicePort proxyAddress4 proxyAddress6;
inherit (config.swarselsystems) sopsFile;
@ -34,6 +34,11 @@ in
};
};
topology.self.services.${serviceName} = {
name = lib.toUpper serviceName;
icon = "${self}/files/topology-images/${serviceName}.png";
};
services.nsd = {
enable = true;
keys = {

View file

@ -3,7 +3,7 @@ with dns.lib.combinators; {
SOA = {
nameServer = "soa";
adminEmail = "admin@${globals.domains.main}"; # this option is not parsed as domain (we cannot just write "admin")
serial = 2025122401; # update this on changes for secondary dns
serial = 2026010201; # update this on changes for secondary dns
};
useOrigin = false;

View file

@ -1,6 +1,7 @@
{ lib, config, globals, dns, confLib, ... }:
let
inherit (confLib.gen { name = "oauth2-proxy"; port = 3004; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6 isHome isProxied homeProxy webProxy dnsServer homeProxyIf webProxyIf;
inherit (confLib.gen { name = "oauth2-proxy"; port = 3004; }) servicePort serviceName serviceUser serviceGroup serviceDomain serviceAddress proxyAddress4 proxyAddress6;
inherit (confLib.static) isHome isProxied homeProxy webProxy dnsServer homeProxyIf webProxyIf homeWebProxy oauthServer nginxAccessRules;
kanidmDomain = globals.services.kanidm.domain;
mainDomain = globals.domains.main;
@ -119,10 +120,6 @@ in
};
config = lib.mkIf config.swarselmodules.server.${serviceName} {
nodes.${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = {
"${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6;
};
sops = {
secrets = {
"oauth2-cookie-secret" = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0440"; };
@ -208,31 +205,41 @@ in
};
};
nodes.${webProxy}.services.nginx = {
upstreams = {
${serviceName} = {
servers = {
"${serviceAddress}:${builtins.toString servicePort}" = { };
};
};
};
virtualHosts = {
"${serviceDomain}" = {
useACMEHost = globals.domains.main;
forceSSL = true;
acmeRoot = null;
locations = {
"/" = {
proxyPass = "http://${serviceName}";
nodes =
let
genNginx = toAddress: extraConfig: {
upstreams = {
${serviceName} = {
servers = {
"${toAddress}:${builtins.toString servicePort}" = { };
};
};
};
virtualHosts = {
"${serviceDomain}" = {
useACMEHost = globals.domains.main;
forceSSL = true;
acmeRoot = null;
locations = {
"/" = {
proxyPass = "http://${serviceName}";
};
};
extraConfig = ''
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri;
'' + lib.optionalString (extraConfig != "") extraConfig;
};
};
extraConfig = ''
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri;
'';
};
in
{
${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = {
"${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6;
};
${webProxy}.services.nginx = genNginx serviceAddress "";
${homeWebProxy}.services.nginx = genNginx globals.hosts.${oauthServer}.wanAddress4 nginxAccessRules;
};
};
};
}

View file

@ -11,6 +11,7 @@ in
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
config = lib.mkIf config.swarselmodules.server.${serviceName} {
services.${serviceName} = {
enable = true;
user = serviceUser;

View file

@ -13,6 +13,8 @@
sops
tmux
busybox
ndisc6
tcpdump
swarsel-deploy
] ++ lib.optionals withHomeManager [
swarsel-gens

View file

@ -0,0 +1,38 @@
{ config, lib, ... }:
let
serviceName = "podman";
in
{
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
config = lib.mkIf config.swarselmodules.server.${serviceName} {
virtualisation = {
podman.enable = true;
oci-containers.backend = "podman";
};
networking.nftables.firewall = lib.mkIf config.networking.nftables.enable {
zones.podman = {
interfaces = [ "podman0" ];
};
rules = {
podman-to-postgres = lib.mkIf config.services.postgresql.enable {
from = [ "podman" ];
to = [ "local" ];
before = [ "drop" ];
allowedTCPPorts = [ config.services.postgresql.settings.port ];
};
local-to-podman = {
from = [ "local" "wgProxy" "wgHme" ];
to = [ "podman" ];
before = [ "drop" ];
verdict = "accept";
};
};
};
};
}

View file

@ -1,4 +1,4 @@
{ lib, config, globals, ... }:
{ lib, config, globals, confLib, ... }:
let
serviceName = "router";
bridgeVLANs = lib.mapAttrsToList
@ -9,6 +9,7 @@ let
selectVLANs = vlans: map (vlan: { VLAN = globals.networks.home-lan.vlans.${vlan}.id; }) vlans;
lan5VLANs = selectVLANs [ "home" "devices" "guests" ];
lan4VLANs = selectVLANs [ "home" "services" ];
inherit (confLib.gen { }) homeDnsServer;
in
{
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
@ -16,13 +17,27 @@ in
{
services.avahi.reflector = true;
topology.self.interfaces = (lib.mapAttrs'
(vlanName: _:
lib.nameValuePair "vlan-${vlanName}" {
network = lib.mkForce vlanName;
}
)
globals.networks.home-lan.vlans) // (lib.mapAttrs'
(vlanName: _:
lib.nameValuePair "me-${vlanName}" {
network = lib.mkForce vlanName;
}
)
globals.networks.home-lan.vlans);
networking.nftables = {
firewall = {
zones = {
untrusted.interfaces = [ "lan" ];
wgHome.interfaces = [ "wgHome" ];
adguardhome.ipv4Addresses = [ globals.networks.home-lan.vlans.services.hosts.hintbooth-adguardhome.ipv4 ];
adguardhome.ipv6Addresses = [ globals.networks.home-lan.vlans.services.hosts.hintbooth-adguardhome.ipv6 ];
adguardhome.ipv4Addresses = [ globals.networks.home-lan.vlans.services.hosts.${homeDnsServer}.ipv4 ];
adguardhome.ipv6Addresses = [ globals.networks.home-lan.vlans.services.hosts.${homeDnsServer}.ipv6 ];
}
// lib.flip lib.concatMapAttrs globals.networks.home-lan.vlans (
vlanName: _: {
@ -32,7 +47,7 @@ in
rules = {
masquerade-internet = {
from = map (name: "vlan-${name}") (builtins.attrNames globals.networks.home-lan.vlans);
from = map (name: "vlan-${name}") (globals.general.internetVLANs);
to = [ "untrusted" ];
# masquerade = true; NOTE: custom rule below for ip4 + ip6
late = true; # Only accept after any rejects have been processed
@ -41,7 +56,7 @@ in
# Allow access to the AdGuardHome DNS server from any VLAN that has internet access
access-adguardhome-dns = {
from = map (name: "vlan-${name}") (builtins.attrNames globals.networks.home-lan.vlans);
from = map (name: "vlan-${name}") (globals.general.internetVLANs);
to = [ "adguardhome" ];
verdict = "accept";
};
@ -61,7 +76,7 @@ in
services-to-local = {
from = [ "vlan-services" ];
to = [ "local" ];
allowedUDPPorts = [ 52829 ];
allowedUDPPorts = [ 52829 547 ];
};
# Forward traffic between wireguard participants
@ -79,7 +94,7 @@ in
late = true;
rules =
lib.forEach
(map (name: "vlan-${name}") (builtins.attrNames globals.networks.home-lan.vlans))
(map (name: "vlan-${name}") (globals.general.internetVLANs))
(
zone:
lib.concatStringsSep " " [
@ -227,8 +242,14 @@ in
IPv6AcceptRA = false;
};
ipv6Prefixes = [
{ Prefix = vlanCfg.cidrv6; }
{
Prefix = vlanCfg.cidrv6;
}
];
ipv6SendRAConfig = {
Managed = true; # set RA M flag -> DHCPv6 for addresses
OtherInformation = true; # optional, for “other info” via DHCPv6
};
linkConfig.RequiredForOnline = "routable";
};
}

View file

@ -12,6 +12,10 @@ in
};
config = lib.mkIf config.swarselmodules.server.${serviceName} {
swarselmodules.server = {
podman = true;
};
nodes.${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = {
"${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6;
};

View file

@ -10,6 +10,10 @@ in
};
config = lib.mkIf config.swarselmodules.server.${serviceName} {
swarselmodules.server = {
podman = true;
};
nodes.${dnsServer}.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = {
"${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6;
};

View file

@ -95,6 +95,14 @@ in
)
);
topology.self.interfaces = lib.mapAttrs'
(wgName: _:
lib.nameValuePair "${wgName}" {
network = wgName;
}
)
config.swarselsystems.server.wireguard.interfaces;
environment.systemPackages = with pkgs; [
wireguard-tools
];

View file

@ -1,27 +1,27 @@
{ self, config, lib, globals, inputs, outputs, minimal, nixosConfig ? null, ... }:
let
domainDefault = service: config.repo.secrets.common.services.domains.${service};
proxyDefault = config.swarselsystems.proxyHost;
addressDefault =
if
config.swarselsystems.proxyHost != config.node.name
then
if
config.swarselsystems.server.wireguard.interfaces.wgProxy.isClient
then
globals.networks."${config.swarselsystems.server.wireguard.interfaces.wgProxy.serverNetConfigPrefix}-wgProxy".hosts.${config.node.name}.ipv4
else
globals.networks.${config.swarselsystems.server.netConfigName}.hosts.${config.node.name}.ipv4
else
"localhost";
in
{
_module.args = {
confLib = rec {
addressDefault =
if
config.swarselsystems.proxyHost != config.node.name
then
if
config.swarselsystems.server.wireguard.interfaces.wgProxy.isClient
then
globals.networks."${config.swarselsystems.server.wireguard.interfaces.wgProxy.serverNetConfigPrefix}-wgProxy".hosts.${config.node.name}.ipv4
else
globals.networks.${config.swarselsystems.server.netConfigName}.hosts.${config.node.name}.ipv4
else
"localhost";
domainDefault = service: config.repo.secrets.common.services.domains.${service};
proxyDefault = config.swarselsystems.proxyHost;
getConfig = if nixosConfig == null then config else nixosConfig;
gen = { name, user ? name, group ? name, dir ? null, port ? null, domain ? (domainDefault name), address ? addressDefault, proxy ? proxyDefault }: rec {
gen = { name ? "n/a", user ? name, group ? name, dir ? null, port ? null, domain ? (domainDefault name), address ? addressDefault, proxy ? proxyDefault }: rec {
servicePort = port;
serviceName = name;
specificServiceName = "${name}-${config.node.name}";
@ -36,12 +36,28 @@
proxyAddress4 = globals.hosts.${proxy}.wanAddress4 or null;
proxyAddress6 = globals.hosts.${proxy}.wanAddress6 or null;
inherit (globals.hosts.${config.node.name}) isHome;
inherit (globals.general) homeProxy webProxy dnsServer idmServer;
inherit (globals.general) homeProxy webProxy dnsServer homeDnsServer homeWebProxy idmServer;
webProxyIf = "${webProxy}-wgProxy";
homeProxyIf = "home-wgHome";
isProxied = config.node.name != webProxy;
};
static = rec {
inherit (globals.hosts.${config.node.name}) isHome;
inherit (globals.general) homeProxy webProxy dnsServer homeDnsServer homeWebProxy idmServer oauthServer;
webProxyIf = "${webProxy}-wgProxy";
homeProxyIf = "home-wgHome";
isProxied = config.node.name != webProxy;
nginxAccessRules = ''
allow ${globals.networks.home-lan.vlans.home.cidrv4};
allow ${globals.networks.home-lan.vlans.home.cidrv6};
allow ${globals.networks.home-lan.vlans.services.hosts.${homeProxy}.ipv4};
allow ${globals.networks.home-lan.vlans.services.hosts.${homeProxy}.ipv6};
deny all;
'';
homeServiceAddress = lib.optionalString (config.swarselsystems.server.wireguard.interfaces ? wgHome) globals.networks."${config.swarselsystems.server.wireguard.interfaces.wgHome.serverNetConfigPrefix}-wgHome".hosts.${config.node.name}.ipv4;
};
mkMicrovm =
if config.swarselsystems.withMicroVMs then
(guestName: {
@ -49,7 +65,7 @@
backend = "microvm";
autostart = true;
modules = [
(config.node.configDir + /guests/${guestName}.nix)
(config.node.configDir + /guests/${guestName}/default.nix)
{
node.secretsDir = config.node.configDir + /secrets/${guestName};
node.configDir = config.node.configDir + /guests/${guestName};
@ -62,6 +78,7 @@
};
}
"${self}/modules/nixos/optional/microvm-guest.nix"
"${self}/modules/nixos/optional/systemd-networkd-base.nix"
];
microvm = {
system = config.node.arch;
@ -74,6 +91,7 @@
inherit inputs outputs minimal;
inherit (inputs) self;
withHomeManager = false;
microVMParent = config.node.name;
globals = outputs.globals.${config.node.arch};
};
};