{ inputs, config, pkgs, username, ... }: let http_port = 80; https_port = 443; home_domain = "home.technicalissues.us"; backend_ip = "127.0.0.1"; mini_watcher = "192.168.23.20"; restic_backup_time = "02:00"; in { imports = [ ./hardware-configuration.nix ./containers/audiobookshelf.nix ./containers/mountain-mesh-bot-discord.nix ./containers/pinchflat.nix ./containers/psitransfer.nix ../../common/linux/lets-encrypt.nix ../../common/linux/restic.nix ]; system.stateVersion = "23.11"; # Bootloader. boot = { loader = { efi.canTouchEfiVariables = true; systemd-boot.enable = true; }; supportedFilesystems = [ "zfs" ]; zfs = { extraPools = [ "orico" ]; forceImportRoot = false; }; }; environment = { sessionVariables = { LIBVA_DRIVER_NAME = "iHD"; }; systemPackages = with pkgs; [ inputs.compose2nix.packages.${pkgs.stdenv.hostPlatform.system}.default docker-compose intel-gpu-tools jellyfin jellyfin-ffmpeg jellyfin-web net-snmp nginx nvme-cli podman-compose podman-tui # status of containers in the terminal yt-dlp ]; }; # https://wiki.nixos.org/wiki/Jellyfin hardware.graphics = { enable = true; extraPackages = with pkgs; [ intel-compute-runtime-legacy1 # pre-13th gen cpu intel-media-driver # For Broadwell and newer (ca. 2014+), use with LIBVA_DRIVER_NAME=iHD: intel-ocl # Generic OpenCL support ]; }; mailserver = { enable = true; enableImap = false; enableImapSsl = false; fqdn = "mail.${home_domain}"; domains = [ home_domain ]; forwards = { "${username}@localhost" = "${username}@technicalissues.us"; "root@localhost" = "root@technicalissues.us"; "root@${config.networking.hostName}" = "root@technicalissues.us"; }; stateVersion = 3; # Use Let's Encrypt certificates from Nginx certificateScheme = "acme"; }; networking = { # Open ports in the firewall. firewall = { allowedTCPPorts = [ 22 # ssh 80 # http to local Nginx 443 # https to local Nginx 3000 # PsiTransfer in oci-container 3001 # immich-kiosk in compose 3002 # grafana 3005 # Firefly III 3006 # Firefly III Data Importer 3030 # Forgejo 8001 # Tube Archivist 8384 # Syncthing gui 8888 # Atuin 8090 # Wallabag in docker compose 8945 # Pinchflat in oci-container 9090 # Prometheus Server 9273 # Telegraf's Prometheus endpoint 13378 # Audiobookshelf in oci-container 22000 # Syncthing transfers ]; allowedUDPPorts = [ 1900 # Jellyfin service auto-discovery 7359 # Jellyfin auto-discovery 21027 # Syncthing discovery 22000 # Syncthing transfers ]; }; # Or disable the firewall altogether. # firewall.enable = false; hostId = "c5826b45"; # head -c4 /dev/urandom | od -A none -t x4 useDHCP = false; networkmanager.enable = false; useNetworkd = true; vlans = { vlan23 = { id = 23; interface = "eno1"; }; }; interfaces = { eno1.useDHCP = true; vlan23.ipv4.addresses = [{ address = "192.168.23.21"; prefixLength = 24; }]; }; }; # Enable sound with pipewire. security.rtkit.enable = true; services.pipewire = { enable = true; alsa.enable = true; alsa.support32Bit = true; pulse.enable = true; }; services.pulseaudio.enable = false; programs.mtr.enable = true; # List services that you want to enable: services = { atuin = { enable = true; host = "127.0.0.1"; maxHistoryLength = 2000000000; }; avahi = { enable = true; nssmdns4 = true; openFirewall = true; }; ## ## Gandi (gandi.net) ## ## Single host update # protocol=gandi # zone=example.com # password=my-gandi-access-token # use-personal-access-token=yes # ttl=10800 # optional # myhost.example.com ddclient = { enable = true; protocol = "gandi"; zone = "technicalissues.us"; domains = [ home_domain ]; username = "unused"; extraConfig = '' usev4=webv4 #usev6=webv6 #use-personal-access-token=yes ttl=300 ''; passwordFile = "${config.sops.secrets.gandi_api.path}"; }; firefly-iii = { enable = true; enableNginx = true; settings.APP_KEY_FILE = "${config.sops.secrets.firefly_app_key.path}"; virtualHost = "budget.${home_domain}"; }; firefly-iii-data-importer = { enable = true; enableNginx = true; settings = { FIREFLY_III_URL = "http://localhost:3005"; VANITY_URL_FILE = "${config.sops.secrets.firefly_vanity_url.path}"; FIREFLY_III_ACCESS_TOKEN_FILE = "${config.sops.secrets.firefly_pat_data_import.path}"; SIMPLEFIN_TOKEN_FILE = "${config.sops.secrets.firefly_simplefin_token.path}"; TZ = "America/New_York"; }; virtualHost = "budget-importer.${home_domain}"; }; forgejo = { enable = true; database.type = "postgres"; lfs.enable = true; settings = { # Add support for actions, based on act: https://github.com/nektos/act actions = { ENABLED = true; DEFAULT_ACTIONS_URL = "github"; }; DEFAULT.APP_NAME = "Beantown's Code"; repository = { DEFAULT_PUSH_CREATE_PRIVATE = true; ENABLE_PUSH_CREATE_ORG = true; ENABLE_PUSH_CREATE_USER = true; }; server = { DOMAIN = "git.${home_domain}"; HTTP_PORT = 3030; LANDING_PAGE = "explore"; ROOT_URL = "https://git.${home_domain}/"; }; service.DISABLE_REGISTRATION = true; session.COOKIE_SECURE = true; }; stateDir = "/orico/forgejo"; }; fwupd.enable = true; grafana = { enable = true; settings = { server = { domain = "monitoring.${home_domain}"; http_addr = "0.0.0.0"; http_port = 3002; root_url = "https://monitoring.${home_domain}/grafana/"; # Not needed if it is `https://your.domain/` serve_from_sub_path = true; }; }; }; jellyfin = { enable = true; openFirewall = true; }; lldpd.enable = true; mealie = { enable = true; credentialsFile = config.sops.secrets.mealie.path; listenAddress = "0.0.0.0"; port = 9000; settings = { ALLOW_SIGNUP = "false"; BASE_URL = "https://mealie.${home_domain}"; DATA_DIR = "/var/lib/mealie"; DB_ENGINE = "postgres"; POSTGRES_USER = "mealie"; POSTGRES_DB = "mealie"; POSTGRES_SERVER = "localhost"; POSTGRES_PORT = config.services.postgresql.settings.port; SMTP_HOST = "localhost"; SMTP_PORT = 25; SMTP_FROM_NAME = "Mealie"; SMTP_FROM_EMAIL = "mealie@${home_domain}"; SMTP_AUTH_STRATEGY = "NONE"; }; }; nextcloud = { enable = true; hostName = "nextcloud.home.technicalissues.us"; package = pkgs.nextcloud31; # Need to manually increment with every major upgrade. appstoreEnable = true; autoUpdateApps.enable = true; config = { adminuser = username; adminpassFile = config.sops.secrets.nextcloud_admin_pass.path; dbtype = "pgsql"; }; configureRedis = true; database.createLocally = true; #extraApps = with config.services.nextcloud.package.packages.apps; { # # List of apps we want to install and are already packaged in # # https://github.com/NixOS/nixpkgs/blob/master/pkgs/servers/nextcloud/packages/nextcloud-apps.json # inherit calendar contacts cookbook maps notes tasks; #}; #extraAppsEnable = true; home = "/orico/nextcloud"; https = true; maxUploadSize = "100G"; # Increase the PHP maximum file upload size phpOptions."opcache.interned_strings_buffer" = "16"; # Suggested by Nextcloud's health check. settings = { default_phone_region = "US"; # https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/config_sample_php_parameters.html#enabledpreviewproviders enabledPreviewProviders = [ "OC\\Preview\\BMP" "OC\\Preview\\GIF" "OC\\Preview\\JPEG" "OC\\Preview\\Krita" "OC\\Preview\\MarkDown" "OC\\Preview\\MP3" "OC\\Preview\\OpenDocument" "OC\\Preview\\PNG" "OC\\Preview\\TXT" "OC\\Preview\\XBitmap" "OC\\Preview\\HEIC" "OC\\Preview\\Movie" ]; log_type = "file"; maintenance_window_start = 5; overwriteProtocol = "https"; "profile.enabled" = true; }; }; nginx = { enable = true; recommendedGzipSettings = true; recommendedOptimisation = true; recommendedProxySettings = true; recommendedTlsSettings = true; appendHttpConfig = '' # Add HSTS header with preloading to HTTPS requests. # Adding this header to HTTP requests is discouraged map $scheme $hsts_header { https "max-age=31536000;"; } add_header Strict-Transport-Security $hsts_header; ''; virtualHosts = { "${home_domain}" = { default = true; serverAliases = [ "mail.${home_domain}" "nix-tester.${home_domain}" ]; listen = [ { port = https_port; addr = "0.0.0.0"; ssl = true; } ]; enableACME = true; acmeRoot = null; forceSSL = true; locations."/" = { return = "200 '

Hello world ;)

'"; extraConfig = '' add_header Content-Type text/html; ''; }; locations."/server_status" = { extraConfig = '' stub_status; allow 127.0.0.1; deny all; ''; }; }; "ab.${home_domain}" = { listen = [{ port = https_port; addr = "0.0.0.0"; ssl = true; }]; enableACME = true; acmeRoot = null; forceSSL = true; locations."/".proxyWebsockets = true; locations."/".proxyPass = "http://${backend_ip}:13378"; extraConfig = '' client_max_body_size 0; ''; }; "atuin.${home_domain}" = { listen = [{ port = https_port; addr = "0.0.0.0"; ssl = true; }]; enableACME = true; acmeRoot = null; forceSSL = true; locations."/".proxyPass = "http://${backend_ip}:8888"; }; # budget.${home_domain} "${config.services.firefly-iii.virtualHost}".listen = [{ port = 3005; addr = "0.0.0.0"; ssl = false; }]; "${config.services.firefly-iii-data-importer.virtualHost}".listen = [{ port = 3006; addr = "0.0.0.0"; ssl = false; }]; "git.${home_domain}" = { listen = [{ port = https_port; addr = "0.0.0.0"; ssl = true; }]; enableACME = true; acmeRoot = null; forceSSL = true; locations."/".proxyPass = "http://${backend_ip}:3030"; extraConfig = '' client_max_body_size 0; ''; }; "immich.${home_domain}" = { listen = [{ port = https_port; addr = "0.0.0.0"; ssl = true; }]; enableACME = true; acmeRoot = null; forceSSL = true; locations."/".proxyPass = "http://${backend_ip}:2283"; locations."/".proxyWebsockets = true; extraConfig = '' client_max_body_size 0; proxy_read_timeout 600s; proxy_send_timeout 600s; send_timeout 600s; ''; }; "immich-kiosk.${home_domain}" = { listen = [{ port = https_port; addr = "0.0.0.0"; ssl = true; }]; enableACME = true; acmeRoot = null; forceSSL = true; basicAuthFile = config.sops.secrets.immich_kiosk_basic_auth.path; locations."/".proxyPass = "http://${backend_ip}:3001"; locations."/".proxyWebsockets = true; extraConfig = '' client_max_body_size 0; proxy_read_timeout 600s; proxy_send_timeout 600s; send_timeout 600s; ''; }; "jellyfin.${home_domain}" = { listen = [{ port = https_port; addr = "0.0.0.0"; ssl = true; }]; enableACME = true; acmeRoot = null; forceSSL = true; locations = { "/" = { proxyPass = "http://${backend_ip}:8096"; extraConfig = '' proxy_buffering off; proxy_set_header X-Forwarded-Protocol $scheme; ''; }; "/socket" = { proxyPass = "http://${backend_ip}:8096"; proxyWebsockets = true; extraConfig = '' proxy_set_header X-Forwarded-Protocol $scheme; ''; }; }; extraConfig = '' client_max_body_size 20M; ''; }; "mealie.${home_domain}" = { listen = [{ port = https_port; addr = "0.0.0.0"; ssl = true; }]; enableACME = true; acmeRoot = null; forceSSL = true; locations."/".proxyPass = "http://${backend_ip}:9000"; extraConfig = '' client_max_body_size 10M; ''; }; "monitoring.${home_domain}" = { listen = [{ port = https_port; addr = "0.0.0.0"; ssl = true; }]; enableACME = true; acmeRoot = null; forceSSL = true; locations."/grafana/".proxyPass = "http://${backend_ip}:3002/grafana/"; }; "nc.${home_domain}" = { listen = [{ port = https_port; addr = "0.0.0.0"; ssl = true; }]; enableACME = true; acmeRoot = null; forceSSL = true; extraConfig = '' client_max_body_size 0; underscores_in_headers on; ''; locations."/".proxyWebsockets = true; locations."/".proxyPass = "http://${mini_watcher}:8081"; locations."/".extraConfig = '' # these are added per https://www.nicemicro.com/tutorials/debian-snap-nextcloud.html add_header Front-End-Https on; proxy_headers_hash_max_size 512; proxy_headers_hash_bucket_size 64; proxy_buffering off; proxy_max_temp_file_size 0; ''; }; "nextcloud.${home_domain}" = { enableACME = true; acmeRoot = null; forceSSL = true; }; "onlyoffice.${home_domain}" = { listen = [{ port = https_port; addr = "0.0.0.0"; ssl = true; }]; enableACME = true; acmeRoot = null; forceSSL = true; locations."/".proxyWebsockets = true; locations."/".proxyPass = "http://${mini_watcher}:8888"; }; "readit.${home_domain}" = { listen = [{ port = https_port; addr = "0.0.0.0"; ssl = true; }]; enableACME = true; acmeRoot = null; forceSSL = true; locations."/".proxyPass = "http://${backend_ip}:8090"; }; }; }; postgresql = { enable = true; package = pkgs.postgresql_16; }; postgresqlBackup = { enable = true; backupAll = true; startAt = "*-*-* 23:00:00"; }; prometheus = { enable = true; checkConfig = "syntax-only"; globalConfig.scrape_interval = "10s"; # "1m" scrapeConfigs = [ { job_name = "hass"; scrape_interval = "30s"; metrics_path = "/api/prometheus"; static_configs = [{ targets = [ "192.168.22.22:8123" ]; }]; bearer_token_file = config.sops.secrets.home_assistant_token.path; } { job_name = "telegraf"; scrape_interval = "5s"; static_configs = [{ targets = [ "localhost:9273" ]; }]; } { job_name = "uptimekuma"; scheme = "https"; scrape_interval = "30s"; static_configs = [{ targets = [ "utk.technicalissues.us" ]; }]; basic_auth = { password_file = config.sops.secrets.uptimekuma_grafana_api_key.path; username = ""; }; } ]; }; resolved.enable = true; restic.backups.daily = { paths = [ config.services.forgejo.stateDir config.services.grafana.dataDir config.services.mealie.settings.DATA_DIR config.services.nextcloud.home "${config.users.users.${username}.home}/compose-files/wallabag" "/orico/immich/library" "/orico/jellyfin/data" "/orico/jellyfin/staging/downloaded-files" "/var/backup/postgresql" "/var/lib/prometheus2" ]; timerConfig = { OnCalendar = restic_backup_time; Persistent = true; }; }; smartd.enable = true; syncthing = { enable = true; dataDir = "/orico/syncthing"; openDefaultPorts = true; guiAddress = "0.0.0.0:8384"; }; tailscale = { enable = true; authKeyFile = config.sops.secrets.tailscale_key.path; extraUpFlags = [ "--advertise-exit-node" "--operator" "${username}" "--ssh" "--advertise-routes=192.168.20.0/22" ]; useRoutingFeatures = "both"; }; telegraf = { enable = true; extraConfig = { agent = { interval = "15s"; round_interval = true; collection_jitter = "0s"; debug = false; }; inputs = { cpu = { percpu = true; totalcpu = true; report_active = true; collect_cpu_time = true; }; disk = { ignore_fs = [ "aufs" "devfs" "devtmpfs" "iso9660" "overlay" "squashfs" "tmpfs" ]; }; diskio = { skip_serial_number = true; }; docker = { endpoint = "unix:///run/docker.sock"; container_name_include = []; storage_objects = [ "container" "volume" ]; perdevice_include = [ "cpu" "blkio" "network" ]; total_include = [ "cpu" "blkio" "network" ]; timeout = "5s"; gather_services = false; }; mem = {}; net = { ignore_protocol_stats = true; }; nginx = { insecure_skip_verify = true; urls = [ "https://127.0.0.1/server_status" ]; }; #smart = {}; system = {}; #systemd_units = {}; zfs = { datasetMetrics = true; poolMetrics = true; }; }; outputs = { prometheus_client = { collectors_exclude = [ "gocollector" "process" ]; listen = ":9273"; }; }; }; }; zfs.autoScrub.enable = true; }; sops = { age.keyFile = "${config.users.users.${username}.home}/.config/sops/age/keys.txt"; defaultSopsFile = ./secrets.yaml; secrets = { firefly_app_key = { owner = config.services.firefly-iii.user; restartUnits = ["nginx.service"]; }; firefly_pat_data_import = { owner = config.services.firefly-iii-data-importer.user; restartUnits = [ "firefly-iii-data-importer-setup.service" "phpfpm-firefly-iii-data-importer.service" ]; }; firefly_simplefin_token = { owner = config.services.firefly-iii-data-importer.user; restartUnits = [ "firefly-iii-data-importer-setup.service" "phpfpm-firefly-iii-data-importer.service" ]; }; firefly_vanity_url = { owner = config.services.firefly-iii-data-importer.user; restartUnits = [ "firefly-iii-data-importer-setup.service" "phpfpm-firefly-iii-data-importer.service" ]; }; home_assistant_token = { owner = config.users.users.prometheus.name; restartUnits = ["prometheus.service"]; }; immich_kiosk_basic_auth = { owner = config.users.users.nginx.name; restartUnits = ["nginx.service"]; }; local_git_config = { owner = "${username}"; path = "${config.users.users.${username}.home}/.gitconfig-local"; }; local_private_env = { owner = "${username}"; path = "${config.users.users.${username}.home}/.private-env"; }; mealie = { mode = "0444"; restartUnits = ["mealie.service"]; }; nextcloud_admin_pass.owner = config.users.users.nextcloud.name; tailscale_key = { restartUnits = [ "tailscaled-autoconnect.service" ]; }; uptimekuma_grafana_api_key = { owner = config.users.users.prometheus.name; restartUnits = ["prometheus.service"]; sopsFile = ../../common/secrets.yaml; }; }; }; systemd.services = { jellyfin.environment.LIBVA_DRIVER_NAME = "iHD"; "mealie" = { requires = ["postgresql.service"]; after = ["postgresql.service"]; }; "nextcloud-setup" = { requires = ["postgresql.service"]; after = ["postgresql.service"]; }; }; users.users.${username} = { isNormalUser = true; description = "Gene Liverman"; extraGroups = [ "docker" "podman" "networkmanager" "wheel" ]; linger = true; openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFvLaPTfG3r+bcbI6DV4l69UgJjnwmZNCQk79HXyf1Pt gene@rainbow-planet" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ6bRxR9wmwO1AcKjO2gRk6oxbIoDLI3KQL7sj92sN0K Gene on BigBoy" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIp42X5DZ713+bgbOO+GXROufUFdxWo7NjJbGQ285x3N gene.liverman@ltnglobal.com" ]; }; users.users.telegraf.extraGroups = [ "docker" ]; # Enable common container config files in /etc/containers virtualisation.containers.enable = true; virtualisation.oci-containers.backend = "podman"; # Compose based apps were crashing with podman compose, so back to Docker... virtualisation.docker.enable = true; virtualisation.docker.package = pkgs.docker; virtualisation.podman = { enable = true; autoPrune.enable = true; #dockerCompat = true; extraPackages = [ pkgs.zfs ]; # Required if the host is running ZFS # Required for container networking to be able to use names. defaultNetwork.settings.dns_enabled = true; }; }