diff --git a/CLAUDE.md b/CLAUDE.md index ace088e..60952e5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,25 +23,34 @@ This is a NixOS homelab configuration using Nix flakes. The setup includes a sel ### Flake Structure - `flake.nix` - Main flake definition with inputs (nixpkgs, agenix, personal site) -- `configuration.nix` - Default NixOS configuration (mostly commented out) -- `system.nix` - System-level settings (boot, power management) -- `packages.nix` - System packages (neovim, git, tools) -- `users.nix` - User configuration for lucas user with fish shell +- `configuration.nix` - Legacy configuration (minimal, contains system.stateVersion) ### Modular Organization -- `networking/` - Network configuration modules +- `config/` - Shared configuration options + - `common.nix` - Common options used across modules (domain, user, networking, etc.) +- `modules/core/` - Core system functionality + - `boot.nix` - Boot loader and Nix settings + - `server-power.nix` - Server power management (disable sleep/hibernation) + - `packages.nix` - System packages (neovim, git, tools) +- `modules/hardware/` - Hardware-specific configurations + - `hardware-configuration.nix` - Hardware scan results +- `modules/users/` - User management + - `lucas.nix` - User configuration with fish shell and multimedia group +- `modules/networking/` - Network configuration modules - `host.nix` - Static IP, firewall, Avahi discovery - `ssh.nix` - SSH daemon configuration - `adblock.nix` - Ad blocking setup - `vpn-host.nix` - Tailscale VPN with auto-connect -- `services/` - Service modules +- `modules/services/` - Service modules - `jellyfin.nix` - Media server with Deluge torrent client - `site.nix` - Nginx reverse proxy, ACME SSL, Route53 DDNS + - `forgejo.nix` - Git server + - `calibre-web.nix` - Ebook server ### Secret Management - Uses agenix for encrypted secrets - `secrets/aws.age` - AWS credentials for Route53 updates -- `tailscale.age` - Tailscale authentication key +- `secrets/tailscale.age` - Tailscale authentication key ### Key Services - **Jellyfin**: Media server on port 8096, proxied via jellyfin.per-aspera.space @@ -60,6 +69,9 @@ This is a NixOS homelab configuration using Nix flakes. The setup includes a sel - System is configured as a server (no hibernation/suspend) - Uses systemd-boot EFI bootloader -- Multimedia group for media file permissions at /data/media +- Modular configuration with shared options in `config/common.nix` +- Decoupled modules reduce hard-coded dependencies - Fish shell is the default for the lucas user -- ACME certificates automatically managed for per-aspera.space domain \ No newline at end of file +- ACME certificates automatically managed for per-aspera.space domain +- Use `nix flake check` and `nix flake build` to test building without having to do nixos-rebuild +- New files will need to be tracked by `git add` to be able to be used by nix flakes \ No newline at end of file diff --git a/config/common.nix b/config/common.nix new file mode 100644 index 0000000..e9f5769 --- /dev/null +++ b/config/common.nix @@ -0,0 +1,72 @@ +# Common configuration options used across modules +{ lib, ... }: + +{ + options.homelab = { + domain = lib.mkOption { + type = lib.types.str; + default = "per-aspera.space"; + description = "Primary domain for the homelab"; + }; + + user = lib.mkOption { + type = lib.types.str; + default = "lucas"; + description = "Primary user for the homelab"; + }; + + networking = { + interface = lib.mkOption { + type = lib.types.str; + default = "enp0s31f6"; + description = "Primary network interface"; + }; + + staticIP = lib.mkOption { + type = lib.types.str; + default = "192.168.0.10"; + description = "Static IP address"; + }; + + gateway = lib.mkOption { + type = lib.types.str; + default = "192.168.0.1"; + description = "Default gateway"; + }; + + nameservers = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ "205.171.3.25" "8.8.8.8" ]; + description = "DNS nameservers"; + }; + }; + + aws = { + hostedZoneId = lib.mkOption { + type = lib.types.str; + default = "Z09728753LLLNSYFXIBIM"; + description = "AWS Route53 hosted zone ID"; + }; + }; + + mediaGroup = lib.mkOption { + type = lib.types.str; + default = "multimedia"; + description = "Group name for media access"; + }; + }; + + # Set default configuration values + config.homelab = { + domain = "per-aspera.space"; + user = "lucas"; + networking = { + interface = "enp0s31f6"; + staticIP = "192.168.0.10"; + gateway = "192.168.0.1"; + nameservers = [ "205.171.3.25" "8.8.8.8" ]; + }; + aws.hostedZoneId = "Z09728753LLLNSYFXIBIM"; + mediaGroup = "multimedia"; + }; +} \ No newline at end of file diff --git a/configuration.nix b/configuration.nix index fc23f60..c2b6a42 100644 --- a/configuration.nix +++ b/configuration.nix @@ -1,119 +1,9 @@ -# Edit this configuration file to define what should be installed on -# your system. Help is available in the configuration.nix(5) man page, on -# https://search.nixos.org/options and in the NixOS manual (`nixos-help`). - +# Legacy configuration.nix - most functionality moved to modules { config, lib, pkgs, ... }: { - imports = [ # Include the results of the hardware scan. - ./hardware-configuration.nix - ]; - - # Use the systemd-boot EFI boot loader. - boot.loader.systemd-boot.enable = true; - boot.loader.efi.canTouchEfiVariables = true; - - # networking.hostName = "nixos"; # Define your hostname. - # Pick only one of the below networking options. - # networking.wireless.enable = true; # Enables wireless support via wpa_supplicant. - # networking.networkmanager.enable = true; # Easiest to use and most distros use this by default. - - # Set your time zone. - # time.timeZone = "Europe/Amsterdam"; - - # Configure network proxy if necessary - # networking.proxy.default = "http://user:password@proxy:port/"; - # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain"; - - # Select internationalisation properties. - # i18n.defaultLocale = "en_US.UTF-8"; - # console = { - # font = "Lat2-Terminus16"; - # keyMap = "us"; - # useXkbConfig = true; # use xkb.options in tty. - # }; - - # Enable the X11 windowing system. - # services.xserver.enable = true; - - # Configure keymap in X11 - # services.xserver.xkb.layout = "us"; - # services.xserver.xkb.options = "eurosign:e,caps:escape"; - - # Enable CUPS to print documents. - # services.printing.enable = true; - - # Enable sound. - # services.pulseaudio.enable = true; - # OR - # services.pipewire = { - # enable = true; - # pulse.enable = true; - # }; - - # Enable touchpad support (enabled default in most desktopManager). - # services.libinput.enable = true; - - # Define a user account. Don't forget to set a password with ‘passwd’. - # users.users.alice = { - # isNormalUser = true; - # extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user. - # packages = with pkgs; [ - # tree - # ]; - # }; - - # programs.firefox.enable = true; - - # List packages installed in system profile. - # You can use https://search.nixos.org/ to find more packages (and options). - # environment.systemPackages = with pkgs; [ - # vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default. - # wget - # ]; - - # Some programs need SUID wrappers, can be configured further or are - # started in user sessions. - # programs.mtr.enable = true; - # programs.gnupg.agent = { - # enable = true; - # enableSSHSupport = true; - # }; - - # List services that you want to enable: - - # Enable the OpenSSH daemon. - # services.openssh.enable = true; - - # Open ports in the firewall. - # networking.firewall.allowedTCPPorts = [ ... ]; - # networking.firewall.allowedUDPPorts = [ ... ]; - # Or disable the firewall altogether. - # networking.firewall.enable = false; - - # Copy the NixOS configuration file and link it from the resulting system - # (/run/current-system/configuration.nix). This is useful in case you - # accidentally delete configuration.nix. - # system.copySystemConfiguration = true; - - # This option defines the first version of NixOS you have installed on this particular machine, - # and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions. - # - # Most users should NEVER change this value after the initial install, for any reason, - # even if you've upgraded your system to a new NixOS release. - # - # This value does NOT affect the Nixpkgs version your packages and OS are pulled from, - # so changing it will NOT upgrade your system - see https://nixos.org/manual/nixos/stable/#sec-upgrading for how - # to actually do that. - # - # This value being lower than the current NixOS release does NOT mean your system is - # out of date, out of support, or vulnerable. - # - # Do NOT change this value unless you have manually inspected all the changes it would make to your configuration, - # and migrated your data accordingly. - # - # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion . - system.stateVersion = "25.05"; # Did you read the comment? - + # This value defines the first version of NixOS you have installed on this particular machine. + # Do NOT change this value unless you have manually inspected all the changes it would make. + system.stateVersion = "25.05"; } diff --git a/flake.nix b/flake.nix index 1376e94..8496508 100644 --- a/flake.nix +++ b/flake.nix @@ -18,22 +18,40 @@ specialArgs = { inherit inputs; }; modules = [ + # Common configuration options + ./config/common.nix + + # External modules agenix.nixosModules.default - ./system.nix + + # Core system modules + ./modules/core/boot.nix + ./modules/core/server-power.nix + ./modules/core/packages.nix + + # Hardware + ./modules/hardware/hardware-configuration.nix + + # Users + ./modules/users/lucas.nix + + # Networking + ./modules/networking/host.nix + ./modules/networking/ssh.nix + ./modules/networking/adblock.nix + ./modules/networking/vpn-host.nix + + # Services + ./modules/services/jellyfin.nix + ./modules/services/site.nix + ./modules/services/forgejo.nix + ./modules/services/calibre-web.nix + + # Legacy configuration and secrets ./configuration.nix - ./hardware-configuration.nix - ./networking/host.nix - ./packages.nix - ./users.nix - ./networking/ssh.nix - ./networking/adblock.nix - ./networking/vpn-host.nix - ./services/jellyfin.nix - ./services/site.nix - ./services/forgejo.nix { environment.systemPackages = [ agenix.packages.x86_64-linux.default ]; - age.secrets.tailscale.file = ./tailscale.age; + age.secrets.tailscale.file = ./secrets/tailscale.age; age.secrets.aws.file = ./secrets/aws.age; nixpkgs.config.allowUnfree = true; } diff --git a/modules/core/boot.nix b/modules/core/boot.nix new file mode 100644 index 0000000..a3afb6e --- /dev/null +++ b/modules/core/boot.nix @@ -0,0 +1,11 @@ +# Boot configuration +{ config, lib, pkgs, ... }: + +{ + # Use the systemd-boot EFI boot loader + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + # Enable flakes + nix.settings.experimental-features = [ "nix-command" "flakes" ]; +} \ No newline at end of file diff --git a/packages.nix b/modules/core/packages.nix similarity index 71% rename from packages.nix rename to modules/core/packages.nix index bfc8533..6e245d5 100644 --- a/packages.nix +++ b/modules/core/packages.nix @@ -1,4 +1,7 @@ -{ config, lib, pkgs, ... }: { +# System-wide packages +{ config, lib, pkgs, ... }: + +{ environment.systemPackages = with pkgs; [ neovim vimPlugins.LazyVim @@ -8,4 +11,4 @@ btop lazygit ]; -} +} \ No newline at end of file diff --git a/modules/core/server-power.nix b/modules/core/server-power.nix new file mode 100644 index 0000000..3bcaf12 --- /dev/null +++ b/modules/core/server-power.nix @@ -0,0 +1,15 @@ +# Server power management - disable sleep/hibernation +{ config, lib, pkgs, ... }: + +{ + # Don't turn off when lid is closed (for laptop servers) + services.logind.lidSwitchExternalPower = "ignore"; + + # Disable all sleep modes - this is a server! + systemd.sleep.extraConfig = '' + AllowSuspend=no + AllowHibernation=no + AllowHybridSleep=no + AllowSuspendThenHibernate=no + ''; +} \ No newline at end of file diff --git a/hardware-configuration.nix b/modules/hardware/hardware-configuration.nix similarity index 100% rename from hardware-configuration.nix rename to modules/hardware/hardware-configuration.nix diff --git a/networking/adblock.nix b/modules/networking/adblock.nix similarity index 100% rename from networking/adblock.nix rename to modules/networking/adblock.nix diff --git a/networking/host.nix b/modules/networking/host.nix similarity index 53% rename from networking/host.nix rename to modules/networking/host.nix index b99c866..da35ff1 100644 --- a/networking/host.nix +++ b/modules/networking/host.nix @@ -1,4 +1,7 @@ -{ config, lib, pkgs, ... }: { +# Network host configuration with static IP and Avahi discovery +{ config, lib, pkgs, ... }: + +{ services.avahi = { enable = true; nssmdns4 = true; @@ -11,24 +14,23 @@ }; networking = { - nameservers = [ "205.171.3.25" "8.8.8.8" ]; - + nameservers = config.homelab.networking.nameservers; enableIPv6 = false; # Avahi port firewall.allowedUDPPorts = [ 5353 ]; hostName = "homelab"; - interfaces.enp0s31f6 = { + interfaces.${config.homelab.networking.interface} = { ipv4.addresses = [{ - address = "192.168.0.10"; + address = config.homelab.networking.staticIP; prefixLength = 24; }]; }; defaultGateway = { - address = "192.168.0.1"; - interface = "enp0s31f6"; + address = config.homelab.networking.gateway; + interface = config.homelab.networking.interface; }; }; } diff --git a/networking/ssh.nix b/modules/networking/ssh.nix similarity index 100% rename from networking/ssh.nix rename to modules/networking/ssh.nix diff --git a/networking/vpn-host.nix b/modules/networking/vpn-host.nix similarity index 100% rename from networking/vpn-host.nix rename to modules/networking/vpn-host.nix diff --git a/modules/services/calibre-web.nix b/modules/services/calibre-web.nix new file mode 100644 index 0000000..26e9418 --- /dev/null +++ b/modules/services/calibre-web.nix @@ -0,0 +1,25 @@ +{ pkgs, ... }: { + users.users.lucas.extraGroups = [ "multimedia" ]; + + networking.firewall.allowedTCPPorts = [ 8083 ]; + + systemd.tmpfiles.rules = [ + "d /data/calibre 0770 - multimedia - -" + "d /data/calibre/library 0770 - multimedia - -" + "d /data/calibre/config 0770 - multimedia - -" + ]; + + services.calibre-web = { + enable = true; + listen = { + ip = "0.0.0.0"; + port = 8083; + }; + options = { calibreLibrary = "/data/calibre/library"; }; + user = "calibre-web"; + group = "multimedia"; + }; + + environment.systemPackages = with pkgs; [ calibre ]; +} + diff --git a/services/forgejo.nix b/modules/services/forgejo.nix similarity index 100% rename from services/forgejo.nix rename to modules/services/forgejo.nix diff --git a/modules/services/jellyfin.nix b/modules/services/jellyfin.nix new file mode 100644 index 0000000..c218258 --- /dev/null +++ b/modules/services/jellyfin.nix @@ -0,0 +1,29 @@ +# Jellyfin media server with Deluge torrent client +{ config, pkgs, ... }: + +{ + systemd.tmpfiles.rules = [ + "d /data - - - - -" + "d /data/media 0770 - ${config.homelab.mediaGroup} - -" + "d /data/shows 0770 - ${config.homelab.mediaGroup} - -" + ]; + + services.jellyfin = { + enable = true; + openFirewall = true; + group = config.homelab.mediaGroup; + }; + + environment.systemPackages = with pkgs; [ + jellyfin + jellyfin-web + jellyfin-ffmpeg + ]; + + services.deluge = { + enable = true; + web.enable = true; + web.openFirewall = true; + group = config.homelab.mediaGroup; + }; +} diff --git a/services/site.nix b/modules/services/site.nix similarity index 95% rename from services/site.nix rename to modules/services/site.nix index 0aa3848..d358bd2 100644 --- a/services/site.nix +++ b/modules/services/site.nix @@ -1,7 +1,8 @@ +# Website hosting with Nginx reverse proxy and Route53 DDNS { config, pkgs, inputs, lib, ... }: let - domain = "per-aspera.space"; - hostedZoneId = "Z09728753LLLNSYFXIBIM"; + domain = config.homelab.domain; + hostedZoneId = config.homelab.aws.hostedZoneId; # Configurable list of DNS records to update dnsRecords = [ diff --git a/modules/users/lucas.nix b/modules/users/lucas.nix new file mode 100644 index 0000000..dc5d190 --- /dev/null +++ b/modules/users/lucas.nix @@ -0,0 +1,17 @@ +# User configuration for lucas +{ config, lib, pkgs, ... }: + +{ + programs.fish.enable = true; + + users.users.${config.homelab.user} = { + isNormalUser = true; + extraGroups = [ "wheel" config.homelab.mediaGroup ]; + shell = pkgs.fish; + packages = with pkgs; [ eza claude-code ]; + hashedPassword = "$6$X.mw03yY/VFjDThj$t1I68HZz6NBihZGhiJ6Ct8ZuOufX6ZX9pydnK4puTjT1XKfMO1FY5VL1DwywJHrXOEJtohV99TmrABfjdBQY21"; + }; + + # Create multimedia group for media services + users.groups.${config.homelab.mediaGroup} = { }; +} \ No newline at end of file diff --git a/tailscale.age b/secrets/tailscale.age similarity index 100% rename from tailscale.age rename to secrets/tailscale.age diff --git a/services/jellyfin.nix b/services/jellyfin.nix deleted file mode 100644 index 02280ff..0000000 --- a/services/jellyfin.nix +++ /dev/null @@ -1,26 +0,0 @@ -{ pkgs, ... }: { - users.groups.multimedia = { }; - users.users.lucas.extraGroups = [ "multimedia" ]; - - systemd.tmpfiles.rules = - [ "d /data - - - - -" "d /data/media 0770 - multimedia - -" ]; - - services.jellyfin = { - enable = true; - openFirewall = true; - group = "multimedia"; - }; - - environment.systemPackages = with pkgs; [ - jellyfin - jellyfin-web - jellyfin-ffmpeg - ]; - - services.deluge = { - enable = true; - web.enable = true; - web.openFirewall = true; - group = "multimedia"; - }; -} diff --git a/system.nix b/system.nix deleted file mode 100644 index c5e3bdb..0000000 --- a/system.nix +++ /dev/null @@ -1,17 +0,0 @@ -{ config, lib, pkgs, ... }: { - # System boots - boot.loader.systemd-boot.enable = true; - boot.loader.efi.canTouchEfiVariables = true; - nix.settings.experimental-features = [ "nix-command" "flakes" ]; - - # Don't turn off when lid is closed - services.logind.lidSwitchExternalPower = "ignore"; - - # Don't hybernate. You're a server! - systemd.sleep.extraConfig = '' - AllowSuspend=no - AllowHibernation=no - AllowHybridSleep=no - AllowSuspendThenHibernate=no - ''; -} diff --git a/users.nix b/users.nix deleted file mode 100644 index 59b43f5..0000000 --- a/users.nix +++ /dev/null @@ -1,15 +0,0 @@ -{ config, lib, pkgs, ... }: { - programs.fish.enable = true; - - users.users.lucas = { - isNormalUser = true; - extraGroups = [ "wheel" ]; - - shell = pkgs.fish; - - packages = with pkgs; [ eza claude-code ]; - - hashedPassword = - "$6$X.mw03yY/VFjDThj$t1I68HZz6NBihZGhiJ6Ct8ZuOufX6ZX9pydnK4puTjT1XKfMO1FY5VL1DwywJHrXOEJtohV99TmrABfjdBQY21"; - }; -}