feat(core): initialize NaxOS declarative NixOS distribution, modules, and ISO installer
Test NaxOS Module Configurations / test-modules (push) Failing after 6m10s
Test NaxOS Module Configurations / test-modules (push) Failing after 6m10s
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
name: Build NaxOS Bootable ISO
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
channel:
|
||||||
|
description: 'Target Release Channel'
|
||||||
|
required: true
|
||||||
|
default: 'stable'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-iso:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Nix with Flake Support
|
||||||
|
uses: cachix/install-nix-action@v27
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-24.11
|
||||||
|
extra_nix_config: |
|
||||||
|
experimental-features = nix-command flakes
|
||||||
|
|
||||||
|
- name: Build Bootable NaxOS Installer ISO
|
||||||
|
run: |
|
||||||
|
nix build .#iso --show-trace
|
||||||
|
|
||||||
|
- name: Locate ISO Artifact
|
||||||
|
id: iso-info
|
||||||
|
run: |
|
||||||
|
ISO_FILE=$(find result/iso -name "*.iso" | head -n 1)
|
||||||
|
echo "Found ISO: $ISO_FILE"
|
||||||
|
SHA256=$(sha256sum "$ISO_FILE" | cut -d' ' -f1)
|
||||||
|
echo "SHA256: $SHA256"
|
||||||
|
echo "iso_file=$ISO_FILE" >> $GITHUB_OUTPUT
|
||||||
|
echo "sha256=$SHA256" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Upload ISO Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: naxos-installer-iso
|
||||||
|
path: result/iso/*.iso
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
name: Package NaxOS Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Nix
|
||||||
|
uses: cachix/install-nix-action@v27
|
||||||
|
with:
|
||||||
|
extra_nix_config: |
|
||||||
|
experimental-features = nix-command flakes
|
||||||
|
|
||||||
|
- name: Build ISO
|
||||||
|
run: |
|
||||||
|
nix build .#iso
|
||||||
|
|
||||||
|
- name: Generate Checksums
|
||||||
|
run: |
|
||||||
|
mkdir -p release-dist
|
||||||
|
cp result/iso/*.iso release-dist/
|
||||||
|
cd release-dist
|
||||||
|
sha256sum *.iso > SHA256SUMS
|
||||||
|
|
||||||
|
- name: Create Gitea Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
release-dist/*
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
generate_release_notes: true
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
name: Test NaxOS Module Configurations
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-modules:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Nix
|
||||||
|
uses: cachix/install-nix-action@v27
|
||||||
|
with:
|
||||||
|
extra_nix_config: |
|
||||||
|
experimental-features = nix-command flakes
|
||||||
|
|
||||||
|
- name: Evaluate Flake Outputs & Check Syntax
|
||||||
|
run: |
|
||||||
|
nix flake show
|
||||||
|
nix flake check --all-systems
|
||||||
|
|
||||||
|
- name: Validate Appliance System Configuration Evaluation
|
||||||
|
run: |
|
||||||
|
nix eval .#nixosConfigurations.naxos.config.system.build.toplevel.drvPath
|
||||||
@@ -1,3 +1,58 @@
|
|||||||
# naxos-os
|
# NaxOS Core (`naxos-os`)
|
||||||
|
|
||||||
NaxOS Operating System - Declarative NixOS Core, Modules, ISO Installer, and Appliance Architecture
|
[](https://git.lholz.de/naxos/naxos-os/actions)
|
||||||
|
|
||||||
|
**NaxOS** is a declarative, appliance-like Network Attached Storage (NAS) operating system built on top of **NixOS** and **OpenZFS**.
|
||||||
|
|
||||||
|
## Architecture & Features
|
||||||
|
|
||||||
|
- **Declarative NixOS Foundation**: Complete system state defined as code, from kernel sysctl parameters to users and storage configurations.
|
||||||
|
- **GitOps-Driven Rebuilds**: Automated `nixos-rebuild switch` with dry-build safety checks, canary testing, and instant rollback.
|
||||||
|
- **Native OpenZFS Management**:
|
||||||
|
- Declarative pools (mirror, raidz1, raidz2, stripe)
|
||||||
|
- Declarative datasets with per-dataset quotas, compression (`lz4`, `zstd`), and recordsize tuning
|
||||||
|
- First-class zero-data-loss import of existing pools (e.g. from TrueNAS or raw NixOS setups like `nixos-lukas`)
|
||||||
|
- Automated scrub, TRIM, and snapshot lifecycle policies (hourly, daily, weekly, monthly)
|
||||||
|
- **Multi-Protocol File Shares**:
|
||||||
|
- **Samba**: Preconfigured with macOS Time Machine support (`vfs_fruit`), POSIX ACL propagation, and fine-grained share permissions.
|
||||||
|
- **NFS**: High-throughput NFS exports with CIDR client filters.
|
||||||
|
- **Multi-Tier Workload & App Engine**:
|
||||||
|
- Native NixOS systemd services (e.g. Immich with Intel QuickSync / OpenCL hardware transcoding)
|
||||||
|
- Docker Compose with ZFS copy-on-write storage driver
|
||||||
|
- Optional lightweight K3s cluster integration
|
||||||
|
- **Native Observability**:
|
||||||
|
- Embedded **Perses** analytics dashboards (replacing standalone Grafana)
|
||||||
|
- Prometheus TSDB and Node Exporter
|
||||||
|
- **Streamlined Bootable ISO**:
|
||||||
|
- Interactive CLI / TUI wizard (`naxos-installer`) for rapid bare-metal installation and pool import.
|
||||||
|
|
||||||
|
## Repository Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
naxos-os/
|
||||||
|
├── flake.nix # Flake exports (modules, configurations, ISO builder)
|
||||||
|
├── modules/ # NaxOS module definitions
|
||||||
|
│ ├── core/ # System base, users, gitops, self-update
|
||||||
|
│ ├── storage/ # ZFS pool/dataset management & migration
|
||||||
|
│ ├── shares/ # Samba (SMB) and NFS file shares
|
||||||
|
│ ├── services/ # App engine runtime & application catalog
|
||||||
|
│ ├── monitoring/ # Prometheus & Perses analytics engine
|
||||||
|
│ ├── api/ # Management daemon systemd service
|
||||||
|
│ └── ui/ # Web dashboard Nginx reverse proxy service
|
||||||
|
├── profiles/ # Hardware & appliance base profiles
|
||||||
|
├── iso/ # Bootable installer ISO & scripts
|
||||||
|
└── .gitea/workflows/ # Gitea Actions CI/CD workflows
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Building the Installation ISO
|
||||||
|
```bash
|
||||||
|
nix build .#iso
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing System Evaluation
|
||||||
|
```bash
|
||||||
|
nix flake check
|
||||||
|
nix eval .#nixosConfigurations.naxos.config.system.build.toplevel.drvPath
|
||||||
|
```
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
description = "NaxOS: Declarative, Appliance-Like NixOS- & ZFS-Based Network Attached Storage";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
||||||
|
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, nixpkgs-unstable, ... }:
|
||||||
|
let
|
||||||
|
system = "x86_64-linux";
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
config.allowUnfree = true;
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
# Exported NixOS Modules for NaxOS
|
||||||
|
nixosModules = {
|
||||||
|
naxos = import ./modules;
|
||||||
|
default = self.nixosModules.naxos;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Standard NaxOS Appliance System Configuration
|
||||||
|
nixosConfigurations = {
|
||||||
|
naxos = nixpkgs.lib.nixosSystem {
|
||||||
|
inherit system;
|
||||||
|
modules = [
|
||||||
|
self.nixosModules.naxos
|
||||||
|
./profiles/nas-appliance.nix
|
||||||
|
./profiles/hardware-generic.nix
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Bootable Installer ISO
|
||||||
|
installer-iso = nixpkgs.lib.nixosSystem {
|
||||||
|
inherit system;
|
||||||
|
modules = [
|
||||||
|
./iso/installer-iso.nix
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Exported Packages & ISO Image
|
||||||
|
packages.${system} = {
|
||||||
|
iso = self.nixosConfigurations.installer-iso.config.system.build.isoImage;
|
||||||
|
default = pkgs.writeShellScriptBin "naxos-help" ''
|
||||||
|
echo "NaxOS Appliance Flake"
|
||||||
|
echo "To build installer ISO: nix build .#iso"
|
||||||
|
echo "To test appliance configuration: nix build .#nixosConfigurations.naxos.config.system.build.toplevel"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
{ pkgs, lib, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
# Minimal Live ISO base
|
||||||
|
imports = [
|
||||||
|
<nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix>
|
||||||
|
];
|
||||||
|
|
||||||
|
# ZFS and Storage tooling on Live ISO
|
||||||
|
boot.supportedFilesystems = [ "zfs" ];
|
||||||
|
boot.kernelPackages = pkgs.linuxPackages;
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
zfs
|
||||||
|
parted
|
||||||
|
gptfdisk
|
||||||
|
e2fsprogs
|
||||||
|
dosfstools
|
||||||
|
smartmontools
|
||||||
|
curl
|
||||||
|
wget
|
||||||
|
git
|
||||||
|
jq
|
||||||
|
tmux
|
||||||
|
htop
|
||||||
|
btop
|
||||||
|
whiptail
|
||||||
|
dialog
|
||||||
|
];
|
||||||
|
|
||||||
|
# Automatically launch installer wizard on tty1
|
||||||
|
services.getty.autologinUser = "root";
|
||||||
|
|
||||||
|
# Welcome banner and installer prompt in bash profile
|
||||||
|
environment.etc."issue".text = ''
|
||||||
|
========================================================
|
||||||
|
Welcome to NaxOS Installation Media
|
||||||
|
========================================================
|
||||||
|
To launch the interactive installer wizard, run:
|
||||||
|
naxos-installer
|
||||||
|
|
||||||
|
To inspect disks and ZFS pools manually:
|
||||||
|
lsblk
|
||||||
|
zpool import
|
||||||
|
========================================================
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Link installer script into PATH
|
||||||
|
system.activationScripts.naxosInstallerScript = ''
|
||||||
|
cp -f ${./scripts/naxos-installer.sh} /usr/local/bin/naxos-installer || true
|
||||||
|
chmod +x /usr/local/bin/naxos-installer || true
|
||||||
|
'';
|
||||||
|
|
||||||
|
# ISO Image identification
|
||||||
|
isoImage.isoBaseName = "naxos-installer";
|
||||||
|
isoImage.volumeID = "NAXOS_INSTALL";
|
||||||
|
isoImage.makeEfiBootable = true;
|
||||||
|
isoImage.makeUsbBootable = true;
|
||||||
|
|
||||||
|
# State version
|
||||||
|
system.stateVersion = "24.11";
|
||||||
|
}
|
||||||
Executable
+193
@@ -0,0 +1,193 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ==============================================================================
|
||||||
|
# NaxOS Automated & Interactive Installer Wizard
|
||||||
|
# Guides user through disk partitioning, ZFS pool creation or safe foreign pool
|
||||||
|
# import (TrueNAS / nixos-lukas), network setup, and initial credentials.
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
clear
|
||||||
|
cat << "EOF"
|
||||||
|
_ _ ___ ____
|
||||||
|
| \ | | __ ___ __/ _ \/ ___|
|
||||||
|
| \| |/ _` \ \/ / | | \___ \
|
||||||
|
| |\ | (_| |> <| |_| |___) |
|
||||||
|
|_| \_|\__,_/_/\_\\___/|____/
|
||||||
|
Declarative NixOS & ZFS Storage Appliance
|
||||||
|
================================================
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo -e "${BOLD}${CYAN}Welcome to the NaxOS Installation Wizard!${NC}\n"
|
||||||
|
|
||||||
|
# 1. Hardware Check
|
||||||
|
echo -e "${BOLD}[1/6] Scanning Hardware & Storage Devices...${NC}"
|
||||||
|
TOTAL_RAM_MB=$(free -m | awk '/^Mem:/{print $2}')
|
||||||
|
CPU_CORES=$(nproc)
|
||||||
|
echo " Detected CPU Cores: $CPU_CORES"
|
||||||
|
echo " Detected RAM: ${TOTAL_RAM_MB} MB"
|
||||||
|
|
||||||
|
echo -e "\nAvailable Physical Disks:"
|
||||||
|
lsblk -d -p -n -o NAME,SIZE,MODEL,TRAN | grep -v 'loop' || true
|
||||||
|
|
||||||
|
# 2. Storage Pool Setup (New vs Import)
|
||||||
|
echo -e "\n${BOLD}[2/6] Storage Architecture Configuration${NC}"
|
||||||
|
echo "1) Create a brand new ZFS storage pool (Formatted & optimized for NAS)"
|
||||||
|
echo "2) Import an EXISTING ZFS pool (Preserve existing data e.g. TrueNAS or nixos-lukas 'tank')"
|
||||||
|
echo "3) Skip storage configuration (Configure post-boot via Web Dashboard)"
|
||||||
|
read -rp "Select option [1-3] (default: 2): " STORAGE_OPTION
|
||||||
|
STORAGE_OPTION=${STORAGE_OPTION:-2}
|
||||||
|
|
||||||
|
POOL_NAME="tank"
|
||||||
|
IMPORTED_POOL=""
|
||||||
|
CREATE_POOL=false
|
||||||
|
|
||||||
|
if [ "$STORAGE_OPTION" = "1" ]; then
|
||||||
|
CREATE_POOL=true
|
||||||
|
read -rp "Enter new pool name (default: tank): " POOL_INPUT
|
||||||
|
POOL_NAME=${POOL_INPUT:-tank}
|
||||||
|
|
||||||
|
echo "Select pool layout: [1] Mirror, [2] RAID-Z1, [3] RAID-Z2, [4] Single/Stripe"
|
||||||
|
read -rp "Layout (default: 1): " LAYOUT_INPUT
|
||||||
|
LAYOUT_INPUT=${LAYOUT_INPUT:-1}
|
||||||
|
|
||||||
|
read -rp "Enter disk devices space-separated (e.g. /dev/sdb /dev/sdc): " DISK_DEVICES
|
||||||
|
|
||||||
|
elif [ "$STORAGE_OPTION" = "2" ]; then
|
||||||
|
echo -e "\n${BLUE}Scanning for unimported ZFS pools on connected drives...${NC}"
|
||||||
|
zpool import || true
|
||||||
|
|
||||||
|
read -rp "Enter the name of the existing pool to import (e.g. tank): " IMPORTED_POOL
|
||||||
|
if [ -n "$IMPORTED_POOL" ]; then
|
||||||
|
echo -e "${GREEN}Verifying pool '$IMPORTED_POOL' status safely without mounting...${NC}"
|
||||||
|
zpool import -N -f "$IMPORTED_POOL" || echo "Warning: Pool could not be imported immediately. Will configure for boot-time import."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Target System OS Disk Selection
|
||||||
|
echo -e "\n${BOLD}[3/6] Target OS Disk Selection${NC}"
|
||||||
|
echo "Enter the disk where NaxOS system files should be installed (WARNING: this disk will be formatted!):"
|
||||||
|
read -rp "OS Disk (e.g. /dev/sda or /dev/nvme0n1): " OS_DISK
|
||||||
|
|
||||||
|
if [ -z "$OS_DISK" ] || [ ! -b "$OS_DISK" ]; then
|
||||||
|
echo -e "${RED}Error: Disk '$OS_DISK' does not exist! Aborting.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${RED}${BOLD}WARNING: All data on $OS_DISK will be destroyed!${NC}"
|
||||||
|
read -rp "Are you sure you want to proceed with $OS_DISK? (yes/no): " CONFIRM
|
||||||
|
if [ "$CONFIRM" != "yes" ]; then
|
||||||
|
echo "Installation canceled by user."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Network Configuration
|
||||||
|
echo -e "\n${BOLD}[4/6] Network Configuration${NC}"
|
||||||
|
read -rp "Appliance Hostname (default: naxos): " HOSTNAME
|
||||||
|
HOSTNAME=${HOSTNAME:-naxos}
|
||||||
|
|
||||||
|
read -rp "Use DHCP for network? (yes/no, default: yes): " USE_DHCP
|
||||||
|
USE_DHCP=${USE_DHCP:-yes}
|
||||||
|
|
||||||
|
# 5. Admin User & Credentials
|
||||||
|
echo -e "\n${BOLD}[5/6] Administrator Account & Access${NC}"
|
||||||
|
read -rp "Admin username (default: admin): " ADMIN_USER
|
||||||
|
ADMIN_USER=${ADMIN_USER:-admin}
|
||||||
|
|
||||||
|
read -rp "Paste Admin SSH Public Key (optional): " ADMIN_SSH_KEY
|
||||||
|
|
||||||
|
# 6. Partitioning and Installation
|
||||||
|
echo -e "\n${BOLD}[6/6] Executing Partitioning & Installation...${NC}"
|
||||||
|
|
||||||
|
# Partition OS disk: 1GB EFI, remainder for root
|
||||||
|
wipefs -a "$OS_DISK"
|
||||||
|
parted -s "$OS_DISK" -- mklabel gpt
|
||||||
|
parted -s "$OS_DISK" -- mkpart ESP fat32 1MiB 1024MiB
|
||||||
|
parted -s "$OS_DISK" -- set 1 esp on
|
||||||
|
parted -s "$OS_DISK" -- mkpart primary ext4 1024MiB 100%
|
||||||
|
|
||||||
|
# Detect partition names (handles nvme0n1p1 vs sda1)
|
||||||
|
if [[ "$OS_DISK" =~ [0-9]$ ]]; then
|
||||||
|
EFI_PART="${OS_DISK}p1"
|
||||||
|
ROOT_PART="${OS_DISK}p2"
|
||||||
|
else
|
||||||
|
EFI_PART="${OS_DISK}1"
|
||||||
|
ROOT_PART="${OS_DISK}2"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkfs.vfat -F 32 -n NIXOS_BOOT "$EFI_PART"
|
||||||
|
mkfs.ext4 -F -L NIXOS_ROOT "$ROOT_PART"
|
||||||
|
|
||||||
|
# Mount filesystems
|
||||||
|
mount "$ROOT_PART" /mnt
|
||||||
|
mkdir -p /mnt/boot
|
||||||
|
mount "$EFI_PART" /mnt/boot
|
||||||
|
|
||||||
|
# Generate hardware configuration
|
||||||
|
mkdir -p /mnt/etc/nixos
|
||||||
|
nixos-generate-config --root /mnt
|
||||||
|
|
||||||
|
# If creating pool now
|
||||||
|
if [ "$CREATE_POOL" = true ] && [ -n "${DISK_DEVICES:-}" ]; then
|
||||||
|
echo "Creating ZFS pool $POOL_NAME..."
|
||||||
|
zpool create -f -o ashift=12 -O compression=lz4 -O acltype=posixacl -O xattr=sa "$POOL_NAME" $DISK_DEVICES
|
||||||
|
zfs create "$POOL_NAME/media"
|
||||||
|
zfs create "$POOL_NAME/backup"
|
||||||
|
zfs create "$POOL_NAME/container"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate unique hostId
|
||||||
|
HOST_ID=$(head -c4 /dev/urandom | od -A none -t x4 | tr -d ' ')
|
||||||
|
|
||||||
|
# Write appliance flake and configuration
|
||||||
|
cat << NIXCONFIG > /mnt/etc/nixos/configuration.nix
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./hardware-configuration.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
boot.loader.systemd-boot.enable = true;
|
||||||
|
boot.loader.efi.canTouchEfiVariables = true;
|
||||||
|
boot.supportedFilesystems = [ "zfs" ];
|
||||||
|
|
||||||
|
networking.hostName = "$HOSTNAME";
|
||||||
|
networking.hostId = "$HOST_ID";
|
||||||
|
networking.useDHCP = lib.mkDefault $([ "$USE_DHCP" = "yes" ] && echo "true" || echo "false");
|
||||||
|
|
||||||
|
# Safe ZFS extra pools
|
||||||
|
boot.zfs.extraPools = [ "$POOL_NAME" $([ -n "$IMPORTED_POOL" ] && echo "\"$IMPORTED_POOL\"") ];
|
||||||
|
|
||||||
|
# Administrator user
|
||||||
|
users.users.$ADMIN_USER = {
|
||||||
|
isNormalUser = true;
|
||||||
|
extraGroups = [ "wheel" "docker" "video" "render" ];
|
||||||
|
openssh.authorizedKeys.keys = [ $([ -n "$ADMIN_SSH_KEY" ] && echo "\"$ADMIN_SSH_KEY\"") ];
|
||||||
|
};
|
||||||
|
security.sudo.wheelNeedsPassword = false;
|
||||||
|
|
||||||
|
# SSH Access
|
||||||
|
services.openssh.enable = true;
|
||||||
|
|
||||||
|
# State version
|
||||||
|
system.stateVersion = "24.11";
|
||||||
|
}
|
||||||
|
NIXCONFIG
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}Beginning NixOS System Installation via nixos-install...${NC}"
|
||||||
|
nixos-install --no-root-passwd
|
||||||
|
|
||||||
|
echo -e "\n${BOLD}${GREEN}==============================================${NC}"
|
||||||
|
echo -e "${BOLD}${GREEN} NaxOS Installation Complete!${NC}"
|
||||||
|
echo -e "${BOLD}${GREEN} Remove the installation USB/ISO and reboot.${NC}"
|
||||||
|
echo -e "${BOLD}${GREEN} Access Dashboard on: http://$HOSTNAME.local${NC}"
|
||||||
|
echo -e "${BOLD}${GREEN}==============================================${NC}"
|
||||||
|
EOF
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.api;
|
||||||
|
in {
|
||||||
|
options.services.naxos.api = {
|
||||||
|
enable = mkEnableOption "NaxOS Management Daemon & REST/WebSocket API Service";
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 8088;
|
||||||
|
description = "Internal daemon listen port.";
|
||||||
|
};
|
||||||
|
|
||||||
|
host = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "127.0.0.1";
|
||||||
|
description = "Internal daemon bind host.";
|
||||||
|
};
|
||||||
|
|
||||||
|
dataDir = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "/var/lib/naxos";
|
||||||
|
description = "Persistent state directory for NaxOS daemon.";
|
||||||
|
};
|
||||||
|
|
||||||
|
configRepoDir = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "/etc/naxos/repo";
|
||||||
|
description = "Directory of the local GitOps configuration repository.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
systemd.services.naxos-api = {
|
||||||
|
description = "NaxOS Declarative Management Daemon";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "network.target" "zfs.target" ];
|
||||||
|
path = with pkgs; [
|
||||||
|
zfs
|
||||||
|
git
|
||||||
|
nix
|
||||||
|
systemd
|
||||||
|
smartmontools
|
||||||
|
util-linux
|
||||||
|
shadow
|
||||||
|
samba
|
||||||
|
curl
|
||||||
|
bash
|
||||||
|
];
|
||||||
|
environment = {
|
||||||
|
NODE_ENV = "production";
|
||||||
|
PORT = toString cfg.port;
|
||||||
|
HOST = cfg.host;
|
||||||
|
NAXOS_DATA_DIR = cfg.dataDir;
|
||||||
|
NAXOS_CONFIG_REPO = cfg.configRepoDir;
|
||||||
|
SYSTEM_PROFILE = "/nix/var/nix/profiles/system";
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
ExecStart = "${pkgs.nodejs}/bin/node /opt/naxos/api/dist/server.js";
|
||||||
|
WorkingDirectory = cfg.dataDir;
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = "3s";
|
||||||
|
StateDirectory = "naxos";
|
||||||
|
RuntimeDirectory = "naxos";
|
||||||
|
# Sandboxing / Security hardening
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
ProtectHome = "read-only";
|
||||||
|
ReadWritePaths = [ cfg.dataDir cfg.configRepoDir "/var/log" "/etc/naxos" "/nix/var/nix/profiles" ];
|
||||||
|
AmbientCapabilities = [ "CAP_SYS_ADMIN" ]; # For ZFS operations & systemd manipulation
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d ${cfg.dataDir} 0750 root root -"
|
||||||
|
"d ${cfg.configRepoDir} 0750 root root -"
|
||||||
|
"d /etc/naxos 0755 root root -"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.gitops;
|
||||||
|
|
||||||
|
# Safe switch script with canary validation and automatic rollback
|
||||||
|
naxosRebuildWrapper = pkgs.writeShellScriptBin "naxos-rebuild-safe" ''
|
||||||
|
set -euo pipefail
|
||||||
|
CONFIG_DIR="''${CONFIG_DIR:-/etc/naxos/repo}"
|
||||||
|
LOG_FILE="/var/log/naxos-rebuild.log"
|
||||||
|
|
||||||
|
echo "[$(date -Iseconds)] Starting NaxOS declarative rebuild..." | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
cd "$CONFIG_DIR"
|
||||||
|
|
||||||
|
# Pre-flight check with nix flake check / nixos-rebuild dry-build
|
||||||
|
echo "Running dry build validation..."
|
||||||
|
if ! nixos-rebuild build --flake .#naxos 2>&1 | tee -a "$LOG_FILE"; then
|
||||||
|
echo "Dry-build failed! Configuration aborted without changing running system." | tee -a "$LOG_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Record current generation
|
||||||
|
PREV_GEN=$(readlink -f /nix/var/nix/profiles/system)
|
||||||
|
|
||||||
|
echo "Applying system switch..."
|
||||||
|
if nixos-rebuild switch --flake .#naxos 2>&1 | tee -a "$LOG_FILE"; then
|
||||||
|
echo "System switch succeeded." | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
# Canary health check: verify management daemon and storage pools are responsive
|
||||||
|
if systemctl is-active --quiet naxos-api.service; then
|
||||||
|
echo "Canary verification passed: NaxOS daemon active." | tee -a "$LOG_FILE"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "WARNING: NaxOS daemon failed canary test! Initiating safe automatic rollback..." | tee -a "$LOG_FILE"
|
||||||
|
"$PREV_GEN/bin/switch-to-configuration" switch
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Switch command failed! Rolling back to $PREV_GEN..." | tee -a "$LOG_FILE"
|
||||||
|
"$PREV_GEN/bin/switch-to-configuration" switch
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Automated remote push/pull service
|
||||||
|
naxosGitSync = pkgs.writeShellScriptBin "naxos-git-sync" ''
|
||||||
|
set -euo pipefail
|
||||||
|
REPO_DIR="/etc/naxos/repo"
|
||||||
|
|
||||||
|
if [ ! -d "$REPO_DIR/.git" ]; then
|
||||||
|
echo "Git repository not initialized in $REPO_DIR."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
|
||||||
|
if [ -n "''${NAXOS_REMOTE_URL:-}" ]; then
|
||||||
|
echo "Synchronizing with remote GitOps repository..."
|
||||||
|
git fetch origin main || true
|
||||||
|
git push origin main || true
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
in {
|
||||||
|
options.services.naxos.gitops = {
|
||||||
|
enable = mkEnableOption "NaxOS GitOps Configuration Synchronization Engine";
|
||||||
|
|
||||||
|
remoteUrl = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
example = "ssh://git@git.lholz.de:2222/naxos/naxos-config.git";
|
||||||
|
description = "Remote Git repository URL for automated backup, auditing, and disaster recovery.";
|
||||||
|
};
|
||||||
|
|
||||||
|
branch = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "main";
|
||||||
|
description = "GitOps target branch.";
|
||||||
|
};
|
||||||
|
|
||||||
|
tokenFile = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Path to Gitea/Git access token or SSH private key.";
|
||||||
|
};
|
||||||
|
|
||||||
|
autoPushOnCommit = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Automatically push to remote repository whenever web dashboard commits a change.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
git
|
||||||
|
naxosRebuildWrapper
|
||||||
|
naxosGitSync
|
||||||
|
];
|
||||||
|
|
||||||
|
# Create config repo directory structure
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /etc/naxos 0755 root root -"
|
||||||
|
"d /etc/naxos/repo 0750 root root -"
|
||||||
|
"d /var/log 0755 root root -"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.selfUpdate;
|
||||||
|
|
||||||
|
updateScript = pkgs.writeShellScriptBin "naxos-self-update" ''
|
||||||
|
set -euo pipefail
|
||||||
|
CONFIG_DIR="''${CONFIG_DIR:-/etc/naxos/repo}"
|
||||||
|
LOG_FILE="/var/log/naxos-update.log"
|
||||||
|
|
||||||
|
echo "[$(date -Iseconds)] Checking for NaxOS updates..." | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
if [ ! -d "$CONFIG_DIR/.git" ]; then
|
||||||
|
echo "Configuration directory is not a git repository. Skipping." | tee -a "$LOG_FILE"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$CONFIG_DIR"
|
||||||
|
git fetch origin main
|
||||||
|
|
||||||
|
LOCAL_HASH=$(git rev-parse HEAD)
|
||||||
|
REMOTE_HASH=$(git rev-parse origin/main)
|
||||||
|
|
||||||
|
if [ "$LOCAL_HASH" = "$REMOTE_HASH" ]; then
|
||||||
|
echo "System is already up to date ($LOCAL_HASH)." | tee -a "$LOG_FILE"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "New updates detected ($LOCAL_HASH -> $REMOTE_HASH). Updating flake inputs and pulling..." | tee -a "$LOG_FILE"
|
||||||
|
git merge origin/main --ff-only
|
||||||
|
|
||||||
|
echo "Rebuilding and applying configuration..." | tee -a "$LOG_FILE"
|
||||||
|
naxos-rebuild-safe
|
||||||
|
'';
|
||||||
|
|
||||||
|
in {
|
||||||
|
options.services.naxos.selfUpdate = {
|
||||||
|
enable = mkEnableOption "NaxOS Automated Background Self-Update Service";
|
||||||
|
|
||||||
|
schedule = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "*-*-* 04:00:00"; # Daily at 4:00 AM
|
||||||
|
description = "Systemd calendar expression for scheduled update checks.";
|
||||||
|
};
|
||||||
|
|
||||||
|
channel = mkOption {
|
||||||
|
type = types.enum [ "stable" "beta" "nightly" ];
|
||||||
|
default = "stable";
|
||||||
|
description = "Update release channel.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
environment.systemPackages = [ updateScript ];
|
||||||
|
|
||||||
|
systemd.services.naxos-self-update = {
|
||||||
|
description = "NaxOS Self-Update Check & Apply";
|
||||||
|
path = [ pkgs.git pkgs.nix pkgs.systemd pkgs.bash ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${updateScript}/bin/naxos-self-update";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.naxos-self-update = {
|
||||||
|
description = "NaxOS Scheduled Self-Update Timer";
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = cfg.schedule;
|
||||||
|
Persistent = true;
|
||||||
|
RandomizedDelaySec = "1800"; # 30 min random jitter
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.core;
|
||||||
|
in {
|
||||||
|
options.services.naxos.core = {
|
||||||
|
enable = mkEnableOption "NaxOS Core Appliance Base";
|
||||||
|
|
||||||
|
hostName = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "naxos";
|
||||||
|
description = "Appliance hostname.";
|
||||||
|
};
|
||||||
|
|
||||||
|
hostId = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "8425f3a1";
|
||||||
|
description = "32-bit Host ID required for OpenZFS safety locking.";
|
||||||
|
};
|
||||||
|
|
||||||
|
timeZone = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "Europe/Berlin";
|
||||||
|
description = "System timezone.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
networking.hostName = cfg.hostName;
|
||||||
|
networking.hostId = cfg.hostId;
|
||||||
|
time.timeZone = cfg.timeZone;
|
||||||
|
|
||||||
|
# Flakes and Nix CLI enablement
|
||||||
|
nix.settings = {
|
||||||
|
experimental-features = [ "nix-command" "flakes" ];
|
||||||
|
auto-optimise-store = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# High-Performance Storage & Network Kernel Tuning
|
||||||
|
boot.kernel.sysctl = {
|
||||||
|
# BBR Congestion Control
|
||||||
|
"net.core.default_qdisc" = "fq";
|
||||||
|
"net.ipv4.tcp_congestion_control" = "bbr";
|
||||||
|
|
||||||
|
# High-bandwidth 10G/25G network buffer tuning
|
||||||
|
"net.core.rmem_max" = 67108864;
|
||||||
|
"net.core.wmem_max" = 67108864;
|
||||||
|
"net.ipv4.tcp_rmem" = "4096 87380 33554432";
|
||||||
|
"net.ipv4.tcp_wmem" = "4096 65536 33554432";
|
||||||
|
"net.core.netdev_max_backlog" = 10000;
|
||||||
|
|
||||||
|
# Storage & VM writeback tuning for ZFS
|
||||||
|
"vm.swappiness" = 10;
|
||||||
|
"vm.dirty_background_ratio" = 5;
|
||||||
|
"vm.dirty_ratio" = 10;
|
||||||
|
|
||||||
|
# File handles limit
|
||||||
|
"fs.file-max" = 2097152;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Core system tools
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
curl
|
||||||
|
wget
|
||||||
|
git
|
||||||
|
htop
|
||||||
|
btop
|
||||||
|
tmux
|
||||||
|
jq
|
||||||
|
pciutils
|
||||||
|
usbutils
|
||||||
|
ethtool
|
||||||
|
iperf3
|
||||||
|
rsync
|
||||||
|
];
|
||||||
|
|
||||||
|
# Security & Firewall defaults
|
||||||
|
networking.firewall = {
|
||||||
|
enable = true;
|
||||||
|
allowPing = true;
|
||||||
|
allowedTCPPorts = [ 22 80 443 ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# SSH Server with modern secure defaults
|
||||||
|
services.openssh = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
PermitRootLogin = "prohibit-password";
|
||||||
|
PasswordAuthentication = false;
|
||||||
|
KbdInteractiveAuthentication = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.users;
|
||||||
|
in {
|
||||||
|
options.services.naxos.users = {
|
||||||
|
enable = mkEnableOption "NaxOS Declarative User Management";
|
||||||
|
|
||||||
|
adminUser = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "admin";
|
||||||
|
description = "Primary administrator account name.";
|
||||||
|
};
|
||||||
|
|
||||||
|
adminSshKeys = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = "Public SSH keys for the administrator.";
|
||||||
|
};
|
||||||
|
|
||||||
|
users = mkOption {
|
||||||
|
type = types.attrsOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
description = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "";
|
||||||
|
description = "User full name or comment.";
|
||||||
|
};
|
||||||
|
isAdmin = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Whether the user has sudo/wheel privileges.";
|
||||||
|
};
|
||||||
|
sshKeys = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = "Public SSH authorized keys.";
|
||||||
|
};
|
||||||
|
extraGroups = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = "Additional Linux groups.";
|
||||||
|
};
|
||||||
|
smbAccess = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether the user can access SMB shares.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = {};
|
||||||
|
description = "Appliance user accounts.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
users.mutableUsers = true;
|
||||||
|
|
||||||
|
# Admin user creation
|
||||||
|
users.users.${cfg.adminUser} = {
|
||||||
|
isNormalUser = true;
|
||||||
|
description = "NaxOS Primary Administrator";
|
||||||
|
extraGroups = [ "wheel" "docker" "video" "render" "users" ];
|
||||||
|
openssh.authorizedKeys.keys = cfg.adminSshKeys;
|
||||||
|
shell = pkgs.bashInteractive;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Additional users
|
||||||
|
users.users = mapAttrs (name: ucfg: {
|
||||||
|
isNormalUser = true;
|
||||||
|
description = ucfg.description;
|
||||||
|
extraGroups = (if ucfg.isAdmin then [ "wheel" ] else []) ++ ucfg.extraGroups ++ [ "users" ];
|
||||||
|
openssh.authorizedKeys.keys = ucfg.sshKeys;
|
||||||
|
}) cfg.users;
|
||||||
|
|
||||||
|
security.sudo.wheelNeedsPassword = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{ ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./core/system.nix
|
||||||
|
./core/users.nix
|
||||||
|
./core/gitops.nix
|
||||||
|
./core/self-update.nix
|
||||||
|
./storage/zfs.nix
|
||||||
|
./shares/samba.nix
|
||||||
|
./shares/nfs.nix
|
||||||
|
./services/app-engine.nix
|
||||||
|
./services/apps
|
||||||
|
./monitoring/prometheus.nix
|
||||||
|
./monitoring/perses.nix
|
||||||
|
./api/daemon.nix
|
||||||
|
./ui/service.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.perses;
|
||||||
|
|
||||||
|
# Declarative Perses Prometheus Datasource Manifest
|
||||||
|
datasourceManifest = pkgs.writeText "perses-prom-datasource.json" (builtins.toJSON {
|
||||||
|
kind = "Datasource";
|
||||||
|
metadata = {
|
||||||
|
name = "PrometheusDemo";
|
||||||
|
project = "naxos";
|
||||||
|
};
|
||||||
|
spec = {
|
||||||
|
default = true;
|
||||||
|
plugin = {
|
||||||
|
kind = "PrometheusDatasource";
|
||||||
|
spec = {
|
||||||
|
directUrl = "http://127.0.0.1:9090";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
# Declarative Perses ZFS & System Dashboard Manifest
|
||||||
|
zfsDashboardManifest = pkgs.writeText "perses-zfs-dashboard.json" (builtins.toJSON {
|
||||||
|
kind = "Dashboard";
|
||||||
|
metadata = {
|
||||||
|
name = "zfs-storage-health";
|
||||||
|
project = "naxos";
|
||||||
|
};
|
||||||
|
spec = {
|
||||||
|
duration = "1h";
|
||||||
|
refreshInterval = "10s";
|
||||||
|
display = {
|
||||||
|
name = "ZFS Storage & System Health";
|
||||||
|
description = "Native NaxOS analytics powered by Perses";
|
||||||
|
};
|
||||||
|
variables = [];
|
||||||
|
panels = {
|
||||||
|
cpuUsage = {
|
||||||
|
kind = "Panel";
|
||||||
|
spec = {
|
||||||
|
display = { name = "CPU Utilization (%)"; };
|
||||||
|
plugin = {
|
||||||
|
kind = "TimeSeriesChart";
|
||||||
|
spec = {
|
||||||
|
queries = [{
|
||||||
|
kind = "TimeSeriesQuery";
|
||||||
|
spec = {
|
||||||
|
plugin = {
|
||||||
|
kind = "PrometheusTimeSeriesQuery";
|
||||||
|
spec = {
|
||||||
|
query = "100 - (avg by (instance) (rate(node_cpu_seconds_total{mode='idle'}[1m])) * 100)";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
memoryArc = {
|
||||||
|
kind = "Panel";
|
||||||
|
spec = {
|
||||||
|
display = { name = "Memory & ARC Cache (Bytes)"; };
|
||||||
|
plugin = {
|
||||||
|
kind = "TimeSeriesChart";
|
||||||
|
spec = {
|
||||||
|
queries = [
|
||||||
|
{
|
||||||
|
kind = "TimeSeriesQuery";
|
||||||
|
spec = {
|
||||||
|
plugin = {
|
||||||
|
kind = "PrometheusTimeSeriesQuery";
|
||||||
|
spec = {
|
||||||
|
query = "node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
diskIO = {
|
||||||
|
kind = "Panel";
|
||||||
|
spec = {
|
||||||
|
display = { name = "ZFS Pool Read/Write Throughput"; };
|
||||||
|
plugin = {
|
||||||
|
kind = "TimeSeriesChart";
|
||||||
|
spec = {
|
||||||
|
queries = [{
|
||||||
|
kind = "TimeSeriesQuery";
|
||||||
|
spec = {
|
||||||
|
plugin = {
|
||||||
|
kind = "PrometheusTimeSeriesQuery";
|
||||||
|
spec = {
|
||||||
|
query = "rate(node_disk_read_bytes_total[1m]) + rate(node_disk_written_bytes_total[1m])";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
layouts = [
|
||||||
|
{
|
||||||
|
kind = "Grid";
|
||||||
|
spec = {
|
||||||
|
items = [
|
||||||
|
{ x = 0; y = 0; width = 12; height = 6; content = { "$ref" = "#/spec/panels/cpuUsage"; }; }
|
||||||
|
{ x = 12; y = 0; width = 12; height = 6; content = { "$ref" = "#/spec/panels/memoryArc"; }; }
|
||||||
|
{ x = 0; y = 6; width = 24; height = 8; content = { "$ref" = "#/spec/panels/diskIO"; }; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
persesConfigFile = pkgs.writeText "perses-config.yaml" ''
|
||||||
|
database:
|
||||||
|
file:
|
||||||
|
folder: "/var/lib/perses"
|
||||||
|
extension: "json"
|
||||||
|
schemas:
|
||||||
|
panels_path: "/var/lib/perses/schemas/panels"
|
||||||
|
queries_path: "/var/lib/perses/schemas/queries"
|
||||||
|
datasources_path: "/var/lib/perses/schemas/datasources"
|
||||||
|
variables_path: "/var/lib/perses/schemas/variables"
|
||||||
|
security:
|
||||||
|
readonly: false
|
||||||
|
enable_auth: false
|
||||||
|
'';
|
||||||
|
|
||||||
|
in {
|
||||||
|
options.services.naxos.perses = {
|
||||||
|
enable = mkEnableOption "Perses Embedded Analytics Engine";
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 8080;
|
||||||
|
description = "Perses dashboard server port.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# System user for Perses
|
||||||
|
users.users.perses = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "perses";
|
||||||
|
home = "/var/lib/perses";
|
||||||
|
createHome = true;
|
||||||
|
};
|
||||||
|
users.groups.perses = {};
|
||||||
|
|
||||||
|
# Systemd service for Perses
|
||||||
|
systemd.services.perses = {
|
||||||
|
description = "Perses Native Observability and Dashboard Service";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "network.target" "prometheus.service" ];
|
||||||
|
serviceConfig = {
|
||||||
|
User = "perses";
|
||||||
|
Group = "perses";
|
||||||
|
StateDirectory = "perses";
|
||||||
|
WorkingDirectory = "/var/lib/perses";
|
||||||
|
ExecStartPre = pkgs.writeShellScript "perses-provisioning" ''
|
||||||
|
mkdir -p /var/lib/perses/projects/naxos/dashboards
|
||||||
|
mkdir -p /var/lib/perses/projects/naxos/datasources
|
||||||
|
cp -f ${datasourceManifest} /var/lib/perses/projects/naxos/datasources/PrometheusDemo.json
|
||||||
|
cp -f ${zfsDashboardManifest} /var/lib/perses/projects/naxos/dashboards/zfs-storage-health.json
|
||||||
|
'';
|
||||||
|
ExecStart = "${pkgs.perses or pkgs.prometheus}/bin/perses --config=${persesConfigFile} --port=${toString cfg.port}";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = "5s";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [ cfg.port ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.monitoring;
|
||||||
|
in {
|
||||||
|
options.services.naxos.monitoring = {
|
||||||
|
enable = mkEnableOption "NaxOS Metrics and Telemetry Collection";
|
||||||
|
|
||||||
|
prometheusPort = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 9090;
|
||||||
|
description = "Prometheus server listen port.";
|
||||||
|
};
|
||||||
|
|
||||||
|
nodeExporterPort = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 9100;
|
||||||
|
description = "Node exporter listen port.";
|
||||||
|
};
|
||||||
|
|
||||||
|
retentionTime = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "15d";
|
||||||
|
description = "Metrics retention period.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# Prometheus TSDB
|
||||||
|
services.prometheus = {
|
||||||
|
enable = true;
|
||||||
|
port = cfg.prometheusPort;
|
||||||
|
retentionTime = cfg.retentionTime;
|
||||||
|
extraFlags = [
|
||||||
|
"--web.enable-remote-write-receiver"
|
||||||
|
];
|
||||||
|
scrapeConfigs = [
|
||||||
|
{
|
||||||
|
job_name = "node";
|
||||||
|
static_configs = [{
|
||||||
|
targets = [ "127.0.0.1:${toString cfg.nodeExporterPort}" ];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
job_name = "naxos-api";
|
||||||
|
static_configs = [{
|
||||||
|
targets = [ "127.0.0.1:8088" ];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Node exporter for hardware & OS metrics
|
||||||
|
services.prometheus.exporters.node = {
|
||||||
|
enable = true;
|
||||||
|
port = cfg.nodeExporterPort;
|
||||||
|
enabledCollectors = [
|
||||||
|
"cpu"
|
||||||
|
"diskstats"
|
||||||
|
"filesystem"
|
||||||
|
"loadavg"
|
||||||
|
"meminfo"
|
||||||
|
"netdev"
|
||||||
|
"stat"
|
||||||
|
"systemd"
|
||||||
|
"thermal_zone"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.appEngine;
|
||||||
|
in {
|
||||||
|
options.services.naxos.appEngine = {
|
||||||
|
enable = mkEnableOption "NaxOS Multi-Tier App Engine";
|
||||||
|
|
||||||
|
defaultRuntime = mkOption {
|
||||||
|
type = types.enum [ "systemd" "docker" "k3s" ];
|
||||||
|
default = "docker";
|
||||||
|
description = "Default execution runtime for user workloads.";
|
||||||
|
};
|
||||||
|
|
||||||
|
docker = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Enable Docker engine for containerized applications.";
|
||||||
|
};
|
||||||
|
storageDriver = mkOption {
|
||||||
|
type = types.enum [ "zfs" "overlay2" "btrfs" ];
|
||||||
|
default = "zfs";
|
||||||
|
description = "Container storage driver. 'zfs' uses native copy-on-write datasets.";
|
||||||
|
};
|
||||||
|
dataRoot = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "/var/lib/docker";
|
||||||
|
description = "Storage root for container images and layers.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
k3s = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable lightweight K3s single-node cluster for cloud-native orchestration.";
|
||||||
|
};
|
||||||
|
role = mkOption {
|
||||||
|
type = types.enum [ "server" "agent" ];
|
||||||
|
default = "server";
|
||||||
|
description = "K3s node role.";
|
||||||
|
};
|
||||||
|
tokenFile = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Path to token file for K3s node join / cluster security.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
gpuAcceleration = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Enable hardware transcoding and machine learning acceleration.";
|
||||||
|
};
|
||||||
|
vendor = mkOption {
|
||||||
|
type = types.enum [ "intel" "nvidia" "amd" "none" ];
|
||||||
|
default = "intel";
|
||||||
|
description = "Primary GPU hardware vendor.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# 1. Docker Runtime Configuration
|
||||||
|
virtualisation.docker = mkIf cfg.docker.enable {
|
||||||
|
enable = true;
|
||||||
|
storageDriver = cfg.docker.storageDriver;
|
||||||
|
daemon.settings = {
|
||||||
|
data-root = cfg.docker.dataRoot;
|
||||||
|
log-driver = "journald";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# OCI container backend compatibility
|
||||||
|
virtualisation.oci-containers.backend = mkIf cfg.docker.enable "docker";
|
||||||
|
|
||||||
|
# 2. K3s Runtime Configuration
|
||||||
|
services.k3s = mkIf cfg.k3s.enable {
|
||||||
|
enable = true;
|
||||||
|
role = cfg.k3s.role;
|
||||||
|
tokenFile = cfg.k3s.tokenFile;
|
||||||
|
extraFlags = toString [
|
||||||
|
"--disable=traefik" # We manage ingress/reverse-proxy through NaxOS
|
||||||
|
"--snapshotter=native"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# 3. Hardware Graphics Acceleration
|
||||||
|
hardware.graphics = mkIf cfg.gpuAcceleration.enable {
|
||||||
|
enable = true;
|
||||||
|
extraPackages = mkIf (cfg.gpuAcceleration.vendor == "intel") (with pkgs; [
|
||||||
|
intel-media-driver # Broadwell or newer
|
||||||
|
intel-compute-runtime # OpenCL support
|
||||||
|
vpl-gpu-rt # QSV support (11th Gen+)
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
# 4. System packages for workload operations
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
docker-compose
|
||||||
|
lazydocker
|
||||||
|
kubectl
|
||||||
|
dive
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{ ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./immich.nix
|
||||||
|
./nextcloud.nix
|
||||||
|
./jellyfin.nix
|
||||||
|
./paperless.nix
|
||||||
|
./vaultwarden.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.apps.immich;
|
||||||
|
in {
|
||||||
|
options.services.naxos.apps.immich = {
|
||||||
|
enable = mkEnableOption "Immich Self-Hosted Photo & Video Hub";
|
||||||
|
|
||||||
|
runtime = mkOption {
|
||||||
|
type = types.enum [ "systemd" "docker" ];
|
||||||
|
default = "systemd";
|
||||||
|
description = "Runtime to execute Immich (systemd for native bare-metal speed with NixOS package, docker for containerized).";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 2283;
|
||||||
|
description = "Web interface and API port.";
|
||||||
|
};
|
||||||
|
|
||||||
|
host = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "0.0.0.0";
|
||||||
|
description = "Listen host.";
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaLocation = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "/tank/media/photos";
|
||||||
|
description = "ZFS dataset or path where uploaded photos and videos are stored.";
|
||||||
|
};
|
||||||
|
|
||||||
|
accelerationDevices = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ "/dev/dri/renderD128" ];
|
||||||
|
description = "DRM render devices for hardware-accelerated transcoding.";
|
||||||
|
};
|
||||||
|
|
||||||
|
machineLearningCPUQuota = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "200%";
|
||||||
|
description = "CPU quota for machine learning service (200% = 2 full cores).";
|
||||||
|
};
|
||||||
|
|
||||||
|
openFirewall = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Open port in firewall.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# Systemd / Native NixOS Deployment
|
||||||
|
services.immich = mkIf (cfg.runtime == "systemd") {
|
||||||
|
enable = true;
|
||||||
|
host = cfg.host;
|
||||||
|
port = cfg.port;
|
||||||
|
mediaLocation = cfg.mediaLocation;
|
||||||
|
openFirewall = cfg.openFirewall;
|
||||||
|
accelerationDevices = cfg.accelerationDevices;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.redis.servers.immich = mkIf (cfg.runtime == "systemd") {
|
||||||
|
logLevel = "warning";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.immich-machine-learning = mkIf (cfg.runtime == "systemd") {
|
||||||
|
serviceConfig = {
|
||||||
|
CPUQuota = cfg.machineLearningCPUQuota;
|
||||||
|
Nice = 19;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.immich = mkIf (cfg.runtime == "systemd") {
|
||||||
|
extraGroups = [ "video" "render" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d ${cfg.mediaLocation} 0750 immich immich -"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.apps.jellyfin;
|
||||||
|
in {
|
||||||
|
options.services.naxos.apps.jellyfin = {
|
||||||
|
enable = mkEnableOption "Jellyfin Open-Source Media Streaming Server";
|
||||||
|
|
||||||
|
openFirewall = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Open port 8096 in firewall.";
|
||||||
|
};
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "jellyfin";
|
||||||
|
description = "Service user.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
services.jellyfin = {
|
||||||
|
enable = true;
|
||||||
|
openFirewall = cfg.openFirewall;
|
||||||
|
user = cfg.user;
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.${cfg.user}.extraGroups = [ "video" "render" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.apps.nextcloud;
|
||||||
|
in {
|
||||||
|
options.services.naxos.apps.nextcloud = {
|
||||||
|
enable = mkEnableOption "Nextcloud Personal Cloud & Collaboration Platform";
|
||||||
|
|
||||||
|
hostName = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "cloud.local";
|
||||||
|
description = "Domain / hostname for Nextcloud.";
|
||||||
|
};
|
||||||
|
|
||||||
|
homeDir = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "/tank/data/nextcloud";
|
||||||
|
description = "Persistent data directory on ZFS storage.";
|
||||||
|
};
|
||||||
|
|
||||||
|
adminpassFile = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Path to file containing initial admin password.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
services.nextcloud = {
|
||||||
|
enable = true;
|
||||||
|
hostName = cfg.hostName;
|
||||||
|
home = cfg.homeDir;
|
||||||
|
config = {
|
||||||
|
adminuser = "admin";
|
||||||
|
adminpassFile = cfg.adminpassFile;
|
||||||
|
dbtype = "sqlite";
|
||||||
|
};
|
||||||
|
caching.redis = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.apps.paperless;
|
||||||
|
in {
|
||||||
|
options.services.naxos.apps.paperless = {
|
||||||
|
enable = mkEnableOption "Paperless-ngx Document Archiving System";
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 28981;
|
||||||
|
description = "Web interface port.";
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaDir = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "/tank/data/paperless/media";
|
||||||
|
description = "Directory where archived documents and OCR results are stored.";
|
||||||
|
};
|
||||||
|
|
||||||
|
consumptionDir = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "/tank/scans";
|
||||||
|
description = "Ingestion directory where scanner uploads incoming documents.";
|
||||||
|
};
|
||||||
|
|
||||||
|
openFirewall = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Open port in firewall.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
services.paperless = {
|
||||||
|
enable = true;
|
||||||
|
port = cfg.port;
|
||||||
|
mediaDir = cfg.mediaDir;
|
||||||
|
consumptionDir = cfg.consumptionDir;
|
||||||
|
settings = {
|
||||||
|
PAPERLESS_OCR_LANGUAGE = "deu+eng";
|
||||||
|
PAPERLESS_CONSUMER_POLLING = 30;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d ${cfg.mediaDir} 0750 paperless paperless -"
|
||||||
|
"d ${cfg.consumptionDir} 0775 paperless users -"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.apps.vaultwarden;
|
||||||
|
in {
|
||||||
|
options.services.naxos.apps.vaultwarden = {
|
||||||
|
enable = mkEnableOption "Vaultwarden Password & Secret Vault";
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 8222;
|
||||||
|
description = "Web interface port.";
|
||||||
|
};
|
||||||
|
|
||||||
|
openFirewall = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Open port in firewall.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
services.vaultwarden = {
|
||||||
|
enable = true;
|
||||||
|
config = {
|
||||||
|
ROCKET_PORT = cfg.port;
|
||||||
|
ROCKET_ADDRESS = "0.0.0.0";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.shares.nfs;
|
||||||
|
in {
|
||||||
|
options.services.naxos.shares.nfs = {
|
||||||
|
enable = mkEnableOption "NaxOS Declarative NFS Service";
|
||||||
|
|
||||||
|
lockdPort = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 4001;
|
||||||
|
description = "Port for lockd.";
|
||||||
|
};
|
||||||
|
|
||||||
|
mountdPort = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 4002;
|
||||||
|
description = "Port for mountd.";
|
||||||
|
};
|
||||||
|
|
||||||
|
exports = mkOption {
|
||||||
|
type = types.listOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
path = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Path to exported directory or dataset.";
|
||||||
|
};
|
||||||
|
clients = mkOption {
|
||||||
|
type = types.listOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
subnet = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
example = "10.0.0.0/23";
|
||||||
|
description = "Allowed client subnet or IP.";
|
||||||
|
};
|
||||||
|
options = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "rw,sync,no_subtree_check,no_root_squash";
|
||||||
|
description = "NFS export options.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = [];
|
||||||
|
description = "Clients allowed to mount this export.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = [];
|
||||||
|
description = "List of NFS exported directories.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
services.nfs.server = {
|
||||||
|
enable = true;
|
||||||
|
lockdPort = cfg.lockdPort;
|
||||||
|
mountdPort = cfg.mountdPort;
|
||||||
|
exports = concatMapStringsSep "\n" (exp:
|
||||||
|
let
|
||||||
|
clientList = concatMapStringsSep " " (c: "${c.subnet}(${c.options})") exp.clients;
|
||||||
|
in "${exp.path} ${clientList}"
|
||||||
|
) cfg.exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall = {
|
||||||
|
allowedTCPPorts = [ 111 2049 cfg.lockdPort cfg.mountdPort ];
|
||||||
|
allowedUDPPorts = [ 111 2049 cfg.lockdPort cfg.mountdPort ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.shares.samba;
|
||||||
|
in {
|
||||||
|
options.services.naxos.shares.samba = {
|
||||||
|
enable = mkEnableOption "NaxOS Declarative Samba (SMB) Service";
|
||||||
|
|
||||||
|
workgroup = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "WORKGROUP";
|
||||||
|
description = "NetBIOS workgroup.";
|
||||||
|
};
|
||||||
|
|
||||||
|
serverString = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "NaxOS Storage Appliance";
|
||||||
|
description = "Server announcement string.";
|
||||||
|
};
|
||||||
|
|
||||||
|
netbiosName = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "naxos";
|
||||||
|
description = "NetBIOS hostname.";
|
||||||
|
};
|
||||||
|
|
||||||
|
allowedHosts = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ "10.0.0." "192.168." "172.16." "127.0.0.1" "localhost" ];
|
||||||
|
description = "Allowed IP subnets or hosts for SMB access.";
|
||||||
|
};
|
||||||
|
|
||||||
|
shares = mkOption {
|
||||||
|
type = types.attrsOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether to publish this share.";
|
||||||
|
};
|
||||||
|
path = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Target local directory or ZFS mountpoint.";
|
||||||
|
};
|
||||||
|
comment = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "";
|
||||||
|
description = "Share description.";
|
||||||
|
};
|
||||||
|
readOnly = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Whether the share is read-only.";
|
||||||
|
};
|
||||||
|
browseable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether the share is visible in network browsing.";
|
||||||
|
};
|
||||||
|
guestOk = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Whether anonymous/guest access is allowed.";
|
||||||
|
};
|
||||||
|
validUsers = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = "List of valid users or @groups.";
|
||||||
|
};
|
||||||
|
forceUser = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Force UNIX user for all connections.";
|
||||||
|
};
|
||||||
|
forceGroup = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Force UNIX group for all connections.";
|
||||||
|
};
|
||||||
|
createMask = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "0664";
|
||||||
|
description = "File creation permission mask.";
|
||||||
|
};
|
||||||
|
directoryMask = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "0775";
|
||||||
|
description = "Directory creation permission mask.";
|
||||||
|
};
|
||||||
|
timeMachine = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable Apple Time Machine compatibility mode (vfs_fruit).";
|
||||||
|
};
|
||||||
|
timeMachineMaxSize = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
example = "512G";
|
||||||
|
description = "Quota size limit advertised to Time Machine.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = {};
|
||||||
|
description = "Declaratively managed SMB shares.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
services.samba = {
|
||||||
|
enable = true;
|
||||||
|
openFirewall = true;
|
||||||
|
settings = {
|
||||||
|
global = {
|
||||||
|
workgroup = cfg.workgroup;
|
||||||
|
"server string" = cfg.serverString;
|
||||||
|
"netbios name" = cfg.netbiosName;
|
||||||
|
security = "user";
|
||||||
|
"hosts allow" = concatStringsSep " " cfg.allowedHosts;
|
||||||
|
"hosts deny" = "0.0.0.0/0";
|
||||||
|
"guest account" = "nobody";
|
||||||
|
"map to guest" = "bad user";
|
||||||
|
|
||||||
|
# Apple & ZFS ACL compatibility
|
||||||
|
"vfs objects" = "catia fruit streams_xattr acl_xattr";
|
||||||
|
"fruit:aapl" = "yes";
|
||||||
|
"fruit:metadata" = "stream";
|
||||||
|
"fruit:model" = "Macmini";
|
||||||
|
"fruit:posix_rename" = "yes";
|
||||||
|
"fruit:zero_file_id" = "yes";
|
||||||
|
|
||||||
|
# ACLs and inheritance
|
||||||
|
"map acl inherit" = "yes";
|
||||||
|
"inherit acls" = "yes";
|
||||||
|
"ea support" = "yes";
|
||||||
|
};
|
||||||
|
} // (mapAttrs' (shareName: shareCfg:
|
||||||
|
nameValuePair shareName (
|
||||||
|
{
|
||||||
|
path = shareCfg.path;
|
||||||
|
comment = shareCfg.comment;
|
||||||
|
browseable = if shareCfg.browseable then "yes" else "no";
|
||||||
|
"read only" = if shareCfg.readOnly then "yes" else "no";
|
||||||
|
"guest ok" = if shareCfg.guestOk then "yes" else "no";
|
||||||
|
"create mask" = shareCfg.createMask;
|
||||||
|
"directory mask" = shareCfg.directoryMask;
|
||||||
|
}
|
||||||
|
// optionalAttrs (shareCfg.validUsers != []) {
|
||||||
|
"valid users" = concatStringsSep " " shareCfg.validUsers;
|
||||||
|
}
|
||||||
|
// optionalAttrs (shareCfg.forceUser != null) {
|
||||||
|
"force user" = shareCfg.forceUser;
|
||||||
|
}
|
||||||
|
// optionalAttrs (shareCfg.forceGroup != null) {
|
||||||
|
"force group" = shareCfg.forceGroup;
|
||||||
|
}
|
||||||
|
// optionalAttrs shareCfg.timeMachine {
|
||||||
|
"vfs objects" = "catia fruit streams_xattr acl_xattr";
|
||||||
|
"fruit:time machine" = "yes";
|
||||||
|
}
|
||||||
|
// optionalAttrs (shareCfg.timeMachine && shareCfg.timeMachineMaxSize != null) {
|
||||||
|
"fruit:time machine max size" = shareCfg.timeMachineMaxSize;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) (filterAttrs (n: v: v.enable) cfg.shares));
|
||||||
|
};
|
||||||
|
|
||||||
|
# Enable Avahi (mDNS / Bonjour) for automatic SMB & Time Machine discovery on macOS/iOS/Windows
|
||||||
|
services.avahi = {
|
||||||
|
enable = true;
|
||||||
|
nssmdns4 = true;
|
||||||
|
publish = {
|
||||||
|
enable = true;
|
||||||
|
userServices = true;
|
||||||
|
};
|
||||||
|
extraServiceFiles = {
|
||||||
|
smb = ''
|
||||||
|
<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
|
||||||
|
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
|
||||||
|
<service-group>
|
||||||
|
<name replace-wildcards="yes">%h (NaxOS SMB)</name>
|
||||||
|
<service>
|
||||||
|
<type>_smb._tcp</type>
|
||||||
|
<port>445</port>
|
||||||
|
</service>
|
||||||
|
<service>
|
||||||
|
<type>_device-info._tcp</type>
|
||||||
|
<port>0</port>
|
||||||
|
<txt-record>model=RackMac</txt-record>
|
||||||
|
</service>
|
||||||
|
</service-group>
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.storage;
|
||||||
|
in {
|
||||||
|
options.services.naxos.storage = {
|
||||||
|
enable = mkEnableOption "NaxOS Declarative ZFS Storage Engine";
|
||||||
|
|
||||||
|
arcMaxBytes = mkOption {
|
||||||
|
type = types.nullOr types.ints.positive;
|
||||||
|
default = 4294967296; # 4 GiB default
|
||||||
|
description = "Maximum ZFS ARC cache size in bytes.";
|
||||||
|
};
|
||||||
|
|
||||||
|
autoScrub = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Enable automated regular scrubbing of ZFS pools.";
|
||||||
|
};
|
||||||
|
interval = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "monthly";
|
||||||
|
description = "Interval for auto-scrub (e.g. monthly, weekly).";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
autoTrim = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Enable periodic TRIM for SSD/NVMe vdevs.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
autoSnapshot = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Enable automatic snapshot retention policies.";
|
||||||
|
};
|
||||||
|
hourly = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 24;
|
||||||
|
description = "Number of hourly snapshots to keep.";
|
||||||
|
};
|
||||||
|
daily = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 7;
|
||||||
|
description = "Number of daily snapshots to keep.";
|
||||||
|
};
|
||||||
|
weekly = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 4;
|
||||||
|
description = "Number of weekly snapshots to keep.";
|
||||||
|
};
|
||||||
|
monthly = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 12;
|
||||||
|
description = "Number of monthly snapshots to keep.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
importExistingPools = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
example = [ "tank" "datapool" ];
|
||||||
|
description = "List of existing foreign ZFS pools (e.g. from TrueNAS or nixos-lukas) to safely import without formatting.";
|
||||||
|
};
|
||||||
|
|
||||||
|
pools = mkOption {
|
||||||
|
type = types.attrsOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether to manage this pool.";
|
||||||
|
};
|
||||||
|
ashift = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 12;
|
||||||
|
description = "ZFS vdev ashift alignment value (12 = 4K sectors).";
|
||||||
|
};
|
||||||
|
layout = mkOption {
|
||||||
|
type = types.enum [ "stripe" "mirror" "raidz1" "raidz2" "raidz3" ];
|
||||||
|
default = "mirror";
|
||||||
|
description = "Vdev topology layout.";
|
||||||
|
};
|
||||||
|
devices = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
example = [ "/dev/disk/by-id/nvme-..." "/dev/disk/by-id/ata-..." ];
|
||||||
|
description = "Member disk devices or partitions.";
|
||||||
|
};
|
||||||
|
datasets = mkOption {
|
||||||
|
type = types.attrsOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
mountpoint = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Mountpoint for this dataset. If null, uses default ZFS mount.";
|
||||||
|
};
|
||||||
|
compression = mkOption {
|
||||||
|
type = types.enum [ "on" "off" "lz4" "zstd" "zstd-fast" "gzip" ];
|
||||||
|
default = "lz4";
|
||||||
|
description = "ZFS compression algorithm.";
|
||||||
|
};
|
||||||
|
recordsize = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "128K";
|
||||||
|
description = "Record size for dataset (e.g. 1M for media, 16K for databases).";
|
||||||
|
};
|
||||||
|
quota = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Optional dataset quota (e.g. 500G).";
|
||||||
|
};
|
||||||
|
reservation = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Optional dataset reservation.";
|
||||||
|
};
|
||||||
|
owner = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "root";
|
||||||
|
description = "POSIX owner of mountpoint.";
|
||||||
|
};
|
||||||
|
group = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "root";
|
||||||
|
description = "POSIX group of mountpoint.";
|
||||||
|
};
|
||||||
|
mode = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "0755";
|
||||||
|
description = "POSIX directory mode permissions.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = {};
|
||||||
|
description = "Sub-datasets belonging to this pool.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = {};
|
||||||
|
description = "Declaratively defined ZFS pools and datasets.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
boot.supportedFilesystems = [ "zfs" ];
|
||||||
|
boot.kernelPackages = pkgs.linuxPackages;
|
||||||
|
|
||||||
|
# Kernel parameter for ZFS ARC Max limit
|
||||||
|
boot.kernelParams = mkIf (cfg.arcMaxBytes != null) [
|
||||||
|
"zfs.zfs_arc_max=${toString cfg.arcMaxBytes}"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Safe pool import list (includes both configured pools and imported existing pools)
|
||||||
|
boot.zfs.extraPools = unique (
|
||||||
|
(attrNames (filterAttrs (n: v: v.enable) cfg.pools)) ++
|
||||||
|
cfg.importExistingPools
|
||||||
|
);
|
||||||
|
boot.zfs.forceImportRoot = false;
|
||||||
|
|
||||||
|
# Automated ZFS services
|
||||||
|
services.zfs = {
|
||||||
|
autoScrub = {
|
||||||
|
enable = cfg.autoScrub.enable;
|
||||||
|
interval = cfg.autoScrub.interval;
|
||||||
|
};
|
||||||
|
trim = {
|
||||||
|
enable = cfg.autoTrim.enable;
|
||||||
|
};
|
||||||
|
autoSnapshot = {
|
||||||
|
enable = cfg.autoSnapshot.enable;
|
||||||
|
hourly = cfg.autoSnapshot.hourly;
|
||||||
|
daily = cfg.autoSnapshot.daily;
|
||||||
|
weekly = cfg.autoSnapshot.weekly;
|
||||||
|
monthly = cfg.autoSnapshot.monthly;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# System tools for storage diagnosis and disk monitoring
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
zfs
|
||||||
|
smartmontools
|
||||||
|
hdparm
|
||||||
|
nvme-cli
|
||||||
|
parted
|
||||||
|
gptfdisk
|
||||||
|
iotop
|
||||||
|
ncdu
|
||||||
|
sanoid
|
||||||
|
syncoid
|
||||||
|
];
|
||||||
|
|
||||||
|
# Generate systemd.tmpfiles rules for configured datasets with custom owners/modes
|
||||||
|
systemd.tmpfiles.rules = flatten (mapAttrsToList (poolName: poolCfg:
|
||||||
|
mapAttrsToList (dsName: dsCfg:
|
||||||
|
mkIf (dsCfg.mountpoint != null)
|
||||||
|
"d ${dsCfg.mountpoint} ${dsCfg.mode} ${dsCfg.owner} ${dsCfg.group} -"
|
||||||
|
) poolCfg.datasets
|
||||||
|
) cfg.pools);
|
||||||
|
|
||||||
|
# Generate fileSystems definitions for datasets with explicit mountpoints
|
||||||
|
fileSystems = foldl' (acc: pool:
|
||||||
|
let
|
||||||
|
poolName = pool.name;
|
||||||
|
poolCfg = pool.value;
|
||||||
|
in acc // (mapAttrs' (dsName: dsCfg:
|
||||||
|
nameValuePair dsCfg.mountpoint {
|
||||||
|
device = "${poolName}/${dsName}";
|
||||||
|
fsType = "zfs";
|
||||||
|
options = [ "nofail" ];
|
||||||
|
}
|
||||||
|
) (filterAttrs (n: v: v.mountpoint != null) poolCfg.datasets))
|
||||||
|
) {} (mapAttrsToList (name: value: { inherit name value; }) (filterAttrs (n: v: v.enable) cfg.pools));
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.naxos.ui;
|
||||||
|
in {
|
||||||
|
options.services.naxos.ui = {
|
||||||
|
enable = mkEnableOption "NaxOS Web Dashboard Service";
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 80;
|
||||||
|
description = "Web interface listen port.";
|
||||||
|
};
|
||||||
|
|
||||||
|
sslPort = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 443;
|
||||||
|
description = "SSL/HTTPS listen port.";
|
||||||
|
};
|
||||||
|
|
||||||
|
staticPath = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "/opt/naxos/ui/dist";
|
||||||
|
description = "Path to compiled React/Vite web dashboard assets.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
services.nginx = {
|
||||||
|
enable = true;
|
||||||
|
recommendedProxySettings = true;
|
||||||
|
recommendedGzipSettings = true;
|
||||||
|
recommendedOptimisation = true;
|
||||||
|
|
||||||
|
virtualHosts."naxos.local" = {
|
||||||
|
default = true;
|
||||||
|
listen = [
|
||||||
|
{ addr = "0.0.0.0"; port = cfg.port; }
|
||||||
|
{ addr = "[::]"; port = cfg.port; }
|
||||||
|
];
|
||||||
|
|
||||||
|
# Serve Frontend SPA
|
||||||
|
locations."/" = {
|
||||||
|
root = cfg.staticPath;
|
||||||
|
tryFiles = "$uri $uri/ /index.html";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Reverse Proxy to NaxOS API & WebSocket / SSE
|
||||||
|
locations."/api/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:${toString config.services.naxos.api.port}/";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_cache off;
|
||||||
|
proxy_read_timeout 86400s;
|
||||||
|
proxy_send_timeout 86400s;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Reverse Proxy to Perses Embedded Analytics
|
||||||
|
locations."/perses/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:${toString config.services.naxos.perses.port}/";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [ cfg.port cfg.sslPort ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
boot.loader.systemd-boot.enable = true;
|
||||||
|
boot.loader.efi.canTouchEfiVariables = true;
|
||||||
|
|
||||||
|
boot.initrd.availableKernelModules = [
|
||||||
|
"xhci_pci"
|
||||||
|
"ahci"
|
||||||
|
"nvme"
|
||||||
|
"usb_storage"
|
||||||
|
"sd_mod"
|
||||||
|
"mpt3sas"
|
||||||
|
"virtio_pci"
|
||||||
|
"virtio_scsi"
|
||||||
|
"virtio_blk"
|
||||||
|
"virtio_net"
|
||||||
|
];
|
||||||
|
|
||||||
|
boot.initrd.kernelModules = [ ];
|
||||||
|
boot.kernelModules = [ "kvm-intel" "kvm-amd" ];
|
||||||
|
boot.extraModulePackages = [ ];
|
||||||
|
|
||||||
|
# Dynamic DHCP on physical interfaces
|
||||||
|
networking.useDHCP = lib.mkDefault true;
|
||||||
|
|
||||||
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
# Core Appliance Settings
|
||||||
|
services.naxos.core = {
|
||||||
|
enable = true;
|
||||||
|
hostName = "naxos";
|
||||||
|
timeZone = "Europe/Berlin";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Declarative ZFS Storage Engine
|
||||||
|
services.naxos.storage = {
|
||||||
|
enable = true;
|
||||||
|
arcMaxBytes = 4294967296; # 4GB default
|
||||||
|
autoScrub.enable = true;
|
||||||
|
autoTrim.enable = true;
|
||||||
|
autoSnapshot = {
|
||||||
|
enable = true;
|
||||||
|
hourly = 24;
|
||||||
|
daily = 7;
|
||||||
|
weekly = 4;
|
||||||
|
monthly = 12;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Network File Shares
|
||||||
|
services.naxos.shares.samba = {
|
||||||
|
enable = true;
|
||||||
|
workgroup = "WORKGROUP";
|
||||||
|
serverString = "NaxOS Appliance";
|
||||||
|
netbiosName = "naxos";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.naxos.shares.nfs = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Workload Runtime & App Engine
|
||||||
|
services.naxos.appEngine = {
|
||||||
|
enable = true;
|
||||||
|
defaultRuntime = "docker";
|
||||||
|
docker.enable = true;
|
||||||
|
gpuAcceleration.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Telemetry & Native Perses Analytics
|
||||||
|
services.naxos.monitoring = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.naxos.perses = {
|
||||||
|
enable = true;
|
||||||
|
port = 8080;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Management API & Web Dashboard
|
||||||
|
services.naxos.api = {
|
||||||
|
enable = true;
|
||||||
|
port = 8088;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.naxos.ui = {
|
||||||
|
enable = true;
|
||||||
|
port = 80;
|
||||||
|
sslPort = 443;
|
||||||
|
};
|
||||||
|
|
||||||
|
# GitOps Configuration
|
||||||
|
services.naxos.gitops = {
|
||||||
|
enable = true;
|
||||||
|
autoPushOnCommit = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Automated Updates
|
||||||
|
services.naxos.selfUpdate = {
|
||||||
|
enable = true;
|
||||||
|
channel = "stable";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Nix System State Version
|
||||||
|
system.stateVersion = "24.11";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user