Reorganization
This commit is contained in:
parent
2821a76450
commit
aa3b8519f9
21 changed files with 241 additions and 204 deletions
11
modules/core/boot.nix
Normal file
11
modules/core/boot.nix
Normal file
|
|
@ -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" ];
|
||||
}
|
||||
14
modules/core/packages.nix
Normal file
14
modules/core/packages.nix
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# System-wide packages
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
neovim
|
||||
vimPlugins.LazyVim
|
||||
git
|
||||
wget
|
||||
nixfmt-classic
|
||||
btop
|
||||
lazygit
|
||||
];
|
||||
}
|
||||
15
modules/core/server-power.nix
Normal file
15
modules/core/server-power.nix
Normal file
|
|
@ -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
|
||||
'';
|
||||
}
|
||||
50
modules/hardware/hardware-configuration.nix
Normal file
50
modules/hardware/hardware-configuration.nix
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||
# and may be overwritten by future invocations. Please make changes
|
||||
# to /etc/nixos/configuration.nix instead.
|
||||
{ config, lib, pkgs, modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
|
||||
|
||||
boot.initrd.availableKernelModules =
|
||||
[ "xhci_pci" "ahci" "sd_mod" "rtsx_pci_sdmmc" ];
|
||||
boot.initrd.kernelModules = [ ];
|
||||
boot.kernelModules = [ "kvm-intel" ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
fileSystems."/" = {
|
||||
device = "/dev/disk/by-uuid/887383a8-555c-4156-9b62-337ae007ce51";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
fileSystems."/boot" = {
|
||||
device = "/dev/disk/by-uuid/6979-163D";
|
||||
fsType = "vfat";
|
||||
options = [ "fmask=0077" "dmask=0077" ];
|
||||
};
|
||||
|
||||
swapDevices =
|
||||
[{ device = "/dev/disk/by-uuid/3fc91af7-13f4-4088-bb9a-7d6c8adf6d73"; }];
|
||||
|
||||
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
|
||||
# (the default) this is the recommended approach. When using systemd-networkd it's
|
||||
# still possible to use this option, but it's recommended to use it in conjunction
|
||||
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
|
||||
networking.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.cni0.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.enp0s31f6.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.flannel.1.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.veth17db2936.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.veth43e2454a.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.veth4c6fcbd9.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.veth55d9b858.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.veth61cf02b5.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.vethe8736672.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.vethf6a2e389.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.wg0.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.wlp2s0.useDHCP = lib.mkDefault true;
|
||||
|
||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||
hardware.cpu.intel.updateMicrocode =
|
||||
lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||
}
|
||||
71
modules/networking/adblock.nix
Normal file
71
modules/networking/adblock.nix
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
adblockLocalZones = pkgs.stdenv.mkDerivation {
|
||||
name = "unbound-zones-adblock";
|
||||
|
||||
src = (pkgs.fetchFromGitHub {
|
||||
owner = "StevenBlack";
|
||||
repo = "hosts";
|
||||
rev = "3.16.20";
|
||||
sha256 = "sha256-z3VWoF5/evd0n97AmrWgSskaNqVaad0Ex2pn53JHkSk=";
|
||||
} + "/hosts");
|
||||
|
||||
phases = [ "installPhase" ];
|
||||
|
||||
installPhase = ''
|
||||
${pkgs.gawk}/bin/awk '{sub(/\r$/,"")} {sub(/^127\.0\.0\.1/,"0.0.0.0")} BEGIN { OFS = "" } NF == 2 && $1 == "0.0.0.0" { print "local-zone: \"", $2, "\" static"}' $src | tr '[:upper:]' '[:lower:]' | sort -u > $out
|
||||
'';
|
||||
|
||||
};
|
||||
in {
|
||||
services.resolved.enable = false;
|
||||
networking.firewall.allowedTCPPorts = [ 53 ];
|
||||
networking.firewall.allowedUDPPorts = [ 53 ];
|
||||
|
||||
services.unbound = {
|
||||
enable = true;
|
||||
settings = {
|
||||
server = {
|
||||
port = 53;
|
||||
interface = [ "0.0.0.0" "127.0.0.1" ];
|
||||
access-control = [
|
||||
"127.0.0.0/8 allow"
|
||||
"10.0.0.0/8 allow"
|
||||
"172.16.0.0/12 allow"
|
||||
"192.168.0.0/16 allow"
|
||||
];
|
||||
|
||||
include = [ "${adblockLocalZones}" ];
|
||||
|
||||
num-threads = 2;
|
||||
so-reuseport = "yes";
|
||||
hide-identity = "yes";
|
||||
hide-version = "yes";
|
||||
qname-minimisation = "yes";
|
||||
harden-dnssec-stripped = "yes";
|
||||
prefetch = "yes";
|
||||
serve-expired = "yes";
|
||||
rrset-roundrobin = "yes";
|
||||
do-ip6 = "no";
|
||||
do-udp = "yes";
|
||||
do-tcp = "yes";
|
||||
};
|
||||
forward-zone = [{
|
||||
name = ".";
|
||||
forward-tls-upstream = "yes";
|
||||
forward-addr = [
|
||||
"1.1.1.1@853#cloudflare-dns.com"
|
||||
"1.0.0.1@853#cloudflare-dns.com"
|
||||
"8.8.8.8@853#dns.google"
|
||||
"8.8.4.4@853#dns.google"
|
||||
];
|
||||
}];
|
||||
};
|
||||
};
|
||||
|
||||
networking.resolvconf.enable = false;
|
||||
environment.etc."resolv.conf".text = ''
|
||||
nameserver 127.0.0.1
|
||||
options edns0
|
||||
'';
|
||||
}
|
||||
36
modules/networking/host.nix
Normal file
36
modules/networking/host.nix
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# Network host configuration with static IP and Avahi discovery
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
services.avahi = {
|
||||
enable = true;
|
||||
nssmdns4 = true;
|
||||
openFirewall = true;
|
||||
publish = {
|
||||
enable = true;
|
||||
userServices = true;
|
||||
addresses = true;
|
||||
};
|
||||
};
|
||||
|
||||
networking = {
|
||||
nameservers = config.homelab.networking.nameservers;
|
||||
enableIPv6 = false;
|
||||
|
||||
# Avahi port
|
||||
firewall.allowedUDPPorts = [ 5353 ];
|
||||
|
||||
hostName = "homelab";
|
||||
interfaces.${config.homelab.networking.interface} = {
|
||||
ipv4.addresses = [{
|
||||
address = config.homelab.networking.staticIP;
|
||||
prefixLength = 24;
|
||||
}];
|
||||
};
|
||||
|
||||
defaultGateway = {
|
||||
address = config.homelab.networking.gateway;
|
||||
interface = config.homelab.networking.interface;
|
||||
};
|
||||
};
|
||||
}
|
||||
5
modules/networking/ssh.nix
Normal file
5
modules/networking/ssh.nix
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{ config, lib, pkgs, ... }: {
|
||||
services.openssh.enable = true;
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 22 ];
|
||||
}
|
||||
32
modules/networking/vpn-host.nix
Normal file
32
modules/networking/vpn-host.nix
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{ config, lib, pkgs, ... }: {
|
||||
environment.systemPackages = [ pkgs.tailscale ];
|
||||
|
||||
services.tailscale.enable = true;
|
||||
|
||||
systemd.services.tailscale-autoconnect = {
|
||||
description = "Automatic oneshot connection to tailscale";
|
||||
|
||||
after = [ "network-pre.target" "tailscale.service" ];
|
||||
wants = [ "network-pre.target" "tailscale.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig.Type = "oneshot";
|
||||
|
||||
script = with pkgs; ''
|
||||
sleep 2
|
||||
|
||||
status="$(${tailscale}/bin/tailscale status -json | ${jq}/bin/jq -r .BackendState)"
|
||||
if [ $status = "Running" ]; then # if so, then do nothing
|
||||
exit 0
|
||||
fi
|
||||
|
||||
${tailscale}/bin/tailscale up -authkey ${config.age.secrets.tailscale.path}
|
||||
'';
|
||||
};
|
||||
|
||||
networking.firewall = {
|
||||
trustedInterfaces = [ "tailscale0" ];
|
||||
allowedUDPPorts = [ config.services.tailscale.port ];
|
||||
allowedTCPPorts = [ 22 ];
|
||||
};
|
||||
}
|
||||
25
modules/services/calibre-web.nix
Normal file
25
modules/services/calibre-web.nix
Normal file
|
|
@ -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 ];
|
||||
}
|
||||
|
||||
18
modules/services/forgejo.nix
Normal file
18
modules/services/forgejo.nix
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{ pkgs, ... }: {
|
||||
services.forgejo = {
|
||||
enable = true;
|
||||
settings = {
|
||||
server = {
|
||||
ROOT_URL = "https://git.per-aspera.space";
|
||||
HTTP_ADDR = "0.0.0.0";
|
||||
HTTP_PORT = 3000;
|
||||
SSH_DOMAIN = "git.per-aspera.space";
|
||||
SSH_PORT = 2222;
|
||||
};
|
||||
service = { DISABLE_REGISTRATION = false; };
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 3000 2222 ];
|
||||
}
|
||||
|
||||
29
modules/services/jellyfin.nix
Normal file
29
modules/services/jellyfin.nix
Normal file
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
131
modules/services/site.nix
Normal file
131
modules/services/site.nix
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
# Website hosting with Nginx reverse proxy and Route53 DDNS
|
||||
{ config, pkgs, inputs, lib, ... }:
|
||||
let
|
||||
domain = config.homelab.domain;
|
||||
hostedZoneId = config.homelab.aws.hostedZoneId;
|
||||
|
||||
# Configurable list of DNS records to update
|
||||
dnsRecords = [
|
||||
domain
|
||||
"jellyfin.${domain}"
|
||||
"git.${domain}"
|
||||
# Add more records here as needed
|
||||
# "api.${domain}"
|
||||
# "mail.${domain}"
|
||||
];
|
||||
|
||||
updateRoute53 = pkgs.writeShellScript "update-route53" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
HOSTED_ZONE_ID="${hostedZoneId}"
|
||||
DNS_RECORDS=(${lib.concatStringsSep " " (map lib.escapeShellArg dnsRecords)})
|
||||
|
||||
# Get current public IP
|
||||
CURRENT_IP=$(${pkgs.curl}/bin/curl -s https://ifconfig.me)
|
||||
|
||||
# Validate IP format
|
||||
if ! echo "$CURRENT_IP" | grep -qE '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'; then
|
||||
echo "[$(date)] ERROR: Invalid IP format: $CURRENT_IP" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to update a DNS record
|
||||
update_record() {
|
||||
local RECORD_NAME=$1
|
||||
local DNS_IP=$(${pkgs.dig}/bin/dig +short "$RECORD_NAME" @8.8.8.8 | tail -n1)
|
||||
|
||||
if [ "$CURRENT_IP" != "$DNS_IP" ]; then
|
||||
echo "[$(date)] $RECORD_NAME IP changed: $DNS_IP -> $CURRENT_IP"
|
||||
|
||||
${pkgs.awscli2}/bin/aws route53 change-resource-record-sets \
|
||||
--hosted-zone-id "$HOSTED_ZONE_ID" \
|
||||
--change-batch "{
|
||||
\"Changes\": [{
|
||||
\"Action\": \"UPSERT\",
|
||||
\"ResourceRecordSet\": {
|
||||
\"Name\": \"$RECORD_NAME\",
|
||||
\"Type\": \"A\",
|
||||
\"TTL\": 300,
|
||||
\"ResourceRecords\": [{\"Value\": \"$CURRENT_IP\"}]
|
||||
}
|
||||
}]
|
||||
}"
|
||||
|
||||
echo "[$(date)] $RECORD_NAME DNS updated successfully to $CURRENT_IP"
|
||||
else
|
||||
echo "[$(date)] $RECORD_NAME IP unchanged: $CURRENT_IP"
|
||||
fi
|
||||
}
|
||||
|
||||
# Update all configured records
|
||||
for record in "''${DNS_RECORDS[@]}"; do
|
||||
update_record "$record"
|
||||
done
|
||||
'';
|
||||
in {
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedTlsSettings = true;
|
||||
|
||||
virtualHosts."${domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
||||
root = "${inputs.thelenlucas.packages.${pkgs.system}.default}";
|
||||
};
|
||||
|
||||
virtualHosts."jellyfin.${domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
||||
locations."/" = { proxyPass = "http://localhost:8096"; };
|
||||
};
|
||||
|
||||
virtualHosts."git.${domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
||||
locations."/" = { proxyPass = "http://localhost:3000"; };
|
||||
};
|
||||
|
||||
# Local git access to avoid NAT hairpinning
|
||||
virtualHosts."git.homelab" = {
|
||||
locations."/" = { proxyPass = "http://localhost:3000"; };
|
||||
};
|
||||
};
|
||||
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults.email = "thelenlucas@gmail.com";
|
||||
};
|
||||
|
||||
environment.systemPackages = [ pkgs.awscli2 ];
|
||||
systemd.services.route53-ddns = {
|
||||
description = "Update Route 53 with current IP periodically";
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = updateRoute53;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.route53-ddns = {
|
||||
description = "Route 53 DDNS Update Timer";
|
||||
wantedBy = [ "timers.target" ];
|
||||
|
||||
timerConfig = {
|
||||
OnBootSec = "1min";
|
||||
OnUnitActiveSec = "5min";
|
||||
Persistent = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
17
modules/users/lucas.nix
Normal file
17
modules/users/lucas.nix
Normal file
|
|
@ -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} = { };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue