From fbb4defab89715ce8195bae62beedd5f7236c02c Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Wed, 12 Jun 2024 09:13:42 +0200 Subject: [PATCH 01/31] Initial commit --- hosts/Isabel/.keep | 0 hosts/Niko/default.nix | 282 ++++++++++++++++++++++++++ hosts/Niko/hardware-configuration.nix | 45 ++++ 3 files changed, 327 insertions(+) create mode 100644 hosts/Isabel/.keep create mode 100644 hosts/Niko/default.nix create mode 100644 hosts/Niko/hardware-configuration.nix diff --git a/hosts/Isabel/.keep b/hosts/Isabel/.keep new file mode 100644 index 0000000..e69de29 diff --git a/hosts/Niko/default.nix b/hosts/Niko/default.nix new file mode 100644 index 0000000..8855d07 --- /dev/null +++ b/hosts/Niko/default.nix @@ -0,0 +1,282 @@ +{ config, pkgs, ... }: + +{ + imports = [ + # Include the results of the hardware scan. + ./hardware-configuration.nix + ]; + + # Use the systemd-boot EFI boot loader. + boot.loader = { + systemd-boot.enable = true; + efi = { + canTouchEfiVariables = true; + efiSysMountPoint = "/boot/efi"; + }; + }; + + console = { + font = "Lat2-Terminus16"; + keyMap = "us"; + }; + + # List packages installed in the system profile. To search, run: + # $ nix search wget + environment.systemPackages = with pkgs; [ + curl + git + tmux + vim + wget + ]; + + hardware = { + enableRedistributableFirmware = true; + enableAllFirmware = true; + pulseaudio.enable = true; + opengl.enable = true; + }; + + # Select internationalisation properties. + i18n.defaultLocale = "en_GB.utf8"; + + networking = { + hostName = "Niko"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + networkmanager.enable = true; + }; + + nix = { + package = pkgs.nixFlakes; + extraOptions = '' + experimental-features = nix-command flakes + ''; + settings.trusted-users = [ + config.users.users.admin.name + ]; + }; + + nixpkgs.config.allowUnfree = true; + + # List services that you want to enable: + services = { + displayManager = { + enable = true; + autoLogin = { + enable = true; + user = config.users.users.jellyfin-mpv-shim.name; + }; + }; + xserver = { + enable = true; + displayManager.startx.enable = true; + }; + + openssh = { + # Enable the OpenSSH daemon. + enable = true; + settings = { + PasswordAuthentication = false; + PermitRootLogin = "no"; + }; + }; + }; + + sound.enable = true; + + # Set your time zone. + time.timeZone = "Europe/Brussels"; + + users = { + # Define users groups + groups = { + # The group used to deploy rebuilds without password authentication + deploy = { }; + }; + + # Define a user account. Don't forget to set a password with 'passwd'. + users = { + admin = { + description = "System Administrator"; + isNormalUser = true; + extraGroups = [ + config.users.groups.wheel.name # Enable 'sudo' for the user. + config.users.groups.deploy.name + ]; + initialPassword = "ChangeMe"; + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrG+ldRBdCeHEXrsy/qHXIJYg8xQXVuiUR0DxhFjYNg" + ]; + }; + + jellyfin-mpv-shim = { + description = "Jellyfin MPV Shim User"; + isNormalUser = true; + extraGroups = [ + config.users.groups.audio.name + config.users.groups.video.name + ]; + packages = with pkgs; [ + jellyfin-mpv-shim + ]; + }; + }; + }; + + security.sudo = { + enable = true; + extraRules = [ + { + groups = [ config.users.groups.deploy.name ]; + commands = [ + { + command = "/nix/store/*/bin/switch-to-configuration"; + options = [ "NOPASSWD" ]; + } + { + command = "/run/current-system/sw/bin/nix-store"; + options = [ "NOPASSWD" ]; + } + { + command = ''/bin/sh -c "readlink -e /nix/var/nix/profiles/system || readlink -e /run/current-system"''; + options = [ "NOPASSWD" ]; + } + { + command = "${config.system.build.nixos-rebuild}/bin/nixos-rebuild"; + options = [ "NOPASSWD" ]; + } + ]; + } + ]; + }; + + system.stateVersion = "24.05"; + + virtualisation = { + docker = { + enable = true; + autoPrune.enable = true; + }; + + oci-containers = { + backend = "docker"; + containers = { + reverse-proxy = { + hostname = "traefik"; + image = "traefik:v3.0"; + cmd = [ + "--api.insecure=true" + # Add Docker provider + "--providers.docker=true" + "--providers.docker.exposedByDefault=false" + # Add web entrypoint + "--entrypoints.web.address=:80/tcp" + "--entrypoints.web.http.redirections.entrypoint.to=websecure" + "--entrypoints.web.http.redirections.entrypoint.scheme=https" + # Add websecure entrypoint + "--entrypoints.websecure.address=:443/tcp" + "--entrypoints.websecure.http.tls=true" + "--entrypoints.websecure.http.tls.certResolver=letsencrypt" + "--entrypoints.websecure.http.tls.domains[0].main=depeuter.dev" + "--entrypoints.websecure.http.tls.domains[0].sans=*.depeuter.dev" + "--entrypoints.websecure.http.tls.domains[1].sans=*.niko.depeuter.dev" + # Certificates + "--certificatesresolvers.letsencrypt.acme.dnschallenge=true" + "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare" + "--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be" + "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + ]; + ports = [ + "80:80/tcp" + "443:443/tcp" + # "8080:8080/tcp" # The Web UI (enabled by --api.insecure=true) + ]; + environment = { + "CLOUDFLARE_DNS_API_TOKEN" = "6Vz64Op_a6Ls1ljGeBxFoOVfQ-yB-svRbf6OyPv2"; + }; + environmentFiles = [ + ]; + volumes = [ + "/var/run/docker.sock:/var/run/docker.sock:ro" # So that Traefik can listen to the Docker events + "letsencrypt:/letsencrypt" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.traefik.rule" = "Host(`traefik.niko.depeuter.dev`)"; + "traefik.http.services.traefik.loadbalancer.server.port" = "8080"; + }; + autoStart = true; + }; + technitium-dns = { + hostname = "technitium-dns"; + image = "technitium/dns-server:12.1"; + ports = [ + # "5380:5380/tcp" #DNS web console (HTTP) + # "53443:53443/tcp" #DNS web console (HTTPS) + "53:53/udp" #DNS service + "53:53/tcp" #DNS service + # "853:853/udp" #DNS-over-QUIC service + # "853:853/tcp" #DNS-over-TLS service + # "443:443/udp" #DNS-over-HTTPS service (HTTP/3) + # "443:443/tcp" #DNS-over-HTTPS service (HTTP/1.1, HTTP/2) + # "80:80/tcp" #DNS-over-HTTP service (use with reverse proxy or certbot certificate renewal) + # "8053:8053/tcp" #DNS-over-HTTP service (use with reverse proxy) + # "67:67/udp" #DHCP service + ]; + environment = { + # The primary domain name used by this DNS Server to identify itself. + DNS_SERVER_DOMAIN = config.networking.hostName; + # DNS Server will use IPv6 for querying whenever possible with this option enabled. + DNS_SERVER_PREFER_IPV6 = "true"; + # The TCP port number for the DNS web console over HTTP protocol. + # DNS_SERVER_WEB_SERVICE_HTTP_PORT=5380 + # The TCP port number for the DNS web console over HTTPS protocol. + # DNS_SERVER_WEB_SERVICE_HTTPS_PORT=53443 + # Enables HTTPS for the DNS web console. + # DNS_SERVER_WEB_SERVICE_ENABLE_HTTPS=false + # Enables self signed TLS certificate for the DNS web console. + # DNS_SERVER_WEB_SERVICE_USE_SELF_SIGNED_CERT=false + # Enables DNS server optional protocol DNS-over-HTTP on TCP port 8053 to be used with a TLS terminating reverse proxy like nginx. + # DNS_SERVER_OPTIONAL_PROTOCOL_DNS_OVER_HTTP=false + # Recursion options: Allow, Deny, AllowOnlyForPrivateNetworks, UseSpecifiedNetworks. + #nDNS_SERVER_RECURSION=AllowOnlyForPrivateNetworks + # Comma separated list of IP addresses or network addresses to deny recursion. Valid only for `UseSpecifiedNetworks` recursion option. + # DNS_SERVER_RECURSION_DENIED_NETWORKS=1.1.1.0/24 + # Comma separated list of IP addresses or network addresses to allow recursion. Valid only for `UseSpecifiedNetworks` recursion option. + # DNS_SERVER_RECURSION_ALLOWED_NETWORKS=127.0.0.1, 192.168.1.0/24 + # Sets the DNS server to block domain names using Blocked Zone and Block List Zone. + DNS_SERVER_ENABLE_BLOCKING = "false"; + # Specifies if the DNS Server should respond with TXT records containing a blocked domain report for TXT type requests. + # DNS_SERVER_ALLOW_TXT_BLOCKING_REPORT=false + # A comma separated list of block list URLs. + # DNS_SERVER_BLOCK_LIST_URLS= + #Comma separated list of forwarder addresses. + DNS_SERVER_FORWARDERS="195.130.130.2,195.130.131.2"; + # Forwarder protocol options: Udp, Tcp, Tls, Https, HttpsJson. + # DNS_SERVER_FORWARDER_PROTOCOL=Tcp + # Enable this option to use local time instead of UTC for logging. + # DNS_SERVER_LOG_USING_LOCAL_TIME=true + }; + volumes = [ + "dns:/etc/dns" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.technitium-dns.rule" = "Host(`dns.niko.depeuter.dev`)"; + "traefik.http.services.technitium-dns.loadbalancer.server.port" = "5380"; + "traefik.tls.options.default.minVersion" = "VersionTLS13"; + }; + autoStart = true; + }; + }; + }; + }; +} diff --git a/hosts/Niko/hardware-configuration.nix b/hosts/Niko/hardware-configuration.nix new file mode 100644 index 0000000..bf837af --- /dev/null +++ b/hosts/Niko/hardware-configuration.nix @@ -0,0 +1,45 @@ +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot = { + initrd = { + availableKernelModules = [ + "xhci_pci" + "ahci" + "usb_storage" + "sd_mod" + "iwlwifi" + ]; + }; + kernelModules = [ ]; + extraModulePackages = [ ]; + }; + + fileSystems = { + "/" = { + device = "/dev/disk/by-uuid/20b7eff3-fca5-4b60-a5a9-13219f70ce23"; + fsType = "ext4"; + }; + + "/boot/efi" = { + device = "/dev/disk/by-uuid/0B6D-0DCD"; + fsType = "vfat"; + }; + }; + + swapDevices = [ + { device = "/dev/disk/by-uuid/f3679da0-45b3-45c0-a1d0-af8d771a7dbf"; } + ]; + + networking = { + hostId = "7a139e16"; + useDHCP = lib.mkDefault true; + }; + + powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} From ad60695af447d7bc43ccf340537fdee34f13eb4d Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 6 Jul 2024 10:26:21 +0200 Subject: [PATCH 02/31] feat: Jellyfin-mpv-shim with local idle pics --- hosts/Niko/default.nix | 31 ++++++++++++++++++++------- hosts/Niko/hardware-configuration.nix | 10 ++++++++- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/hosts/Niko/default.nix b/hosts/Niko/default.nix index 8855d07..9efdf12 100644 --- a/hosts/Niko/default.nix +++ b/hosts/Niko/default.nix @@ -52,6 +52,10 @@ }; networkmanager.enable = true; + + extraHosts = '' + 192.168.0.11 jelly.depeuter.dev + ''; }; nix = { @@ -68,16 +72,20 @@ # List services that you want to enable: services = { - displayManager = { + # Cage, a wayland kiosk service + cage = { enable = true; - autoLogin = { - enable = true; - user = config.users.users.jellyfin-mpv-shim.name; + environment = { + # Do not fail when there are no input devices. + # WLR_LIBINPUT_NO_DEVICES = "1"; }; - }; - xserver = { - enable = true; - displayManager.startx.enable = true; + extraArguments = [ + "-d" # Don't draw client side decorations, when possible + # "-m" "last" # Use only the last connected output + "-s" # Allow VT switching + ]; + program = "/home/jellyfin-mpv-shim/start.sh"; + user = config.users.users.jellyfin-mpv-shim.name; }; openssh = { @@ -126,6 +134,8 @@ ]; packages = with pkgs; [ jellyfin-mpv-shim + mpv + socat ]; }; }; @@ -158,9 +168,14 @@ ]; }; + systemd.services."cage-tty1".serviceConfig.Restart = "always"; + system.stateVersion = "24.05"; virtualisation = { + # Enable Android emulator + # waydroid.enable = true; + docker = { enable = true; autoPrune.enable = true; diff --git a/hosts/Niko/hardware-configuration.nix b/hosts/Niko/hardware-configuration.nix index bf837af..260cf1f 100644 --- a/hosts/Niko/hardware-configuration.nix +++ b/hosts/Niko/hardware-configuration.nix @@ -12,7 +12,6 @@ "ahci" "usb_storage" "sd_mod" - "iwlwifi" ]; }; kernelModules = [ ]; @@ -29,6 +28,15 @@ device = "/dev/disk/by-uuid/0B6D-0DCD"; fsType = "vfat"; }; + +# "/data/photos" = { +# device = "//192.168.0.11/CANVAS"; +# fsType = "cifs"; +# options = let +# # this line prevents hanging on network split +# automount_opts = "x-systemd.automount,noauto,x-systemd.idle-timeout=60,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s"; +# in ["${automount_opts},credentials=/etc/nixos/smb-secrets"]; +# }; }; swapDevices = [ From c1025627ae3f29670d890a8073205ed6d5027808 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Mon, 8 Jul 2024 13:37:33 +0200 Subject: [PATCH 03/31] chore: Add Niko to Tailscale --- hosts/Niko/default.nix | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/hosts/Niko/default.nix b/hosts/Niko/default.nix index 9efdf12..ece9d5f 100644 --- a/hosts/Niko/default.nix +++ b/hosts/Niko/default.nix @@ -96,6 +96,20 @@ PermitRootLogin = "no"; }; }; + + tailscale = { + enable = true; + useRoutingFeatures = "server"; + authKeyFile = "/etc/nixos/tailscale-authkey"; + extraUpFlags = [ + "--advertise-routes=192.168.0.0/24" + "--exit-node" + ]; + }; + + # Fix DNS issues. See: + # https://github.com/tailscale/tailscale/issues/4254 + # resolved.enable = true; }; sound.enable = true; From cef3a949feae5ef6e87dee00cf44def0b2dd2ed7 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sun, 10 Nov 2024 20:15:47 +0100 Subject: [PATCH 04/31] Sync --- flake.lock | 81 +++ flake.nix | 50 ++ hosts/ACE/default.nix | 41 ++ hosts/Binnenpost/default.nix | 81 +++ hosts/Development/default.nix | 65 ++ hosts/Gitea/default.nix | 38 + hosts/Ingress/default.nix | 233 +++++++ hosts/Isabel/dashboard/config/bookmarks.yaml | 32 + hosts/Isabel/dashboard/config/services.yaml | 30 + hosts/Isabel/default.nix | 255 +++++++ hosts/Isabel/hardware-configuration.nix | 39 ++ hosts/Niko/default.nix | 169 +---- hosts/Niko/hardware-configuration.nix | 16 +- hosts/ProductionGPU/default.nix | 98 +++ hosts/Template/default.nix | 36 + hosts/Testing/default.nix | 48 ++ hosts/Vaultwarden/default.nix | 38 + modules/apps/arr/default.nix | 269 +++++++ modules/apps/calibre/default.nix | 17 + modules/apps/default.nix | 12 + modules/apps/gitea/default.nix | 654 ++++++++++++++++++ modules/apps/jellyfin/default.nix | 155 +++++ modules/apps/plex/default.nix | 50 ++ modules/apps/speedtest/default.nix | 27 + modules/apps/technitium-dns/default.nix | 73 ++ modules/apps/vaultwarden/default.nix | 636 +++++++++++++++++ modules/common/default.nix | 16 + modules/default.nix | 9 + modules/services/actions/default.nix | 49 ++ modules/services/default.nix | 6 + modules/services/openssh/default.nix | 20 + modules/virtualisation/containers/default.nix | 23 + modules/virtualisation/default.nix | 6 + modules/virtualisation/guest/default.nix | 34 + users/admin/default.nix | 33 + users/apps/default.nix | 18 + users/backup/default.nix | 26 + users/default.nix | 9 + users/deploy/default.nix | 49 ++ users/media/default.nix | 18 + 40 files changed, 3401 insertions(+), 158 deletions(-) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 hosts/ACE/default.nix create mode 100644 hosts/Binnenpost/default.nix create mode 100644 hosts/Development/default.nix create mode 100644 hosts/Gitea/default.nix create mode 100644 hosts/Ingress/default.nix create mode 100644 hosts/Isabel/dashboard/config/bookmarks.yaml create mode 100644 hosts/Isabel/dashboard/config/services.yaml create mode 100644 hosts/Isabel/default.nix create mode 100644 hosts/Isabel/hardware-configuration.nix create mode 100644 hosts/ProductionGPU/default.nix create mode 100644 hosts/Template/default.nix create mode 100644 hosts/Testing/default.nix create mode 100644 hosts/Vaultwarden/default.nix create mode 100644 modules/apps/arr/default.nix create mode 100644 modules/apps/calibre/default.nix create mode 100644 modules/apps/default.nix create mode 100644 modules/apps/gitea/default.nix create mode 100644 modules/apps/jellyfin/default.nix create mode 100644 modules/apps/plex/default.nix create mode 100644 modules/apps/speedtest/default.nix create mode 100644 modules/apps/technitium-dns/default.nix create mode 100644 modules/apps/vaultwarden/default.nix create mode 100644 modules/common/default.nix create mode 100644 modules/default.nix create mode 100644 modules/services/actions/default.nix create mode 100644 modules/services/default.nix create mode 100644 modules/services/openssh/default.nix create mode 100644 modules/virtualisation/containers/default.nix create mode 100644 modules/virtualisation/default.nix create mode 100644 modules/virtualisation/guest/default.nix create mode 100644 users/admin/default.nix create mode 100644 users/apps/default.nix create mode 100644 users/backup/default.nix create mode 100644 users/default.nix create mode 100644 users/deploy/default.nix create mode 100644 users/media/default.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..8df2f99 --- /dev/null +++ b/flake.lock @@ -0,0 +1,81 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1730785428, + "narHash": "sha256-Zwl8YgTVJTEum+L+0zVAWvXAGbWAuXHax3KzuejaDyo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4aa36568d413aca0ea84a1684d2d46f55dbabad7", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "utils": "utils" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "flake-utils": [ + "flake-utils" + ] + }, + "locked": { + "lastModified": 1722363685, + "narHash": "sha256-XCf2PIAT6lH7BwytgioPmVf/wkzXjSKScC4KzcZgb64=", + "owner": "gytis-ivaskevicius", + "repo": "flake-utils-plus", + "rev": "6b10f51ff73a66bb29f3bc8151a59d217713f496", + "type": "github" + }, + "original": { + "owner": "gytis-ivaskevicius", + "repo": "flake-utils-plus", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..57bbff8 --- /dev/null +++ b/flake.nix @@ -0,0 +1,50 @@ +{ + description = "Homelab configuration using flakes"; + + inputs = { + nixpkgs.url = "nixpkgs/nixos-unstable"; + + flake-utils.url = "github:numtide/flake-utils"; + utils = { + url = "github:gytis-ivaskevicius/flake-utils-plus"; + inputs.flake-utils.follows = "flake-utils"; + }; + }; + + outputs = inputs@{ + self, nixpkgs, + flake-utils, utils, + ... + }: + let + system = "x86_64-linux"; + in + utils.lib.mkFlake { + inherit self inputs; + + hostDefaults = { + inherit system; + + modules = [ + ./modules + ./users + ]; + }; + + hosts = { + Niko.modules = [ ./hosts/Niko ]; + + Ingress.modules = [ ./hosts/Ingress ]; + Gitea.modules = [ ./hosts/Gitea ]; + Vaultwarden.modules = [ ./hosts/Vaultwarden ]; + + Binnenpost.modules = [ ./hosts/Binnenpost ]; + ProductionGPU.modules = [ ./hosts/ProductionGPU ]; + ACE.modules = [ ./hosts/ACE ]; + + Template.modules = [ ./hosts/Template ]; + Development.modules = [ ./hosts/Development ]; + Testing.modules = [ ./hosts/Testing ]; + }; + }; +} diff --git a/hosts/ACE/default.nix b/hosts/ACE/default.nix new file mode 100644 index 0000000..04aa284 --- /dev/null +++ b/hosts/ACE/default.nix @@ -0,0 +1,41 @@ +{ pkgs, ... }: + +{ + config = { + homelab = { + services.actions.enable = true; + virtualisation.guest.enable = true; + }; + + networking = { + hostName = "ACE"; + hostId = "aaaa4100"; + domain = "depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.41"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Binnenpost/default.nix b/hosts/Binnenpost/default.nix new file mode 100644 index 0000000..d78e2da --- /dev/null +++ b/hosts/Binnenpost/default.nix @@ -0,0 +1,81 @@ +{ pkgs, ... }: + +{ + config = { + environment = { + etc."nixos/tailscale-authkey".text = '' + tskey-auth-k1tfJLTnGB11CNTRL-HhnegtfNzQ3G8h71SC2DR38PFXwseQiu + ''; + + systemPackages = with pkgs; [ + ethtool + ]; + }; + + homelab = { + apps = { + speedtest.enable = true; + technitiumDNS.enable = true; + }; + virtualisation.guest.enable = true; + }; + + networking = { + hostName = "Binnenpost"; + hostId = "aaaa1001"; + domain = "depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.89"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + services = { + networkd-dispatcher = { + enable = true; + rules."50-tailscale" = { + onState = ["routable"]; + script = '' + ${pkgs.ethtool}/bin/ethtool -K ens18 rx-udp-gro-forwarding on rx-gro-list off + ''; + }; + }; + + tailscale = { + enable = true; + useRoutingFeatures = "server"; + authKeyFile = "/etc/nixos/tailscale-authkey"; + extraUpFlags = [ + "--advertise-routes=192.168.0.0/24" + "--exit-node" + ]; + }; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Development/default.nix b/hosts/Development/default.nix new file mode 100644 index 0000000..40983c9 --- /dev/null +++ b/hosts/Development/default.nix @@ -0,0 +1,65 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps.arr = { + qbittorrent.enable = true; + }; + virtualisation.guest.enable = true; + }; + + networking = { + hostId = "aaaa9100"; + domain = "roxanne.depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.91"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + + virtualisation.oci-containers.containers = { + pgadmin = { + image = "dpage/pgadmin4:8.11.0"; + ports = [ + "30056:80/tcp" + ]; + environment = { + # NOTE Required + # The email address used when setting up the initial administrator account to login to pgAdmin. + PGADMIN_DEFAULT_EMAIL = "kmtl.hugo+pgadmin@gmail.com"; + # NOTE Required + # The password used when setting up the initial administrator account to login to pgAdmin. + PGADMIN_DEFAULT_PASSWORD = "ChangeMe"; + }; + autoStart = true; + }; + }; + }; +} diff --git a/hosts/Gitea/default.nix b/hosts/Gitea/default.nix new file mode 100644 index 0000000..5b2492f --- /dev/null +++ b/hosts/Gitea/default.nix @@ -0,0 +1,38 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps.gitea.enable = true; + virtualisation.guest.enable = true; + }; + + networking = { + hostId = "aaaa1500"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + useDHCP = false; + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.24"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Ingress/default.nix b/hosts/Ingress/default.nix new file mode 100644 index 0000000..63e3ced --- /dev/null +++ b/hosts/Ingress/default.nix @@ -0,0 +1,233 @@ +{ config, pkgs, modulesPath, lib, system, ... }: + +{ + config = { + homelab.virtualisation.guest.enable = true; + + networking = { + hostName = "Ingress"; + hostId = "aaaa1000"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + useDHCP = false; + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.10"; +prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + + firewall = { + enable = true; + allowedTCPPorts = [ + 80 # HTTP + 443 # HTTPS + ]; + }; + }; + + security.acme = { + acceptTerms = true; + defaults = { + inherit (config.services.nginx) group; + dnsPropagationCheck = true; + dnsProvider = "cloudflare"; + dnsResolver = "1.1.1.1:53"; + email = "tibo.depeuter@telenet.be"; + credentialFiles = { + CLOUDFLARE_DNS_API_TOKEN_FILE = "/var/lib/secrets/depeuter-dev-cloudflare-api-token"; + }; + reloadServices = [ "nginx" ]; + }; + certs = { + "depeuter.dev" = { + domain = "depeuter.dev"; + extraDomainNames = [ "*.depeuter.dev" ]; + }; + "cloud.depeuter.dev" = { }; + "git.depeuter.dev" = { }; + "jelly.depeuter.dev" = { }; + "vault.depeuter.dev" = { }; + }; + }; + + # List services that you want to enable. + services = { + # Enable Nginx as a reverse proxy + nginx = { + enable = true; + + # Use recommended settings + # recommendedGzipSettings = true; + # recommendedOptimisation = true; + # recommendedProxySettings = true; + # recommendedTlsSettings = true; + + # Only allow PFS-enabled ciphers with AES256 + sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL"; + + upstreams.docservice.servers."192.168.0.14:8080" = {}; + + appendHttpConfig = '' + map $http_x_forwarded_proto $the_scheme { + default $http_x_forwarded_proto; + "" $scheme; + } + + map $http_x_forwarded_host $the_host { + default $http_x_forwarded_host; + "" $host; + } + + map $http_upgrade $proxy_connection { + default upgrade; + "" close; + } + ''; + + # Define hosts + virtualHosts = { + # Disable automatic routing. + "default" = { + locations."/".return = "301 https://youtu.be/dQw4w9WgXcQ"; + default = true; + }; + + "cloud.depeuter.dev" = { + enableACME = true; + forceSSL = true; + locations = { + "/" = { + proxyPass = "http://192.168.0.14"; + extraConfig = '' + add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; + fastcgi_request_buffering off; + ''; + }; + "/office/" = { + proxyPass = "http://192.168.0.14:8080/"; + priority = 500; + recommendedProxySettings = false; + extraConfig = '' + proxy_http_version 1.1; + ''; + }; + }; + extraConfig = '' + client_max_body_size 10G; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $proxy_connection; + proxy_set_header X-Forwarded-Host $the_host/office; + proxy_set_header X-Forwarded-Proto $the_scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + ''; + }; + "calendar.depeuter.dev".locations."/".return = "301 https://cloud.depeuter.dev/apps/calendar"; + "tasks.depeuter.dev".locations."/".return = "301 https://cloud.depeuter.dev/apps/tasks"; + "notes.depeuter.dev".locations."/".return = "301 https://cloud.depeuter.dev/apps/notes"; + + "jelly.depeuter.dev" = { + enableACME = true; + forceSSL = true; + locations = { + "/" = { + proxyPass = "http://192.168.0.94:8096"; + extraConfig = '' + # Proxy main Jellyfin traffic + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Protocol $scheme; + proxy_set_header X-Forwarded-Host $http_host; + + # Disable buffering when the nginx proxy gets very resource heavy upon streaming + proxy_buffering off; + ''; + }; + "/socket" = { + proxyPass = "http://192.168.0.91:8096"; + extraConfig = '' + # Proxy Jellyfin Websockets traffic + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Protocol $scheme; + proxy_set_header X-Forwarded-Host $http_host; + ''; + }; + }; + extraConfig = '' + client_max_body_size 20M; + + # Security / XSS Mitigation Headers + # NOTE: X-Frame-Options may cause issues with the webOS app + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + + # Permissions policy. May cause issues with some clients + add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), battery=(), bluetooth=(), camera=(), clipboard-read=(), display-capture=(), document-domain=(), encrypted-media=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), idle-detection=(), interest-cohort=(), keyboard-map=(), local-fonts=(), magnetometer=(), microphone=(), payment=(), publickey-credentials-get=(), serial=(), sync-xhr=(), usb=(), xr-spatial-tracking=()" always; + + # Content Security Policy + # See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP + # Enforces https content and restricts JS/CSS to origin + # External Javascript (such as cast_sender.js for Chromecast) must be whitelisted. + # NOTE: The default CSP headers may cause issues with the webOS app + add_header Content-Security-Policy "default-src https: data: blob: ; img-src 'self' https://* ; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' https://www.gstatic.com https://www.youtube.com blob:; worker-src 'self' blob:; connect-src 'self'; object-src 'none'; frame-ancestors 'self'"; + ''; + }; + "git.depeuter.dev" = { + enableACME = true; + forceSSL = true; + locations."/".proxyPass = "http://192.168.0.24:3000"; + extraConfig = '' + proxy_set_header Connection $http_connection; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + client_max_body_size 512M; + keepalive_timeout 600s; + proxy_buffers 4 256k; # Number and size of buffers for reading response + proxy_buffer_size 256k; # Buffer for the first part of the response + proxy_busy_buffers_size 256k; # Max size of busy buffers + proxy_http_version 1.1; + proxy_read_timeout 600s; + proxy_temp_file_write_size 256k; # Size of temp file for large responses + ''; + }; + "vault.depeuter.dev" = { + enableACME = true; + forceSSL = true; + locations = { + "/".proxyPass = "http://192.168.0.22:10102"; + "~ ^/admin".return = 403; + }; + }; + }; + }; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Isabel/dashboard/config/bookmarks.yaml b/hosts/Isabel/dashboard/config/bookmarks.yaml new file mode 100644 index 0000000..ac0566a --- /dev/null +++ b/hosts/Isabel/dashboard/config/bookmarks.yaml @@ -0,0 +1,32 @@ +- Office: + - Zoho Mail: + - icon: zohomail + href: https://mail.zoho.eu +- Network: + - Cloudlfare: + - icon: cloudflare + href: https://dash.cloudflare.com + - Pulsetic: + - href: https://status.depeuter.dev + icon: https://pulsetic.com/favicon-196x196.png + - Telenet Internet usage: + - icon: https://static.telenet.be/assets/favicon/favicon.ico + href: https://www2.telenet.be/nl/klantenservice/raadpleeg-je-internetverbruik/ + - Telenet Modem: + - icon: https://static.telenet.be/assets/favicon/favicon.ico + # href: https://mijn.telenet.be/mijntelenet/rgw/settings.do?identifier=u381160&action=showAdvancedSettings + href: https://www2.telenet.be/residential/nl/mijn-telenet/je-thuisnetwerk#/mainnavitem=hgw/mainnavitemid=item-1/subnavitem=modem_general + - TransIP: + - icon: https://www.transip.eu/cache-60c9b25f/img/transip-new/favicons/favicon.png + href: https://www.transip.eu/cp/ +- Homemade: + - AI-Transparency: + - href: https://ai-transparency.depeuter.dev + icon: https://ai-transparency.depeuter.dev/img/transparency.png + - Down-message: + - href: https://down.depeuter.dev + icon: https://down.depeuter.dev/assets/icon.jpg + - Portfolio: + - href: https://tibo.depeuter.dev + icon: https://tibo.depeuter.dev/assets/owl_circuit.png + diff --git a/hosts/Isabel/dashboard/config/services.yaml b/hosts/Isabel/dashboard/config/services.yaml new file mode 100644 index 0000000..e944db1 --- /dev/null +++ b/hosts/Isabel/dashboard/config/services.yaml @@ -0,0 +1,30 @@ +- Networking: + - AXE5400 Tri-Band Wi-Fi 6E Router: + description: Router + href: https://tplinkwifi.net + ping: http://192.168.0.1 + icon: tp-link + - Traefik Isabel: + description: Reverse proxy manager + href: https://traefik.isabel.depeuter.dev/dashboard/# + ping: https://traefik.isabel.depeuter.dev/dashboard/# + icon: traefik + widget: + type: traefik + url: https://traefik.isabel.depeuter.dev + - Traefik Niko: + description: Reverse proxy manager + href: https://traefik.niko.depeuter.dev/dashboard/# + ping: https://traefik.niko.depeuter.dev/dashboard/# + - Technitium DNS Isabel: + description: DNS server + href: https://dns.Isabel.depeuter.dev + ping: http://192.168.0.13:53 + icon: technitium + - Technitium DNS Niko: + description: DNS server + href: https://dns.niko.depeuter.dev + ping: http://192.168.0.30:53 + icon: technitium + + diff --git a/hosts/Isabel/default.nix b/hosts/Isabel/default.nix new file mode 100644 index 0000000..0a1f50f --- /dev/null +++ b/hosts/Isabel/default.nix @@ -0,0 +1,255 @@ +{ config, pkgs, ... }: + +{ + imports = [ + # Include the results of the hardware scan. + ./hardware-configuration.nix + ]; + + # Use the systemd-boot EFI boot loader. + boot.loader = { + systemd-boot.enable = true; + efi = { + canTouchEfiVariables = true; + efiSysMountPoint = "/boot/efi"; + }; + }; + + console = { + font = "Lat2-Terminus16"; + keyMap = "us"; + }; + + # List packages installed in the system profile. To search, run: + # $ nix search wget + environment.systemPackages = with pkgs; [ + ]; + + environment.etc = { + "homepage/bookmarks.yaml".text = '' +- Office: + - Zoho Mail: + - icon: zohomail + href: https://mail.zoho.eu +- Network: + - Cloudlfare: + - icon: cloudflare + href: https://dash.cloudflare.com + - TransIP: + - icon: https://www.transip.eu/cache-60c9b25f/img/transip-new/favicons/favicon.png + href: https://www.transip.eu/cp/ + - Telenet Internet usage: + - icon: https://static.telenet.be/assets/favicon/favicon.ico + href: https://www2.telenet.be/nl/klantenservice/raadpleeg-je-internetverbruik/ + - Telenet Modem: + - icon: https://static.telenet.be/assets/favicon/favicon.ico + # href: https://mijn.telenet.be/mijntelenet/rgw/settings.do?identifier=u381160&action=showAdvancedSettings + href: https://www2.telenet.be/residential/nl/mijn-telenet/je-thuisnetwerk#/mainnavitem=hgw/mainnavitemid=item-1/subnavitem=modem_general + - Pulsetic: + - href: https://status.depeuter.dev + icon: https://pulsetic.com/favicon-196x196.png +- Homemade: + - AI-Transparency: + - href: https://ai-transparency.depeuter.dev + icon: https://ai-transparency.depeuter.dev/img/transparency.png + - Down-message: + - href: https://down.depeuter.dev + icon: https://down.depeuter.dev/assets/icon.jpg + - Portfolio: + - href: https://tibo.depeuter.dev + icon: https://tibo.depeuter.dev/assets/owl_circuit.png + ''; + + "homepage/services.yaml".text = '' +- Networking: + - Traefik Isabel: + description: Reverse proxy manager + href: https://traefik.isabel.depeuter.dev/dashboard/# + ping: https://traefik.isabel.depeuter.dev/dashboard/# + icon: traefik + widget: + type: traefik + url: https://traefik.isabel.depeuter.dev + - Traefik Niko: + description: Reverse proxy manager + href: https://traefik.niko.depeuter.dev/dashboard/# + ping: https://traefik.niko.depeuter.dev/dashboard/# + icon: traefik + widget: + type: traefik + url: https://traefik.niko.depeuter.dev + ''; + + "homepage/settings.yaml".text = '' +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/en/configs/settings + +providers: + openweathermap: openweathermapapikey + weatherapi: weatherapiapikey + ''; + }; + + homelab.apps.technitiumDNS.enable = true; + + # Select internationalisation properties. + i18n.defaultLocale = "en_GB.utf8"; + + networking = { + hostName = "Hugo-Isabel"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + networkmanager.enable = true; + }; + + # List services that you want to enable: + services = { + tailscale = { + enable = true; + useRoutingFeatures = "server"; + authKeyFile = "/etc/nixos/tailscale-authkey"; + extraUpFlags = [ + "--advertise-routes=192.168.0.0/24" + "--exit-node" + ]; + }; + + # Fix DNS issues. See: + # https://github.com/tailscale/tailscale/issues/4254 + # resolved.enable = true; + }; + + system.stateVersion = "24.05"; + + security.sudo = { + enable = true; + }; + + virtualisation = { + docker = { + enable = true; + autoPrune.enable = true; + }; + + oci-containers = { + backend = "docker"; + containers = { + reverse-proxy = { + hostname = "traefik"; + image = "traefik:v3.0"; + cmd = [ + "--api.insecure=true" + # Add Docker provider + "--providers.docker=true" + "--providers.docker.exposedByDefault=false" + # Add web entrypoint + "--entrypoints.web.address=:80/tcp" + "--entrypoints.web.http.redirections.entrypoint.to=websecure" + "--entrypoints.web.http.redirections.entrypoint.scheme=https" + # Add websecure entrypoint + "--entrypoints.websecure.address=:443/tcp" + "--entrypoints.websecure.http.tls=true" + "--entrypoints.websecure.http.tls.certResolver=letsencrypt" + "--entrypoints.websecure.http.tls.domains[0].main=depeuter.dev" + "--entrypoints.websecure.http.tls.domains[0].sans=*.depeuter.dev" + "--entrypoints.websecure.http.tls.domains[1].sans=*.isabel.depeuter.dev" + "--entrypoints.websecure.http.tls.domains[2].sans=*.jelly.depeuter.dev" + # Certificates + "--certificatesresolvers.letsencrypt.acme.dnschallenge=true" + "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare" + "--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be" + "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + + # Additional routes + ]; + ports = [ + "80:80/tcp" + "443:443/tcp" + # "8080:8080/tcp" # The Web UI (enabled by --api.insecure=true) + ]; + environment = { + # TODO Hide this! + "CLOUDFLARE_DNS_API_TOKEN" = "6Vz64Op_a6Ls1ljGeBxFoOVfQ-yB-svRbf6OyPv2"; + }; + environmentFiles = [ + ]; + volumes = [ + "/var/run/docker.sock:/var/run/docker.sock:ro" # So that Traefik can listen to the Docker events + "letsencrypt:/letsencrypt" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.traefik.rule" = "Host(`traefik.isabel.depeuter.dev`)"; + "traefik.http.services.traefik.loadbalancer.server.port" = "8080"; + }; + autoStart = true; + }; + feishin = { + hostname = "feishin"; + image = "ghcr.io/jeffvli/feishin:0.7.1"; + ports = [ + # "9180:9180/tcp" # Web player (HTTP) + ]; + environment = { + # pre defined server name + SERVER_NAME = "Hugo"; + # When true AND name/type/url are set, only username/password can be toggled + SERVER_LOCK = "true"; + # navidrome also works + SERVER_TYPE = "jellyfin"; + # http://address:port + SERVER_URL= "https://jelly.depeuter.dev"; + TZ = config.time.timeZone; + }; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.feishin.rule" = "Host(`music.depeuter.dev`)"; + "traefik.http.services.feishin.loadbalancer.server.port" = "9180"; + "traefik.tls.options.default.minVersion" = "VersionTLS13"; + }; + autoStart = true; + }; + dashboard = { + hostname = "dashboard"; + image = "ghcr.io/gethomepage/homepage:v0.9.3"; + ports = [ + # "3000:3000/tcp" + ]; + volumes = [ + "/etc/homepage:/app/config" # Make sure your local config directory exists + "/var/run/docker.sock:/var/run/docker.sock:ro" # optional, for docker integrations + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.dashboard.rule" = "Host(`dash.depeuter.dev`)"; + "traefik.http.services.dashboard.loadbalancer.server.port" = "3000"; + "traefik.tls.options.default.minVersion" = "VersionTLS13"; + }; + autoStart = true; + }; + prometheus = { + hostname = "prometheus"; + image = "prom/prometheus:v2.45.6"; + ports = [ + # "127.0.0.1:9090:9090/tcp" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.prometheus.rule" = "Host(`prometheus.isabel.depeuter.dev`)"; + "traefik.http.services.prometheus.loadbalancer.server.port" = "9090"; + "traefik.tls.options.default.minVersion" = "VersionTLS13"; + }; + autoStart = true; + }; + }; + }; + }; +} diff --git a/hosts/Isabel/hardware-configuration.nix b/hosts/Isabel/hardware-configuration.nix new file mode 100644 index 0000000..ec7ffda --- /dev/null +++ b/hosts/Isabel/hardware-configuration.nix @@ -0,0 +1,39 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = + [ (modulesPath + "/profiles/qemu-guest.nix") + ]; + + boot.initrd.availableKernelModules = [ "ata_piix" "xhci_pci" "ahci" "sd_mod" "sr_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-label/NIX-ROOT"; + fsType = "ext4"; + }; + + fileSystems."/boot/efi" = + { device = "/dev/disk/by-label/NIX-BOOT"; + fsType = "vfat"; + }; + + swapDevices = + [ { device = "/dev/disk/by-label/SWAP"; } + ]; + + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking + # (the default) this is the recommended approach. When using systemd-networkd it's + # still possible to use this option, but it's recommended to use it in conjunction + # with explicit per-interface declarations with `networking.interfaces..useDHCP`. + networking.useDHCP = lib.mkDefault true; + # networking.interfaces.ens3.useDHCP = lib.mkDefault true; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/hosts/Niko/default.nix b/hosts/Niko/default.nix index ece9d5f..57dbc27 100644 --- a/hosts/Niko/default.nix +++ b/hosts/Niko/default.nix @@ -6,6 +6,11 @@ ./hardware-configuration.nix ]; + homelab = { + apps.technitiumDNS.enable = true; + users.deploy.enable = true; + }; + # Use the systemd-boot EFI boot loader. boot.loader = { systemd-boot.enable = true; @@ -23,11 +28,7 @@ # List packages installed in the system profile. To search, run: # $ nix search wget environment.systemPackages = with pkgs; [ - curl - git - tmux - vim - wget + cifs-utils ]; hardware = { @@ -58,16 +59,6 @@ ''; }; - nix = { - package = pkgs.nixFlakes; - extraOptions = '' - experimental-features = nix-command flakes - ''; - settings.trusted-users = [ - config.users.users.admin.name - ]; - }; - nixpkgs.config.allowUnfree = true; # List services that you want to enable: @@ -88,15 +79,6 @@ user = config.users.users.jellyfin-mpv-shim.name; }; - openssh = { - # Enable the OpenSSH daemon. - enable = true; - settings = { - PasswordAuthentication = false; - PermitRootLogin = "no"; - }; - }; - tailscale = { enable = true; useRoutingFeatures = "server"; @@ -114,71 +96,18 @@ sound.enable = true; - # Set your time zone. - time.timeZone = "Europe/Brussels"; - - users = { - # Define users groups - groups = { - # The group used to deploy rebuilds without password authentication - deploy = { }; - }; - - # Define a user account. Don't forget to set a password with 'passwd'. - users = { - admin = { - description = "System Administrator"; - isNormalUser = true; - extraGroups = [ - config.users.groups.wheel.name # Enable 'sudo' for the user. - config.users.groups.deploy.name - ]; - initialPassword = "ChangeMe"; - openssh.authorizedKeys.keys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrG+ldRBdCeHEXrsy/qHXIJYg8xQXVuiUR0DxhFjYNg" - ]; - }; - - jellyfin-mpv-shim = { - description = "Jellyfin MPV Shim User"; - isNormalUser = true; - extraGroups = [ - config.users.groups.audio.name - config.users.groups.video.name - ]; - packages = with pkgs; [ - jellyfin-mpv-shim - mpv - socat - ]; - }; - }; - }; - - security.sudo = { - enable = true; - extraRules = [ - { - groups = [ config.users.groups.deploy.name ]; - commands = [ - { - command = "/nix/store/*/bin/switch-to-configuration"; - options = [ "NOPASSWD" ]; - } - { - command = "/run/current-system/sw/bin/nix-store"; - options = [ "NOPASSWD" ]; - } - { - command = ''/bin/sh -c "readlink -e /nix/var/nix/profiles/system || readlink -e /run/current-system"''; - options = [ "NOPASSWD" ]; - } - { - command = "${config.system.build.nixos-rebuild}/bin/nixos-rebuild"; - options = [ "NOPASSWD" ]; - } - ]; - } + # Define a user account. Don't forget to set a password with 'passwd'. + users.users.jellyfin-mpv-shim = { + description = "Jellyfin MPV Shim User"; + isNormalUser = true; + extraGroups = [ + config.users.groups.audio.name + config.users.groups.video.name + ]; + packages = with pkgs; [ + jellyfin-mpv-shim + mpv + socat ]; }; @@ -229,6 +158,7 @@ # "8080:8080/tcp" # The Web UI (enabled by --api.insecure=true) ]; environment = { + # TODO Hide this! "CLOUDFLARE_DNS_API_TOKEN" = "6Vz64Op_a6Ls1ljGeBxFoOVfQ-yB-svRbf6OyPv2"; }; environmentFiles = [ @@ -244,67 +174,6 @@ }; autoStart = true; }; - technitium-dns = { - hostname = "technitium-dns"; - image = "technitium/dns-server:12.1"; - ports = [ - # "5380:5380/tcp" #DNS web console (HTTP) - # "53443:53443/tcp" #DNS web console (HTTPS) - "53:53/udp" #DNS service - "53:53/tcp" #DNS service - # "853:853/udp" #DNS-over-QUIC service - # "853:853/tcp" #DNS-over-TLS service - # "443:443/udp" #DNS-over-HTTPS service (HTTP/3) - # "443:443/tcp" #DNS-over-HTTPS service (HTTP/1.1, HTTP/2) - # "80:80/tcp" #DNS-over-HTTP service (use with reverse proxy or certbot certificate renewal) - # "8053:8053/tcp" #DNS-over-HTTP service (use with reverse proxy) - # "67:67/udp" #DHCP service - ]; - environment = { - # The primary domain name used by this DNS Server to identify itself. - DNS_SERVER_DOMAIN = config.networking.hostName; - # DNS Server will use IPv6 for querying whenever possible with this option enabled. - DNS_SERVER_PREFER_IPV6 = "true"; - # The TCP port number for the DNS web console over HTTP protocol. - # DNS_SERVER_WEB_SERVICE_HTTP_PORT=5380 - # The TCP port number for the DNS web console over HTTPS protocol. - # DNS_SERVER_WEB_SERVICE_HTTPS_PORT=53443 - # Enables HTTPS for the DNS web console. - # DNS_SERVER_WEB_SERVICE_ENABLE_HTTPS=false - # Enables self signed TLS certificate for the DNS web console. - # DNS_SERVER_WEB_SERVICE_USE_SELF_SIGNED_CERT=false - # Enables DNS server optional protocol DNS-over-HTTP on TCP port 8053 to be used with a TLS terminating reverse proxy like nginx. - # DNS_SERVER_OPTIONAL_PROTOCOL_DNS_OVER_HTTP=false - # Recursion options: Allow, Deny, AllowOnlyForPrivateNetworks, UseSpecifiedNetworks. - #nDNS_SERVER_RECURSION=AllowOnlyForPrivateNetworks - # Comma separated list of IP addresses or network addresses to deny recursion. Valid only for `UseSpecifiedNetworks` recursion option. - # DNS_SERVER_RECURSION_DENIED_NETWORKS=1.1.1.0/24 - # Comma separated list of IP addresses or network addresses to allow recursion. Valid only for `UseSpecifiedNetworks` recursion option. - # DNS_SERVER_RECURSION_ALLOWED_NETWORKS=127.0.0.1, 192.168.1.0/24 - # Sets the DNS server to block domain names using Blocked Zone and Block List Zone. - DNS_SERVER_ENABLE_BLOCKING = "false"; - # Specifies if the DNS Server should respond with TXT records containing a blocked domain report for TXT type requests. - # DNS_SERVER_ALLOW_TXT_BLOCKING_REPORT=false - # A comma separated list of block list URLs. - # DNS_SERVER_BLOCK_LIST_URLS= - #Comma separated list of forwarder addresses. - DNS_SERVER_FORWARDERS="195.130.130.2,195.130.131.2"; - # Forwarder protocol options: Udp, Tcp, Tls, Https, HttpsJson. - # DNS_SERVER_FORWARDER_PROTOCOL=Tcp - # Enable this option to use local time instead of UTC for logging. - # DNS_SERVER_LOG_USING_LOCAL_TIME=true - }; - volumes = [ - "dns:/etc/dns" - ]; - labels = { - "traefik.enable" = "true"; - "traefik.http.routers.technitium-dns.rule" = "Host(`dns.niko.depeuter.dev`)"; - "traefik.http.services.technitium-dns.loadbalancer.server.port" = "5380"; - "traefik.tls.options.default.minVersion" = "VersionTLS13"; - }; - autoStart = true; - }; }; }; }; diff --git a/hosts/Niko/hardware-configuration.nix b/hosts/Niko/hardware-configuration.nix index 260cf1f..34c1dc6 100644 --- a/hosts/Niko/hardware-configuration.nix +++ b/hosts/Niko/hardware-configuration.nix @@ -29,14 +29,14 @@ fsType = "vfat"; }; -# "/data/photos" = { -# device = "//192.168.0.11/CANVAS"; -# fsType = "cifs"; -# options = let -# # this line prevents hanging on network split -# automount_opts = "x-systemd.automount,noauto,x-systemd.idle-timeout=60,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s"; -# in ["${automount_opts},credentials=/etc/nixos/smb-secrets"]; -# }; + "/media/photos" = { + device = "//192.168.0.11/CANVAS"; + fsType = "cifs"; + options = let + # This line prevents hanging on network split + automount_opts = "x-systemd.automount,noauto,x-systemd.idle-timeout=60,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s,user,users"; + in ["${automount_opts},credentials=/etc/nixos/smb-secrets,uid=1002,gid=100"]; + }; }; swapDevices = [ diff --git a/hosts/ProductionGPU/default.nix b/hosts/ProductionGPU/default.nix new file mode 100644 index 0000000..75e48e7 --- /dev/null +++ b/hosts/ProductionGPU/default.nix @@ -0,0 +1,98 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps.jellyfin.enable = true; + virtualisation.guest.enable = true; + }; + + networking = { + hostId = "aaaa2200"; + domain = "roxanne.depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "enp6s18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.enp6s18 = { + ipv4.addresses = [ + { + address = "192.168.0.94"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "unstable"; + + ### Nvidia GPU support ### + + services.xserver.videoDrivers = [ "nvidia" ]; + + # virtualisation.docker.package = pkgs.nvidia-docker; + + nixpkgs.config = { + allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ + "nvidia-x11" + "nvidia-settings" + "nvidia-persistenced" + ]; + + # enable vaapi on OS-level + # packageOverrides = pkgs: { + # vaapiIntel = pkgs.vaapiIntel.override { + # enableHybridCodec = true; + # }; + # }; + }; + + hardware = { + opengl = { + enable = true; + # driSupport = true; + # driSupport32Bit = true; + extraPackages = with pkgs; [ + # intel-media-driver + # intel-vaapi-driver # previously vaapiIntel + # vaapiVdpau + # intel-compute-runtime # OpenCL filter support (hardware tonemapping and subtitle burn-in) + # unstable.vpl-gpu-rt # QSV on 11th gen or newer + # intel-media-sdk # QSV up to 11th gen + ]; + }; + + nvidia = { + package = config.boot.kernelPackages.nvidiaPackages.stable; + # Whether to enable kernel modesetting when using the NVIDIA proprietary driver. + modesetting.enable = true; +# powerManagement = { +# enable = false; +# finegrained = false; +# }; + open = false; + nvidiaSettings = false; + + # Whether to enable nvidia-persistenced a update for NVIDIA GPU headless mode, i.e. It ensures all GPUs stay awake even during headless mode . + # nvidiaPersistenced = true; + }; + nvidia-container-toolkit.enable = true; + }; + }; +} diff --git a/hosts/Template/default.nix b/hosts/Template/default.nix new file mode 100644 index 0000000..21e54b7 --- /dev/null +++ b/hosts/Template/default.nix @@ -0,0 +1,36 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab.virtualisation.guest.enable = true; + + networking = { + # TODO hostName = "nixos"; + # TODO hostId = "aaaa9000"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + useDHCP = false; + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.90"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Testing/default.nix b/hosts/Testing/default.nix new file mode 100644 index 0000000..78abc58 --- /dev/null +++ b/hosts/Testing/default.nix @@ -0,0 +1,48 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + virtualisation = { + containers.enable = true; + guest.enable = true; + }; + }; + + networking = { + hostName = "Testing"; + hostId = "aaaa9200"; + domain = "roxanne.depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.92"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Vaultwarden/default.nix b/hosts/Vaultwarden/default.nix new file mode 100644 index 0000000..9f98d84 --- /dev/null +++ b/hosts/Vaultwarden/default.nix @@ -0,0 +1,38 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps.vaultwarden.enable = true; + virtualisation.guest.enable = true; + }; + + networking = { + hostId = "aaaa1300"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + useDHCP = false; + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.22"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/modules/apps/arr/default.nix b/modules/apps/arr/default.nix new file mode 100644 index 0000000..8be935f --- /dev/null +++ b/modules/apps/arr/default.nix @@ -0,0 +1,269 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.apps.arr; + + networkName = "arrStack"; + appNames = [ "bazarr" "lidarr" "prowlarr" "qbittorrent" "radarr" "sonarr" ]; + inUse = builtins.any (app: cfg.${app}.enable) appNames; + + PGID = toString config.users.groups.media.gid; + UMASK = "002"; +in { + options.homelab.apps.arr = { + enable = lib.mkEnableOption "Arr Stack using Docker"; + + bazarr.enable = lib.mkEnableOption "Bazarr using Docker"; + lidarr.enable = lib.mkEnableOption "Lidarr using Docker"; + prowlarr.enable = lib.mkEnableOption "Prowlarr using Docker"; + qbittorrent.enable = lib.mkEnableOption "qBittorrent using Docker"; + radarr.enable = lib.mkEnableOption "Radarr using Docker"; + sonarr.enable = lib.mkEnableOption "Sonarr using Docker"; + }; + + config = { + homelab = { + users.media.enable = lib.mkIf inUse true; + + # "Master switch": Enable all apps. + apps.arr = { + bazarr.enable = lib.mkIf cfg.enable true; + lidarr.enable = lib.mkIf cfg.enable true; + prowlarr.enable = lib.mkIf cfg.enable true; + qbittorrent.enable = lib.mkIf cfg.enable true; + radarr.enable = lib.mkIf cfg.enable true; + sonarr.enable = lib.mkIf cfg.enable true; + }; + }; + + fileSystems = lib.mkIf inUse { + "/srv/video" = { + device = "192.168.0.11:/mnt/SMALL/MEDIA/VIDEO"; + fsType = "nfs"; + options = [ + "rw" + "nfsvers=4.2" + "async" "soft" + "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" + "nosuid" "tcp" + ]; + }; + + "/srv/qbittorrent" = { + device = "192.168.0.11:/mnt/SMALL/CONFIG/QBITTORRENT/qBittorrent"; + fsType = "nfs"; + options = [ + "rw" + "nfsvers=4.2" + "async" "soft" + "nosuid" "tcp" + ]; + }; + }; + + # Make sure the Docker network exists. + systemd.services."docker-${networkName}-create-network" = lib.mkIf inUse { + description = "Create Docker network for ${networkName}"; + requiredBy = [ + "docker-bazarr.service" + "docker-lidarr.service" + "docker-prowlarr.service" + "docker-qbittorrent.service" + "docker-radarr.service" + "docker-sonarr.service" + ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then + ${pkgs.docker}/bin/docker network create ${networkName} + fi + ''; + }; + + # Create a user for each app. + users.users = { + bazarr = lib.mkIf cfg.bazarr.enable { + uid = lib.mkForce 3003; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + lidarr = lib.mkIf cfg.lidarr.enable { + uid = lib.mkForce 3002; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + prowlarr = lib.mkIf cfg.prowlarr.enable { + uid = lib.mkForce 3004; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + qbittorrent = lib.mkIf cfg.qbittorrent.enable { + uid = lib.mkForce 3005; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + radarr = lib.mkIf cfg.radarr.enable { + uid = lib.mkForce 3006; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + sonarr = lib.mkIf cfg.sonarr.enable { + uid = lib.mkForce 3007; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + }; + + virtualisation.oci-containers.containers = { + bazarr = lib.mkIf cfg.bazarr.enable { + hostname = "bazarr"; + image = "ghcr.io/hotio/bazarr:release-1.4.4"; + autoStart = true; + ports = [ + "6767:6767/tcp" + "6767:6767/udp" + ]; + extraOptions = [ + "--network=${networkName}" + + "--mount" ''type=volume,source=bazarr-backup,target=/backup,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/mnt/BIG/BACKUP/BAZARR,"volume-opt=o=addr=192.168.0.11,rw,nfsvers=4.2,async,nosuid"'' + ]; + environment = { + PUID = toString config.users.users.bazarr.uid; + inherit PGID UMASK; + TZ = config.time.timeZone; + WEBUI_PORTS = "6767/tcp,6767/udp"; + }; + volumes = [ + "bazarr-config:/config" + "/srv/video:/data" + ]; + }; + + lidarr = lib.mkIf cfg.lidarr.enable { + hostname = "lidarr"; + image = "ghcr.io/hotio/lidarr:release-2.5.3.4341"; + autoStart = true; + ports = [ + "8686:8686/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + + "--mount" ''type=volume,source=lidarr-backup,target=/backup,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/mnt/BIG/BACKUP/LIDARR,"volume-opt=o=addr=192.168.0.11,rw,nfsvers=4.2,async,nosuid"'' + ]; + environment = { + PUID = toString config.users.users.lidarr.uid; + inherit PGID UMASK; + TZ = config.time.timeZone; + }; + volumes = [ + "lidarr-config:/config" + # TODO "data:/data" + ]; + }; + + prowlarr = lib.mkIf cfg.prowlarr.enable { + hostname = "prowlarr"; + image = "ghcr.io/hotio/prowlarr:release-1.23.1.4708"; + autoStart = true; + ports = [ + "9696:9696/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + environment = { + PUID = toString config.users.users.prowlarr.uid; + inherit PGID UMASK; + TZ = config.time.timeZone; + }; + volumes = [ + # TODO "config:/config" + ]; + }; + + qbittorrent = lib.mkIf cfg.qbittorrent.enable { + hostname = "qbittorrent"; + image = "ghcr.io/hotio/qbittorrent:release-4.6.7"; + autoStart = true; + ports = [ + "10095:10095/udp" + "10095:10095/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + + "--mount" ''type=volume,source=torrents,target=/data,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/mnt/SMALL/MEDIA/TORRENT,"volume-opt=o=addr=192.168.0.11,rw,nfsvers=4.2,async,nosuid"'' + ]; + environment = { + PUID = toString config.users.users.qbittorrent.uid; + inherit PGID UMASK; + TZ = config.time.timeZone; + WEBUI_PORTS = "10095/tcp,10095/udp"; + }; + volumes = [ + "/srv/qbittorrent:/config/config" + "/srv/video:/media/video" + ]; + }; + + radarr = lib.mkIf cfg.radarr.enable { + hostname = "radarr"; + image = "ghcr.io/hotio/radarr:release-5.9.1.9070"; + autoStart = true; + ports = [ + "7878:7878/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + environment = { + PUID = toString config.users.users.radarr.uid; + inherit PGID UMASK; + TZ = config.time.timeZone; + }; + volumes = [ + # TODO "config:/config" + # TODO "data:/data" + ]; + }; + + sonarr = lib.mkIf cfg.sonarr.enable { + hostname = "sonarr"; + image = "ghcr.io/hotio/sonarr:release-4.0.9.2244"; + autoStart = true; + ports = [ + "8989:8989/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + environment = { + PUID = toString config.users.users.sonarr.uid; + inherit PGID UMASK; + TZ = config.time.timeZone; + }; + volumes = [ + # TODO "config:/config" + # TODO "data:/data" + ]; + }; + }; + }; +} diff --git a/modules/apps/calibre/default.nix b/modules/apps/calibre/default.nix new file mode 100644 index 0000000..6fddb81 --- /dev/null +++ b/modules/apps/calibre/default.nix @@ -0,0 +1,17 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.apps.calibre; +in { + options.homelab.apps.calibre.enable = lib.mkEnableOption "Calibre"; + + config = lib.mkIf cfg.enable { + users.users.calibre = { + uid = lib.mkForce 3010; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + }; +} diff --git a/modules/apps/default.nix b/modules/apps/default.nix new file mode 100644 index 0000000..268476e --- /dev/null +++ b/modules/apps/default.nix @@ -0,0 +1,12 @@ +{ + imports = [ + ./arr + ./calibre + ./gitea + ./jellyfin + ./plex + ./speedtest + ./technitium-dns + ./vaultwarden + ]; +} diff --git a/modules/apps/gitea/default.nix b/modules/apps/gitea/default.nix new file mode 100644 index 0000000..7a99a55 --- /dev/null +++ b/modules/apps/gitea/default.nix @@ -0,0 +1,654 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.apps.gitea; + + networkName = "gitea"; + + UID = 3015; + GID = config.users.groups.apps.gid; + postgresPassword = "ChangeMe"; + repoDir = "/srv/git"; + webPort = 3000; + sshPort = 2222; + dbPort = 5432; + redisPort = 6379; + + title = "Hugo's Forge"; + slogan = "Forging ideas into reality."; + description = "Personal git server for projects that don't need collaboration."; +in { + options.homelab.apps.gitea.enable = lib.mkEnableOption "Gitea"; + + config = lib.mkIf cfg.enable { + homelab = { + users = { + apps.enable = true; + backup.enable = true; + }; + + virtualisation.containers.enable = true; + }; + + users.users.gitea = { + uid = lib.mkForce UID; + isSystemUser = true; + group = config.users.groups.apps.name; + home = "/var/empty"; + shell = null; + }; + + # Use filesystem mounts because rootless containers otherwise don't have access to the mount path (nested in docker directories). + # You could probably fix this by modifying the access rights on the path, but what would the point of that be? + fileSystems = { + # Mount options: + # - hard: retry requests indefinitely if the server becomes unresponsive. + # - nosuid: prevent set-user-id and set-group-id bits + "/srv/gitea-config" = { + device = "192.168.0.11:/mnt/SMALL/CONFIG/GITEA"; + fsType = "nfs"; + options = [ + "rw" + "nfsvers=4.2" + "async" "soft" "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" + "nosuid" + "tcp" + ]; + }; + + "/srv/gitea-git" = { + device = "192.168.0.11:/mnt/SMALL/DATA/GIT"; + fsType = "nfs"; + options = [ + "rw" + "nfsvers=4.2" + "async" "soft" "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" + "nosuid" + "tcp" + ]; + }; + }; + + # Make sure the Docker network exists. + systemd.services."docker-${networkName}-create-network" = { + description = "Create Docker network for ${networkName}"; + requiredBy = [ + "docker-gitea-db.service" + "docker-gitea.service" + ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then + ${pkgs.docker}/bin/docker network create ${networkName} + fi + ''; + }; + + virtualisation.oci-containers.containers = { + gitea-db = { + hostname = "gitea-db"; + image = "postgres:15.8-alpine"; + autoStart = true; + ports = [ + "5432:${toString dbPort}/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + environment = { + POSTGRES_PASSWORD = "ChangeMe"; + PGDATA = "/var/lib/postgresql/data/pgdata"; + }; + volumes = [ + "gitea-db:/var/lib/postgresql/data/pgdata" + ]; + }; + + gitea-redis = { + hostname = "gitea-redis"; + image = "redis:7.4.0-alpine3.20"; + autoStart = true; + ports = [ + "6379:${toString redisPort}/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + volumes = [ + "gitea-redis:/data" + ]; + }; + + gitea = { + hostname = "gitea"; + image = "codeberg.org/forgejo/forgejo:8.0.3-rootless"; + autoStart = true; + user = "${toString UID}:${toString GID}"; + ports = [ + "3000:${toString webPort}/tcp" + "2222:${toString sshPort}/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + dependsOn = [ + "gitea-db" + "gitea-redis" + ]; + volumes = [ + "/srv/gitea-config:/var/lib/gitea" + "/srv/gitea-git:/srv/git" + "/etc/timezone:/etc/timezone:ro" + "/etc/localtime:/etc/localtime:ro" + ]; + environmentFiles = [ + # NOTE Don't forget to create this file. + # TODO Put in place using age(nix)? + "/var/lib/gitea.env" + ]; + environment = { + # App name that shows in every page title. + FORGEJO__APP_NAME = title; + # Shows a slogan near the App name in every page title. + FORGEJO__APP_SLOGAN = slogan; + # Defines how the AppDisplayName should be presented. + #FORGEJO__APP_DISPLAY_NAME_FORMAT = ""; + # Will automaticaly detect the current user - but you can set it here. + FORGEJO__RUN_USER = "gitea"; + # Application run mode, affects performance and debugging: "dev" or "prod", default is + # "prod". Mode "dev" makes Gitea easier to develop and debug, values other than "dev" are + # treated as "prod" which is for production use. + FORGEJO__RUN_MODE = "prod"; + # The working directory. + #WORK_PATH = ""; + + # Disable SSH feature when not available. + FORGEJO__server__DISABLE_SSH = "false"; + # Whether to use the builltin SSH server or not. + FORGEJO__server__START_SSH_SERVER = "true"; + # Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER. + #FORGEJO__server__BUILTIN_SSH_SERVER_USER = "git"; + # Domain to be exposed in clone URL. + #FORGEJO__server__SSH_DOMAIN = ""; + # SSH username displayed in clone URLs. + #FORGEJO__server__SSH_USER = "git"; + # The network interface the builtin SSH server should listen on. + #FORGEJO__server__SSH_LISTEN_HOST = "ens18"; + # Port number to be exposed in clone URL. + FORGEJO__server__SSH_PORT = "22"; + # Port number the builtin SSH server should listen on. + FORGEJO__server__SSH_LISTEN_PORT = toString sshPort; + # Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'. + FORGEJO__server__SSH_ROOT_PATH = "/var/lib/gitea/ssh"; + # Gitea will create a authorized_keys file by default when it is not using the internal ssh server + # If you intend to use the AuthorizedKeysCommand functionality then you should turn this off. + #FORGEJO__server__SSH_CREATE_AUTHORIZED_KEYS_FILE = "true"; + # Gitea will create a authorized_principals file by default when it is not using the internal ssh server + # If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off. + #FORGEJO__server__SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = "true"; + # For the built-in SSH server, choose the ciphers to support for SSH connections, + # for system SSH this setting has no effect + #FORGEJO__server__SSH_SERVER_CIPHERS = "chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com"; + # For the built-in SSH server, choose the key exchange algorithms to support for SSH connections, + # for system SSH this setting has no effect + #FORGEJO__server__SSH_SERVER_KEY_EXCHANGES = "curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1"; + # For the built-in SSH server, choose the MACs to support for SSH connections, + # for system SSH this setting has no effect + #FORGEJO__server__SSH_SERVER_MACS = "hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1"; + # For the built-in SSH server, choose the keypair to offer as the host key + # The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub + # relative paths are made absolute relative to the %(APP_DATA_PATH)s + FORGEJO__server__SSH_SERVER_HOST_KEYS = "/var/lib/gitea/ssh/forgejo.ed25519"; + # Directory to create temporary files in when testing public keys using ssh-keygen, + # default is the system temporary directory. + #FORGEJO__server__SSH_KEY_TEST_PATH = ""; + # Use `ssh-keygen` to parse public SSH keys. The value is passed to the shell. By default, Gitea does the parsing itself. + #FORGEJO__server__SSH_KEYGEN_PATH = ""; + # Enable SSH Authorized Key Backup when rewriting all keys, default is false + FORGEJO__server__SSH_AUTHORIZED_KEYS_BACKUP = "false"; + # ... + # Enable exposure of SSH clone URL to anonymous visitors, default is false. + FORGEJO__server__EXPOSE_ANONYMOUS = "false"; + # ... + # Enables git-lfs support. true or false, default is false. + FORGEJO__server__LFS_START_SERVER = "false"; + # ... + + # Database to use. Either "mysql", "postgres" or "sqlite3". + FORGEJO__database__DB_TYPE = "postgres"; + FORGEJO__database__HOST = "gitea-db:${toString dbPort}"; + FORGEJO__database__NAME = "gitea"; + FORGEJO__database__USER = "gitea"; + FORGEJO__database__PASSWD = postgresPassword; + #FORGEJO__database__SCHEMA = ""; + #FORGEJO__database__SSL_MODE = "disable"; + + # Whether the installer is disabled (set to true to disable the installer). + #FORGEJO__security__INSTALL_LOCK = "false"; + # Global security key that will be used. + # This key is VERY IMPORTANT. If you lose it, the data encrypted by it can't be decrypted anymore. + #FORGEJO__security__SECRET_KEY = ""; + # Alternatively, specify the location of the secret key. + #FORGEJO__security__SECRET_KEY_URI = "file:/etc/gitea/secret_key"; + # ... + + # IF the camo is enabled. + #FORGEJO__camo__ENABLED = "false"; + # .... + + # Enables OAuth2 provider + FORGEJO__oauth2__ENABLED = "false"; + # ... + + # Root path for the log files - defaults to %(GITEA_WORK_DIR)/log + #FORGEJO__log__ROOT_PATH = ""; + # Either "console", "file" or "conn", default is "console" + FORGEJO__log__MODE = "file"; + # Either "Trace", "Debug", "Info", "Warn", "Error" or "None", default is "Info". + FORGEJO__log__LEVEL = "Warn"; + # ... + # Collect SSH logs (Creates logs from ssh git requests) + FORGEJO__log__ENABLE_SSH_LOG = "true"; + # ... + + # The path of git executable. If empty, Gitea searches through the PATH environment. + #FORGEJO__git__PATH = ""; + # ... + FORGEJO__git_0x2E_timeout__MIGRATE = "600"; + FORGEJO__git_0x2E_timeout__MIRROR = "600"; + + # Time limit to confirm account/email registration. + #FORGEJO__service__ACTIVE_CODE_LIVE_MINUTES = "180"; + # Time limit to perform the reset of a forgotten password. + #FORGEJO__service__RESET_PASSWD_CODE_LIVE_MINUTES = "180"; + # Whether a new user needs to confirm their email when registering. + FORGEJO__service__REGISTER_EMAIL_CONFIRM = "true"; + # Whether a new user needs to be confirmed manually after registration. + FORGEJO__service__REGISTER_MANUAL_CONFIRM = "true"; + # List of domain names that are allowed to be used to register on a Gitea instance, wildcard is supported. + #FORGEJO__service__EMAIL_DOMAIN_ALLOWLIST = ""; + # Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance, wildcard is supported. + #FORGEJO__service__EMAIL_DOMAIN_BLOCKLIST = ""; + # Disallow registration, only allow admins to create accounts. + FORGEJO__service__DISABLE_REGISTRATION = "true"; + # Allow registration only using gitea itself, it works only when DISABLE_REGISTRATION is false. + FORGEJO__service__ALLOW_ONLY_INTERNAL_REGISTRATION = "true"; + # Allow registration only using third-party services, it works only when DISABLE_REGISTRATION is false. + FORGEJO__service__ALLOW_ONLY_EXTERNAL_REGISTRATION = "false"; + # User must sign in to view anything. + FORGEJO__service__REQUIRE_SIGNIN_VIEW = "false"; + # Mail notification + FORGEJO__service__ENABLE_NOTIFY_MAIL = "true"; + # This setting enables gitea to be signed in with HTTP BASIC Authentication using the user's password. + # If you set this to false you will not be able to access the tokens endpoints on the API with your password. + # Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token. + FORGEJO__service__ENABLE_BASIC_AUTHENTICATION = "false"; + # ... + # Enable captcha validation for registration. + FORGEJO__service__ENABLE_CAPTCHA = "true"; + # Enable this to require captcha validation for login. + FORGEJO__service__REQUIRE_CAPTCHA_FOR_LOGIN = "true"; + # Requires captcha for external registrations + #FORGEJO__service__REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA = "false"; + # Requires a password for external registrations. + #FORGEJO__service__REQUIRE_EXTERNAL_REGISTRATION_PASSWORD = "false"; + # Type of captcha you want to use. Options: image, recaptcha, hcaptcha, mcaptcha, cfturnstile. + FORGEJO__service__CAPTCHA_TYPE = "image"; + # ... + # Default value for KeepEmailPrivate + # Each new user will get the value of this setting copied into their profile + FORGEJO__service__DEFAULT_KEEP_EMAIL_PRIVATE = "true"; + # Default value for AllowCreateOrganization + # Every new user will have rights set to create organizations depending on this setting. + FORGEJO__service__DEFAULT_ALLOW_CREATE_ORGANIZATION = "true"; + # Default value for IsRestricted + # Every new user will have restricted permissions depending on this setting. + FORGEJO__service__DEFAULT_USER_IS_RESTRICTED = "false"; + # Users will be able to use dots when choosing their username. Disabling this is + # helpful if your usersare having issues with e.g. RSS feeds or advanced third-party + # extensions that use strange regex patterns. + FORGEJO__service__ALLOW_DOTS_IN_USERNAMES = "false"; + # Either "public", "limited" or "private", default is "public". + # Limited is for users visible only to signed users. + # Private is for users visible only to members of their organizations + # Public is for users visible for everyone + FORGEJO__service__DEFAULT_USER_VISIBILITY = "limited"; + # Set which visibility modes a user can have + FORGEJO__service__ALLOWED_USER_VISIBILITY_MODES = "public,limited,private"; + # Either "public", "limited" or "private", default is "public". + # Limited is for organizations visible only to signed users + # Private is for organizations visible only to members of the organization + # Public is for organizations visible to everyone + FORGEJO__service__DEFAULT_ORG_VISIBILITY = "limited"; + # Default value for DefaultOrgMemberVisible + # True will make the membership of the users visible when added to the organisation + FORGEJO__service__DEFAULT_ORG_MEMBER_VISIBLE = "false"; + # Default value for EnableDependencies + # Repositories will use dependencies by default depending on this setting + #FORGEJO__service__DEFAULT_ENABLE_DEPENDENCIES = "true"; + # Dependencies can be added from any repository where the user is granted access or only from the current repository depending on this setting. + #FORGEJO__service__ALLOW_CROSS_REPOSITORY_DEPENDENCIES = "true"; + # Default map service. No external API support has been included. A service has to allow + # searching using URL parameters, the location will be appended to the URL as escaped query parameter. + # Some example values are: + # - OpenStreetMap: https://www.openstreetmap.org/search?query= + # - Google Maps: https://www.google.com/maps/place/ + # - MapQuest: https://www.mapquest.com/search/ + # - Bing Maps: https://www.bing.com/maps?where1= + #FORGEJO__service__USER_LOCATION_MAP_URL = "https://www.openstreetmap.org/search?query="; + # Enable heatmap on users profiles. + FORGEJO__service__ENABLE_USER_HEATMAP = "true"; + # Enable Timetracking + FORGEJO__service__ENABLE_TIMETRACKING = "true"; + # Default value for EnableTimetracking + # Repositories will use timetracking by default depending on this setting + FORGEJO__service__DEFAULT_ENABLE_TIMETRACKING = "false"; + # Default value for AllowOnlyContributorsToTrackTime + # Only users with write permissions can track time if this is true + #FORGEJO__service__DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME = "true"; + # Value for the domain part of the user's email address in the git log if user + # has set KeepEmailPrivate to true. The user's email will be replaced with a + # concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. Default + # value is "noreply." + DOMAIN, where DOMAIN resolves to the value from server.DOMAIN + # Note: do not use the notation below + FORGEJO__service__NO_REPLY_ADDRESS = "noreply.depeuter.dev"; + # Show Registration button. + FOGEJO__service__SHOW_REGISTRATION_BUTTON = "false"; + # Show milestones dashboard page - a view of all the user's milestones. + #FORGEJO__service__SHOW_MILESTONES_DASHBOARD_PAGE = "true"; + # Default value for AutoWatchNewRepos + # When adding a repo to a team or creating a new repo all team members will watch the + # repo automatically if enabled + #FORGEJO__service__AUTO_WATCH_NEW_REPOS = "true"; + # Default value for AutoWatchOnChanges + # Make the user watch a repository When they commit for the first time + #FORGEJO__service__AUTO_WATCH_ON_CHANGES = "false"; + # Minimum amount of time a user must exist before comments are kept when the user is deleted. + #FORGEJO__service__USER_DELETE_WITH_COMMENTS_MAX_TIME = "0"; + # Valid site url schemes for user profiles + #FORGEJO__service__VALID_SITE_URL_SCHEMES = "http,https"; + + # Enable repository badges (via shields.io or a similar generator) + #FORGEJO__badges__ENABLED = "true"; + # ... + + # Root path for storing all repository data. By default, it is set to %(APP_DATA_PATH)s/gitea-repositories. + # A relative path is interpreted as _`AppWorkPath`_/%(ROOT)s + FORGEJO__repository__ROOT = repoDir; + # ... + # Disable stars feature. + FORGEJO__repository__DISABLE_STARS = "true"; + # Disable repository forking. + #FORGEJO__repository__DISABLE_FORKS = "false"; + # The default branch name of new repositories + FORGEJO__repository__DEFAULT_BRANCH = "main"; + # ... + + # List of prefixes used in Pull Request title to mark them as Work In Progress (matched in a case-insensitive manner) + FORGEJO__repository_0x2E_pull_0X2D_request__WORK_IN_PROGRESS_PREFIXES = "WIP:,[WIP],WIP"; + # ... + # In the default merge message for squash commits walk all commits to include all authors in the Co-authored-by otherwise just use those in the limited list. + FORGEJO__repository_0x2E_pull_0X2D_request__DEFAULT_MERGE_MESSAGE_ALL_AUTHORS = "true"; + # ... + + # Enable cors headers (disabled by default) + FORGEJO__cors__ENABLED = "true"; + # list of requesting origins that are allowed, eg: "https://*.example.com". + FORGEJO__cors__ALLOW_DOMAINS = "https://git.depeuter.dev,http://192.168.0.24:${toString webPort}"; + + # Set the default theme for the Gitea install. + FORGEJO__ui__DEFAULT_THEME = "gitea-auto"; + # All available themes. Allow users to select personalized themes regardless of `DEFAULT_THEME`. + FORGEJO__ui__THEMES = "gitea-auto,gitea-light,gitea-dark,forgejo-auto,forgejo-light,forgejo-dark,forgejo-auto-deuteranopia-protanopia,forgejo-light-deuteranopia-protanopia,forgejo-dark-deuteranopia-protanopia,forgejo-auto-tritanopia,forgejo-light-tritanopia-forgejo-dark-tritanopia,github-auto,github,github-dark,edge-auto,edge-light,edge-dark,everforest-auto,everforest-light,everforest-dark,gruvbox-auto,gruvbox-light,gruvbox-dark,gruvbox-material-auto,grubox-material-dark,gruvbox-material-light,sonokai-andromeda,sonokai-atlantis,sonokai-espresso,sonokai-maia,sonokai-shusia,sonokai,catppuccin-frappe-green,catppuccin-frappe-teal,catppuccin-frappe-sky,catppuccin-frappe-sapphire,catppuccin-frappe-blue,catppuccin-frappe-lavender,catppuccin-macchiato-green,catppuccin-macchiato-teal,catppuccin-macchiato-sky,catppuccin-macchiato-sapphire,catppuccin-macchiato-blue,catppuccin-macchiato-lavender,catppuccin-mocha-green,catppuccin-mocha-teal,catppuccin-mocha-sky,catppuccin-mocha-sapphire,catppuccin-mocha-blue,catppuccin-mocha-lavender,nord,pitchblack,matrix,dark-arc"; + + FORGEJO__ui_0x2E_meta__AUTHOR = "${title} - ${slogan}"; + FORGEJO__ui_0x2E_meta__DESCRIPTION = description; + FORGEJO__ui_0x2E_meta__KEYWORDS = "git,self-hosted,projects,code"; + + # Whether to render SVG files as images. If SVG rendering is disabled, SVG files are displayed as text and cannot be embedded in markdown files as images. + FORGEJO__ui_0x2E_svg__ENABLE_RENDER = "true"; + + # ... + # Enables math inline and block detection + FORGEJO__markdown__ENABLE_MATH = "true"; + + # Define allowed algorithms and their minimum key length (use -1 to disable a type) + #FORGEJO__ssh__0x2E__minimum_key_sizes__ED25519 = "256"; + #FORGEJO__ssh__0x2E__minimum_key_sizes__ECDSA = "256"; + FORGEJO__ssh_0x2E_minimum_key_sizes__RSA = "-1"; + FORGEJO__ssh_0x2E_minimum_key_sizes__DSA = "-1"; + + # ... indexer + + # ... queue + + # Disallow regular (non-admin) users from creating organizations. + #FORGEJO__admin__DISABLE_REGULAR_ORG_CREATION = "false"; + # Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled + FORGEJO__admin__DEFAULT_EMAIL_NOTIFICATIONS = "enabled"; + # Send an email to all admins when a new user signs up to inform the admins about this act. Options: true, false + FORGEJO__admin__SEND_NOTIFICATION_EMAIL_ON_NEW_USER = "true"; + # Disabled features for users, could be "deletion", "manage_ssh_keys","manage_gpg_keys" more features can be disabled in future + # - deletion: a user cannot delete their own account + # - manage_ssh_keys: a user cannot configure ssh keys + # - manage_gpg_keys: a user cannot configure gpg keys + #FORGEJO__admin__USER_DISABLED_FEATURES = ""; + # Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`. This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior. + # - deletion: a user cannot delete their own account + # - manage_ssh_keys: a user cannot configure ssh keys + # - manage_gpg_keys: a user cannot configure gpg keys + #FORGEJO__admin__EXTERNAL_USER_DISABLE_FEATURES = ""; + + # Whether to allow signin in via OpenID + FORGEJO__openid__ENABLE_OPENID_SIGNIN = "false"; + # Whether to allow registering via OpenID + # Do not include to rely on rhw DISABLE_REGISTRATION setting + FORGEJO__openid__ENABLE_OPENID_SIGNUP = "false"; + # ... + + # ... oath2_client + + # ... webhook + + FORGEJO__mailer__ENABLED = "true"; + # Buffer length of channel, keep it as it is if you don't know what it is. + #FORGEJO__mailer__SEND_BUFFER_LEN = "100"; + # Prefix displayed before subject in mail. + #FORGEJO__mailer__SUBJECT_PREFIX = ""; + # Mail server protocol. One of "smtp", "smtps", "smtp+starttls", "smtp+unix", "sendmail", "dummy" + FORGEJO__mailer__PROTOCOL = "smtps"; + # Mail server address + FORGEJO__mailer__SMTP_ADDR = "smtp.gmail.com"; + # Mail server port. If no protocol is specified, it will be inferred by this setting. + FORGEJO__mailer__SMTP_PORT = "465"; + # Enable HELO operation. Defaults to true. + #FORGEJO__mailer__ENABLE_HELO = "true"; + # Custom hostname fo the HELO operation. If no value is provided, one is retrieved from + # the system. + #FORGEJO__mailer__HELO_HOSTNAME = ""; + # If set to 'true', completely ignores server certificate validation errors. UNSAFE! + #FORGEJO__mailer__FORCE_TRUST_SERVER_CERT = "false"; + # Use client certificate in connection. + #FORGEJO__mailer__USE_CLIENT_CERT = "false"; + #FORGEJO__mailer__CLIENT_CERT_FILE = "custom/mailer/cert.pem"; + #FORGEJO__mailer__CLIENT_KEY_FILE = "custom/mailer/key.pem"; + # Mail from address, RFC 5322. This can be just an email address, or the + # `"Name" ` format. + FORGEJO__mailer__FROM = ''"${title}" ''; + # Sometimes it is helpful to use a different address on the envelope. Set this to use + # ENVELOPE_FROM as the from on the envelope. Set to `<>` to send an empty address. + #FORGEJO__mailer__ENVELOPE_FROM = ""; + # If gitea sends mails on behave of users, it will just use the name also displayed in the + # WebUI. If you want e.g. `Mister X (by CodeIt) `, set it to + # `{{ .DisplayName }} (by {{ .AppName }})`. + # Available Variables: `.DisplayName`, `.AppName` and `.Domain`. + #FORGEJO__mailer__FROM_DISPLAY_NAME_FORMAT = "{{ .DisplayName }}"; + # Mailer user name and password, if required by provider. + #FORGEJO__mailer__USER = ""; + # Use PASSWD = `your password` for quoting if you use special characters in the password. + #FORGEJO__mailer__PASSWD = ""; + # Send mails only in plain text, without HTML alternative + #FORGEJO__mailer__SEND_AS_PLAIN_TEXT = "false"; + # Specify an alternative sendmail binary + #FORGEJO__mailer__SENDMAIL_PATH = "sendmail"; + # Specify any extra sendmail arguments + # WARNING: if your sendmail program interprets options you should set this to "--" or terminate these args with "--" + #FORGEJO__mailer__SENDMAIL_ARGS = ""; + # Timeout for Sendmail + #FORGEJO__mailer__SENDMAIL_TIMEOUT = "5m"; + # convert \r\n to \n for Sendmail + #FORGEJO__mailer__SENDMAIL_CONVERT_CRLF = "true"; + + # ... email.incoming + + # Either "memory", "redis", "memcache", or "twoqueue". default is "memory" + FORGEJO__cache__ADAPTER = "redis"; + # For "memory" only, GC interval in seconds, default is 60. + #FORGEJO__cache__INTERVAL = "60"; + # For "redis" and "memcache", connection host address + # redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` (or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for a Redis cluster) + # memcache: `127.0.0.1:11211` + # twoqueue: `{"size":50000,"recent_ratio":0.25,"ghost_ratio":0.5}` or `50000` + FORGEJO__cache__HOST = "redis://gitea-redis:${toString redisPort}/0?pool_size=100&idle_timeout=180s"; + # Time to keep items in cache if not used, default is 16 hours. + # Setting it to -1 disables caching + FORGEJO__cache__ITEM_TTL = "16h"; + # Time to keep items in cache if not used, default is 8760 hours. + # Setting it to -1 disables caching + FORGEJO__cache_0X2E_last_0X2D_commit__ITEM_TTL = "8760h"; + # Only enable the cache when repository's commits count great than + FORGEJO__cache_0X2E_last_0X2D_commit__COMMITS_COUNT = "100"; + + # Either "memory", "file", "redis", "db", "mysql", "couchbase", "memcache" or "postgres" + # Default is "memory". "db" will reuse the configuration in [database] + #FORGEJO__session__PROVIDER = "memory"; + # Provider config options + # memory: doesn't have any config yet + # file: session file path, e.g. `data/sessions` + # redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` (or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for a Redis cluster) + # mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table` + #FORGEJO__session__PROVIDER_CONFIG = "data/sessions"; # Relative paths will be made absolute against _`AppWorkPath`_. + # Session cookie name + FORGEJO__session__COOKIE_NAME = "i_like_tibo"; + # If you use session in https only: true or false. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL. + FORGEJO__session__COOKIE_SECURE = "true"; + # Session GC time interval in seconds, default is 86400 (1 day) + #FORGEJO__session__GC_0X2E_INTERVAL_0X2E_TIME = "86400"; + # Session life time in seconds, default is 86400 (1 day) + #FORGEJO__session__SESSION_0X2E_LIFE_0X2E_TIME = "86400"; + # Cookie domain name. Default is empty + FORGEJO__session__DOMAIN = "git.depeuter.dev"; + # SameSite settings. Either "none", "lax", or "strict" + FORGEJO__session__SAME_SITE = "strict"; + + # How Gitea deals with missing repository avatars + # none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used + #FORGEJO__picture__REPOSITORY_AVATAR_FALLBACK = "none"; + #FORGEJO__picture__REPOSITORY_AVATAR_FALLBACK_IMAGE = "/img/repo_default.png"; + # Max Width and Height of uploaded avatars. + # This is to limit the amount of RAM used when resizing the image. + FORGEJO__picture__AVATAR_MAX_WIDTH = "10000"; + FORGEJO__picture__AVATAR_MAX_HEIGTH = "10000"; + # The multiplication factor for rendered avatar images. + # Larger values result in finer rendering on HiDPI devices. + #FORGEJO__picture__AVATAR_RENDERED_SIZE_FACTOR = "2"; + # Maximum allowed file size for uploaded avatars. + # This is to limit the amount of RAM used when resizing the image. + #FORGEJO__picture__AVATAR_MAX_FILE_SIZE = "1048576"; + # If the uploaded file is not larger than this byte size, the image will be used as is, without resizing/converting. + #FORGEJO__picture__AVATAR_MAX_ORIGIN_SIZE = "262144"; + # Chinese users can choose "duoshuo" + # or a custom avatar source, like: http://cn.gravatar.com/avatar/ + #FORGEJO__picture__GRAVATAR_SOURCE = "gravatar"; + # This value will always be true in offline mode. + #FORGEJO__picture__DISABLE_GRAVATAR = "false"; + # Federated avatar lookup uses DNS to discover avatar associated. + # with emails, see https://www.libravatar.org + # This value will always be false in offline mode or when Gravatar is disabled. + #FORGEJO__picture__ENABLE_FEDERATED_AVATAR = "false"; + + # ... attachment + + # ... time + + # ... cron + + # Enables the mirror functionality. Set to **false** to disable all mirrors. Pre-existing mirrors remain valid but won't be updated; may be converted to regular repo. + FORGEJO__mirror__ENABLED = "true"; + # Disable the creation of **new** pull mirrors. Pre-existing mirrors remain valid. Will be ignored if `mirror.ENABLED` is `false`. + FORGEJO__mirror__DISABLE_NEW_PULL = "false"; + # Disable the creation of **new** push mirrors. Pre-existing mirrors remain valid. Will be ignored if `mirror.ENABLED` is `false`. + FORGEJO__mirror__DISABLE_NEW_PUSH = "false"; + # Default interval as a duration between each check + FORGEJO__mirror__DEFAULT_INTERVAL = "1h"; + # Min interval as a duration must be > 1m + FORGEJO__mirror__MIN_INTERVAL = "5m"; + + # ... api + + # ... i18n + + # .. highlight.mapping + + # Show version information about Gitea and Go in the footer + FORGEJO__other__SHOW_FOOTER_VERSION = "false"; + # Show template execution time in the footer + FORGEJO__other__SHOW_FOOTER_TEMPLATE_LOAD_TIME = "false"; + # Show the "powered by" text in the footer + FORGEJO__other__SHOW_FOOTER_POWERED_BY = "false"; + # Generate sitemap. Defaults to `true`. + FORGEJO__other__ENABLE_SITEMAP = "true"; + # Enable/Disable RSS/Atom feed + FORGEJO__other__ENABLE_FEED = "true"; + + # ... markup + + # ... metrics + + # ... migrations + + # ... f3 + + # Enable/Disable federation capabilities + FORGEJO__federation_ENABLED = "false"; + # ... + + # Enable/Disable package registry capabilities + FORGEJO__packages__ENABLED = "true"; + + # ... storage + + # Repo-archive storage will override storage. + #FORGEJO__repo_0X2D_archive__STORAGE_TYPE = "local"; + # Where your lfs files reside, default is data/lfs + FORGEJO__repo_0X2D_archive__PATH = ""; + # Override the minio base path if storage type is minio. + #FORGEJO__repo_0X2D_archive__MINIO_BASE_PATH = ""; + + # lfs storage will override storage. + #FORGEJO__lfs__STORAGE_TYPE = "local"; + # Where your lfs files reside, default is data/lfs + FORGEJO__lfs__PATH = ""; + # Override the minio base path if storage is set to minio. + #FORGEJO__lfs__MINIO_BASE_PATH = "lfs/"; + + # Enable the proxy, all requests to external via HTTP will be affected + FORGEJO__proxy__PROXY_ENABLED = "false"; + # Proxy server URL, support http://, https//, socks://, blank will follow environment http_proxy/https_proxy/no_proxy + #FORGEJO__proxy__PROXY_URL = ""; + # Comma separated list of host names requiring proxy. Glob patterns (*) are accepted; use ** to match all hosts. + #FORGEJO__proxy__PROXY_HOSTS = ""; + + # Enable/Disable actions capabilities + FORGEJO__actions__ENABLED = "true"; + # Default address to get action plugins, e.g. the default value means downloading from "https://code.forgejo.org/actions/checkout" for "uses: actions/checkout@v3" + #FORGEJO__actions__DEFAULT_ACTIONS_URL = "https://code.forgejo.org"; + # ... + }; + }; + }; + }; +} diff --git a/modules/apps/jellyfin/default.nix b/modules/apps/jellyfin/default.nix new file mode 100644 index 0000000..9a1bc14 --- /dev/null +++ b/modules/apps/jellyfin/default.nix @@ -0,0 +1,155 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.apps.jellyfin; + + networkName = "jellyfin"; + + UID = 3008; + GID = config.users.groups.media.gid; +in { + options.homelab.apps.jellyfin.enable = lib.mkEnableOption "Jellyfin using Docker"; + + config = lib.mkIf cfg.enable { + homelab = { + users = { + apps.enable = true; + media.enable = true; + }; + virtualisation.containers.enable = true; + }; + + fileSystems = { + "/srv/audio" = { + device = "192.168.0.11:/mnt/SMALL/MEDIA/AUDIO"; + fsType = "nfs"; + options = [ + "ro" + "nfsvers=4.2" + "async" "soft" + "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" + "nosuid" "tcp" + ]; + }; + + "/srv/video" = { + device = "192.168.0.11:/mnt/SMALL/MEDIA/VIDEO"; + fsType = "nfs"; + options = [ + "ro" + "nfsvers=4.2" + "async" "soft" + "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" + "nosuid" "tcp" + ]; + }; + + "/srv/homevideo" = { + device = "192.168.0.11:/mnt/BIG/MEDIA/HOMEVIDEO/ARCHIVE"; + fsType = "nfs"; + options = [ + "ro" + "nfsvers=4.2" + "async" "soft" + "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" + "nosuid" "tcp" + ]; + }; + + "/srv/photo" = { + device = "192.168.0.11:/mnt/BIG/MEDIA/PHOTO/ARCHIVE"; + fsType = "nfs"; + options = [ + "ro" + "nfsvers=4.2" + "async" "soft" + "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" + "nosuid" "tcp" + ]; + }; + }; + + users.users.jellyfin = { + uid = lib.mkForce UID; + isSystemUser = true; + group = config.users.groups.apps.name; + extraGroups = [ + config.users.groups.media.name + ]; + home = "/var/empty"; + shell = null; + }; + + # Make sure the Docker network exists. + systemd.services."docker-${networkName}-create-network" = { + description = "Create Docker network for ${networkName}"; + requiredBy = [ + "docker-jellyfin.service" + "docker-feishin.service" + ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then + ${pkgs.docker}/bin/docker network create ${networkName} + fi + ''; + }; + + virtualisation.oci-containers.containers = { + jellyfin = { + hostname = "jellyfin"; + image = "jellyfin/jellyfin:10.10.0"; + user = "${toString UID}:${toString GID}"; + autoStart = true; + ports = [ + "8096:8096/tcp" + # "8920:8920/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + "--device=nvidia.com/gpu=all" # Equivalent to --gpus=all + ]; + volumes = [ + "jellyfin-config:/config" + "cache:/cache" + + "/srv/audio:/media/audio" + "/srv/video:/media/video" + "/srv/homevideo:/media/homevideo" + "/srv/photo:/media/photo" + ]; + environment = { + # TODO + }; + }; + + feishin = { + hostname = "feishin"; + image = "ghcr.io/jeffvli/feishin:0.7.1"; + ports = [ + "9180:9180/tcp" # Web player (HTTP) + ]; + extraOptions = [ + "--network=${networkName}" + ]; + environment = { + # pre defined server name + SERVER_NAME = "Hugo"; + # When true AND name/type/url are set, only username/password can be toggled + SERVER_LOCK = "true"; + # Either "jellyfin" or "navidrome" + SERVER_TYPE = "jellyfin"; + # http://address:port + SERVER_URL= "https://jelly.depeuter.dev"; + TZ = config.time.timeZone; + }; + labels = { + }; + autoStart = true; + }; + }; + }; +} diff --git a/modules/apps/plex/default.nix b/modules/apps/plex/default.nix new file mode 100644 index 0000000..251f9dd --- /dev/null +++ b/modules/apps/plex/default.nix @@ -0,0 +1,50 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.apps.plex; +in { + options.homelab.apps.plex.enable = lib.mkEnableOption "Plex"; + + config = lib.mkIf cfg.enable { + users.users.plex = { + uid = lib.mkForce 3009; + isSystemUser = true; + group = config.users.groups.media; + home = "/var/empty"; + shell = null; + }; + + virtualisation.oci-containers.containers = { + plex = { + hostname = "plex"; + image = "plexinc/pms-docker:1.41.0.8992-8463ad060"; + autoStart = true; + ports = [ + "32400:32400/tcp" # Plex Media Server + "1900:1900/udp" # Plex DLNA Server + "32469:32469/tcp" # Plex DLNA Server + "32410:32410/udp" # GDM network discovery + "32412:32412/udp" # GDM network discovery + "32413:32413/udp" # GDM network discovery + "32414:32414/udp" # GDM network discovery + # "8324:8324/tcp" # Controlling Plex for Roku via Plex Companion + ]; + environment = { + ADVERTISE_AP = "..."; # TODO Configure ip + ALLOWED_NETWORKS = "192.168.0.0/24,172.16.0.0/16"; + CHANGE_CONFIG_DIR_OWNERSHIP = "false"; + HOSTNAME = "PlexServer"; + PLEX_CLAIM = "..."; # TODO Add token + PLEX_UID = config.users.users.plex.uid; + PLEX_GID = config.users.groups.media.gid; + TZ = config.time.timeZone; + }; + volumes = [ + # TODO "config:/var/lib/plexmediaserver" + # TODO "transcode-temp:/transcode" + # TODO "media:/data" + ]; + }; + }; + }; +} diff --git a/modules/apps/speedtest/default.nix b/modules/apps/speedtest/default.nix new file mode 100644 index 0000000..4c42c64 --- /dev/null +++ b/modules/apps/speedtest/default.nix @@ -0,0 +1,27 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.apps.speedtest; +in { + options.homelab.apps.speedtest.enable = lib.mkEnableOption "Speedtest"; + + config = lib.mkIf cfg.enable { + homelab.virtualisation.containers.enable = true; + + virtualisation.oci-containers.containers.speedtest = { + hostname = "speedtest"; + image = "openspeedtest/latest:v2.0.5"; + ports = [ + "3000:3000" + "3001:3001" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.speedtest.rule" = "Host(`speedtest.${config.networking.hostName}.${config.networking.domain}`)"; + "traefik.http.services.speedtest.loadbalancer.server.port" = "9090"; + "traefik.tls.options.default.minVersion" = "VersionTLS13"; + }; + autoStart = true; + }; + }; +} diff --git a/modules/apps/technitium-dns/default.nix b/modules/apps/technitium-dns/default.nix new file mode 100644 index 0000000..0d0c71c --- /dev/null +++ b/modules/apps/technitium-dns/default.nix @@ -0,0 +1,73 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.apps.technitiumDNS; +in { + options.homelab.apps.technitiumDNS.enable = lib.mkEnableOption "Technitium DNS"; + + config = lib.mkIf cfg.enable { + homelab.virtualisation.containers.enable = true; + + virtualisation.oci-containers.containers.technitium-dns = { + hostname = "technitium-dns"; + image = "technitium/dns-server:12.1"; + ports = [ + # "5380:5380/tcp" #DNS web console (HTTP) + # "53443:53443/tcp" #DNS web console (HTTPS) + "53:53/udp" #DNS service + "53:53/tcp" #DNS service + # "853:853/udp" #DNS-over-QUIC service + # "853:853/tcp" #DNS-over-TLS service + # "443:443/udp" #DNS-over-HTTPS service (HTTP/3) + # "443:443/tcp" #DNS-over-HTTPS service (HTTP/1.1, HTTP/2) + # "80:80/tcp" #DNS-over-HTTP service (use with reverse proxy or certbot certificate renewal) + # "8053:8053/tcp" #DNS-over-HTTP service (use with reverse proxy) + # "67:67/udp" #DHCP service + ]; + environment = { + # The primary domain name used by this DNS Server to identify itself. + DNS_SERVER_DOMAIN = config.networking.hostName; + # DNS Server will use IPv6 for querying whenever possible with this option enabled. + DNS_SERVER_PREFER_IPV6 = "true"; + # The TCP port number for the DNS web console over HTTP protocol. + # DNS_SERVER_WEB_SERVICE_HTTP_PORT=5380 + # The TCP port number for the DNS web console over HTTPS protocol. + # DNS_SERVER_WEB_SERVICE_HTTPS_PORT=53443 + # Enables HTTPS for the DNS web console. + # DNS_SERVER_WEB_SERVICE_ENABLE_HTTPS=false + # Enables self signed TLS certificate for the DNS web console. + # DNS_SERVER_WEB_SERVICE_USE_SELF_SIGNED_CERT=false + # Enables DNS server optional protocol DNS-over-HTTP on TCP port 8053 to be used with a TLS terminating reverse proxy like nginx. + # DNS_SERVER_OPTIONAL_PROTOCOL_DNS_OVER_HTTP=false + # Recursion options: Allow, Deny, AllowOnlyForPrivateNetworks, UseSpecifiedNetworks. + #nDNS_SERVER_RECURSION=AllowOnlyForPrivateNetworks + # Comma separated list of IP addresses or network addresses to deny recursion. Valid only for `UseSpecifiedNetworks` recursion option. + # DNS_SERVER_RECURSION_DENIED_NETWORKS=1.1.1.0/24 + # Comma separated list of IP addresses or network addresses to allow recursion. Valid only for `UseSpecifiedNetworks` recursion option. + # DNS_SERVER_RECURSION_ALLOWED_NETWORKS=127.0.0.1, 192.168.1.0/24 + # Sets the DNS server to block domain names using Blocked Zone and Block List Zone. + DNS_SERVER_ENABLE_BLOCKING = "false"; + # Specifies if the DNS Server should respond with TXT records containing a blocked domain report for TXT type requests. + # DNS_SERVER_ALLOW_TXT_BLOCKING_REPORT=false + # A comma separated list of block list URLs. + # DNS_SERVER_BLOCK_LIST_URLS= + #Comma separated list of forwarder addresses. + DNS_SERVER_FORWARDERS="195.130.130.2,195.130.131.2"; + # Forwarder protocol options: Udp, Tcp, Tls, Https, HttpsJson. + # DNS_SERVER_FORWARDER_PROTOCOL=Tcp + # Enable this option to use local time instead of UTC for logging. + # DNS_SERVER_LOG_USING_LOCAL_TIME=true + }; + volumes = [ + "technitium_dns:/etc/dns" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.technitium-dns.rule" = "Host(`dns.${config.networking.hostName}.${config.networking.domain}`)"; + "traefik.http.services.technitium-dns.loadbalancer.server.port" = "5380"; + "traefik.tls.options.default.minVersion" = "VersionTLS13"; + }; + autoStart = true; + }; + }; +} diff --git a/modules/apps/vaultwarden/default.nix b/modules/apps/vaultwarden/default.nix new file mode 100644 index 0000000..196beb4 --- /dev/null +++ b/modules/apps/vaultwarden/default.nix @@ -0,0 +1,636 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.apps.vaultwarden; + + networkName = "vaultwarden"; +in { + options.homelab.apps.vaultwarden.enable = lib.mkEnableOption "Vaultwarden"; + + config = lib.mkIf cfg.enable { + homelab = { + # Allow remote backups. + users.backup.enable = true; + + virtualisation.containers.enable = true; + }; + + # Make sure the Docker network exists. + systemd.services."docker-${networkName}-create-network" = { + description = "Create Docker network for ${networkName}"; + requiredBy = [ + "docker-vaultwarden-db.service" + "docker-vaultwarden.service" + ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then + ${pkgs.docker}/bin/docker network create ${networkName} + fi + ''; + }; + + virtualisation.oci-containers.containers = { + vaultwarden-db = { + hostname = "vaultwarden-db"; + image = "postgres:15.8-alpine"; + autoStart = true; + ports = [ + "5432:5432/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + environment = { + POSTGRES_PASSWORD = "ChangeMe"; + PGDATA = "/var/lib/postgresql/data/pgdata"; + }; + volumes = [ + "vaultwarden-db:/var/lib/postgresql/data" + ]; + }; + + vaultwarden = let + dataDir = "/data"; + in { + hostname = "vaultwarden"; + image = "vaultwarden/server:1.30.5-alpine"; + autoStart = true; + ports = [ + "10102:80/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + dependsOn = [ + "vaultwarden-db" + ]; + volumes = [ + "vaultwarden:${dataDir}" + ]; + environmentFiles = [ + # NOTE Don't forget to create this file + # TODO Put in place using age(nix)? + "/var/lib/vaultwarden.env" + ]; + environment = { + #################### + ### Data folders ### + #################### + + ## Main data folder + DATA_FOLDER = dataDir; + + ## Individual folders, these override %DATA_FOLDER% + # ICON_CACHE_FOLDER=data/icon_cache + # ATTACHMENTS_FOLDER=data/attachments + # SENDS_FOLDER=data/sends + # TMP_FOLDER=data/tmp + + ## Templates data folder, by default uses embedded templates + ## Check source code to see the format + # TEMPLATES_FOLDER=data/templates + ## Automatically reload the templates for every request, slow, use only for development + # RELOAD_TEMPLATES=false + + ## Web vault settings + # WEB_VAULT_FOLDER=web-vault/ + # WEB_VAULT_ENABLED=true + + ######################### + ### Database settings ### + ######################### + + ## Database URL + ## When using SQLite, this is the path to the DB file, default to %DATA_FOLDER%/db.sqlite3 + # DATABASE_URL=data/db.sqlite3 + ## When using MySQL, specify an appropriate connection URI. + ## Details: https://docs.diesel.rs/2.1.x/diesel/mysql/struct.MysqlConnection.html + # DATABASE_URL=mysql://user:password@host[:port]/database_name + ## When using PostgreSQL, specify an appropriate connection URI (recommended) + ## or keyword/value connection string. + ## Details: + ## - https://docs.diesel.rs/2.1.x/diesel/pg/struct.PgConnection.html + ## - https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING + DATABASE_URL = "postgresql://vaultwarden:ChangeMe@vaultwarden-db:5432/vaultwarden"; + + ## Enable WAL for the DB + ## Set to false to avoid enabling WAL during startup. + ## Note that if the DB already has WAL enabled, you will also need to disable WAL in the DB, + ## this setting only prevents Vaultwarden from automatically enabling it on start. + ## Please read project wiki page about this setting first before changing the value as it can + ## cause performance degradation or might render the service unable to start. + # ENABLE_DB_WAL=true + + ## Database connection retries + ## Number of times to retry the database connection during startup, with 1 second delay between each retry, set to 0 to retry indefinitely + # DB_CONNECTION_RETRIES=15 + + ## Database timeout + ## Timeout when acquiring database connection + # DATABASE_TIMEOUT=30 + + ## Database max connections + ## Define the size of the connection pool used for connecting to the database. + # DATABASE_MAX_CONNS=10 + + ## Database connection initialization + ## Allows SQL statements to be run whenever a new database connection is created. + ## This is mainly useful for connection-scoped pragmas. + ## If empty, a database-specific default is used: + ## - SQLite: "PRAGMA busy_timeout = 5000; PRAGMA synchronous = NORMAL;" + ## - MySQL: "" + ## - PostgreSQL: "" + # DATABASE_CONN_INIT="" + + ################# + ### WebSocket ### + ################# + + ## Enable websocket notifications + # ENABLE_WEBSOCKET=true + + ########################## + ### Push notifications ### + ########################## + + ## Enables push notifications (requires key and id from https://bitwarden.com/host) + ## Details about mobile client push notification: + ## - https://github.com/dani-garcia/vaultwarden/wiki/Enabling-Mobile-Client-push-notification + # PUSH_ENABLED=false + # PUSH_INSTALLATION_ID=CHANGEME + # PUSH_INSTALLATION_KEY=CHANGEME + + # WARNING: Do not modify the following settings unless you fully understand their implications! + # Default Push Relay and Identity URIs + # PUSH_RELAY_URI=https://push.bitwarden.com + # PUSH_IDENTITY_URI=https://identity.bitwarden.com + # European Union Data Region Settings + # If you have selected "European Union" as your data region, use the following URIs instead. + # PUSH_RELAY_URI=https://api.bitwarden.eu + # PUSH_IDENTITY_URI=https://identity.bitwarden.eu + + ##################### + ### Schedule jobs ### + ##################### + + ## Job scheduler settings + ## + ## Job schedules use a cron-like syntax (as parsed by https://crates.io/crates/cron), + ## and are always in terms of UTC time (regardless of your local time zone settings). + ## + ## The schedule format is a bit different from crontab as crontab does not contains seconds. + ## You can test the the format here: https://crontab.guru, but remove the first digit! + ## SEC MIN HOUR DAY OF MONTH MONTH DAY OF WEEK + ## "0 30 9,12,15 1,15 May-Aug Mon,Wed,Fri" + ## "0 30 * * * * " + ## "0 30 1 * * * " + ## + ## How often (in ms) the job scheduler thread checks for jobs that need running. + ## Set to 0 to globally disable scheduled jobs. + # JOB_POLL_INTERVAL_MS=30000 + ## + ## Cron schedule of the job that checks for Sends past their deletion date. + ## Defaults to hourly (5 minutes after the hour). Set blank to disable this job. + # SEND_PURGE_SCHEDULE="0 5 * * * *" + ## + ## Cron schedule of the job that checks for trashed items to delete permanently. + ## Defaults to daily (5 minutes after midnight). Set blank to disable this job. + # TRASH_PURGE_SCHEDULE="0 5 0 * * *" + ## + ## Cron schedule of the job that checks for incomplete 2FA logins. + ## Defaults to once every minute. Set blank to disable this job. + # INCOMPLETE_2FA_SCHEDULE="30 * * * * *" + ## + ## Cron schedule of the job that sends expiration reminders to emergency access grantors. + ## Defaults to hourly (3 minutes after the hour). Set blank to disable this job. + # EMERGENCY_NOTIFICATION_REMINDER_SCHEDULE="0 3 * * * *" + ## + ## Cron schedule of the job that grants emergency access requests that have met the required wait time. + ## Defaults to hourly (7 minutes after the hour). Set blank to disable this job. + # EMERGENCY_REQUEST_TIMEOUT_SCHEDULE="0 7 * * * *" + ## + ## Cron schedule of the job that cleans old events from the event table. + ## Defaults to daily. Set blank to disable this job. Also without EVENTS_DAYS_RETAIN set, this job will not start. + # EVENT_CLEANUP_SCHEDULE="0 10 0 * * *" + ## Number of days to retain events stored in the database. + ## If unset (the default), events are kept indefinitely and the scheduled job is disabled! + # EVENTS_DAYS_RETAIN= + ## + ## Cron schedule of the job that cleans old auth requests from the auth request. + ## Defaults to every minute. Set blank to disable this job. + # AUTH_REQUEST_PURGE_SCHEDULE="30 * * * * *" + ## + ## Cron schedule of the job that cleans expired Duo contexts from the database. Does nothing if Duo MFA is disabled or set to use the legacy iframe prompt. + ## Defaults to every minute. Set blank to disable this job. + # DUO_CONTEXT_PURGE_SCHEDULE="30 * * * * *" + + ######################## + ### General settings ### + ######################## + + ## Domain settings + ## The domain must match the address from where you access the server + ## It's recommended to configure this value, otherwise certain functionality might not work, + ## like attachment downloads, email links and U2F. + ## For U2F to work, the server must use HTTPS, you can use Let's Encrypt for free certs + ## To use HTTPS, the recommended way is to put Vaultwarden behind a reverse proxy + ## Details: + ## - https://github.com/dani-garcia/vaultwarden/wiki/Enabling-HTTPS + ## - https://github.com/dani-garcia/vaultwarden/wiki/Proxy-examples + ## For development + # DOMAIN=http://localhost + ## For public server + DOMAIN = "https://vault.depeuter.dev"; + ## For public server (URL with port number) + # DOMAIN=https://vw.domain.tld:8443 + ## For public server (URL with path) + # DOMAIN=https://domain.tld/vw + + ## Controls whether users are allowed to create Bitwarden Sends. + ## This setting applies globally to all users. + ## To control this on a per-org basis instead, use the "Disable Send" org policy. + # SENDS_ALLOWED=true + + ## HIBP Api Key + ## HaveIBeenPwned API Key, request it here: https://haveibeenpwned.com/API/Key + # HIBP_API_KEY= + + ## Per-organization attachment storage limit (KB) + ## Max kilobytes of attachment storage allowed per organization. + ## When this limit is reached, organization members will not be allowed to upload further attachments for ciphers owned by that organization. + # ORG_ATTACHMENT_LIMIT= + ## Per-user attachment storage limit (KB) + ## Max kilobytes of attachment storage allowed per user. + ## When this limit is reached, the user will not be allowed to upload further attachments. + # USER_ATTACHMENT_LIMIT= + ## Per-user send storage limit (KB) + ## Max kilobytes of send storage allowed per user. + ## When this limit is reached, the user will not be allowed to upload further sends. + # USER_SEND_LIMIT= + + ## Number of days to wait before auto-deleting a trashed item. + ## If unset (the default), trashed items are not auto-deleted. + ## This setting applies globally, so make sure to inform all users of any changes to this setting. + # TRASH_AUTO_DELETE_DAYS= + + ## Number of minutes to wait before a 2FA-enabled login is considered incomplete, + ## resulting in an email notification. An incomplete 2FA login is one where the correct + ## master password was provided but the required 2FA step was not completed, which + ## potentially indicates a master password compromise. Set to 0 to disable this check. + ## This setting applies globally to all users. + # INCOMPLETE_2FA_TIME_LIMIT=3 + + ## Disable icon downloading + ## Set to true to disable icon downloading in the internal icon service. + ## This still serves existing icons from $ICON_CACHE_FOLDER, without generating any external + ## network requests. $ICON_CACHE_TTL must also be set to 0; otherwise, the existing icons + ## will be deleted eventually, but won't be downloaded again. + # DISABLE_ICON_DOWNLOAD=false + + ## Controls if new users can register + SIGNUPS_ALLOWED = "false"; + + ## Controls if new users need to verify their email address upon registration + ## Note that setting this option to true prevents logins until the email address has been verified! + ## The welcome email will include a verification link, and login attempts will periodically + ## trigger another verification email to be sent. + SIGNUPS_VERIFY = "true"; + + ## If SIGNUPS_VERIFY is set to true, this limits how many seconds after the last time + ## an email verification link has been sent another verification email will be sent + # SIGNUPS_VERIFY_RESEND_TIME=3600 + + ## If SIGNUPS_VERIFY is set to true, this limits how many times an email verification + ## email will be re-sent upon an attempted login. + # SIGNUPS_VERIFY_RESEND_LIMIT=6 + + ## Controls if new users from a list of comma-separated domains can register + ## even if SIGNUPS_ALLOWED is set to false + # SIGNUPS_DOMAINS_WHITELIST=example.com,example.net,example.org + + ## Controls whether event logging is enabled for organizations + ## This setting applies to organizations. + ## Disabled by default. Also check the EVENT_CLEANUP_SCHEDULE and EVENTS_DAYS_RETAIN settings. + # ORG_EVENTS_ENABLED=false + + ## Controls which users can create new orgs. + ## Blank or 'all' means all users can create orgs (this is the default): + # ORG_CREATION_USERS= + ## 'none' means no users can create orgs: + # ORG_CREATION_USERS=none + ## A comma-separated list means only those users can create orgs: + # ORG_CREATION_USERS=admin1@example.com,admin2@example.com + + ## Invitations org admins to invite users, even when signups are disabled + # INVITATIONS_ALLOWED=true + ## Name shown in the invitation emails that don't come from a specific organization + INVITATION_ORG_NAME = "Hugo's Vault"; + + ## The number of hours after which an organization invite token, emergency access invite token, + ## email verification token and deletion request token will expire (must be at least 1) + # INVITATION_EXPIRATION_HOURS=120 + + ## Controls whether users can enable emergency access to their accounts. + ## This setting applies globally to all users. + # EMERGENCY_ACCESS_ALLOWED=true + + ## Controls whether users can change their email. + ## This setting applies globally to all users + # EMAIL_CHANGE_ALLOWED=true + + ## Number of server-side passwords hashing iterations for the password hash. + ## The default for new users. If changed, it will be updated during login for existing users. + # PASSWORD_ITERATIONS=600000 + + ## Controls whether users can set password hints. This setting applies globally to all users. + # PASSWORD_HINTS_ALLOWED=true + + ## Controls whether a password hint should be shown directly in the web page if + ## SMTP service is not configured. Not recommended for publicly-accessible instances + ## as this provides unauthenticated access to potentially sensitive data. + SHOW_PASSWORD_HINT = "false"; + + ######################### + ### Advanced settings ### + ######################### + + ## Client IP Header, used to identify the IP of the client, defaults to "X-Real-IP" + ## Set to the string "none" (without quotes), to disable any headers and just use the remote IP + # IP_HEADER=X-Real-IP + + ## Icon service + ## The predefined icon services are: internal, bitwarden, duckduckgo, google. + ## To specify a custom icon service, set a URL template with exactly one instance of `{}`, + ## which is replaced with the domain. For example: `https://icon.example.com/domain/{}`. + ## + ## `internal` refers to Vaultwarden's built-in icon fetching implementation. + ## If an external service is set, an icon request to Vaultwarden will return an HTTP + ## redirect to the corresponding icon at the external service. An external service may + ## be useful if your Vaultwarden instance has no external network connectivity, or if + ## you are concerned that someone may probe your instance to try to detect whether icons + ## for certain sites have been cached. + # ICON_SERVICE=internal + + ## Icon redirect code + ## The HTTP status code to use for redirects to an external icon service. + ## The supported codes are 301 (legacy permanent), 302 (legacy temporary), 307 (temporary), and 308 (permanent). + ## Temporary redirects are useful while testing different icon services, but once a service + ## has been decided on, consider using permanent redirects for cacheability. The legacy codes + ## are currently better supported by the Bitwarden clients. + # ICON_REDIRECT_CODE=302 + + ## Cache time-to-live for successfully obtained icons, in seconds (0 is "forever") + ## Default: 2592000 (30 days) + # ICON_CACHE_TTL=2592000 + ## Cache time-to-live for icons which weren't available, in seconds (0 is "forever") + ## Default: 2592000 (3 days) + # ICON_CACHE_NEGTTL=259200 + + ## Icon download timeout + ## Configure the timeout value when downloading the favicons. + ## The default is 10 seconds, but this could be to low on slower network connections + # ICON_DOWNLOAD_TIMEOUT=10 + + ## Block HTTP domains/IPs by Regex + ## Any domains or IPs that match this regex won't be fetched by the internal HTTP client. + ## Useful to hide other servers in the local network. Check the WIKI for more details + ## NOTE: Always enclose this regex withing single quotes! + # HTTP_REQUEST_BLOCK_REGEX='^(192\.168\.0\.[0-9]+|192\.168\.1\.[0-9]+)$' + + ## Enabling this will cause the internal HTTP client to refuse to connect to any non global IP address. + ## Useful to secure your internal environment: See https://en.wikipedia.org/wiki/Reserved_IP_addresses for a list of IPs which it will block + # HTTP_REQUEST_BLOCK_NON_GLOBAL_IPS=true + + ## Client Settings + ## Enable experimental feature flags for clients. + ## This is a comma-separated list of flags, e.g. "flag1,flag2,flag3". + ## + ## The following flags are available: + ## - "autofill-overlay": Add an overlay menu to form fields for quick access to credentials. + ## - "autofill-v2": Use the new autofill implementation. + ## - "browser-fileless-import": Directly import credentials from other providers without a file. + ## - "fido2-vault-credentials": Enable the use of FIDO2 security keys as second factor. + # EXPERIMENTAL_CLIENT_FEATURE_FLAGS=fido2-vault-credentials + + ## Require new device emails. When a user logs in an email is required to be sent. + ## If sending the email fails the login attempt will fail!! + # REQUIRE_DEVICE_EMAIL=false + + ## Enable extended logging, which shows timestamps and targets in the logs + # EXTENDED_LOGGING=true + + ## Timestamp format used in extended logging. + ## Format specifiers: https://docs.rs/chrono/latest/chrono/format/strftime + # LOG_TIMESTAMP_FORMAT="%Y-%m-%d %H:%M:%S.%3f" + + ## Logging to Syslog + ## This requires extended logging + # USE_SYSLOG=false + + ## Logging to file + # LOG_FILE=/path/to/log + + ## Log level + ## Change the verbosity of the log output + ## Valid values are "trace", "debug", "info", "warn", "error" and "off" + ## Setting it to "trace" or "debug" would also show logs for mounted routes and static file, websocket and alive requests + ## For a specific module append a comma separated `path::to::module=log_level` + ## For example, to only see debug logs for icons use: LOG_LEVEL="info,vaultwarden::api::icons=debug" + LOG_LEVEL = "warn"; + + ## Token for the admin interface, preferably an Argon2 PCH string + ## Vaultwarden has a built-in generator by calling `vaultwarden hash` + ## For details see: https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page#secure-the-admin_token + ## If not set, the admin panel is disabled + ## New Argon2 PHC string + ## Note that for some environments, like docker-compose you need to escape all the dollar signs `$` with an extra dollar sign like `$$` + ## Also, use single quotes (') instead of double quotes (") to enclose the string when needed + # ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78' + ## Old plain text string (Will generate warnings in favor of Argon2) + # ADMIN_TOKEN=Vy2VyYTTsKPv8W5aEOWUbB/Bt3DEKePbHmI4m9VcemUMS2rEviDowNAFqYi1xjmp + + ## Enable this to bypass the admin panel security. This option is only + ## meant to be used with the use of a separate auth layer in front + # DISABLE_ADMIN_TOKEN=false + + ## Number of seconds, on average, between admin login requests from the same IP address before rate limiting kicks in. + # ADMIN_RATELIMIT_SECONDS=300 + ## Allow a burst of requests of up to this size, while maintaining the average indicated by `ADMIN_RATELIMIT_SECONDS`. + # ADMIN_RATELIMIT_MAX_BURST=3 + + ## Set the lifetime of admin sessions to this value (in minutes). + # ADMIN_SESSION_LIFETIME=20 + + ## Allowed iframe ancestors (Know the risks!) + ## https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors + ## Allows other domains to embed the web vault into an iframe, useful for embedding into secure intranets + ## This adds the configured value to the 'Content-Security-Policy' headers 'frame-ancestors' value. + ## Multiple values must be separated with a whitespace. + # ALLOWED_IFRAME_ANCESTORS= + + ## Number of seconds, on average, between login requests from the same IP address before rate limiting kicks in. + # LOGIN_RATELIMIT_SECONDS=60 + ## Allow a burst of requests of up to this size, while maintaining the average indicated by `LOGIN_RATELIMIT_SECONDS`. + ## Note that this applies to both the login and the 2FA, so it's recommended to allow a burst size of at least 2. + # LOGIN_RATELIMIT_MAX_BURST=10 + + ## BETA FEATURE: Groups + ## Controls whether group support is enabled for organizations + ## This setting applies to organizations. + ## Disabled by default because this is a beta feature, it contains known issues! + ## KNOW WHAT YOU ARE DOING! + # ORG_GROUPS_ENABLED=false + + ## Increase secure note size limit (Know the risks!) + ## Sets the secure note size limit to 100_000 instead of the default 10_000. + ## WARNING: This could cause issues with clients. Also exports will not work on Bitwarden servers! + ## KNOW WHAT YOU ARE DOING! + # INCREASE_NOTE_SIZE_LIMIT=false + + ## Enforce Single Org with Reset Password Policy + ## Enforce that the Single Org policy is enabled before setting the Reset Password policy + ## Bitwarden enforces this by default. In Vaultwarden we encouraged to use multiple organizations because groups were not available. + ## Setting this to true will enforce the Single Org Policy to be enabled before you can enable the Reset Password policy. + # ENFORCE_SINGLE_ORG_WITH_RESET_PW_POLICY=false + + ######################## + ### MFA/2FA settings ### + ######################## + + ## Yubico (Yubikey) Settings + ## Set your Client ID and Secret Key for Yubikey OTP + ## You can generate it here: https://upgrade.yubico.com/getapikey/ + ## You can optionally specify a custom OTP server + # YUBICO_CLIENT_ID=11111 + # YUBICO_SECRET_KEY=AAAAAAAAAAAAAAAAAAAAAAAA + # YUBICO_SERVER=http://yourdomain.com/wsapi/2.0/verify + + ## Duo Settings + ## You need to configure the DUO_IKEY, DUO_SKEY, and DUO_HOST options to enable global Duo support. + ## Otherwise users will need to configure it themselves. + ## Create an account and protect an application as mentioned in this link (only the first step, not the rest): + ## https://help.bitwarden.com/article/setup-two-step-login-duo/#create-a-duo-security-account + ## Then set the following options, based on the values obtained from the last step: + # DUO_IKEY= + # DUO_SKEY= + # DUO_HOST= + ## After that, you should be able to follow the rest of the guide linked above, + ## ignoring the fields that ask for the values that you already configured beforehand. + ## + ## If you want to attempt to use Duo's 'Traditional Prompt' (deprecated, iframe based) set DUO_USE_IFRAME to 'true'. + ## Duo no longer supports this, but it still works for some integrations. + ## If you aren't sure, leave this alone. + # DUO_USE_IFRAME=false + + ## Email 2FA settings + ## Email token size + ## Number of digits in an email 2FA token (min: 6, max: 255). + ## Note that the Bitwarden clients are hardcoded to mention 6 digit codes regardless of this setting! + # EMAIL_TOKEN_SIZE=6 + ## + ## Token expiration time + ## Maximum time in seconds a token is valid. The time the user has to open email client and copy token. + # EMAIL_EXPIRATION_TIME=600 + ## + ## Maximum attempts before an email token is reset and a new email will need to be sent. + # EMAIL_ATTEMPTS_LIMIT=3 + ## + ## Setup email 2FA regardless of any organization policy + # EMAIL_2FA_ENFORCE_ON_VERIFIED_INVITE=false + ## Automatically setup email 2FA as fallback provider when needed + # EMAIL_2FA_AUTO_FALLBACK=false + + ## Other MFA/2FA settings + ## Disable 2FA remember + ## Enabling this would force the users to use a second factor to login every time. + ## Note that the checkbox would still be present, but ignored. + # DISABLE_2FA_REMEMBER=false + ## + ## Authenticator Settings + ## Disable authenticator time drifted codes to be valid. + ## TOTP codes of the previous and next 30 seconds will be invalid + ## + ## According to the RFC6238 (https://tools.ietf.org/html/rfc6238), + ## we allow by default the TOTP code which was valid one step back and one in the future. + ## This can however allow attackers to be a bit more lucky with there attempts because there are 3 valid codes. + ## You can disable this, so that only the current TOTP Code is allowed. + ## Keep in mind that when a sever drifts out of time, valid codes could be marked as invalid. + ## In any case, if a code has been used it can not be used again, also codes which predates it will be invalid. + # AUTHENTICATOR_DISABLE_TIME_DRIFT=false + + ########################### + ### SMTP Email settings ### + ########################### + + ## Mail specific settings, set SMTP_FROM and either SMTP_HOST or USE_SENDMAIL to enable the mail service. + ## To make sure the email links are pointing to the correct host, set the DOMAIN variable. + ## Note: if SMTP_USERNAME is specified, SMTP_PASSWORD is mandatory + SMTP_HOST = "smtp.gmail.com"; + SMTP_FROM = "vault@depeuter.dev"; + SMTP_FROM_NAME = "Hugo's Vault"; + # SMTP_USERNAME=username + # SMTP_PASSWORD=password + # SMTP_TIMEOUT=15 + + ## Choose the type of secure connection for SMTP. The default is "starttls". + ## The available options are: + ## - "starttls": The default port is 587. + ## - "force_tls": The default port is 465. + ## - "off": The default port is 25. + ## Ports 587 (submission) and 25 (smtp) are standard without encryption and with encryption via STARTTLS (Explicit TLS). Port 465 (submissions) is used for encrypted submission (Implicit TLS). + SMTP_SECURITY = "starttls"; + SMTP_PORT = "587"; + + # Whether to send mail via the `sendmail` command + # USE_SENDMAIL=false + # Which sendmail command to use. The one found in the $PATH is used if not specified. + # SENDMAIL_COMMAND="/path/to/sendmail" + + ## Defaults for SSL is "Plain" and "Login" and nothing for Non-SSL connections. + ## Possible values: ["Plain", "Login", "Xoauth2"]. + ## Multiple options need to be separated by a comma ','. + SMTP_AUTH_MECHANISM = "Login"; + + ## Server name sent during the SMTP HELO + ## By default this value should be is on the machine's hostname, + ## but might need to be changed in case it trips some anti-spam filters + # HELO_NAME= + + ## Embed images as email attachments + # SMTP_EMBED_IMAGES=true + + ## SMTP debugging + ## When set to true this will output very detailed SMTP messages. + ## WARNING: This could contain sensitive information like passwords and usernames! Only enable this during troubleshooting! + # SMTP_DEBUG=false + + ## Accept Invalid Certificates + ## DANGEROUS: This option introduces significant vulnerabilities to man-in-the-middle attacks! + ## Only use this as a last resort if you are not able to use a valid certificate. + ## If the Certificate is valid but the hostname doesn't match, please use SMTP_ACCEPT_INVALID_HOSTNAMES instead. + # SMTP_ACCEPT_INVALID_CERTS=false + + ## Accept Invalid Hostnames + ## DANGEROUS: This option introduces significant vulnerabilities to man-in-the-middle attacks! + ## Only use this as a last resort if you are not able to use a valid certificate. + # SMTP_ACCEPT_INVALID_HOSTNAMES=false + + ####################### + ### Rocket settings ### + ####################### + + ## Rocket specific settings + ## See https://rocket.rs/v0.5/guide/configuration/ for more details. + # ROCKET_ADDRESS=0.0.0.0 + ## The default port is 8000, unless running in a Docker container, in which case it is 80. + # ROCKET_PORT=8000 + # ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"} + }; + }; + }; + }; +} diff --git a/modules/common/default.nix b/modules/common/default.nix new file mode 100644 index 0000000..44309f5 --- /dev/null +++ b/modules/common/default.nix @@ -0,0 +1,16 @@ +{ + config = { + homelab = { + services.openssh.enable = true; + users.admin.enable = true; + }; + + nix.settings.experimental-features = [ + "flakes" + "nix-command" + ]; + + # Set your time zone. + time.timeZone = "Europe/Brussels"; + }; +} diff --git a/modules/default.nix b/modules/default.nix new file mode 100644 index 0000000..5d901bc --- /dev/null +++ b/modules/default.nix @@ -0,0 +1,9 @@ +{ + imports = [ + ./apps + ./services + ./virtualisation + + ./common + ]; +} diff --git a/modules/services/actions/default.nix b/modules/services/actions/default.nix new file mode 100644 index 0000000..338b963 --- /dev/null +++ b/modules/services/actions/default.nix @@ -0,0 +1,49 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.services.actions; +in { + options.homelab.services.actions.enable = lib.mkEnableOption "Actions runner"; + + config = lib.mkIf cfg.enable { + homelab.virtualisation.containers.enable = true; + + services.gitea-actions-runner = { + instances.depeuter-dev = { + enable = true; + url = "https://git.depeuter.dev"; + tokenFile = "/etc/runner/depeuter-dev"; + name = config.networking.hostName; + labels = [ + "debian-11:docker://debian:11" + "debian-12:docker://debian:12" + "debian-latest:docker://debian:latest" + "docker:host" + "Linux:host" + "self-hosted:host" + "ubuntu-22.04:docker://ubuntu:22.04" + "ubuntu-24.04:docker://ubuntu:24.04" + "ubuntu-latest:docker://ubuntu:latest" + ]; + settings = { + cache.enabled = true; + container.privileged = true; + }; + hostPackages = with pkgs; [ + bash + cmake + coreutils + curl + docker + gawk + git + gnused + nodejs + openssh + wget + ]; + }; + }; + + }; +} diff --git a/modules/services/default.nix b/modules/services/default.nix new file mode 100644 index 0000000..f70bc54 --- /dev/null +++ b/modules/services/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./actions + ./openssh + ]; +} diff --git a/modules/services/openssh/default.nix b/modules/services/openssh/default.nix new file mode 100644 index 0000000..4b9cb5e --- /dev/null +++ b/modules/services/openssh/default.nix @@ -0,0 +1,20 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.services.openssh; +in { + options.homelab.services.openssh.enable = lib.mkEnableOption "OpenSSH daemon"; + + config = lib.mkIf cfg.enable { + services.openssh = { + # Enable the OpenSSH daemon. + enable = true; + settings = { + PasswordAuthentication = false; + PermitRootLogin = "no"; + # Disable keyboard-interactive authentication. + KbdInteractiveAuthentication = false; + }; + }; + }; +} diff --git a/modules/virtualisation/containers/default.nix b/modules/virtualisation/containers/default.nix new file mode 100644 index 0000000..ed87990 --- /dev/null +++ b/modules/virtualisation/containers/default.nix @@ -0,0 +1,23 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.virtualisation.containers; +in { + options.homelab.virtualisation.containers.enable = lib.mkEnableOption "OCI containers"; + + config = lib.mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + nfs-utils + ]; + + virtualisation = { + docker = { + enable = true; + enableOnBoot = true; + autoPrune.enable = true; + }; + + oci-containers.backend = "docker"; + }; + }; +} diff --git a/modules/virtualisation/default.nix b/modules/virtualisation/default.nix new file mode 100644 index 0000000..2290be2 --- /dev/null +++ b/modules/virtualisation/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./containers + ./guest + ]; +} diff --git a/modules/virtualisation/guest/default.nix b/modules/virtualisation/guest/default.nix new file mode 100644 index 0000000..bbb9a50 --- /dev/null +++ b/modules/virtualisation/guest/default.nix @@ -0,0 +1,34 @@ +{ config, lib, modulesPath, ... }: + +let + cfg = config.homelab.virtualisation.guest; +in { + options.homelab.virtualisation.guest.enable = lib.mkEnableOption "Settings for devices running on virtualisation, e.g. Proxmox"; + + imports = [ + (modulesPath + "/profiles/qemu-guest.nix") + ]; + + config = lib.mkIf cfg.enable { + boot = { + # Whether to enable growing the root partition on boot. + growPartition = true; + # Use Grub bootloader + loader.grub = { + enable = true; + devices = [ + "nodev" + ]; + }; + }; + + fileSystems."/" = lib.mkDefault { + device = "/dev/disk/by-label/nixos"; + autoResize = true; + fsType = "ext4"; + }; + + # Enable QEMU Guest for Proxmox + services.qemuGuest.enable = true; + }; +} diff --git a/users/admin/default.nix b/users/admin/default.nix new file mode 100644 index 0000000..552909b --- /dev/null +++ b/users/admin/default.nix @@ -0,0 +1,33 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.users.admin; +in { + options.homelab.users.admin.enable = lib.mkEnableOption "user System Administrator"; + + config = lib.mkIf cfg.enable { + nix.settings.trusted-users = [ + config.users.users.admin.name + ]; + + users.users.admin = { + description = "System Administrator"; + isNormalUser = true; + extraGroups = [ + config.users.groups.wheel.name # Enable 'sudo' for the user. + ]; + initialPassword = "ChangeMe"; + openssh.authorizedKeys.keys = [ + # TODO ChangeMe + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrG+ldRBdCeHEXrsy/qHXIJYg8xQXVuiUR0DxhFjYNg" + ]; + packages = with pkgs; [ + curl + git + tmux + vim + wget + ]; + }; + }; +} diff --git a/users/apps/default.nix b/users/apps/default.nix new file mode 100644 index 0000000..e4f7011 --- /dev/null +++ b/users/apps/default.nix @@ -0,0 +1,18 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.users.apps; +in { + options.homelab.users.apps.enable = lib.mkEnableOption "user Apps"; + + config.users = lib.mkIf cfg.enable { + groups.apps.gid = lib.mkForce 568; + users.apps = { + uid = lib.mkForce 568; + isSystemUser = true; + group = config.users.groups.apps.name; + home = "/var/empty"; + shell = null; + }; + }; +} diff --git a/users/backup/default.nix b/users/backup/default.nix new file mode 100644 index 0000000..8181d02 --- /dev/null +++ b/users/backup/default.nix @@ -0,0 +1,26 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.users.backup; +in { + options.homelab.users.backup.enable = lib.mkEnableOption "user Backup"; + + config = lib.mkIf cfg.enable { + users.users.backup = { + description = "Backup User"; + isNormalUser = true; + extraGroups = [ + "docker" # Allow access to the docker socket. + ]; + openssh.authorizedKeys.keys = [ + # TODO ChangeMe + + # Tibo-NixFat + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrG+ldRBdCeHEXrsy/qHXIJYg8xQXVuiUR0DxhFjYNg" + + # Hugo + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDAxR813vqq5zbu1NHrIybu5Imlu3k0rDCGxHiuGEhPoVV9c5FpnKNGLCi3ctm15ZcVBX4HcponYsKRBsCzM2pI4uXjxhHkLzbss5LttFuSzv5v/QHfLW1bvyJEMBEPxguGqAydAeWrBFdI9uHBEXeb325uKxMKBZHYvvpyAQ115c1wKy1bL8BfR0LTkhsFqexRvI86q59AVrAU/KFf6RXO0T9QA6H/vyWLlIPc7Ta+tSWwQ68bMmS5Pwn8q58tOAOAd6Lpt4TqUDJSppPjLEPKyKC6ShwMdEjwmwpEG0hxfsvaU8XERyQbSbEE9sLHRA2LoEdtMx3J8nzX3AwYUNspsqIv6NQZksnVqJ8OfL45ngUFcSJ6kBsUvCZfzEUGUTJ6Js0v84NOIXxNG/ZfPsk6ArXm3dvj2TYeK8llO6wpJnMMyztmmiODWoj9tepZSij44IgVM5wdWYIK/RZoYTsCQbmvJFfB8jhyJnf/7F19Vo5+LwhmCOsQh/KEK0F1DVc= admin@Hugo" + ]; + }; + }; +} diff --git a/users/default.nix b/users/default.nix new file mode 100644 index 0000000..fe82019 --- /dev/null +++ b/users/default.nix @@ -0,0 +1,9 @@ +{ + imports = [ + ./admin + ./apps + ./backup + ./deploy + ./media + ]; +} diff --git a/users/deploy/default.nix b/users/deploy/default.nix new file mode 100644 index 0000000..0509d1e --- /dev/null +++ b/users/deploy/default.nix @@ -0,0 +1,49 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.users.deploy; +in { + options.homelab.users.deploy.enable = lib.mkEnableOption "user Deploy"; + + config = lib.mkIf cfg.enable { + users = { + groups.deploy = { }; + + # The user used to deploy rebuilds without password authentication + users.deploy = { + group = config.users.groups.deploy.name; + isSystemUser = true; + home = "/var/empty"; + shell = pkgs.bashInteractive; + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrG+ldRBdCeHEXrsy/qHXIJYg8xQXVuiUR0DxhFjYNg" + ]; + }; + }; + + security.sudo.extraRules = [ + { + groups = [ + config.users.groups.deploy.name + ]; + commands = [ + { + command = "/nix/store/*-nix-*/bin/nix-env -p /nix/var/nix/profile/system --set /nix/store/*-*"; + options = [ "NOPASSWD" ]; + } + ]; + } + { + groups = [ + config.users.groups.deploy.name + ]; + commands = [ + { + command = "/nix/store/*/bin/switch-to-configuration"; + options = [ "NOPASSWD" ]; + } + ]; + } + ]; + }; +} diff --git a/users/media/default.nix b/users/media/default.nix new file mode 100644 index 0000000..e70a2b3 --- /dev/null +++ b/users/media/default.nix @@ -0,0 +1,18 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.users.media; +in { + options.homelab.users.media.enable = lib.mkEnableOption "user Media"; + + config.users = lib.mkIf cfg.enable { + groups.media.gid = lib.mkForce 3000; + users.media = { + uid = lib.mkForce 3001; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + }; +} From 32849cc5d24c039872bc7c996436270a13df9f66 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 9 Jan 2025 22:25:00 +0100 Subject: [PATCH 05/31] Initial commit --- flake.lock | 81 +++ flake.nix | 51 ++ hosts/ACE/default.nix | 41 ++ hosts/Binnenpost/default.nix | 81 +++ hosts/Development/default.nix | 67 ++ hosts/Gitea/default.nix | 38 + hosts/Ingress/default.nix | 233 ++++++ hosts/Isabel/.keep | 0 hosts/Isabel/dashboard/config/bookmarks.yaml | 32 + hosts/Isabel/dashboard/config/services.yaml | 30 + hosts/Isabel/default.nix | 255 +++++++ hosts/Isabel/hardware-configuration.nix | 39 + hosts/Niko/default.nix | 180 +++++ hosts/Niko/hardware-configuration.nix | 53 ++ hosts/Production/default.nix | 45 ++ hosts/ProductionGPU/default.nix | 98 +++ hosts/Template/default.nix | 36 + hosts/Testing/default.nix | 46 ++ hosts/Vaultwarden/default.nix | 38 + modules/apps/arr/default.nix | 282 ++++++++ modules/apps/calibre/default.nix | 17 + modules/apps/changedetection/default.nix | 28 + modules/apps/default.nix | 14 + modules/apps/freshrss/default.nix | 76 ++ modules/apps/gitea/default.nix | 673 ++++++++++++++++++ modules/apps/jellyfin/default.nix | 175 +++++ modules/apps/plex/default.nix | 50 ++ modules/apps/speedtest/default.nix | 27 + modules/apps/technitium-dns/default.nix | 73 ++ modules/apps/vaultwarden/default.nix | 636 +++++++++++++++++ modules/common/default.nix | 16 + modules/default.nix | 9 + modules/services/actions/default.nix | 49 ++ modules/services/default.nix | 6 + modules/services/openssh/default.nix | 20 + modules/virtualisation/containers/default.nix | 23 + modules/virtualisation/default.nix | 6 + modules/virtualisation/guest/default.nix | 34 + users/admin/default.nix | 33 + users/apps/default.nix | 18 + users/backup/default.nix | 26 + users/default.nix | 9 + users/deploy/default.nix | 49 ++ users/media/default.nix | 18 + 44 files changed, 3811 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 hosts/ACE/default.nix create mode 100644 hosts/Binnenpost/default.nix create mode 100644 hosts/Development/default.nix create mode 100644 hosts/Gitea/default.nix create mode 100644 hosts/Ingress/default.nix create mode 100644 hosts/Isabel/.keep create mode 100644 hosts/Isabel/dashboard/config/bookmarks.yaml create mode 100644 hosts/Isabel/dashboard/config/services.yaml create mode 100644 hosts/Isabel/default.nix create mode 100644 hosts/Isabel/hardware-configuration.nix create mode 100644 hosts/Niko/default.nix create mode 100644 hosts/Niko/hardware-configuration.nix create mode 100644 hosts/Production/default.nix create mode 100644 hosts/ProductionGPU/default.nix create mode 100644 hosts/Template/default.nix create mode 100644 hosts/Testing/default.nix create mode 100644 hosts/Vaultwarden/default.nix create mode 100644 modules/apps/arr/default.nix create mode 100644 modules/apps/calibre/default.nix create mode 100644 modules/apps/changedetection/default.nix create mode 100644 modules/apps/default.nix create mode 100644 modules/apps/freshrss/default.nix create mode 100644 modules/apps/gitea/default.nix create mode 100644 modules/apps/jellyfin/default.nix create mode 100644 modules/apps/plex/default.nix create mode 100644 modules/apps/speedtest/default.nix create mode 100644 modules/apps/technitium-dns/default.nix create mode 100644 modules/apps/vaultwarden/default.nix create mode 100644 modules/common/default.nix create mode 100644 modules/default.nix create mode 100644 modules/services/actions/default.nix create mode 100644 modules/services/default.nix create mode 100644 modules/services/openssh/default.nix create mode 100644 modules/virtualisation/containers/default.nix create mode 100644 modules/virtualisation/default.nix create mode 100644 modules/virtualisation/guest/default.nix create mode 100644 users/admin/default.nix create mode 100644 users/apps/default.nix create mode 100644 users/backup/default.nix create mode 100644 users/default.nix create mode 100644 users/deploy/default.nix create mode 100644 users/media/default.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..e3284fd --- /dev/null +++ b/flake.lock @@ -0,0 +1,81 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1735291276, + "narHash": "sha256-NYVcA06+blsLG6wpAbSPTCyLvxD/92Hy4vlY9WxFI1M=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "634fd46801442d760e09493a794c4f15db2d0cbb", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "utils": "utils" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "flake-utils": [ + "flake-utils" + ] + }, + "locked": { + "lastModified": 1722363685, + "narHash": "sha256-XCf2PIAT6lH7BwytgioPmVf/wkzXjSKScC4KzcZgb64=", + "owner": "gytis-ivaskevicius", + "repo": "flake-utils-plus", + "rev": "6b10f51ff73a66bb29f3bc8151a59d217713f496", + "type": "github" + }, + "original": { + "owner": "gytis-ivaskevicius", + "repo": "flake-utils-plus", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..48c91de --- /dev/null +++ b/flake.nix @@ -0,0 +1,51 @@ +{ + description = "Homelab configuration using flakes"; + + inputs = { + nixpkgs.url = "nixpkgs/nixos-unstable"; + + flake-utils.url = "github:numtide/flake-utils"; + utils = { + url = "github:gytis-ivaskevicius/flake-utils-plus"; + inputs.flake-utils.follows = "flake-utils"; + }; + }; + + outputs = inputs@{ + self, nixpkgs, + flake-utils, utils, + ... + }: + let + system = "x86_64-linux"; + in + utils.lib.mkFlake { + inherit self inputs; + + hostDefaults = { + inherit system; + + modules = [ + ./modules + ./users + ]; + }; + + hosts = { + Niko.modules = [ ./hosts/Niko ]; + + Ingress.modules = [ ./hosts/Ingress ]; + Gitea.modules = [ ./hosts/Gitea ]; + Vaultwarden.modules = [ ./hosts/Vaultwarden ]; + + Binnenpost.modules = [ ./hosts/Binnenpost ]; + Production.modules = [ ./hosts/Production ]; + ProductionGPU.modules = [ ./hosts/ProductionGPU ]; + ACE.modules = [ ./hosts/ACE ]; + + Template.modules = [ ./hosts/Template ]; + Development.modules = [ ./hosts/Development ]; + Testing.modules = [ ./hosts/Testing ]; + }; + }; +} diff --git a/hosts/ACE/default.nix b/hosts/ACE/default.nix new file mode 100644 index 0000000..04aa284 --- /dev/null +++ b/hosts/ACE/default.nix @@ -0,0 +1,41 @@ +{ pkgs, ... }: + +{ + config = { + homelab = { + services.actions.enable = true; + virtualisation.guest.enable = true; + }; + + networking = { + hostName = "ACE"; + hostId = "aaaa4100"; + domain = "depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.41"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Binnenpost/default.nix b/hosts/Binnenpost/default.nix new file mode 100644 index 0000000..d78e2da --- /dev/null +++ b/hosts/Binnenpost/default.nix @@ -0,0 +1,81 @@ +{ pkgs, ... }: + +{ + config = { + environment = { + etc."nixos/tailscale-authkey".text = '' + tskey-auth-k1tfJLTnGB11CNTRL-HhnegtfNzQ3G8h71SC2DR38PFXwseQiu + ''; + + systemPackages = with pkgs; [ + ethtool + ]; + }; + + homelab = { + apps = { + speedtest.enable = true; + technitiumDNS.enable = true; + }; + virtualisation.guest.enable = true; + }; + + networking = { + hostName = "Binnenpost"; + hostId = "aaaa1001"; + domain = "depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.89"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + services = { + networkd-dispatcher = { + enable = true; + rules."50-tailscale" = { + onState = ["routable"]; + script = '' + ${pkgs.ethtool}/bin/ethtool -K ens18 rx-udp-gro-forwarding on rx-gro-list off + ''; + }; + }; + + tailscale = { + enable = true; + useRoutingFeatures = "server"; + authKeyFile = "/etc/nixos/tailscale-authkey"; + extraUpFlags = [ + "--advertise-routes=192.168.0.0/24" + "--exit-node" + ]; + }; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Development/default.nix b/hosts/Development/default.nix new file mode 100644 index 0000000..da995f8 --- /dev/null +++ b/hosts/Development/default.nix @@ -0,0 +1,67 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps = { + arr = { + qbittorrent.enable = true; + }; + }; + virtualisation.guest.enable = true; + }; + + networking = { + hostId = "aaaa9100"; + domain = "roxanne.depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.91"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + + virtualisation.oci-containers.containers = { + pgadmin = { + image = "dpage/pgadmin4:8.11.0"; + ports = [ + "30056:80/tcp" + ]; + environment = { + # NOTE Required + # The email address used when setting up the initial administrator account to login to pgAdmin. + PGADMIN_DEFAULT_EMAIL = "kmtl.hugo+pgadmin@gmail.com"; + # NOTE Required + # The password used when setting up the initial administrator account to login to pgAdmin. + PGADMIN_DEFAULT_PASSWORD = "ChangeMe"; + }; + autoStart = true; + }; + }; + }; +} diff --git a/hosts/Gitea/default.nix b/hosts/Gitea/default.nix new file mode 100644 index 0000000..5b2492f --- /dev/null +++ b/hosts/Gitea/default.nix @@ -0,0 +1,38 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps.gitea.enable = true; + virtualisation.guest.enable = true; + }; + + networking = { + hostId = "aaaa1500"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + useDHCP = false; + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.24"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Ingress/default.nix b/hosts/Ingress/default.nix new file mode 100644 index 0000000..63e3ced --- /dev/null +++ b/hosts/Ingress/default.nix @@ -0,0 +1,233 @@ +{ config, pkgs, modulesPath, lib, system, ... }: + +{ + config = { + homelab.virtualisation.guest.enable = true; + + networking = { + hostName = "Ingress"; + hostId = "aaaa1000"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + useDHCP = false; + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.10"; +prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + + firewall = { + enable = true; + allowedTCPPorts = [ + 80 # HTTP + 443 # HTTPS + ]; + }; + }; + + security.acme = { + acceptTerms = true; + defaults = { + inherit (config.services.nginx) group; + dnsPropagationCheck = true; + dnsProvider = "cloudflare"; + dnsResolver = "1.1.1.1:53"; + email = "tibo.depeuter@telenet.be"; + credentialFiles = { + CLOUDFLARE_DNS_API_TOKEN_FILE = "/var/lib/secrets/depeuter-dev-cloudflare-api-token"; + }; + reloadServices = [ "nginx" ]; + }; + certs = { + "depeuter.dev" = { + domain = "depeuter.dev"; + extraDomainNames = [ "*.depeuter.dev" ]; + }; + "cloud.depeuter.dev" = { }; + "git.depeuter.dev" = { }; + "jelly.depeuter.dev" = { }; + "vault.depeuter.dev" = { }; + }; + }; + + # List services that you want to enable. + services = { + # Enable Nginx as a reverse proxy + nginx = { + enable = true; + + # Use recommended settings + # recommendedGzipSettings = true; + # recommendedOptimisation = true; + # recommendedProxySettings = true; + # recommendedTlsSettings = true; + + # Only allow PFS-enabled ciphers with AES256 + sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL"; + + upstreams.docservice.servers."192.168.0.14:8080" = {}; + + appendHttpConfig = '' + map $http_x_forwarded_proto $the_scheme { + default $http_x_forwarded_proto; + "" $scheme; + } + + map $http_x_forwarded_host $the_host { + default $http_x_forwarded_host; + "" $host; + } + + map $http_upgrade $proxy_connection { + default upgrade; + "" close; + } + ''; + + # Define hosts + virtualHosts = { + # Disable automatic routing. + "default" = { + locations."/".return = "301 https://youtu.be/dQw4w9WgXcQ"; + default = true; + }; + + "cloud.depeuter.dev" = { + enableACME = true; + forceSSL = true; + locations = { + "/" = { + proxyPass = "http://192.168.0.14"; + extraConfig = '' + add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; + fastcgi_request_buffering off; + ''; + }; + "/office/" = { + proxyPass = "http://192.168.0.14:8080/"; + priority = 500; + recommendedProxySettings = false; + extraConfig = '' + proxy_http_version 1.1; + ''; + }; + }; + extraConfig = '' + client_max_body_size 10G; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $proxy_connection; + proxy_set_header X-Forwarded-Host $the_host/office; + proxy_set_header X-Forwarded-Proto $the_scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + ''; + }; + "calendar.depeuter.dev".locations."/".return = "301 https://cloud.depeuter.dev/apps/calendar"; + "tasks.depeuter.dev".locations."/".return = "301 https://cloud.depeuter.dev/apps/tasks"; + "notes.depeuter.dev".locations."/".return = "301 https://cloud.depeuter.dev/apps/notes"; + + "jelly.depeuter.dev" = { + enableACME = true; + forceSSL = true; + locations = { + "/" = { + proxyPass = "http://192.168.0.94:8096"; + extraConfig = '' + # Proxy main Jellyfin traffic + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Protocol $scheme; + proxy_set_header X-Forwarded-Host $http_host; + + # Disable buffering when the nginx proxy gets very resource heavy upon streaming + proxy_buffering off; + ''; + }; + "/socket" = { + proxyPass = "http://192.168.0.91:8096"; + extraConfig = '' + # Proxy Jellyfin Websockets traffic + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Protocol $scheme; + proxy_set_header X-Forwarded-Host $http_host; + ''; + }; + }; + extraConfig = '' + client_max_body_size 20M; + + # Security / XSS Mitigation Headers + # NOTE: X-Frame-Options may cause issues with the webOS app + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + + # Permissions policy. May cause issues with some clients + add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), battery=(), bluetooth=(), camera=(), clipboard-read=(), display-capture=(), document-domain=(), encrypted-media=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), idle-detection=(), interest-cohort=(), keyboard-map=(), local-fonts=(), magnetometer=(), microphone=(), payment=(), publickey-credentials-get=(), serial=(), sync-xhr=(), usb=(), xr-spatial-tracking=()" always; + + # Content Security Policy + # See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP + # Enforces https content and restricts JS/CSS to origin + # External Javascript (such as cast_sender.js for Chromecast) must be whitelisted. + # NOTE: The default CSP headers may cause issues with the webOS app + add_header Content-Security-Policy "default-src https: data: blob: ; img-src 'self' https://* ; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' https://www.gstatic.com https://www.youtube.com blob:; worker-src 'self' blob:; connect-src 'self'; object-src 'none'; frame-ancestors 'self'"; + ''; + }; + "git.depeuter.dev" = { + enableACME = true; + forceSSL = true; + locations."/".proxyPass = "http://192.168.0.24:3000"; + extraConfig = '' + proxy_set_header Connection $http_connection; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + client_max_body_size 512M; + keepalive_timeout 600s; + proxy_buffers 4 256k; # Number and size of buffers for reading response + proxy_buffer_size 256k; # Buffer for the first part of the response + proxy_busy_buffers_size 256k; # Max size of busy buffers + proxy_http_version 1.1; + proxy_read_timeout 600s; + proxy_temp_file_write_size 256k; # Size of temp file for large responses + ''; + }; + "vault.depeuter.dev" = { + enableACME = true; + forceSSL = true; + locations = { + "/".proxyPass = "http://192.168.0.22:10102"; + "~ ^/admin".return = 403; + }; + }; + }; + }; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Isabel/.keep b/hosts/Isabel/.keep new file mode 100644 index 0000000..e69de29 diff --git a/hosts/Isabel/dashboard/config/bookmarks.yaml b/hosts/Isabel/dashboard/config/bookmarks.yaml new file mode 100644 index 0000000..ac0566a --- /dev/null +++ b/hosts/Isabel/dashboard/config/bookmarks.yaml @@ -0,0 +1,32 @@ +- Office: + - Zoho Mail: + - icon: zohomail + href: https://mail.zoho.eu +- Network: + - Cloudlfare: + - icon: cloudflare + href: https://dash.cloudflare.com + - Pulsetic: + - href: https://status.depeuter.dev + icon: https://pulsetic.com/favicon-196x196.png + - Telenet Internet usage: + - icon: https://static.telenet.be/assets/favicon/favicon.ico + href: https://www2.telenet.be/nl/klantenservice/raadpleeg-je-internetverbruik/ + - Telenet Modem: + - icon: https://static.telenet.be/assets/favicon/favicon.ico + # href: https://mijn.telenet.be/mijntelenet/rgw/settings.do?identifier=u381160&action=showAdvancedSettings + href: https://www2.telenet.be/residential/nl/mijn-telenet/je-thuisnetwerk#/mainnavitem=hgw/mainnavitemid=item-1/subnavitem=modem_general + - TransIP: + - icon: https://www.transip.eu/cache-60c9b25f/img/transip-new/favicons/favicon.png + href: https://www.transip.eu/cp/ +- Homemade: + - AI-Transparency: + - href: https://ai-transparency.depeuter.dev + icon: https://ai-transparency.depeuter.dev/img/transparency.png + - Down-message: + - href: https://down.depeuter.dev + icon: https://down.depeuter.dev/assets/icon.jpg + - Portfolio: + - href: https://tibo.depeuter.dev + icon: https://tibo.depeuter.dev/assets/owl_circuit.png + diff --git a/hosts/Isabel/dashboard/config/services.yaml b/hosts/Isabel/dashboard/config/services.yaml new file mode 100644 index 0000000..e944db1 --- /dev/null +++ b/hosts/Isabel/dashboard/config/services.yaml @@ -0,0 +1,30 @@ +- Networking: + - AXE5400 Tri-Band Wi-Fi 6E Router: + description: Router + href: https://tplinkwifi.net + ping: http://192.168.0.1 + icon: tp-link + - Traefik Isabel: + description: Reverse proxy manager + href: https://traefik.isabel.depeuter.dev/dashboard/# + ping: https://traefik.isabel.depeuter.dev/dashboard/# + icon: traefik + widget: + type: traefik + url: https://traefik.isabel.depeuter.dev + - Traefik Niko: + description: Reverse proxy manager + href: https://traefik.niko.depeuter.dev/dashboard/# + ping: https://traefik.niko.depeuter.dev/dashboard/# + - Technitium DNS Isabel: + description: DNS server + href: https://dns.Isabel.depeuter.dev + ping: http://192.168.0.13:53 + icon: technitium + - Technitium DNS Niko: + description: DNS server + href: https://dns.niko.depeuter.dev + ping: http://192.168.0.30:53 + icon: technitium + + diff --git a/hosts/Isabel/default.nix b/hosts/Isabel/default.nix new file mode 100644 index 0000000..0a1f50f --- /dev/null +++ b/hosts/Isabel/default.nix @@ -0,0 +1,255 @@ +{ config, pkgs, ... }: + +{ + imports = [ + # Include the results of the hardware scan. + ./hardware-configuration.nix + ]; + + # Use the systemd-boot EFI boot loader. + boot.loader = { + systemd-boot.enable = true; + efi = { + canTouchEfiVariables = true; + efiSysMountPoint = "/boot/efi"; + }; + }; + + console = { + font = "Lat2-Terminus16"; + keyMap = "us"; + }; + + # List packages installed in the system profile. To search, run: + # $ nix search wget + environment.systemPackages = with pkgs; [ + ]; + + environment.etc = { + "homepage/bookmarks.yaml".text = '' +- Office: + - Zoho Mail: + - icon: zohomail + href: https://mail.zoho.eu +- Network: + - Cloudlfare: + - icon: cloudflare + href: https://dash.cloudflare.com + - TransIP: + - icon: https://www.transip.eu/cache-60c9b25f/img/transip-new/favicons/favicon.png + href: https://www.transip.eu/cp/ + - Telenet Internet usage: + - icon: https://static.telenet.be/assets/favicon/favicon.ico + href: https://www2.telenet.be/nl/klantenservice/raadpleeg-je-internetverbruik/ + - Telenet Modem: + - icon: https://static.telenet.be/assets/favicon/favicon.ico + # href: https://mijn.telenet.be/mijntelenet/rgw/settings.do?identifier=u381160&action=showAdvancedSettings + href: https://www2.telenet.be/residential/nl/mijn-telenet/je-thuisnetwerk#/mainnavitem=hgw/mainnavitemid=item-1/subnavitem=modem_general + - Pulsetic: + - href: https://status.depeuter.dev + icon: https://pulsetic.com/favicon-196x196.png +- Homemade: + - AI-Transparency: + - href: https://ai-transparency.depeuter.dev + icon: https://ai-transparency.depeuter.dev/img/transparency.png + - Down-message: + - href: https://down.depeuter.dev + icon: https://down.depeuter.dev/assets/icon.jpg + - Portfolio: + - href: https://tibo.depeuter.dev + icon: https://tibo.depeuter.dev/assets/owl_circuit.png + ''; + + "homepage/services.yaml".text = '' +- Networking: + - Traefik Isabel: + description: Reverse proxy manager + href: https://traefik.isabel.depeuter.dev/dashboard/# + ping: https://traefik.isabel.depeuter.dev/dashboard/# + icon: traefik + widget: + type: traefik + url: https://traefik.isabel.depeuter.dev + - Traefik Niko: + description: Reverse proxy manager + href: https://traefik.niko.depeuter.dev/dashboard/# + ping: https://traefik.niko.depeuter.dev/dashboard/# + icon: traefik + widget: + type: traefik + url: https://traefik.niko.depeuter.dev + ''; + + "homepage/settings.yaml".text = '' +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/en/configs/settings + +providers: + openweathermap: openweathermapapikey + weatherapi: weatherapiapikey + ''; + }; + + homelab.apps.technitiumDNS.enable = true; + + # Select internationalisation properties. + i18n.defaultLocale = "en_GB.utf8"; + + networking = { + hostName = "Hugo-Isabel"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + networkmanager.enable = true; + }; + + # List services that you want to enable: + services = { + tailscale = { + enable = true; + useRoutingFeatures = "server"; + authKeyFile = "/etc/nixos/tailscale-authkey"; + extraUpFlags = [ + "--advertise-routes=192.168.0.0/24" + "--exit-node" + ]; + }; + + # Fix DNS issues. See: + # https://github.com/tailscale/tailscale/issues/4254 + # resolved.enable = true; + }; + + system.stateVersion = "24.05"; + + security.sudo = { + enable = true; + }; + + virtualisation = { + docker = { + enable = true; + autoPrune.enable = true; + }; + + oci-containers = { + backend = "docker"; + containers = { + reverse-proxy = { + hostname = "traefik"; + image = "traefik:v3.0"; + cmd = [ + "--api.insecure=true" + # Add Docker provider + "--providers.docker=true" + "--providers.docker.exposedByDefault=false" + # Add web entrypoint + "--entrypoints.web.address=:80/tcp" + "--entrypoints.web.http.redirections.entrypoint.to=websecure" + "--entrypoints.web.http.redirections.entrypoint.scheme=https" + # Add websecure entrypoint + "--entrypoints.websecure.address=:443/tcp" + "--entrypoints.websecure.http.tls=true" + "--entrypoints.websecure.http.tls.certResolver=letsencrypt" + "--entrypoints.websecure.http.tls.domains[0].main=depeuter.dev" + "--entrypoints.websecure.http.tls.domains[0].sans=*.depeuter.dev" + "--entrypoints.websecure.http.tls.domains[1].sans=*.isabel.depeuter.dev" + "--entrypoints.websecure.http.tls.domains[2].sans=*.jelly.depeuter.dev" + # Certificates + "--certificatesresolvers.letsencrypt.acme.dnschallenge=true" + "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare" + "--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be" + "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + + # Additional routes + ]; + ports = [ + "80:80/tcp" + "443:443/tcp" + # "8080:8080/tcp" # The Web UI (enabled by --api.insecure=true) + ]; + environment = { + # TODO Hide this! + "CLOUDFLARE_DNS_API_TOKEN" = "6Vz64Op_a6Ls1ljGeBxFoOVfQ-yB-svRbf6OyPv2"; + }; + environmentFiles = [ + ]; + volumes = [ + "/var/run/docker.sock:/var/run/docker.sock:ro" # So that Traefik can listen to the Docker events + "letsencrypt:/letsencrypt" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.traefik.rule" = "Host(`traefik.isabel.depeuter.dev`)"; + "traefik.http.services.traefik.loadbalancer.server.port" = "8080"; + }; + autoStart = true; + }; + feishin = { + hostname = "feishin"; + image = "ghcr.io/jeffvli/feishin:0.7.1"; + ports = [ + # "9180:9180/tcp" # Web player (HTTP) + ]; + environment = { + # pre defined server name + SERVER_NAME = "Hugo"; + # When true AND name/type/url are set, only username/password can be toggled + SERVER_LOCK = "true"; + # navidrome also works + SERVER_TYPE = "jellyfin"; + # http://address:port + SERVER_URL= "https://jelly.depeuter.dev"; + TZ = config.time.timeZone; + }; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.feishin.rule" = "Host(`music.depeuter.dev`)"; + "traefik.http.services.feishin.loadbalancer.server.port" = "9180"; + "traefik.tls.options.default.minVersion" = "VersionTLS13"; + }; + autoStart = true; + }; + dashboard = { + hostname = "dashboard"; + image = "ghcr.io/gethomepage/homepage:v0.9.3"; + ports = [ + # "3000:3000/tcp" + ]; + volumes = [ + "/etc/homepage:/app/config" # Make sure your local config directory exists + "/var/run/docker.sock:/var/run/docker.sock:ro" # optional, for docker integrations + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.dashboard.rule" = "Host(`dash.depeuter.dev`)"; + "traefik.http.services.dashboard.loadbalancer.server.port" = "3000"; + "traefik.tls.options.default.minVersion" = "VersionTLS13"; + }; + autoStart = true; + }; + prometheus = { + hostname = "prometheus"; + image = "prom/prometheus:v2.45.6"; + ports = [ + # "127.0.0.1:9090:9090/tcp" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.prometheus.rule" = "Host(`prometheus.isabel.depeuter.dev`)"; + "traefik.http.services.prometheus.loadbalancer.server.port" = "9090"; + "traefik.tls.options.default.minVersion" = "VersionTLS13"; + }; + autoStart = true; + }; + }; + }; + }; +} diff --git a/hosts/Isabel/hardware-configuration.nix b/hosts/Isabel/hardware-configuration.nix new file mode 100644 index 0000000..ec7ffda --- /dev/null +++ b/hosts/Isabel/hardware-configuration.nix @@ -0,0 +1,39 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = + [ (modulesPath + "/profiles/qemu-guest.nix") + ]; + + boot.initrd.availableKernelModules = [ "ata_piix" "xhci_pci" "ahci" "sd_mod" "sr_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-label/NIX-ROOT"; + fsType = "ext4"; + }; + + fileSystems."/boot/efi" = + { device = "/dev/disk/by-label/NIX-BOOT"; + fsType = "vfat"; + }; + + swapDevices = + [ { device = "/dev/disk/by-label/SWAP"; } + ]; + + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking + # (the default) this is the recommended approach. When using systemd-networkd it's + # still possible to use this option, but it's recommended to use it in conjunction + # with explicit per-interface declarations with `networking.interfaces..useDHCP`. + networking.useDHCP = lib.mkDefault true; + # networking.interfaces.ens3.useDHCP = lib.mkDefault true; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/hosts/Niko/default.nix b/hosts/Niko/default.nix new file mode 100644 index 0000000..57dbc27 --- /dev/null +++ b/hosts/Niko/default.nix @@ -0,0 +1,180 @@ +{ config, pkgs, ... }: + +{ + imports = [ + # Include the results of the hardware scan. + ./hardware-configuration.nix + ]; + + homelab = { + apps.technitiumDNS.enable = true; + users.deploy.enable = true; + }; + + # Use the systemd-boot EFI boot loader. + boot.loader = { + systemd-boot.enable = true; + efi = { + canTouchEfiVariables = true; + efiSysMountPoint = "/boot/efi"; + }; + }; + + console = { + font = "Lat2-Terminus16"; + keyMap = "us"; + }; + + # List packages installed in the system profile. To search, run: + # $ nix search wget + environment.systemPackages = with pkgs; [ + cifs-utils + ]; + + hardware = { + enableRedistributableFirmware = true; + enableAllFirmware = true; + pulseaudio.enable = true; + opengl.enable = true; + }; + + # Select internationalisation properties. + i18n.defaultLocale = "en_GB.utf8"; + + networking = { + hostName = "Niko"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + networkmanager.enable = true; + + extraHosts = '' + 192.168.0.11 jelly.depeuter.dev + ''; + }; + + nixpkgs.config.allowUnfree = true; + + # List services that you want to enable: + services = { + # Cage, a wayland kiosk service + cage = { + enable = true; + environment = { + # Do not fail when there are no input devices. + # WLR_LIBINPUT_NO_DEVICES = "1"; + }; + extraArguments = [ + "-d" # Don't draw client side decorations, when possible + # "-m" "last" # Use only the last connected output + "-s" # Allow VT switching + ]; + program = "/home/jellyfin-mpv-shim/start.sh"; + user = config.users.users.jellyfin-mpv-shim.name; + }; + + tailscale = { + enable = true; + useRoutingFeatures = "server"; + authKeyFile = "/etc/nixos/tailscale-authkey"; + extraUpFlags = [ + "--advertise-routes=192.168.0.0/24" + "--exit-node" + ]; + }; + + # Fix DNS issues. See: + # https://github.com/tailscale/tailscale/issues/4254 + # resolved.enable = true; + }; + + sound.enable = true; + + # Define a user account. Don't forget to set a password with 'passwd'. + users.users.jellyfin-mpv-shim = { + description = "Jellyfin MPV Shim User"; + isNormalUser = true; + extraGroups = [ + config.users.groups.audio.name + config.users.groups.video.name + ]; + packages = with pkgs; [ + jellyfin-mpv-shim + mpv + socat + ]; + }; + + systemd.services."cage-tty1".serviceConfig.Restart = "always"; + + system.stateVersion = "24.05"; + + virtualisation = { + # Enable Android emulator + # waydroid.enable = true; + + docker = { + enable = true; + autoPrune.enable = true; + }; + + oci-containers = { + backend = "docker"; + containers = { + reverse-proxy = { + hostname = "traefik"; + image = "traefik:v3.0"; + cmd = [ + "--api.insecure=true" + # Add Docker provider + "--providers.docker=true" + "--providers.docker.exposedByDefault=false" + # Add web entrypoint + "--entrypoints.web.address=:80/tcp" + "--entrypoints.web.http.redirections.entrypoint.to=websecure" + "--entrypoints.web.http.redirections.entrypoint.scheme=https" + # Add websecure entrypoint + "--entrypoints.websecure.address=:443/tcp" + "--entrypoints.websecure.http.tls=true" + "--entrypoints.websecure.http.tls.certResolver=letsencrypt" + "--entrypoints.websecure.http.tls.domains[0].main=depeuter.dev" + "--entrypoints.websecure.http.tls.domains[0].sans=*.depeuter.dev" + "--entrypoints.websecure.http.tls.domains[1].sans=*.niko.depeuter.dev" + # Certificates + "--certificatesresolvers.letsencrypt.acme.dnschallenge=true" + "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare" + "--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be" + "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + ]; + ports = [ + "80:80/tcp" + "443:443/tcp" + # "8080:8080/tcp" # The Web UI (enabled by --api.insecure=true) + ]; + environment = { + # TODO Hide this! + "CLOUDFLARE_DNS_API_TOKEN" = "6Vz64Op_a6Ls1ljGeBxFoOVfQ-yB-svRbf6OyPv2"; + }; + environmentFiles = [ + ]; + volumes = [ + "/var/run/docker.sock:/var/run/docker.sock:ro" # So that Traefik can listen to the Docker events + "letsencrypt:/letsencrypt" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.traefik.rule" = "Host(`traefik.niko.depeuter.dev`)"; + "traefik.http.services.traefik.loadbalancer.server.port" = "8080"; + }; + autoStart = true; + }; + }; + }; + }; +} diff --git a/hosts/Niko/hardware-configuration.nix b/hosts/Niko/hardware-configuration.nix new file mode 100644 index 0000000..34c1dc6 --- /dev/null +++ b/hosts/Niko/hardware-configuration.nix @@ -0,0 +1,53 @@ +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot = { + initrd = { + availableKernelModules = [ + "xhci_pci" + "ahci" + "usb_storage" + "sd_mod" + ]; + }; + kernelModules = [ ]; + extraModulePackages = [ ]; + }; + + fileSystems = { + "/" = { + device = "/dev/disk/by-uuid/20b7eff3-fca5-4b60-a5a9-13219f70ce23"; + fsType = "ext4"; + }; + + "/boot/efi" = { + device = "/dev/disk/by-uuid/0B6D-0DCD"; + fsType = "vfat"; + }; + + "/media/photos" = { + device = "//192.168.0.11/CANVAS"; + fsType = "cifs"; + options = let + # This line prevents hanging on network split + automount_opts = "x-systemd.automount,noauto,x-systemd.idle-timeout=60,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s,user,users"; + in ["${automount_opts},credentials=/etc/nixos/smb-secrets,uid=1002,gid=100"]; + }; + }; + + swapDevices = [ + { device = "/dev/disk/by-uuid/f3679da0-45b3-45c0-a1d0-af8d771a7dbf"; } + ]; + + networking = { + hostId = "7a139e16"; + useDHCP = lib.mkDefault true; + }; + + powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/hosts/Production/default.nix b/hosts/Production/default.nix new file mode 100644 index 0000000..cd929ff --- /dev/null +++ b/hosts/Production/default.nix @@ -0,0 +1,45 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps.changedetection.enable = true; + virtualisation.guest.enable = true; + }; + + networking = { + hostId = "aaaa2100"; + domain = "roxanne.depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.31"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/ProductionGPU/default.nix b/hosts/ProductionGPU/default.nix new file mode 100644 index 0000000..75e48e7 --- /dev/null +++ b/hosts/ProductionGPU/default.nix @@ -0,0 +1,98 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps.jellyfin.enable = true; + virtualisation.guest.enable = true; + }; + + networking = { + hostId = "aaaa2200"; + domain = "roxanne.depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "enp6s18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.enp6s18 = { + ipv4.addresses = [ + { + address = "192.168.0.94"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "unstable"; + + ### Nvidia GPU support ### + + services.xserver.videoDrivers = [ "nvidia" ]; + + # virtualisation.docker.package = pkgs.nvidia-docker; + + nixpkgs.config = { + allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ + "nvidia-x11" + "nvidia-settings" + "nvidia-persistenced" + ]; + + # enable vaapi on OS-level + # packageOverrides = pkgs: { + # vaapiIntel = pkgs.vaapiIntel.override { + # enableHybridCodec = true; + # }; + # }; + }; + + hardware = { + opengl = { + enable = true; + # driSupport = true; + # driSupport32Bit = true; + extraPackages = with pkgs; [ + # intel-media-driver + # intel-vaapi-driver # previously vaapiIntel + # vaapiVdpau + # intel-compute-runtime # OpenCL filter support (hardware tonemapping and subtitle burn-in) + # unstable.vpl-gpu-rt # QSV on 11th gen or newer + # intel-media-sdk # QSV up to 11th gen + ]; + }; + + nvidia = { + package = config.boot.kernelPackages.nvidiaPackages.stable; + # Whether to enable kernel modesetting when using the NVIDIA proprietary driver. + modesetting.enable = true; +# powerManagement = { +# enable = false; +# finegrained = false; +# }; + open = false; + nvidiaSettings = false; + + # Whether to enable nvidia-persistenced a update for NVIDIA GPU headless mode, i.e. It ensures all GPUs stay awake even during headless mode . + # nvidiaPersistenced = true; + }; + nvidia-container-toolkit.enable = true; + }; + }; +} diff --git a/hosts/Template/default.nix b/hosts/Template/default.nix new file mode 100644 index 0000000..21e54b7 --- /dev/null +++ b/hosts/Template/default.nix @@ -0,0 +1,36 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab.virtualisation.guest.enable = true; + + networking = { + # TODO hostName = "nixos"; + # TODO hostId = "aaaa9000"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + useDHCP = false; + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.90"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Testing/default.nix b/hosts/Testing/default.nix new file mode 100644 index 0000000..2da6563 --- /dev/null +++ b/hosts/Testing/default.nix @@ -0,0 +1,46 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps.freshrss.enable = true; + virtualisation.guest.enable = true; + }; + + networking = { + hostName = "Testing"; + hostId = "aaaa9200"; + domain = "roxanne.depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.92"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/hosts/Vaultwarden/default.nix b/hosts/Vaultwarden/default.nix new file mode 100644 index 0000000..9f98d84 --- /dev/null +++ b/hosts/Vaultwarden/default.nix @@ -0,0 +1,38 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + apps.vaultwarden.enable = true; + virtualisation.guest.enable = true; + }; + + networking = { + hostId = "aaaa1300"; + domain = "depeuter.dev"; + + enableIPv6 = true; + + useDHCP = false; + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.22"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} diff --git a/modules/apps/arr/default.nix b/modules/apps/arr/default.nix new file mode 100644 index 0000000..a88ed9c --- /dev/null +++ b/modules/apps/arr/default.nix @@ -0,0 +1,282 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.apps.arr; + + networkName = "arrStack"; + appNames = [ "bazarr" "lidarr" "prowlarr" "qbittorrent" "radarr" "sonarr" ]; + inUse = builtins.any (app: cfg.${app}.enable) appNames; + + PGID = toString config.users.groups.media.gid; + UMASK = "002"; +in { + options.homelab.apps.arr = { + enable = lib.mkEnableOption "Arr Stack using Docker"; + + bazarr.enable = lib.mkEnableOption "Bazarr using Docker"; + lidarr.enable = lib.mkEnableOption "Lidarr using Docker"; + prowlarr.enable = lib.mkEnableOption "Prowlarr using Docker"; + qbittorrent.enable = lib.mkEnableOption "qBittorrent using Docker"; + radarr.enable = lib.mkEnableOption "Radarr using Docker"; + sonarr.enable = lib.mkEnableOption "Sonarr using Docker"; + }; + + config = { + homelab = { + users = lib.mkIf inUse { + apps.enable = true; + media.enable = true; + }; + + # "Master switch": Enable all apps. + apps.arr = lib.mkIf cfg.enable { + bazarr.enable = true; + lidarr.enable = true; + prowlarr.enable = true; + qbittorrent.enable = true; + radarr.enable = true; + sonarr.enable = true; + }; + + virtualisation.containers.enable = lib.mkIf inUse true; + }; + + fileSystems = lib.mkIf inUse { + "/srv/video" = { + device = "192.168.0.11:/mnt/SMALL/MEDIA/VIDEO"; + fsType = "nfs"; + options = [ + "rw" + "auto" + "nfsvers=4.2" + "soft" + "rsize=1048576" "wsize=1048576" + "timeo=600" "retrans=2" + "_netdev" "nosuid" "tcp" + ]; + }; + + "/srv/qbittorrent" = { + device = "192.168.0.11:/mnt/SMALL/CONFIG/QBITTORRENT"; + fsType = "nfs"; + options = [ + "rw" + "auto" + "nfsvers=4.2" + "hard" + "rsize=1048576" "wsize=1048576" + "timeo=600" "retrans=2" + "_netdev" "nosuid" "tcp" + ]; + }; + }; + + # Make sure the Docker network exists. + systemd.services."docker-${networkName}-create-network" = lib.mkIf inUse { + description = "Create Docker network for ${networkName}"; + requiredBy = [ + "docker-bazarr.service" + "docker-lidarr.service" + "docker-prowlarr.service" + "docker-qbittorrent.service" + "docker-radarr.service" + "docker-sonarr.service" + ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then + ${pkgs.docker}/bin/docker network create ${networkName} + fi + ''; + }; + + # Create a user for each app. + users.users = { + bazarr = lib.mkIf cfg.bazarr.enable { + uid = lib.mkForce 3003; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + lidarr = lib.mkIf cfg.lidarr.enable { + uid = lib.mkForce 3002; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + prowlarr = lib.mkIf cfg.prowlarr.enable { + uid = lib.mkForce 3004; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + qbittorrent = lib.mkIf cfg.qbittorrent.enable { + uid = lib.mkForce 3005; + isSystemUser = true; + group = config.users.groups.media.name; + extraGroups = [ + config.users.groups.apps.name + ]; + home = "/var/empty"; + shell = null; + }; + radarr = lib.mkIf cfg.radarr.enable { + uid = lib.mkForce 3006; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + sonarr = lib.mkIf cfg.sonarr.enable { + uid = lib.mkForce 3007; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + }; + + virtualisation.oci-containers.containers = { + bazarr = lib.mkIf cfg.bazarr.enable { + hostname = "bazarr"; + image = "ghcr.io/hotio/bazarr:release-1.4.4"; + autoStart = true; + ports = [ + "6767:6767/tcp" + "6767:6767/udp" + ]; + extraOptions = [ + "--network=${networkName}" + + "--mount" ''type=volume,source=bazarr-backup,target=/backup,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/mnt/BIG/BACKUP/BAZARR,"volume-opt=o=addr=192.168.0.11,rw,nfsvers=4.2,async,nosuid"'' + ]; + environment = { + PUID = toString config.users.users.bazarr.uid; + inherit PGID UMASK; + TZ = config.time.timeZone; + WEBUI_PORTS = "6767/tcp,6767/udp"; + }; + volumes = [ + "bazarr-config:/config" + "/srv/video:/data" + ]; + }; + + lidarr = lib.mkIf cfg.lidarr.enable { + hostname = "lidarr"; + image = "ghcr.io/hotio/lidarr:release-2.5.3.4341"; + autoStart = true; + ports = [ + "8686:8686/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + + "--mount" ''type=volume,source=lidarr-backup,target=/backup,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/mnt/BIG/BACKUP/LIDARR,"volume-opt=o=addr=192.168.0.11,rw,nfsvers=4.2,async,nosuid"'' + ]; + environment = { + PUID = toString config.users.users.lidarr.uid; + inherit PGID UMASK; + TZ = config.time.timeZone; + }; + volumes = [ + "lidarr-config:/config" + # TODO "data:/data" + ]; + }; + + prowlarr = lib.mkIf cfg.prowlarr.enable { + hostname = "prowlarr"; + image = "ghcr.io/hotio/prowlarr:release-1.23.1.4708"; + autoStart = true; + ports = [ + "9696:9696/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + environment = { + PUID = toString config.users.users.prowlarr.uid; + inherit PGID UMASK; + TZ = config.time.timeZone; + }; + volumes = [ + # TODO "config:/config" + ]; + }; + + qbittorrent = lib.mkIf cfg.qbittorrent.enable { + hostname = "qbittorrent"; + image = "ghcr.io/hotio/qbittorrent:release-4.6.7"; + autoStart = true; + ports = [ + "10095:10095/udp" + "10095:10095/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + + "--mount" ''type=volume,source=torrents,target=/data,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/mnt/SMALL/MEDIA/TORRENT,"volume-opt=o=addr=192.168.0.11,rw,auto,nfsvers=4.2,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,_netdev,nosuid,tcp"'' + ]; + environment = { + PUID = toString config.users.users.qbittorrent.uid; + inherit PGID UMASK; + TZ = config.time.timeZone; + WEBUI_PORTS = "10095/tcp,10095/udp"; + }; + volumes = [ + "/srv/qbittorrent:/config/config" + "/srv/video:/media/video" + ]; + }; + + radarr = lib.mkIf cfg.radarr.enable { + hostname = "radarr"; + image = "ghcr.io/hotio/radarr:release-5.9.1.9070"; + autoStart = true; + ports = [ + "7878:7878/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + environment = { + PUID = toString config.users.users.radarr.uid; + inherit PGID UMASK; + TZ = config.time.timeZone; + }; + volumes = [ + # TODO "config:/config" + # TODO "data:/data" + ]; + }; + + sonarr = lib.mkIf cfg.sonarr.enable { + hostname = "sonarr"; + image = "ghcr.io/hotio/sonarr:release-4.0.9.2244"; + autoStart = true; + ports = [ + "8989:8989/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + environment = { + PUID = toString config.users.users.sonarr.uid; + inherit PGID UMASK; + TZ = config.time.timeZone; + }; + volumes = [ + # TODO "config:/config" + # TODO "data:/data" + ]; + }; + }; + }; +} diff --git a/modules/apps/calibre/default.nix b/modules/apps/calibre/default.nix new file mode 100644 index 0000000..6fddb81 --- /dev/null +++ b/modules/apps/calibre/default.nix @@ -0,0 +1,17 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.apps.calibre; +in { + options.homelab.apps.calibre.enable = lib.mkEnableOption "Calibre"; + + config = lib.mkIf cfg.enable { + users.users.calibre = { + uid = lib.mkForce 3010; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + }; +} diff --git a/modules/apps/changedetection/default.nix b/modules/apps/changedetection/default.nix new file mode 100644 index 0000000..ee88751 --- /dev/null +++ b/modules/apps/changedetection/default.nix @@ -0,0 +1,28 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.apps.changedetection; +in { + options.homelab.apps.changedetection.enable = lib.mkEnableOption "Changedetection.io"; + + config = lib.mkIf cfg.enable { + homelab.virtualisation.containers.enable = true; + + virtualisation.oci-containers.containers.changedetection = { + hostname = "changedetection"; + image = "ghcr.io/dgtlmoon/changedetection.io"; + autoStart = true; + ports = [ + "5000:5000/tcp" + ]; + extraOptions = [ + ]; + volumes = [ + "changedetection:/datastore" + ]; + environment = { + LOGGER_LEVEL = "WARNING"; + }; + }; + }; +} diff --git a/modules/apps/default.nix b/modules/apps/default.nix new file mode 100644 index 0000000..2d487e8 --- /dev/null +++ b/modules/apps/default.nix @@ -0,0 +1,14 @@ +{ + imports = [ + ./arr + ./calibre + ./changedetection + ./freshrss + ./gitea + ./jellyfin + ./plex + ./speedtest + ./technitium-dns + ./vaultwarden + ]; +} diff --git a/modules/apps/freshrss/default.nix b/modules/apps/freshrss/default.nix new file mode 100644 index 0000000..f2ea7ba --- /dev/null +++ b/modules/apps/freshrss/default.nix @@ -0,0 +1,76 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.apps.freshrss; + + networkName = "freshrss"; +in { + options.homelab.apps.freshrss = { + enable = lib.mkEnableOption "FreshRSS"; + port = lib.mkOption { + type = lib.types.int; + default = 9080; + description = "FreshRSS WebUI port"; + }; + }; + + config = let + inherit (config.homelab.apps.freshrss) port; + in + lib.mkIf cfg.enable { + homelab.virtualisation.containers.enable = true; + + fileSystems."/srv/freshrss" = { + device = "192.168.0.11:/mnt/SMALL/CONFIG/FRESHRSS"; + fsType = "nfs"; + options = [ + "rw" + "auto" + "nfsvers=4.2" + "sync" "hard" "timeo=600" + "retrans=2" + "_netdev" + "nosuid" + "tcp" + ]; + }; + + systemd.services."docker-${networkName}-create-network" = { + description = "Create Docker network for ${networkName}"; + requiredBy = [ + "docker-freshrss.service" + ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then + ${pkgs.docker}/bin/docker network create ${networkName} + fi + ''; + }; + + virtualisation.oci-containers.containers.freshrss = { + hostname = "freshrss"; + image = "freshrss/freshrss:1.24.0"; + autoStart = true; + user = "0:33"; + ports = [ + "${toString port}:${toString port}/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + environment = { + TZ = config.time.timeZone; + CRON_TIME = "3,18,33,48"; # Alternatively, configure cron inside container. + LISTEN = "0.0.0.0:${toString port}"; + }; + volumes = [ + "/srv/freshrss/www/freshrss/data:/var/www/FreshRSS/data" + "/srv/freshrss/www/freshrss/extensions:/var/www/FreshRSS/extensions" + ]; + }; + }; +} diff --git a/modules/apps/gitea/default.nix b/modules/apps/gitea/default.nix new file mode 100644 index 0000000..02f60cd --- /dev/null +++ b/modules/apps/gitea/default.nix @@ -0,0 +1,673 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.apps.gitea; + + networkName = "gitea"; + + UID = 3015; + GID = config.users.groups.apps.gid; + postgresPassword = "ChangeMe"; + repoDir = "/srv/git"; + webPort = 3000; + sshPort = 2222; + dbPort = 5432; + redisPort = 6379; + + title = "Hugo's Forge"; + slogan = "Forging ideas into reality."; + description = "Personal git server for projects that don't need collaboration."; +in { + options.homelab.apps.gitea.enable = lib.mkEnableOption "Gitea"; + + config = lib.mkIf cfg.enable { + homelab = { + users = { + apps.enable = true; + backup.enable = true; + }; + + virtualisation.containers.enable = true; + }; + + users.users.gitea = { + uid = lib.mkForce UID; + isSystemUser = true; + group = config.users.groups.apps.name; + home = "/var/empty"; + shell = null; + }; + + # Use filesystem mounts because rootless containers otherwise don't have access to the mount path (nested in docker directories). + # You could probably fix this by modifying the access rights on the path, but what would the point of that be? + fileSystems = { + # Mount options: + # - hard: retry requests indefinitely if the server becomes unresponsive. + # - nosuid: prevent set-user-id and set-group-id bits + "/srv/gitea-config" = { + device = "192.168.0.11:/mnt/SMALL/CONFIG/GITEA"; + fsType = "nfs"; + options = [ + "rw" + "nfsvers=4.2" + "async" "soft" "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" + "nosuid" + "tcp" + ]; + }; + + "/srv/gitea-git" = { + device = "192.168.0.11:/mnt/SMALL/DATA/GIT"; + fsType = "nfs"; + options = [ + "rw" + "nfsvers=4.2" + "async" "soft" "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" + "nosuid" + "tcp" + ]; + }; + }; + + # Make sure the Docker network exists. + systemd.services."docker-${networkName}-create-network" = { + description = "Create Docker network for ${networkName}"; + requiredBy = [ + "docker-gitea-db.service" + "docker-gitea.service" + ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then + ${pkgs.docker}/bin/docker network create ${networkName} + fi + ''; + }; + + virtualisation.oci-containers.containers = { + gitea-db = { + hostname = "gitea-db"; + image = "postgres:15.8-alpine"; + autoStart = true; + ports = [ + "5432:${toString dbPort}/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + environment = { + POSTGRES_PASSWORD = "ChangeMe"; + PGDATA = "/var/lib/postgresql/data/pgdata"; + }; + volumes = [ + "gitea-db:/var/lib/postgresql/data/pgdata" + ]; + }; + + gitea-redis = { + hostname = "gitea-redis"; + image = "redis:7.4.0-alpine3.20"; + autoStart = true; + ports = [ + "6379:${toString redisPort}/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + volumes = [ + "gitea-redis:/data" + ]; + }; + + gitea = { + hostname = "gitea"; + image = "codeberg.org/forgejo/forgejo:8.0.3-rootless"; + autoStart = true; + user = "${toString UID}:${toString GID}"; + ports = [ + "3000:${toString webPort}/tcp" + "2222:${toString sshPort}/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + dependsOn = [ + "gitea-db" + "gitea-redis" + ]; + volumes = [ + "/srv/gitea-config:/var/lib/gitea" + "/srv/gitea-git:/srv/git" + "/etc/timezone:/etc/timezone:ro" + "/etc/localtime:/etc/localtime:ro" + ]; + environmentFiles = [ + # NOTE Don't forget to create this file. + # TODO Put in place using age(nix)? + "/var/lib/gitea.env" + ]; + environment = { + # App name that shows in every page title. + FORGEJO__APP_NAME = title; + # Shows a slogan near the App name in every page title. + FORGEJO__APP_SLOGAN = slogan; + # Defines how the AppDisplayName should be presented. + #FORGEJO__APP_DISPLAY_NAME_FORMAT = ""; + # Will automaticaly detect the current user - but you can set it here. + FORGEJO__RUN_USER = "gitea"; + # Application run mode, affects performance and debugging: "dev" or "prod", default is + # "prod". Mode "dev" makes Gitea easier to develop and debug, values other than "dev" are + # treated as "prod" which is for production use. + FORGEJO__RUN_MODE = "prod"; + # The working directory. + #WORK_PATH = ""; + + # Disable SSH feature when not available. + FORGEJO__server__DISABLE_SSH = "false"; + # Whether to use the builltin SSH server or not. + FORGEJO__server__START_SSH_SERVER = "true"; + # Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER. + #FORGEJO__server__BUILTIN_SSH_SERVER_USER = "git"; + # Domain to be exposed in clone URL. + #FORGEJO__server__SSH_DOMAIN = ""; + # SSH username displayed in clone URLs. + #FORGEJO__server__SSH_USER = "git"; + # The network interface the builtin SSH server should listen on. + #FORGEJO__server__SSH_LISTEN_HOST = "ens18"; + # Port number to be exposed in clone URL. + FORGEJO__server__SSH_PORT = "22"; + # Port number the builtin SSH server should listen on. + FORGEJO__server__SSH_LISTEN_PORT = toString sshPort; + # Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'. + FORGEJO__server__SSH_ROOT_PATH = "/var/lib/gitea/ssh"; + # Gitea will create a authorized_keys file by default when it is not using the internal ssh server + # If you intend to use the AuthorizedKeysCommand functionality then you should turn this off. + #FORGEJO__server__SSH_CREATE_AUTHORIZED_KEYS_FILE = "true"; + # Gitea will create a authorized_principals file by default when it is not using the internal ssh server + # If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off. + #FORGEJO__server__SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = "true"; + # For the built-in SSH server, choose the ciphers to support for SSH connections, + # for system SSH this setting has no effect + #FORGEJO__server__SSH_SERVER_CIPHERS = "chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com"; + # For the built-in SSH server, choose the key exchange algorithms to support for SSH connections, + # for system SSH this setting has no effect + #FORGEJO__server__SSH_SERVER_KEY_EXCHANGES = "curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1"; + # For the built-in SSH server, choose the MACs to support for SSH connections, + # for system SSH this setting has no effect + #FORGEJO__server__SSH_SERVER_MACS = "hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1"; + # For the built-in SSH server, choose the keypair to offer as the host key + # The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub + # relative paths are made absolute relative to the %(APP_DATA_PATH)s + FORGEJO__server__SSH_SERVER_HOST_KEYS = "/var/lib/gitea/ssh/forgejo.ed25519"; + # Directory to create temporary files in when testing public keys using ssh-keygen, + # default is the system temporary directory. + #FORGEJO__server__SSH_KEY_TEST_PATH = ""; + # Use `ssh-keygen` to parse public SSH keys. The value is passed to the shell. By default, Gitea does the parsing itself. + #FORGEJO__server__SSH_KEYGEN_PATH = ""; + # Enable SSH Authorized Key Backup when rewriting all keys, default is false + FORGEJO__server__SSH_AUTHORIZED_KEYS_BACKUP = "false"; + # ... + # Enable exposure of SSH clone URL to anonymous visitors, default is false. + FORGEJO__server__EXPOSE_ANONYMOUS = "false"; + # ... + # Enables git-lfs support. true or false, default is false. + FORGEJO__server__LFS_START_SERVER = "false"; + # ... + + # Database to use. Either "mysql", "postgres" or "sqlite3". + FORGEJO__database__DB_TYPE = "postgres"; + FORGEJO__database__HOST = "gitea-db:${toString dbPort}"; + FORGEJO__database__NAME = "gitea"; + FORGEJO__database__USER = "gitea"; + FORGEJO__database__PASSWD = postgresPassword; + #FORGEJO__database__SCHEMA = ""; + #FORGEJO__database__SSL_MODE = "disable"; + + # Whether the installer is disabled (set to true to disable the installer). + #FORGEJO__security__INSTALL_LOCK = "false"; + # Global security key that will be used. + # This key is VERY IMPORTANT. If you lose it, the data encrypted by it can't be decrypted anymore. + #FORGEJO__security__SECRET_KEY = ""; + # Alternatively, specify the location of the secret key. + #FORGEJO__security__SECRET_KEY_URI = "file:/etc/gitea/secret_key"; + # ... + + # IF the camo is enabled. + #FORGEJO__camo__ENABLED = "false"; + # .... + + # Enables OAuth2 provider + FORGEJO__oauth2__ENABLED = "false"; + # ... + + # Root path for the log files - defaults to %(GITEA_WORK_DIR)/log + #FORGEJO__log__ROOT_PATH = ""; + # Either "console", "file" or "conn", default is "console" + FORGEJO__log__MODE = "file"; + # Either "Trace", "Debug", "Info", "Warn", "Error" or "None", default is "Info". + FORGEJO__log__LEVEL = "Warn"; + # ... + # Collect SSH logs (Creates logs from ssh git requests) + FORGEJO__log__ENABLE_SSH_LOG = "true"; + # ... + + # The path of git executable. If empty, Gitea searches through the PATH environment. + #FORGEJO__git__PATH = ""; + # ... + FORGEJO__git_0x2E_timeout__MIGRATE = "600"; + FORGEJO__git_0x2E_timeout__MIRROR = "600"; + + # Time limit to confirm account/email registration. + #FORGEJO__service__ACTIVE_CODE_LIVE_MINUTES = "180"; + # Time limit to perform the reset of a forgotten password. + #FORGEJO__service__RESET_PASSWD_CODE_LIVE_MINUTES = "180"; + # Whether a new user needs to confirm their email when registering. + FORGEJO__service__REGISTER_EMAIL_CONFIRM = "true"; + # Whether a new user needs to be confirmed manually after registration. + FORGEJO__service__REGISTER_MANUAL_CONFIRM = "true"; + # List of domain names that are allowed to be used to register on a Gitea instance, wildcard is supported. + #FORGEJO__service__EMAIL_DOMAIN_ALLOWLIST = ""; + # Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance, wildcard is supported. + #FORGEJO__service__EMAIL_DOMAIN_BLOCKLIST = ""; + # Disallow registration, only allow admins to create accounts. + FORGEJO__service__DISABLE_REGISTRATION = "true"; + # Allow registration only using gitea itself, it works only when DISABLE_REGISTRATION is false. + FORGEJO__service__ALLOW_ONLY_INTERNAL_REGISTRATION = "true"; + # Allow registration only using third-party services, it works only when DISABLE_REGISTRATION is false. + FORGEJO__service__ALLOW_ONLY_EXTERNAL_REGISTRATION = "false"; + # User must sign in to view anything. + FORGEJO__service__REQUIRE_SIGNIN_VIEW = "false"; + # Mail notification + FORGEJO__service__ENABLE_NOTIFY_MAIL = "true"; + # This setting enables gitea to be signed in with HTTP BASIC Authentication using the user's password. + # If you set this to false you will not be able to access the tokens endpoints on the API with your password. + # Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token. + FORGEJO__service__ENABLE_BASIC_AUTHENTICATION = "false"; + # ... + # Enable captcha validation for registration. + FORGEJO__service__ENABLE_CAPTCHA = "true"; + # Enable this to require captcha validation for login. + FORGEJO__service__REQUIRE_CAPTCHA_FOR_LOGIN = "true"; + # Requires captcha for external registrations + #FORGEJO__service__REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA = "false"; + # Requires a password for external registrations. + #FORGEJO__service__REQUIRE_EXTERNAL_REGISTRATION_PASSWORD = "false"; + # Type of captcha you want to use. Options: image, recaptcha, hcaptcha, mcaptcha, cfturnstile. + FORGEJO__service__CAPTCHA_TYPE = "image"; + # ... + # Default value for KeepEmailPrivate + # Each new user will get the value of this setting copied into their profile + FORGEJO__service__DEFAULT_KEEP_EMAIL_PRIVATE = "true"; + # Default value for AllowCreateOrganization + # Every new user will have rights set to create organizations depending on this setting. + FORGEJO__service__DEFAULT_ALLOW_CREATE_ORGANIZATION = "true"; + # Default value for IsRestricted + # Every new user will have restricted permissions depending on this setting. + FORGEJO__service__DEFAULT_USER_IS_RESTRICTED = "false"; + # Users will be able to use dots when choosing their username. Disabling this is + # helpful if your usersare having issues with e.g. RSS feeds or advanced third-party + # extensions that use strange regex patterns. + FORGEJO__service__ALLOW_DOTS_IN_USERNAMES = "false"; + # Either "public", "limited" or "private", default is "public". + # Limited is for users visible only to signed users. + # Private is for users visible only to members of their organizations + # Public is for users visible for everyone + FORGEJO__service__DEFAULT_USER_VISIBILITY = "limited"; + # Set which visibility modes a user can have + FORGEJO__service__ALLOWED_USER_VISIBILITY_MODES = "public,limited,private"; + # Either "public", "limited" or "private", default is "public". + # Limited is for organizations visible only to signed users + # Private is for organizations visible only to members of the organization + # Public is for organizations visible to everyone + FORGEJO__service__DEFAULT_ORG_VISIBILITY = "limited"; + # Default value for DefaultOrgMemberVisible + # True will make the membership of the users visible when added to the organisation + FORGEJO__service__DEFAULT_ORG_MEMBER_VISIBLE = "false"; + # Default value for EnableDependencies + # Repositories will use dependencies by default depending on this setting + #FORGEJO__service__DEFAULT_ENABLE_DEPENDENCIES = "true"; + # Dependencies can be added from any repository where the user is granted access or only from the current repository depending on this setting. + #FORGEJO__service__ALLOW_CROSS_REPOSITORY_DEPENDENCIES = "true"; + # Default map service. No external API support has been included. A service has to allow + # searching using URL parameters, the location will be appended to the URL as escaped query parameter. + # Some example values are: + # - OpenStreetMap: https://www.openstreetmap.org/search?query= + # - Google Maps: https://www.google.com/maps/place/ + # - MapQuest: https://www.mapquest.com/search/ + # - Bing Maps: https://www.bing.com/maps?where1= + #FORGEJO__service__USER_LOCATION_MAP_URL = "https://www.openstreetmap.org/search?query="; + # Enable heatmap on users profiles. + FORGEJO__service__ENABLE_USER_HEATMAP = "true"; + # Enable Timetracking + FORGEJO__service__ENABLE_TIMETRACKING = "true"; + # Default value for EnableTimetracking + # Repositories will use timetracking by default depending on this setting + FORGEJO__service__DEFAULT_ENABLE_TIMETRACKING = "false"; + # Default value for AllowOnlyContributorsToTrackTime + # Only users with write permissions can track time if this is true + #FORGEJO__service__DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME = "true"; + # Value for the domain part of the user's email address in the git log if user + # has set KeepEmailPrivate to true. The user's email will be replaced with a + # concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. Default + # value is "noreply." + DOMAIN, where DOMAIN resolves to the value from server.DOMAIN + # Note: do not use the notation below + FORGEJO__service__NO_REPLY_ADDRESS = "noreply.depeuter.dev"; + # Show Registration button. + FOGEJO__service__SHOW_REGISTRATION_BUTTON = "false"; + # Show milestones dashboard page - a view of all the user's milestones. + #FORGEJO__service__SHOW_MILESTONES_DASHBOARD_PAGE = "true"; + # Default value for AutoWatchNewRepos + # When adding a repo to a team or creating a new repo all team members will watch the + # repo automatically if enabled + #FORGEJO__service__AUTO_WATCH_NEW_REPOS = "true"; + # Default value for AutoWatchOnChanges + # Make the user watch a repository When they commit for the first time + #FORGEJO__service__AUTO_WATCH_ON_CHANGES = "false"; + # Minimum amount of time a user must exist before comments are kept when the user is deleted. + #FORGEJO__service__USER_DELETE_WITH_COMMENTS_MAX_TIME = "0"; + # Valid site url schemes for user profiles + #FORGEJO__service__VALID_SITE_URL_SCHEMES = "http,https"; + + # Enable repository badges (via shields.io or a similar generator) + #FORGEJO__badges__ENABLED = "true"; + # ... + + # Root path for storing all repository data. By default, it is set to %(APP_DATA_PATH)s/gitea-repositories. + # A relative path is interpreted as _`AppWorkPath`_/%(ROOT)s + FORGEJO__repository__ROOT = repoDir; + # ... + # Force every new repository to be private. + FORGEJO__repository__FORCE_PRIVATE = "false"; + # Default private when creating a new repository with push-to-create. + FORGEJO__repository__DEFAULT_PUSH_TO_CREATE = "true"; + # ... + # Allow users to push local repositories to Forgejo and have them automatically created for a user. + FORGEJO__repository__ENABLE_PUSH_CREATE_USER = "true"; + # Allow users to push local repositories to Forgejo and have them automatically created for an org. + FORGEJO__repository__ENABLE_PUSH_CREATE_ORG = "false"; + # Comma separated list of globally disabled repo units. + FORGEJO__repository__DISABLED_REPO_UNITS = ""; + # Comma separated list of default new repo units. + FORGEJO__repository__DEFAULT_REPO_UNITS = "repo.code,repo.issues,repo.pulls,repo.releases,repo.actions"; + # Comma separated list of default forked repo units. + FORGEJO__repository__DEFAULT_FORK_REPO_UNITS = "repo.code,repo.pulls"; + # Prefix archive files by placing them in a directory named after the repository. + FORGEJO__repository__PREFIX_ARCHIVE_FILES = "true"; + # Disable migrating feature. + FORGEJO__repository__DISABLE_MIGRATIONS = "false"; + # Disable stars feature. + FORGEJO__repository__DISABLE_STARS = "true"; + # Disable repository forking. + #FORGEJO__repository__DISABLE_FORKS = "false"; + # The default branch name of new repositories + FORGEJO__repository__DEFAULT_BRANCH = "main"; + # ... + + # List of prefixes used in Pull Request title to mark them as Work In Progress (matched in a case-insensitive manner) + FORGEJO__repository_0x2E_pull_0X2D_request__WORK_IN_PROGRESS_PREFIXES = "WIP:,[WIP],WIP"; + # ... + # In the default merge message for squash commits walk all commits to include all authors in the Co-authored-by otherwise just use those in the limited list. + FORGEJO__repository_0x2E_pull_0X2D_request__DEFAULT_MERGE_MESSAGE_ALL_AUTHORS = "true"; + # ... + + # Enable cors headers (disabled by default) + FORGEJO__cors__ENABLED = "true"; + # list of requesting origins that are allowed, eg: "https://*.example.com". + FORGEJO__cors__ALLOW_DOMAINS = "https://git.depeuter.dev,http://192.168.0.24:${toString webPort}"; + + # Set the default theme for the Gitea install. + FORGEJO__ui__DEFAULT_THEME = "gitea-auto"; + # All available themes. Allow users to select personalized themes regardless of `DEFAULT_THEME`. + FORGEJO__ui__THEMES = "gitea-auto,gitea-light,gitea-dark,forgejo-auto,forgejo-light,forgejo-dark,forgejo-auto-deuteranopia-protanopia,forgejo-light-deuteranopia-protanopia,forgejo-dark-deuteranopia-protanopia,forgejo-auto-tritanopia,forgejo-light-tritanopia-forgejo-dark-tritanopia,github-auto,github,github-dark,edge-auto,edge-light,edge-dark,everforest-auto,everforest-light,everforest-dark,gruvbox-auto,gruvbox-light,gruvbox-dark,gruvbox-material-auto,grubox-material-dark,gruvbox-material-light,sonokai-andromeda,sonokai-atlantis,sonokai-espresso,sonokai-maia,sonokai-shusia,sonokai,catppuccin-frappe-green,catppuccin-frappe-teal,catppuccin-frappe-sky,catppuccin-frappe-sapphire,catppuccin-frappe-blue,catppuccin-frappe-lavender,catppuccin-macchiato-green,catppuccin-macchiato-teal,catppuccin-macchiato-sky,catppuccin-macchiato-sapphire,catppuccin-macchiato-blue,catppuccin-macchiato-lavender,catppuccin-mocha-green,catppuccin-mocha-teal,catppuccin-mocha-sky,catppuccin-mocha-sapphire,catppuccin-mocha-blue,catppuccin-mocha-lavender,nord,pitchblack,matrix,dark-arc"; + + FORGEJO__ui_0x2E_meta__AUTHOR = "${title} - ${slogan}"; + FORGEJO__ui_0x2E_meta__DESCRIPTION = description; + FORGEJO__ui_0x2E_meta__KEYWORDS = "git,self-hosted,projects,code"; + + # Whether to render SVG files as images. If SVG rendering is disabled, SVG files are displayed as text and cannot be embedded in markdown files as images. + FORGEJO__ui_0x2E_svg__ENABLE_RENDER = "true"; + + # ... + # Enables math inline and block detection + FORGEJO__markdown__ENABLE_MATH = "true"; + + # Define allowed algorithms and their minimum key length (use -1 to disable a type) + #FORGEJO__ssh__0x2E__minimum_key_sizes__ED25519 = "256"; + #FORGEJO__ssh__0x2E__minimum_key_sizes__ECDSA = "256"; + FORGEJO__ssh_0x2E_minimum_key_sizes__RSA = "-1"; + FORGEJO__ssh_0x2E_minimum_key_sizes__DSA = "-1"; + + # ... indexer + + # ... queue + + # Disallow regular (non-admin) users from creating organizations. + #FORGEJO__admin__DISABLE_REGULAR_ORG_CREATION = "false"; + # Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled + FORGEJO__admin__DEFAULT_EMAIL_NOTIFICATIONS = "enabled"; + # Send an email to all admins when a new user signs up to inform the admins about this act. Options: true, false + FORGEJO__admin__SEND_NOTIFICATION_EMAIL_ON_NEW_USER = "true"; + # Disabled features for users, could be "deletion", "manage_ssh_keys","manage_gpg_keys" more features can be disabled in future + # - deletion: a user cannot delete their own account + # - manage_ssh_keys: a user cannot configure ssh keys + # - manage_gpg_keys: a user cannot configure gpg keys + #FORGEJO__admin__USER_DISABLED_FEATURES = ""; + # Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`. This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior. + # - deletion: a user cannot delete their own account + # - manage_ssh_keys: a user cannot configure ssh keys + # - manage_gpg_keys: a user cannot configure gpg keys + #FORGEJO__admin__EXTERNAL_USER_DISABLE_FEATURES = ""; + + # Whether to allow signin in via OpenID + FORGEJO__openid__ENABLE_OPENID_SIGNIN = "false"; + # Whether to allow registering via OpenID + # Do not include to rely on rhw DISABLE_REGISTRATION setting + FORGEJO__openid__ENABLE_OPENID_SIGNUP = "false"; + # ... + + # ... oath2_client + + # ... webhook + + FORGEJO__mailer__ENABLED = "true"; + # Buffer length of channel, keep it as it is if you don't know what it is. + #FORGEJO__mailer__SEND_BUFFER_LEN = "100"; + # Prefix displayed before subject in mail. + #FORGEJO__mailer__SUBJECT_PREFIX = ""; + # Mail server protocol. One of "smtp", "smtps", "smtp+starttls", "smtp+unix", "sendmail", "dummy" + FORGEJO__mailer__PROTOCOL = "smtps"; + # Mail server address + FORGEJO__mailer__SMTP_ADDR = "smtp.gmail.com"; + # Mail server port. If no protocol is specified, it will be inferred by this setting. + FORGEJO__mailer__SMTP_PORT = "465"; + # Enable HELO operation. Defaults to true. + #FORGEJO__mailer__ENABLE_HELO = "true"; + # Custom hostname fo the HELO operation. If no value is provided, one is retrieved from + # the system. + #FORGEJO__mailer__HELO_HOSTNAME = ""; + # If set to 'true', completely ignores server certificate validation errors. UNSAFE! + #FORGEJO__mailer__FORCE_TRUST_SERVER_CERT = "false"; + # Use client certificate in connection. + #FORGEJO__mailer__USE_CLIENT_CERT = "false"; + #FORGEJO__mailer__CLIENT_CERT_FILE = "custom/mailer/cert.pem"; + #FORGEJO__mailer__CLIENT_KEY_FILE = "custom/mailer/key.pem"; + # Mail from address, RFC 5322. This can be just an email address, or the + # `"Name" ` format. + FORGEJO__mailer__FROM = ''"${title}" ''; + # Sometimes it is helpful to use a different address on the envelope. Set this to use + # ENVELOPE_FROM as the from on the envelope. Set to `<>` to send an empty address. + #FORGEJO__mailer__ENVELOPE_FROM = ""; + # If gitea sends mails on behave of users, it will just use the name also displayed in the + # WebUI. If you want e.g. `Mister X (by CodeIt) `, set it to + # `{{ .DisplayName }} (by {{ .AppName }})`. + # Available Variables: `.DisplayName`, `.AppName` and `.Domain`. + #FORGEJO__mailer__FROM_DISPLAY_NAME_FORMAT = "{{ .DisplayName }}"; + # Mailer user name and password, if required by provider. + #FORGEJO__mailer__USER = ""; + # Use PASSWD = `your password` for quoting if you use special characters in the password. + #FORGEJO__mailer__PASSWD = ""; + # Send mails only in plain text, without HTML alternative + #FORGEJO__mailer__SEND_AS_PLAIN_TEXT = "false"; + # Specify an alternative sendmail binary + #FORGEJO__mailer__SENDMAIL_PATH = "sendmail"; + # Specify any extra sendmail arguments + # WARNING: if your sendmail program interprets options you should set this to "--" or terminate these args with "--" + #FORGEJO__mailer__SENDMAIL_ARGS = ""; + # Timeout for Sendmail + #FORGEJO__mailer__SENDMAIL_TIMEOUT = "5m"; + # convert \r\n to \n for Sendmail + #FORGEJO__mailer__SENDMAIL_CONVERT_CRLF = "true"; + + # ... email.incoming + + # Either "memory", "redis", "memcache", or "twoqueue". default is "memory" + FORGEJO__cache__ADAPTER = "redis"; + # For "memory" only, GC interval in seconds, default is 60. + #FORGEJO__cache__INTERVAL = "60"; + # For "redis" and "memcache", connection host address + # redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` (or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for a Redis cluster) + # memcache: `127.0.0.1:11211` + # twoqueue: `{"size":50000,"recent_ratio":0.25,"ghost_ratio":0.5}` or `50000` + FORGEJO__cache__HOST = "redis://gitea-redis:${toString redisPort}/0?pool_size=100&idle_timeout=180s"; + # Time to keep items in cache if not used, default is 16 hours. + # Setting it to -1 disables caching + FORGEJO__cache__ITEM_TTL = "16h"; + # Time to keep items in cache if not used, default is 8760 hours. + # Setting it to -1 disables caching + FORGEJO__cache_0X2E_last_0X2D_commit__ITEM_TTL = "8760h"; + # Only enable the cache when repository's commits count great than + FORGEJO__cache_0X2E_last_0X2D_commit__COMMITS_COUNT = "100"; + + # Either "memory", "file", "redis", "db", "mysql", "couchbase", "memcache" or "postgres" + # Default is "memory". "db" will reuse the configuration in [database] + #FORGEJO__session__PROVIDER = "memory"; + # Provider config options + # memory: doesn't have any config yet + # file: session file path, e.g. `data/sessions` + # redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` (or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for a Redis cluster) + # mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table` + #FORGEJO__session__PROVIDER_CONFIG = "data/sessions"; # Relative paths will be made absolute against _`AppWorkPath`_. + # Session cookie name + FORGEJO__session__COOKIE_NAME = "i_like_tibo"; + # If you use session in https only: true or false. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL. + FORGEJO__session__COOKIE_SECURE = "true"; + # Session GC time interval in seconds, default is 86400 (1 day) + #FORGEJO__session__GC_0X2E_INTERVAL_0X2E_TIME = "86400"; + # Session life time in seconds, default is 86400 (1 day) + #FORGEJO__session__SESSION_0X2E_LIFE_0X2E_TIME = "86400"; + # Cookie domain name. Default is empty + FORGEJO__session__DOMAIN = "git.depeuter.dev"; + # SameSite settings. Either "none", "lax", or "strict" + FORGEJO__session__SAME_SITE = "strict"; + + # How Gitea deals with missing repository avatars + # none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used + #FORGEJO__picture__REPOSITORY_AVATAR_FALLBACK = "none"; + #FORGEJO__picture__REPOSITORY_AVATAR_FALLBACK_IMAGE = "/img/repo_default.png"; + # Max Width and Height of uploaded avatars. + # This is to limit the amount of RAM used when resizing the image. + FORGEJO__picture__AVATAR_MAX_WIDTH = "10000"; + FORGEJO__picture__AVATAR_MAX_HEIGTH = "10000"; + # The multiplication factor for rendered avatar images. + # Larger values result in finer rendering on HiDPI devices. + #FORGEJO__picture__AVATAR_RENDERED_SIZE_FACTOR = "2"; + # Maximum allowed file size for uploaded avatars. + # This is to limit the amount of RAM used when resizing the image. + #FORGEJO__picture__AVATAR_MAX_FILE_SIZE = "1048576"; + # If the uploaded file is not larger than this byte size, the image will be used as is, without resizing/converting. + #FORGEJO__picture__AVATAR_MAX_ORIGIN_SIZE = "262144"; + # Chinese users can choose "duoshuo" + # or a custom avatar source, like: http://cn.gravatar.com/avatar/ + #FORGEJO__picture__GRAVATAR_SOURCE = "gravatar"; + # This value will always be true in offline mode. + #FORGEJO__picture__DISABLE_GRAVATAR = "false"; + # Federated avatar lookup uses DNS to discover avatar associated. + # with emails, see https://www.libravatar.org + # This value will always be false in offline mode or when Gravatar is disabled. + #FORGEJO__picture__ENABLE_FEDERATED_AVATAR = "false"; + + # ... attachment + + # ... time + + # ... cron + + # Enables the mirror functionality. Set to **false** to disable all mirrors. Pre-existing mirrors remain valid but won't be updated; may be converted to regular repo. + FORGEJO__mirror__ENABLED = "true"; + # Disable the creation of **new** pull mirrors. Pre-existing mirrors remain valid. Will be ignored if `mirror.ENABLED` is `false`. + FORGEJO__mirror__DISABLE_NEW_PULL = "false"; + # Disable the creation of **new** push mirrors. Pre-existing mirrors remain valid. Will be ignored if `mirror.ENABLED` is `false`. + FORGEJO__mirror__DISABLE_NEW_PUSH = "false"; + # Default interval as a duration between each check + FORGEJO__mirror__DEFAULT_INTERVAL = "1h"; + # Min interval as a duration must be > 1m + FORGEJO__mirror__MIN_INTERVAL = "5m"; + + # ... api + + # ... i18n + + # .. highlight.mapping + + # Show version information about Gitea and Go in the footer + FORGEJO__other__SHOW_FOOTER_VERSION = "false"; + # Show template execution time in the footer + FORGEJO__other__SHOW_FOOTER_TEMPLATE_LOAD_TIME = "false"; + # Show the "powered by" text in the footer + FORGEJO__other__SHOW_FOOTER_POWERED_BY = "false"; + # Generate sitemap. Defaults to `true`. + FORGEJO__other__ENABLE_SITEMAP = "true"; + # Enable/Disable RSS/Atom feed + FORGEJO__other__ENABLE_FEED = "true"; + + # ... markup + + # ... metrics + + # ... migrations + + # ... f3 + + # Enable/Disable federation capabilities + FORGEJO__federation_ENABLED = "false"; + # ... + + # Enable/Disable package registry capabilities + FORGEJO__packages__ENABLED = "true"; + + # ... storage + + # Repo-archive storage will override storage. + #FORGEJO__repo_0X2D_archive__STORAGE_TYPE = "local"; + # Where your lfs files reside, default is data/lfs + FORGEJO__repo_0X2D_archive__PATH = ""; + # Override the minio base path if storage type is minio. + #FORGEJO__repo_0X2D_archive__MINIO_BASE_PATH = ""; + + # lfs storage will override storage. + #FORGEJO__lfs__STORAGE_TYPE = "local"; + # Where your lfs files reside, default is data/lfs + FORGEJO__lfs__PATH = ""; + # Override the minio base path if storage is set to minio. + #FORGEJO__lfs__MINIO_BASE_PATH = "lfs/"; + + # Enable the proxy, all requests to external via HTTP will be affected + FORGEJO__proxy__PROXY_ENABLED = "false"; + # Proxy server URL, support http://, https//, socks://, blank will follow environment http_proxy/https_proxy/no_proxy + #FORGEJO__proxy__PROXY_URL = ""; + # Comma separated list of host names requiring proxy. Glob patterns (*) are accepted; use ** to match all hosts. + #FORGEJO__proxy__PROXY_HOSTS = ""; + + # Enable/Disable actions capabilities + FORGEJO__actions__ENABLED = "true"; + # Default address to get action plugins, e.g. the default value means downloading from "https://code.forgejo.org/actions/checkout" for "uses: actions/checkout@v3" + #FORGEJO__actions__DEFAULT_ACTIONS_URL = "https://code.forgejo.org"; + # ... + }; + }; + }; + }; +} diff --git a/modules/apps/jellyfin/default.nix b/modules/apps/jellyfin/default.nix new file mode 100644 index 0000000..5b4081a --- /dev/null +++ b/modules/apps/jellyfin/default.nix @@ -0,0 +1,175 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.apps.jellyfin; + + networkName = "jellyfin"; + + UID = 3008; + GID = config.users.groups.media.gid; +in { + options.homelab.apps.jellyfin.enable = lib.mkEnableOption "Jellyfin using Docker"; + + config = lib.mkIf cfg.enable { + homelab = { + users = { + apps.enable = true; + media.enable = true; + }; + virtualisation.containers.enable = true; + }; + + fileSystems = { + "/srv/audio" = { + device = "192.168.0.11:/mnt/SMALL/MEDIA/AUDIO"; + fsType = "nfs"; + options = [ + "ro" + "nfsvers=4.2" + "async" "soft" + "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" + "nosuid" "tcp" + ]; + }; + + "/srv/video" = { + device = "192.168.0.11:/mnt/SMALL/MEDIA/VIDEO"; + fsType = "nfs"; + options = [ + "ro" + "nfsvers=4.2" + "async" "soft" + "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" + "nosuid" "tcp" + ]; + }; + + "/srv/homevideo" = { + device = "192.168.0.11:/mnt/BIG/MEDIA/HOMEVIDEO/ARCHIVE"; + fsType = "nfs"; + options = [ + "ro" + "nfsvers=4.2" + "async" "soft" + "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" + "nosuid" "tcp" + ]; + }; + + "/srv/photo" = { + device = "192.168.0.11:/mnt/BIG/MEDIA/PHOTO/ARCHIVE"; + fsType = "nfs"; + options = [ + "ro" + "nfsvers=4.2" + "async" "soft" + "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" + "nosuid" "tcp" + ]; + }; + }; + + users.users.jellyfin = { + uid = lib.mkForce UID; + isSystemUser = true; + group = config.users.groups.apps.name; + extraGroups = [ + config.users.groups.media.name + ]; + home = "/var/empty"; + shell = null; + }; + + # Make sure the Docker network exists. + systemd.services."docker-${networkName}-create-network" = { + description = "Create Docker network for ${networkName}"; + requiredBy = [ + "docker-jellyfin.service" + "docker-feishin.service" + ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then + ${pkgs.docker}/bin/docker network create ${networkName} + fi + ''; + }; + + virtualisation.oci-containers.containers = { + jellyfin = { + hostname = "jellyfin"; + image = "jellyfin/jellyfin:10.10.0"; + user = "${toString UID}:${toString GID}"; + autoStart = true; + ports = [ + "8096:8096/tcp" + # "8920:8920/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + "--device=nvidia.com/gpu=all" # Equivalent to --gpus=all + ]; + volumes = [ + "jellyfin-config:/config" + "cache:/cache" + + "/srv/audio:/media/audio" + "/srv/video:/media/video" + "/srv/homevideo:/media/homevideo" + "/srv/photo:/media/photo" + ]; + environment = { + # TODO + }; + }; + + jellyfin-vue = { + hostname = "jellyfin-vue"; + image = "ghcr.io/jellyfin/jellyfin-vue:unstable"; + autoStart = true; + ports = [ + "8080:80/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + labels = { + }; + }; + + feishin = let + feishinPort = "9180"; + in { + hostname = "feishin"; + image = "ghcr.io/jeffvli/feishin:0.7.1"; + autoStart = true; + ports = [ + "${feishinPort}:9180/tcp" # Web player (HTTP) + ]; + extraOptions = [ + "--network=${networkName}" + ]; + environment = { + # pre defined server name + SERVER_NAME = "Hugo"; + # When true AND name/type/url are set, only username/password can be toggled + SERVER_LOCK = "true"; + # Either "jellyfin" or "navidrome" + SERVER_TYPE = "jellyfin"; + # http://address:port + SERVER_URL= "https://jelly.depeuter.dev"; + TZ = config.time.timeZone; + }; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.feishin.rule" = "Host(`play.jelly.depeuter.dev`)"; + "traefik.http.services.feishin.loadbalancer.server.port" = feishinPort; + "traefik.tls.options.default.minVersion" = "VersionTLS13"; + }; + }; + }; + }; +} diff --git a/modules/apps/plex/default.nix b/modules/apps/plex/default.nix new file mode 100644 index 0000000..251f9dd --- /dev/null +++ b/modules/apps/plex/default.nix @@ -0,0 +1,50 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.apps.plex; +in { + options.homelab.apps.plex.enable = lib.mkEnableOption "Plex"; + + config = lib.mkIf cfg.enable { + users.users.plex = { + uid = lib.mkForce 3009; + isSystemUser = true; + group = config.users.groups.media; + home = "/var/empty"; + shell = null; + }; + + virtualisation.oci-containers.containers = { + plex = { + hostname = "plex"; + image = "plexinc/pms-docker:1.41.0.8992-8463ad060"; + autoStart = true; + ports = [ + "32400:32400/tcp" # Plex Media Server + "1900:1900/udp" # Plex DLNA Server + "32469:32469/tcp" # Plex DLNA Server + "32410:32410/udp" # GDM network discovery + "32412:32412/udp" # GDM network discovery + "32413:32413/udp" # GDM network discovery + "32414:32414/udp" # GDM network discovery + # "8324:8324/tcp" # Controlling Plex for Roku via Plex Companion + ]; + environment = { + ADVERTISE_AP = "..."; # TODO Configure ip + ALLOWED_NETWORKS = "192.168.0.0/24,172.16.0.0/16"; + CHANGE_CONFIG_DIR_OWNERSHIP = "false"; + HOSTNAME = "PlexServer"; + PLEX_CLAIM = "..."; # TODO Add token + PLEX_UID = config.users.users.plex.uid; + PLEX_GID = config.users.groups.media.gid; + TZ = config.time.timeZone; + }; + volumes = [ + # TODO "config:/var/lib/plexmediaserver" + # TODO "transcode-temp:/transcode" + # TODO "media:/data" + ]; + }; + }; + }; +} diff --git a/modules/apps/speedtest/default.nix b/modules/apps/speedtest/default.nix new file mode 100644 index 0000000..4c42c64 --- /dev/null +++ b/modules/apps/speedtest/default.nix @@ -0,0 +1,27 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.apps.speedtest; +in { + options.homelab.apps.speedtest.enable = lib.mkEnableOption "Speedtest"; + + config = lib.mkIf cfg.enable { + homelab.virtualisation.containers.enable = true; + + virtualisation.oci-containers.containers.speedtest = { + hostname = "speedtest"; + image = "openspeedtest/latest:v2.0.5"; + ports = [ + "3000:3000" + "3001:3001" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.speedtest.rule" = "Host(`speedtest.${config.networking.hostName}.${config.networking.domain}`)"; + "traefik.http.services.speedtest.loadbalancer.server.port" = "9090"; + "traefik.tls.options.default.minVersion" = "VersionTLS13"; + }; + autoStart = true; + }; + }; +} diff --git a/modules/apps/technitium-dns/default.nix b/modules/apps/technitium-dns/default.nix new file mode 100644 index 0000000..0d0c71c --- /dev/null +++ b/modules/apps/technitium-dns/default.nix @@ -0,0 +1,73 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.apps.technitiumDNS; +in { + options.homelab.apps.technitiumDNS.enable = lib.mkEnableOption "Technitium DNS"; + + config = lib.mkIf cfg.enable { + homelab.virtualisation.containers.enable = true; + + virtualisation.oci-containers.containers.technitium-dns = { + hostname = "technitium-dns"; + image = "technitium/dns-server:12.1"; + ports = [ + # "5380:5380/tcp" #DNS web console (HTTP) + # "53443:53443/tcp" #DNS web console (HTTPS) + "53:53/udp" #DNS service + "53:53/tcp" #DNS service + # "853:853/udp" #DNS-over-QUIC service + # "853:853/tcp" #DNS-over-TLS service + # "443:443/udp" #DNS-over-HTTPS service (HTTP/3) + # "443:443/tcp" #DNS-over-HTTPS service (HTTP/1.1, HTTP/2) + # "80:80/tcp" #DNS-over-HTTP service (use with reverse proxy or certbot certificate renewal) + # "8053:8053/tcp" #DNS-over-HTTP service (use with reverse proxy) + # "67:67/udp" #DHCP service + ]; + environment = { + # The primary domain name used by this DNS Server to identify itself. + DNS_SERVER_DOMAIN = config.networking.hostName; + # DNS Server will use IPv6 for querying whenever possible with this option enabled. + DNS_SERVER_PREFER_IPV6 = "true"; + # The TCP port number for the DNS web console over HTTP protocol. + # DNS_SERVER_WEB_SERVICE_HTTP_PORT=5380 + # The TCP port number for the DNS web console over HTTPS protocol. + # DNS_SERVER_WEB_SERVICE_HTTPS_PORT=53443 + # Enables HTTPS for the DNS web console. + # DNS_SERVER_WEB_SERVICE_ENABLE_HTTPS=false + # Enables self signed TLS certificate for the DNS web console. + # DNS_SERVER_WEB_SERVICE_USE_SELF_SIGNED_CERT=false + # Enables DNS server optional protocol DNS-over-HTTP on TCP port 8053 to be used with a TLS terminating reverse proxy like nginx. + # DNS_SERVER_OPTIONAL_PROTOCOL_DNS_OVER_HTTP=false + # Recursion options: Allow, Deny, AllowOnlyForPrivateNetworks, UseSpecifiedNetworks. + #nDNS_SERVER_RECURSION=AllowOnlyForPrivateNetworks + # Comma separated list of IP addresses or network addresses to deny recursion. Valid only for `UseSpecifiedNetworks` recursion option. + # DNS_SERVER_RECURSION_DENIED_NETWORKS=1.1.1.0/24 + # Comma separated list of IP addresses or network addresses to allow recursion. Valid only for `UseSpecifiedNetworks` recursion option. + # DNS_SERVER_RECURSION_ALLOWED_NETWORKS=127.0.0.1, 192.168.1.0/24 + # Sets the DNS server to block domain names using Blocked Zone and Block List Zone. + DNS_SERVER_ENABLE_BLOCKING = "false"; + # Specifies if the DNS Server should respond with TXT records containing a blocked domain report for TXT type requests. + # DNS_SERVER_ALLOW_TXT_BLOCKING_REPORT=false + # A comma separated list of block list URLs. + # DNS_SERVER_BLOCK_LIST_URLS= + #Comma separated list of forwarder addresses. + DNS_SERVER_FORWARDERS="195.130.130.2,195.130.131.2"; + # Forwarder protocol options: Udp, Tcp, Tls, Https, HttpsJson. + # DNS_SERVER_FORWARDER_PROTOCOL=Tcp + # Enable this option to use local time instead of UTC for logging. + # DNS_SERVER_LOG_USING_LOCAL_TIME=true + }; + volumes = [ + "technitium_dns:/etc/dns" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.technitium-dns.rule" = "Host(`dns.${config.networking.hostName}.${config.networking.domain}`)"; + "traefik.http.services.technitium-dns.loadbalancer.server.port" = "5380"; + "traefik.tls.options.default.minVersion" = "VersionTLS13"; + }; + autoStart = true; + }; + }; +} diff --git a/modules/apps/vaultwarden/default.nix b/modules/apps/vaultwarden/default.nix new file mode 100644 index 0000000..6d06287 --- /dev/null +++ b/modules/apps/vaultwarden/default.nix @@ -0,0 +1,636 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.apps.vaultwarden; + + networkName = "vaultwarden"; +in { + options.homelab.apps.vaultwarden.enable = lib.mkEnableOption "Vaultwarden"; + + config = lib.mkIf cfg.enable { + homelab = { + # Allow remote backups. + users.backup.enable = true; + + virtualisation.containers.enable = true; + }; + + # Make sure the Docker network exists. + systemd.services."docker-${networkName}-create-network" = { + description = "Create Docker network for ${networkName}"; + requiredBy = [ + "docker-vaultwarden-db.service" + "docker-vaultwarden.service" + ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then + ${pkgs.docker}/bin/docker network create ${networkName} + fi + ''; + }; + + virtualisation.oci-containers.containers = { + vaultwarden-db = { + hostname = "vaultwarden-db"; + image = "postgres:15.8-alpine"; + autoStart = true; + ports = [ + "5432:5432/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + environment = { + POSTGRES_PASSWORD = "ChangeMe"; + PGDATA = "/var/lib/postgresql/data/pgdata"; + }; + volumes = [ + "vaultwarden-db:/var/lib/postgresql/data" + ]; + }; + + vaultwarden = let + dataDir = "/data"; + in { + hostname = "vaultwarden"; + image = "vaultwarden/server:1.32.5-alpine"; + autoStart = true; + ports = [ + "10102:80/tcp" + ]; + extraOptions = [ + "--network=${networkName}" + ]; + dependsOn = [ + "vaultwarden-db" + ]; + volumes = [ + "vaultwarden:${dataDir}" + ]; + environmentFiles = [ + # NOTE Don't forget to create this file + # TODO Put in place using age(nix)? + "/var/lib/vaultwarden.env" + ]; + environment = { + #################### + ### Data folders ### + #################### + + ## Main data folder + DATA_FOLDER = dataDir; + + ## Individual folders, these override %DATA_FOLDER% + # ICON_CACHE_FOLDER=data/icon_cache + # ATTACHMENTS_FOLDER=data/attachments + # SENDS_FOLDER=data/sends + # TMP_FOLDER=data/tmp + + ## Templates data folder, by default uses embedded templates + ## Check source code to see the format + # TEMPLATES_FOLDER=data/templates + ## Automatically reload the templates for every request, slow, use only for development + # RELOAD_TEMPLATES=false + + ## Web vault settings + # WEB_VAULT_FOLDER=web-vault/ + # WEB_VAULT_ENABLED=true + + ######################### + ### Database settings ### + ######################### + + ## Database URL + ## When using SQLite, this is the path to the DB file, default to %DATA_FOLDER%/db.sqlite3 + # DATABASE_URL=data/db.sqlite3 + ## When using MySQL, specify an appropriate connection URI. + ## Details: https://docs.diesel.rs/2.1.x/diesel/mysql/struct.MysqlConnection.html + # DATABASE_URL=mysql://user:password@host[:port]/database_name + ## When using PostgreSQL, specify an appropriate connection URI (recommended) + ## or keyword/value connection string. + ## Details: + ## - https://docs.diesel.rs/2.1.x/diesel/pg/struct.PgConnection.html + ## - https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING + DATABASE_URL = "postgresql://vaultwarden:ChangeMe@vaultwarden-db:5432/vaultwarden"; + + ## Enable WAL for the DB + ## Set to false to avoid enabling WAL during startup. + ## Note that if the DB already has WAL enabled, you will also need to disable WAL in the DB, + ## this setting only prevents Vaultwarden from automatically enabling it on start. + ## Please read project wiki page about this setting first before changing the value as it can + ## cause performance degradation or might render the service unable to start. + # ENABLE_DB_WAL=true + + ## Database connection retries + ## Number of times to retry the database connection during startup, with 1 second delay between each retry, set to 0 to retry indefinitely + # DB_CONNECTION_RETRIES=15 + + ## Database timeout + ## Timeout when acquiring database connection + # DATABASE_TIMEOUT=30 + + ## Database max connections + ## Define the size of the connection pool used for connecting to the database. + # DATABASE_MAX_CONNS=10 + + ## Database connection initialization + ## Allows SQL statements to be run whenever a new database connection is created. + ## This is mainly useful for connection-scoped pragmas. + ## If empty, a database-specific default is used: + ## - SQLite: "PRAGMA busy_timeout = 5000; PRAGMA synchronous = NORMAL;" + ## - MySQL: "" + ## - PostgreSQL: "" + # DATABASE_CONN_INIT="" + + ################# + ### WebSocket ### + ################# + + ## Enable websocket notifications + # ENABLE_WEBSOCKET=true + + ########################## + ### Push notifications ### + ########################## + + ## Enables push notifications (requires key and id from https://bitwarden.com/host) + ## Details about mobile client push notification: + ## - https://github.com/dani-garcia/vaultwarden/wiki/Enabling-Mobile-Client-push-notification + # PUSH_ENABLED=false + # PUSH_INSTALLATION_ID=CHANGEME + # PUSH_INSTALLATION_KEY=CHANGEME + + # WARNING: Do not modify the following settings unless you fully understand their implications! + # Default Push Relay and Identity URIs + # PUSH_RELAY_URI=https://push.bitwarden.com + # PUSH_IDENTITY_URI=https://identity.bitwarden.com + # European Union Data Region Settings + # If you have selected "European Union" as your data region, use the following URIs instead. + # PUSH_RELAY_URI=https://api.bitwarden.eu + # PUSH_IDENTITY_URI=https://identity.bitwarden.eu + + ##################### + ### Schedule jobs ### + ##################### + + ## Job scheduler settings + ## + ## Job schedules use a cron-like syntax (as parsed by https://crates.io/crates/cron), + ## and are always in terms of UTC time (regardless of your local time zone settings). + ## + ## The schedule format is a bit different from crontab as crontab does not contains seconds. + ## You can test the the format here: https://crontab.guru, but remove the first digit! + ## SEC MIN HOUR DAY OF MONTH MONTH DAY OF WEEK + ## "0 30 9,12,15 1,15 May-Aug Mon,Wed,Fri" + ## "0 30 * * * * " + ## "0 30 1 * * * " + ## + ## How often (in ms) the job scheduler thread checks for jobs that need running. + ## Set to 0 to globally disable scheduled jobs. + # JOB_POLL_INTERVAL_MS=30000 + ## + ## Cron schedule of the job that checks for Sends past their deletion date. + ## Defaults to hourly (5 minutes after the hour). Set blank to disable this job. + # SEND_PURGE_SCHEDULE="0 5 * * * *" + ## + ## Cron schedule of the job that checks for trashed items to delete permanently. + ## Defaults to daily (5 minutes after midnight). Set blank to disable this job. + # TRASH_PURGE_SCHEDULE="0 5 0 * * *" + ## + ## Cron schedule of the job that checks for incomplete 2FA logins. + ## Defaults to once every minute. Set blank to disable this job. + # INCOMPLETE_2FA_SCHEDULE="30 * * * * *" + ## + ## Cron schedule of the job that sends expiration reminders to emergency access grantors. + ## Defaults to hourly (3 minutes after the hour). Set blank to disable this job. + # EMERGENCY_NOTIFICATION_REMINDER_SCHEDULE="0 3 * * * *" + ## + ## Cron schedule of the job that grants emergency access requests that have met the required wait time. + ## Defaults to hourly (7 minutes after the hour). Set blank to disable this job. + # EMERGENCY_REQUEST_TIMEOUT_SCHEDULE="0 7 * * * *" + ## + ## Cron schedule of the job that cleans old events from the event table. + ## Defaults to daily. Set blank to disable this job. Also without EVENTS_DAYS_RETAIN set, this job will not start. + # EVENT_CLEANUP_SCHEDULE="0 10 0 * * *" + ## Number of days to retain events stored in the database. + ## If unset (the default), events are kept indefinitely and the scheduled job is disabled! + # EVENTS_DAYS_RETAIN= + ## + ## Cron schedule of the job that cleans old auth requests from the auth request. + ## Defaults to every minute. Set blank to disable this job. + # AUTH_REQUEST_PURGE_SCHEDULE="30 * * * * *" + ## + ## Cron schedule of the job that cleans expired Duo contexts from the database. Does nothing if Duo MFA is disabled or set to use the legacy iframe prompt. + ## Defaults to every minute. Set blank to disable this job. + # DUO_CONTEXT_PURGE_SCHEDULE="30 * * * * *" + + ######################## + ### General settings ### + ######################## + + ## Domain settings + ## The domain must match the address from where you access the server + ## It's recommended to configure this value, otherwise certain functionality might not work, + ## like attachment downloads, email links and U2F. + ## For U2F to work, the server must use HTTPS, you can use Let's Encrypt for free certs + ## To use HTTPS, the recommended way is to put Vaultwarden behind a reverse proxy + ## Details: + ## - https://github.com/dani-garcia/vaultwarden/wiki/Enabling-HTTPS + ## - https://github.com/dani-garcia/vaultwarden/wiki/Proxy-examples + ## For development + # DOMAIN=http://localhost + ## For public server + DOMAIN = "https://vault.depeuter.dev"; + ## For public server (URL with port number) + # DOMAIN=https://vw.domain.tld:8443 + ## For public server (URL with path) + # DOMAIN=https://domain.tld/vw + + ## Controls whether users are allowed to create Bitwarden Sends. + ## This setting applies globally to all users. + ## To control this on a per-org basis instead, use the "Disable Send" org policy. + # SENDS_ALLOWED=true + + ## HIBP Api Key + ## HaveIBeenPwned API Key, request it here: https://haveibeenpwned.com/API/Key + # HIBP_API_KEY= + + ## Per-organization attachment storage limit (KB) + ## Max kilobytes of attachment storage allowed per organization. + ## When this limit is reached, organization members will not be allowed to upload further attachments for ciphers owned by that organization. + # ORG_ATTACHMENT_LIMIT= + ## Per-user attachment storage limit (KB) + ## Max kilobytes of attachment storage allowed per user. + ## When this limit is reached, the user will not be allowed to upload further attachments. + # USER_ATTACHMENT_LIMIT= + ## Per-user send storage limit (KB) + ## Max kilobytes of send storage allowed per user. + ## When this limit is reached, the user will not be allowed to upload further sends. + # USER_SEND_LIMIT= + + ## Number of days to wait before auto-deleting a trashed item. + ## If unset (the default), trashed items are not auto-deleted. + ## This setting applies globally, so make sure to inform all users of any changes to this setting. + # TRASH_AUTO_DELETE_DAYS= + + ## Number of minutes to wait before a 2FA-enabled login is considered incomplete, + ## resulting in an email notification. An incomplete 2FA login is one where the correct + ## master password was provided but the required 2FA step was not completed, which + ## potentially indicates a master password compromise. Set to 0 to disable this check. + ## This setting applies globally to all users. + # INCOMPLETE_2FA_TIME_LIMIT=3 + + ## Disable icon downloading + ## Set to true to disable icon downloading in the internal icon service. + ## This still serves existing icons from $ICON_CACHE_FOLDER, without generating any external + ## network requests. $ICON_CACHE_TTL must also be set to 0; otherwise, the existing icons + ## will be deleted eventually, but won't be downloaded again. + # DISABLE_ICON_DOWNLOAD=false + + ## Controls if new users can register + SIGNUPS_ALLOWED = "false"; + + ## Controls if new users need to verify their email address upon registration + ## Note that setting this option to true prevents logins until the email address has been verified! + ## The welcome email will include a verification link, and login attempts will periodically + ## trigger another verification email to be sent. + SIGNUPS_VERIFY = "false"; + + ## If SIGNUPS_VERIFY is set to true, this limits how many seconds after the last time + ## an email verification link has been sent another verification email will be sent + # SIGNUPS_VERIFY_RESEND_TIME=3600 + + ## If SIGNUPS_VERIFY is set to true, this limits how many times an email verification + ## email will be re-sent upon an attempted login. + # SIGNUPS_VERIFY_RESEND_LIMIT=6 + + ## Controls if new users from a list of comma-separated domains can register + ## even if SIGNUPS_ALLOWED is set to false + # SIGNUPS_DOMAINS_WHITELIST=example.com,example.net,example.org + + ## Controls whether event logging is enabled for organizations + ## This setting applies to organizations. + ## Disabled by default. Also check the EVENT_CLEANUP_SCHEDULE and EVENTS_DAYS_RETAIN settings. + # ORG_EVENTS_ENABLED=false + + ## Controls which users can create new orgs. + ## Blank or 'all' means all users can create orgs (this is the default): + # ORG_CREATION_USERS= + ## 'none' means no users can create orgs: + # ORG_CREATION_USERS=none + ## A comma-separated list means only those users can create orgs: + # ORG_CREATION_USERS=admin1@example.com,admin2@example.com + + ## Invitations org admins to invite users, even when signups are disabled + # INVITATIONS_ALLOWED=true + ## Name shown in the invitation emails that don't come from a specific organization + INVITATION_ORG_NAME = "Hugo's Vault"; + + ## The number of hours after which an organization invite token, emergency access invite token, + ## email verification token and deletion request token will expire (must be at least 1) + # INVITATION_EXPIRATION_HOURS=120 + + ## Controls whether users can enable emergency access to their accounts. + ## This setting applies globally to all users. + # EMERGENCY_ACCESS_ALLOWED=true + + ## Controls whether users can change their email. + ## This setting applies globally to all users + # EMAIL_CHANGE_ALLOWED=true + + ## Number of server-side passwords hashing iterations for the password hash. + ## The default for new users. If changed, it will be updated during login for existing users. + # PASSWORD_ITERATIONS=600000 + + ## Controls whether users can set password hints. This setting applies globally to all users. + # PASSWORD_HINTS_ALLOWED=true + + ## Controls whether a password hint should be shown directly in the web page if + ## SMTP service is not configured. Not recommended for publicly-accessible instances + ## as this provides unauthenticated access to potentially sensitive data. + SHOW_PASSWORD_HINT = "false"; + + ######################### + ### Advanced settings ### + ######################### + + ## Client IP Header, used to identify the IP of the client, defaults to "X-Real-IP" + ## Set to the string "none" (without quotes), to disable any headers and just use the remote IP + # IP_HEADER=X-Real-IP + + ## Icon service + ## The predefined icon services are: internal, bitwarden, duckduckgo, google. + ## To specify a custom icon service, set a URL template with exactly one instance of `{}`, + ## which is replaced with the domain. For example: `https://icon.example.com/domain/{}`. + ## + ## `internal` refers to Vaultwarden's built-in icon fetching implementation. + ## If an external service is set, an icon request to Vaultwarden will return an HTTP + ## redirect to the corresponding icon at the external service. An external service may + ## be useful if your Vaultwarden instance has no external network connectivity, or if + ## you are concerned that someone may probe your instance to try to detect whether icons + ## for certain sites have been cached. + # ICON_SERVICE=internal + + ## Icon redirect code + ## The HTTP status code to use for redirects to an external icon service. + ## The supported codes are 301 (legacy permanent), 302 (legacy temporary), 307 (temporary), and 308 (permanent). + ## Temporary redirects are useful while testing different icon services, but once a service + ## has been decided on, consider using permanent redirects for cacheability. The legacy codes + ## are currently better supported by the Bitwarden clients. + # ICON_REDIRECT_CODE=302 + + ## Cache time-to-live for successfully obtained icons, in seconds (0 is "forever") + ## Default: 2592000 (30 days) + # ICON_CACHE_TTL=2592000 + ## Cache time-to-live for icons which weren't available, in seconds (0 is "forever") + ## Default: 2592000 (3 days) + # ICON_CACHE_NEGTTL=259200 + + ## Icon download timeout + ## Configure the timeout value when downloading the favicons. + ## The default is 10 seconds, but this could be to low on slower network connections + # ICON_DOWNLOAD_TIMEOUT=10 + + ## Block HTTP domains/IPs by Regex + ## Any domains or IPs that match this regex won't be fetched by the internal HTTP client. + ## Useful to hide other servers in the local network. Check the WIKI for more details + ## NOTE: Always enclose this regex withing single quotes! + # HTTP_REQUEST_BLOCK_REGEX='^(192\.168\.0\.[0-9]+|192\.168\.1\.[0-9]+)$' + + ## Enabling this will cause the internal HTTP client to refuse to connect to any non global IP address. + ## Useful to secure your internal environment: See https://en.wikipedia.org/wiki/Reserved_IP_addresses for a list of IPs which it will block + # HTTP_REQUEST_BLOCK_NON_GLOBAL_IPS=true + + ## Client Settings + ## Enable experimental feature flags for clients. + ## This is a comma-separated list of flags, e.g. "flag1,flag2,flag3". + ## + ## The following flags are available: + ## - "autofill-overlay": Add an overlay menu to form fields for quick access to credentials. + ## - "autofill-v2": Use the new autofill implementation. + ## - "browser-fileless-import": Directly import credentials from other providers without a file. + ## - "fido2-vault-credentials": Enable the use of FIDO2 security keys as second factor. + # EXPERIMENTAL_CLIENT_FEATURE_FLAGS=fido2-vault-credentials + + ## Require new device emails. When a user logs in an email is required to be sent. + ## If sending the email fails the login attempt will fail!! + # REQUIRE_DEVICE_EMAIL=false + + ## Enable extended logging, which shows timestamps and targets in the logs + # EXTENDED_LOGGING=true + + ## Timestamp format used in extended logging. + ## Format specifiers: https://docs.rs/chrono/latest/chrono/format/strftime + # LOG_TIMESTAMP_FORMAT="%Y-%m-%d %H:%M:%S.%3f" + + ## Logging to Syslog + ## This requires extended logging + # USE_SYSLOG=false + + ## Logging to file + # LOG_FILE=/path/to/log + + ## Log level + ## Change the verbosity of the log output + ## Valid values are "trace", "debug", "info", "warn", "error" and "off" + ## Setting it to "trace" or "debug" would also show logs for mounted routes and static file, websocket and alive requests + ## For a specific module append a comma separated `path::to::module=log_level` + ## For example, to only see debug logs for icons use: LOG_LEVEL="info,vaultwarden::api::icons=debug" + LOG_LEVEL = "warn"; + + ## Token for the admin interface, preferably an Argon2 PCH string + ## Vaultwarden has a built-in generator by calling `vaultwarden hash` + ## For details see: https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page#secure-the-admin_token + ## If not set, the admin panel is disabled + ## New Argon2 PHC string + ## Note that for some environments, like docker-compose you need to escape all the dollar signs `$` with an extra dollar sign like `$$` + ## Also, use single quotes (') instead of double quotes (") to enclose the string when needed + # ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78' + ## Old plain text string (Will generate warnings in favor of Argon2) + # ADMIN_TOKEN=Vy2VyYTTsKPv8W5aEOWUbB/Bt3DEKePbHmI4m9VcemUMS2rEviDowNAFqYi1xjmp + + ## Enable this to bypass the admin panel security. This option is only + ## meant to be used with the use of a separate auth layer in front + # DISABLE_ADMIN_TOKEN=false + + ## Number of seconds, on average, between admin login requests from the same IP address before rate limiting kicks in. + # ADMIN_RATELIMIT_SECONDS=300 + ## Allow a burst of requests of up to this size, while maintaining the average indicated by `ADMIN_RATELIMIT_SECONDS`. + # ADMIN_RATELIMIT_MAX_BURST=3 + + ## Set the lifetime of admin sessions to this value (in minutes). + # ADMIN_SESSION_LIFETIME=20 + + ## Allowed iframe ancestors (Know the risks!) + ## https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors + ## Allows other domains to embed the web vault into an iframe, useful for embedding into secure intranets + ## This adds the configured value to the 'Content-Security-Policy' headers 'frame-ancestors' value. + ## Multiple values must be separated with a whitespace. + # ALLOWED_IFRAME_ANCESTORS= + + ## Number of seconds, on average, between login requests from the same IP address before rate limiting kicks in. + # LOGIN_RATELIMIT_SECONDS=60 + ## Allow a burst of requests of up to this size, while maintaining the average indicated by `LOGIN_RATELIMIT_SECONDS`. + ## Note that this applies to both the login and the 2FA, so it's recommended to allow a burst size of at least 2. + # LOGIN_RATELIMIT_MAX_BURST=10 + + ## BETA FEATURE: Groups + ## Controls whether group support is enabled for organizations + ## This setting applies to organizations. + ## Disabled by default because this is a beta feature, it contains known issues! + ## KNOW WHAT YOU ARE DOING! + # ORG_GROUPS_ENABLED=false + + ## Increase secure note size limit (Know the risks!) + ## Sets the secure note size limit to 100_000 instead of the default 10_000. + ## WARNING: This could cause issues with clients. Also exports will not work on Bitwarden servers! + ## KNOW WHAT YOU ARE DOING! + # INCREASE_NOTE_SIZE_LIMIT=false + + ## Enforce Single Org with Reset Password Policy + ## Enforce that the Single Org policy is enabled before setting the Reset Password policy + ## Bitwarden enforces this by default. In Vaultwarden we encouraged to use multiple organizations because groups were not available. + ## Setting this to true will enforce the Single Org Policy to be enabled before you can enable the Reset Password policy. + # ENFORCE_SINGLE_ORG_WITH_RESET_PW_POLICY=false + + ######################## + ### MFA/2FA settings ### + ######################## + + ## Yubico (Yubikey) Settings + ## Set your Client ID and Secret Key for Yubikey OTP + ## You can generate it here: https://upgrade.yubico.com/getapikey/ + ## You can optionally specify a custom OTP server + # YUBICO_CLIENT_ID=11111 + # YUBICO_SECRET_KEY=AAAAAAAAAAAAAAAAAAAAAAAA + # YUBICO_SERVER=http://yourdomain.com/wsapi/2.0/verify + + ## Duo Settings + ## You need to configure the DUO_IKEY, DUO_SKEY, and DUO_HOST options to enable global Duo support. + ## Otherwise users will need to configure it themselves. + ## Create an account and protect an application as mentioned in this link (only the first step, not the rest): + ## https://help.bitwarden.com/article/setup-two-step-login-duo/#create-a-duo-security-account + ## Then set the following options, based on the values obtained from the last step: + # DUO_IKEY= + # DUO_SKEY= + # DUO_HOST= + ## After that, you should be able to follow the rest of the guide linked above, + ## ignoring the fields that ask for the values that you already configured beforehand. + ## + ## If you want to attempt to use Duo's 'Traditional Prompt' (deprecated, iframe based) set DUO_USE_IFRAME to 'true'. + ## Duo no longer supports this, but it still works for some integrations. + ## If you aren't sure, leave this alone. + # DUO_USE_IFRAME=false + + ## Email 2FA settings + ## Email token size + ## Number of digits in an email 2FA token (min: 6, max: 255). + ## Note that the Bitwarden clients are hardcoded to mention 6 digit codes regardless of this setting! + # EMAIL_TOKEN_SIZE=6 + ## + ## Token expiration time + ## Maximum time in seconds a token is valid. The time the user has to open email client and copy token. + # EMAIL_EXPIRATION_TIME=600 + ## + ## Maximum attempts before an email token is reset and a new email will need to be sent. + # EMAIL_ATTEMPTS_LIMIT=3 + ## + ## Setup email 2FA regardless of any organization policy + # EMAIL_2FA_ENFORCE_ON_VERIFIED_INVITE=false + ## Automatically setup email 2FA as fallback provider when needed + # EMAIL_2FA_AUTO_FALLBACK=false + + ## Other MFA/2FA settings + ## Disable 2FA remember + ## Enabling this would force the users to use a second factor to login every time. + ## Note that the checkbox would still be present, but ignored. + # DISABLE_2FA_REMEMBER=false + ## + ## Authenticator Settings + ## Disable authenticator time drifted codes to be valid. + ## TOTP codes of the previous and next 30 seconds will be invalid + ## + ## According to the RFC6238 (https://tools.ietf.org/html/rfc6238), + ## we allow by default the TOTP code which was valid one step back and one in the future. + ## This can however allow attackers to be a bit more lucky with there attempts because there are 3 valid codes. + ## You can disable this, so that only the current TOTP Code is allowed. + ## Keep in mind that when a sever drifts out of time, valid codes could be marked as invalid. + ## In any case, if a code has been used it can not be used again, also codes which predates it will be invalid. + # AUTHENTICATOR_DISABLE_TIME_DRIFT=false + + ########################### + ### SMTP Email settings ### + ########################### + + ## Mail specific settings, set SMTP_FROM and either SMTP_HOST or USE_SENDMAIL to enable the mail service. + ## To make sure the email links are pointing to the correct host, set the DOMAIN variable. + ## Note: if SMTP_USERNAME is specified, SMTP_PASSWORD is mandatory + SMTP_HOST = "smtp.gmail.com"; + SMTP_FROM = "vault@depeuter.dev"; + SMTP_FROM_NAME = "Hugo's Vault"; + # SMTP_USERNAME=username + # SMTP_PASSWORD=password + # SMTP_TIMEOUT=15 + + ## Choose the type of secure connection for SMTP. The default is "starttls". + ## The available options are: + ## - "starttls": The default port is 587. + ## - "force_tls": The default port is 465. + ## - "off": The default port is 25. + ## Ports 587 (submission) and 25 (smtp) are standard without encryption and with encryption via STARTTLS (Explicit TLS). Port 465 (submissions) is used for encrypted submission (Implicit TLS). + SMTP_SECURITY = "starttls"; + SMTP_PORT = "587"; + + # Whether to send mail via the `sendmail` command + # USE_SENDMAIL=false + # Which sendmail command to use. The one found in the $PATH is used if not specified. + # SENDMAIL_COMMAND="/path/to/sendmail" + + ## Defaults for SSL is "Plain" and "Login" and nothing for Non-SSL connections. + ## Possible values: ["Plain", "Login", "Xoauth2"]. + ## Multiple options need to be separated by a comma ','. + SMTP_AUTH_MECHANISM = "Login"; + + ## Server name sent during the SMTP HELO + ## By default this value should be is on the machine's hostname, + ## but might need to be changed in case it trips some anti-spam filters + # HELO_NAME= + + ## Embed images as email attachments + # SMTP_EMBED_IMAGES=true + + ## SMTP debugging + ## When set to true this will output very detailed SMTP messages. + ## WARNING: This could contain sensitive information like passwords and usernames! Only enable this during troubleshooting! + # SMTP_DEBUG=false + + ## Accept Invalid Certificates + ## DANGEROUS: This option introduces significant vulnerabilities to man-in-the-middle attacks! + ## Only use this as a last resort if you are not able to use a valid certificate. + ## If the Certificate is valid but the hostname doesn't match, please use SMTP_ACCEPT_INVALID_HOSTNAMES instead. + # SMTP_ACCEPT_INVALID_CERTS=false + + ## Accept Invalid Hostnames + ## DANGEROUS: This option introduces significant vulnerabilities to man-in-the-middle attacks! + ## Only use this as a last resort if you are not able to use a valid certificate. + # SMTP_ACCEPT_INVALID_HOSTNAMES=false + + ####################### + ### Rocket settings ### + ####################### + + ## Rocket specific settings + ## See https://rocket.rs/v0.5/guide/configuration/ for more details. + # ROCKET_ADDRESS=0.0.0.0 + ## The default port is 8000, unless running in a Docker container, in which case it is 80. + # ROCKET_PORT=8000 + # ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"} + }; + }; + }; + }; +} diff --git a/modules/common/default.nix b/modules/common/default.nix new file mode 100644 index 0000000..44309f5 --- /dev/null +++ b/modules/common/default.nix @@ -0,0 +1,16 @@ +{ + config = { + homelab = { + services.openssh.enable = true; + users.admin.enable = true; + }; + + nix.settings.experimental-features = [ + "flakes" + "nix-command" + ]; + + # Set your time zone. + time.timeZone = "Europe/Brussels"; + }; +} diff --git a/modules/default.nix b/modules/default.nix new file mode 100644 index 0000000..5d901bc --- /dev/null +++ b/modules/default.nix @@ -0,0 +1,9 @@ +{ + imports = [ + ./apps + ./services + ./virtualisation + + ./common + ]; +} diff --git a/modules/services/actions/default.nix b/modules/services/actions/default.nix new file mode 100644 index 0000000..338b963 --- /dev/null +++ b/modules/services/actions/default.nix @@ -0,0 +1,49 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.services.actions; +in { + options.homelab.services.actions.enable = lib.mkEnableOption "Actions runner"; + + config = lib.mkIf cfg.enable { + homelab.virtualisation.containers.enable = true; + + services.gitea-actions-runner = { + instances.depeuter-dev = { + enable = true; + url = "https://git.depeuter.dev"; + tokenFile = "/etc/runner/depeuter-dev"; + name = config.networking.hostName; + labels = [ + "debian-11:docker://debian:11" + "debian-12:docker://debian:12" + "debian-latest:docker://debian:latest" + "docker:host" + "Linux:host" + "self-hosted:host" + "ubuntu-22.04:docker://ubuntu:22.04" + "ubuntu-24.04:docker://ubuntu:24.04" + "ubuntu-latest:docker://ubuntu:latest" + ]; + settings = { + cache.enabled = true; + container.privileged = true; + }; + hostPackages = with pkgs; [ + bash + cmake + coreutils + curl + docker + gawk + git + gnused + nodejs + openssh + wget + ]; + }; + }; + + }; +} diff --git a/modules/services/default.nix b/modules/services/default.nix new file mode 100644 index 0000000..f70bc54 --- /dev/null +++ b/modules/services/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./actions + ./openssh + ]; +} diff --git a/modules/services/openssh/default.nix b/modules/services/openssh/default.nix new file mode 100644 index 0000000..4b9cb5e --- /dev/null +++ b/modules/services/openssh/default.nix @@ -0,0 +1,20 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.services.openssh; +in { + options.homelab.services.openssh.enable = lib.mkEnableOption "OpenSSH daemon"; + + config = lib.mkIf cfg.enable { + services.openssh = { + # Enable the OpenSSH daemon. + enable = true; + settings = { + PasswordAuthentication = false; + PermitRootLogin = "no"; + # Disable keyboard-interactive authentication. + KbdInteractiveAuthentication = false; + }; + }; + }; +} diff --git a/modules/virtualisation/containers/default.nix b/modules/virtualisation/containers/default.nix new file mode 100644 index 0000000..ed87990 --- /dev/null +++ b/modules/virtualisation/containers/default.nix @@ -0,0 +1,23 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.virtualisation.containers; +in { + options.homelab.virtualisation.containers.enable = lib.mkEnableOption "OCI containers"; + + config = lib.mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + nfs-utils + ]; + + virtualisation = { + docker = { + enable = true; + enableOnBoot = true; + autoPrune.enable = true; + }; + + oci-containers.backend = "docker"; + }; + }; +} diff --git a/modules/virtualisation/default.nix b/modules/virtualisation/default.nix new file mode 100644 index 0000000..2290be2 --- /dev/null +++ b/modules/virtualisation/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./containers + ./guest + ]; +} diff --git a/modules/virtualisation/guest/default.nix b/modules/virtualisation/guest/default.nix new file mode 100644 index 0000000..bbb9a50 --- /dev/null +++ b/modules/virtualisation/guest/default.nix @@ -0,0 +1,34 @@ +{ config, lib, modulesPath, ... }: + +let + cfg = config.homelab.virtualisation.guest; +in { + options.homelab.virtualisation.guest.enable = lib.mkEnableOption "Settings for devices running on virtualisation, e.g. Proxmox"; + + imports = [ + (modulesPath + "/profiles/qemu-guest.nix") + ]; + + config = lib.mkIf cfg.enable { + boot = { + # Whether to enable growing the root partition on boot. + growPartition = true; + # Use Grub bootloader + loader.grub = { + enable = true; + devices = [ + "nodev" + ]; + }; + }; + + fileSystems."/" = lib.mkDefault { + device = "/dev/disk/by-label/nixos"; + autoResize = true; + fsType = "ext4"; + }; + + # Enable QEMU Guest for Proxmox + services.qemuGuest.enable = true; + }; +} diff --git a/users/admin/default.nix b/users/admin/default.nix new file mode 100644 index 0000000..552909b --- /dev/null +++ b/users/admin/default.nix @@ -0,0 +1,33 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.users.admin; +in { + options.homelab.users.admin.enable = lib.mkEnableOption "user System Administrator"; + + config = lib.mkIf cfg.enable { + nix.settings.trusted-users = [ + config.users.users.admin.name + ]; + + users.users.admin = { + description = "System Administrator"; + isNormalUser = true; + extraGroups = [ + config.users.groups.wheel.name # Enable 'sudo' for the user. + ]; + initialPassword = "ChangeMe"; + openssh.authorizedKeys.keys = [ + # TODO ChangeMe + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrG+ldRBdCeHEXrsy/qHXIJYg8xQXVuiUR0DxhFjYNg" + ]; + packages = with pkgs; [ + curl + git + tmux + vim + wget + ]; + }; + }; +} diff --git a/users/apps/default.nix b/users/apps/default.nix new file mode 100644 index 0000000..e4f7011 --- /dev/null +++ b/users/apps/default.nix @@ -0,0 +1,18 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.users.apps; +in { + options.homelab.users.apps.enable = lib.mkEnableOption "user Apps"; + + config.users = lib.mkIf cfg.enable { + groups.apps.gid = lib.mkForce 568; + users.apps = { + uid = lib.mkForce 568; + isSystemUser = true; + group = config.users.groups.apps.name; + home = "/var/empty"; + shell = null; + }; + }; +} diff --git a/users/backup/default.nix b/users/backup/default.nix new file mode 100644 index 0000000..8181d02 --- /dev/null +++ b/users/backup/default.nix @@ -0,0 +1,26 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.users.backup; +in { + options.homelab.users.backup.enable = lib.mkEnableOption "user Backup"; + + config = lib.mkIf cfg.enable { + users.users.backup = { + description = "Backup User"; + isNormalUser = true; + extraGroups = [ + "docker" # Allow access to the docker socket. + ]; + openssh.authorizedKeys.keys = [ + # TODO ChangeMe + + # Tibo-NixFat + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrG+ldRBdCeHEXrsy/qHXIJYg8xQXVuiUR0DxhFjYNg" + + # Hugo + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDAxR813vqq5zbu1NHrIybu5Imlu3k0rDCGxHiuGEhPoVV9c5FpnKNGLCi3ctm15ZcVBX4HcponYsKRBsCzM2pI4uXjxhHkLzbss5LttFuSzv5v/QHfLW1bvyJEMBEPxguGqAydAeWrBFdI9uHBEXeb325uKxMKBZHYvvpyAQ115c1wKy1bL8BfR0LTkhsFqexRvI86q59AVrAU/KFf6RXO0T9QA6H/vyWLlIPc7Ta+tSWwQ68bMmS5Pwn8q58tOAOAd6Lpt4TqUDJSppPjLEPKyKC6ShwMdEjwmwpEG0hxfsvaU8XERyQbSbEE9sLHRA2LoEdtMx3J8nzX3AwYUNspsqIv6NQZksnVqJ8OfL45ngUFcSJ6kBsUvCZfzEUGUTJ6Js0v84NOIXxNG/ZfPsk6ArXm3dvj2TYeK8llO6wpJnMMyztmmiODWoj9tepZSij44IgVM5wdWYIK/RZoYTsCQbmvJFfB8jhyJnf/7F19Vo5+LwhmCOsQh/KEK0F1DVc= admin@Hugo" + ]; + }; + }; +} diff --git a/users/default.nix b/users/default.nix new file mode 100644 index 0000000..fe82019 --- /dev/null +++ b/users/default.nix @@ -0,0 +1,9 @@ +{ + imports = [ + ./admin + ./apps + ./backup + ./deploy + ./media + ]; +} diff --git a/users/deploy/default.nix b/users/deploy/default.nix new file mode 100644 index 0000000..0509d1e --- /dev/null +++ b/users/deploy/default.nix @@ -0,0 +1,49 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.users.deploy; +in { + options.homelab.users.deploy.enable = lib.mkEnableOption "user Deploy"; + + config = lib.mkIf cfg.enable { + users = { + groups.deploy = { }; + + # The user used to deploy rebuilds without password authentication + users.deploy = { + group = config.users.groups.deploy.name; + isSystemUser = true; + home = "/var/empty"; + shell = pkgs.bashInteractive; + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrG+ldRBdCeHEXrsy/qHXIJYg8xQXVuiUR0DxhFjYNg" + ]; + }; + }; + + security.sudo.extraRules = [ + { + groups = [ + config.users.groups.deploy.name + ]; + commands = [ + { + command = "/nix/store/*-nix-*/bin/nix-env -p /nix/var/nix/profile/system --set /nix/store/*-*"; + options = [ "NOPASSWD" ]; + } + ]; + } + { + groups = [ + config.users.groups.deploy.name + ]; + commands = [ + { + command = "/nix/store/*/bin/switch-to-configuration"; + options = [ "NOPASSWD" ]; + } + ]; + } + ]; + }; +} diff --git a/users/media/default.nix b/users/media/default.nix new file mode 100644 index 0000000..e70a2b3 --- /dev/null +++ b/users/media/default.nix @@ -0,0 +1,18 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.users.media; +in { + options.homelab.users.media.enable = lib.mkEnableOption "user Media"; + + config.users = lib.mkIf cfg.enable { + groups.media.gid = lib.mkForce 3000; + users.media = { + uid = lib.mkForce 3001; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + }; +} From 217fee5b3fbd2513a9ea98f63a4b803894f3cf59 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Wed, 12 Feb 2025 19:26:43 +0100 Subject: [PATCH 06/31] feat: Configure *arr stack --- modules/apps/arr/default.nix | 228 ++++++++++++++++++++++++++++------- 1 file changed, 183 insertions(+), 45 deletions(-) diff --git a/modules/apps/arr/default.nix b/modules/apps/arr/default.nix index a88ed9c..e45b1bf 100644 --- a/modules/apps/arr/default.nix +++ b/modules/apps/arr/default.nix @@ -42,6 +42,104 @@ in { }; fileSystems = lib.mkIf inUse { + "/srv/bazarr-backup" = lib.mkIf cfg.bazarr.enable { + device = "192.168.0.11:/mnt/BIG/BACKUP/BAZARR"; + fsType = "nfs"; + options = [ + "rw" + "auto" + "nfsvers=4.2" + "rsize=1048576" "wsize=1048576" + "hard" + "timeo=600" "retrans=2" + "_netdev" "nosuid" "tcp" + ]; + }; + + "/srv/lidarr-backup" = lib.mkIf cfg.lidarr.enable { + device = "192.168.0.11:/mnt/BIG/BACKUP/LIDARR"; + fsType = "nfs"; + options = [ + "rw" + "auto" + "nfsvers=4.2" + "rsize=1048576" "wsize=1048576" + "hard" + "timeo=600" "retrans=2" + "_netdev" "nosuid" "tcp" + ]; + }; + + "/srv/prowlarr-backup" = lib.mkIf cfg.prowlarr.enable { + device = "192.168.0.11:/mnt/BIG/BACKUP/PROWLARR"; + fsType = "nfs"; + options = [ + "rw" + "auto" + "nfsvers=4.2" + "rsize=1048576" "wsize=1048576" + "hard" + "timeo=600" "retrans=2" + "_netdev" "nosuid" "tcp" + ]; + }; + + "/srv/qbittorrent" = lib.mkIf cfg.qbittorrent.enable { + device = "192.168.0.11:/mnt/SMALL/CONFIG/QBITTORRENT"; + fsType = "nfs"; + options = [ + "rw" + "auto" + "nfsvers=4.2" + "rsize=1048576" "wsize=1048576" + "hard" + "timeo=600" "retrans=2" + "_netdev" "nosuid" "tcp" + ]; + }; + + "/srv/radarr-backup" = lib.mkIf cfg.radarr.enable { + device = "192.168.0.11:/mnt/BIG/BACKUP/RADARR"; + fsType = "nfs"; + options = [ + "rw" + "auto" + "nfsvers=4.2" + "rsize=1048576" "wsize=1048576" + "hard" + "timeo=600" "retrans=2" + "_netdev" "nosuid" "tcp" + ]; + }; + + "/srv/sonarr-backup" = lib.mkIf cfg.sonarr.enable { + device = "192.168.0.11:/mnt/BIG/BACKUP/SONARR"; + fsType = "nfs"; + options = [ + "rw" + "auto" + "nfsvers=4.2" + "rsize=1048576" "wsize=1048576" + "hard" + "timeo=600" "retrans=2" + "_netdev" "nosuid" "tcp" + ]; + }; + + "/srv/torrent" = { + device = "192.168.0.11:/mnt/SMALL/MEDIA/TORRENT"; + fsType = "nfs"; + options = [ + "rw" + "auto" + "nfsvers=4.2" + "rsize=1048576" "wsize=1048576" + "hard" + "timeo=600" "retrans=2" + "_netdev" "nosuid" "tcp" + ]; + }; + "/srv/video" = { device = "192.168.0.11:/mnt/SMALL/MEDIA/VIDEO"; fsType = "nfs"; @@ -55,20 +153,6 @@ in { "_netdev" "nosuid" "tcp" ]; }; - - "/srv/qbittorrent" = { - device = "192.168.0.11:/mnt/SMALL/CONFIG/QBITTORRENT"; - fsType = "nfs"; - options = [ - "rw" - "auto" - "nfsvers=4.2" - "hard" - "rsize=1048576" "wsize=1048576" - "timeo=600" "retrans=2" - "_netdev" "nosuid" "tcp" - ]; - }; }; # Make sure the Docker network exists. @@ -143,42 +227,53 @@ in { }; virtualisation.oci-containers.containers = { - bazarr = lib.mkIf cfg.bazarr.enable { + bazarr = let + port = 6767; + in lib.mkIf cfg.bazarr.enable { hostname = "bazarr"; image = "ghcr.io/hotio/bazarr:release-1.4.4"; autoStart = true; ports = [ - "6767:6767/tcp" - "6767:6767/udp" + # Open ports if you don't use Traefik + # "${toString port}:${toString port}/tcp" + # "${toString port}:${toString port}/udp" ]; extraOptions = [ "--network=${networkName}" - - "--mount" ''type=volume,source=bazarr-backup,target=/backup,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/mnt/BIG/BACKUP/BAZARR,"volume-opt=o=addr=192.168.0.11,rw,nfsvers=4.2,async,nosuid"'' ]; environment = { PUID = toString config.users.users.bazarr.uid; inherit PGID UMASK; TZ = config.time.timeZone; - WEBUI_PORTS = "6767/tcp,6767/udp"; + WEBUI_PORTS = "${toString port}/tcp,${toString port}/udp"; }; volumes = [ "bazarr-config:/config" - "/srv/video:/data" + + "/srv/bazarr-backup:/config/backup" + + "/srv/video/Films:/media/movies" + "/srv/video/Series:/media/series" ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.bazarr.rule" = "Host(`bazarr.depeuter.dev`)"; + "traefik.http.services.bazarr.loadbalancer.server.port" = toString port; + }; }; - lidarr = lib.mkIf cfg.lidarr.enable { + lidarr = let + port = 8686; + in lib.mkIf cfg.lidarr.enable { hostname = "lidarr"; image = "ghcr.io/hotio/lidarr:release-2.5.3.4341"; autoStart = true; ports = [ - "8686:8686/tcp" + # Open ports if you don't use Traefik + # "${toString port}:${toString port}/tcp" ]; extraOptions = [ "--network=${networkName}" - - "--mount" ''type=volume,source=lidarr-backup,target=/backup,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/mnt/BIG/BACKUP/LIDARR,"volume-opt=o=addr=192.168.0.11,rw,nfsvers=4.2,async,nosuid"'' ]; environment = { PUID = toString config.users.users.lidarr.uid; @@ -187,16 +282,21 @@ in { }; volumes = [ "lidarr-config:/config" - # TODO "data:/data" + + # TODO Fix path + "/srv/lidarr-backup:/media/Backups" ]; }; - prowlarr = lib.mkIf cfg.prowlarr.enable { + prowlarr = let + port = 9696; + in lib.mkIf cfg.prowlarr.enable { hostname = "prowlarr"; image = "ghcr.io/hotio/prowlarr:release-1.23.1.4708"; autoStart = true; ports = [ - "9696:9696/tcp" + # Open ports if you don't use Traefik + # "${toString port}:${toString port}/tcp" ]; extraOptions = [ "--network=${networkName}" @@ -207,41 +307,58 @@ in { TZ = config.time.timeZone; }; volumes = [ - # TODO "config:/config" + "prowlarr-config:/config" + + "/srv/prowlarr-backup:/config/Backups" ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.prowlarr.rule" = "Host(`prowlarr.depeuter.dev`)"; + "traefik.http.services.prowlarr.loadbalancer.server.port" = toString port; + }; }; - qbittorrent = lib.mkIf cfg.qbittorrent.enable { + qbittorrent = let + port = 10095; + in lib.mkIf cfg.qbittorrent.enable { hostname = "qbittorrent"; image = "ghcr.io/hotio/qbittorrent:release-4.6.7"; autoStart = true; ports = [ - "10095:10095/udp" - "10095:10095/tcp" + # Open ports if you don't use Traefik + # "${toString port}:${toString port}/tcp" + # "${toString port}:${toString port}/udp" ]; extraOptions = [ "--network=${networkName}" - - "--mount" ''type=volume,source=torrents,target=/data,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/mnt/SMALL/MEDIA/TORRENT,"volume-opt=o=addr=192.168.0.11,rw,auto,nfsvers=4.2,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,_netdev,nosuid,tcp"'' ]; environment = { PUID = toString config.users.users.qbittorrent.uid; inherit PGID UMASK; TZ = config.time.timeZone; - WEBUI_PORTS = "10095/tcp,10095/udp"; + WEBUI_PORTS = "${toString port}/tcp,${toString port}/udp"; }; volumes = [ - "/srv/qbittorrent:/config/config" - "/srv/video:/media/video" + "/srv/qbittorrent:/config" + + "/srv/torrent:/media/cache" ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.qbittorrent.rule" = "Host(`qb.depeuter.dev`)"; + "traefik.http.services.qbittorrent.loadbalancer.server.port" = toString port; + }; }; - radarr = lib.mkIf cfg.radarr.enable { + radarr = let + port = 7878; + in lib.mkIf cfg.radarr.enable { hostname = "radarr"; image = "ghcr.io/hotio/radarr:release-5.9.1.9070"; autoStart = true; ports = [ - "7878:7878/tcp" + # Open ports if you don't use Traefik + # "${toString port}:${toString port}/tcp" ]; extraOptions = [ "--network=${networkName}" @@ -252,17 +369,29 @@ in { TZ = config.time.timeZone; }; volumes = [ - # TODO "config:/config" - # TODO "data:/data" + "radarr-config:/config" + + "/srv/radarr-backup:/config/Backups" + + "/srv/torrent:/media/cache" + "/srv/video/Films:/media/movies" ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.radarr.rule" = "Host(`radarr.depeuter.dev`)"; + "traefik.http.services.radarr.loadbalancer.server.port" = toString port; + }; }; - sonarr = lib.mkIf cfg.sonarr.enable { + sonarr = let + port = 8989; + in lib.mkIf cfg.sonarr.enable { hostname = "sonarr"; image = "ghcr.io/hotio/sonarr:release-4.0.9.2244"; autoStart = true; ports = [ - "8989:8989/tcp" + # Open ports if you don't use Traefik + # "${toString port}:${toString port}/tcp" ]; extraOptions = [ "--network=${networkName}" @@ -273,9 +402,18 @@ in { TZ = config.time.timeZone; }; volumes = [ - # TODO "config:/config" - # TODO "data:/data" + "sonarr-config:/config" + + "/srv/sonarr-backup:/config/Backups" + + "/srv/torrent:/media/cache" + "/srv/video/Series:/media/series" ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.sonarr.rule" = "Host(`sonarr.depeuter.dev`)"; + "traefik.http.services.sonarr.loadbalancer.server.port" = toString port; + }; }; }; }; From 8c3bb2b3cea9efe1a2202c2bf533029aab06dbd6 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 21 Mar 2025 21:53:33 +0100 Subject: [PATCH 07/31] feat: Plex without synchronized config --- modules/apps/plex/default.nix | 44 ++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/modules/apps/plex/default.nix b/modules/apps/plex/default.nix index 251f9dd..7e21745 100644 --- a/modules/apps/plex/default.nix +++ b/modules/apps/plex/default.nix @@ -6,14 +6,37 @@ in { options.homelab.apps.plex.enable = lib.mkEnableOption "Plex"; config = lib.mkIf cfg.enable { + homelab = { + users = { + apps.enable = true; + media.enable = true; + }; + virtualisation.containers.enable = true; + }; + users.users.plex = { uid = lib.mkForce 3009; isSystemUser = true; - group = config.users.groups.media; + group = config.users.groups.apps.name; + extraGroups = [ + config.users.groups.media.name + ]; home = "/var/empty"; shell = null; }; + fileSystems."/srv/video" = { + device = "192.168.0.11:/mnt/SMALL/MEDIA/VIDEO"; + fsType = "nfs"; + options = [ + "ro" + "nfsvers=4.2" + "async" "soft" + "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" + "nosuid" "tcp" + ]; + }; + virtualisation.oci-containers.containers = { plex = { hostname = "plex"; @@ -30,20 +53,25 @@ in { # "8324:8324/tcp" # Controlling Plex for Roku via Plex Companion ]; environment = { - ADVERTISE_AP = "..."; # TODO Configure ip + #ADVERTISE_AP = "..."; # TODO Configure ip ALLOWED_NETWORKS = "192.168.0.0/24,172.16.0.0/16"; CHANGE_CONFIG_DIR_OWNERSHIP = "false"; HOSTNAME = "PlexServer"; - PLEX_CLAIM = "..."; # TODO Add token - PLEX_UID = config.users.users.plex.uid; - PLEX_GID = config.users.groups.media.gid; + #PLEX_CLAIM = "..."; # TODO Add token + PLEX_UID = toString config.users.users.plex.uid; + PLEX_GID = toString config.users.groups.media.gid; TZ = config.time.timeZone; }; volumes = [ - # TODO "config:/var/lib/plexmediaserver" - # TODO "transcode-temp:/transcode" - # TODO "media:/data" + "plex-config:/var/lib/plexmediaserver" + "plex-transcode:/transcode" + "/srv/video:/data/video" ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.plex.rule=" = "Host(`plex.depeuter.dev`)"; + "traefik.http.services.plex.loadbalancer.server.port" = "32400"; + }; }; }; }; From f3090538d802d4cfa5d8b239581ef727a69466ee Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 28 Mar 2025 12:37:27 +0100 Subject: [PATCH 08/31] chore: Update Vaultwarden --- hosts/Vaultwarden/default.nix | 6 +++- modules/apps/vaultwarden/default.nix | 42 ++++++++++++++++++++-------- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/hosts/Vaultwarden/default.nix b/hosts/Vaultwarden/default.nix index 9f98d84..d8115bc 100644 --- a/hosts/Vaultwarden/default.nix +++ b/hosts/Vaultwarden/default.nix @@ -3,7 +3,11 @@ { config = { homelab = { - apps.vaultwarden.enable = true; + apps.vaultwarden = { + enable = true; + domain = "https://vault.depeuter.dev"; + name = "Hugo's Vault"; + }; virtualisation.guest.enable = true; }; diff --git a/modules/apps/vaultwarden/default.nix b/modules/apps/vaultwarden/default.nix index 6d06287..a2f8d0a 100644 --- a/modules/apps/vaultwarden/default.nix +++ b/modules/apps/vaultwarden/default.nix @@ -5,7 +5,24 @@ let networkName = "vaultwarden"; in { - options.homelab.apps.vaultwarden.enable = lib.mkEnableOption "Vaultwarden"; + options.homelab.apps.vaultwarden = { + enable = lib.mkEnableOption "Vaultwarden"; + port = lib.mkOption { + type = lib.types.int; + default = 10102; + description = "Vaultwarden WebUI port"; + }; + domain = lib.mkOption { + type = lib.types.string; + example = "https://vault.depeuter.dev"; + description = "Domain to configure Vaultwarden on"; + }; + name = lib.mkOption { + type = lib.types.string; + example = "Hugo's Vault"; + description = "Service name to use for invitations and mail"; + }; + }; config = lib.mkIf cfg.enable { homelab = { @@ -33,13 +50,16 @@ in { ''; }; - virtualisation.oci-containers.containers = { + virtualisation.oci-containers.containers = let + dbHostname = "vaultwarden-db"; + dbPort = 5432; + in { vaultwarden-db = { - hostname = "vaultwarden-db"; + hostname = dbHostname; image = "postgres:15.8-alpine"; autoStart = true; ports = [ - "5432:5432/tcp" + "${toString dbPort}:5432/tcp" ]; extraOptions = [ "--network=${networkName}" @@ -57,16 +77,16 @@ in { dataDir = "/data"; in { hostname = "vaultwarden"; - image = "vaultwarden/server:1.32.5-alpine"; + image = "vaultwarden/server:1.33.2-alpine"; autoStart = true; ports = [ - "10102:80/tcp" + "${toString cfg.port}:80/tcp" ]; extraOptions = [ "--network=${networkName}" ]; dependsOn = [ - "vaultwarden-db" + dbHostname ]; volumes = [ "vaultwarden:${dataDir}" @@ -115,7 +135,7 @@ in { ## Details: ## - https://docs.diesel.rs/2.1.x/diesel/pg/struct.PgConnection.html ## - https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING - DATABASE_URL = "postgresql://vaultwarden:ChangeMe@vaultwarden-db:5432/vaultwarden"; + DATABASE_URL = "postgresql://vaultwarden:ChangeMe@${dbHostname}:${toString dbPort}/vaultwarden"; ## Enable WAL for the DB ## Set to false to avoid enabling WAL during startup. @@ -244,7 +264,7 @@ in { ## For development # DOMAIN=http://localhost ## For public server - DOMAIN = "https://vault.depeuter.dev"; + DOMAIN = cfg.domain; ## For public server (URL with port number) # DOMAIN=https://vw.domain.tld:8443 ## For public server (URL with path) @@ -328,7 +348,7 @@ in { ## Invitations org admins to invite users, even when signups are disabled # INVITATIONS_ALLOWED=true ## Name shown in the invitation emails that don't come from a specific organization - INVITATION_ORG_NAME = "Hugo's Vault"; + INVITATION_ORG_NAME = cfg.name; ## The number of hours after which an organization invite token, emergency access invite token, ## email verification token and deletion request token will expire (must be at least 1) @@ -571,7 +591,7 @@ in { ## Note: if SMTP_USERNAME is specified, SMTP_PASSWORD is mandatory SMTP_HOST = "smtp.gmail.com"; SMTP_FROM = "vault@depeuter.dev"; - SMTP_FROM_NAME = "Hugo's Vault"; + SMTP_FROM_NAME = cfg.name; # SMTP_USERNAME=username # SMTP_PASSWORD=password # SMTP_TIMEOUT=15 From c541fa4e6e2d07dd7a4da538d4e35b4ce09285db Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 24 May 2025 20:04:26 +0200 Subject: [PATCH 09/31] feat: Calibre (Desktop + Web) --- modules/apps/calibre/default.nix | 171 +++++++++++++++++++++++++++++-- 1 file changed, 160 insertions(+), 11 deletions(-) diff --git a/modules/apps/calibre/default.nix b/modules/apps/calibre/default.nix index 6fddb81..fc7cd57 100644 --- a/modules/apps/calibre/default.nix +++ b/modules/apps/calibre/default.nix @@ -1,17 +1,166 @@ -{ config, lib, ... }: +{ config, lib, pkgs, ... }: let cfg = config.homelab.apps.calibre; -in { - options.homelab.apps.calibre.enable = lib.mkEnableOption "Calibre"; - config = lib.mkIf cfg.enable { - users.users.calibre = { - uid = lib.mkForce 3010; - isSystemUser = true; - group = config.users.groups.media.name; - home = "/var/empty"; - shell = null; - }; + PUID = toString config.users.users.calibre.uid; + PGID = toString config.users.groups.media.gid; + + networkName = "calibre"; +in { + options.homelab.apps.calibre = { + enable = lib.mkEnableOption "Calibre (Desktop + Web)"; + desktop = lib.mkEnableOption "Calibre Desktop (KasmVNC)"; + web = lib.mkEnableOption "Calibre Web"; }; + + config = lib.mkMerge [ + { + homelab.apps.calibre = lib.mkIf cfg.enable { + desktop = true; + web = true; + }; + } + + # Common + (lib.mkIf (cfg.desktop || cfg.web) { + homelab = { + users.media.enable = true; + virtualisation.containers.enable = true; + }; + + users.users.calibre = { + uid = lib.mkForce 3010; + isSystemUser = true; + group = config.users.groups.media.name; + home = "/var/empty"; + shell = null; + }; + + fileSystems."/srv/books" = { + device = "192.168.0.11:/mnt/SMALL/MEDIA/BOOKS"; + fsType = "nfs"; + options = [ + "rw" + "auto" + "nfsvers=4.2" + "rsize=1048576" "wsize=1048576" + "soft" + "timeo=600" "retrans=2" + "_netdev" "nosuid" "tcp" + ]; + }; + + # Make sure the Docker network exists. + systemd.services."docker-${networkName}-create-network" = { + requiredBy = [ + "docker-calibre.service" + ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then + ${pkgs.docker}/bin/docker network create ${networkName} + fi + ''; + }; + }) + + # Calibre desktop + { + fileSystems."/srv/calibre-config" = { + device = "192.168.0.11:/mnt/SMALL/CONFIG/CALIBRE"; + fsType = "nfs"; + options = [ + "rw" + "auto" + "nfsvers=4.2" + "rsize=1048576" "wsize=1048576" + "soft" + "timeo=600" "retrans=2" + "_netdev" "nosuid" "tcp" + ]; + }; + + virtualisation.oci-containers.containers.calibre = { + hostname = "calibre"; + image = "lscr.io/linuxserver/calibre:latest"; + autoStart = true; + ports = [ + # Open ports if you don't use Traefik + "9480:8080" # Calibre desktop GUI + #"9481:8181" # Calibre desktop GUI HTTPS + #"9581:8081" # Calibre webserver gui + ]; + extraOptions = [ + "--network=${networkName}" + + # syscalls are unkown to Docker + #"--security-opt" "seccomp=unconfined" + ]; + environment = { + inherit PUID PGID; + #UMASK = "022"; + + TZ = config.time.timeZone; + + #PASSWORD = ""; + #CLI_ARGS = ""; + }; + volumes = [ + "/srv/calibre-config:/config" + + "/srv/books:/media/books" + ]; + }; + } + + # Calibre Web + { + fileSystems."/srv/calibre-web-config" = { + device = "192.168.0.11:/mnt/SMALL/CONFIG/CALIBRE-WEB"; + fsType = "nfs"; + options = [ + "rw" + "auto" + "nfsvers=4.2" + "rsize=1048576" "wsize=1048576" + "soft" + "timeo=600" "retrans=2" + "_netdev" "nosuid" "tcp" + ]; + }; + + virtualisation.oci-containers.containers.calibre-web = { + hostname = "calibre-web"; + image = "lscr.io/linuxserver/calibre-web:latest"; + autoStart = true; + ports = [ + # Open ports if you don't use Traefik + "8083:8083" # Web UI + ]; + extraOptions = [ + "--network=${networkName}" + ]; + environment = { + inherit PUID PGID; + #UMASK = "022"; + + TZ = config.time.timeZone; + + # (x86-64 only) Adds the ability to perform ebook conversion + DOCKER_MODS = "linuxserver/mods:universal-calibre"; + # Allow Google Oauth + #OAUTHLIB_RELAX_TOKEN_SCOPE = "1"; + }; + volumes = [ + "/srv/calibre-web-config:/config" + + "/srv/books:/media/books" + ]; + }; + } + ]; } From 48fb68c2fd24a0ee2815402bd88a3290f83a290e Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sun, 25 May 2025 14:32:47 +0200 Subject: [PATCH 10/31] fix: Separate nfs config --- modules/apps/arr/default.nix | 31 +++----- modules/apps/plex/default.nix | 81 +++++++++------------ modules/fileSystems/default.nix | 5 ++ modules/fileSystems/media/default.nix | 5 ++ modules/fileSystems/media/video/default.nix | 42 +++++++++++ 5 files changed, 100 insertions(+), 64 deletions(-) create mode 100644 modules/fileSystems/default.nix create mode 100644 modules/fileSystems/media/default.nix create mode 100644 modules/fileSystems/media/video/default.nix diff --git a/modules/apps/arr/default.nix b/modules/apps/arr/default.nix index e45b1bf..2687e2a 100644 --- a/modules/apps/arr/default.nix +++ b/modules/apps/arr/default.nix @@ -38,6 +38,11 @@ in { sonarr.enable = true; }; + fileSystems.media.video = { + enable = true; + permissions = [ "read" "write" ]; + }; + virtualisation.containers.enable = lib.mkIf inUse true; }; @@ -139,20 +144,6 @@ in { "_netdev" "nosuid" "tcp" ]; }; - - "/srv/video" = { - device = "192.168.0.11:/mnt/SMALL/MEDIA/VIDEO"; - fsType = "nfs"; - options = [ - "rw" - "auto" - "nfsvers=4.2" - "soft" - "rsize=1048576" "wsize=1048576" - "timeo=600" "retrans=2" - "_netdev" "nosuid" "tcp" - ]; - }; }; # Make sure the Docker network exists. @@ -226,7 +217,9 @@ in { }; }; - virtualisation.oci-containers.containers = { + virtualisation.oci-containers.containers = let + videoHostPath = config.homelab.fileSystems.media.video.hostPath; + in { bazarr = let port = 6767; in lib.mkIf cfg.bazarr.enable { @@ -252,8 +245,8 @@ in { "/srv/bazarr-backup:/config/backup" - "/srv/video/Films:/media/movies" - "/srv/video/Series:/media/series" + "${videoHostPath}/Films:/media/movies" + "${videoHostPath}/Series:/media/series" ]; labels = { "traefik.enable" = "true"; @@ -374,7 +367,7 @@ in { "/srv/radarr-backup:/config/Backups" "/srv/torrent:/media/cache" - "/srv/video/Films:/media/movies" + "${videoHostPath}/Films:/media/movies" ]; labels = { "traefik.enable" = "true"; @@ -407,7 +400,7 @@ in { "/srv/sonarr-backup:/config/Backups" "/srv/torrent:/media/cache" - "/srv/video/Series:/media/series" + "${videoHostPath}/Series:/media/series" ]; labels = { "traefik.enable" = "true"; diff --git a/modules/apps/plex/default.nix b/modules/apps/plex/default.nix index 7e21745..b307b86 100644 --- a/modules/apps/plex/default.nix +++ b/modules/apps/plex/default.nix @@ -11,6 +11,7 @@ in { apps.enable = true; media.enable = true; }; + fileSystems.media.video.enable = true; virtualisation.containers.enable = true; }; @@ -25,53 +26,43 @@ in { shell = null; }; - fileSystems."/srv/video" = { - device = "192.168.0.11:/mnt/SMALL/MEDIA/VIDEO"; - fsType = "nfs"; - options = [ - "ro" - "nfsvers=4.2" - "async" "soft" - "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" - "nosuid" "tcp" + virtualisation.oci-containers.containers.plex = let + videoHostPath = config.homelab.fileSystems.media.video.hostPath; + in { + hostname = "plex"; + image = "plexinc/pms-docker:1.41.6.9685-d301f511a"; + autoStart = true; + ports = [ + "32400:32400/tcp" # Plex Media Server + "1900:1900/udp" # Plex DLNA Server + "32469:32469/tcp" # Plex DLNA Server + "32410:32410/udp" # GDM network discovery + "32412:32412/udp" # GDM network discovery + "32413:32413/udp" # GDM network discovery + "32414:32414/udp" # GDM network discovery + # "8324:8324/tcp" # Controlling Plex for Roku via Plex Companion ]; - }; + environment = { + #ADVERTISE_AP = "..."; # TODO Configure ip + ALLOWED_NETWORKS = "192.168.0.0/24,172.16.0.0/16"; + CHANGE_CONFIG_DIR_OWNERSHIP = "false"; + HOSTNAME = "Hugo-Plex"; + PLEX_CLAIM = "claim-d5MqsjMeCZrUF6oUvssr"; + PLEX_UID = toString config.users.users.plex.uid; + PLEX_GID = toString config.users.groups.media.gid; + TZ = config.time.timeZone; + }; + volumes = [ + # TODO Backup over NFS + "plex-config:/config" + "plex-transcode:/transcode" - virtualisation.oci-containers.containers = { - plex = { - hostname = "plex"; - image = "plexinc/pms-docker:1.41.0.8992-8463ad060"; - autoStart = true; - ports = [ - "32400:32400/tcp" # Plex Media Server - "1900:1900/udp" # Plex DLNA Server - "32469:32469/tcp" # Plex DLNA Server - "32410:32410/udp" # GDM network discovery - "32412:32412/udp" # GDM network discovery - "32413:32413/udp" # GDM network discovery - "32414:32414/udp" # GDM network discovery - # "8324:8324/tcp" # Controlling Plex for Roku via Plex Companion - ]; - environment = { - #ADVERTISE_AP = "..."; # TODO Configure ip - ALLOWED_NETWORKS = "192.168.0.0/24,172.16.0.0/16"; - CHANGE_CONFIG_DIR_OWNERSHIP = "false"; - HOSTNAME = "PlexServer"; - #PLEX_CLAIM = "..."; # TODO Add token - PLEX_UID = toString config.users.users.plex.uid; - PLEX_GID = toString config.users.groups.media.gid; - TZ = config.time.timeZone; - }; - volumes = [ - "plex-config:/var/lib/plexmediaserver" - "plex-transcode:/transcode" - "/srv/video:/data/video" - ]; - labels = { - "traefik.enable" = "true"; - "traefik.http.routers.plex.rule=" = "Host(`plex.depeuter.dev`)"; - "traefik.http.services.plex.loadbalancer.server.port" = "32400"; - }; + "${videoHostPath}:/data/video:ro" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.plex.rule" = "Host(`plex.depeuter.dev`)"; + "traefik.http.services.plex.loadbalancer.server.port" = "32400"; }; }; }; diff --git a/modules/fileSystems/default.nix b/modules/fileSystems/default.nix new file mode 100644 index 0000000..7c25689 --- /dev/null +++ b/modules/fileSystems/default.nix @@ -0,0 +1,5 @@ +{ + imports = [ + ./media + ]; +} diff --git a/modules/fileSystems/media/default.nix b/modules/fileSystems/media/default.nix new file mode 100644 index 0000000..41cb81f --- /dev/null +++ b/modules/fileSystems/media/default.nix @@ -0,0 +1,5 @@ +{ + imports = [ + ./video + ]; +} diff --git a/modules/fileSystems/media/video/default.nix b/modules/fileSystems/media/video/default.nix new file mode 100644 index 0000000..e46193c --- /dev/null +++ b/modules/fileSystems/media/video/default.nix @@ -0,0 +1,42 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.fileSystems.media.video; + + remotePath = "/mnt/SMALL/MEDIA/VIDEO"; + + maxPermissions = permissions: + if builtins.elem "write" permissions then "rw" + else "ro"; + permissionsOption = maxPermissions cfg.permissions; +in { + options.homelab.fileSystems.media.video = { + enable = lib.mkEnableOption "MEDIA/VIDEO dataset"; + hostPath = lib.mkOption { + type = lib.types.path; + default = "/srv/video"; + description = "Mountpath on host"; + }; + permissions = lib.mkOption { + type = lib.types.listOf (lib.types.enum [ "read" "write" ]); + default = [ "read" ]; + description = "Mount options permissions"; + }; + }; + + config = lib.mkIf cfg.enable { + fileSystems."${cfg.hostPath}" = { + device = "192.168.0.11:${remotePath}"; + fsType = "nfs"; + options = [ + permissionsOption + "auto" + "nfsvers=4.2" + "async" "soft" + "rsize=1048576" "wsize=1048576" + "timeo=600" "retry=50" "retrans=2" "actimeo=1800" "lookupcache=all" + "_netdev" "nosuid" "tcp" + ]; + }; + }; +} From c294e159e2a2fed0b1265aff92e18ea3e7007f5b Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Mon, 26 May 2025 22:38:29 +0200 Subject: [PATCH 11/31] feat: Basic recursive dns --- modules/apps/bind9/db.depeuter.dev | 16 ++++++++ modules/apps/bind9/default.nix | 54 +++++++++++++++++++++++++++ modules/apps/bind9/named.conf | 2 + modules/apps/bind9/named.conf.local | 4 ++ modules/apps/bind9/named.conf.options | 35 +++++++++++++++++ modules/apps/default.nix | 1 + 6 files changed, 112 insertions(+) create mode 100644 modules/apps/bind9/db.depeuter.dev create mode 100644 modules/apps/bind9/default.nix create mode 100644 modules/apps/bind9/named.conf create mode 100644 modules/apps/bind9/named.conf.local create mode 100644 modules/apps/bind9/named.conf.options diff --git a/modules/apps/bind9/db.depeuter.dev b/modules/apps/bind9/db.depeuter.dev new file mode 100644 index 0000000..fbd06c3 --- /dev/null +++ b/modules/apps/bind9/db.depeuter.dev @@ -0,0 +1,16 @@ +$TTL 604800 +@ IN SOA ns1.depeuter.dev. admin.depeuter.dev. ( + 5 ; Serial + 604800 ; Refresh + 86400 ; Retry + 2419200 ; Expire + 604800 ) ; Negative Cache TTL + +; name servers - NS records + IN NS ns1.depeuter.dev. +; IN NS ns2.depeuter.dev. + +ns1.depeuter.dev. IN A 192.168.0.91 +;ns1.depeuter.dev. IN A 192.158.0.X + +hugo.depeuter.dev. IN A 192.168.0.11 diff --git a/modules/apps/bind9/default.nix b/modules/apps/bind9/default.nix new file mode 100644 index 0000000..a2346c1 --- /dev/null +++ b/modules/apps/bind9/default.nix @@ -0,0 +1,54 @@ +{ config, lib, ... }: + +let + cfg = config.homelab.apps.bind9; +in { + options.homelab.apps.bind9.enable = lib.mkEnableOption "ISC BIND 9 (Docker)"; + + config = lib.mkIf cfg.enable { + homelab.virtualisation.containers.enable = true; + + environment.etc = { + "bind/named.conf" = { + source = ./named.conf; + mode = "0555"; + }; + "bind/named.conf.options" = { + source = ./named.conf.options; + mode = "0555"; + }; + "bind/named.conf.local" = { + source = ./named.conf.local; + mode = "0555"; + }; + "bind/zones/db.depeuter.dev" = { + source = ./db.depeuter.dev; + mode = "0555"; + }; + }; + + virtualisation.oci-containers.containers.bind9 = { + hostname = "bind9"; + #image = "internetsystemsconsortium/bind9:9.20"; # Current stable + image = "ubuntu/bind9"; # Current stable + autoStart = true; + ports = [ + "53:53/udp" + "53:53/tcp" + "953:953/tcp" + ]; + extraOptions = [ + ]; + environment = { + }; + volumes = [ + "/etc/bind:/etc/bind" # For configuration, your `named.conf` lives here + "bind9-cache:/var/cache/bind" + #"...:/var/lib/bind" # Secondary zones + "bind9-logs:/var/log" # Logfiles + ]; + labels = { + }; + }; + }; +} diff --git a/modules/apps/bind9/named.conf b/modules/apps/bind9/named.conf new file mode 100644 index 0000000..d301bd7 --- /dev/null +++ b/modules/apps/bind9/named.conf @@ -0,0 +1,2 @@ +include "/etc/bind/named.conf.options"; +include "/etc/bind/named.conf.local"; diff --git a/modules/apps/bind9/named.conf.local b/modules/apps/bind9/named.conf.local new file mode 100644 index 0000000..442eca9 --- /dev/null +++ b/modules/apps/bind9/named.conf.local @@ -0,0 +1,4 @@ +zone "depeuter.dev" { + type primary; + file "/etc/bind/zones/db.depeuter.dev"; +}; diff --git a/modules/apps/bind9/named.conf.options b/modules/apps/bind9/named.conf.options new file mode 100644 index 0000000..b05f4bf --- /dev/null +++ b/modules/apps/bind9/named.conf.options @@ -0,0 +1,35 @@ +http local { + endpoints { "/dns-query"; }; +}; + +acl bogusnets { +}; + +acl trusted { + 192.168.0.0/16; +}; + +options { + directory "/var/cache/bind"; + + version "not currently available"; + + listen-on { any; }; + listen-on-v6 { any; }; + listen-on tls ephemeral { any; }; + listen-on-v6 tls ephemeral { any; }; + listen-on tls ephemeral http local { any; }; + listen-on-v6 tls ephemeral http local { any; }; + + recursion yes; + forwarders { + 9.9.9.9; + 149.112.112.112; + }; + forward only; + + allow-query { any; }; + allow-recursion { any; }; + allow-transfer { none; }; + blackhole { bogusnets; }; +}; diff --git a/modules/apps/default.nix b/modules/apps/default.nix index 2d487e8..81c6a06 100644 --- a/modules/apps/default.nix +++ b/modules/apps/default.nix @@ -1,6 +1,7 @@ { imports = [ ./arr + ./bind9 ./calibre ./changedetection ./freshrss From d693c4a93b0aa75204465269d79d050cb0c0d1c5 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 26 Jun 2025 11:50:52 +0200 Subject: [PATCH 12/31] feat(calibre): Pin image versions --- modules/apps/calibre/default.nix | 64 ++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/modules/apps/calibre/default.nix b/modules/apps/calibre/default.nix index fc7cd57..aa00c89 100644 --- a/modules/apps/calibre/default.nix +++ b/modules/apps/calibre/default.nix @@ -6,24 +6,28 @@ let PUID = toString config.users.users.calibre.uid; PGID = toString config.users.groups.media.gid; + books = "/srv/books"; + calibre-config = "/srv/calibre-config"; + calibre-web-config = "/srv/calibre-web-config"; + networkName = "calibre"; in { options.homelab.apps.calibre = { - enable = lib.mkEnableOption "Calibre (Desktop + Web)"; - desktop = lib.mkEnableOption "Calibre Desktop (KasmVNC)"; - web = lib.mkEnableOption "Calibre Web"; + enable = lib.mkEnableOption "Calibre (Desktop + Web)"; + desktop.enable = lib.mkEnableOption "Calibre Desktop (KasmVNC)"; + web.enable = lib.mkEnableOption "Calibre Web"; }; config = lib.mkMerge [ { homelab.apps.calibre = lib.mkIf cfg.enable { - desktop = true; - web = true; + desktop.enable = true; + web.enable = true; }; } # Common - (lib.mkIf (cfg.desktop || cfg.web) { + (lib.mkIf (cfg.desktop.enable || cfg.web.enable) { homelab = { users.media.enable = true; virtualisation.containers.enable = true; @@ -37,7 +41,7 @@ in { shell = null; }; - fileSystems."/srv/books" = { + fileSystems."${books}" = { device = "192.168.0.11:/mnt/SMALL/MEDIA/BOOKS"; fsType = "nfs"; options = [ @@ -69,8 +73,8 @@ in { }) # Calibre desktop - { - fileSystems."/srv/calibre-config" = { + (lib.mkIf cfg.desktop.enable { + fileSystems."${calibre-config}" = { device = "192.168.0.11:/mnt/SMALL/CONFIG/CALIBRE"; fsType = "nfs"; options = [ @@ -84,13 +88,15 @@ in { ]; }; - virtualisation.oci-containers.containers.calibre = { + virtualisation.oci-containers.containers.calibre = let + innerPort = 8080; + in { hostname = "calibre"; - image = "lscr.io/linuxserver/calibre:latest"; + image = "lscr.io/linuxserver/calibre:8.5.0"; autoStart = true; ports = [ # Open ports if you don't use Traefik - "9480:8080" # Calibre desktop GUI + "9480:${toString innerPort}" # Calibre desktop GUI #"9481:8181" # Calibre desktop GUI HTTPS #"9581:8081" # Calibre webserver gui ]; @@ -110,16 +116,21 @@ in { #CLI_ARGS = ""; }; volumes = [ - "/srv/calibre-config:/config" + "${calibre-config}:/config" - "/srv/books:/media/books" + "${books}:/media/books" ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.calibre.rule" = "Host(`calibre.depeuter.dev`)"; + "traefik.http.services.calibre.loadbalancer.server.port" = toString innerPort; + }; }; - } + }) # Calibre Web - { - fileSystems."/srv/calibre-web-config" = { + (lib.mkIf cfg.web.enable { + fileSystems."${calibre-web-config}" = { device = "192.168.0.11:/mnt/SMALL/CONFIG/CALIBRE-WEB"; fsType = "nfs"; options = [ @@ -133,13 +144,15 @@ in { ]; }; - virtualisation.oci-containers.containers.calibre-web = { + virtualisation.oci-containers.containers.calibre-web = let + innerPort = 8083; + in { hostname = "calibre-web"; - image = "lscr.io/linuxserver/calibre-web:latest"; + image = "lscr.io/linuxserver/calibre-web:0.6.24"; autoStart = true; ports = [ # Open ports if you don't use Traefik - "8083:8083" # Web UI + "8083:${toString innerPort}" # Web UI ]; extraOptions = [ "--network=${networkName}" @@ -156,11 +169,16 @@ in { #OAUTHLIB_RELAX_TOKEN_SCOPE = "1"; }; volumes = [ - "/srv/calibre-web-config:/config" + "${calibre-web-config}:/config" - "/srv/books:/media/books" + "${books}:/media/books" ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.calibre-web.rule" = "Host(`books.depeuter.dev`)"; + "traefik.http.services.calibre-web.loadbalancer.server.port" = toString innerPort; + }; }; - } + }) ]; } From d0d6fac7ef9410ca0471d5c9f9bd0cc7554ad177 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Mon, 1 Sep 2025 18:06:25 +0200 Subject: [PATCH 13/31] chore(vaultwarden): Update image --- modules/apps/vaultwarden/default.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/apps/vaultwarden/default.nix b/modules/apps/vaultwarden/default.nix index a2f8d0a..4510299 100644 --- a/modules/apps/vaultwarden/default.nix +++ b/modules/apps/vaultwarden/default.nix @@ -54,7 +54,7 @@ in { dbHostname = "vaultwarden-db"; dbPort = 5432; in { - vaultwarden-db = { + vaultwardenDb = { hostname = dbHostname; image = "postgres:15.8-alpine"; autoStart = true; @@ -77,7 +77,7 @@ in { dataDir = "/data"; in { hostname = "vaultwarden"; - image = "vaultwarden/server:1.33.2-alpine"; + image = "vaultwarden/server:1.34.3-alpine"; autoStart = true; ports = [ "${toString cfg.port}:80/tcp" @@ -86,7 +86,7 @@ in { "--network=${networkName}" ]; dependsOn = [ - dbHostname + "vaultwardenDb" ]; volumes = [ "vaultwarden:${dataDir}" From ac47ec4689a227d71c928250589409be0db60ca9 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 4 Sep 2025 10:39:02 +0200 Subject: [PATCH 14/31] fix(traefik): Add proxy network --- modules/apps/arr/default.nix | 8 +++ modules/apps/traefik/default.nix | 90 ++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 modules/apps/traefik/default.nix diff --git a/modules/apps/arr/default.nix b/modules/apps/arr/default.nix index 2687e2a..de696f5 100644 --- a/modules/apps/arr/default.nix +++ b/modules/apps/arr/default.nix @@ -4,6 +4,8 @@ let cfg = config.homelab.apps.arr; networkName = "arrStack"; + proxyNet = config.homelab.apps.traefik.sharedNetworkName; + appNames = [ "bazarr" "lidarr" "prowlarr" "qbittorrent" "radarr" "sonarr" ]; inUse = builtins.any (app: cfg.${app}.enable) appNames; @@ -233,6 +235,7 @@ in { ]; extraOptions = [ "--network=${networkName}" + "--network=${proxyNet}" ]; environment = { PUID = toString config.users.users.bazarr.uid; @@ -267,6 +270,7 @@ in { ]; extraOptions = [ "--network=${networkName}" + "--network=${proxyNet}" ]; environment = { PUID = toString config.users.users.lidarr.uid; @@ -293,6 +297,7 @@ in { ]; extraOptions = [ "--network=${networkName}" + "--network=${proxyNet}" ]; environment = { PUID = toString config.users.users.prowlarr.uid; @@ -324,6 +329,7 @@ in { ]; extraOptions = [ "--network=${networkName}" + "--network=${proxyNet}" ]; environment = { PUID = toString config.users.users.qbittorrent.uid; @@ -355,6 +361,7 @@ in { ]; extraOptions = [ "--network=${networkName}" + "--network=${proxyNet}" ]; environment = { PUID = toString config.users.users.radarr.uid; @@ -388,6 +395,7 @@ in { ]; extraOptions = [ "--network=${networkName}" + "--network=${proxyNet}" ]; environment = { PUID = toString config.users.users.sonarr.uid; diff --git a/modules/apps/traefik/default.nix b/modules/apps/traefik/default.nix new file mode 100644 index 0000000..7f6ce38 --- /dev/null +++ b/modules/apps/traefik/default.nix @@ -0,0 +1,90 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.homelab.apps.traefik; + + port = 8080; +in { + options.homelab.apps.traefik = { + enable = lib.mkEnableOption "Traefik Reverse Proxy"; + sharedNetworkName = lib.mkOption { + type = lib.types.str; + default = "traefik"; + description = "The name of the shared network to connect the container to."; + }; + }; + + config = lib.mkIf cfg.enable { + homelab.virtualisation.containers.enable = true; + + # Make sure the Docker network exists. + systemd.services."docker-${cfg.sharedNetworkName}-create-network" = { + description = "Create Docker network for ${cfg.sharedNetworkName}"; + requiredBy = [ + "docker-traefik.service" + ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if ! ${pkgs.docker}/bin/docker network ls | grep -q ${cfg.sharedNetworkName}; then + ${pkgs.docker}/bin/docker network create ${cfg.sharedNetworkName} + fi + ''; + }; + + virtualisation.oci-containers.containers.traefik = { + hostname = "traefik"; + image = "traefik:v3.4.3"; + autoStart = true; + ports = [ + "80:80/tcp" + "443:443/tcp" + "${toString port}:${toString port}/tcp" # Web UI (enabled by --api.insecure=true) + ]; + extraOptions = [ + "--network=${cfg.sharedNetworkName}" + ]; + environmentFiles = [ + /home/admin/.cloudflare.secret + ]; + cmd = [ + "--api.insecure=true" + + # Add Docker provider + "--providers.docker=true" + "--providers.docker.exposedByDefault=false" + + # Add web entrypoint + "--entrypoints.web.address=:80/tcp" + "--entrypoints.web.http.redirections.entrypoint.to=websecure" + "--entrypoints.web.http.redirections.entrypoint.scheme=https" + + # Add websecure entrypoint + "--entrypoints.websecure.address=:443/tcp" + "--entrypoints.websecure.http.tls=true" + "--entrypoints.websecure.http.tls.certResolver=letsencrypt" + "--entrypoints.websecure.http.tls.domains[0].main=depeuter.dev" + "--entrypoints.websecure.http.tls.domains[0].sans=*.depeuter.dev" + "--entrypoints.websecure.http.tls.domains[1].sans=*.${config.networking.hostName}.depeuter.dev" + + # Certificates + "--certificatesresolvers.letsencrypt.acme.dnschallenge=true" + "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare" + "--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be" + "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + ]; + volumes = [ + "letsencryp:/letsencrypt" + + "/var/run/docker.sock:/var/run/docker.sock:ro" + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.traefik.rule" = "Host(`traefik.${config.networking.hostName}.depeuter.dev`)"; + "traefik.http.services.traefik.loadbalancer.server.port" = toString port; + }; + }; + }; +} From a7f56a7cf73b7a3c8f385e983b6d6af25ae702db Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 4 Sep 2025 10:46:41 +0200 Subject: [PATCH 15/31] fix(traefik): Specify network --- modules/apps/arr/default.nix | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/modules/apps/arr/default.nix b/modules/apps/arr/default.nix index de696f5..cedd01e 100644 --- a/modules/apps/arr/default.nix +++ b/modules/apps/arr/default.nix @@ -253,6 +253,7 @@ in { ]; labels = { "traefik.enable" = "true"; + "traefik.docker.network" = proxyNet; "traefik.http.routers.bazarr.rule" = "Host(`bazarr.depeuter.dev`)"; "traefik.http.services.bazarr.loadbalancer.server.port" = toString port; }; @@ -283,6 +284,12 @@ in { # TODO Fix path "/srv/lidarr-backup:/media/Backups" ]; + labels = { + "traefik.enable" = "true"; + "traefik.docker.network" = proxyNet; + "traefik.http.routers.lidarr.rule" = "Host(`lidarr.depeuter.dev`)"; + "traefik.http.services.lidarr.loadbalancer.server.port" = toString port; + }; }; prowlarr = let @@ -311,6 +318,7 @@ in { ]; labels = { "traefik.enable" = "true"; + "traefik.docker.network" = proxyNet; "traefik.http.routers.prowlarr.rule" = "Host(`prowlarr.depeuter.dev`)"; "traefik.http.services.prowlarr.loadbalancer.server.port" = toString port; }; @@ -344,6 +352,7 @@ in { ]; labels = { "traefik.enable" = "true"; + "traefik.docker.network" = proxyNet; "traefik.http.routers.qbittorrent.rule" = "Host(`qb.depeuter.dev`)"; "traefik.http.services.qbittorrent.loadbalancer.server.port" = toString port; }; @@ -378,6 +387,7 @@ in { ]; labels = { "traefik.enable" = "true"; + "traefik.docker.network" = proxyNet; "traefik.http.routers.radarr.rule" = "Host(`radarr.depeuter.dev`)"; "traefik.http.services.radarr.loadbalancer.server.port" = toString port; }; @@ -412,6 +422,7 @@ in { ]; labels = { "traefik.enable" = "true"; + "traefik.docker.network" = proxyNet; "traefik.http.routers.sonarr.rule" = "Host(`sonarr.depeuter.dev`)"; "traefik.http.services.sonarr.loadbalancer.server.port" = toString port; }; From badc78ece6eca01060d06a1d5fece1353452578d Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 5 Sep 2025 10:24:55 +0200 Subject: [PATCH 16/31] chore: Add new host --- flake.nix | 1 + hosts/ProductionArr/default.nix | 44 +++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 hosts/ProductionArr/default.nix diff --git a/flake.nix b/flake.nix index 48c91de..44d63c2 100644 --- a/flake.nix +++ b/flake.nix @@ -41,6 +41,7 @@ Binnenpost.modules = [ ./hosts/Binnenpost ]; Production.modules = [ ./hosts/Production ]; ProductionGPU.modules = [ ./hosts/ProductionGPU ]; + ProductionArr.modules = [ ./hosts/ProductionArr ]; ACE.modules = [ ./hosts/ACE ]; Template.modules = [ ./hosts/Template ]; diff --git a/hosts/ProductionArr/default.nix b/hosts/ProductionArr/default.nix new file mode 100644 index 0000000..3a430d9 --- /dev/null +++ b/hosts/ProductionArr/default.nix @@ -0,0 +1,44 @@ +{ config, pkgs, lib, system, ... }: + +{ + config = { + homelab = { + virtualisation.guest.enable = true; + }; + + networking = { + hostId = "aaaa2300"; + domain = "roxanne.depeuter.dev"; + + useDHCP = false; + + enableIPv6 = true; + + defaultGateway = { + address = "192.168.0.1"; + interface = "ens18"; + }; + + # Open ports in the firewall. + firewall = { + enable = true; + }; + + interfaces.ens18 = { + ipv4.addresses = [ + { + address = "192.168.0.33"; + prefixLength = 24; + } + ]; + }; + + nameservers = [ + "1.1.1.1" # Cloudflare + "1.0.0.1" # Cloudflare + ]; + }; + + system.stateVersion = "24.05"; + }; +} From d9e020a0c7ab3fff27460af7d40bf16632e46794 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 5 Sep 2025 10:28:00 +0200 Subject: [PATCH 17/31] chore(arr): Move to new host --- hosts/Development/default.nix | 6 +++--- hosts/ProductionArr/default.nix | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/hosts/Development/default.nix b/hosts/Development/default.nix index da995f8..b2237b7 100644 --- a/hosts/Development/default.nix +++ b/hosts/Development/default.nix @@ -4,9 +4,9 @@ config = { homelab = { apps = { - arr = { - qbittorrent.enable = true; - }; + bind9.enable = true; + traefik.enable = true; + plex.enable = true; }; virtualisation.guest.enable = true; }; diff --git a/hosts/ProductionArr/default.nix b/hosts/ProductionArr/default.nix index 3a430d9..929750a 100644 --- a/hosts/ProductionArr/default.nix +++ b/hosts/ProductionArr/default.nix @@ -3,6 +3,16 @@ { config = { homelab = { + apps = { + arr = { + bazarr.enable = true; + prowlarr.enable = true; + qbittorrent.enable = true; + radarr.enable = true; + sonarr.enable = true; + }; + traefik.enable = true; + }; virtualisation.guest.enable = true; }; From 9f5c9ecdd9d51cc512c275874e1c4e607d0dea8a Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 5 Sep 2025 10:30:57 +0200 Subject: [PATCH 18/31] chore(arr): Remove lidarr config --- hosts/ProductionArr/default.nix | 8 +---- modules/apps/arr/default.nix | 59 +-------------------------------- 2 files changed, 2 insertions(+), 65 deletions(-) diff --git a/hosts/ProductionArr/default.nix b/hosts/ProductionArr/default.nix index 929750a..ff4f4c2 100644 --- a/hosts/ProductionArr/default.nix +++ b/hosts/ProductionArr/default.nix @@ -4,13 +4,7 @@ config = { homelab = { apps = { - arr = { - bazarr.enable = true; - prowlarr.enable = true; - qbittorrent.enable = true; - radarr.enable = true; - sonarr.enable = true; - }; + arr.enable = true; traefik.enable = true; }; virtualisation.guest.enable = true; diff --git a/modules/apps/arr/default.nix b/modules/apps/arr/default.nix index cedd01e..0ce41a3 100644 --- a/modules/apps/arr/default.nix +++ b/modules/apps/arr/default.nix @@ -6,7 +6,7 @@ let networkName = "arrStack"; proxyNet = config.homelab.apps.traefik.sharedNetworkName; - appNames = [ "bazarr" "lidarr" "prowlarr" "qbittorrent" "radarr" "sonarr" ]; + appNames = [ "bazarr" "prowlarr" "qbittorrent" "radarr" "sonarr" ]; inUse = builtins.any (app: cfg.${app}.enable) appNames; PGID = toString config.users.groups.media.gid; @@ -16,7 +16,6 @@ in { enable = lib.mkEnableOption "Arr Stack using Docker"; bazarr.enable = lib.mkEnableOption "Bazarr using Docker"; - lidarr.enable = lib.mkEnableOption "Lidarr using Docker"; prowlarr.enable = lib.mkEnableOption "Prowlarr using Docker"; qbittorrent.enable = lib.mkEnableOption "qBittorrent using Docker"; radarr.enable = lib.mkEnableOption "Radarr using Docker"; @@ -33,7 +32,6 @@ in { # "Master switch": Enable all apps. apps.arr = lib.mkIf cfg.enable { bazarr.enable = true; - lidarr.enable = true; prowlarr.enable = true; qbittorrent.enable = true; radarr.enable = true; @@ -63,20 +61,6 @@ in { ]; }; - "/srv/lidarr-backup" = lib.mkIf cfg.lidarr.enable { - device = "192.168.0.11:/mnt/BIG/BACKUP/LIDARR"; - fsType = "nfs"; - options = [ - "rw" - "auto" - "nfsvers=4.2" - "rsize=1048576" "wsize=1048576" - "hard" - "timeo=600" "retrans=2" - "_netdev" "nosuid" "tcp" - ]; - }; - "/srv/prowlarr-backup" = lib.mkIf cfg.prowlarr.enable { device = "192.168.0.11:/mnt/BIG/BACKUP/PROWLARR"; fsType = "nfs"; @@ -153,7 +137,6 @@ in { description = "Create Docker network for ${networkName}"; requiredBy = [ "docker-bazarr.service" - "docker-lidarr.service" "docker-prowlarr.service" "docker-qbittorrent.service" "docker-radarr.service" @@ -179,13 +162,6 @@ in { home = "/var/empty"; shell = null; }; - lidarr = lib.mkIf cfg.lidarr.enable { - uid = lib.mkForce 3002; - isSystemUser = true; - group = config.users.groups.media.name; - home = "/var/empty"; - shell = null; - }; prowlarr = lib.mkIf cfg.prowlarr.enable { uid = lib.mkForce 3004; isSystemUser = true; @@ -259,39 +235,6 @@ in { }; }; - lidarr = let - port = 8686; - in lib.mkIf cfg.lidarr.enable { - hostname = "lidarr"; - image = "ghcr.io/hotio/lidarr:release-2.5.3.4341"; - autoStart = true; - ports = [ - # Open ports if you don't use Traefik - # "${toString port}:${toString port}/tcp" - ]; - extraOptions = [ - "--network=${networkName}" - "--network=${proxyNet}" - ]; - environment = { - PUID = toString config.users.users.lidarr.uid; - inherit PGID UMASK; - TZ = config.time.timeZone; - }; - volumes = [ - "lidarr-config:/config" - - # TODO Fix path - "/srv/lidarr-backup:/media/Backups" - ]; - labels = { - "traefik.enable" = "true"; - "traefik.docker.network" = proxyNet; - "traefik.http.routers.lidarr.rule" = "Host(`lidarr.depeuter.dev`)"; - "traefik.http.services.lidarr.loadbalancer.server.port" = toString port; - }; - }; - prowlarr = let port = 9696; in lib.mkIf cfg.prowlarr.enable { From 5f027ca12a6f425fd210172caf6fbf279b394f1e Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 5 Sep 2025 10:35:46 +0200 Subject: [PATCH 19/31] fix(bind9): Update arr records --- modules/apps/bind9/db.depeuter.dev | 53 +++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/modules/apps/bind9/db.depeuter.dev b/modules/apps/bind9/db.depeuter.dev index fbd06c3..72f3825 100644 --- a/modules/apps/bind9/db.depeuter.dev +++ b/modules/apps/bind9/db.depeuter.dev @@ -1,16 +1,45 @@ $TTL 604800 -@ IN SOA ns1.depeuter.dev. admin.depeuter.dev. ( - 5 ; Serial - 604800 ; Refresh - 86400 ; Retry - 2419200 ; Expire - 604800 ) ; Negative Cache TTL +@ IN SOA ns1 admin ( + 15 ; Serial + 604800 ; Refresh + 86400 ; Retry + 2419200 ; Expire + 604800 ) ; Negative Cache TTL -; name servers - NS records - IN NS ns1.depeuter.dev. -; IN NS ns2.depeuter.dev. +; Name servers - NS records + IN NS ns1 +; IN NS ns2 -ns1.depeuter.dev. IN A 192.168.0.91 -;ns1.depeuter.dev. IN A 192.158.0.X +ns1 IN A 192.168.0.91 +;ns2 IN A 192.158.0.X -hugo.depeuter.dev. IN A 192.168.0.11 +; Hostnames +hugo.kmtl IN A 192.168.0.11 + +ingress.kmtl IN A 192.168.0.10 +ingress.kmtl IN AAAA fe80::be24:11ff:fed6:842a + +; Core services +cloud IN A 192.168.0.10 +git IN A 78.23.37.117 +home IN A 192.168.0.10 +jelly IN CNAME ingress.kmtl +vault IN A 192.168.0.10 + +; Production VM +books IN A 192.168.0.31 +calibre IN A 192.168.0.31 + +; Production VM - Arr +bazarr IN A 192.168.0.33 +prowlarr IN A 192.168.0.33 +qb IN A 192.168.0.33 +radarr IN A 192.168.0.33 +sonarr IN A 192.168.0.33 + +; Development VM +plex IN A 192.168.0.91 + +; Catchalls +*.production IN A 192.168.0.31 +*.development IN A 192.168.0.91 From 74e4e8dcd6e76cbbcc77dd542319d2882e5f9661 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 5 Sep 2025 14:19:34 +0200 Subject: [PATCH 20/31] feat(arr): Add exposePorts option --- modules/apps/arr/default.nix | 80 ++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/modules/apps/arr/default.nix b/modules/apps/arr/default.nix index 0ce41a3..5cff39c 100644 --- a/modules/apps/arr/default.nix +++ b/modules/apps/arr/default.nix @@ -14,12 +14,53 @@ let in { options.homelab.apps.arr = { enable = lib.mkEnableOption "Arr Stack using Docker"; + exposePorts = lib.mkOption { + type = lib.types.bool; + description = "Expose all app ports"; + # Only expose ports by default if Traefik is not in use. + default = ! config.homelab.apps.traefik.enable; + }; - bazarr.enable = lib.mkEnableOption "Bazarr using Docker"; - prowlarr.enable = lib.mkEnableOption "Prowlarr using Docker"; - qbittorrent.enable = lib.mkEnableOption "qBittorrent using Docker"; - radarr.enable = lib.mkEnableOption "Radarr using Docker"; - sonarr.enable = lib.mkEnableOption "Sonarr using Docker"; + bazarr = { + enable = lib.mkEnableOption "Bazarr using Docker"; + exposePorts = lib.mkOption { + type = lib.types.bool; + description = "Expose Bazarr port"; + default = cfg.exposePorts; + }; + }; + prowlarr = { + enable = lib.mkEnableOption "Prowlarr using Docker"; + exposePorts = lib.mkOption { + type = lib.types.bool; + description = "Expose Prowlarr port"; + default = cfg.exposePorts; + }; + }; + qbittorrent = { + enable = lib.mkEnableOption "qBittorrent using Docker"; + exposePorts = lib.mkOption { + type = lib.types.bool; + description = "Expose qBittorrent port"; + default = cfg.exposePorts; + }; + }; + radarr = { + enable = lib.mkEnableOption "Radarr using Docker"; + exposePorts = lib.mkOption { + type = lib.types.bool; + description = "Expose Radarr port"; + default = cfg.exposePorts; + }; + }; + sonarr = { + enable = lib.mkEnableOption "Sonarr using Docker"; + exposePorts = lib.mkOption { + type = lib.types.bool; + description = "Expose Sonarr port"; + default = cfg.exposePorts; + }; + }; }; config = { @@ -204,10 +245,9 @@ in { hostname = "bazarr"; image = "ghcr.io/hotio/bazarr:release-1.4.4"; autoStart = true; - ports = [ - # Open ports if you don't use Traefik - # "${toString port}:${toString port}/tcp" - # "${toString port}:${toString port}/udp" + ports = lib.mkIf cfg.bazarr.exposePorts [ + "${toString port}:${toString port}/tcp" + "${toString port}:${toString port}/udp" ]; extraOptions = [ "--network=${networkName}" @@ -241,9 +281,8 @@ in { hostname = "prowlarr"; image = "ghcr.io/hotio/prowlarr:release-1.23.1.4708"; autoStart = true; - ports = [ - # Open ports if you don't use Traefik - # "${toString port}:${toString port}/tcp" + ports = lib.mkIf cfg.prowlarr.exposePorts [ + "${toString port}:${toString port}/tcp" ]; extraOptions = [ "--network=${networkName}" @@ -273,10 +312,9 @@ in { hostname = "qbittorrent"; image = "ghcr.io/hotio/qbittorrent:release-4.6.7"; autoStart = true; - ports = [ - # Open ports if you don't use Traefik - # "${toString port}:${toString port}/tcp" - # "${toString port}:${toString port}/udp" + ports = lib.mkIf cfg.qbittorrent.exposePorts [ + "${toString port}:${toString port}/tcp" + "${toString port}:${toString port}/udp" ]; extraOptions = [ "--network=${networkName}" @@ -307,9 +345,8 @@ in { hostname = "radarr"; image = "ghcr.io/hotio/radarr:release-5.9.1.9070"; autoStart = true; - ports = [ - # Open ports if you don't use Traefik - # "${toString port}:${toString port}/tcp" + ports = lib.mkIf cfg.radarr.exposePorts [ + "${toString port}:${toString port}/tcp" ]; extraOptions = [ "--network=${networkName}" @@ -342,9 +379,8 @@ in { hostname = "sonarr"; image = "ghcr.io/hotio/sonarr:release-4.0.9.2244"; autoStart = true; - ports = [ - # Open ports if you don't use Traefik - # "${toString port}:${toString port}/tcp" + ports = lib.mkIf cfg.sonarr.exposePorts [ + "${toString port}:${toString port}/tcp" ]; extraOptions = [ "--network=${networkName}" From 07a97f360c3177bf518d900bada88fbd1d7f2060 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 11 Sep 2025 11:27:37 +0200 Subject: [PATCH 21/31] chore(arr): Update images --- modules/apps/arr/default.nix | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/apps/arr/default.nix b/modules/apps/arr/default.nix index 5cff39c..3b05429 100644 --- a/modules/apps/arr/default.nix +++ b/modules/apps/arr/default.nix @@ -243,7 +243,7 @@ in { port = 6767; in lib.mkIf cfg.bazarr.enable { hostname = "bazarr"; - image = "ghcr.io/hotio/bazarr:release-1.4.4"; + image = "ghcr.io/hotio/bazarr:release-1.5.2"; autoStart = true; ports = lib.mkIf cfg.bazarr.exposePorts [ "${toString port}:${toString port}/tcp" @@ -279,7 +279,7 @@ in { port = 9696; in lib.mkIf cfg.prowlarr.enable { hostname = "prowlarr"; - image = "ghcr.io/hotio/prowlarr:release-1.23.1.4708"; + image = "ghcr.io/hotio/prowlarr:release-2.0.5.5160"; autoStart = true; ports = lib.mkIf cfg.prowlarr.exposePorts [ "${toString port}:${toString port}/tcp" @@ -310,7 +310,7 @@ in { port = 10095; in lib.mkIf cfg.qbittorrent.enable { hostname = "qbittorrent"; - image = "ghcr.io/hotio/qbittorrent:release-4.6.7"; + image = "ghcr.io/hotio/qbittorrent:release-5.1.2"; autoStart = true; ports = lib.mkIf cfg.qbittorrent.exposePorts [ "${toString port}:${toString port}/tcp" @@ -343,7 +343,7 @@ in { port = 7878; in lib.mkIf cfg.radarr.enable { hostname = "radarr"; - image = "ghcr.io/hotio/radarr:release-5.9.1.9070"; + image = "ghcr.io/hotio/radarr:release-5.28.0.10205"; autoStart = true; ports = lib.mkIf cfg.radarr.exposePorts [ "${toString port}:${toString port}/tcp" @@ -377,7 +377,7 @@ in { port = 8989; in lib.mkIf cfg.sonarr.enable { hostname = "sonarr"; - image = "ghcr.io/hotio/sonarr:release-4.0.9.2244"; + image = "ghcr.io/hotio/sonarr:release-4.0.15.2941"; autoStart = true; ports = lib.mkIf cfg.sonarr.exposePorts [ "${toString port}:${toString port}/tcp" From cfee4fd83541457971bbbf93b38c001f725bab01 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 11 Sep 2025 12:09:26 +0200 Subject: [PATCH 22/31] fix(calibre): Specify proxy network --- hosts/Production/default.nix | 5 ++++- modules/apps/calibre/default.nix | 5 +++++ modules/apps/default.nix | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/hosts/Production/default.nix b/hosts/Production/default.nix index cd929ff..9bb565d 100644 --- a/hosts/Production/default.nix +++ b/hosts/Production/default.nix @@ -3,7 +3,10 @@ { config = { homelab = { - apps.changedetection.enable = true; + apps = { + calibre.enable = true; + traefik.enable = true; + }; virtualisation.guest.enable = true; }; diff --git a/modules/apps/calibre/default.nix b/modules/apps/calibre/default.nix index aa00c89..e18ecb9 100644 --- a/modules/apps/calibre/default.nix +++ b/modules/apps/calibre/default.nix @@ -11,6 +11,7 @@ let calibre-web-config = "/srv/calibre-web-config"; networkName = "calibre"; + proxyNet = config.homelab.apps.traefik.sharedNetworkName; in { options.homelab.apps.calibre = { enable = lib.mkEnableOption "Calibre (Desktop + Web)"; @@ -102,6 +103,7 @@ in { ]; extraOptions = [ "--network=${networkName}" + "--network=${proxyNet}" # syscalls are unkown to Docker #"--security-opt" "seccomp=unconfined" @@ -122,6 +124,7 @@ in { ]; labels = { "traefik.enable" = "true"; + "traefik.docker.network" = proxyNet; "traefik.http.routers.calibre.rule" = "Host(`calibre.depeuter.dev`)"; "traefik.http.services.calibre.loadbalancer.server.port" = toString innerPort; }; @@ -156,6 +159,7 @@ in { ]; extraOptions = [ "--network=${networkName}" + "--network=${proxyNet}" ]; environment = { inherit PUID PGID; @@ -175,6 +179,7 @@ in { ]; labels = { "traefik.enable" = "true"; + "traefik.docker.network" = proxyNet; "traefik.http.routers.calibre-web.rule" = "Host(`books.depeuter.dev`)"; "traefik.http.services.calibre-web.loadbalancer.server.port" = toString innerPort; }; diff --git a/modules/apps/default.nix b/modules/apps/default.nix index 81c6a06..7c8b8f8 100644 --- a/modules/apps/default.nix +++ b/modules/apps/default.nix @@ -10,6 +10,7 @@ ./plex ./speedtest ./technitium-dns + ./traefik ./vaultwarden ]; } From 59f721f4d7d3d50fbfe68312ce19b820658930ad Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 11 Sep 2025 12:18:10 +0200 Subject: [PATCH 23/31] chore(calibre): Update images --- modules/apps/calibre/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/apps/calibre/default.nix b/modules/apps/calibre/default.nix index e18ecb9..bddf5c8 100644 --- a/modules/apps/calibre/default.nix +++ b/modules/apps/calibre/default.nix @@ -93,7 +93,7 @@ in { innerPort = 8080; in { hostname = "calibre"; - image = "lscr.io/linuxserver/calibre:8.5.0"; + image = "lscr.io/linuxserver/calibre:v8.10.0-ls354"; autoStart = true; ports = [ # Open ports if you don't use Traefik @@ -151,7 +151,7 @@ in { innerPort = 8083; in { hostname = "calibre-web"; - image = "lscr.io/linuxserver/calibre-web:0.6.24"; + image = "lscr.io/linuxserver/calibre-web:0.6.25-ls346"; autoStart = true; ports = [ # Open ports if you don't use Traefik From f1ba0a98e846ff62d03c2f2310e67704dae14800 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Wed, 1 Oct 2025 16:33:55 +0200 Subject: [PATCH 24/31] Updates --- flake.lock | 12 ++--- flake.nix | 8 ++- hosts/Ingress/default.nix | 34 ++++++++++-- hosts/Niko/default.nix | 77 +++------------------------- hosts/ProductionGPU/default.nix | 8 +-- hosts/Testing/default.nix | 5 +- modules/apps/arr/default.nix | 2 +- modules/apps/freshrss/default.nix | 27 ++++++++-- modules/apps/gitea/default.nix | 4 +- modules/apps/jellyfin/default.nix | 24 ++++----- modules/default.nix | 1 + modules/services/actions/default.nix | 2 +- 12 files changed, 95 insertions(+), 109 deletions(-) diff --git a/flake.lock b/flake.lock index e3284fd..78e4127 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1735291276, - "narHash": "sha256-NYVcA06+blsLG6wpAbSPTCyLvxD/92Hy4vlY9WxFI1M=", + "lastModified": 1756787288, + "narHash": "sha256-rw/PHa1cqiePdBxhF66V7R+WAP8WekQ0mCDG4CFqT8Y=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "634fd46801442d760e09493a794c4f15db2d0cbb", + "rev": "d0fc30899600b9b3466ddb260fd83deb486c32f1", "type": "github" }, "original": { @@ -62,11 +62,11 @@ ] }, "locked": { - "lastModified": 1722363685, - "narHash": "sha256-XCf2PIAT6lH7BwytgioPmVf/wkzXjSKScC4KzcZgb64=", + "lastModified": 1738591040, + "narHash": "sha256-4WNeriUToshQ/L5J+dTSWC5OJIwT39SEP7V7oylndi8=", "owner": "gytis-ivaskevicius", "repo": "flake-utils-plus", - "rev": "6b10f51ff73a66bb29f3bc8151a59d217713f496", + "rev": "afcb15b845e74ac5e998358709b2b5fe42a948d1", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 44d63c2..7701f3a 100644 --- a/flake.nix +++ b/flake.nix @@ -32,18 +32,24 @@ }; hosts = { - Niko.modules = [ ./hosts/Niko ]; + # Physical hosts + Niko.modules = [ ./hosts/Niko ]; + # Virtual machines + + # Single-service Ingress.modules = [ ./hosts/Ingress ]; Gitea.modules = [ ./hosts/Gitea ]; Vaultwarden.modules = [ ./hosts/Vaultwarden ]; + # Production multi-service Binnenpost.modules = [ ./hosts/Binnenpost ]; Production.modules = [ ./hosts/Production ]; ProductionGPU.modules = [ ./hosts/ProductionGPU ]; ProductionArr.modules = [ ./hosts/ProductionArr ]; ACE.modules = [ ./hosts/ACE ]; + # Others Template.modules = [ ./hosts/Template ]; Development.modules = [ ./hosts/Development ]; Testing.modules = [ ./hosts/Testing ]; diff --git a/hosts/Ingress/default.nix b/hosts/Ingress/default.nix index 63e3ced..68cdcfe 100644 --- a/hosts/Ingress/default.nix +++ b/hosts/Ingress/default.nix @@ -59,6 +59,7 @@ prefixLength = 24; }; "cloud.depeuter.dev" = { }; "git.depeuter.dev" = { }; + "home.depeuter.dev" = { }; "jelly.depeuter.dev" = { }; "vault.depeuter.dev" = { }; }; @@ -136,10 +137,27 @@ prefixLength = 24; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ''; }; - "calendar.depeuter.dev".locations."/".return = "301 https://cloud.depeuter.dev/apps/calendar"; + "calendar.depeuter.dev" = { + useACMEHost = "depeuter.dev"; + locations."/".return = "301 https://cloud.depeuter.dev/apps/calendar"; + }; "tasks.depeuter.dev".locations."/".return = "301 https://cloud.depeuter.dev/apps/tasks"; "notes.depeuter.dev".locations."/".return = "301 https://cloud.depeuter.dev/apps/notes"; + "home.depeuter.dev" = { + enableACME = true; + forceSSL = true; + locations."/" = { + proxyPass = "http://192.168.0.21:8123"; + extraConfig = '' + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + ''; + }; + }; + "jelly.depeuter.dev" = { enableACME = true; forceSSL = true; @@ -176,7 +194,7 @@ prefixLength = 24; }; }; extraConfig = '' - client_max_body_size 20M; + client_max_body_size 512M; # Security / XSS Mitigation Headers # NOTE: X-Frame-Options may cause issues with the webOS app @@ -206,7 +224,7 @@ prefixLength = 24; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - client_max_body_size 512M; + client_max_body_size 10G; keepalive_timeout 600s; proxy_buffers 4 256k; # Number and size of buffers for reading response proxy_buffer_size 256k; # Buffer for the first part of the response @@ -220,10 +238,18 @@ prefixLength = 24; enableACME = true; forceSSL = true; locations = { - "/".proxyPass = "http://192.168.0.22:10102"; + "/" = { + proxyPass = "http://192.168.0.22:10102"; + proxyWebSockets = true; + }; "~ ^/admin".return = 403; }; }; + "rss.depeuter.dev" = { + enableACME = true; + forceSSL = true; + locations."/".proxyPass = "http://192.168.92:${toString config.homelab.apps.freshrss.port}"; + }; }; }; }; diff --git a/hosts/Niko/default.nix b/hosts/Niko/default.nix index 57dbc27..910f325 100644 --- a/hosts/Niko/default.nix +++ b/hosts/Niko/default.nix @@ -7,7 +7,10 @@ ]; homelab = { - apps.technitiumDNS.enable = true; + apps = { + technitiumDNS.enable = true; + traefik.enable = true; + }; users.deploy.enable = true; }; @@ -34,12 +37,11 @@ hardware = { enableRedistributableFirmware = true; enableAllFirmware = true; - pulseaudio.enable = true; - opengl.enable = true; + graphics.enable = true; }; # Select internationalisation properties. - i18n.defaultLocale = "en_GB.utf8"; + i18n.defaultLocale = "en_GB.UTF-8"; networking = { hostName = "Niko"; @@ -79,6 +81,8 @@ user = config.users.users.jellyfin-mpv-shim.name; }; + pulseaudio.enable = true; + tailscale = { enable = true; useRoutingFeatures = "server"; @@ -94,8 +98,6 @@ # resolved.enable = true; }; - sound.enable = true; - # Define a user account. Don't forget to set a password with 'passwd'. users.users.jellyfin-mpv-shim = { description = "Jellyfin MPV Shim User"; @@ -114,67 +116,4 @@ systemd.services."cage-tty1".serviceConfig.Restart = "always"; system.stateVersion = "24.05"; - - virtualisation = { - # Enable Android emulator - # waydroid.enable = true; - - docker = { - enable = true; - autoPrune.enable = true; - }; - - oci-containers = { - backend = "docker"; - containers = { - reverse-proxy = { - hostname = "traefik"; - image = "traefik:v3.0"; - cmd = [ - "--api.insecure=true" - # Add Docker provider - "--providers.docker=true" - "--providers.docker.exposedByDefault=false" - # Add web entrypoint - "--entrypoints.web.address=:80/tcp" - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https" - # Add websecure entrypoint - "--entrypoints.websecure.address=:443/tcp" - "--entrypoints.websecure.http.tls=true" - "--entrypoints.websecure.http.tls.certResolver=letsencrypt" - "--entrypoints.websecure.http.tls.domains[0].main=depeuter.dev" - "--entrypoints.websecure.http.tls.domains[0].sans=*.depeuter.dev" - "--entrypoints.websecure.http.tls.domains[1].sans=*.niko.depeuter.dev" - # Certificates - "--certificatesresolvers.letsencrypt.acme.dnschallenge=true" - "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare" - "--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" - ]; - ports = [ - "80:80/tcp" - "443:443/tcp" - # "8080:8080/tcp" # The Web UI (enabled by --api.insecure=true) - ]; - environment = { - # TODO Hide this! - "CLOUDFLARE_DNS_API_TOKEN" = "6Vz64Op_a6Ls1ljGeBxFoOVfQ-yB-svRbf6OyPv2"; - }; - environmentFiles = [ - ]; - volumes = [ - "/var/run/docker.sock:/var/run/docker.sock:ro" # So that Traefik can listen to the Docker events - "letsencrypt:/letsencrypt" - ]; - labels = { - "traefik.enable" = "true"; - "traefik.http.routers.traefik.rule" = "Host(`traefik.niko.depeuter.dev`)"; - "traefik.http.services.traefik.loadbalancer.server.port" = "8080"; - }; - autoStart = true; - }; - }; - }; - }; } diff --git a/hosts/ProductionGPU/default.nix b/hosts/ProductionGPU/default.nix index 75e48e7..fa9ca8c 100644 --- a/hosts/ProductionGPU/default.nix +++ b/hosts/ProductionGPU/default.nix @@ -17,7 +17,7 @@ defaultGateway = { address = "192.168.0.1"; - interface = "enp6s18"; + interface = "ens18"; }; # Open ports in the firewall. @@ -25,7 +25,7 @@ enable = true; }; - interfaces.enp6s18 = { + interfaces.ens18 = { ipv4.addresses = [ { address = "192.168.0.94"; @@ -40,7 +40,7 @@ ]; }; - system.stateVersion = "unstable"; + system.stateVersion = "24.11"; ### Nvidia GPU support ### @@ -64,7 +64,7 @@ }; hardware = { - opengl = { + graphics = { enable = true; # driSupport = true; # driSupport32Bit = true; diff --git a/hosts/Testing/default.nix b/hosts/Testing/default.nix index 2da6563..cc353f6 100644 --- a/hosts/Testing/default.nix +++ b/hosts/Testing/default.nix @@ -3,7 +3,10 @@ { config = { homelab = { - apps.freshrss.enable = true; + apps = { + freshrss.enable = true; + traefik.enable = true; + }; virtualisation.guest.enable = true; }; diff --git a/modules/apps/arr/default.nix b/modules/apps/arr/default.nix index 3b05429..e2c0df5 100644 --- a/modules/apps/arr/default.nix +++ b/modules/apps/arr/default.nix @@ -343,7 +343,7 @@ in { port = 7878; in lib.mkIf cfg.radarr.enable { hostname = "radarr"; - image = "ghcr.io/hotio/radarr:release-5.28.0.10205"; + image = "ghcr.io/hotio/radarr:testing-5.28.0.10205"; autoStart = true; ports = lib.mkIf cfg.radarr.exposePorts [ "${toString port}:${toString port}/tcp" diff --git a/modules/apps/freshrss/default.nix b/modules/apps/freshrss/default.nix index f2ea7ba..4f4456f 100644 --- a/modules/apps/freshrss/default.nix +++ b/modules/apps/freshrss/default.nix @@ -27,7 +27,7 @@ in { "rw" "auto" "nfsvers=4.2" - "sync" "hard" "timeo=600" + "async" "soft" "timeo=600" "retrans=2" "_netdev" "nosuid" @@ -53,24 +53,41 @@ in { virtualisation.oci-containers.containers.freshrss = { hostname = "freshrss"; - image = "freshrss/freshrss:1.24.0"; + image = "freshrss/freshrss:1.25.0"; autoStart = true; user = "0:33"; ports = [ - "${toString port}:${toString port}/tcp" + "${toString port}:80/tcp" ]; extraOptions = [ "--network=${networkName}" ]; environment = { TZ = config.time.timeZone; - CRON_TIME = "3,18,33,48"; # Alternatively, configure cron inside container. - LISTEN = "0.0.0.0:${toString port}"; + CRON_MIN = "3,18,33,48"; # Alternatively, configure cron inside container. + SERVER_DNS = "rss.depeuter.dev"; + TRUSTED_PROXY = "172.16.0.1/12 192.168.0.1/16"; }; volumes = [ "/srv/freshrss/www/freshrss/data:/var/www/FreshRSS/data" "/srv/freshrss/www/freshrss/extensions:/var/www/FreshRSS/extensions" ]; + labels = { + "traefik.enable" = "true"; + + "traefik.http.middlewares.freshrssM1.compress" = "true"; + "traefik.http.middlewares.freshrssM2.headers.browserXssFilter" = "true"; + "traefik.http.middlewares.freshrssM2.headers.forceSTSHeader" = "true"; + "traefik.http.middlewares.freshrssM2.headers.frameDeny" = "true"; + "traefik.http.middlewares.freshrssM2.headers.referrerPolicy" = "no-referrer-when-downgrade"; + "traefik.http.middlewares.freshrssM2.headers.stsSeconds" = "31536000"; + "traefik.http.routers.freshrss.entryPoints" = "websecure"; + "traefik.http.routers.freshrss.tls" = "true"; + + "traefik.http.services.freshrss.loadbalancer.server.port" = "80"; + "traefik.http.routers.freshrss.middlewares" = "freshrssM1,freshrssM2"; + "traefik.http.routers.freshrss.rule" = "Host(`rss.depeuter.dev`)"; + }; }; }; } diff --git a/modules/apps/gitea/default.nix b/modules/apps/gitea/default.nix index 02f60cd..0361bd5 100644 --- a/modules/apps/gitea/default.nix +++ b/modules/apps/gitea/default.nix @@ -124,7 +124,7 @@ in { gitea = { hostname = "gitea"; - image = "codeberg.org/forgejo/forgejo:8.0.3-rootless"; + image = "codeberg.org/forgejo/forgejo:11.0.1-rootless"; autoStart = true; user = "${toString UID}:${toString GID}"; ports = [ @@ -576,7 +576,7 @@ in { #FORGEJO__picture__AVATAR_RENDERED_SIZE_FACTOR = "2"; # Maximum allowed file size for uploaded avatars. # This is to limit the amount of RAM used when resizing the image. - #FORGEJO__picture__AVATAR_MAX_FILE_SIZE = "1048576"; + FORGEJO__picture__AVATAR_MAX_FILE_SIZE = "1048576"; # If the uploaded file is not larger than this byte size, the image will be used as is, without resizing/converting. #FORGEJO__picture__AVATAR_MAX_ORIGIN_SIZE = "262144"; # Chinese users can choose "duoshuo" diff --git a/modules/apps/jellyfin/default.nix b/modules/apps/jellyfin/default.nix index 5b4081a..011f56b 100644 --- a/modules/apps/jellyfin/default.nix +++ b/modules/apps/jellyfin/default.nix @@ -4,6 +4,7 @@ let cfg = config.homelab.apps.jellyfin; networkName = "jellyfin"; + inherit (config.homelab.fileSystems) media; UID = 3008; GID = config.users.groups.media.gid; @@ -12,6 +13,11 @@ in { config = lib.mkIf cfg.enable { homelab = { + fileSystems.media.video = { + enable = true; + permissions = [ "read" ]; + }; + users = { apps.enable = true; media.enable = true; @@ -32,18 +38,6 @@ in { ]; }; - "/srv/video" = { - device = "192.168.0.11:/mnt/SMALL/MEDIA/VIDEO"; - fsType = "nfs"; - options = [ - "ro" - "nfsvers=4.2" - "async" "soft" - "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all" - "nosuid" "tcp" - ]; - }; - "/srv/homevideo" = { device = "192.168.0.11:/mnt/BIG/MEDIA/HOMEVIDEO/ARCHIVE"; fsType = "nfs"; @@ -101,7 +95,7 @@ in { virtualisation.oci-containers.containers = { jellyfin = { hostname = "jellyfin"; - image = "jellyfin/jellyfin:10.10.0"; + image = "jellyfin/jellyfin:10.10.7"; user = "${toString UID}:${toString GID}"; autoStart = true; ports = [ @@ -117,7 +111,7 @@ in { "cache:/cache" "/srv/audio:/media/audio" - "/srv/video:/media/video" + "${media.video.hostPath}:/media/video" "/srv/homevideo:/media/homevideo" "/srv/photo:/media/photo" ]; @@ -144,7 +138,7 @@ in { feishinPort = "9180"; in { hostname = "feishin"; - image = "ghcr.io/jeffvli/feishin:0.7.1"; + image = "ghcr.io/jeffvli/feishin:0.19.0"; autoStart = true; ports = [ "${feishinPort}:9180/tcp" # Web player (HTTP) diff --git a/modules/default.nix b/modules/default.nix index 5d901bc..1a000c3 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -1,6 +1,7 @@ { imports = [ ./apps + ./fileSystems ./services ./virtualisation diff --git a/modules/services/actions/default.nix b/modules/services/actions/default.nix index 338b963..ea6b025 100644 --- a/modules/services/actions/default.nix +++ b/modules/services/actions/default.nix @@ -44,6 +44,6 @@ in { ]; }; }; - }; } + From 12015f8589f83882fca484c70814925c02f888b3 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 4 Oct 2025 19:59:03 +0200 Subject: [PATCH 25/31] feat(sops): Setup sops --- .sops.yaml | 8 ++++++++ flake.nix | 10 ++++++++-- secrets/secrets.yaml | 19 +++++++++++++++++++ users/admin/default.nix | 13 ++++++++++--- 4 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 .sops.yaml create mode 100644 secrets/secrets.yaml diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..8d0d445 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,8 @@ +keys: + - &tdpeuter_Tibo-NixTop age1qzutny0mqpcccqw6myyfntu6wcskruu9ghzvt6r4te7afkqwnguq05ex37 + +creation_rules: + - path_regex: secrets/[^/]+\.(yaml|json|env|ini)$ + key_groups: + - age: + - *tdpeuter_Tibo-NixTop diff --git a/flake.nix b/flake.nix index 7701f3a..446f4ce 100644 --- a/flake.nix +++ b/flake.nix @@ -5,6 +5,10 @@ nixpkgs.url = "nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; + sops-nix = { + url = "github:Mic92/sops-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; utils = { url = "github:gytis-ivaskevicius/flake-utils-plus"; inputs.flake-utils.follows = "flake-utils"; @@ -13,11 +17,11 @@ outputs = inputs@{ self, nixpkgs, - flake-utils, utils, + flake-utils, sops-nix, utils, ... }: let - system = "x86_64-linux"; + system = utils.lib.system.x86_64-linux; in utils.lib.mkFlake { inherit self inputs; @@ -28,6 +32,8 @@ modules = [ ./modules ./users + + sops-nix.nixosModules.sops ]; }; diff --git a/secrets/secrets.yaml b/secrets/secrets.yaml new file mode 100644 index 0000000..e17dab1 --- /dev/null +++ b/secrets/secrets.yaml @@ -0,0 +1,19 @@ +users: + admin: + authorized_keys: + NixOS: ENC[AES256_GCM,data:sj2hkUkWp628KuXp+AnncLdawHpxb9fH1ZHnIisP0x9Tght9+/X2sWHpuMSeqi2i/R8B+Wgte66QkuwAOB0j+oB9N+66EhehmWZlK5hD/22p,iv:z18U+LvAQgPDfBBewE3lJmWZd0NGCPwJIe/h3tupuZc=,tag:ZJar3spO66JbDXygdTHh2w==,type:str] +sops: + age: + - recipient: age1qzutny0mqpcccqw6myyfntu6wcskruu9ghzvt6r4te7afkqwnguq05ex37 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjUSt2REk2Mmd0bk9ubjJk + dXFiY2JNR1dyZW9qTUdzaWZhY3c3amVwQzA0CkZHNVpZVjhsWXhVQVNaR0xONzhh + Y0lQaWNaNmpYYVdrRnZIZUhvUFUzcWMKLS0tIDAvSmF0VmpxcnZEQStXUjNCUE5Z + RnA2Lzk2WHFxOEh6dHN0aGhVSVpLTW8KA7IOvGDMBtgo4pe0Sw3Lol243xCDAJ4i + PhcJFiUObVRFZN7ISlULnOlTO3pT9jWvvmC5rDZWId3PQ8qjPvnOUg== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2025-10-04T17:33:22Z" + mac: ENC[AES256_GCM,data:I7I7uDFEWfw9+4KROtjHMVhaxYrVK5QmLfFZShSajF0A2Zxu9lg+fDGiMHk40JC5zD31P70QS/ipye1mBGQbCbLEA7uBUhNzZ7G1g58cIXF6vSGmt0fovm0MVSxEJ44r05fx6uT4OJu5BYVxYSlG84gTj9rCFXxxcBJMrh+6yaI=,iv:c1vudsp9bg0Pc2ddRyvWn6Tf0LhqNuEjxG9D4PpHqxs=,tag:K/1PSHhrTdsNPcPmRv/2Ew==,type:str] + unencrypted_suffix: _unencrypted + version: 3.10.2 diff --git a/users/admin/default.nix b/users/admin/default.nix index 552909b..bc3ccc7 100644 --- a/users/admin/default.nix +++ b/users/admin/default.nix @@ -2,6 +2,8 @@ let cfg = config.homelab.users.admin; + + owner = config.users.users.admin.name; in { options.homelab.users.admin.enable = lib.mkEnableOption "user System Administrator"; @@ -10,6 +12,12 @@ in { config.users.users.admin.name ]; + sops.secrets."users/admin/authorized_keys" = { + format = "yaml"; + sopsFile = ../../secrets/secrets.yaml; + inherit owner; + }; + users.users.admin = { description = "System Administrator"; isNormalUser = true; @@ -17,9 +25,8 @@ in { config.users.groups.wheel.name # Enable 'sudo' for the user. ]; initialPassword = "ChangeMe"; - openssh.authorizedKeys.keys = [ - # TODO ChangeMe - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrG+ldRBdCeHEXrsy/qHXIJYg8xQXVuiUR0DxhFjYNg" + openssh.authorizedKeys.keyFiles = [ + /run/secrets/users/admin/authorized_keys ]; packages = with pkgs; [ curl From a37c5ae83a4beb84e46d04bac7bb0ba9bde9a12d Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 4 Oct 2025 20:21:59 +0200 Subject: [PATCH 26/31] fix(sops): Add Tibo-NixFat --- .sops.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.sops.yaml b/.sops.yaml index 8d0d445..02cc451 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -1,8 +1,10 @@ keys: + - &tdpeuter_Tibo-NixFatDesk age1fva6s64s884z0q2w7de024sp69ucvqu0pg9shrhhqsn3ewlpjfpsh6md7y - &tdpeuter_Tibo-NixTop age1qzutny0mqpcccqw6myyfntu6wcskruu9ghzvt6r4te7afkqwnguq05ex37 creation_rules: - path_regex: secrets/[^/]+\.(yaml|json|env|ini)$ key_groups: - age: + - *tdpeuter_Tibo-NixFatDesk - *tdpeuter_Tibo-NixTop From 4ab3848c830a2a1254e78189909cd316011f5817 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 4 Oct 2025 20:25:09 +0200 Subject: [PATCH 27/31] fix(Development): Replace authorized_keys --- users/admin/default.nix | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/users/admin/default.nix b/users/admin/default.nix index bc3ccc7..4038266 100644 --- a/users/admin/default.nix +++ b/users/admin/default.nix @@ -2,8 +2,6 @@ let cfg = config.homelab.users.admin; - - owner = config.users.users.admin.name; in { options.homelab.users.admin.enable = lib.mkEnableOption "user System Administrator"; @@ -12,12 +10,6 @@ in { config.users.users.admin.name ]; - sops.secrets."users/admin/authorized_keys" = { - format = "yaml"; - sopsFile = ../../secrets/secrets.yaml; - inherit owner; - }; - users.users.admin = { description = "System Administrator"; isNormalUser = true; @@ -25,8 +17,9 @@ in { config.users.groups.wheel.name # Enable 'sudo' for the user. ]; initialPassword = "ChangeMe"; - openssh.authorizedKeys.keyFiles = [ - /run/secrets/users/admin/authorized_keys + openssh.authorizedKeys.keys = [ + # HomeLab > NixOS > admin > ssh + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGWIOOEqTy8cWKpENVbzD4p7bsQgQb/Dgpzk8i0dZ00T" ]; packages = with pkgs; [ curl From 81a07af1521169a7ffac5e0850a0e236d9580248 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 4 Oct 2025 20:26:01 +0200 Subject: [PATCH 28/31] nix flake update --- flake.lock | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 78e4127..ca6e418 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1756787288, - "narHash": "sha256-rw/PHa1cqiePdBxhF66V7R+WAP8WekQ0mCDG4CFqT8Y=", + "lastModified": 1759381078, + "narHash": "sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d0fc30899600b9b3466ddb260fd83deb486c32f1", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", "type": "github" }, "original": { @@ -37,9 +37,30 @@ "inputs": { "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", + "sops-nix": "sops-nix", "utils": "utils" } }, + "sops-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1759188042, + "narHash": "sha256-f9QC2KKiNReZDG2yyKAtDZh0rSK2Xp1wkPzKbHeQVRU=", + "owner": "Mic92", + "repo": "sops-nix", + "rev": "9fcfabe085281dd793589bdc770a2e577a3caa5d", + "type": "github" + }, + "original": { + "owner": "Mic92", + "repo": "sops-nix", + "type": "github" + } + }, "systems": { "locked": { "lastModified": 1681028828, From 297a6df29e56fd72e4888fad295a4683659c9e6f Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 11 Oct 2025 15:40:43 +0200 Subject: [PATCH 29/31] feat: Add gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea From b2e904306b299e96fdd316857ead0ac351c5a4c1 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 11 Oct 2025 15:41:13 +0200 Subject: [PATCH 30/31] feat(traefik): Add external services --- hosts/Binnenpost/default.nix | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hosts/Binnenpost/default.nix b/hosts/Binnenpost/default.nix index d78e2da..561fbe1 100644 --- a/hosts/Binnenpost/default.nix +++ b/hosts/Binnenpost/default.nix @@ -16,6 +16,7 @@ apps = { speedtest.enable = true; technitiumDNS.enable = true; + traefik.enable = true; }; virtualisation.guest.enable = true; }; @@ -76,6 +77,14 @@ }; }; + virtualisation.oci-containers.containers.traefik.labels = { + "traefik.http.routers.roxanne.rule" = "Host(`roxanne.depeuter.dev`)"; + "traefik.http.services.roxanne.loadbalancer.server.url" = "https://192.168.0.13:8006"; + + "traefik.http.routers.hugo.rule" = "Host(`hugo.depeuter.dev`)"; + "traefik.http.services.hugo.loadbalancer.server.url" = "https://192.168.0.11:444"; + }; + system.stateVersion = "24.05"; }; } From 2c195bf8077c90b2330ba6d43e3de1b5048bfa92 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Mon, 13 Oct 2025 20:35:46 +0200 Subject: [PATCH 31/31] chore(arr): Use functions --- modules/apps/arr/default.nix | 178 +++++++---------------------------- 1 file changed, 35 insertions(+), 143 deletions(-) diff --git a/modules/apps/arr/default.nix b/modules/apps/arr/default.nix index e2c0df5..7b530c3 100644 --- a/modules/apps/arr/default.nix +++ b/modules/apps/arr/default.nix @@ -12,7 +12,16 @@ let PGID = toString config.users.groups.media.gid; UMASK = "002"; in { - options.homelab.apps.arr = { + options.homelab.apps.arr = let + mkAppOption = appName: { + enable = lib.mkEnableOption "${appName} using Docker"; + exposePorts = lib.mkOption { + type = lib.types.bool; + description = "Expose ${appName} port"; + default = cfg.exposePorts; + }; + }; + in { enable = lib.mkEnableOption "Arr Stack using Docker"; exposePorts = lib.mkOption { type = lib.types.bool; @@ -21,46 +30,11 @@ in { default = ! config.homelab.apps.traefik.enable; }; - bazarr = { - enable = lib.mkEnableOption "Bazarr using Docker"; - exposePorts = lib.mkOption { - type = lib.types.bool; - description = "Expose Bazarr port"; - default = cfg.exposePorts; - }; - }; - prowlarr = { - enable = lib.mkEnableOption "Prowlarr using Docker"; - exposePorts = lib.mkOption { - type = lib.types.bool; - description = "Expose Prowlarr port"; - default = cfg.exposePorts; - }; - }; - qbittorrent = { - enable = lib.mkEnableOption "qBittorrent using Docker"; - exposePorts = lib.mkOption { - type = lib.types.bool; - description = "Expose qBittorrent port"; - default = cfg.exposePorts; - }; - }; - radarr = { - enable = lib.mkEnableOption "Radarr using Docker"; - exposePorts = lib.mkOption { - type = lib.types.bool; - description = "Expose Radarr port"; - default = cfg.exposePorts; - }; - }; - sonarr = { - enable = lib.mkEnableOption "Sonarr using Docker"; - exposePorts = lib.mkOption { - type = lib.types.bool; - description = "Expose Sonarr port"; - default = cfg.exposePorts; - }; - }; + bazarr = mkAppOption "Bazarr"; + prowlarr = mkAppOption "Prowlarr"; + qbittorrent = mkAppOption "qBittorrent"; + radarr = mkAppOption "Radarr"; + sonarr = mkAppOption "Sonarr"; }; config = { @@ -87,9 +61,9 @@ in { virtualisation.containers.enable = lib.mkIf inUse true; }; - fileSystems = lib.mkIf inUse { - "/srv/bazarr-backup" = lib.mkIf cfg.bazarr.enable { - device = "192.168.0.11:/mnt/BIG/BACKUP/BAZARR"; + fileSystems = let + mkFileSystem = device: { + inherit device; fsType = "nfs"; options = [ "rw" @@ -102,75 +76,14 @@ in { ]; }; - "/srv/prowlarr-backup" = lib.mkIf cfg.prowlarr.enable { - device = "192.168.0.11:/mnt/BIG/BACKUP/PROWLARR"; - fsType = "nfs"; - options = [ - "rw" - "auto" - "nfsvers=4.2" - "rsize=1048576" "wsize=1048576" - "hard" - "timeo=600" "retrans=2" - "_netdev" "nosuid" "tcp" - ]; - }; - - "/srv/qbittorrent" = lib.mkIf cfg.qbittorrent.enable { - device = "192.168.0.11:/mnt/SMALL/CONFIG/QBITTORRENT"; - fsType = "nfs"; - options = [ - "rw" - "auto" - "nfsvers=4.2" - "rsize=1048576" "wsize=1048576" - "hard" - "timeo=600" "retrans=2" - "_netdev" "nosuid" "tcp" - ]; - }; - - "/srv/radarr-backup" = lib.mkIf cfg.radarr.enable { - device = "192.168.0.11:/mnt/BIG/BACKUP/RADARR"; - fsType = "nfs"; - options = [ - "rw" - "auto" - "nfsvers=4.2" - "rsize=1048576" "wsize=1048576" - "hard" - "timeo=600" "retrans=2" - "_netdev" "nosuid" "tcp" - ]; - }; - - "/srv/sonarr-backup" = lib.mkIf cfg.sonarr.enable { - device = "192.168.0.11:/mnt/BIG/BACKUP/SONARR"; - fsType = "nfs"; - options = [ - "rw" - "auto" - "nfsvers=4.2" - "rsize=1048576" "wsize=1048576" - "hard" - "timeo=600" "retrans=2" - "_netdev" "nosuid" "tcp" - ]; - }; - - "/srv/torrent" = { - device = "192.168.0.11:/mnt/SMALL/MEDIA/TORRENT"; - fsType = "nfs"; - options = [ - "rw" - "auto" - "nfsvers=4.2" - "rsize=1048576" "wsize=1048576" - "hard" - "timeo=600" "retrans=2" - "_netdev" "nosuid" "tcp" - ]; - }; + hugoBackup = "192.168.0.11:/mnt/BIG/BACKUP"; + in lib.mkIf inUse { + "/srv/bazarr-backup" = lib.mkIf cfg.bazarr.enable (mkFileSystem "${hugoBackup}/BAZARR"); + "/srv/prowlarr-backup" = lib.mkIf cfg.bazarr.enable (mkFileSystem "${hugoBackup}/PROWLARR"); + "/srv/qbittorrent" = lib.mkIf cfg.qbittorrent.enable (mkFileSystem "192.168.0.11:/mnt/SMALL/CONFIG/QBITTORRENT"); + "/srv/radarr-backup" = lib.mkIf cfg.radarr.enable (mkFileSystem "${hugoBackup}/RADARR"); + "/srv/sonarr-backup" = lib.mkIf cfg.sonarr.enable (mkFileSystem "${hugoBackup}/SONARR"); + "/srv/torrent" = mkFileSystem "192.168.0.11:/mnt/SMALL/MEDIA/TORRENT"; }; # Make sure the Docker network exists. @@ -195,45 +108,24 @@ in { }; # Create a user for each app. - users.users = { - bazarr = lib.mkIf cfg.bazarr.enable { - uid = lib.mkForce 3003; + users.users = let + mkUser = uid: { + uid = lib.mkForce uid; isSystemUser = true; group = config.users.groups.media.name; home = "/var/empty"; shell = null; }; - prowlarr = lib.mkIf cfg.prowlarr.enable { - uid = lib.mkForce 3004; - isSystemUser = true; - group = config.users.groups.media.name; - home = "/var/empty"; - shell = null; - }; - qbittorrent = lib.mkIf cfg.qbittorrent.enable { - uid = lib.mkForce 3005; - isSystemUser = true; - group = config.users.groups.media.name; + in { + bazarr = lib.mkIf cfg.bazarr.enable (mkUser 3003); + prowlarr = lib.mkIf cfg.prowlarr.enable (mkUser 3004); + qbittorrent = lib.mkIf cfg.qbittorrent.enable (mkUser 3005) // { extraGroups = [ config.users.groups.apps.name ]; - home = "/var/empty"; - shell = null; - }; - radarr = lib.mkIf cfg.radarr.enable { - uid = lib.mkForce 3006; - isSystemUser = true; - group = config.users.groups.media.name; - home = "/var/empty"; - shell = null; - }; - sonarr = lib.mkIf cfg.sonarr.enable { - uid = lib.mkForce 3007; - isSystemUser = true; - group = config.users.groups.media.name; - home = "/var/empty"; - shell = null; }; + radarr = lib.mkIf cfg.radarr.enable (mkUser 3006); + sonarr = lib.mkIf cfg.sonarr.enable (mkUser 3007); }; virtualisation.oci-containers.containers = let