this repo has no description
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

box zfs

+816 -241
+97
agents.md
··· 1 + # Helm Environment Overview 2 + 3 + ## Box NAS Server (`box` / `mossnet.lan`) 4 + 5 + ### Hardware 6 + - NVMe boot drive (LUKS encrypted) 7 + - 3x 4TB drives in ZFS RAIDZ1 pool (`tank`) - ~7.14TB usable 8 + 9 + ### ZFS Datasets 10 + | Dataset | Mountpoint | Purpose | 11 + |---------|------------|---------| 12 + | `tank/data/media` | `/tank/media` | Media library (music, photos, tv, movies) | 13 + | `tank/data/books` | `/tank/books` | Calibre library | 14 + | `tank/data/podcasts` | `/tank/podcasts` | Podcast storage | 15 + | `tank/data/new-music` | `/tank/new-music` | Incoming music from seedbox | 16 + | `tank/data/backup` | `/tank/backup` | PostgreSQL backups | 17 + | `tank/data/archive` | `/tank/archive` | Old data (memories, old-home-dirs, etc.) | 18 + 19 + ### Services 20 + - **Immich** - Photo management (`/tank/media/photos`) 21 + - **Gonic** - Music streaming (`/tank/media/music`) 22 + - **Calibre-server/Calibre-web** - Ebook management (`/tank/books`) 23 + - **Jellyfin** - Media streaming 24 + - **Lidarr** - Music management (runs as `headphones:audio`) 25 + - **Radicale** - CalDAV/CardDAV 26 + - **Syncthing** - File sync 27 + - **PostgreSQL** - Database 28 + - **Taskserver** - Taskwarrior sync 29 + 30 + --- 31 + 32 + ## Repository Structure 33 + 34 + ``` 35 + helm/ 36 + ├── flake.nix # Main flake - defines all hosts 37 + ├── hosts/ 38 + │ ├── box/ 39 + │ │ ├── default.nix # Box host config, imports profiles 40 + │ │ ├── configuration.nix # Hardware/boot config 41 + │ │ └── disko.nix # Disk/ZFS layout 42 + │ ├── profiles/ # NixOS service profiles 43 + │ │ ├── sync/music/ # get-music-sync service 44 + │ │ ├── headphones/ # Lidarr config 45 + │ │ ├── jellyfin/ 46 + │ │ ├── monitoring/ 47 + │ │ └── ... 48 + ├── home/ 49 + │ ├── dev/ 50 + │ │ └── default.nix # Home-manager config for box 51 + │ └── profiles/ 52 + │ ├── beets/ # Beets music library config 53 + │ ├── cli/ 54 + │ ├── nvim/ 55 + │ ├── git/ 56 + │ └── opencode/ 57 + ├── secrets/ # Agenix encrypted secrets 58 + └── modules/ # Custom NixOS modules 59 + ``` 60 + 61 + --- 62 + 63 + ## Deployment 64 + 65 + ```bash 66 + # Deploy to a host 67 + deploy .#box 68 + deploy .#curve 69 + deploy .#helix 70 + deploy .#lituus 71 + 72 + # SSH access to box 73 + ssh anish@mossnet.lan 74 + ``` 75 + 76 + --- 77 + 78 + ## Key Users/Groups 79 + 80 + | User | Group | Purpose | 81 + |------|-------|---------| 82 + | `anish` | `users`, `wheel`, `audio`, `video`, `docker` | Primary user | 83 + | `headphones` | `audio` | Lidarr service | 84 + | `gonic` | `audio` | Gonic music streaming | 85 + | `immich` | `immich` | Photo management | 86 + | `calibre-server` | `calibre-server` | Ebook server | 87 + 88 + --- 89 + 90 + ## Hosts 91 + 92 + | Host | Description | 93 + |------|-------------| 94 + | `box` | NAS server (mossnet.lan) | 95 + | `curve` | Workstation | 96 + | `helix` | Workstation | 97 + | `lituus` | VPS/Server |
+5 -5
flake.lock
··· 737 737 "treefmt-nix": "treefmt-nix_2" 738 738 }, 739 739 "locked": { 740 - "lastModified": 1768359433, 741 - "narHash": "sha256-e/6qI81VBJo0lAQsyUG+2jMsL0q3YLz88NZoZOCVFu8=", 742 - "owner": "Chickensoupwithrice", 740 + "lastModified": 1768434130, 741 + "narHash": "sha256-4rBBs7spDuimvUcL3egp2Zh94Lk8pf00VsjkOs59h7E=", 742 + "owner": "numtide", 743 743 "repo": "llm-agents.nix", 744 - "rev": "596bf03f14e9a54654473a1666b3b274bbc5939e", 744 + "rev": "d0ed3ef68a04b5bd127fecd405baf803eea29c29", 745 745 "type": "github" 746 746 }, 747 747 "original": { 748 - "owner": "Chickensoupwithrice", 748 + "owner": "numtide", 749 749 "repo": "llm-agents.nix", 750 750 "type": "github" 751 751 }
+3 -3
flake.nix
··· 75 75 inputs.nixpkgs.follows = "nixpkgs"; 76 76 }; 77 77 78 - # LLM Agents (using fork until chainlink PR is merged) 79 - llm-agents.url = "github:Chickensoupwithrice/llm-agents.nix"; 78 + llm-agents.url = "github:numtide/llm-agents.nix"; 80 79 81 80 # Others 82 81 nur.url = "github:nix-community/NUR"; ··· 361 360 pkgs = nixpkgsFor.${system}; 362 361 modules = [ 363 362 ./hosts/box 363 + disko.nixosModules.disko 364 364 agenix.nixosModules.age 365 365 self.nixosModules.backup 366 366 self.nixosModules.wireguard ··· 368 368 self.nixosModules.gpodder2go 369 369 self.nixosModules.wallabag 370 370 self.nixosModules.ulogger-server 371 - grasp.nixosModule 371 + # grasp.nixosModule # Disabled for initial install - private repo 372 372 home-manager.nixosModules.home-manager 373 373 { 374 374 nix.registry.nixpkgs.flake = nixpkgs;
+15 -3
home/dev/default.nix
··· 1 - { self, pkgs, inputs, ... }: { 2 - imports = 3 - [ ../profiles/cli ../profiles/nvim ../profiles/direnv ../profiles/git ../profiles/opencode ]; 1 + { 2 + self, 3 + pkgs, 4 + inputs, 5 + ... 6 + }: 7 + { 8 + imports = [ 9 + ../profiles/cli 10 + ../profiles/nvim 11 + ../profiles/direnv 12 + ../profiles/git 13 + ../profiles/opencode 14 + ../profiles/beets 15 + ]; 4 16 home.stateVersion = "22.05"; 5 17 }
+53
home/profiles/beets/default.nix
··· 1 + { pkgs, ... }: 2 + { 3 + programs.beets = { 4 + enable = true; 5 + package = pkgs.beets.override { 6 + pluginOverrides = { 7 + fetchart.enable = true; 8 + embedart.enable = true; 9 + lastgenre.enable = true; 10 + duplicates.enable = true; 11 + missing.enable = true; 12 + }; 13 + }; 14 + settings = { 15 + directory = "/tank/media/music"; 16 + library = "/home/anish/.local/share/beets/library.db"; 17 + 18 + import = { 19 + move = true; # Move files from new-music to library 20 + write = true; # Write tags to files 21 + log = "/tank/new-music/beets-import.log"; 22 + incremental = true; # Skip already-imported directories 23 + }; 24 + 25 + # Path format for organizing music 26 + paths = { 27 + default = "$albumartist/$album%aunique{}/$track $title"; 28 + singleton = "Non-Album/$artist/$title"; 29 + comp = "Compilations/$album%aunique{}/$track $title"; 30 + }; 31 + 32 + plugins = [ 33 + "fetchart" 34 + "embedart" 35 + "lastgenre" 36 + "duplicates" 37 + "missing" 38 + ]; 39 + 40 + fetchart = { 41 + auto = true; 42 + }; 43 + 44 + embedart = { 45 + auto = true; 46 + }; 47 + 48 + lastgenre = { 49 + auto = true; 50 + }; 51 + }; 52 + }; 53 + }
+50 -60
hosts/box/configuration.nix
··· 1 1 # Edit this configuration file to define what should be installed on 2 2 # your system. Help is available in the configuration.nix(5) man page 3 - # and in the NixOS manual (accessible by running ‘nixos-help’). 3 + # and in the NixOS manual (accessible by running 'nixos-help'). 4 4 5 5 { config, pkgs, ... }: 6 6 7 7 { 8 - imports = 9 - [ 10 - # Include the results of the hardware scan. 11 - ./hardware-configuration.nix 12 - ]; 8 + imports = [ 9 + # Include the results of the hardware scan. 10 + ./hardware-configuration.nix 11 + ./disko.nix 12 + ]; 13 13 14 14 # No systemd emergency mode (can't reliably be accessed over SSH) 15 15 systemd.enableEmergencyMode = false; 16 16 17 - # Use the GRUB 2 boot loader. 18 - boot.loader.efi.canTouchEfiVariables = false; 19 - boot.loader.efi.efiSysMountPoint = "/boot/efi"; 20 - boot.loader.grub = { 21 - enable = true; 22 - device = "nodev"; 23 - efiSupport = true; 24 - enableCryptodisk = true; 25 - efiInstallAsRemovable = true; 26 - }; 17 + # ZFS requires a hostId 18 + networking.hostId = "bb7d707a"; 27 19 28 - boot.initrd.secrets = { 29 - "/keyfile0.bin" = /etc/secrets/initrd/keyfile0.bin; 30 - "/keyfile1.bin" = /etc/secrets/initrd/keyfile1.bin; 31 - }; 20 + # Boot configuration for LUKS + ZFS 21 + boot.loader.efi.canTouchEfiVariables = true; 22 + boot.loader.efi.efiSysMountPoint = "/boot"; 23 + boot.loader.systemd-boot.enable = true; 32 24 33 - # Remote SSH unlock 34 - boot.initrd.network.enable = true; 35 - boot.initrd.network.ssh = { 36 - enable = true; 37 - port = 22; 38 - authorizedKeys = [ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDM0Zvei46x/yZl/IeBCq6+IYQQ0avulzVyBysF9cPigZMCybWRV7IEU+E3k9t6JrbdbdGfJkcZIWmsWDdKS8W8mBnZpVoT0ffLynu8JQ/TKdGm4Qv6bgUeKNrGsNv0ZPs2CDaGSLj0oJfRF7Ko10tcLP0vW+yujrh+y6TH/vVzJioaV4TGvtCUpn+wEQah9ROwPQLUUofsSWdnRsDJ/gp37zXWs4l5wyjSKtP3O9RZUP7kBekbSqEgSXiTk0oUQSVqIWl9NDiP6onk/gSOjXsR/JPqsSN/XI/c/yj6gyY0f51Ru2D7iBxuMJIJcWV+rU6coIj+ULcQWLzt/7TI8jq5AOOzI/ll4zbL24Eo84Rz+TP9tvMMhDZ0VaMN22AJ8qQEjc5P09tWKsX7Jg39XelyV1jHXncE4yvIE9F4RSCHzWCeKeXakizQNuzSaxTxIExRFYHjNW5bR6+3MTGwVrEIXU+qML+0yFTR86MT+tdY5AreAJQLwbog79O1NupeXJE= anish@curve " ]; 39 - hostKeys = [ "/etc/secrets/initrd/ssh_host_ed25519_key" ]; # create this file with `ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key` 25 + # ZFS support 26 + boot.supportedFilesystems = [ "zfs" ]; 27 + boot.zfs = { 28 + requestEncryptionCredentials = [ "tank" ]; # Load key for tank pool 29 + forceImportRoot = false; 40 30 }; 41 - boot.initrd.availableKernelModules = [ "igc" "iwlwifi" ]; 42 31 43 - boot.initrd.luks.devices = { 44 - "root" = { 45 - #name = "root"; 46 - device = "/dev/disk/by-uuid/f37f3222-47d7-42d8-b400-363320a31853"; # UUID for /dev/nvme01np2 47 - preLVM = true; 48 - allowDiscards = true; 49 - keyFile = "/keyfile0.bin"; 32 + # ZFS services 33 + services.zfs = { 34 + autoScrub = { 35 + enable = true; 36 + interval = "weekly"; 50 37 }; 38 + autoSnapshot = { 39 + enable = true; 40 + frequent = 4; # 15-minute snapshots 41 + hourly = 24; 42 + daily = 7; 43 + weekly = 4; 44 + monthly = 12; 45 + }; 46 + trim.enable = true; 51 47 }; 52 48 53 - # Data mount 54 - # fileSystems."/data" = { 55 - # device = "/dev/disk/by-uuid/3276a297-9ee4-4998-b262-1ed100366c06"; # UUID for /dev/mapper/crypted-data 56 - # encrypted = { 57 - # enable = true; 58 - # label = "crypted-data"; 59 - # blkDev = "/dev/disk/by-uuid/8a317bf4-fe13-4334-a6df-5fe5a5048b5e"; # UUID for /dev/sda1 60 - # keyFile = "/keyfile1.bin"; 61 - # }; 62 - # }; 63 - 64 49 networking.interfaces.enp2s0 = { 65 - ipv4.addresses = [{ 66 - address = "192.168.1.240"; 67 - prefixLength = 24; 68 - }]; 69 - ipv6.addresses = [{ 70 - address = "fd7d:587a:4300:1::240"; 71 - prefixLength = 64; 72 - }]; 73 - ipv4.routes = [{ address = "192.168.1.0"; prefixLength = 24; via = "192.168.1.1"; }]; 50 + ipv4.addresses = [ 51 + { 52 + address = "192.168.1.240"; 53 + prefixLength = 24; 54 + } 55 + ]; 56 + ipv6.addresses = [ 57 + { 58 + address = "fd7d:587a:4300:1::240"; 59 + prefixLength = 64; 60 + } 61 + ]; 62 + ipv4.routes = [ 63 + { 64 + address = "192.168.1.0"; 65 + prefixLength = 24; 66 + via = "192.168.1.1"; 67 + } 68 + ]; 74 69 useDHCP = false; 75 70 }; 76 - #networking.nameservers = [ "172.16.11.240" ]; 77 71 networking.nameservers = [ "192.168.1.1" ]; 78 72 networking.defaultGateway = { 79 73 address = "192.168.1.1"; ··· 81 75 }; 82 76 83 77 networking.hostName = "box"; # Define your hostname. 84 - # networking.wireless.enable = true; # Enables wireless support via wpa_supplicant. 85 78 86 79 # The global useDHCP flag is deprecated, therefore explicitly set to false here. 87 - # Per-interface useDHCP will be mandatory in the future, so this generated config 88 - # replicates the default behaviour. 89 80 networking.useDHCP = false; 90 81 networking.interfaces.wlp3s0.useDHCP = true; 91 82 ··· 103 94 system.stateVersion = "19.09"; # Did you read the comment? 104 95 105 96 } 106 -
+36 -30
hosts/box/default.nix
··· 1 - { self, pkgs, ... }: { 1 + { self, pkgs, ... }: 2 + { 2 3 imports = [ 3 4 ./configuration.nix 4 5 ../profiles/core ··· 6 7 ../profiles/taskd 7 8 ../profiles/shaarli 8 9 ../profiles/dns 9 - # ../profiles/monitoring 10 + ../profiles/monitoring 10 11 ../profiles/nfs 11 12 ../profiles/gonic 12 - ../profiles/headphones 13 + ../profiles/headphones 13 14 ../profiles/radicale 14 15 # ../profiles/seafile # waiting for https://github.com/NixOS/nixpkgs/pull/249523 to be merged 15 16 ../profiles/syncthing ··· 19 20 ../profiles/finance 20 21 ../profiles/sync/website 21 22 ../profiles/sync/music 22 - ../profiles/grasp 23 - # ../profiles/archivebox 24 - # ../profiles/woodpecker-agent 25 - # ../profiles/jellyfin 23 + # ../profiles/grasp # private repo - disabled 24 + # ../profiles/archivebox # requires insecure django - fix in flake.nix permittedInsecurePackages 25 + ../profiles/woodpecker-agent 26 + ../profiles/jellyfin 26 27 ../profiles/ulogger-server 27 28 ../profiles/immich 28 29 ../profiles/jacket 29 30 ../profiles/gpodder 30 31 ../profiles/transmission 31 32 ../profiles/raven 32 - #../profiles/postgres_upgrade_script 33 + # ../profiles/postgres_upgrade_script # one-time use 33 34 ]; 34 35 35 36 # Backups 36 37 age.secrets.borg-password.file = "${self}/secrets/borg-password.age"; 37 38 services.postgresqlBackup = { 38 39 enable = true; 39 - databases = [ "wallabag" "immich" "ulogger" ]; 40 - location = "/var/backup/postgresql"; 40 + databases = [ 41 + "wallabag" 42 + "immich" 43 + "ulogger" 44 + ]; 45 + location = "/tank/backup/postgresql"; 41 46 }; 42 47 mossnet.backup = { 43 48 enable = true; 44 49 name = "mossnet"; 45 50 paths = [ 46 51 "/var/lib/taskserver" # taskwarrior 47 - "/var/www/shaarli-config" # sharli 48 - "/var/backup/postgresql" # wallabag 52 + "/var/www/shaarli-config" # shaarli 53 + "/tank/backup/postgresql" # postgresql backups 49 54 "/var/lib/radicale" # radicale 50 - "/home/anish/usr/drawing" # syncthing 51 - "/data/books" # calibre-web 52 - # "/home/anish/usr/nonfiction" # syncthing 55 + "/tank/syncthing/drawing" # syncthing 56 + "/tank/books" # calibre-web 53 57 "/home/anish/usr/finance" # beancount 54 - "/mnt/two/postgres" # sealight postgres backups TODO remove once moved to capsul 55 - "/mnt/two/photos" 56 - "/mnt/two/music" 58 + "/tank/postgres" # postgres data 59 + "/tank/media/photos" 60 + "/tank/media/music" 57 61 ]; 58 - # seafile 59 62 }; 60 63 61 64 # opencode-manager ports 62 65 networking.firewall = { 63 66 allowedTCPPorts = [ 64 - 5003 # opencode-manager backend 65 - 5173 # opencode-manager frontend 66 - 5551 # opencode server 67 + 5003 # opencode-manager backend 68 + 5173 # opencode-manager frontend 69 + 5551 # opencode server 67 70 ]; 68 - allowedTCPPortRanges = [{ 69 - from = 7000; 70 - to = 9000; 71 - }]; # ports for testing user changes 71 + allowedTCPPortRanges = [ 72 + { 73 + from = 7000; 74 + to = 9000; 75 + } 76 + ]; # ports for testing user changes 72 77 }; 73 78 74 79 environment.systemPackages = with pkgs; [ lm_sensors ]; 75 - hardware.fancontrol = { 76 - enable = false; 77 - config = ''''; 78 - }; 80 + # hardware.fancontrol = { 81 + # enable = false; 82 + # config = ''''; 83 + # }; 79 84 85 + # Secrets 80 86 age.secrets.box-wg.file = "${self}/secrets/box-wg.age"; 81 87 age.secrets.box-wg.owner = "anish"; 82 88 age.secrets.borg-key.file = "${self}/secrets/borg-key.age";
+238
hosts/box/disko.nix
··· 1 + # Disko configuration for box NAS 2 + # NVMe boot drive with LUKS + 3x 4TB ZFS RAIDZ1 pool (~8TB usable) 3 + # Unified encryption: LUKS passphrase unlocks root, ZFS uses keyfile inside encrypted root 4 + # 5 + # NOTE: Using RAIDZ1 (3 drives) temporarily due to DOA drive. Can migrate to RAIDZ2 6 + # with 4 drives later by creating a new pool and copying data. 7 + # 8 + # Installation steps: 9 + # 10 + # 1. Generate the ZFS keyfile and LUKS password: 11 + # dd if=/dev/urandom of=/tmp/tank.key bs=32 count=1 12 + # echo -n "your-luks-password" > /tmp/luks-password 13 + # 14 + # 2. Run disko-install: 15 + # sudo nix run 'github:nix-community/disko/latest#disko-install' -- \ 16 + # --flake ~/helm#box \ 17 + # --disk nvme /dev/disk/by-id/nvme-CT500P310SSD8_2544543B87C2 \ 18 + # --disk zfs1 /dev/disk/by-id/ata-WDC_WD40EFPX-68C6CN0_WD-WX32D954A2J7 \ 19 + # --disk zfs2 /dev/disk/by-id/ata-WDC_WD40EFPX-68C6CN0_WD-WX32D95FVZVL \ 20 + # --disk zfs3 /dev/disk/by-id/ata-WDC_WD40EFPX-68C6CN0_WD-WX42D95M807R 21 + # 22 + # 3. Copy the keyfile and update keylocation: 23 + # sudo mkdir -p /mnt/etc/zfs 24 + # sudo cp /tmp/tank.key /mnt/etc/zfs/tank.key 25 + # sudo chmod 000 /mnt/etc/zfs/tank.key 26 + # sudo zfs set keylocation=file:///etc/zfs/tank.key tank 27 + { 28 + disko.devices = { 29 + disk = { 30 + # Boot drive - 500GB NVMe with LUKS encryption 31 + nvme = { 32 + type = "disk"; 33 + device = "/dev/disk/by-id/nvme-placeholder"; # Override with --disk nvme /dev/disk/by-id/... 34 + content = { 35 + type = "gpt"; 36 + partitions = { 37 + ESP = { 38 + size = "512M"; 39 + type = "EF00"; 40 + content = { 41 + type = "filesystem"; 42 + format = "vfat"; 43 + mountpoint = "/boot"; 44 + mountOptions = [ "umask=0077" ]; 45 + }; 46 + }; 47 + luks = { 48 + size = "100%"; 49 + content = { 50 + type = "luks"; 51 + name = "cryptroot"; 52 + settings = { 53 + allowDiscards = true; 54 + }; 55 + # Passphrase will be prompted during boot 56 + passwordFile = "/tmp/luks-password"; # Only used during install, set this before running disko 57 + content = { 58 + type = "filesystem"; 59 + format = "ext4"; 60 + mountpoint = "/"; 61 + mountOptions = [ "noatime" ]; 62 + }; 63 + }; 64 + }; 65 + }; 66 + }; 67 + }; 68 + 69 + # ZFS pool drives - 3x 4TB in RAIDZ1 70 + zfs1 = { 71 + type = "disk"; 72 + device = "/dev/disk/by-id/placeholder-zfs1"; # Override with --disk zfs1 /dev/disk/by-id/... 73 + content = { 74 + type = "gpt"; 75 + partitions = { 76 + zfs = { 77 + size = "100%"; 78 + content = { 79 + type = "zfs"; 80 + pool = "tank"; 81 + }; 82 + }; 83 + }; 84 + }; 85 + }; 86 + zfs2 = { 87 + type = "disk"; 88 + device = "/dev/disk/by-id/placeholder-zfs2"; 89 + content = { 90 + type = "gpt"; 91 + partitions = { 92 + zfs = { 93 + size = "100%"; 94 + content = { 95 + type = "zfs"; 96 + pool = "tank"; 97 + }; 98 + }; 99 + }; 100 + }; 101 + }; 102 + zfs3 = { 103 + type = "disk"; 104 + device = "/dev/disk/by-id/placeholder-zfs3"; 105 + content = { 106 + type = "gpt"; 107 + partitions = { 108 + zfs = { 109 + size = "100%"; 110 + content = { 111 + type = "zfs"; 112 + pool = "tank"; 113 + }; 114 + }; 115 + }; 116 + }; 117 + }; 118 + }; 119 + 120 + zpool = { 121 + tank = { 122 + type = "zpool"; 123 + mode = "raidz1"; 124 + options = { 125 + ashift = "12"; 126 + cachefile = "none"; # Needed for disko 127 + }; 128 + rootFsOptions = { 129 + compression = "lz4"; 130 + atime = "off"; 131 + xattr = "sa"; 132 + acltype = "posixacl"; 133 + # ZFS native encryption 134 + # During install: keyfile at /tmp/tank.key 135 + # After install: install-box.sh copies to /etc/zfs/tank.key and updates keylocation 136 + encryption = "aes-256-gcm"; 137 + keyformat = "raw"; 138 + keylocation = "file:///tmp/tank.key"; 139 + "com.sun:auto-snapshot" = "false"; 140 + }; 141 + # Don't mount the pool root directly 142 + mountpoint = null; 143 + 144 + datasets = { 145 + # /nix is on the NVMe ext4 root, not on ZFS 146 + # This simplifies boot dependencies - ZFS is purely for data storage 147 + 148 + # Parent dataset for all data - inherits encryption 149 + data = { 150 + type = "zfs_fs"; 151 + options.mountpoint = "none"; 152 + }; 153 + 154 + # Media datasets 155 + "data/media" = { 156 + type = "zfs_fs"; 157 + options.mountpoint = "none"; 158 + }; 159 + "data/media/music" = { 160 + type = "zfs_fs"; 161 + mountpoint = "/tank/media/music"; 162 + options.recordsize = "1M"; # Large files benefit from larger recordsize 163 + }; 164 + "data/media/photos" = { 165 + type = "zfs_fs"; 166 + mountpoint = "/tank/media/photos"; 167 + options.recordsize = "1M"; 168 + }; 169 + "data/media/movies" = { 170 + type = "zfs_fs"; 171 + mountpoint = "/tank/media/movies"; 172 + options.recordsize = "1M"; 173 + }; 174 + "data/media/tv" = { 175 + type = "zfs_fs"; 176 + mountpoint = "/tank/media/tv"; 177 + options.recordsize = "1M"; 178 + }; 179 + 180 + # Other data 181 + "data/books" = { 182 + type = "zfs_fs"; 183 + mountpoint = "/tank/books"; 184 + options."com.sun:auto-snapshot" = "true"; 185 + }; 186 + "data/podcasts" = { 187 + type = "zfs_fs"; 188 + mountpoint = "/tank/podcasts"; 189 + }; 190 + "data/postgres" = { 191 + type = "zfs_fs"; 192 + mountpoint = "/tank/postgres"; 193 + options = { 194 + recordsize = "16K"; # Better for databases 195 + "com.sun:auto-snapshot" = "true"; 196 + }; 197 + }; 198 + "data/syncthing" = { 199 + type = "zfs_fs"; 200 + options.mountpoint = "none"; 201 + options."com.sun:auto-snapshot" = "true"; 202 + }; 203 + "data/syncthing/drawing" = { 204 + type = "zfs_fs"; 205 + mountpoint = "/tank/syncthing/drawing"; 206 + }; 207 + "data/backup" = { 208 + type = "zfs_fs"; 209 + mountpoint = "/tank/backup"; 210 + options."com.sun:auto-snapshot" = "true"; 211 + }; 212 + "data/ftp" = { 213 + type = "zfs_fs"; 214 + mountpoint = "/tank/ftp"; 215 + }; 216 + "data/cache" = { 217 + type = "zfs_fs"; 218 + mountpoint = "/tank/cache"; 219 + options."com.sun:auto-snapshot" = "false"; 220 + }; 221 + "data/playlists" = { 222 + type = "zfs_fs"; 223 + mountpoint = "/tank/playlists"; 224 + }; 225 + "data/new-music" = { 226 + type = "zfs_fs"; 227 + mountpoint = "/tank/new-music"; 228 + }; 229 + "data/archive" = { 230 + type = "zfs_fs"; 231 + mountpoint = "/tank/archive"; 232 + options."com.sun:auto-snapshot" = "true"; 233 + }; 234 + }; 235 + }; 236 + }; 237 + }; 238 + }
+24 -56
hosts/box/hardware-configuration.nix
··· 1 - # Do not modify this file! It was generated by ‘nixos-generate-config’ 2 - # and may be overwritten by future invocations. Please make changes 3 - # to /etc/nixos/configuration.nix instead. 4 - { config, lib, pkgs, modulesPath, ... }: 1 + # Hardware configuration for box NAS 2 + # Filesystem mounts are handled by disko.nix 3 + { 4 + config, 5 + lib, 6 + pkgs, 7 + modulesPath, 8 + ... 9 + }: 5 10 6 11 { 7 - imports = 8 - [ 9 - (modulesPath + "/installer/scan/not-detected.nix") 10 - ]; 12 + imports = [ 13 + (modulesPath + "/installer/scan/not-detected.nix") 14 + ]; 11 15 12 - boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "thunderbolt" "uas" "usb_storage" "sd_mod" ]; 13 - boot.initrd.kernelModules = [ "dm-snapshot" ]; 16 + boot.initrd.availableKernelModules = [ 17 + "xhci_pci" 18 + "ahci" 19 + "nvme" 20 + "thunderbolt" 21 + "uas" 22 + "usb_storage" 23 + "sd_mod" 24 + ]; 25 + boot.initrd.kernelModules = [ ]; 14 26 boot.kernelModules = [ "kvm-intel" ]; 15 27 boot.extraModulePackages = [ ]; 16 28 17 - fileSystems."/" = 18 - { 19 - device = "/dev/disk/by-uuid/ade0752d-84d3-4e39-865b-9027ba2d5c67"; 20 - fsType = "ext4"; 21 - }; 22 - 23 - fileSystems."/boot/efi" = 24 - { 25 - device = "/dev/disk/by-uuid/1715-278E"; 26 - fsType = "vfat"; 27 - }; 28 - 29 - fileSystems."/mnt/one" = 30 - { 31 - device = "/dev/disk/by-uuid/0f857c6e-509d-436f-9e78-bc25f1b0d23b"; 32 - fsType = "ext4"; 33 - options = [ 34 - "noatime" 35 - "nodiratime" 36 - "nofail" 37 - ]; 38 - }; 39 - 40 - fileSystems."/mnt/two" = 41 - { 42 - device = "/dev/disk/by-uuid/5bc894bf-ed87-4c30-aab4-87e154e0cd08"; 43 - fsType = "ext4"; 44 - options = [ 45 - "noatime" 46 - "nodiratime" 47 - "nofail" 48 - ]; 49 - }; 50 - 51 - fileSystems."/mnt/three" = 52 - { 53 - device = "/dev/disk/by-uuid/0be3ded1-9c8b-40aa-94ca-dc2297d5988e"; 54 - fsType = "ext4"; 55 - options = [ 56 - "noatime" 57 - "nodiratime" 58 - "nofail" 59 - ]; 60 - }; 29 + # Filesystems are managed by disko - do not define them here 61 30 62 - swapDevices = 63 - [{ device = "/dev/disk/by-uuid/b790abb4-ba5f-4476-8f09-b0fc575414aa"; }]; 31 + swapDevices = [ ]; 64 32 65 33 powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; 66 34 hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
+58 -29
hosts/curve/default.nix
··· 1 - { self, pkgs, ... }: { 1 + { self, pkgs, ... }: 2 + { 2 3 imports = [ 3 4 ./configuration.nix 4 5 ../users/anish ··· 26 27 programs.gnupg.agent = { 27 28 enable = true; 28 29 pinentryPackage = pkgs.pinentry-rofi; 29 - }; 30 + }; 30 31 31 32 hardware.keyboard.qmk.enable = true; 32 33 services.udev.packages = with pkgs; [ via ]; ··· 36 37 37 38 virtualisation.docker.enable = true; 38 39 virtualisation.docker.storageDriver = "btrfs"; 39 - environment.systemPackages = with pkgs; [ docker-compose via ]; 40 + environment.systemPackages = with pkgs; [ 41 + docker-compose 42 + via 43 + ]; 40 44 41 - # Speed up boot by removing dependency on network 45 + # Speed up boot by removing dependency on network 42 46 systemd = { 43 - targets.network-online.wantedBy = 44 - pkgs.lib.mkForce [ ]; # Normally ["multi-user.target"] 45 - services.NetworkManager-wait-online.wantedBy = 46 - pkgs.lib.mkForce [ ]; # Normally ["network-online.target"] 47 + targets.network-online.wantedBy = pkgs.lib.mkForce [ ]; # Normally ["multi-user.target"] 48 + services.NetworkManager-wait-online.wantedBy = pkgs.lib.mkForce [ ]; # Normally ["network-online.target"] 47 49 }; 48 50 49 - 50 - 51 51 fileSystems."/mnt/ftp" = { 52 - device = "192.168.1.240:/home/ftp"; 52 + device = "192.168.1.240:/tank/ftp"; 53 53 fsType = "nfs"; 54 - options = [ "x-systemd.automount" "noauto" "x-systemd.idle-timeout=600" ]; 54 + options = [ 55 + "x-systemd.automount" 56 + "noauto" 57 + "x-systemd.idle-timeout=600" 58 + ]; 55 59 }; 56 60 57 61 fileSystems."/mnt/tv" = { 58 - device = "192.168.1.240:/mnt/three/tv"; 62 + device = "192.168.1.240:/tank/media/tv"; 59 63 fsType = "nfs"; 60 - options = [ "x-systemd.automount" "noauto" "x-systemd.idle-timeout=600" ]; 64 + options = [ 65 + "x-systemd.automount" 66 + "noauto" 67 + "x-systemd.idle-timeout=600" 68 + ]; 61 69 }; 62 70 63 71 fileSystems."/mnt/movies" = { 64 - device = "192.168.1.240:/mnt/three/movies"; 72 + device = "192.168.1.240:/tank/media/movies"; 65 73 fsType = "nfs"; 66 - options = [ "x-systemd.automount" "noauto" "x-systemd.idle-timeout=600" ]; 74 + options = [ 75 + "x-systemd.automount" 76 + "noauto" 77 + "x-systemd.idle-timeout=600" 78 + ]; 67 79 }; 68 80 69 81 boot.supportedFilesystems = [ "ntfs" ]; ··· 77 89 78 90 # lazy enable of ports necessary for KDE connect which is installed via cli home profile (for some reason?) 79 91 networking.firewall = { 80 - allowedTCPPorts = [ 22 4173 3000 ]; # allow ssh and vibekanban 81 - allowedTCPPortRanges = [{ 82 - from = 1714; 83 - to = 1764; 84 - }]; 85 - allowedUDPPortRanges = [{ 86 - from = 1714; 87 - to = 1764; 88 - }]; 92 + allowedTCPPorts = [ 93 + 22 94 + 4173 95 + 3000 96 + ]; # allow ssh and vibekanban 97 + allowedTCPPortRanges = [ 98 + { 99 + from = 1714; 100 + to = 1764; 101 + } 102 + ]; 103 + allowedUDPPortRanges = [ 104 + { 105 + from = 1714; 106 + to = 1764; 107 + } 108 + ]; 89 109 }; 90 110 91 111 age.secrets.curve-wg.file = "${self}/secrets/curve-wg.age"; ··· 105 125 mossnet.backup = { 106 126 enable = true; 107 127 name = "curve"; 108 - paths = [ "/home/anish/usr" "/home/anish/.ssh" "/home/anish/.password-store/" ]; 128 + paths = [ 129 + "/home/anish/usr" 130 + "/home/anish/.ssh" 131 + "/home/anish/.password-store/" 132 + ]; 109 133 }; 110 134 111 - # enable adb 135 + # enable adb 112 136 # TODO move this (it's for KaiOS WebIDE devShell?) 113 137 programs.adb.enable = true; 114 138 #virtualisation.docker.enable = true; 115 139 boot.blacklistedKernelModules = [ "qcserial" ]; 116 140 # Used for packer Capsul 117 - users.users.anish.extraGroups = 118 - [ "adbusers" "wheel" "plugdev" "libvertd" "docker" ]; 141 + users.users.anish.extraGroups = [ 142 + "adbusers" 143 + "wheel" 144 + "plugdev" 145 + "libvertd" 146 + "docker" 147 + ]; 119 148 virtualisation.libvirtd.enable = true; 120 149 hardware.keyboard.zsa.enable = true; 121 150 services.udev.extraRules = ''
+17 -6
hosts/profiles/archivebox/default.nix
··· 1 - { config, options, lib, pkgs, ... }: 1 + { 2 + config, 3 + options, 4 + lib, 5 + pkgs, 6 + ... 7 + }: 2 8 with lib; 3 9 let 4 10 # cfg = config.services.archivebox; ··· 7 13 port = "8123"; 8 14 in 9 15 { 10 - nixpkgs.config.permittedInsecurePackages = [ 11 - "python3.10-django-3.1.14" 12 - ]; 16 + # Note: permittedInsecurePackages must be set in flake.nix nixpkgsFor config 17 + # if archivebox still requires python3.10-django-3.1.14 13 18 14 19 services.nginx.virtualHosts."archive.mossnet.lan" = { 15 20 enableACME = false; ··· 26 31 systemd.services.archivebox-install = { 27 32 description = "archivebox install service"; 28 33 wantedBy = [ "multi-user.target" ]; 29 - path = with pkgs; [ coreutils archivebox ]; 34 + path = with pkgs; [ 35 + coreutils 36 + archivebox 37 + ]; 30 38 31 39 serviceConfig = { 32 40 User = user; ··· 51 59 systemd.services.archivebox-server = { 52 60 description = "archivebox server service"; 53 61 wantedBy = [ "multi-user.target" ]; 54 - path = with pkgs; [ coreutils archivebox ]; 62 + path = with pkgs; [ 63 + coreutils 64 + archivebox 65 + ]; 55 66 56 67 serviceConfig = { 57 68 User = user;
+2 -2
hosts/profiles/calibre/default.nix
··· 8 8 user = "calibre-server"; 9 9 group = "calibre-server"; 10 10 options = { 11 - calibreLibrary = "/data/books"; 11 + calibreLibrary = "/tank/books"; 12 12 enableBookUploading = true; 13 13 }; 14 14 }; ··· 20 20 21 21 services.calibre-server = { 22 22 enable = true; 23 - libraries = [ "/data/books" ]; 23 + libraries = [ "/tank/books" ]; 24 24 # Bug in the module puts this in quotes in the systemd file 25 25 # user = calibre; 26 26 # group = calibre;
+10 -5
hosts/profiles/gonic/default.nix
··· 1 - { config, lib, pkgs, ... }: 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 2 7 { 3 8 environment.systemPackages = [ pkgs.ffmpeg ]; 4 9 mossnet.gonic.enable = true; 5 10 mossnet.gonic.settings = '' 6 - music-path /mnt/two/music/ 7 - podcast-path /data/podcasts 8 - cache-path /data/cache 9 - playlists-path /data/playlists 11 + music-path /tank/media/music/ 12 + podcast-path /tank/podcasts 13 + cache-path /tank/cache 14 + playlists-path /tank/playlists 10 15 ''; 11 16 mossnet.gonic.user = "gonic"; 12 17 mossnet.gonic.group = "audio";
+7 -2
hosts/profiles/headphones/default.nix
··· 1 - { config, lib, pkgs, ... }: 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 2 7 { 3 8 services.headphones = { 4 9 enable = true; ··· 6 11 port = 8181; 7 12 user = "headphones"; 8 13 group = "audio"; 9 - dataDir = "/data/music"; 14 + dataDir = "/tank/media/music"; 10 15 }; 11 16 services.nginx.virtualHosts."headphones.mossnet.lan" = { 12 17 enableACME = false;
+1 -1
hosts/profiles/immich/default.nix
··· 8 8 }; 9 9 host = "0.0.0.0"; 10 10 port = 8567; 11 - mediaLocation = "/mnt/two/photos"; 11 + mediaLocation = "/tank/media/photos"; 12 12 openFirewall = true; 13 13 settings.server.externalDomain = "https://photos.sealight.xyz"; 14 14 };
+23 -8
hosts/profiles/jellyfin/default.nix
··· 1 - { config, lib, pkgs, ... }: 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 2 7 { 3 8 # Enable Hardware Acceleration for transcoding 4 - nixpkgs.config.packageOverrides = pkgs: { 5 - vaapiIntel = pkgs.vaapiIntel.override { enableHybridCodec = true; }; 6 - }; 7 - hardware.opengl = { 9 + # Note: vaapiIntel override with enableHybridCodec should be in flake.nix overlay if needed 10 + hardware.graphics = { 8 11 enable = true; 9 12 extraPackages = with pkgs; [ 10 13 intel-media-driver ··· 18 21 enable = true; 19 22 user = "jellyfin"; 20 23 group = "video"; 21 - openFirewall = true; # only for defaults 24 + openFirewall = true; # only for defaults (8096) 22 25 }; 23 - networking.firewall.allowedTCPPorts = [ 8181 ]; 24 26 users.users.jellyfin = { 25 - extraGroups = [ "video" "audio" ]; 27 + extraGroups = [ 28 + "video" 29 + "audio" 30 + ]; 31 + }; 32 + services.nginx = { 33 + enable = true; 34 + virtualHosts = { 35 + "jellyfin.mossnet.lan" = { 36 + forceSSL = false; 37 + enableACME = false; 38 + locations."/".proxyPass = "http://localhost:8096/"; 39 + }; 40 + }; 26 41 }; 27 42 }
+30 -18
hosts/profiles/monitoring/default.nix
··· 1 - { self, config, pkgs, ... }: { 1 + { 2 + self, 3 + config, 4 + pkgs, 5 + ... 6 + }: 7 + { 2 8 age.secrets.nullhex-smtp.file = "${self}/secrets/nullhex-smtp.age"; 3 9 age.secrets.nullhex-smtp.owner = "grafana"; 4 10 ··· 25 31 }; 26 32 27 33 # nginx reverse proxy 28 - # services.nginx.recommendedProxySettings = true; # Needed for new grafana versions 34 + # services.nginx.recommendedProxySettings = true; # Needed for new grafana versions 29 35 services.nginx.virtualHosts.${config.services.grafana.settings.server.domain} = { 30 36 locations."/" = { 31 37 proxyPass = "http://127.0.0.1:2342"; ··· 33 39 }; 34 40 }; 35 41 36 - services.postgresql = { 37 - ensureUsers = [{ 42 + services.postgresql = { 43 + ensureUsers = [ 44 + { 38 45 name = "grafana"; 39 - # TODO this is deprecated 40 - # Need to translate this to 41 - # systemd.services.postgresql.postStart 42 - # or initialScript 43 - ensurePermissions = { 44 - "ALL TABLES IN SCHEMA public" = "SELECT"; 45 - "DATABASE wallabag" = "CONNECT"; 46 - "DATABASE ulogger" = "CONNECT"; 47 - "DATABASE photoprism" = "CONNECT"; 48 - }; 49 - }]; 50 - }; 46 + } 47 + ]; 48 + }; 49 + 50 + # Grant grafana user read access to databases for monitoring 51 + systemd.services.postgresql.postStart = pkgs.lib.mkAfter '' 52 + $PSQL -tAc "GRANT CONNECT ON DATABASE wallabag TO grafana" 2>/dev/null || true 53 + $PSQL -tAc "GRANT CONNECT ON DATABASE ulogger TO grafana" 2>/dev/null || true 54 + $PSQL -tAc "GRANT CONNECT ON DATABASE photoprism TO grafana" 2>/dev/null || true 55 + $PSQL -d wallabag -tAc "GRANT SELECT ON ALL TABLES IN SCHEMA public TO grafana" 2>/dev/null || true 56 + $PSQL -d ulogger -tAc "GRANT SELECT ON ALL TABLES IN SCHEMA public TO grafana" 2>/dev/null || true 57 + $PSQL -d photoprism -tAc "GRANT SELECT ON ALL TABLES IN SCHEMA public TO grafana" 2>/dev/null || true 58 + ''; 51 59 52 60 services.prometheus = { 53 61 enable = true; ··· 66 74 scrapeConfigs = [ 67 75 { 68 76 job_name = "box"; 69 - static_configs = [{ targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.node.port}" ]; }]; 77 + static_configs = [ 78 + { targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.node.port}" ]; } 79 + ]; 70 80 } 71 81 { 72 82 job_name = "dns"; 73 - static_configs = [{ targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.dnsmasq.port}" ]; }]; 83 + static_configs = [ 84 + { targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.dnsmasq.port}" ]; } 85 + ]; 74 86 } 75 87 ]; 76 88 };
+21 -6
hosts/profiles/nfs/default.nix
··· 12 12 statdPort = 4000; 13 13 extraNfsdConfig = ''''; 14 14 exports = '' 15 - /home/ftp 192.168.1.0/24(rw) 16 - /mnt/one 192.168.1.0/24(rw) 17 - /mnt/two 192.168.1.0/24(rw,async,no_subtree_check) 10.0.69.0/24(rw,async,no_subtree_check) 18 - /mnt/three 192.168.1.0/24(rw) 15 + /tank/ftp 192.168.1.0/24(rw) 16 + /tank/media/music 192.168.1.0/24(rw,async,no_subtree_check) 10.0.69.0/24(rw,async,no_subtree_check) 17 + /tank/media/photos 192.168.1.0/24(rw,async,no_subtree_check) 10.0.69.0/24(rw,async,no_subtree_check) 18 + /tank/media/movies 192.168.1.0/24(rw) 19 + /tank/media/tv 192.168.1.0/24(rw) 19 20 ''; 20 21 }; 21 22 22 23 networking.firewall = { 23 - allowedTCPPorts = [ 111 2049 4000 4001 4002 20048 ]; 24 - allowedUDPPorts = [ 111 2049 4000 4001 4002 20048 ]; 24 + allowedTCPPorts = [ 25 + 111 26 + 2049 27 + 4000 28 + 4001 29 + 4002 30 + 20048 31 + ]; 32 + allowedUDPPorts = [ 33 + 111 34 + 2049 35 + 4000 36 + 4001 37 + 4002 38 + 20048 39 + ]; 25 40 }; 26 41 27 42 #systemd.services.create-mount-dir = {
+4 -4
hosts/profiles/sync/music/get-music.sh
··· 4 4 5 5 REMOTE_HOST="aynish@talos.feralhosting.com" 6 6 REMOTE_PATH="private/transmission/data/" 7 - LOCAL_PATH="/mnt/two/incoming" 8 - TRACKING_FILE="/mnt/two/incoming/.downloaded_albums" 9 - LOG_FILE="/mnt/two/incoming/download-log" 7 + LOCAL_PATH="/tank/new-music" 8 + TRACKING_FILE="/tank/new-music/.downloaded_albums" 9 + LOG_FILE="/tank/new-music/download-log" 10 10 11 11 # Create tracking file if it doesn't exist 12 12 touch "$TRACKING_FILE" ··· 47 47 echo "$(date): Importing $album to beets..." >>"$LOG_FILE" 48 48 # Set umask to allow group read/write access 49 49 umask 002 50 - if beet -p fetchart import -m -l /home/anish/music.log -q -g "$LOCAL_PATH/$album"; then 50 + if beet import -q "$LOCAL_PATH/$album"; then 51 51 echo "$(date): Successfully imported $album to beets" >>"$LOG_FILE" 52 52 else 53 53 echo "$(date): Failed to import $album to beets" >>"$LOG_FILE"
+3 -3
hosts/profiles/transmission/default.nix
··· 11 11 # Normally, I would write this into the homedir with home-manager 12 12 # And explictly set the dir to be the output of the home-manager location 13 13 script-torrent-done-filename = pkgs.writeShellScript "beet-import.sh" '' 14 - #!/usr/bin/env bash 14 + #!/usr/bin/env bash 15 15 16 - beet -p fetchart import -l /home/anish/music.log -q -g "$TR_TORRENT_DIR" 16 + beet -p fetchart import -l /home/anish/music.log -q -g "$TR_TORRENT_DIR" 17 17 ''; 18 18 rpc-url = "/transmission/rpc/"; 19 - download-dir = "/mnt/two/new-music"; 19 + download-dir = "/tank/new-music"; 20 20 }; 21 21 }; 22 22 services.nginx.virtualHosts."transmission.mossnet.lan" = {
+119
install-box.sh
··· 1 + #!/usr/bin/env bash 2 + set -euo pipefail 3 + 4 + # Install script for box NAS 5 + # Run this from the NixOS installer after rsync'ing the helm repo 6 + # 7 + # Prerequisites: 8 + # - Boot NixOS installer 9 + # - Enable SSH: passwd && sudo systemctl start sshd 10 + # - rsync helm repo: rsync -avz --exclude='.git' /path/to/helm nixos@<IP>:~/ 11 + # 12 + # Usage: 13 + # cd ~/helm 14 + # ./install-box.sh 15 + 16 + # Configuration 17 + FLAKE="$HOME/helm#box" 18 + NVME="/dev/disk/by-id/nvme-CT500P310SSD8_2544543B87C2" 19 + 20 + # ZFS drives - update these if drives change 21 + ZFS1="/dev/disk/by-id/ata-WDC_WD40EFPX-68C6CN0_WD-WX32D954A2J7" 22 + ZFS2="/dev/disk/by-id/ata-WDC_WD40EFPX-68C6CN0_WD-WX32D95FVZVL" 23 + ZFS3="/dev/disk/by-id/ata-WDC_WD40EFPX-68C6CN0_WD-WX42D95M807R" 24 + 25 + echo "=== Box NAS Installation ===" 26 + echo "" 27 + echo "This will install NixOS with:" 28 + echo " - NVMe boot drive: $NVME" 29 + echo " - ZFS RAIDZ1 pool with 3x 4TB drives (~8TB usable)" 30 + echo "" 31 + 32 + # Verify drives exist 33 + echo "Verifying drives..." 34 + for disk in "$NVME" "$ZFS1" "$ZFS2" "$ZFS3"; do 35 + if [[ ! -e "$disk" ]]; then 36 + echo "ERROR: Disk not found: $disk" 37 + echo "Available disks:" 38 + ls -la /dev/disk/by-id/ | grep -E '(nvme|ata)' | grep -v part 39 + exit 1 40 + fi 41 + done 42 + echo "All drives found." 43 + echo "" 44 + 45 + # Generate ZFS keyfile 46 + echo "Generating ZFS keyfile..." 47 + dd if=/dev/urandom of=/tmp/tank.key bs=32 count=1 2>/dev/null 48 + echo "ZFS keyfile created at /tmp/tank.key" 49 + echo "" 50 + 51 + # Get LUKS password 52 + echo "Enter LUKS password for boot drive encryption:" 53 + read -s LUKS_PASSWORD 54 + echo "" 55 + echo "Confirm LUKS password:" 56 + read -s LUKS_PASSWORD_CONFIRM 57 + echo "" 58 + 59 + if [[ "$LUKS_PASSWORD" != "$LUKS_PASSWORD_CONFIRM" ]]; then 60 + echo "ERROR: Passwords do not match" 61 + exit 1 62 + fi 63 + 64 + echo -n "$LUKS_PASSWORD" > /tmp/luks-password 65 + echo "LUKS password saved." 66 + echo "" 67 + 68 + # Confirm before proceeding 69 + echo "WARNING: This will DESTROY all data on the following drives:" 70 + echo " - $NVME" 71 + echo " - $ZFS1" 72 + echo " - $ZFS2" 73 + echo " - $ZFS3" 74 + echo "" 75 + read -p "Type 'yes' to continue: " CONFIRM 76 + if [[ "$CONFIRM" != "yes" ]]; then 77 + echo "Aborted." 78 + exit 1 79 + fi 80 + 81 + echo "" 82 + echo "Running disko-install..." 83 + sudo nix \ 84 + --extra-experimental-features nix-command \ 85 + --extra-experimental-features flakes \ 86 + run 'github:nix-community/disko/latest#disko-install' -- \ 87 + --flake "$FLAKE" \ 88 + --disk nvme "$NVME" \ 89 + --disk zfs1 "$ZFS1" \ 90 + --disk zfs2 "$ZFS2" \ 91 + --disk zfs3 "$ZFS3" 92 + 93 + echo "" 94 + echo "Copying ZFS keyfile to installed system..." 95 + # disko-install mounts the root filesystem at /mnt 96 + if [[ ! -d /mnt/etc ]]; then 97 + echo "ERROR: /mnt/etc does not exist. Is the root filesystem mounted?" 98 + exit 1 99 + fi 100 + sudo mkdir -p /mnt/etc/zfs 101 + sudo cp /tmp/tank.key /mnt/etc/zfs/tank.key 102 + sudo chmod 000 /mnt/etc/zfs/tank.key 103 + 104 + echo "Updating ZFS keylocation to permanent path..." 105 + # Update keylocation so ZFS looks for the key in the installed system 106 + sudo zfs set keylocation=file:///etc/zfs/tank.key tank 107 + 108 + echo "" 109 + echo "Cleaning up..." 110 + rm -f /tmp/luks-password /tmp/tank.key 111 + 112 + echo "" 113 + echo "=== Installation complete! ===" 114 + echo "" 115 + echo "Next steps:" 116 + echo " 1. Reboot: sudo reboot" 117 + echo " 2. Enter LUKS password at boot prompt" 118 + echo " 3. SSH to box at 192.168.1.240" 119 + echo ""