diff --git a/sys/machines/hopper/lab/acme.nix b/sys/machines/hopper/lab/acme.nix new file mode 100644 index 0000000..c91c16a --- /dev/null +++ b/sys/machines/hopper/lab/acme.nix @@ -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"]; + }; + }; + }; +} diff --git a/sys/machines/hopper/lab/adguard.nix b/sys/machines/hopper/lab/adguard.nix new file mode 100644 index 0000000..36fca9f --- /dev/null +++ b/sys/machines/hopper/lab/adguard.nix @@ -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; + } + ]; + }; + }; +} diff --git a/sys/machines/hopper/lab/caddy.nix b/sys/machines/hopper/lab/caddy.nix new file mode 100644 index 0000000..7694562 --- /dev/null +++ b/sys/machines/hopper/lab/caddy.nix @@ -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.." + } + ''; + }; + }; + }; +} diff --git a/sys/machines/hopper/lab/default.nix b/sys/machines/hopper/lab/default.nix index 8bd03de..76b1d7d 100644 --- a/sys/machines/hopper/lab/default.nix +++ b/sys/machines/hopper/lab/default.nix @@ -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" - ]; - }; } diff --git a/sys/machines/hopper/lab/homepage.nix b/sys/machines/hopper/lab/homepage.nix new file mode 100644 index 0000000..c24ed7e --- /dev/null +++ b/sys/machines/hopper/lab/homepage.nix @@ -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"; + }; + } + ]; + } + ]; + }; +} diff --git a/sys/machines/hopper/lab/jellyfin.nix b/sys/machines/hopper/lab/jellyfin.nix new file mode 100644 index 0000000..17b70a0 --- /dev/null +++ b/sys/machines/hopper/lab/jellyfin.nix @@ -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" + ]; +} diff --git a/sys/machines/hopper/lab/navidrome.nix b/sys/machines/hopper/lab/navidrome.nix new file mode 100644 index 0000000..27fc481 --- /dev/null +++ b/sys/machines/hopper/lab/navidrome.nix @@ -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"]; + }; +} diff --git a/sys/machines/hopper/lab/prometheus.nix b/sys/machines/hopper/lab/prometheus.nix new file mode 100644 index 0000000..e0bdc6b --- /dev/null +++ b/sys/machines/hopper/lab/prometheus.nix @@ -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; + }; +} diff --git a/sys/machines/hopper/lab/restic.nix b/sys/machines/hopper/lab/restic.nix new file mode 100644 index 0000000..5c4f015 --- /dev/null +++ b/sys/machines/hopper/lab/restic.nix @@ -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" + ]; + }; +} diff --git a/sys/machines/hopper/lab/samba.nix b/sys/machines/hopper/lab/samba.nix index fac3f70..7327236 100644 --- a/sys/machines/hopper/lab/samba.nix +++ b/sys/machines/hopper/lab/samba.nix @@ -62,4 +62,6 @@ }; }; }; + + services.restic.backups.hopper.paths = ["/srv/vault"]; } diff --git a/sys/machines/hopper/lab/slskd.nix b/sys/machines/hopper/lab/slskd.nix new file mode 100644 index 0000000..d3f0cec --- /dev/null +++ b/sys/machines/hopper/lab/slskd.nix @@ -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; + }; + }; + }; +} diff --git a/sys/machines/hopper/lab/transmission.nix b/sys/machines/hopper/lab/transmission.nix new file mode 100644 index 0000000..38da61f --- /dev/null +++ b/sys/machines/hopper/lab/transmission.nix @@ -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; + }; +} diff --git a/sys/machines/hopper/lab/vaultwarden.nix b/sys/machines/hopper/lab/vaultwarden.nix new file mode 100644 index 0000000..afb73ef --- /dev/null +++ b/sys/machines/hopper/lab/vaultwarden.nix @@ -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"]; +} diff --git a/sys/machines/hopper/lab/vpn-namespace.nix b/sys/machines/hopper/lab/vpn-namespace.nix new file mode 100644 index 0000000..e51ab5b --- /dev/null +++ b/sys/machines/hopper/lab/vpn-namespace.nix @@ -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); + }; +}