From cef3a949feae5ef6e87dee00cf44def0b2dd2ed7 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sun, 10 Nov 2024 20:15:47 +0100 Subject: [PATCH] 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; + }; + }; +}