├── .envrc ├── .github └── workflows │ ├── push.yml │ └── update-flake-lock.yml ├── .gitignore ├── LICENSE ├── README.md ├── bin └── generateServicesTable.nix ├── flake.lock ├── flake.nix ├── justfile └── modules ├── devshell.nix ├── dots ├── kitty │ └── default.nix ├── neofetch │ ├── config.conf │ └── default.nix ├── nvim │ ├── coc.nix │ └── default.nix ├── tmux │ └── default.nix └── zsh │ └── default.nix ├── homelab ├── backup │ └── default.nix ├── default.nix ├── fail2ban-cloudflare │ └── default.nix ├── motd │ └── default.nix ├── networks │ └── default.nix ├── samba │ └── default.nix └── services │ ├── arr │ ├── bazarr │ │ └── default.nix │ ├── jellyseerr │ │ └── default.nix │ ├── lidarr │ │ └── default.nix │ ├── prowlarr │ │ └── default.nix │ ├── radarr │ │ └── default.nix │ └── sonarr │ │ └── default.nix │ ├── audiobookshelf │ └── default.nix │ ├── deemix │ ├── default.nix │ └── service.nix │ ├── default.nix │ ├── deluge │ └── default.nix │ ├── homepage │ └── default.nix │ ├── immich │ └── default.nix │ ├── invoiceplane │ ├── default.nix │ └── template.nix │ ├── jellyfin │ └── default.nix │ ├── keycloak │ ├── default.nix │ ├── theme.nix │ ├── themes │ │ └── notthebee │ │ │ └── login │ │ │ ├── resources │ │ │ ├── background.jpg │ │ │ └── css │ │ │ │ └── styles.css │ │ │ └── theme.properties │ └── trusted-device.nix │ ├── microbin │ ├── default.nix │ ├── nord.css │ └── nord_ui.css │ ├── miniflux │ └── default.nix │ ├── monitoring │ ├── grafana │ │ └── default.nix │ └── prometheus │ │ ├── default.nix │ │ └── exporters │ │ └── shelly_plug_exporter │ │ ├── default.nix │ │ ├── metrics.patch │ │ └── package.nix │ ├── navidrome │ └── default.nix │ ├── nextcloud │ └── default.nix │ ├── paperless-ngx │ └── default.nix │ ├── radicale │ └── default.nix │ ├── sabnzbd │ └── default.nix │ ├── slskd │ ├── beets.nix │ └── default.nix │ ├── smarthome │ ├── homeassistant │ │ └── default.nix │ └── raspberrymatic │ │ └── default.nix │ ├── uptime-kuma │ └── default.nix │ ├── vaultwarden │ └── default.nix │ └── wireguard-netns │ └── default.nix ├── machines ├── darwin │ ├── _common │ │ └── default.nix │ ├── default.nix │ └── meredith │ │ ├── configuration.nix │ │ ├── secrets.nix │ │ └── system.nix ├── installer │ └── default.nix └── nixos │ ├── _common │ ├── default.nix │ ├── filesystems │ │ ├── default.nix │ │ └── snapraid.nix │ ├── monitoring │ │ └── default.nix │ └── nix │ │ └── default.nix │ ├── alison │ ├── configuration.nix │ ├── disko.nix │ ├── filesystems │ │ └── default.nix │ ├── router │ │ ├── default.nix │ │ ├── dns.nix │ │ ├── firewall.nix │ │ └── tailscale.nix │ └── secrets │ │ └── default.nix │ ├── aria │ ├── backup │ │ └── default.nix │ ├── configuration.nix │ ├── disko.nix │ ├── filesystems │ │ ├── default.nix │ │ └── snapraid.nix │ ├── homelab │ │ └── default.nix │ ├── secrets │ │ └── default.nix │ └── syncthing │ │ └── default.nix │ ├── default.nix │ ├── emily │ ├── backup │ │ └── default.nix │ ├── configuration.nix │ ├── disko.nix │ ├── filesystems │ │ ├── default.nix │ │ └── snapraid.nix │ ├── homelab │ │ └── default.nix │ └── secrets │ │ └── default.nix │ ├── maya │ ├── boot.nix │ ├── configuration.nix │ ├── disko.nix │ ├── lact.nix │ ├── lact │ │ └── config.yaml │ └── no-rgb.nix │ └── spencer │ ├── configuration.nix │ ├── matrix.nix │ ├── plausible.nix │ ├── secrets.nix │ └── wireguard.nix ├── misc ├── agenix │ └── default.nix ├── email │ └── default.nix ├── lgtv │ └── default.nix ├── mover │ ├── default.nix │ └── mergerfs-uncache.py ├── notthebe.ee │ └── default.nix ├── ryzen-undervolting │ ├── default.nix │ └── package.nix ├── tailscale │ └── default.nix ├── tg-notify │ └── default.nix ├── withings2intervals │ ├── default.nix │ └── package.nix └── zfs-root │ ├── boot.nix │ ├── default.nix │ ├── fileSystems.nix │ └── monitoring.nix └── users └── notthebee ├── age.nix ├── default.nix ├── dots.nix └── gitconfig.nix /.envrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | git pull 3 | use flake .#default 4 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: push 2 | on: 3 | workflow_dispatch: 4 | push: 5 | pull_request_review: 6 | 7 | jobs: 8 | lint: 9 | runs-on: ubuntu-latest 10 | environment: production 11 | steps: 12 | - name: Access private repository 13 | uses: webfactory/ssh-agent@master 14 | with: 15 | ssh-private-key: ${{ secrets.GH_PRIVATE_KEY }} 16 | - uses: actions/checkout@v4 17 | - uses: DeterminateSystems/nix-installer-action@v19 18 | - name: Run the Magic Nix Cache 19 | uses: DeterminateSystems/magic-nix-cache-action@v13 20 | - run: git config --global --add safe.directory "${PWD}" 21 | - run: nix flake check --accept-flake-config 22 | 23 | generate-readme: 24 | runs-on: ubuntu-latest 25 | environment: production 26 | steps: 27 | - name: Access private repository 28 | uses: webfactory/ssh-agent@master 29 | with: 30 | ssh-private-key: ${{ secrets.GH_PRIVATE_KEY }} 31 | - uses: actions/checkout@v4 32 | - uses: DeterminateSystems/nix-installer-action@v19 33 | - name: Run the Magic Nix Cache 34 | uses: DeterminateSystems/magic-nix-cache-action@v13 35 | - run: mkdir newreadme 36 | - run: (grep -B 99999 "BEGIN SERVICE LIST" README.md && nix eval --offline --raw --file bin/generateServicesTable.nix && grep -A 99999 "END SERVICE LIST" README.md) > newreadme/README.md 37 | - uses: actions/upload-artifact@v4 38 | with: 39 | name: new-readme 40 | path: newreadme 41 | 42 | push-readme: 43 | runs-on: ubuntu-latest 44 | environment: production 45 | needs: 46 | - lint 47 | - generate-readme 48 | steps: 49 | - uses: actions/checkout@v4 50 | - uses: actions/download-artifact@v4 51 | with: 52 | name: new-readme 53 | path: newreadme 54 | - run: mv newreadme/README.md README.md 55 | - name: Commit and push README 56 | run: | 57 | git config user.name github-actions 58 | git config user.email github-actions@github.com 59 | git add README.md 60 | git diff-index --quiet HEAD || git commit -m "chore: Generate README.md" 61 | git push 62 | -------------------------------------------------------------------------------- /.github/workflows/update-flake-lock.yml: -------------------------------------------------------------------------------- 1 | name: update-flake-lock 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '0 3 * * *' 6 | 7 | jobs: 8 | update-flake-lock: 9 | runs-on: ubuntu-latest 10 | environment: production 11 | steps: 12 | - name: Access private repository 13 | uses: webfactory/ssh-agent@master 14 | with: 15 | ssh-private-key: ${{ secrets.GH_PRIVATE_KEY }} 16 | - uses: actions/checkout@v4 17 | - uses: DeterminateSystems/nix-installer-action@v12 18 | - id: update 19 | uses: DeterminateSystems/update-flake-lock@v23 20 | with: 21 | commit-msg: "chore(flake.lock): update" 22 | pr-title: "chore(flake.lock): update" 23 | pr-labels: auto-merge 24 | token: "${{ secrets.GITHUB_TOKEN }}" 25 | - name: Merge 26 | run: gh pr merge --auto "${{ steps.update.outputs.pull-request-number }}" --rebase 27 | env: 28 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 29 | if: ${{ steps.update.outputs.pull-request-number != '' }} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .Trash-1000 3 | ..bfg-report 4 | .DS_Store 5 | /secrets 6 | .direnv 7 | .env 8 | result 9 | -------------------------------------------------------------------------------- /bin/generateServicesTable.nix: -------------------------------------------------------------------------------- 1 | with builtins.getFlake (toString ../.); 2 | let 3 | lib = import ; 4 | hl = hostname: nixosConfigurations.${hostname}.config.homelab; 5 | enabledHomepageServices = 6 | let 7 | services = hostname: builtins.filter (x: x != "enable") (builtins.attrNames (hl hostname).services); 8 | in 9 | hostname: 10 | builtins.filter (x: x != null) ( 11 | builtins.map ( 12 | x: 13 | if ((hl hostname).services.${x}.enable && (hl hostname).services.${x} ? homepage) then x else null 14 | ) (services hostname) 15 | ); 16 | homepageServicesData = 17 | hostname: 18 | builtins.map ( 19 | service: 20 | let 21 | format = icon: if lib.strings.hasSuffix "svg" icon then "svg" else "png"; 22 | iconlink = 23 | icon: 24 | ""; 25 | serviceConfig = (hl hostname).services.${service}.homepage; 26 | in 27 | "|${iconlink serviceConfig.icon}|${serviceConfig.name}|${serviceConfig.description}|${serviceConfig.category}|" 28 | ) (enabledHomepageServices hostname); 29 | allHostsServiceData = 30 | let 31 | homelabHostnames = builtins.filter (x: x != null) ( 32 | builtins.map (hostname: if (hl hostname).enable then hostname else null) ( 33 | builtins.attrNames nixosConfigurations 34 | ) 35 | ); 36 | in 37 | builtins.map ( 38 | hostname: 39 | lib.strings.concatLines [ 40 | "### ${hostname}" 41 | "|Icon|Name|Description|Category|" 42 | "|---|---|---|---|" 43 | (lib.strings.concatLines (homepageServicesData hostname)) 44 | ] 45 | ) homelabHostnames; 46 | in 47 | lib.strings.concatLines allHostsServiceData 48 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | nixConfig = { 3 | trusted-substituters = [ 4 | "https://cachix.cachix.org" 5 | "https://nixpkgs.cachix.org" 6 | "https://nix-community.cachix.org" 7 | ]; 8 | trusted-public-keys = [ 9 | "cachix.cachix.org-1:eWNHQldwUO7G2VkjpnjDbWwy4KQ/HNxht7H4SSoMckM=" 10 | "nixpkgs.cachix.org-1:q91R6hxbwFvDqTSDKwDAV4T5PxqXGxswD8vhONFMeOE=" 11 | "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" 12 | "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" 13 | ]; 14 | }; 15 | inputs = { 16 | nixpkgs = { 17 | url = "github:nixos/nixpkgs/nixos-25.05?shallow=true"; 18 | }; 19 | nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable?shallow=true"; 20 | flake-parts = { 21 | url = "github:hercules-ci/flake-parts"; 22 | inputs.nixpkgs-lib.follows = "nixpkgs"; 23 | }; 24 | nixvim = { 25 | url = "github:nix-community/nixvim?shallow=true"; 26 | inputs.nixpkgs.follows = "nixpkgs-unstable"; 27 | }; 28 | treefmt-nix = { 29 | url = "github:numtide/treefmt-nix"; 30 | inputs.nixpkgs.follows = "nixpkgs"; 31 | }; 32 | home-manager = { 33 | url = "github:nix-community/home-manager/release-25.05?shallow=true"; 34 | inputs.nixpkgs.follows = "nixpkgs"; 35 | }; 36 | home-manager-unstable = { 37 | url = "github:nix-community/home-manager/master?shallow=true"; 38 | inputs.nixpkgs.follows = "nixpkgs-unstable"; 39 | }; 40 | autoaspm = { 41 | url = "github:notthebee/AutoASPM"; 42 | inputs.nixpkgs.follows = "nixpkgs"; 43 | }; 44 | agenix = { 45 | url = "github:ryantm/agenix?shallow=true"; 46 | inputs.nixpkgs.follows = "nixpkgs"; 47 | }; 48 | nix-darwin = { 49 | url = "github:LnL7/nix-darwin/master?shallow=true"; 50 | inputs.nixpkgs.follows = "nixpkgs-unstable"; 51 | }; 52 | adios-bot = { 53 | url = "github:notthebee/adiosbot?shallow=true"; 54 | inputs.nixpkgs.follows = "nixpkgs"; 55 | }; 56 | nix-index-database = { 57 | url = "github:Mic92/nix-index-database?shallow=true"; 58 | inputs.nixpkgs.follows = "nixpkgs"; 59 | }; 60 | secrets = { 61 | url = "git+ssh://git@github.com/notthebee/nix-private.git"; 62 | flake = false; 63 | }; 64 | jovian = { 65 | url = "github:Jovian-Experiments/Jovian-NixOS?shallow=true"; 66 | inputs.nixpkgs.follows = "nixpkgs-unstable"; 67 | }; 68 | alga = { 69 | url = "github:Tenzer/alga?shallow=true"; 70 | inputs.nixpkgs.follows = "nixpkgs-unstable"; 71 | }; 72 | }; 73 | 74 | outputs = 75 | inputs@{ flake-parts, ... }: 76 | inputs.flake-parts.lib.mkFlake { inherit inputs; } ( 77 | { ... }: 78 | { 79 | systems = [ 80 | "x86_64-linux" 81 | "aarch64-darwin" 82 | ]; 83 | imports = [ 84 | ./modules/machines/nixos 85 | ./modules/machines/darwin 86 | ./modules/devshell.nix 87 | ]; 88 | _module.args.rootPath = ./.; 89 | } 90 | ); 91 | 92 | } 93 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # vim: set ft=make : 2 | set quiet 3 | 4 | update: 5 | nix flake update 6 | 7 | build-iso $host: 8 | just copy {{ host }}; ssh {{ host }} "nix-shell -p nixos-generators.out --run 'nixos-generate -c /etc/nixos/machines/installer/default.nix -f install-iso -I nixpkgs=channel:nixos-25.05'" 9 | 10 | check: 11 | nix flake check 12 | 13 | dry-run $host: 14 | nixos-rebuild-ng dry-activate --flake .#{{host}} --target-host {{host}} --build-host {{host}} --no-reexec --sudo 15 | 16 | deploy $host: (copy host) 17 | nixos-rebuild-ng switch --flake .#{{host}} --target-host {{host}} --build-host {{host}} --no-reexec --sudo 18 | 19 | check-clean: 20 | if [ -n "$(git status --porcelain)" ]; then echo -e "\e[31merror\e[0m: git tree is dirty. Refusing to copy configuration." >&2; exit 1; fi 21 | 22 | copy $host: 23 | rsync -ax --delete --rsync-path="sudo rsync" ./ {{host}}:/etc/nixos/ 24 | -------------------------------------------------------------------------------- /modules/devshell.nix: -------------------------------------------------------------------------------- 1 | { inputs, ... }: 2 | { 3 | systems = [ 4 | "aarch64-darwin" 5 | ]; 6 | imports = [ inputs.treefmt-nix.flakeModule ]; 7 | perSystem = 8 | { pkgs, ... }: 9 | { 10 | treefmt = { 11 | projectRootFile = "flake.nix"; 12 | settings.global.excludes = [ 13 | "*.lock" 14 | ".gitignore" 15 | "secrets/*" 16 | ]; 17 | programs.nixfmt.enable = true; 18 | programs.nixfmt.package = pkgs.nixfmt-rfc-style; 19 | programs.deadnix.enable = true; 20 | programs.shellcheck.enable = true; 21 | }; 22 | packages.default = pkgs.mkShell { 23 | packages = [ 24 | pkgs.just 25 | pkgs.nixos-rebuild-ng 26 | ]; 27 | }; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /modules/dots/kitty/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | programs.kitty = { 3 | enable = true; 4 | themeFile = "Nord"; 5 | keybindings = { 6 | "cmd+w" = "no_op"; 7 | "cmd+t" = "no_op"; 8 | }; 9 | shellIntegration = { 10 | enableZshIntegration = true; 11 | }; 12 | settings = { 13 | macos_titlebar_color = "#2E3440"; 14 | window_padding_width = 0; 15 | window_margin_width = 0; 16 | adjust_line_height = "120%"; 17 | font_family = "Comic Code Ligatures"; 18 | font_size = "16.0"; 19 | shell = "/run/current-system/sw/bin/tmux"; 20 | }; 21 | extraConfig = '' 22 | # "Nerd Fonts - Pomicons" 23 | symbol_map U+E000-U+E00D Symbols Nerd Font 24 | 25 | # "Nerd Fonts - Powerline" 26 | symbol_map U+e0a0-U+e0a2,U+e0b0-U+e0b3 Symbols Nerd Font 27 | 28 | # "Nerd Fonts - Powerline Extra" 29 | symbol_map U+e0a3-U+e0a3,U+e0b4-U+e0c8,U+e0cc-U+e0d2,U+e0d4-U+e0d4 Symbols Nerd Font 30 | 31 | # "Nerd Fonts - Symbols original" 32 | symbol_map U+e5fa-U+e62b Symbols Nerd Font 33 | 34 | # "Nerd Fonts - Devicons" 35 | symbol_map U+e700-U+e7c5 Symbols Nerd Font 36 | 37 | # "Nerd Fonts - Font awesome" 38 | symbol_map U+f000-U+f2e0 Symbols Nerd Font 39 | 40 | # "Nerd Fonts - Font awesome extension" 41 | symbol_map U+e200-U+e2a9 Symbols Nerd Font 42 | 43 | # "Nerd Fonts - Octicons" 44 | symbol_map U+f400-U+f4a8,U+2665-U+2665,U+26A1-U+26A1,U+f27c-U+f27c Symbols Nerd Font 45 | 46 | # "Nerd Fonts - Font Linux" 47 | symbol_map U+F300-U+F313 Symbols Nerd Font 48 | 49 | # Nerd Fonts - Font Power Symbols" 50 | symbol_map U+23fb-U+23fe,U+2b58-U+2b58 Symbols Nerd Font 51 | 52 | # "Nerd Fonts - Material Design Icons" 53 | symbol_map U+f500-U+fd46 Symbols Nerd Font 54 | 55 | # "Nerd Fonts - Weather Icons" 56 | symbol_map U+e300-U+e3eb Symbols Nerd Font 57 | 58 | # Misc Code Point Fixes 59 | symbol_map U+21B5,U+25B8,U+2605,U+2630,U+2632,U+2714,U+E0A3,U+E615,U+E62B Symbols Nerd Font 60 | ''; 61 | 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /modules/dots/neofetch/config.conf: -------------------------------------------------------------------------------- 1 | print_info() { 2 | info title 3 | info "OS" distro 4 | info "Host" model 5 | info "Kernel" kernel 6 | info "Uptime" uptime 7 | info "Packages" packages 8 | info "Terminal" term 9 | info "Shell" shell 10 | info "CPU" cpu 11 | info "Memory" memory 12 | } 13 | kernel_shorthand="on" 14 | distro_shorthand="off" 15 | os_arch="on" 16 | uptime_shorthand="on" 17 | shell_path="off" 18 | shell_version="off" 19 | speed_type="bios_limit" 20 | speed_shorthand="on" 21 | cpu_brand="on" 22 | cpu_speed="on" 23 | cpu_cores="off" 24 | cpu_temp="off" 25 | gpu_brand="on" 26 | gpu_type="all" 27 | refresh_rate="off" 28 | gtk_shorthand="off" 29 | gtk2="on" 30 | gtk3="on" 31 | public_ip_host="http://ident.me" 32 | disk_show=('/') 33 | disk_subtitle="mount" 34 | song_shorthand="off" 35 | install_time="on" 36 | install_time_format="12h" 37 | colors=distro 38 | bold="on" 39 | underline_enabled="on" 40 | underline_char="-" 41 | block_range=(0 7) 42 | color_blocks="off" 43 | block_width=3 44 | block_height=1 45 | bar_char_elapsed="-" 46 | bar_char_total="=" 47 | bar_border="on" 48 | bar_length=15 49 | bar_color_elapsed="distro" 50 | bar_color_total="distro" 51 | cpu_display="off" 52 | memory_display="off" 53 | battery_display="off" 54 | disk_display="off" 55 | image_backend="ascii" 56 | image_source="auto" 57 | ascii_colors=distro 58 | ascii_bold="on" 59 | image_loop="off" 60 | thumbnail_dir="${XDG_CACHE_HOME:-${HOME}/.cache}/thumbnails/neofetch" 61 | crop_mode="normal" 62 | crop_offset="center" 63 | image_size="auto" 64 | gap=3 65 | yoffset=0 66 | xoffset=0 67 | background_color= 68 | scrot="off" 69 | scrot_cmd="auto" 70 | scrot_name="neofetch-$(date +%F-%I-%M-%S-${RANDOM}).png" 71 | image_host="teknik" 72 | stdout="off" 73 | config_version="3.3.0" 74 | 75 | -------------------------------------------------------------------------------- /modules/dots/neofetch/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | { 3 | home.packages = with pkgs; [ 4 | neofetch 5 | ]; 6 | xdg.configFile = { 7 | "neofetch/config.conf" = { 8 | source = ./config.conf; 9 | }; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /modules/dots/nvim/coc.nix: -------------------------------------------------------------------------------- 1 | { homeDir, pkgs }: 2 | { 3 | languageserver.terraform = { 4 | command = "terraform-ls"; 5 | args = [ "serve" ]; 6 | filetypes = [ "tf" ]; 7 | initializationOptions = { }; 8 | }; 9 | eslint.autoFixOnSave = true; 10 | inlayHint.enable = false; 11 | coc.preferences.colorSupport = false; 12 | prettier.disableSuccessMessage = true; 13 | coc.preferences.formatOnSaveFiletypes = [ 14 | "css" 15 | "javascript" 16 | "javascriptreact" 17 | "typescript" 18 | "typescriptreact" 19 | "nix" 20 | "python" 21 | "php" 22 | "markdown" 23 | "tf" 24 | ]; 25 | nil.server.path = "${pkgs.nil}/bin/nil"; 26 | nil.formatting.command = [ "${pkgs.nixfmt-rfc-style}/bin/nixfmt" ]; 27 | nil.diagnostics.excludedFiles = [ "generated.nix" ]; 28 | nil.nix.flake.autoEvalInputs = false; 29 | nil.nix.maxMemoryMB = 2048; 30 | nil.nix.binary = "${pkgs.writeShellScript "nil-nix-wrapper" '' 31 | nix --allow-import-from-derivation "$@" 32 | ''}"; 33 | links.tooltip = true; 34 | #semanticTokens.filetypes = [ "nix" ]; 35 | suggest.completionItemKindLabels = { 36 | variable = ""; 37 | constant = ""; 38 | struct = "פּ"; 39 | class = "ﴯ"; 40 | interface = ""; 41 | text = ""; 42 | enum = ""; 43 | enumMember = ""; 44 | color = ""; 45 | property = "ﰠ"; 46 | field = "ﰠ"; 47 | unit = "塞"; 48 | file = ""; 49 | value = ""; 50 | event = ""; 51 | folder = ""; 52 | keyword = ""; 53 | snippet = ""; 54 | operator = ""; 55 | reference = ""; 56 | typeParameter = ""; 57 | default = ""; 58 | }; 59 | suggest.noselect = false; 60 | diagnostic.warningSign = ""; 61 | diagnostic.errorSign = ""; 62 | diagnostic.infoSign = ""; 63 | python.jediEnabled = false; 64 | ansible.dev.serverPath = "${homeDir}/.nix-profile/bin/ansible-language-server"; 65 | ansible.builtin.isWithYamllint = true; 66 | ansible.disableProgressNotification = false; 67 | explorer.icon.enableNerdfont = true; 68 | explorer.width = 30; 69 | explorer.file.showHiddenFiles = true; 70 | explorer.openAction.strategy = "sourceWindow"; 71 | explorer.root.customRules = { 72 | vcs = { 73 | patterns = [ 74 | ".git" 75 | ".hg" 76 | ".projections.json" 77 | ]; 78 | }; 79 | vcs-r = { 80 | patterns = [ 81 | ".git" 82 | ".hg" 83 | ".projections.json" 84 | ]; 85 | bottomUp = true; 86 | }; 87 | }; 88 | explorer.root.strategies = [ 89 | "custom:vcs" 90 | "workspace" 91 | "cwd" 92 | ]; 93 | explorer.quitOnOpen = true; 94 | explorer.buffer.root.template = "[icon & 1] OPEN EDITORS"; 95 | explorer.file.reveal.auto = false; 96 | explorer.file.root.template = "[icon & 1] PROJECT ([root])"; 97 | explorer.file.child.template = "[git | 2] [selection | clip | 1] [indent][icon | 1] [diagnosticError & 1][filename omitCenter 1][modified][readonly] [linkIcon & 1][link growRight 1 omitCenter 5]"; 98 | explorer.keyMappings = { 99 | s = "open:vsplit"; 100 | mm = "rename"; 101 | mc = "copyFile"; 102 | C = "copyFile"; 103 | md = "delete"; 104 | D = "delete"; 105 | ma = "addFile"; 106 | mA = "addDirectory"; 107 | }; 108 | phpstan.level = "max"; 109 | } 110 | -------------------------------------------------------------------------------- /modules/dots/tmux/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | { 3 | home.packages = with pkgs; [ 4 | tmux 5 | ]; 6 | programs.tmux = { 7 | enable = true; 8 | sensibleOnTop = false; 9 | extraConfig = '' 10 | set -g default-terminal "xterm-256color" 11 | set -ag terminal-overrides ",xterm-256color:RGB" 12 | set-option -g default-shell ${pkgs.zsh}/bin/zsh 13 | set -g status-keys vi 14 | 15 | 16 | set-window-option -g mode-keys vi 17 | bind h select-pane -L 18 | bind j select-pane -D 19 | bind k select-pane -U 20 | bind l select-pane -R 21 | 22 | bind-key x kill-pane 23 | 24 | set -g set-titles-string ' #{pane_title} ' 25 | 26 | bind-key / copy-mode \; send-key ? 27 | 28 | bind -n M-Left select-pane -L 29 | bind -n M-Right select-pane -R 30 | bind -n M-Up select-pane -U 31 | bind -n M-Down select-pane -D 32 | set -g mouse on 33 | set-option -g visual-activity off 34 | set-option -g visual-bell off 35 | set-option -g visual-silence off 36 | set-window-option -g monitor-activity off 37 | set-window-option -g mode-style bg=0,fg=default,noreverse 38 | set-window-option -g window-status-current-style bg=green,fg=black 39 | setw -g window-status-format " #I:#W#F " 40 | setw -g window-status-current-format " #I:#W#F " 41 | set-window-option -g window-status-style fg=green 42 | set-option -g renumber-windows on 43 | 44 | bind-key r source-file ~/.tmux.conf \; display-message "tmux.conf reloaded." 45 | 46 | 47 | # remap prefix from 'C-b' to 'C-s' 48 | unbind C-b 49 | set -g prefix C-s 50 | bind-key C-s send-prefix 51 | 52 | set-option -g bell-action none 53 | set -g status-position bottom 54 | set -g status-justify left 55 | set -g status-bg colour8 56 | set -g status-fg blue 57 | set -g status-right ' #(cd #{pane_current_path}; git rev-parse --abbrev-ref HEAD)  #{=50:pane_current_path} %b %d %H:%M ' 58 | set -g status-right-length 200 59 | set -g status-left ''' 60 | set -sg escape-time 0 61 | 62 | set -g base-index 1 63 | setw -g pane-base-index 1 64 | set -g pane-border-format " #P: #{pane_current_command} " 65 | ''; 66 | plugins = with pkgs.tmuxPlugins; [ 67 | yank 68 | ]; 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /modules/dots/zsh/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | config, 4 | inputs, 5 | lib, 6 | ... 7 | }: 8 | { 9 | home.packages = with pkgs; [ grc ]; 10 | age.secrets = lib.mkIf (pkgs.system == "aarch64-darwin") { 11 | bwSession.file = "${inputs.secrets}/bwSession.age"; 12 | }; 13 | 14 | programs = { 15 | fzf = { 16 | enable = true; 17 | enableZshIntegration = true; 18 | colors = { 19 | fg = "#D8DEE9"; 20 | bg = "#2E3440"; 21 | hl = "#A3BE8C"; 22 | "fg+" = "#D8DEE9"; 23 | "bg+" = "#434C5E"; 24 | "hl+" = "#A3BE8C"; 25 | pointer = "#BF616A"; 26 | info = "#4C566A"; 27 | spinner = "#4C566A"; 28 | header = "#4C566A"; 29 | prompt = "#81A1C1"; 30 | marker = "#EBCB8B"; 31 | }; 32 | }; 33 | starship = { 34 | enable = true; 35 | settings = { 36 | add_newline = false; 37 | gcloud = { 38 | detect_env_vars = [ "GOOGLE_CLOUD" ]; 39 | }; 40 | aws = { 41 | disabled = true; 42 | }; 43 | }; 44 | }; 45 | zoxide = { 46 | enable = true; 47 | enableZshIntegration = true; 48 | options = [ "--cmd cd" ]; 49 | }; 50 | 51 | direnv = { 52 | enable = true; 53 | enableZshIntegration = true; 54 | }; 55 | 56 | zsh = { 57 | enable = true; 58 | enableCompletion = false; 59 | zplug = { 60 | enable = true; 61 | plugins = [ 62 | { name = "zsh-users/zsh-autosuggestions"; } 63 | { name = "zsh-users/zsh-syntax-highlighting"; } 64 | { name = "zsh-users/zsh-completions"; } 65 | { name = "zsh-users/zsh-history-substring-search"; } 66 | { name = "unixorn/warhol.plugin.zsh"; } 67 | ]; 68 | }; 69 | shellAliases = { 70 | la = "ls --color -lha"; 71 | df = "df -h"; 72 | du = "du -ch"; 73 | ipp = "curl ipinfo.io/ip"; 74 | yh = "yt-dlp --continue --no-check-certificate --format=bestvideo+bestaudio --exec='ffmpeg -i {} -c:a copy -c:v copy {}.mkv && rm {}'"; 75 | yd = "yt-dlp --continue --no-check-certificate --format=bestvideo+bestaudio --exec='ffmpeg -i {} -c:v prores_ks -profile:v 1 -vf fps=25/1 -pix_fmt yuv422p -c:a pcm_s16le {}.mov && rm {}'"; 76 | ya = "yt-dlp --continue --no-check-certificate --format=bestaudio -x --audio-format wav"; 77 | aspm = "sudo lspci -vv | awk '/ASPM/{print $0}' RS= | grep --color -P '(^[a-z0-9:.]+|ASPM )'"; 78 | mkdir = "mkdir -p"; 79 | # Only do `nix flake update` if flake.lock hasn't been updated within an hour 80 | deploy-nix = "f() { if [[ $(find . -mmin -60 -type f -name flake.lock | wc -c) -eq 0 ]]; then nix flake update; fi && deploy .#$1 --remote-build -s --auto-rollback false && rsync -ax --delete ./ $1:/etc/nixos/ };f"; 81 | }; 82 | 83 | initContent = '' 84 | # Cycle back in the suggestions menu using Shift+Tab 85 | bindkey '^[[Z' reverse-menu-complete 86 | 87 | bindkey '^B' autosuggest-toggle 88 | # Make Ctrl+W remove one path segment instead of the whole path 89 | WORDCHARS=''${WORDCHARS/\/} 90 | 91 | # Highlight the selected suggestion 92 | zstyle ':completion:*' list-colors ''${(s.:.)LS_COLORS} 93 | zstyle ':completion:*' menu yes=long select 94 | 95 | ${ 96 | if (pkgs.system == "aarch64-darwin") then 97 | '' 98 | path=("$HOME/.nix-profile/bin" "/run/wrappers/bin" "/etc/profiles/per-user/$USER/bin" "/nix/var/nix/profiles/default/bin" "/run/current-system/sw/bin" "/opt/homebrew/bin" $path) 99 | export BW_SESSION=$(${pkgs.coreutils}/bin/cat ${config.age.secrets.bwSession.path}) 100 | export DOCKER_HOST="unix://$HOME/.colima/default/docker.sock" 101 | alias lsblk="diskutil list" 102 | ulimit -n 2048 103 | '' 104 | else 105 | "" 106 | } 107 | 108 | export EDITOR=nvim || export EDITOR=vim 109 | export LANG=en_US.UTF-8 110 | export LC_CTYPE=en_US.UTF-8 111 | export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES 112 | 113 | source $ZPLUG_HOME/repos/unixorn/warhol.plugin.zsh/warhol.plugin.zsh 114 | bindkey '^[[A' history-substring-search-up 115 | bindkey '^[[B' history-substring-search-down 116 | 117 | if command -v motd &> /dev/null 118 | then 119 | motd 120 | fi 121 | bindkey -e 122 | ''; 123 | }; 124 | }; 125 | } 126 | -------------------------------------------------------------------------------- /modules/homelab/default.nix: -------------------------------------------------------------------------------- 1 | { lib, config, ... }: 2 | let 3 | cfg = config.homelab; 4 | in 5 | { 6 | options.homelab = { 7 | enable = lib.mkEnableOption "The homelab services and configuration variables"; 8 | mounts.slow = lib.mkOption { 9 | default = "/mnt/mergerfs_slow"; 10 | type = lib.types.path; 11 | description = '' 12 | Path to the 'slow' tier mount 13 | ''; 14 | }; 15 | mounts.fast = lib.mkOption { 16 | default = "/mnt/cache"; 17 | type = lib.types.path; 18 | description = '' 19 | Path to the 'fast' tier mount 20 | ''; 21 | }; 22 | mounts.config = lib.mkOption { 23 | default = "/persist/opt/services"; 24 | type = lib.types.path; 25 | description = '' 26 | Path to the service configuration files 27 | ''; 28 | }; 29 | mounts.merged = lib.mkOption { 30 | default = "/mnt/user"; 31 | type = lib.types.path; 32 | description = '' 33 | Path to the merged tier mount 34 | ''; 35 | }; 36 | user = lib.mkOption { 37 | default = "share"; 38 | type = lib.types.str; 39 | description = '' 40 | User to run the homelab services as 41 | ''; 42 | }; 43 | group = lib.mkOption { 44 | default = "share"; 45 | type = lib.types.str; 46 | description = '' 47 | Group to run the homelab services as 48 | ''; 49 | }; 50 | timeZone = lib.mkOption { 51 | default = "Europe/Berlin"; 52 | type = lib.types.str; 53 | description = '' 54 | Time zone to be used for the homelab services 55 | ''; 56 | }; 57 | baseDomain = lib.mkOption { 58 | default = ""; 59 | type = lib.types.str; 60 | description = '' 61 | Base domain name to be used to access the homelab services via Caddy reverse proxy 62 | ''; 63 | }; 64 | cloudflare.dnsCredentialsFile = lib.mkOption { 65 | type = lib.types.path; 66 | }; 67 | }; 68 | imports = [ 69 | ./backup 70 | ./services 71 | ./samba 72 | ./networks 73 | ./motd 74 | ./fail2ban-cloudflare 75 | ]; 76 | config = lib.mkIf cfg.enable { 77 | users = { 78 | groups.${cfg.group} = { 79 | gid = 993; 80 | }; 81 | users.${cfg.user} = { 82 | uid = 994; 83 | isSystemUser = true; 84 | group = cfg.group; 85 | }; 86 | }; 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /modules/homelab/fail2ban-cloudflare/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | config, 4 | pkgs, 5 | ... 6 | }: 7 | 8 | let 9 | cfg = config.services.fail2ban-cloudflare; 10 | in 11 | { 12 | options.services.fail2ban-cloudflare = { 13 | enable = lib.mkEnableOption { 14 | description = "Enable fail2ban-cloudflare"; 15 | }; 16 | apiKeyFile = lib.mkOption { 17 | description = "File containing your API key, scoped to Firewall Rules: Edit"; 18 | type = lib.types.str; 19 | example = lib.literalExpression '' 20 | Authorization: Bearer Qj06My1wXJEzcW46QCyjFbSMgVtwIGfX63Ki3NOj79o= 21 | ''' 22 | ''; 23 | }; 24 | zoneId = lib.mkOption { 25 | type = lib.types.str; 26 | }; 27 | jails = lib.mkOption { 28 | type = lib.types.attrsOf ( 29 | lib.types.submodule { 30 | options = { 31 | serviceName = lib.mkOption { 32 | example = "vaultwarden"; 33 | type = lib.types.str; 34 | }; 35 | failRegex = lib.mkOption { 36 | type = lib.types.str; 37 | example = "Login failed from IP: "; 38 | }; 39 | ignoreRegex = lib.mkOption { 40 | type = lib.types.str; 41 | default = ""; 42 | }; 43 | maxRetry = lib.mkOption { 44 | type = lib.types.int; 45 | default = 3; 46 | }; 47 | }; 48 | } 49 | ); 50 | }; 51 | }; 52 | config = lib.mkIf cfg.enable { 53 | services.fail2ban = { 54 | enable = true; 55 | extraPackages = [ 56 | pkgs.curl 57 | pkgs.jq 58 | ]; 59 | 60 | jails = lib.attrsets.mapAttrs (name: value: { 61 | settings = { 62 | bantime = "30d"; 63 | findtime = "1h"; 64 | enabled = true; 65 | backend = "systemd"; 66 | journalmatch = "_SYSTEMD_UNIT=${value.serviceName}.service"; 67 | port = "http,https"; 68 | filter = "${name}"; 69 | maxretry = 3; 70 | action = "cloudflare-token-agenix"; 71 | }; 72 | }) cfg.jails; 73 | }; 74 | 75 | environment.etc = lib.attrsets.mergeAttrsList [ 76 | (lib.attrsets.mapAttrs' ( 77 | name: value: 78 | (lib.nameValuePair ("fail2ban/filter.d/${name}.conf") ({ 79 | text = '' 80 | [Definition] 81 | failregex = ${value.failRegex} 82 | ignoreregex = ${value.ignoreRegex} 83 | ''; 84 | })) 85 | ) cfg.jails) 86 | { 87 | "fail2ban/action.d/cloudflare-token-agenix.conf".text = 88 | let 89 | notes = "Fail2Ban on ${config.networking.hostName}"; 90 | cfapi = "https://api.cloudflare.com/client/v4/zones/${cfg.zoneId}/firewall/access_rules/rules"; 91 | in 92 | '' 93 | [Definition] 94 | actionstart = 95 | actionstop = 96 | actioncheck = 97 | actionunban = id=$(curl -s -X GET "${cfapi}" \ 98 | -H @${cfg.apiKeyFile} -H "Content-Type: application/json" \ 99 | | jq -r '.result[] | select(.notes == "${notes}" and .configuration.target == "ip" and .configuration.value == "") | .id') 100 | if [ -z "$id" ]; then echo "id for cannot be found"; exit 0; fi; \ 101 | curl -s -X DELETE "${cfapi}/$id" \ 102 | -H @${cfg.apiKeyFile} -H "Content-Type: application/json" \ 103 | --data '{"cascade": "none"}' 104 | actionban = curl -X POST "${cfapi}" -H @${cfg.apiKeyFile} -H "Content-Type: application/json" --data '{"mode":"block","configuration":{"target":"ip","value":""},"notes":"${notes}"}' 105 | [Init] 106 | name = cloudflare-token-agenix 107 | ''; 108 | } 109 | ]; 110 | }; 111 | } 112 | -------------------------------------------------------------------------------- /modules/homelab/motd/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | let 8 | enabledNixosServices = lib.attrsets.mapAttrsToList (name: _value: name) ( 9 | lib.attrsets.filterAttrs ( 10 | name: value: 11 | value != "enable" && name != "backup" && value ? configDir && value ? enable && value.enable 12 | ) config.homelab.services 13 | ); 14 | monitoredServices = lib.lists.flatten ( 15 | lib.lists.forEach enabledNixosServices ( 16 | x: 17 | let 18 | svc = config.homelab.services.${x}; 19 | in 20 | if (svc ? monitoredServices) then svc.monitoredServices else [ x ] 21 | ) 22 | ); 23 | 24 | networkInterface = 25 | if lib.attrsets.hasAttrByPath [ config.networking.hostName ] config.homelab.networks.external then 26 | config.homelab.networks.external.${config.networking.hostName}.interface 27 | else 28 | ""; 29 | motd = pkgs.writeShellScriptBin "motd" '' 30 | #! /usr/bin/env bash 31 | source /etc/os-release 32 | RED="\e[31m" 33 | GREEN="\e[32m" 34 | YELLOW="\e[33m" 35 | BOLD="\e[1m" 36 | ENDCOLOR="\e[0m" 37 | LOAD1=`cat /proc/loadavg | awk {'print $1'}` 38 | LOAD5=`cat /proc/loadavg | awk {'print $2'}` 39 | LOAD15=`cat /proc/loadavg | awk {'print $3'}` 40 | 41 | MEMORY=`free -m | awk 'NR==2{printf "%s/%sMB (%.2f%%)\n", $3,$2,$3*100 / $2 }'` 42 | 43 | # time of day 44 | HOUR=$(date +"%H") 45 | if [ $HOUR -lt 12 -a $HOUR -ge 0 ] 46 | then TIME="morning" 47 | elif [ $HOUR -lt 17 -a $HOUR -ge 12 ] 48 | then TIME="afternoon" 49 | else 50 | TIME="evening" 51 | fi 52 | 53 | 54 | uptime=`cat /proc/uptime | cut -f1 -d.` 55 | upDays=$((uptime/60/60/24)) 56 | upHours=$((uptime/60/60%24)) 57 | upMins=$((uptime/60%60)) 58 | upSecs=$((uptime%60)) 59 | 60 | printf "$BOLD Welcome to $(hostname)!$ENDCOLOR\n" 61 | printf "\n" 62 | ${lib.strings.concatMapStrings (x: "${x}\n") ( 63 | lib.lists.forEach config.homelab.motd.networkInterfaces ( 64 | x: 65 | lib.strings.concatMapStrings (x: "${x}\n") ([ 66 | ( 67 | if x == "" then 68 | '' 69 | NETDEV=$(ip -o route get 8.8.8.8 | cut -f 5 -d " ") 70 | '' 71 | else 72 | '' 73 | NETDEV=${x} 74 | '' 75 | ) 76 | '' 77 | printf "$BOLD * %-20s$ENDCOLOR %s\n" "IPv4 $NETDEV" "$(ip -4 addr show $NETDEV | grep -oP '(?<=inet\s)\d+(\.\d+){3}')" 78 | '' 79 | ]) 80 | ) 81 | )} 82 | printf "$BOLD * %-20s$ENDCOLOR %s\n" "Release" "$PRETTY_NAME" 83 | printf "$BOLD * %-20s$ENDCOLOR %s\n" "Kernel" "$(uname -rs)" 84 | printf "\n" 85 | printf "$BOLD * %-20s$ENDCOLOR %s\n" "CPU usage" "$LOAD1, $LOAD5, $LOAD15 (1, 5, 15 min)" 86 | printf "$BOLD * %-20s$ENDCOLOR %s\n" "Memory" "$MEMORY" 87 | printf "$BOLD * %-20s$ENDCOLOR %s\n" "System uptime" "$upDays days $upHours hours $upMins minutes $upSecs seconds" 88 | 89 | printf "\n" 90 | printf "$BOLD Service status$ENDCOLOR\n" 91 | 92 | function get_service_status() { 93 | if systemctl is-failed "$1" | grep -q 'failed'; then 94 | printf "$RED• $ENDCOLOR%-50s $RED[failed]$ENDCOLOR\n" "$1" 95 | elif systemctl is-failed "$1" | grep -q 'active'; then 96 | printf "$GREEN• $ENDCOLOR%-50s $GREEN[active]$ENDCOLOR\n" "$1" 97 | else 98 | printf "$YELLOW• $ENDCOLOR%-50s $YELLOW[unknown]$ENDCOLOR\n" "$1" 99 | fi 100 | } 101 | ${lib.strings.concatStrings (lib.lists.forEach monitoredServices (x: "get_service_status ${x}\n"))} 102 | ''; 103 | in 104 | { 105 | options.homelab.motd = { 106 | enable = lib.mkEnableOption { 107 | description = "motd Greeting"; 108 | }; 109 | networkInterfaces = lib.mkOption { 110 | description = "Network interfaces to monitor"; 111 | type = lib.types.listOf lib.types.str; 112 | default = [ networkInterface ]; 113 | }; 114 | }; 115 | config = lib.mkIf config.homelab.motd.enable { 116 | environment.systemPackages = [ motd ]; 117 | }; 118 | } 119 | -------------------------------------------------------------------------------- /modules/homelab/networks/default.nix: -------------------------------------------------------------------------------- 1 | { lib, config, ... }: 2 | let 3 | cfg = config.homelab.networks; 4 | in 5 | { 6 | options.homelab.networks = { 7 | external = lib.mkOption { 8 | default = { }; 9 | example = lib.literalExpression '' 10 | hostname = { 11 | address = "192.168.2.2"; 12 | gateway = "192.168.2.1"; 13 | interface = "enp1s0"; 14 | }; 15 | ''; 16 | type = lib.types.attrsOf ( 17 | lib.types.submodule { 18 | options = { 19 | address = lib.mkOption { 20 | example = "192.168.2.2"; 21 | type = lib.types.str; 22 | }; 23 | gateway = lib.mkOption { 24 | example = "192.168.2.1"; 25 | type = lib.types.str; 26 | }; 27 | interface = lib.mkOption { 28 | example = "enp4s0"; 29 | type = lib.types.str; 30 | }; 31 | }; 32 | } 33 | ); 34 | }; 35 | local = lib.mkOption { 36 | default = { }; 37 | type = lib.types.attrsOf ( 38 | lib.types.submodule { 39 | options = { 40 | id = lib.mkOption { 41 | example = 1; 42 | type = lib.types.int; 43 | }; 44 | cidr.v4 = lib.mkOption { 45 | example = "192.168.2.1"; 46 | type = lib.types.str; 47 | }; 48 | cidr.v6 = lib.mkOption { 49 | example = "fd14:d122:ca4c::"; 50 | default = null; 51 | type = lib.types.nullOr lib.types.str; 52 | }; 53 | interface = lib.mkOption { 54 | example = "enp4s0"; 55 | type = lib.types.str; 56 | }; 57 | trusted = lib.mkOption { 58 | type = lib.types.bool; 59 | default = false; 60 | description = '' 61 | Whether the network should be trusted. 62 | Trusted networks can access all ports and hosts on the local network regardless of the firewall rules 63 | ''; 64 | }; 65 | dhcp.v4 = lib.mkOption { 66 | type = lib.types.bool; 67 | default = true; 68 | description = '' 69 | Whether to run a DHCPv4 server on the network 70 | ''; 71 | }; 72 | dhcp.v6 = lib.mkOption { 73 | type = lib.types.bool; 74 | default = cfg.cidr.ipv6; 75 | description = '' 76 | Whether to run a DHCPv6 server on the network 77 | ''; 78 | }; 79 | reservations = lib.mkOption { 80 | type = lib.types.attrs; 81 | default = { }; 82 | example = lib.literalExpression '' 83 | { 84 | optina = { MACAddress = "d4:3d:7e:4d:c4:7f"; Address = "10.40.33.20"; }; 85 | valaam = { MACAddress = "00:c0:08:9d:ba:42"; Address = "10.40.33.21"; }; 86 | atari = { MACAddress = "94:08:53:84:9b:9d"; Address = "10.40.33.22"; }; 87 | kodiak = { MACAddress = "ec:f4:bb:e7:4b:dc"; Address = "10.40.33.23"; }; 88 | valaam-wifi = { MACAddress = "3c:58:c2:f9:87:5b"; Address = "10.40.33.31"; }; 89 | printer = { MACAddress = "a4:5d:36:d6:22:d9"; Address = "10.40.33.50"; }; 90 | } 91 | ''; 92 | }; 93 | }; 94 | } 95 | ); 96 | }; 97 | }; 98 | } 99 | -------------------------------------------------------------------------------- /modules/homelab/samba/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | let 8 | hl = config.homelab; 9 | cfg = hl.samba; 10 | ext = hl.networks.external; 11 | int = hl.networks.local; 12 | smb_networks = 13 | if ext ? config.networking.hostName then 14 | lib.lists.singleton "${ext.${config.networking.hostName}.gateway}/24" 15 | else 16 | lib.mapAttrsToList (_: val: "${val.cidr.v4}/24") (lib.attrsets.filterAttrs (_: v: v.trusted) int); 17 | in 18 | { 19 | options.homelab.samba = { 20 | enable = lib.mkEnableOption { 21 | description = "Samba shares for the homelab"; 22 | }; 23 | example = lib.mkOption { 24 | default = lib.attrsets.mapAttrs ( 25 | _name: value: _name: 26 | value.settings 27 | ) cfg.shares; 28 | }; 29 | passwordFile = lib.mkOption { 30 | type = lib.types.path; 31 | default = /dev/null; 32 | description = "Path to samba password file"; 33 | }; 34 | globalSettings = lib.mkOption { 35 | description = "Global Samba parameters"; 36 | type = lib.types.attrsOf lib.types.str; 37 | default = { }; 38 | example = { 39 | "browseable" = "yes"; 40 | "writeable" = "yes"; 41 | "read only" = "no"; 42 | "guest ok" = "no"; 43 | }; 44 | }; 45 | commonSettings = lib.mkOption { 46 | description = "Parameters applied to each share"; 47 | type = lib.types.attrsOf lib.types.str; 48 | default = { }; 49 | example = { 50 | "security" = "user"; 51 | "invalid users" = [ "root" ]; 52 | }; 53 | apply = 54 | old: 55 | lib.attrsets.mergeAttrsList [ 56 | { 57 | "preserve case" = "yes"; 58 | "short preserve case" = "yes"; 59 | "browseable" = "yes"; 60 | "writeable" = "yes"; 61 | "read only" = "no"; 62 | "guest ok" = "no"; 63 | "create mask" = "0644"; 64 | "directory mask" = "0755"; 65 | "valid users" = hl.user; 66 | "fruit:aapl" = "yes"; 67 | "vfs objects" = "catia fruit streams_xattr"; 68 | } 69 | old 70 | ]; 71 | }; 72 | shares = lib.mkOption { 73 | type = lib.types.attrs; 74 | example = lib.literalExpression '' 75 | CoolShare = { 76 | path = "/mnt/CoolShare"; 77 | "fruit:aapl" = "yes"; 78 | }; 79 | ''; 80 | default = { }; 81 | }; 82 | }; 83 | config = lib.mkIf cfg.enable { 84 | services.samba-wsdd.enable = true; # make shares visible for windows 10 clients 85 | 86 | environment.systemPackages = [ config.services.samba.package ]; 87 | 88 | systemd.tmpfiles.rules = map (x: "d ${x.path} 0775 ${hl.user} ${hl.group} - -") ( 89 | lib.attrValues cfg.shares 90 | ); 91 | 92 | system.activationScripts.samba_user_create = '' 93 | smb_password=$(cat "${config.age.secrets.sambaPassword.path}") 94 | echo -e "$smb_password\n$smb_password\n" | ${lib.getExe' pkgs.samba "smbpasswd"} -a -s ${hl.user} 95 | ''; 96 | 97 | networking.firewall = { 98 | allowedTCPPorts = [ 5357 ]; 99 | allowedUDPPorts = [ 3702 ]; 100 | }; 101 | 102 | services.samba = { 103 | enable = true; 104 | openFirewall = true; 105 | settings = { 106 | global = lib.mkMerge [ 107 | { 108 | workgroup = lib.mkDefault "WORKGROUP"; 109 | "server string" = lib.mkDefault config.networking.hostName; 110 | "netbios name" = lib.mkDefault config.networking.hostName; 111 | "security" = lib.mkDefault "user"; 112 | "invalid users" = [ "root" ]; 113 | "hosts allow" = lib.mkDefault (lib.strings.concatStringsSep " " smb_networks); 114 | "guest account" = lib.mkDefault "nobody"; 115 | "map to guest" = lib.mkDefault "bad user"; 116 | "passdb backend" = lib.mkDefault "tdbsam"; 117 | } 118 | cfg.globalSettings 119 | ]; 120 | } 121 | // builtins.mapAttrs (_name: value: value // cfg.commonSettings) cfg.shares; 122 | }; 123 | services.avahi = { 124 | enable = true; 125 | nssmdns4 = true; 126 | publish = { 127 | enable = true; 128 | addresses = true; 129 | domain = true; 130 | hinfo = true; 131 | userServices = true; 132 | workstation = true; 133 | }; 134 | extraServiceFiles = { 135 | smb = '' 136 | 137 | 138 | 139 | %h 140 | 141 | _smb._tcp 142 | 445 143 | 144 | 145 | ''; 146 | }; 147 | }; 148 | }; 149 | } 150 | -------------------------------------------------------------------------------- /modules/homelab/services/arr/bazarr/default.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | service = "bazarr"; 4 | cfg = config.homelab.services.${service}; 5 | homelab = config.homelab; 6 | in 7 | { 8 | options.homelab.services.${service} = { 9 | enable = lib.mkEnableOption { 10 | description = "Enable ${service}"; 11 | }; 12 | configDir = lib.mkOption { 13 | type = lib.types.str; 14 | default = "/var/lib/${service}"; 15 | }; 16 | url = lib.mkOption { 17 | type = lib.types.str; 18 | default = "${service}.${homelab.baseDomain}"; 19 | }; 20 | homepage.name = lib.mkOption { 21 | type = lib.types.str; 22 | default = "Bazarr"; 23 | }; 24 | homepage.description = lib.mkOption { 25 | type = lib.types.str; 26 | default = "Subtitle manager"; 27 | }; 28 | homepage.icon = lib.mkOption { 29 | type = lib.types.str; 30 | default = "bazarr.svg"; 31 | }; 32 | homepage.category = lib.mkOption { 33 | type = lib.types.str; 34 | default = "Arr"; 35 | }; 36 | }; 37 | config = lib.mkIf cfg.enable { 38 | services.${service} = { 39 | enable = true; 40 | user = homelab.user; 41 | group = homelab.group; 42 | }; 43 | services.caddy.virtualHosts."${cfg.url}" = { 44 | useACMEHost = homelab.baseDomain; 45 | extraConfig = '' 46 | reverse_proxy http://127.0.0.1:${toString config.services.${service}.listenPort} 47 | ''; 48 | }; 49 | }; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /modules/homelab/services/arr/jellyseerr/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | config, 4 | lib, 5 | ... 6 | }: 7 | let 8 | service = "jellyseerr"; 9 | cfg = config.homelab.services.${service}; 10 | homelab = config.homelab; 11 | in 12 | { 13 | options.homelab.services.${service} = { 14 | enable = lib.mkEnableOption { 15 | description = "Enable ${service}"; 16 | }; 17 | url = lib.mkOption { 18 | type = lib.types.str; 19 | default = "${service}.${homelab.baseDomain}"; 20 | }; 21 | port = lib.mkOption { 22 | type = lib.types.port; 23 | default = 5055; 24 | }; 25 | package = lib.mkPackageOption pkgs "jellyseerr" { }; 26 | homepage.name = lib.mkOption { 27 | type = lib.types.str; 28 | default = "Jellyseerr"; 29 | }; 30 | homepage.description = lib.mkOption { 31 | type = lib.types.str; 32 | default = "Media request and discovery manager"; 33 | }; 34 | homepage.icon = lib.mkOption { 35 | type = lib.types.str; 36 | default = "jellyseerr.svg"; 37 | }; 38 | homepage.category = lib.mkOption { 39 | type = lib.types.str; 40 | default = "Arr"; 41 | }; 42 | }; 43 | config = lib.mkIf cfg.enable { 44 | services.${service} = { 45 | enable = true; 46 | port = cfg.port; 47 | package = cfg.package; 48 | }; 49 | services.caddy.virtualHosts."${cfg.url}" = { 50 | useACMEHost = homelab.baseDomain; 51 | extraConfig = '' 52 | reverse_proxy http://127.0.0.1:${toString cfg.port} 53 | ''; 54 | }; 55 | }; 56 | 57 | } 58 | -------------------------------------------------------------------------------- /modules/homelab/services/arr/lidarr/default.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | service = "lidarr"; 4 | cfg = config.homelab.services.${service}; 5 | homelab = config.homelab; 6 | in 7 | { 8 | options.homelab.services.${service} = { 9 | enable = lib.mkEnableOption { 10 | description = "Enable ${service}"; 11 | }; 12 | configDir = lib.mkOption { 13 | type = lib.types.str; 14 | default = "/var/lib/${service}"; 15 | }; 16 | url = lib.mkOption { 17 | type = lib.types.str; 18 | default = "${service}.${homelab.baseDomain}"; 19 | }; 20 | homepage.name = lib.mkOption { 21 | type = lib.types.str; 22 | default = "Lidarr"; 23 | }; 24 | homepage.description = lib.mkOption { 25 | type = lib.types.str; 26 | default = "Music collection manager"; 27 | }; 28 | homepage.icon = lib.mkOption { 29 | type = lib.types.str; 30 | default = "lidarr.svg"; 31 | }; 32 | homepage.category = lib.mkOption { 33 | type = lib.types.str; 34 | default = "Arr"; 35 | }; 36 | }; 37 | config = lib.mkIf cfg.enable { 38 | services.${service} = { 39 | enable = true; 40 | user = homelab.user; 41 | group = homelab.group; 42 | }; 43 | services.caddy.virtualHosts."${cfg.url}" = { 44 | useACMEHost = homelab.baseDomain; 45 | extraConfig = '' 46 | reverse_proxy http://127.0.0.1:8686 47 | ''; 48 | }; 49 | }; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /modules/homelab/services/arr/prowlarr/default.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | service = "prowlarr"; 4 | cfg = config.homelab.services.${service}; 5 | homelab = config.homelab; 6 | in 7 | { 8 | options.homelab.services.${service} = { 9 | enable = lib.mkEnableOption { 10 | description = "Enable ${service}"; 11 | }; 12 | configDir = lib.mkOption { 13 | type = lib.types.str; 14 | default = "/var/lib/${service}"; 15 | }; 16 | url = lib.mkOption { 17 | type = lib.types.str; 18 | default = "${service}.${homelab.baseDomain}"; 19 | }; 20 | homepage.name = lib.mkOption { 21 | type = lib.types.str; 22 | default = "Prowlarr"; 23 | }; 24 | homepage.description = lib.mkOption { 25 | type = lib.types.str; 26 | default = "PVR indexer"; 27 | }; 28 | homepage.icon = lib.mkOption { 29 | type = lib.types.str; 30 | default = "prowlarr.svg"; 31 | }; 32 | homepage.category = lib.mkOption { 33 | type = lib.types.str; 34 | default = "Arr"; 35 | }; 36 | }; 37 | config = lib.mkIf cfg.enable { 38 | services.${service} = { 39 | enable = true; 40 | }; 41 | services.caddy.virtualHosts."${cfg.url}" = { 42 | useACMEHost = homelab.baseDomain; 43 | extraConfig = '' 44 | reverse_proxy http://127.0.0.1:9696 45 | ''; 46 | }; 47 | }; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /modules/homelab/services/arr/radarr/default.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | service = "radarr"; 4 | cfg = config.homelab.services.${service}; 5 | homelab = config.homelab; 6 | in 7 | { 8 | options.homelab.services.${service} = { 9 | enable = lib.mkEnableOption { 10 | description = "Enable ${service}"; 11 | }; 12 | configDir = lib.mkOption { 13 | type = lib.types.str; 14 | default = "/var/lib/${service}"; 15 | }; 16 | url = lib.mkOption { 17 | type = lib.types.str; 18 | default = "${service}.${homelab.baseDomain}"; 19 | }; 20 | homepage.name = lib.mkOption { 21 | type = lib.types.str; 22 | default = "Radarr"; 23 | }; 24 | homepage.description = lib.mkOption { 25 | type = lib.types.str; 26 | default = "Movie collection manager"; 27 | }; 28 | homepage.icon = lib.mkOption { 29 | type = lib.types.str; 30 | default = "radarr.svg"; 31 | }; 32 | homepage.category = lib.mkOption { 33 | type = lib.types.str; 34 | default = "Arr"; 35 | }; 36 | }; 37 | config = lib.mkIf cfg.enable { 38 | services.${service} = { 39 | enable = true; 40 | user = homelab.user; 41 | group = homelab.group; 42 | }; 43 | services.caddy.virtualHosts."${cfg.url}" = { 44 | useACMEHost = homelab.baseDomain; 45 | extraConfig = '' 46 | reverse_proxy http://127.0.0.1:7878 47 | ''; 48 | }; 49 | }; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /modules/homelab/services/arr/sonarr/default.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | service = "sonarr"; 4 | cfg = config.homelab.services.${service}; 5 | homelab = config.homelab; 6 | in 7 | { 8 | options.homelab.services.${service} = { 9 | enable = lib.mkEnableOption { 10 | description = "Enable ${service}"; 11 | }; 12 | configDir = lib.mkOption { 13 | type = lib.types.str; 14 | default = "/var/lib/${service}"; 15 | }; 16 | url = lib.mkOption { 17 | type = lib.types.str; 18 | default = "${service}.${homelab.baseDomain}"; 19 | }; 20 | homepage.name = lib.mkOption { 21 | type = lib.types.str; 22 | default = "Sonarr"; 23 | }; 24 | homepage.description = lib.mkOption { 25 | type = lib.types.str; 26 | default = "TV show collection manager"; 27 | }; 28 | homepage.icon = lib.mkOption { 29 | type = lib.types.str; 30 | default = "sonarr.svg"; 31 | }; 32 | homepage.category = lib.mkOption { 33 | type = lib.types.str; 34 | default = "Arr"; 35 | }; 36 | }; 37 | config = lib.mkIf cfg.enable { 38 | services.${service} = { 39 | enable = true; 40 | user = homelab.user; 41 | group = homelab.group; 42 | }; 43 | services.caddy.virtualHosts."${cfg.url}" = { 44 | useACMEHost = homelab.baseDomain; 45 | extraConfig = '' 46 | reverse_proxy http://127.0.0.1:8989 47 | ''; 48 | }; 49 | }; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /modules/homelab/services/audiobookshelf/default.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | service = "audiobookshelf"; 4 | cfg = config.homelab.services.${service}; 5 | homelab = config.homelab; 6 | in 7 | { 8 | options.homelab.services.${service} = { 9 | enable = lib.mkEnableOption { 10 | description = "Enable ${service}"; 11 | }; 12 | configDir = lib.mkOption { 13 | type = lib.types.str; 14 | default = "/var/lib/${service}"; 15 | }; 16 | url = lib.mkOption { 17 | type = lib.types.str; 18 | default = "audiobooks.${homelab.baseDomain}"; 19 | }; 20 | homepage.name = lib.mkOption { 21 | type = lib.types.str; 22 | default = "Audiobookshelf"; 23 | }; 24 | homepage.description = lib.mkOption { 25 | type = lib.types.str; 26 | default = "Audiobook and podcast player"; 27 | }; 28 | homepage.icon = lib.mkOption { 29 | type = lib.types.str; 30 | default = "audiobookshelf.svg"; 31 | }; 32 | homepage.category = lib.mkOption { 33 | type = lib.types.str; 34 | default = "Media"; 35 | }; 36 | }; 37 | config = lib.mkIf cfg.enable { 38 | services.${service} = { 39 | enable = true; 40 | user = homelab.user; 41 | group = homelab.group; 42 | port = 8113; 43 | }; 44 | services.caddy.virtualHosts."${cfg.url}" = { 45 | useACMEHost = homelab.baseDomain; 46 | extraConfig = '' 47 | reverse_proxy http://127.0.0.1:${toString config.services.${service}.port} 48 | ''; 49 | }; 50 | }; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /modules/homelab/services/deemix/default.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | service = "deemix"; 4 | cfg = config.homelab.services.${service}; 5 | hl = config.homelab; 6 | in 7 | { 8 | imports = [ ./service.nix ]; 9 | options.homelab.services.${service} = { 10 | enable = lib.mkEnableOption { 11 | description = "Enable ${service}"; 12 | }; 13 | dataDir = lib.mkOption { 14 | type = lib.types.str; 15 | default = "/var/lib/${service}"; 16 | }; 17 | musicDir = lib.mkOption { 18 | type = lib.types.str; 19 | default = "${hl.mounts.fast}/Media/Music/Import"; 20 | }; 21 | url = lib.mkOption { 22 | type = lib.types.str; 23 | default = "${service}.${hl.baseDomain}"; 24 | }; 25 | homepage.name = lib.mkOption { 26 | type = lib.types.str; 27 | default = "Deemix"; 28 | }; 29 | homepage.description = lib.mkOption { 30 | type = lib.types.str; 31 | default = "Deezer downloader"; 32 | }; 33 | homepage.icon = lib.mkOption { 34 | type = lib.types.str; 35 | default = "deemix.svg"; 36 | }; 37 | homepage.category = lib.mkOption { 38 | type = lib.types.str; 39 | default = "Downloads"; 40 | }; 41 | }; 42 | config = lib.mkIf cfg.enable { 43 | services.${service} = { 44 | enable = true; 45 | user = hl.user; 46 | group = hl.group; 47 | musicDir = cfg.musicDir; 48 | listenHost = "127.0.0.1"; 49 | }; 50 | services.caddy.virtualHosts."${cfg.url}" = { 51 | useACMEHost = hl.baseDomain; 52 | extraConfig = '' 53 | reverse_proxy http://${config.services.${service}.listenHost}:${ 54 | toString config.services.${service}.listenPort 55 | } 56 | ''; 57 | }; 58 | }; 59 | 60 | } 61 | -------------------------------------------------------------------------------- /modules/homelab/services/deemix/service.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | config, 5 | ... 6 | }: 7 | let 8 | cfg = config.services.deemix; 9 | nodejs = pkgs.nodejs_20; 10 | pname = "deemix"; 11 | version = "v4.3.0"; 12 | src = pkgs.fetchFromGitHub { 13 | owner = "bambanah"; 14 | repo = "deemix"; 15 | rev = "05c66311a5e91a42471685217bc8efa2cb178860"; 16 | hash = "sha256-OIMOupciEoj3CAblEAxfX5awjKySYPtvLfnzrUcIjzY="; 17 | }; 18 | deemix = pkgs.stdenv.mkDerivation (finalAttrs: { 19 | inherit pname version src; 20 | 21 | nativeBuildInputs = [ 22 | nodejs 23 | pkgs.pnpm.configHook 24 | pkgs.cacert 25 | pkgs.turbo 26 | ]; 27 | 28 | pnpmDeps = pkgs.pnpm.fetchDeps { 29 | inherit (finalAttrs) 30 | pname 31 | version 32 | src 33 | ; 34 | hash = "sha256-7CEBFv85SngWekWhbKQhRRL7P/Llf6fQ3JSyu5+2SDc="; 35 | }; 36 | 37 | buildPhase = '' 38 | runHook preBuild 39 | turbo prune deemix-webui --docker 40 | mkdir builder 41 | cp -r out/json/* builder 42 | runHook postBuild 43 | ''; 44 | 45 | installPhase = '' 46 | runHook preInstall 47 | pushd builder 48 | pnpm install --frozen-lockfile 49 | cp -r ../out/full/* . 50 | pnpm turbo build --filter=deemix-webui... 51 | popd 52 | runHook postInstall 53 | ''; 54 | 55 | postInstall = '' 56 | mkdir -p $out/bin 57 | rm -r $(find -type d -name .turbo) 58 | cp -r builder/* $out/ 59 | cat < $out/bin/deemix 60 | #!${pkgs.runtimeShell} 61 | exec ${nodejs}/bin/node $out/webui/dist/main.js 62 | EOF 63 | chmod +x $out/bin/deemix 64 | ''; 65 | 66 | }); 67 | in 68 | { 69 | options.services.deemix = { 70 | enable = lib.mkEnableOption "A web-based tool that facilitates downloading music from Deezer"; 71 | openFirewall = lib.mkOption { 72 | type = lib.types.bool; 73 | default = false; 74 | description = '' 75 | Open the port in the firewall for Deemix 76 | ''; 77 | }; 78 | dataDir = lib.mkOption { 79 | type = lib.types.str; 80 | default = "/var/lib/deemix"; 81 | description = '' 82 | The directory where Deemix stores its data files 83 | ''; 84 | }; 85 | musicDir = lib.mkOption { 86 | type = lib.types.str; 87 | default = "/var/lib/deemix/downloads"; 88 | description = '' 89 | The directory for Deemix downloads 90 | ''; 91 | }; 92 | user = lib.mkOption { 93 | type = lib.types.str; 94 | default = "deemix"; 95 | description = '' 96 | User account under which Deemix runs. 97 | ''; 98 | }; 99 | group = lib.mkOption { 100 | type = lib.types.str; 101 | default = "deemix"; 102 | description = '' 103 | Group under which Deemix runs. 104 | ''; 105 | }; 106 | listenPort = lib.mkOption { 107 | type = lib.types.port; 108 | default = 6595; 109 | description = '' 110 | Port under which Deemix runs. 111 | ''; 112 | }; 113 | listenHost = lib.mkOption { 114 | type = lib.types.str; 115 | default = "0.0.0.0"; 116 | description = '' 117 | Host which Deemix uses to run. Change to 127.0.0.1 if using a reverse proxy 118 | ''; 119 | }; 120 | }; 121 | config = lib.mkIf cfg.enable { 122 | systemd.tmpfiles.settings."10-deemix".${cfg.dataDir}.d = { 123 | inherit (cfg) user group; 124 | mode = "0700"; 125 | }; 126 | 127 | systemd.services.deemix = { 128 | description = "Deemix is a web-based tool that facilitates downloading music from Deezer"; 129 | after = [ "network.target" ]; 130 | wantedBy = [ "multi-user.target" ]; 131 | environment = { 132 | DEEMIX_DATA_DIR = cfg.dataDir; 133 | DEEMIX_MUSIC_DIR = cfg.musicDir; 134 | DEEMIX_SERVER_PORT = toString cfg.listenPort; 135 | DEEMIX_SERVER_HOST = cfg.listenHost; 136 | NODE_ENV = "production"; 137 | }; 138 | serviceConfig = { 139 | Type = "simple"; 140 | User = cfg.user; 141 | Group = cfg.group; 142 | ExecStart = "${deemix}/bin/deemix"; 143 | Restart = "on-failure"; 144 | StateDirectory = "deemix"; 145 | PrivateTmp = true; 146 | }; 147 | }; 148 | 149 | networking.firewall = lib.mkIf cfg.openFirewall { 150 | allowedTCPPorts = [ cfg.listenPort ]; 151 | }; 152 | 153 | users.users = lib.mkIf (cfg.user == "deemix") { 154 | deemix = { 155 | group = cfg.group; 156 | home = cfg.dataDir; 157 | uid = config.ids.uids.deemix; 158 | }; 159 | }; 160 | 161 | users.groups = lib.mkIf (cfg.group == "deemix") { 162 | deemix = { 163 | gid = config.ids.gids.deemix; 164 | }; 165 | }; 166 | }; 167 | } 168 | -------------------------------------------------------------------------------- /modules/homelab/services/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | { 8 | options.homelab.services = { 9 | enable = lib.mkEnableOption "Settings and services for the homelab"; 10 | }; 11 | 12 | config = lib.mkIf config.homelab.services.enable { 13 | networking.firewall.allowedTCPPorts = [ 14 | 80 15 | 443 16 | ]; 17 | security.acme = { 18 | acceptTerms = true; 19 | defaults.email = "moe@notthebe.ee"; 20 | certs.${config.homelab.baseDomain} = { 21 | reloadServices = [ "caddy.service" ]; 22 | domain = "${config.homelab.baseDomain}"; 23 | extraDomainNames = [ "*.${config.homelab.baseDomain}" ]; 24 | dnsProvider = "cloudflare"; 25 | dnsResolver = "1.1.1.1:53"; 26 | dnsPropagationCheck = true; 27 | group = config.services.caddy.group; 28 | environmentFile = config.homelab.cloudflare.dnsCredentialsFile; 29 | }; 30 | }; 31 | services.caddy = { 32 | enable = true; 33 | globalConfig = '' 34 | auto_https off 35 | ''; 36 | virtualHosts = { 37 | "http://${config.homelab.baseDomain}" = { 38 | extraConfig = '' 39 | redir https://{host}{uri} 40 | ''; 41 | }; 42 | "http://*.${config.homelab.baseDomain}" = { 43 | extraConfig = '' 44 | redir https://{host}{uri} 45 | ''; 46 | }; 47 | 48 | }; 49 | }; 50 | nixpkgs.config.permittedInsecurePackages = [ 51 | "dotnet-sdk-6.0.428" 52 | "aspnetcore-runtime-6.0.36" 53 | ]; 54 | virtualisation.podman = { 55 | dockerCompat = true; 56 | autoPrune.enable = true; 57 | extraPackages = [ pkgs.zfs ]; 58 | defaultNetwork.settings = { 59 | dns_enabled = true; 60 | }; 61 | }; 62 | virtualisation.oci-containers = { 63 | backend = "podman"; 64 | }; 65 | 66 | networking.firewall.interfaces.podman0.allowedUDPPorts = 67 | lib.lists.optionals config.virtualisation.podman.enable 68 | [ 53 ]; 69 | }; 70 | 71 | imports = [ 72 | ./arr/prowlarr 73 | ./arr/bazarr 74 | ./arr/jellyseerr 75 | ./arr/sonarr 76 | ./arr/radarr 77 | #./arr/lidarr 78 | ./audiobookshelf 79 | ./deluge 80 | #./deemix 81 | ./homepage 82 | ./immich 83 | ./invoiceplane 84 | ./jellyfin 85 | ./keycloak 86 | ./microbin 87 | ./miniflux 88 | ./monitoring/grafana 89 | ./monitoring/prometheus 90 | ./monitoring/prometheus/exporters/shelly_plug_exporter 91 | ./navidrome 92 | ./nextcloud 93 | ./smarthome/homeassistant 94 | ./smarthome/raspberrymatic 95 | ./paperless-ngx 96 | ./radicale 97 | ./sabnzbd 98 | ./slskd 99 | ./uptime-kuma 100 | ./vaultwarden 101 | ./wireguard-netns 102 | ]; 103 | } 104 | -------------------------------------------------------------------------------- /modules/homelab/services/deluge/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | let 8 | hl = config.homelab; 9 | cfg = hl.services.deluge; 10 | ns = hl.services.wireguard-netns.namespace; 11 | in 12 | { 13 | options.homelab.services.deluge = { 14 | enable = lib.mkEnableOption "Deluge torrent client (bound to a Wireguard VPN network)"; 15 | configDir = lib.mkOption { 16 | type = lib.types.str; 17 | default = "/var/lib/deluge"; 18 | }; 19 | url = lib.mkOption { 20 | type = lib.types.str; 21 | default = "deluge.${hl.baseDomain}"; 22 | }; 23 | monitoredServices = lib.mkOption { 24 | type = lib.types.listOf lib.types.str; 25 | default = [ 26 | "delugeweb" 27 | "deluged-proxy" 28 | "deluged" 29 | ]; 30 | }; 31 | homepage.name = lib.mkOption { 32 | type = lib.types.str; 33 | default = "Deluge"; 34 | }; 35 | homepage.description = lib.mkOption { 36 | type = lib.types.str; 37 | default = "Torrent client"; 38 | }; 39 | homepage.icon = lib.mkOption { 40 | type = lib.types.str; 41 | default = "deluge.svg"; 42 | }; 43 | homepage.category = lib.mkOption { 44 | type = lib.types.str; 45 | default = "Downloads"; 46 | }; 47 | }; 48 | config = lib.mkIf cfg.enable { 49 | services.deluge = { 50 | enable = true; 51 | user = hl.user; 52 | group = hl.group; 53 | web = { 54 | enable = true; 55 | }; 56 | }; 57 | 58 | services.caddy.virtualHosts."${cfg.url}" = { 59 | useACMEHost = hl.baseDomain; 60 | extraConfig = '' 61 | reverse_proxy http://127.0.0.1:8112 62 | ''; 63 | }; 64 | 65 | systemd = lib.mkIf hl.services.wireguard-netns.enable { 66 | services.deluged.bindsTo = [ "netns@${ns}.service" ]; 67 | services.deluged.requires = [ 68 | "network-online.target" 69 | "${ns}.service" 70 | ]; 71 | services.deluged.serviceConfig.NetworkNamespacePath = [ "/var/run/netns/${ns}" ]; 72 | sockets."deluged-proxy" = { 73 | enable = true; 74 | description = "Socket for Proxy to Deluge WebUI"; 75 | listenStreams = [ "58846" ]; 76 | wantedBy = [ "sockets.target" ]; 77 | }; 78 | services."deluged-proxy" = { 79 | enable = true; 80 | description = "Proxy to Deluge Daemon in Network Namespace"; 81 | requires = [ 82 | "deluged.service" 83 | "deluged-proxy.socket" 84 | ]; 85 | after = [ 86 | "deluged.service" 87 | "deluged-proxy.socket" 88 | ]; 89 | unitConfig = { 90 | JoinsNamespaceOf = "deluged.service"; 91 | }; 92 | serviceConfig = { 93 | User = config.services.deluge.user; 94 | Group = config.services.deluge.group; 95 | ExecStart = "${pkgs.systemd}/lib/systemd/systemd-socket-proxyd --exit-idle-time=5min 127.0.0.1:58846"; 96 | PrivateNetwork = "yes"; 97 | }; 98 | }; 99 | }; 100 | }; 101 | } 102 | -------------------------------------------------------------------------------- /modules/homelab/services/immich/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | ... 5 | }: 6 | let 7 | cfg = config.homelab.services.immich; 8 | homelab = config.homelab; 9 | in 10 | { 11 | options.homelab.services.immich = { 12 | enable = lib.mkEnableOption "Self-hosted photo and video management solution"; 13 | user = lib.mkOption { 14 | default = config.homelab.user; 15 | type = lib.types.str; 16 | description = '' 17 | User to run the Immich container as 18 | ''; 19 | }; 20 | group = lib.mkOption { 21 | default = config.homelab.group; 22 | type = lib.types.str; 23 | description = '' 24 | Group to run the Immich container as 25 | ''; 26 | }; 27 | mediaDir = lib.mkOption { 28 | type = lib.types.path; 29 | default = "${config.homelab.mounts.fast}/Photos/Immich"; 30 | }; 31 | url = lib.mkOption { 32 | type = lib.types.str; 33 | default = "photos.${homelab.baseDomain}"; 34 | }; 35 | homepage.name = lib.mkOption { 36 | type = lib.types.str; 37 | default = "Immich"; 38 | }; 39 | homepage.description = lib.mkOption { 40 | type = lib.types.str; 41 | default = "Self-hosted photo and video management solution"; 42 | }; 43 | homepage.icon = lib.mkOption { 44 | type = lib.types.str; 45 | default = "immich.svg"; 46 | }; 47 | homepage.category = lib.mkOption { 48 | type = lib.types.str; 49 | default = "Media"; 50 | }; 51 | }; 52 | config = lib.mkIf cfg.enable { 53 | systemd.tmpfiles.rules = [ "d ${cfg.mediaDir} 0775 immich ${homelab.group} - -" ]; 54 | users.users.immich.extraGroups = [ 55 | "video" 56 | "render" 57 | ]; 58 | services.immich = { 59 | group = homelab.group; 60 | enable = true; 61 | port = 2283; 62 | mediaLocation = "${cfg.mediaDir}"; 63 | }; 64 | services.caddy.virtualHosts."${cfg.url}" = { 65 | useACMEHost = homelab.baseDomain; 66 | extraConfig = '' 67 | reverse_proxy http://${config.services.immich.host}:${toString config.services.immich.port} 68 | ''; 69 | }; 70 | }; 71 | 72 | } 73 | -------------------------------------------------------------------------------- /modules/homelab/services/invoiceplane/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | let 8 | service = "invoiceplane"; 9 | cfg = config.homelab.services.${service}; 10 | hl = config.homelab; 11 | in 12 | { 13 | options.homelab.services.${service} = { 14 | enable = lib.mkEnableOption { 15 | description = "Enable ${service}"; 16 | }; 17 | configDir = lib.mkOption { 18 | type = lib.types.str; 19 | default = "/var/lib/${service}"; 20 | }; 21 | dbPasswordFile = lib.mkOption { 22 | type = lib.types.path; 23 | }; 24 | url = lib.mkOption { 25 | type = lib.types.str; 26 | default = "invoice.${hl.baseDomain}"; 27 | }; 28 | monitoredServices = lib.mkOption { 29 | type = lib.types.listOf lib.types.str; 30 | default = [ 31 | "phpfpm-invoiceplane-${cfg.url}" 32 | ]; 33 | }; 34 | homepage.name = lib.mkOption { 35 | type = lib.types.str; 36 | default = "InvoicePlane"; 37 | }; 38 | homepage.description = lib.mkOption { 39 | type = lib.types.str; 40 | default = "Invoicing application"; 41 | }; 42 | homepage.icon = lib.mkOption { 43 | type = lib.types.str; 44 | default = "invoiceplane.svg"; 45 | }; 46 | homepage.category = lib.mkOption { 47 | type = lib.types.str; 48 | default = "Services"; 49 | }; 50 | }; 51 | config = lib.mkIf cfg.enable { 52 | services.${service} = { 53 | sites.${cfg.url} = { 54 | invoiceTemplates = 55 | let 56 | notthebee = pkgs.callPackage ./template.nix { }; 57 | in 58 | [ notthebee ]; 59 | settings = { 60 | DISABLE_SETUP = true; 61 | SETUP_COMPLETED = true; 62 | IP_URL = "https://${cfg.url}"; 63 | DISABLE_READ_ONLY = true; 64 | ENABLE_INVOICE_DELETION = true; 65 | }; 66 | }; 67 | }; 68 | services.caddy.virtualHosts."${cfg.url}" = 69 | let 70 | url = "http://${cfg.url}"; 71 | in 72 | { 73 | useACMEHost = hl.baseDomain; 74 | extraConfig = config.services.caddy.virtualHosts.${url}.extraConfig; 75 | }; 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /modules/homelab/services/invoiceplane/template.nix: -------------------------------------------------------------------------------- 1 | { stdenv, pkgs }: 2 | stdenv.mkDerivation { 3 | name = "invoiceplane-template-notthebee"; 4 | src = pkgs.fetchurl { 5 | url = "https://github.com/InvoicePlane/InvoicePlane/raw/refs/tags/v1.6.3/application/views/invoice_templates/pdf/InvoicePlane.php"; 6 | hash = "sha256-x8xssyOtriKozCPG1JiJWXrFaf57Wyab9aZQn8WBreY="; 7 | }; 8 | dontUnpack = true; 9 | buildOutputs = [ pkgs.gawk ]; 10 | installPhase = '' 11 | mkdir -p $out 12 | awk '{ 13 | print 14 | if ($0=="
") 15 | print "" 16 | }' $src > $out/InvoicePlane_Custom.php 17 | ''; 18 | } 19 | -------------------------------------------------------------------------------- /modules/homelab/services/jellyfin/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | ... 5 | }: 6 | let 7 | service = "jellyfin"; 8 | cfg = config.homelab.services.${service}; 9 | homelab = config.homelab; 10 | in 11 | { 12 | options.homelab.services.${service} = { 13 | enable = lib.mkEnableOption { 14 | description = "Enable ${service}"; 15 | }; 16 | configDir = lib.mkOption { 17 | type = lib.types.str; 18 | default = "/var/lib/${service}"; 19 | }; 20 | url = lib.mkOption { 21 | type = lib.types.str; 22 | default = "jellyfin.${homelab.baseDomain}"; 23 | }; 24 | homepage.name = lib.mkOption { 25 | type = lib.types.str; 26 | default = "Jellyfin"; 27 | }; 28 | homepage.description = lib.mkOption { 29 | type = lib.types.str; 30 | default = "The Free Software Media System"; 31 | }; 32 | homepage.icon = lib.mkOption { 33 | type = lib.types.str; 34 | default = "jellyfin.svg"; 35 | }; 36 | homepage.category = lib.mkOption { 37 | type = lib.types.str; 38 | default = "Media"; 39 | }; 40 | }; 41 | config = lib.mkIf cfg.enable { 42 | nixpkgs.overlays = [ 43 | (_final: prev: { 44 | jellyfin-web = prev.jellyfin-web.overrideAttrs ( 45 | _finalAttrs: _previousAttrs: { 46 | installPhase = '' 47 | runHook preInstall 48 | 49 | # this is the important line 50 | sed -i "s###" dist/index.html 51 | 52 | mkdir -p $out/share 53 | cp -a dist $out/share/jellyfin-web 54 | 55 | runHook postInstall 56 | ''; 57 | } 58 | ); 59 | }) 60 | ]; 61 | services.${service} = { 62 | enable = true; 63 | user = homelab.user; 64 | group = homelab.group; 65 | }; 66 | services.caddy.virtualHosts."${cfg.url}" = { 67 | useACMEHost = homelab.baseDomain; 68 | extraConfig = '' 69 | reverse_proxy http://127.0.0.1:8096 70 | ''; 71 | }; 72 | }; 73 | 74 | } 75 | -------------------------------------------------------------------------------- /modules/homelab/services/keycloak/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | let 8 | service = "keycloak"; 9 | cfg = config.homelab.services.${service}; 10 | hl = config.homelab; 11 | in 12 | { 13 | options.homelab.services.${service} = { 14 | enable = lib.mkEnableOption { 15 | description = "Enable ${service}"; 16 | }; 17 | url = lib.mkOption { 18 | type = lib.types.str; 19 | default = "login.${hl.baseDomain}"; 20 | }; 21 | homepage.name = lib.mkOption { 22 | type = lib.types.str; 23 | default = "Keycloak"; 24 | }; 25 | homepage.description = lib.mkOption { 26 | type = lib.types.str; 27 | default = "Open Source Identity and Access Management"; 28 | }; 29 | homepage.icon = lib.mkOption { 30 | type = lib.types.str; 31 | default = "keycloak.svg"; 32 | }; 33 | homepage.category = lib.mkOption { 34 | type = lib.types.str; 35 | default = "Services"; 36 | }; 37 | dbPasswordFile = lib.mkOption { 38 | type = lib.types.path; 39 | }; 40 | cloudflared.credentialsFile = lib.mkOption { 41 | type = lib.types.str; 42 | example = lib.literalExpression '' 43 | pkgs.writeText "cloudflare-credentials.json" ''' 44 | {"AccountTag":"secret"."TunnelSecret":"secret","TunnelID":"secret"} 45 | ''' 46 | ''; 47 | }; 48 | cloudflared.tunnelId = lib.mkOption { 49 | type = lib.types.str; 50 | example = "00000000-0000-0000-0000-000000000000"; 51 | }; 52 | }; 53 | config = lib.mkIf cfg.enable { 54 | services.cloudflared = { 55 | enable = true; 56 | tunnels.${cfg.cloudflared.tunnelId} = { 57 | credentialsFile = cfg.cloudflared.credentialsFile; 58 | default = "http_status:404"; 59 | ingress."${cfg.url}".service = "http://127.0.0.1:${ 60 | toString config.services.${service}.settings.http-port 61 | }"; 62 | }; 63 | }; 64 | 65 | environment.systemPackages = [ 66 | pkgs.keycloak 67 | pkgs.custom_keycloak_themes.notthebee 68 | ]; 69 | nixpkgs.overlays = [ 70 | (_final: _prev: { 71 | custom_keycloak_themes = { 72 | notthebee = pkgs.callPackage ./theme.nix { }; 73 | }; 74 | custom_keycloak_plugins = { 75 | keycloak_spi_trusted_device = pkgs.callPackage ./trusted-device.nix { }; 76 | }; 77 | }) 78 | ]; 79 | 80 | services.${service} = { 81 | enable = true; 82 | initialAdminPassword = "schneke123"; 83 | database.passwordFile = cfg.dbPasswordFile; 84 | themes = { 85 | notthebee = pkgs.custom_keycloak_themes.notthebee; 86 | }; 87 | plugins = [ pkgs.custom_keycloak_plugins.keycloak_spi_trusted_device ]; 88 | settings = { 89 | spi-theme-static-max-age = "-1"; 90 | spi-theme-cache-themes = false; 91 | spi-theme-cache-templates = false; 92 | http-port = 8821; 93 | hostname = cfg.url; 94 | hostname-strict = false; 95 | hostname-strict-https = false; 96 | proxy-headers = "xforwarded"; 97 | http-enabled = true; 98 | }; 99 | }; 100 | }; 101 | 102 | } 103 | -------------------------------------------------------------------------------- /modules/homelab/services/keycloak/theme.nix: -------------------------------------------------------------------------------- 1 | { stdenv }: 2 | stdenv.mkDerivation rec { 3 | name = "keycloak_theme_notthebee"; 4 | version = "1.0"; 5 | 6 | src = ./themes/notthebee; 7 | 8 | nativeBuildInputs = [ ]; 9 | buildInputs = [ ]; 10 | 11 | installPhase = '' 12 | mkdir -p $out 13 | cp -a login $out 14 | ''; 15 | } 16 | -------------------------------------------------------------------------------- /modules/homelab/services/keycloak/themes/notthebee/login/resources/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notthebee/nix-config/74b56c5821ae4f626a27055a1c95d510189eee34/modules/homelab/services/keycloak/themes/notthebee/login/resources/background.jpg -------------------------------------------------------------------------------- /modules/homelab/services/keycloak/themes/notthebee/login/resources/css/styles.css: -------------------------------------------------------------------------------- 1 | #kc-header { 2 | display: none; 3 | } 4 | #keycloak-bg { 5 | background: url("../background.jpg"); 6 | background-size: cover; 7 | background-repeat: no-repeat; 8 | background-position: center center; 9 | } 10 | -------------------------------------------------------------------------------- /modules/homelab/services/keycloak/themes/notthebee/login/theme.properties: -------------------------------------------------------------------------------- 1 | parent=keycloak.v2 2 | styles=css/styles.css 3 | meta=viewport==width=device-width,initial-scale=1 4 | -------------------------------------------------------------------------------- /modules/homelab/services/keycloak/trusted-device.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | fetchFromGitHub, 4 | maven, 5 | }: 6 | 7 | maven.buildMavenPackage rec { 8 | pname = "keycloak-spi-trusted-device"; 9 | version = "0.0.2"; 10 | 11 | src = fetchFromGitHub { 12 | owner = "wouterh-dev"; 13 | repo = "keycloak-spi-trusted-device"; 14 | rev = "v${version}"; 15 | hash = "sha256-3GFQsgFXDEN5ORO7rkHSlEcdCbnVR3V8byXsGXCd00o="; 16 | }; 17 | 18 | mvnHash = "sha256-Gi7Wx9LI/Y4MprNJrMkhhJSIK/z2aVB4OpzZnD1+70I="; 19 | 20 | installPhase = '' 21 | install -D "spi/target/keycloak-spi-trusted-device-1.0-SNAPSHOT.jar" "$out/spi-trusted-device-${version}.jar" 22 | ''; 23 | 24 | meta = with lib; { 25 | homepage = "https://github.com/wouterh-dev/keycloak-spi-trusted-device"; 26 | description = "Third party module that adds OTP trusted device functionality to Keycloak"; 27 | sourceProvenance = with sourceTypes; [ 28 | fromSource 29 | binaryBytecode 30 | ]; 31 | license = licenses.asl20; 32 | maintainers = with maintainers; [ notthebee ]; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /modules/homelab/services/microbin/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | pkgs, 4 | lib, 5 | ... 6 | }: 7 | let 8 | nordHighlight = builtins.toFile "nord.css" (builtins.readFile ./nord.css); 9 | nordUi = builtins.toFile "nord_ui.css" (builtins.readFile ./nord_ui.css); 10 | highlightJsNix = pkgs.fetchurl { 11 | url = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/nix.min.js"; 12 | hash = "sha256-j4dmtrr8qUODoICuOsgnj1ojTAmxbKe00mE5sfElC/I="; 13 | }; 14 | highlightJs = pkgs.fetchurl { 15 | url = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"; 16 | hash = "sha256-xKOZ3W9Ii8l6NUbjR2dHs+cUyZxXuUcxVMb7jSWbk4E="; 17 | }; 18 | service = "microbin"; 19 | cfg = config.homelab.services.${service}; 20 | homelab = config.homelab; 21 | in 22 | { 23 | options.homelab.services.${service} = { 24 | enable = lib.mkEnableOption { 25 | description = "Enable ${service}"; 26 | }; 27 | configDir = lib.mkOption { 28 | type = lib.types.str; 29 | default = "/var/lib/microbin"; 30 | }; 31 | url = lib.mkOption { 32 | type = lib.types.str; 33 | default = "bin.${homelab.baseDomain}"; 34 | }; 35 | passwordFile = lib.mkOption { 36 | default = ""; 37 | type = lib.types.str; 38 | example = lib.literalExpression '' 39 | pkgs.writeText "microbin-secret.txt" ''' 40 | MICROBIN_ADMIN_USERNAME 41 | MICROBIN_ADMIN_PASSWORD 42 | MICROBIN_UPLOADER_PASSWORD 43 | ''' 44 | ''; 45 | }; 46 | cloudflared.credentialsFile = lib.mkOption { 47 | type = lib.types.str; 48 | example = lib.literalExpression '' 49 | pkgs.writeText "cloudflare-credentials.json" ''' 50 | {"AccountTag":"secret"."TunnelSecret":"secret","TunnelID":"secret"} 51 | ''' 52 | ''; 53 | }; 54 | cloudflared.tunnelId = lib.mkOption { 55 | type = lib.types.str; 56 | example = "00000000-0000-0000-0000-000000000000"; 57 | }; 58 | homepage.name = lib.mkOption { 59 | type = lib.types.str; 60 | default = "Microbin"; 61 | }; 62 | homepage.description = lib.mkOption { 63 | type = lib.types.str; 64 | default = "A minimal pastebin"; 65 | }; 66 | homepage.icon = lib.mkOption { 67 | type = lib.types.str; 68 | default = "microbin.png"; 69 | }; 70 | homepage.category = lib.mkOption { 71 | type = lib.types.str; 72 | default = "Services"; 73 | }; 74 | }; 75 | config = lib.mkIf cfg.enable { 76 | nixpkgs.overlays = with pkgs; [ 77 | (_final: prev: { 78 | microbin = prev.microbin.overrideAttrs ( 79 | _finalAttrs: _previousAttrs: { 80 | postPatch = '' 81 | cp ${nordHighlight} templates/assets/highlight/highlight.min.css 82 | cp ${highlightJs} templates/assets/highlight/highlight.min.js 83 | cp ${highlightJsNix} templates/assets/highlight/nix.min.js 84 | echo "" >> templates/assets/water.css 85 | cat ${nordUi} >> templates/assets/water.css 86 | sed -i "s#