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