split up hopper lab configuration into multiple files

This commit is contained in:
xunuwu 2025-02-22 18:52:16 +01:00
parent f077fbe3fd
commit b69fbbcde9
Signed by: xun
SSH key fingerprint: SHA256:Uot/1WoAjWAeqLOHA5vYy4phhVydsH7jCPmBjaPZfgI
14 changed files with 435 additions and 404 deletions

View file

@ -0,0 +1,15 @@
{config, ...}: {
security.acme = {
acceptTerms = true;
defaults.email = "xunuwu@gmail.com";
certs = {
"xunuwu.xyz" = {
domain = "*.xunuwu.xyz";
dnsProvider = "cloudflare";
reloadServices = ["caddy.service"];
credentialFiles.CF_DNS_API_TOKEN_FILE = config.sops.secrets.cloudflare.path;
extraDomainNames = ["xunuwu.xyz"];
};
};
};
}

View file

@ -0,0 +1,39 @@
{
services.adguardhome = {
enable = true;
mutableSettings = false;
port = 23489;
settings = {
dhcp.enabled = false;
dns = {
upstream_dns = [
"quic://dns.nextdns.io"
"https://cloudflare-dns.com/dns-query"
"tls://unfiltered.adguard-dns.com"
"https://dns10.quad9.net/dns-query"
];
bind_hosts = ["100.115.105.144"];
bootstrap_dns = ["1.1.1.1" "8.8.8.8"];
};
filtering = {
rewrites = [
{
domain = "*.hopper.xun.host";
answer = "100.115.105.144";
}
{
domain = "hopper.xun.host";
answer = "100.115.105.144";
}
];
};
filters = [
{
name = "OISD (Big)";
url = "https://big.oisd.nl";
enabled = true;
}
];
};
};
}

View file

@ -0,0 +1,77 @@
{config, ...}: let
domain = "xunuwu.xyz";
caddyPort = 8336;
in {
systemd.services.caddy.vpnConfinement = {
enable = true;
vpnNamespace = "wg";
};
services.caddy = {
enable = true;
virtualHosts = {
jellyfin = {
useACMEHost = domain;
hostName = "jellyfin.${domain}:${toString caddyPort}";
extraConfig = ''
reverse_proxy {
header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}
to localhost:8096
}
'';
};
navidrome = {
useACMEHost = domain;
hostName = "navidrome.${domain}:${toString caddyPort}";
extraConfig = ''
reverse_proxy unix//var/lib/navidrome/navidrome.sock
'';
};
slskd = {
hostName = "slskd.hopper.xun.host:80";
extraConfig = ''
reverse_proxy localhost:${toString config.services.slskd.settings.web.port}
'';
};
transmission = {
hostName = "transmission.hopper.xun.host:80";
extraConfig = ''
reverse_proxy localhost:${toString config.services.transmission.settings.rpc-port}
'';
};
dash = {
hostName = "dash.hopper.xun.host:80";
extraConfig = ''
reverse_proxy localhost:${toString config.services.homepage-dashboard.listenPort}
'';
};
vw = {
useACMEHost = domain;
hostName = "vw.${domain}:${toString caddyPort}";
extraConfig = ''
reverse_proxy {
header_up X-Real-Ip {http.request.header.CF-Connecting-IP}
to localhost:${toString config.services.vaultwarden.config.ROCKET_PORT}
}
'';
};
other = {
useACMEHost = domain;
hostName = ":${toString caddyPort}";
extraConfig = ''
respond 404 {
body "uhh that doesnt exist, i hope this isnt my fault.."
}
'';
};
otherPriv = {
hostName = ":80";
extraConfig = ''
respond 404 {
body "uhh that doesnt exist, i hope this isnt my fault.."
}
'';
};
};
};
}

View file

@ -1,21 +1,20 @@
## TODO look into sops-nix placeholders
## reference: https://github.com/javigomezo/nixos/blob/b3ebe8d570ea9b37aea8bb3a343f6e16e054e322/services/network/authelia/user_database.nix
{
pkgs,
inputs,
config,
lib,
...
}: let
l = lib // builtins;
domain = "xunuwu.xyz";
caddyPort = 8336;
slskdUiPort = 23488;
adguardWebPort = 23489;
in {
imports = [
./acme.nix
./adguard.nix
./caddy.nix
./homepage.nix
./jellyfin.nix
./navidrome.nix
./prometheus.nix
./restic.nix
./samba.nix
inputs.authentik-nix.nixosModules.default
./slskd.nix
./transmission.nix
./vaultwarden.nix
./vpn-namespace.nix
];
users.groups.media = {};
@ -24,65 +23,6 @@ in {
group = "media";
};
security.acme = {
acceptTerms = true;
defaults.email = "xunuwu@gmail.com";
certs = {
${domain} = {
domain = "*.${domain}";
dnsProvider = "cloudflare";
reloadServices = ["caddy.service"];
credentialFiles.CF_DNS_API_TOKEN_FILE = config.sops.secrets.cloudflare.path;
extraDomainNames = [domain];
};
};
};
vpnNamespaces."wg" = {
enable = true;
wireguardConfigFile = config.sops.secrets.wireguard.path;
accessibleFrom = [
"192.168.0.0/24"
# "127.0.0.1"
];
# Forwarded to my vpn, for making things accessible from outside
openVPNPorts = [
{
port = caddyPort;
protocol = "tcp";
}
{
port = config.services.slskd.settings.soulseek.listen_port;
protocol = "both";
}
{
port = config.services.slskd.settings.soulseek.listen_port + 1;
protocol = "both";
}
{
port = config.services.transmission.settings.peer-port;
protocol = "both";
}
];
# From inside of the vpn namespace to outside of it, for making things inside accessible to LAN
portMappings = let
passthrough = [
caddyPort
slskdUiPort
80 # caddy
443 # caddy
1900 # jellyfin discovery
7359 # jellyfin discovery
];
in (l.map (x: {
from = x;
to = x;
})
passthrough);
};
networking.firewall = {
allowedUDPPorts = [1900 7359]; # Jellyfin auto-discovery
allowedTCPPorts = [
@ -93,337 +33,5 @@ in {
];
};
systemd.services.caddy.vpnConfinement = {
enable = true;
vpnNamespace = "wg";
};
services.caddy = {
enable = true;
virtualHosts = {
jellyfin = {
useACMEHost = domain;
hostName = "jellyfin.${domain}:${toString caddyPort}";
extraConfig = ''
reverse_proxy {
header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}
to localhost:8096
}
'';
};
navidrome = {
useACMEHost = domain;
hostName = "navidrome.${domain}:${toString caddyPort}";
extraConfig = ''
reverse_proxy unix//var/lib/navidrome/navidrome.sock
'';
};
slskd = {
hostName = "slskd.hopper.xun.host:80";
extraConfig = ''
reverse_proxy localhost:${toString config.services.slskd.settings.web.port}
'';
};
transmission = {
hostName = "transmission.hopper.xun.host:80";
extraConfig = ''
reverse_proxy localhost:${toString config.services.transmission.settings.rpc-port}
'';
};
dash = {
hostName = "dash.hopper.xun.host:80";
extraConfig = ''
reverse_proxy localhost:${toString config.services.homepage-dashboard.listenPort}
'';
};
vw = {
useACMEHost = domain;
hostName = "vw.${domain}:${toString caddyPort}";
extraConfig = ''
reverse_proxy {
header_up X-Real-Ip {http.request.header.CF-Connecting-IP}
to localhost:${toString config.services.vaultwarden.config.ROCKET_PORT}
}
'';
};
other = {
useACMEHost = domain;
hostName = ":${toString caddyPort}";
extraConfig = ''
respond 404 {
body "uhh that doesnt exist, i hope this isnt my fault.."
}
'';
};
otherPriv = {
hostName = ":80";
extraConfig = ''
respond 404 {
body "uhh that doesnt exist, i hope this isnt my fault.."
}
'';
};
};
};
systemd.services.homepage-dashboard.vpnConfinement = {
enable = true;
vpnNamespace = "wg";
};
services.homepage-dashboard = {
enable = true;
widgets = [
{
resources = {
cpu = true;
disk = "/";
uptime = "";
units = "metric";
cputemp = true;
memory = true;
network = true;
};
}
];
services = [
{
"Downloading" = [
{
"transmission" = {
href = "http://transmission.hopper.xun.host";
icon = "transmission";
};
}
{
"slskd" = {
href = "http://slskd.hopper.xun.host";
icon = "slskd";
};
}
];
}
{
"Services" = [
{
"jellyfin" = {
href = "https://jellyfin.${domain}";
icon = "jellyfin";
};
}
{
"navidrome" = {
href = "https://navidrome.${domain}";
icon = "navidrome";
};
}
{
"adguard home" = {
href = "http://${config.networking.hostName}:${toString config.services.adguardhome.port}";
icon = "adguard-home";
};
}
{
"prometheus" = {
href = "http://${config.networking.hostName}:${toString config.services.prometheus.port}";
icon = "prometheus";
};
}
{
"vaultwarden" = {
href = "https://vw.${domain}";
icon = "vaultwarden";
};
}
];
}
];
};
systemd.services.jellyfin.vpnConfinement = {
enable = true;
vpnNamespace = "wg";
};
services.jellyfin.enable = true;
services.prometheus = {
enable = true;
port = 9001;
extraFlags = ["--storage.tsdb.retention.time=30d"];
scrapeConfigs = [
{
job_name = "node";
static_configs = lib.singleton {
targets = ["127.0.0.1:${toString config.services.prometheus.exporters.node.port}"];
};
}
{
job_name = "tailscale_client";
static_configs = lib.singleton {
targets = ["100.100.100.100"];
};
}
];
};
services.prometheus.exporters = {
node = {
enable = true;
enabledCollectors = ["systemd"];
};
systemd.enable = true;
};
systemd.services.slskd.vpnConfinement = {
enable = true;
vpnNamespace = "wg";
};
services.slskd = {
enable = true;
environmentFile = config.sops.secrets.slskd.path;
domain = null; # why isnt this the default?
settings = {
metrics = {
enabled = true;
authentication.disabled = true;
};
remote_file_management = true;
shares.directories = ["/media/library/music"];
soulseek = {
listen_port = 26449;
description = "";
};
global = {
upload = {
slots = 50;
speed_limit = 10000;
};
download.speed_limit = 10000;
};
};
};
systemd.services.transmission.vpnConfinement = {
enable = true;
vpnNamespace = "wg";
};
services.transmission = {
enable = true;
package = pkgs.transmission_4;
performanceNetParameters = true;
settings = let
mbit = 125;
in {
speed-limit-up-enabled = true;
speed-limit-up = 100 * mbit;
speed-limit-down-enabled = true;
speed-limit-down = 150 * mbit;
rpc-authentication-required = true;
peer-port = 11936;
rpc-bind-address = "0.0.0.0";
rpc-whitelist = "127.0.0.1,192.168.\*.\*";
};
credentialsFile = config.sops.secrets.transmission.path;
};
boot.kernel.sysctl."fs.inotify.max_user_watches" = 99999999;
services.adguardhome = {
enable = true;
mutableSettings = false;
port = adguardWebPort;
settings = {
dhcp.enabled = false;
dns = {
upstream_dns = [
"quic://dns.nextdns.io"
"https://cloudflare-dns.com/dns-query"
"tls://unfiltered.adguard-dns.com"
"https://dns10.quad9.net/dns-query"
];
bind_hosts = ["100.115.105.144"];
bootstrap_dns = ["1.1.1.1" "8.8.8.8"];
};
filtering = {
rewrites = [
{
domain = "*.hopper.xun.host";
answer = "100.115.105.144";
}
{
domain = "hopper.xun.host";
answer = "100.115.105.144";
}
];
};
filters = [
{
name = "OISD (Big)";
url = "https://big.oisd.nl";
enabled = true;
}
];
};
};
users.groups.${config.services.navidrome.group}.members = ["caddy"]; # for caddy to access socket file
services.navidrome = {
enable = true;
settings = {
MusicFolder = "/media/library/music";
Address = "unix:/var/lib/navidrome/navidrome.sock";
EnableSharing = true;
};
};
systemd.services.navidrome.serviceConfig.EnvironmentFile = config.sops.secrets.navidrome.path;
systemd.services.vaultwarden = {
serviceConfig.EnvironmentFile = config.sops.secrets.vaultwarden-env.path;
vpnConfinement = {
enable = true;
vpnNamespace = "wg";
};
};
# NOTE send doesnt work, probably due to my cloudflare port rewriting rules
services.vaultwarden = {
enable = true;
config = {
DOMAIN = "https://${domain}:${toString caddyPort}";
ROCKET_ADDRESS = "127.0.0.1";
ROCKET_PORT = 35381;
ROCKET_LOG = "critical";
SIGNUPS_ALLOWED = false;
};
};
services.restic.backups.hopper = {
initialize = true;
inhibitsSleep = true;
repository = "rest:http://nixdesk:8000/hopper";
passwordFile = config.sops.secrets.restic-password.path;
timerConfig = {
OnCalendar = "18:00";
Persistent = true;
RandomizedDelaySec = "1h";
};
pruneOpts = [
"--keep-daily 7"
"--keep-weekly 5"
"--keep-monthly 12"
"--keep-yearly 2"
];
paths = [
"/var/lib/navidrome"
"/var/lib/jellyfin/data"
"/var/lib/jellyfin/config"
"/var/lib/bitwarden_rs"
"/media/library/music"
];
exclude = [
"/var/lib/navidrome/cache"
];
};
}

View file

@ -0,0 +1,75 @@
{config, ...}: {
systemd.services.homepage-dashboard.vpnConfinement = {
enable = true;
vpnNamespace = "wg";
};
services.homepage-dashboard = {
enable = true;
widgets = [
{
resources = {
cpu = true;
disk = "/";
uptime = "";
units = "metric";
cputemp = true;
memory = true;
network = true;
};
}
];
services = [
{
"Downloading" = [
{
"transmission" = {
href = "http://transmission.hopper.xun.host";
icon = "transmission";
};
}
{
"slskd" = {
href = "http://slskd.hopper.xun.host";
icon = "slskd";
};
}
];
}
{
"Services" = [
{
"jellyfin" = {
href = "https://jellyfin.xunuwu.xyz";
icon = "jellyfin";
};
}
{
"navidrome" = {
href = "https://navidrome.xunuwu.xyz";
icon = "navidrome";
};
}
{
"adguard home" = {
href = "http://${config.networking.hostName}:${toString config.services.adguardhome.port}";
icon = "adguard-home";
};
}
{
"prometheus" = {
href = "http://${config.networking.hostName}:${toString config.services.prometheus.port}";
icon = "prometheus";
};
}
{
"vaultwarden" = {
href = "https://vw.xunuwu.xyz";
icon = "vaultwarden";
};
}
];
}
];
};
}

View file

@ -0,0 +1,12 @@
{
systemd.services.jellyfin.vpnConfinement = {
enable = true;
vpnNamespace = "wg";
};
services.jellyfin.enable = true;
services.restic.backups.hopper.paths = [
"/var/lib/jellyfin/data"
"/var/lib/jellyfin/config"
];
}

View file

@ -0,0 +1,17 @@
{config, ...}: {
users.groups.${config.services.navidrome.group}.members = ["caddy"]; # for caddy to access socket file
services.navidrome = {
enable = true;
settings = {
MusicFolder = "/media/library/music";
Address = "unix:/var/lib/navidrome/navidrome.sock";
EnableSharing = true;
};
};
systemd.services.navidrome.serviceConfig.EnvironmentFile = config.sops.secrets.navidrome.path;
services.restic.backups.hopper = {
paths = ["/var/lib/navidrome"];
exclude = ["/var/lib/navidrome/cache"];
};
}

View file

@ -0,0 +1,33 @@
{
lib,
config,
...
}: {
services.prometheus = {
enable = true;
port = 9001;
extraFlags = ["--storage.tsdb.retention.time=30d"];
scrapeConfigs = [
{
job_name = "node";
static_configs = lib.singleton {
targets = ["127.0.0.1:${toString config.services.prometheus.exporters.node.port}"];
};
}
{
job_name = "tailscale_client";
static_configs = lib.singleton {
targets = ["100.100.100.100"];
};
}
];
};
services.prometheus.exporters = {
node = {
enable = true;
enabledCollectors = ["systemd"];
};
systemd.enable = true;
};
}

View file

@ -0,0 +1,22 @@
{config, ...}: {
services.restic.backups.hopper = {
initialize = true;
inhibitsSleep = true;
repository = "rest:http://nixdesk:8000/hopper";
passwordFile = config.sops.secrets.restic-password.path;
timerConfig = {
OnCalendar = "18:00";
Persistent = true;
RandomizedDelaySec = "1h";
};
pruneOpts = [
"--keep-daily 7"
"--keep-weekly 5"
"--keep-monthly 12"
"--keep-yearly 2"
];
paths = [
"/media/library/music"
];
};
}

View file

@ -62,4 +62,6 @@
};
};
};
services.restic.backups.hopper.paths = ["/srv/vault"];
}

View file

@ -0,0 +1,31 @@
{config, ...}: {
systemd.services.slskd.vpnConfinement = {
enable = true;
vpnNamespace = "wg";
};
services.slskd = {
enable = true;
environmentFile = config.sops.secrets.slskd.path;
domain = null; # why isnt this the default?
settings = {
metrics = {
enabled = true;
authentication.disabled = true;
};
remote_file_management = true;
shares.directories = ["/media/library/music"];
soulseek = {
listen_port = 26449;
description = "";
};
global = {
upload = {
slots = 50;
speed_limit = 10000;
};
download.speed_limit = 10000;
};
};
};
}

View file

@ -0,0 +1,29 @@
{
pkgs,
config,
...
}: {
systemd.services.transmission.vpnConfinement = {
enable = true;
vpnNamespace = "wg";
};
services.transmission = {
enable = true;
package = pkgs.transmission_4;
performanceNetParameters = true;
settings = let
mbit = 125;
in {
speed-limit-up-enabled = true;
speed-limit-up = 100 * mbit;
speed-limit-down-enabled = true;
speed-limit-down = 150 * mbit;
rpc-authentication-required = true;
peer-port = 11936;
rpc-bind-address = "0.0.0.0";
rpc-whitelist = "127.0.0.1,192.168.\*.\*";
};
credentialsFile = config.sops.secrets.transmission.path;
};
}

View file

@ -0,0 +1,22 @@
{config, ...}: {
systemd.services.vaultwarden = {
serviceConfig.EnvironmentFile = config.sops.secrets.vaultwarden-env.path;
vpnConfinement = {
enable = true;
vpnNamespace = "wg";
};
};
# NOTE send doesnt work, probably due to my cloudflare port rewriting rules
services.vaultwarden = {
enable = true;
config = {
DOMAIN = "https://${config.services.caddy.virtualHosts.vw.hostName}";
ROCKET_ADDRESS = "127.0.0.1";
ROCKET_PORT = 35381;
ROCKET_LOG = "critical";
SIGNUPS_ALLOWED = false;
};
};
services.restic.backups.hopper.paths = ["/var/lib/bitwarden_rs"];
}

View file

@ -0,0 +1,49 @@
{
config,
lib,
...
}: {
vpnNamespaces."wg" = {
enable = true;
wireguardConfigFile = config.sops.secrets.wireguard.path;
accessibleFrom = [
"192.168.0.0/24"
# "127.0.0.1"
];
# Forwarded to my vpn, for making things accessible from outside
openVPNPorts = [
{
port = 8336;
protocol = "tcp";
}
{
port = config.services.slskd.settings.soulseek.listen_port;
protocol = "both";
}
{
port = config.services.slskd.settings.soulseek.listen_port + 1;
protocol = "both";
}
{
port = config.services.transmission.settings.peer-port;
protocol = "both";
}
];
# From inside of the vpn namespace to outside of it, for making things inside accessible to LAN
portMappings = let
passthrough = [
8336 # caddy
80 # caddy
443 # caddy
1900 # jellyfin discovery
7359 # jellyfin discovery
];
in (lib.map (x: {
from = x;
to = x;
})
passthrough);
};
}