{ config, lib, pkgs, ... }: let makeVirtualHost = { subdomain, port, }: { name = "${subdomain}.fuckwit.dev"; value = { forceSSL = true; useACMEHost = "fuckwit.dev"; locations."/" = { proxyPass = "http://127.0.0.1:${builtins.toString port}"; proxyWebsockets = true; }; }; }; makeVirtualHosts = sites: builtins.listToAttrs (builtins.map makeVirtualHost sites); disks = [ "/dev/disk/by-id/ata-ST14000NM000G-2KG103_ZL232MW7" "/dev/disk/by-id/ata-ST14000NM000G-2KG103_ZL22L00W" "/dev/disk/by-id/ata-ST14000NM000G-2KG103_ZL23J3P2" "/dev/disk/by-id/ata-ST14000NM000G-2KG103_ZL22LCB4" "/dev/disk/by-id/ata-ST14000NM000G-2KG103_ZL22PG6W" "/dev/disk/by-id/ata-ST14000NM000G-2KG103_ZL20KVKP" ]; in { sops.defaultSopsFile = ./secrets.yaml; sops.secrets."acme.env" = {}; sops.secrets."tailscale-auth-key" = {}; sops.secrets."act-runner-token" = {}; sops.secrets."photoprism-password-file" = {}; sops.secrets."restic_ssh_key" = {}; sops.secrets."restic_documents_repository_password" = {}; sops.secrets."restic_images_repository_password" = {}; imports = [ ./hardware-configuration.nix ]; boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; boot.kernelParams = [ "initcall_blacklist=acpi_cpufreq_init" "amd_pstate=passive" "libata.force=noncq" ]; boot.kernelModules = ["amd-pstate"]; # *arr services are not yet all updated to .NET 8 nixpkgs.config.permittedInsecurePackages = [ "aspnetcore-runtime-6.0.36" "aspnetcore-runtime-wrapped-6.0.36" "dotnet-sdk-6.0.428" "dotnet-sdk-wrapped-6.0.428" ]; system.stateVersion = "23.11"; # Did you read the comment? networking = { hostName = "celestia"; interfaces.enp5s0f0 = { useDHCP = false; ipv4.addresses = [ { address = "10.1.1.11"; prefixLength = 24; } ]; }; firewall = { enable = true; allowedTCPPorts = [22 111 443 2049 4000 4001 4002 20048]; allowedUDPPorts = [53 111 2049 4000 4001 4002 20048]; }; }; time.timeZone = "Europe/Berlin"; i18n.defaultLocale = "en_US.UTF-8"; environment.systemPackages = with pkgs; [ vim wget htop bash zfs lm_sensors ffmpeg rtl_433 dump1090 rtl-sdr ]; users.users."root".openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP8zNAXScQ4FoWNxF4+ALJXMSi3EbpqZP5pO9kfg9t8o patrick@NBG1-DC3-PC20-2017-10-24" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPflDQOANGhgtfo2psRwSFtY5ETHX/bsDmqrho3iX9jt root@arschlinux" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP6oGHBFD3wo16buPtdYDat911gydOw2oFj80fTXL1xo batzi@DESKTOP-8A2VTHL" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICK3otGMe8umxxJX5BbbBQ/+PQg37Puh0qjH8IILL95T patrick@mi" "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIDl3vLxNpinilTJp1rGsSYlVi+hIa+oECtge1i8bwz33AAAACHNzaDptYWlu" ]; users.groups.nas.gid = 2000; users.users.nginx.extraGroups = ["acme"]; environment = { etc = { "sysconfig/lm_sensors".text = '' HWMON_MODULES="nct6775" ''; }; }; security.acme = { acceptTerms = true; defaults = { email = "acme@fuckwit.dev"; dnsProvider = "cloudflare"; environmentFile = config.sops.secrets."acme.env".path; dnsPropagationCheck = true; }; certs."fuckwit.dev" = { extraDomainNames = ["*.fuckwit.dev"]; }; }; services = { tailscale = { enable = true; openFirewall = true; useRoutingFeatures = "both"; extraUpFlags = ["--advertise-routes=192.168.1.11/32"]; authKeyFile = config.sops.secrets."tailscale-auth-key".path; }; dnscrypt-proxy2 = { enable = true; settings = { listen_addresses = ["0.0.0.0:53"]; ipv6_servers = false; dnscrypt_servers = true; cloaking_rules = "/var/lib/dnscrypt-proxy/cloaking"; sources.dnscry-pt-resolvers = { urls = ["https://www.dnscry.pt/resolvers.md"]; minisign_key = "RWQM31Nwkqh01x88SvrBL8djp1NH56Rb4mKLHz16K7qsXgEomnDv6ziQ"; cache_file = "/var/lib/dnscrypt-proxy/dnscry.pt-resolvers.md"; refresh_delay = 72; prefix = "dnscry.pt-"; }; }; }; openssh = { enable = true; settings = { PermitRootLogin = "yes"; }; }; nfs.server = { enable = true; lockdPort = 4001; mountdPort = 4002; statdPort = 4000; extraNfsdConfig = ''''; }; samba = { enable = true; openFirewall = true; settings = { global = { "map to guest" = "bad user"; }; dump = { path = "/tank/dump"; browsable = "yes"; public = "yes"; "guest only" = "yes"; writable = "yes"; # "force create mode" = "0666"; # "force directory mode" = "0777"; }; video = { path = "/tank/video"; browsable = "yes"; public = "yes"; "guest only" = "yes"; writable = "yes"; "force group" = "nas"; # "force create mode" = "0666"; # "force directory mode" = "0777"; }; }; }; zfs = { autoScrub.enable = true; }; gitea-actions-runner.instances = { runner1 = { enable = true; name = "celestia"; url = "https://git.fuckwit.dev"; tokenFile = config.sops.secrets."act-runner-token".path; labels = [ "nix:docker://nixos/nix:latest" ]; # hostPackages = with pkgs; [ # bash # coreutils # curl # wget # gnused # gitMinimal # ]; }; }; restic = let mkBackup = repo: paths: exclude: pruneOpts: { repository = "sftp:u169497-sub5@u169497.your-storagebox.de:${repo}"; passwordFile = config.sops.secrets."restic_${repo}_repository_password".path; initialize = true; extraOptions = [ "sftp.command='ssh -p23 u169497-sub5@u169497.your-storagebox.de -i ${config.sops.secrets."restic_ssh_key".path} -s sftp'" ]; paths = paths; exclude = exclude; pruneOpts = pruneOpts; timerConfig = { OnCalendar = "00:05"; RandomizedDelaySec = "1h"; }; }; in { backups = { documents = mkBackup "documents" ["/tank/documents"] [] ["-d 7" "-w 5" "-m 12"]; images = mkBackup "images" ["/tank/images"] ["/tank/images/import"] ["-d 7" "-w 5" "-m 12"]; }; }; nginx = { enable = true; clientMaxBodySize = "500m"; virtualHosts = makeVirtualHosts [ { subdomain = "jdownloader"; port = 8000; } { subdomain = "jellyfin"; port = 8096; } { subdomain = "sonarr"; port = 8989; } { subdomain = "radarr"; port = 7878; } { subdomain = "lidarr"; port = 8686; } { subdomain = "paperless"; port = 28981; } { subdomain = "homepage"; port = 8082; } { subdomain = "photoprism"; port = 2342; } ]; }; paperless = { enable = true; mediaDir = "/tank/documents"; consumptionDir = "/tank/dump/paperless_consume"; consumptionDirIsPublic = true; settings = { PAPERLESS_URL = "https://paperless.fuckwit.dev"; PAPERLESS_CONSUMER_IGNORE_PATTERN = builtins.toJSON [ ".DS_STORE/*" "desktop.ini" ]; PAPERLESS_OCR_LANGUAGE = "deu+eng"; PAPERLESS_OCR_USER_ARGS = builtins.toJSON { optimize = 1; pdfa_image_compression = "lossless"; }; }; }; lidarr = { enable = true; group = "nas"; dataDir = "/var/lib/lidarr"; }; radarr = { enable = true; group = "nas"; dataDir = "/var/lib/radarr"; }; sonarr = { enable = true; group = "nas"; dataDir = "/var/lib/sonarr"; }; jellyfin.enable = true; photoprism = { enable = true; originalsPath = "/tank/images/pictures"; importPath = "/tank/images/import"; passwordFile = config.sops.secrets."photoprism-password-file".path; settings = { PHOTOPRISM_ADMIN_USER = "root"; PHOTOPRISM_DEFAULT_LOCALE = "de"; PHOTOPRISM_DETECT_NSFW = "true"; PHOTOPRISM_UPLOAD_NSFW = "true"; }; }; homepage-dashboard = { enable = true; settings = { title = "Homelab"; theme = "dark"; layout = [ { Media = { style = "row"; columns = 4; }; } ]; }; widgets = [ { resources = { cpu = true; memory = true; disk = "/tank"; }; } { search = { provider = "duckduckgo"; target = "_blank"; }; } ]; services = [ { Media = [ { Jellyfin = { icon = "jellyfin.png"; href = "https://jellyfin.fuckwit.dev"; siteMonitor = "https://jellyfin.fuckwit.dev"; description = "Media library"; widget = { type = "jellyfin"; url = "https://jellyfin.fuckwit.dev"; key = "d6e4766cda6c412cb4a96626c0f0b51a"; enableBlocks = true; enableNowPlaying = false; }; }; } { Radarr = { icon = "radarr.png"; href = "https://radarr.fuckwit.dev"; siteMonitor = "https://radarr.fuckwit.dev"; description = "Media library"; widget = { type = "radarr"; url = "https://radarr.fuckwit.dev"; key = "01d93b03f6c64a0f9786598b611e58f9"; }; }; } { Sonarr = { icon = "sonarr.png"; href = "https://sonarr.fuckwit.dev"; siteMonitor = "https://sonarr.fuckwit.dev"; description = "Media library"; widget = { type = "sonarr"; url = "https://sonarr.fuckwit.dev"; key = "c6be6b2d78104a97a2c7df560b27bb5c"; }; }; } { Lidarr = { icon = "lidarr.png"; href = "https://lidarr.fuckwit.dev"; siteMonitor = "https://lidarr.fuckwit.dev"; description = "Media library"; widget = { type = "lidarr"; url = "https://lidarr.fuckwit.dev"; key = "e95e25ccd6f04ffe8e8ad0ff488231a8"; }; }; } ]; } ]; }; }; hardware = { rtl-sdr.enable = true; fancontrol = { enable = true; config = '' # Configuration file generated by pwmconfig, changes will be lost INTERVAL=10 DEVPATH=hwmon0=devices/platform/nct6775.656 DEVNAME=hwmon0=nct6779 FCTEMPS=hwmon0/pwm5=hwmon0/temp2_input hwmon0/pwm3=hwmon0/temp2_input FCFANS=hwmon0/pwm5=hwmon0/fan5_input hwmon0/pwm3=hwmon0/fan3_input MINTEMP=hwmon0/pwm5=40 hwmon0/pwm3=40 MAXTEMP=hwmon0/pwm5=80 hwmon0/pwm3=80 MINSTART=hwmon0/pwm5=150 hwmon0/pwm3=150 MINSTOP=hwmon0/pwm5=0 hwmon0/pwm3=0 MAXPWM=hwmon0/pwm5=150 hwmon0/pwm3=150 ''; }; }; virtualisation = { podman = { enable = true; }; oci-containers = { backend = "podman"; containers = { jdownloader = { image = "docker.io/jlesage/jdownloader-2:latest"; autoStart = true; ports = ["0.0.0.0:8000:5800"]; volumes = [ "jdownloader_config:/config" "/tank/dump:/output" ]; }; }; }; }; powerManagement = { enable = true; powerUpCommands = lib.strings.concatMapStringsSep "\n" (disk: "${pkgs.hdparm}/sbin/hdparm -S 241 " + disk) disks; }; systemd.services = let ensure-perms = path: user: group: { enable = true; description = "Ensures permissionsions and ownership of files in ${path}"; wantedBy = ["multi-user.target"]; script = '' while read -r evt file; do ${pkgs.coreutils}/bin/chown ${user}:${group} "$file" ${pkgs.coreutils}/bin/chmod 775 "$file" done < <(${pkgs.inotify-tools}/bin/inotifywait -e create,move -m -r --format '%e %w%f' ${path}) ''; }; in { dnscrypt-proxy2.serviceConfig = { StateDirectory = "dnscrypt-proxy"; }; ensure-radarr-perms = ensure-perms "/tank/video/movie" "radarr" "nas"; ensure-sonarr-perms = ensure-perms "/tank/video/series" "sonarr" "nas"; ensure-lidarr-perms = ensure-perms "/tank/audio" "lidarr" "nas"; }; }