Personal Nix setup

Add options for modules

-1
machines/cola/configuration.nix
···
./zfs.nix
../../modules/router
../../modules/server
-
../../modules/games
];
users.users."${user}" = {
+1
modules/base/default.nix
···
./nix-config.nix
./shell.nix
./linux.nix
+
./gpg.nix
];
}
+23
modules/base/gpg.nix
···
+
{ lib, config, pkgs, ... }:
+
+
with lib;
+
let
+
cfg = config.modules.gpg;
+
in {
+
options.modules.gpg = {
+
enable = mkEnableOption "GnuPG";
+
};
+
+
config = mkIf cfg.enable {
+
environment.systemPackages = [
+
pkgs.gnupg
+
];
+
+
programs.gnupg = {
+
agent = {
+
enable = true;
+
enableSSHSupport = true;
+
};
+
};
+
};
+
}
+6 -6
modules/base/linux.nix
···
-
{ lib, config, pkgs, helpers, ... } @ inputs:
+
{ lib, pkgs, helpers, ... } @ inputs:
+
with lib;
let
inherit (import ../../lib/colors.nix inputs) hex;
-
inherit (lib) mkDefault mkForce;
in helpers.linuxAttrs {
environment.systemPackages = [ pkgs.sbctl ];
···
};
boot = {
-
bootspec.enable = lib.mkDefault true;
-
consoleLogLevel = lib.mkDefault 2;
+
bootspec.enable = mkDefault true;
+
consoleLogLevel = mkDefault 2;
loader = {
timeout = mkDefault 2;
···
systemd.enable = true;
};
-
kernelPackages = lib.mkDefault pkgs.linuxPackages_latest;
+
kernelPackages = mkDefault pkgs.linuxPackages_latest;
kernelParams = [
"quiet"
···
services.dbus.enable = true;
-
virtualisation.oci-containers.backend = mkForce "podman";
+
virtualisation.oci-containers.backend = mkDefault "podman";
}
+4 -3
modules/default.nix
···
-
{ ... }:
-
{
imports = [
./base
+
./desktop
./development
-
./gpg.nix
+
./fonts
./nvim
+
./router
+
./server
];
}
+39 -19
modules/desktop/default.nix
···
-
{ lib, user, helpers, ... }:
+
{ lib, config, user, helpers, ... }:
+
+
with lib;
+
let
+
cfg = config.modules.desktop;
+
in {
+
options.modules.desktop = {
+
enable = mkOption {
+
default = false;
+
example = true;
+
description = "Whether to enable Desktop options.";
+
type = types.bool;
+
};
+
};
-
helpers.linuxAttrs {
+
config.modules.server = {
+
enable = if helpers.isLinux then (mkDefault false) else (mkForce false);
+
};
+
} // helpers.linuxAttrs {
imports = [
./services.nix
./session.nix
./xdg.nix
-
./fonts
+
./fonts.nix
];
-
users.users."${user}".extraGroups = [ "video" ];
+
config = mkIf cfg.enable {
+
users.users."${user}".extraGroups = [ "video" ];
-
services = {
-
fwupd.enable = true;
-
pipewire = {
-
enable = true;
-
wireplumber.enable = true;
-
alsa.enable = true;
-
alsa.support32Bit = true;
-
pulse.enable = true;
-
jack.enable = true;
+
services = {
+
fwupd.enable = true;
+
pipewire = {
+
enable = true;
+
wireplumber.enable = true;
+
pulse.enable = true;
+
jack.enable = true;
+
alsa = {
+
enable = true;
+
support32Bit = true;
+
};
+
};
+
};
+
+
hardware = {
+
pulseaudio.enable = lib.mkForce false;
+
steam-hardware.enable = true;
};
-
};
-
hardware = {
-
pulseaudio.enable = lib.mkForce false;
-
steam-hardware.enable = true;
+
security.rtkit.enable = true;
};
-
-
security.rtkit.enable = true;
}
+36
modules/desktop/fonts.nix
···
+
{ lib, config, pkgs, ... }:
+
+
with lib;
+
let
+
cfg = config.modules.desktop;
+
in {
+
options.modules.desktop.fonts = {
+
enable = mkOption {
+
default = cfg.enable;
+
example = true;
+
description = "Whether to enable default fonts.";
+
type = types.bool;
+
};
+
};
+
+
config = mkIf cfg.fonts.enable {
+
fonts = {
+
fontDir.enable = true;
+
+
packages = with pkgs; [
+
noto-fonts
+
noto-fonts-cjk
+
noto-fonts-emoji
+
roboto-mono
+
inter
+
];
+
+
fontconfig.defaultFonts = {
+
serif = [ "Noto Serif" "Noto Color Emoji" ];
+
sansSerif = [ "Inter" "Noto Color Emoji" ];
+
monospace = [ "Dank Mono" "Roboto Mono" "Noto Color Emoji" ];
+
emoji = [ "Noto Color Emoji" ];
+
};
+
};
+
};
+
}
-67
modules/desktop/fonts/default.nix
···
-
{ config, pkgs, lib, helpers, ... } @ inputs:
-
-
let
-
inherit (pkgs) stdenv;
-
-
fontsDir = if helpers.isDarwin then
-
"/Library/Fonts" else "/usr/share/fonts/nonfree";
-
-
in lib.mkMerge [
-
{
-
age.secrets."DankMono-Regular.otf" = {
-
symlink = false;
-
file = ./encrypt/DankMono-Regular.otf.age;
-
path = "${fontsDir}/DankMono-Regular.otf";
-
mode = "755";
-
};
-
-
age.secrets."DankMono-Italic.otf" = {
-
symlink = false;
-
file = ./encrypt/DankMono-Italic.otf.age;
-
path = "${fontsDir}/DankMono-Italic.otf";
-
mode = "755";
-
};
-
-
age.secrets."DankMono-Bold.otf" = {
-
symlink = false;
-
file = ./encrypt/DankMono-Bold.otf.age;
-
path = "${fontsDir}/DankMono-Bold.otf";
-
mode = "755";
-
};
-
-
age.secrets."codicon.otf" = {
-
symlink = false;
-
file = ./encrypt/codicon.otf.age;
-
path = "${fontsDir}/codicon.otf";
-
mode = "755";
-
};
-
-
age.secrets."faicon.ttf" = {
-
symlink = false;
-
file = ./encrypt/faicon.ttf.age;
-
path = "${fontsDir}/faicon.ttf";
-
mode = "755";
-
};
-
}
-
-
(helpers.linuxAttrs {
-
fonts = {
-
fontDir.enable = true;
-
-
packages = with pkgs; [
-
noto-fonts
-
noto-fonts-cjk
-
noto-fonts-emoji
-
roboto-mono
-
inter
-
];
-
-
fontconfig.defaultFonts = {
-
serif = [ "Noto Serif" "Noto Color Emoji" ];
-
sansSerif = [ "Inter" "Noto Color Emoji" ];
-
monospace = [ "Dank Mono" "Roboto Mono" "Noto Color Emoji" ];
-
emoji = [ "Noto Color Emoji" ];
-
};
-
};
-
})
-
]
modules/desktop/fonts/encrypt/DankMono-Bold.otf.age modules/fonts/encrypt/DankMono-Bold.otf.age
modules/desktop/fonts/encrypt/DankMono-Italic.otf.age modules/fonts/encrypt/DankMono-Italic.otf.age
modules/desktop/fonts/encrypt/DankMono-Regular.otf.age modules/fonts/encrypt/DankMono-Regular.otf.age
modules/desktop/fonts/encrypt/codicon.otf.age modules/fonts/encrypt/codicon.otf.age
modules/desktop/fonts/encrypt/faicon.ttf.age modules/fonts/encrypt/faicon.ttf.age
+21 -7
modules/desktop/services.nix
···
-
{ user, ... }:
+
{ lib, config, user, ... }:
-
{
-
users.users."${user}".extraGroups = [ "video" ];
+
with lib;
+
let
+
cfg = config.modules.desktop;
+
in {
+
options.modules.desktop.services = {
+
enable = mkOption {
+
default = cfg.enable;
+
example = true;
+
description = "Whether to enable services.";
+
type = types.bool;
+
};
+
};
-
services = {
-
printing.enable = true;
-
flatpak.enable = true;
-
colord.enable = true;
+
config = mkIf cfg.services.enable {
+
users.users."${user}".extraGroups = [ "video" ];
+
+
services = {
+
printing.enable = true;
+
flatpak.enable = true;
+
colord.enable = true;
+
};
};
}
+46 -31
modules/desktop/session.nix
···
-
{ pkgs, lib, user, ... }:
+
{ lib, config, pkgs, user, ... }:
-
{
-
boot.plymouth.enable = true;
+
with lib;
+
let
+
cfg = config.modules.desktop;
+
in {
+
options.modules.desktop.session = {
+
enable = mkOption {
+
default = cfg.enable;
+
example = true;
+
description = "Whether to enable session and desktop environment.";
+
type = types.bool;
+
};
+
};
-
services.desktopManager.plasma6.enable = true;
+
config = mkIf cfg.session.enable {
+
boot.plymouth.enable = true;
-
services.displayManager = {
-
defaultSession = "plasma";
-
autoLogin = {
-
inherit user;
-
enable = true;
+
services = {
+
desktopManager.plasma6.enable = true;
+
displayManager = {
+
defaultSession = "plasma";
+
autoLogin = {
+
inherit user;
+
enable = true;
+
};
+
sddm = {
+
enable = true;
+
enableHidpi = true;
+
wayland.enable = true;
+
};
+
};
};
-
sddm = {
-
enable = true;
-
enableHidpi = true;
-
wayland.enable = true;
+
+
environment.plasma6 = {
+
excludePackages = with pkgs.kdePackages; [
+
elisa
+
gwenview
+
oxygen
+
oxygen-sounds
+
khelpcenter
+
konsole
+
];
};
-
};
-
environment.plasma6 = {
-
excludePackages = with pkgs.kdePackages; [
-
elisa
-
gwenview
-
oxygen
-
oxygen-sounds
-
khelpcenter
-
konsole
-
];
-
};
+
security = {
+
polkit.enable = true;
+
};
-
security = {
-
polkit.enable = true;
-
};
-
-
xdg.portal = {
-
enable = true;
-
xdgOpenUsePortal = false;
+
xdg.portal = {
+
enable = true;
+
xdgOpenUsePortal = false;
+
};
};
}
+34 -20
modules/desktop/xdg.nix
···
-
{ lib, user, ... }:
+
{ lib, config, user, ... }:
-
{
-
home-manager.users.${user} = { ... }: {
-
systemd.user.sessionVariables = {
-
"NIXOS_OZONE_WL" = lib.mkDefault "1";
-
"MOZ_ENABLE_WAYLAND" = lib.mkDefault "1";
-
"QT_WAYLAND_DISABLE_WINDOWDECORATIONS" = lib.mkDefault "1";
-
"QT_WAYLAND_FORCE_DPI" = lib.mkDefault "physical";
-
"QT_QPA_PLATFORM" = lib.mkDefault "wayland-egl";
+
with lib;
+
let
+
cfg = config.modules.desktop;
+
in {
+
options.modules.desktop.xdg = {
+
enable = mkOption {
+
default = cfg.enable;
+
example = true;
+
description = "Whether to enable services.";
+
type = types.bool;
};
+
};
-
xdg = {
-
mimeApps = {
-
enable = true;
+
config = mkIf cfg.xdg.enable {
+
home-manager.users.${user} = { ... }: {
+
systemd.user.sessionVariables = {
+
"NIXOS_OZONE_WL" = lib.mkDefault "1";
+
"MOZ_ENABLE_WAYLAND" = lib.mkDefault "1";
+
"QT_WAYLAND_DISABLE_WINDOWDECORATIONS" = lib.mkDefault "1";
+
"QT_WAYLAND_FORCE_DPI" = lib.mkDefault "physical";
+
"QT_QPA_PLATFORM" = lib.mkDefault "wayland-egl";
};
-
userDirs = {
-
enable = true;
-
createDirectories = true;
+
+
xdg = {
+
mimeApps = {
+
enable = true;
+
};
+
userDirs = {
+
enable = true;
+
createDirectories = true;
+
};
+
systemDirs.data = [
+
"/usr/share"
+
"/var/lib/flatpak/exports/share"
+
"$HOME/.local/share/flatpak/exports/share"
+
];
};
-
systemDirs.data = [
-
"/usr/share"
-
"/var/lib/flatpak/exports/share"
-
"$HOME/.local/share/flatpak/exports/share"
-
];
};
};
}
+23 -5
modules/development/cocoapods.nix
···
-
{ pkgs, helpers, ... }:
+
{ lib, config, pkgs, helpers, ... }:
-
helpers.darwinAttrs {
-
environment.systemPackages = [
-
pkgs.cocoapods
-
];
+
with lib;
+
let
+
cfg = config.modules.development;
+
in {
+
options.modules.development.cocoapods = {
+
enable = mkOption {
+
default = cfg.enable;
+
example = true;
+
description = "Whether to enable Cocoapods.";
+
type = types.bool;
+
};
+
};
+
+
config.modules.development.cocoapods = {
+
enable = if helpers.isDarwin then (mkDefault true) else (mkForce false);
+
};
+
} // helpers.darwinAttrs {
+
config = mkIf cfg.cocoapods.enable {
+
environment.systemPackages = [
+
pkgs.cocoapods
+
];
+
};
}
+12 -1
modules/development/default.nix
···
-
{
+
{ lib, ... }:
+
+
with lib; {
+
options.modules.development = {
+
enable = mkOption {
+
default = false;
+
example = true;
+
description = "Whether to enable Development options.";
+
type = types.bool;
+
};
+
};
+
imports = [
./js.nix
./cocoapods.nix
+28 -14
modules/development/js.nix
···
-
{ pkgs, ... }:
+
{ lib, config, pkgs, ... }:
-
{
-
environment.variables = {
-
PNPM_HOME = "$HOME/.local/share/pnpm";
-
BUN_RUNTIME_TRANSPILER_CACHE_PATH = "$HOME/.cache/bun/install/cache/@t@";
-
COREPACK_ENABLE_AUTO_PIN = "0";
+
with lib;
+
let
+
cfg = config.modules.development;
+
in {
+
options.modules.development.js = {
+
enable = mkOption {
+
default = cfg.enable;
+
example = true;
+
description = "Whether to enable JS tools.";
+
type = types.bool;
+
};
};
-
environment.systemPackages = (with pkgs; [
-
bun
-
corepack_22
-
nodejs_22
-
]);
+
config = mkIf cfg.js.enable {
+
environment.variables = {
+
PNPM_HOME = "$HOME/.local/share/pnpm";
+
BUN_RUNTIME_TRANSPILER_CACHE_PATH = "$HOME/.cache/bun/install/cache/@t@";
+
COREPACK_ENABLE_AUTO_PIN = "0";
+
};
+
+
environment.systemPackages = (with pkgs; [
+
bun
+
corepack_22
+
nodejs_22
+
]);
-
environment.interactiveShellInit = ''
-
export PATH=./node_modules/.bin:$HOME/.local/share/pnpm:$PATH
-
'';
+
environment.interactiveShellInit = ''
+
export PATH=./node_modules/.bin:$HOME/.local/share/pnpm:$PATH
+
'';
+
};
}
+53
modules/fonts/default.nix
···
+
{ config, lib, helpers, ... }:
+
+
with lib;
+
let
+
cfg = config.modules.fonts;
+
fontsPath = if helpers.isDarwin then "/Library/Fonts" else "/usr/share/fonts/nonfree";
+
in {
+
options.modules.fonts = {
+
enable = mkOption {
+
default = false;
+
example = true;
+
description = "Whether to enable fonts options.";
+
type = types.bool;
+
};
+
};
+
+
config = mkIf cfg.enable {
+
age.secrets."DankMono-Regular.otf" = {
+
symlink = false;
+
file = ./encrypt/DankMono-Regular.otf.age;
+
path = "${fontsPath}/DankMono-Regular.otf";
+
mode = "755";
+
};
+
+
age.secrets."DankMono-Italic.otf" = {
+
symlink = false;
+
file = ./encrypt/DankMono-Italic.otf.age;
+
path = "${fontsPath}/DankMono-Italic.otf";
+
mode = "755";
+
};
+
+
age.secrets."DankMono-Bold.otf" = {
+
symlink = false;
+
file = ./encrypt/DankMono-Bold.otf.age;
+
path = "${fontsPath}/DankMono-Bold.otf";
+
mode = "755";
+
};
+
+
age.secrets."codicon.otf" = {
+
symlink = false;
+
file = ./encrypt/codicon.otf.age;
+
path = "${fontsPath}/codicon.otf";
+
mode = "755";
+
};
+
+
age.secrets."faicon.ttf" = {
+
symlink = false;
+
file = ./encrypt/faicon.ttf.age;
+
path = "${fontsPath}/faicon.ttf";
+
mode = "755";
+
};
+
};
+
}
-7
modules/games/default.nix
···
-
{ helpers, ... }:
-
-
helpers.linuxAttrs {
-
imports = [
-
./enshrouded-server.nix
-
];
-
}
-51
modules/games/enshrouded-server.nix
···
-
{ lib, config, pkgs, ... }:
-
-
{
-
ids.gids.steam = lib.mkDefault 10000;
-
ids.uids.steam = lib.mkDefault 10000;
-
-
users = {
-
groups.steam.gid = config.ids.gids.steam;
-
users.steam = {
-
uid = config.ids.uids.steam;
-
group = "steam";
-
createHome = true;
-
home = "/var/lib/steam";
-
isSystemUser = true;
-
};
-
};
-
-
virtualisation.oci-containers = {
-
containers.enshrouded = rec {
-
autoStart = false;
-
volumes = [
-
"/var/lib/steam/enshrouded:/home/steam/enshrouded/savegame"
-
"/etc/localtime:/etc/localtime:ro"
-
];
-
environment = {
-
TZ = "Europe/London";
-
SERVER_IP = "0.0.0.0";
-
SERVER_NAME = "London Boroughs";
-
SERVER_PASSWORD = "deepdickgalactic";
-
GAME_PORT = "15636";
-
QUERY_PORT = "15637";
-
SERVER_SLOTS = "8";
-
};
-
image = "docker.io/sknnr/enshrouded-dedicated-server:proton-latest";
-
ports = [
-
"15636:15636/udp"
-
"15637:15637/udp"
-
];
-
extraOptions = [
-
"--userns=keep-id"
-
"--cap-add=NET_RAW"
-
"--runtime=runc"
-
];
-
};
-
};
-
-
networking.firewall.allowedUDPPorts = [
-
15636
-
15637
-
];
-
}
-14
modules/gpg.nix
···
-
{ pkgs, ... }:
-
-
{
-
environment.systemPackages = [
-
pkgs.gnupg
-
];
-
-
programs.gnupg = {
-
agent = {
-
enable = true;
-
enableSSHSupport = true;
-
};
-
};
-
}
+19 -10
modules/nvim/default.nix
···
-
{ pkgs, ... } @ inputs:
+
{ lib, config, pkgs, ... } @ inputs:
+
with lib;
let
inherit (import ../../lib/colors.nix inputs) colors mkVimHardlineColors;
inherit (import ./theme.nix inputs) my-theme;
-
importContents = /*lua*/ ''
-
'';
+
cfg = config.modules.nvim;
initContents = "
\nlua <<EOF\n" + /* lua */ ''
···
neovimPkg = pkgs.neovim-unwrapped;
neovim = pkgs.wrapNeovimUnstable neovimPkg neovimConfig;
in {
-
environment.variables = { EDITOR = "vim"; };
+
options.modules.nvim = {
+
enable = mkEnableOption "Neovim";
+
useCustomConfig = mkEnableOption "Custom Configuration";
+
};
-
environment.systemPackages = with pkgs; [
-
ripgrep
-
fd
-
bat
-
neovim
-
];
+
config = mkIf cfg.enable {
+
environment.variables = { EDITOR = "vim"; };
+
+
environment.systemPackages = with pkgs; if cfg.useCustomConfig then [
+
ripgrep
+
fd
+
bat
+
neovim
+
] else [
+
neovimPkg
+
];
+
};
}
-9
modules/router/avahi.nix
···
-
{ ... }:
-
-
{
-
services.avahi = {
-
enable = true;
-
allowInterfaces = [ "intern0" ];
-
denyInterfaces = [ "extern0" ];
-
};
-
}
+40 -6
modules/router/default.nix
···
-
{ helpers, ... }:
+
{ lib, config, helpers, ... }:
+
+
with lib;
+
let
+
cfg = config.modules.router;
+
in {
+
options.modules.router = {
+
enable = mkOption {
+
default = false;
+
example = true;
+
description = "Whether to enable Router options.";
+
type = types.bool;
+
};
-
helpers.linuxAttrs {
+
interfaces = {
+
external = mkOption {
+
default = "extern0";
+
type = types.str;
+
};
+
internal = mkOption {
+
default = "intern0";
+
type = types.str;
+
};
+
};
+
};
+
+
config.modules.router = {
+
enable = if helpers.isLinux then (mkDefault false) else (mkForce false);
+
};
+
} // helpers.linuxAttrs {
imports = [
-
./ntp.nix
-
./stubby.nix
+
./timeserver.nix
+
./dnsOverTLS.nix
./dnsmasq.nix
./nftables.nix
-
./miniupnpd.nix
-
./avahi.nix
+
./upnp.nix
+
./mdns.nix
+
./kernel.nix
];
+
+
config = mkIf cfg.enable {
+
networking.firewall.trustedInterfaces = [
+
cfg.interfaces.internal
+
];
+
};
}
+47
modules/router/dnsOverTLS.nix
···
+
{ lib, config, ... }:
+
+
with lib;
+
let
+
cfg = config.modules.router;
+
in {
+
options.modules.router = {
+
dnsOverTLS = {
+
enable = mkOption {
+
default = cfg.enable;
+
description = "Whether to enable Stubby DNS proxy";
+
type = types.bool;
+
};
+
+
port = mkOption {
+
default = 53000;
+
description = "Port for Stubby";
+
type = types.int;
+
};
+
};
+
};
+
+
config = mkIf cfg.dnsOverTLS.enable {
+
services.stubby = {
+
enable = true;
+
settings = {
+
resolution_type = "GETDNS_RESOLUTION_STUB";
+
dns_transport_list = [ "GETDNS_TRANSPORT_TLS" ];
+
tls_authentication = "GETDNS_AUTHENTICATION_REQUIRED";
+
tls_query_padding_blocksize = 128;
+
edns_client_subnet_private = 1;
+
round_robin_upstreams = 1;
+
tls_connection_retries = 5;
+
listen_addresses = [
+
"127.0.0.1@${cfg.dnsOverTLS.port}"
+
"0::1@${cfg.dnsOverTLS.port}"
+
];
+
appdata_dir = "/var/cache/stubby";
+
trust_anchors_backoff_time = 2500;
+
upstream_recursive_servers = [
+
{ address_data = "1.1.1.1"; tls_auth_name = "cloudflare-dns.com"; }
+
{ address_data = "1.0.0.1"; tls_auth_name = "cloudflare-dns.com"; }
+
];
+
};
+
};
+
};
+
}
+104 -65
modules/router/dnsmasq.nix
···
-
{ config, lib, ... }:
+
{ lib, config, ... }:
+
+
with lib;
+
let
+
cfg = config.modules.router;
+
+
leaseType = types.submodule {
+
options = {
+
macAddress = mkOption {
+
type = types.str;
+
example = "00:00:00:00:00:00";
+
};
+
ipAddress = mkOption {
+
type = types.str;
+
example = "10.0.0.10";
+
};
+
};
+
};
+
+
dnsServer = if cfg.dnsOverTLS.enable
+
then [ "127.0.0.1#${cfg.dnsOverTLS.port}" ]
+
else [ "1.1.1.1" "1.0.0.1" ];
+
+
dhcpHost = builtins.map (lease: "${lease.macAddress},${lease.ipAddress}") cfg.dnsmasq.leases;
+
in {
+
options.modules.router = {
+
dnsmasq = {
+
enable = mkOption {
+
default = cfg.enable;
+
description = "Whether to enable DNSMasq";
+
type = types.bool;
+
};
+
+
leases = lib.mkOption {
+
default = [];
+
type = lib.types.listOf leaseType;
+
description = "List of reserved IP address leases";
+
};
+
};
+
};
+
+
config = mkIf cfg.dnsmasq.enable {
+
networking.nameservers = [ "127.0.0.1" ];
-
{
-
services.resolved.extraConfig = lib.mkDefault ''
-
[Resolve]
-
DNSStubListener=no
-
'';
+
services.resolved.extraConfig = mkDefault ''
+
[Resolve]
+
DNSStubListener=no
+
'';
-
services.dnsmasq = {
-
enable = true;
-
alwaysKeepRunning = true;
-
settings = {
-
server = if config.services.stubby.enable then [ "127.0.0.1#53000" ] else [ "1.1.1.1" "1.0.0.1" ];
+
services.dnsmasq = {
+
enable = true;
+
alwaysKeepRunning = true;
+
settings = {
+
server = dnsServer;
-
# never forward plain names (without a dot or domain part)
-
domain-needed = true;
-
# never forward addresses in the non-routed address spaces.
-
bogus-priv = true;
-
# filter useless windows-originated DNS requests
-
filterwin2k = true;
-
# never read nameservers from /etc/resolv.conf
-
no-resolv = true;
+
# never forward plain names (without a dot or domain part)
+
domain-needed = true;
+
# never forward addresses in the non-routed address spaces.
+
bogus-priv = true;
+
# filter useless windows-originated DNS requests
+
filterwin2k = true;
+
# never read nameservers from /etc/resolv.conf
+
no-resolv = true;
+
+
cache-size = 5000;
+
no-negcache = true;
+
+
expand-hosts = true;
+
addn-hosts = "/etc/hosts";
-
cache-size = 5000;
-
no-negcache = true;
+
dhcp-range = [
+
"10.0.0.2, 10.0.0.255, 255.255.255.0, 12h"
+
"tag:${cfg.interfaces.internal}, ::1, constructor:${cfg.interfaces.internal}, ra-names, slaac, 12h"
+
];
-
expand-hosts = true;
-
addn-hosts = "/etc/hosts";
+
dhcp-option = [
+
"option6:information-refresh-time, 6h"
+
"option:router,10.0.0.1"
+
"ra-param=${cfg.interfaces.internal},high,0,0"
+
];
-
dhcp-range = [
-
"10.0.0.2, 10.0.0.255, 255.255.255.0, 12h"
-
"tag:intern0, ::1, constructor:intern0, ra-names, slaac, 12h"
-
];
-
dhcp-option = [
-
"option6:information-refresh-time, 6h"
-
"option:router,10.0.0.1"
-
"option:ntp-server,10.0.0.1"
-
"ra-param=intern0,high,0,0"
-
];
+
dhcp-option = mkIf cfg.timeserver.enable [
+
"option:ntp-server,10.0.0.1"
+
];
-
dhcp-host = [
-
"98:ed:7e:c6:57:b2,10.0.0.102" # eero router
-
"c4:f1:74:51:4c:f2,10.0.0.124" # eero router
-
"5c:61:99:7a:16:40,10.0.0.103" # brother printer
-
"24:e8:53:95:e4:02,10.0.0.96" # tv
-
"34:7e:5c:31:4f:fa,10.0.0.56" # sonos
-
"e8:9c:25:6c:40:6f,10.0.0.150" # pepper-pc
-
];
+
dhcp-host = dhcpHost;
-
# listen only on intern0 by excluding extern0
-
except-interface = "extern0";
+
# listen only on intern0 by excluding extern0
+
except-interface = cfg.interfaces.external;
-
# set the DHCP server to authoritative and rapic commit mode
-
dhcp-authoritative = true;
-
dhcp-rapid-commit = true;
+
# set the DHCP server to authoritative and rapic commit mode
+
dhcp-authoritative = true;
+
dhcp-rapid-commit = true;
-
# Detect attempts by Verisign to send queries to unregistered hosts
-
bogus-nxdomain = "64.94.110.11";
+
# Detect attempts by Verisign to send queries to unregistered hosts
+
bogus-nxdomain = "64.94.110.11";
-
address = [
-
"/cola.fable-pancake.ts.net/10.0.0.1"
-
"/time.apple.com/10.0.0.1"
-
"/time1.apple.com/10.0.0.1"
-
"/time2.apple.com/10.0.0.1"
-
"/time3.apple.com/10.0.0.1"
-
"/time4.apple.com/10.0.0.1"
-
"/time5.apple.com/10.0.0.1"
-
"/time6.apple.com/10.0.0.1"
-
"/time7.apple.com/10.0.0.1"
-
"/time.euro.apple.com/10.0.0.1"
-
"/time.windows.com/10.0.0.1"
-
"/0.android.pool.ntp.org/10.0.0.1"
-
"/1.android.pool.ntp.org/10.0.0.1"
-
"/2.android.pool.ntp.org/10.0.0.1"
-
"/3.android.pool.ntp.org/10.0.0.1"
-
];
+
address = [
+
"/cola.fable-pancake.ts.net/10.0.0.1"
+
"/time.apple.com/10.0.0.1"
+
"/time1.apple.com/10.0.0.1"
+
"/time2.apple.com/10.0.0.1"
+
"/time3.apple.com/10.0.0.1"
+
"/time4.apple.com/10.0.0.1"
+
"/time5.apple.com/10.0.0.1"
+
"/time6.apple.com/10.0.0.1"
+
"/time7.apple.com/10.0.0.1"
+
"/time.euro.apple.com/10.0.0.1"
+
"/time.windows.com/10.0.0.1"
+
"/0.android.pool.ntp.org/10.0.0.1"
+
"/1.android.pool.ntp.org/10.0.0.1"
+
"/2.android.pool.ntp.org/10.0.0.1"
+
"/3.android.pool.ntp.org/10.0.0.1"
+
];
+
};
};
};
}
+65
modules/router/kernel.nix
···
+
{ lib, config, ... }:
+
+
with lib;
+
let
+
cfg = config.modules.router;
+
in {
+
options.modules.router = {
+
tweakKernel = mkOption {
+
default = cfg.enable;
+
description = "Whether to tweak kernel configuration";
+
type = types.bool;
+
};
+
};
+
+
config = mkIf cfg.enable {
+
boot.initrd.systemd = {
+
enable = true;
+
network.enable = true;
+
};
+
} // mkIf cfg.tweakKernel.enable {
+
boot.kernel.sysctl = {
+
"net.core.somaxconn" = 4096;
+
"net.core.netdev_max_backlog" = 2000;
+
+
"net.core.rmem_default" = 1048576;
+
"net.core.rmem_max" = 26214400;
+
"net.core.wmem_default" = 1048576;
+
"net.core.wmem_max" = 16777216;
+
"net.core.optmem_max" = 65536;
+
+
"net.ipv4.tcp_rmem" = "4096 1048576 2097152";
+
"net.ipv4.tcp_wmem" = "4096 65536 16777216";
+
+
"net.ipv4.tcp_max_syn_backlog" = 8192;
+
+
"net.ipv4.udp_rmem_min" = 8192;
+
"net.ipv4.udp_wmem_min" = 8192;
+
+
"net.ipv4.tcp_fastopen" = 3;
+
+
"net.ipv4.tcp_max_tw_buckets" = 2000000;
+
"net.ipv4.tcp_tw_reuse" = true;
+
"net.ipv4.tcp_slow_start_after_idle" = false;
+
"net.ipv4.tcp_mtu_probing" = true;
+
+
"net.ipv4.tcp_rfc1337" = true;
+
"net.ipv4.tcp_fin_timeout" = 10;
+
+
"net.ipv4.tcp_keepalive_time" = 60;
+
"net.ipv4.tcp_keepalive_intvl" = 10;
+
"net.ipv4.tcp_keepalive_probes" = 6;
+
+
"net.core.default_qdisc" = "cake";
+
"net.ipv4.tcp_congestion_control" = "bbr";
+
"net.ipv4.tcp_syncookies" = true;
+
+
"net.ipv6.conf.all.forwarding" = true;
+
"net.ipv6.conf.all.use_tempaddr" = false;
+
"net.ipv6.conf.all.autoconf" = false;
+
"net.ipv6.conf.all.accept_ra" = false;
+
+
"net.ipv4.ping_group_range" = "0 65536";
+
};
+
};
+
}
+22
modules/router/mdns.nix
···
+
{ lib, config, ... }:
+
+
with lib;
+
let
+
cfg = config.modules.router;
+
in {
+
options.modules.router = {
+
mdns.enable = mkOption {
+
default = cfg.enable;
+
description = "Whether to enable mDNS Discovery Service";
+
type = types.bool;
+
};
+
};
+
+
config = mkIf cfg.mdns.enable {
+
services.avahi = {
+
enable = true;
+
allowInterfaces = [ cfg.interfaces.internal ];
+
denyInterfaces = [ cfg.interfaces.external ];
+
};
+
};
+
}
-20
modules/router/miniupnpd.nix
···
-
{ ... }:
-
-
{
-
services.miniupnpd = {
-
enable = true;
-
upnp = true;
-
natpmp = true;
-
internalIPs = [ "intern0" ];
-
externalInterface = "extern0";
-
appendConfig = ''
-
ext_ip=137.220.98.13
-
secure_mode=yes
-
notify_interval=60
-
clean_ruleset_interval=600
-
uuid=78b8b903-83c1-4036-8fcd-f64aee25baca
-
allow 1024-65535 10.0.0.0/24 1024-65535
-
deny 0-65535 0.0.0.0/0 0-65535
-
'';
-
};
-
}
+141 -103
modules/router/nftables.nix
···
{ config, lib, ... }:
+
with lib;
let
+
cfg = config.modules.router;
+
trustedInterfaces =
-
lib.strings.concatMapStringsSep ", " lib.strings.escapeNixIdentifier config.networking.firewall.trustedInterfaces;
+
strings.concatMapStringsSep ", " strings.escapeNixIdentifier config.networking.firewall.trustedInterfaces;
+
+
capturePortsRules =
+
string.concatMapStringsSep "\n"
+
(builtins.map (port: " iifname { ${trustedInterfaces} } udp dport ${port} redirect to ${port}") cfg.nftables.capturePorts);
+
+
blockForwardRules =
+
string.concatMapStringsSep "\n"
+
(builtins.map (mac: " iifname ${cfg.interfaces.internal} oifname != ${cfg.interfaces.internal} ether saddr = ${mac} drop"));
in {
-
networking.firewall.checkReversePath = "loose";
+
options.modules.router = {
+
nftables = {
+
enable = mkOption {
+
default = cfg.enable;
+
description = "Whether to enable Router NFTables config";
+
type = types.bool;
+
};
-
networking.nftables = {
-
enable = true;
-
checkRuleset = false;
-
flushRuleset = true;
+
capturePorts = {
+
default = [ 53 123 ];
+
description = "Ports to capture and redirect to router";
+
type = types.listOf types.int;
+
};
-
tables.filter = {
-
family = "inet";
-
content = ''
-
chain prerouting {
-
type nat hook prerouting priority 0; policy accept;
-
iifname { ${trustedInterfaces} } udp dport 53 redirect to 53
-
iifname { ${trustedInterfaces} } udp dport 123 redirect to 123
-
}
+
blockForward = {
+
default = [];
+
description = "MAC Addresses of devices to block internet access for";
+
type = types.listOf types.str;
+
};
+
};
+
};
-
chain postrouting {
-
type nat hook postrouting priority 0; policy accept;
-
oifname != { ${trustedInterfaces} } masquerade
-
}
+
config = mkIf cfg.nftables.enable {
+
networking.useNetworkd = mkDefault true;
+
networking.firewall = {
+
enable = mkDefault true;
+
checkReversePath = "loose";
+
};
-
chain input {
-
type filter hook input priority 0;
-
ct state { established, related } accept
-
ct state invalid drop
-
iifname { ${trustedInterfaces} } accept
-
iifname { ${trustedInterfaces} } pkttype { broadcast, multicast } accept
-
tcp flags & (fin|syn|rst|ack) != syn ct state new counter drop
-
tcp flags & (fin|syn|rst|psh|ack|urg) == fin|syn|rst|psh|ack|urg counter drop
-
tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 counter drop
-
ip protocol icmp \
-
icmp type { destination-unreachable, echo-reply, echo-request, source-quench, time-exceeded } \
-
accept
-
ip6 nexthdr icmpv6 accept
-
udp dport 546 ct state { new, untracked } accept
-
udp dport dhcpv6-client accept
-
udp dport { http, https } ct state new accept
-
tcp dport { http, https } ct state new accept
-
udp dport 41641 ct state new accept
-
reject with icmpx type port-unreachable
-
}
+
networking.nftables = {
+
enable = mkForce true;
+
checkRuleset = false;
+
flushRuleset = true;
-
chain forward {
-
type filter hook forward priority 0; policy drop;
-
ip6 nexthdr ipv6-icmp accept
-
udp dport dhcpv6-client accept
-
iifname intern0 oifname != intern0 ether saddr == ec:e5:12:1d:23:40 drop # drop tado internet traffic
-
iifname { ${trustedInterfaces} } accept
-
oifname { ${trustedInterfaces} } ct state { established, related } accept
-
ct state invalid drop
-
}
+
tables.filter = {
+
family = "inet";
+
content = ''
+
chain prerouting {
+
type nat hook prerouting priority 0; policy accept;
+
${capturePortsRules}
+
}
-
chain output {
-
type filter hook output priority 0; policy accept;
-
ip6 nexthdr ipv6-icmp accept
-
udp dport dhcpv6-client accept
-
iifname lo accept
-
ct state invalid drop
-
}
-
'';
-
};
+
chain postrouting {
+
type nat hook postrouting priority 0; policy accept;
+
oifname != { ${trustedInterfaces} } masquerade
+
}
-
tables.arp_filter = {
-
family = "arp";
-
content = ''
-
chain input {
-
type filter hook input priority 0; policy accept;
-
iifname != { ${trustedInterfaces} } limit rate 1/second burst 2 packets accept
-
}
+
chain input {
+
type filter hook input priority 0;
+
ct state { established, related } accept
+
ct state invalid drop
+
iifname { ${trustedInterfaces} } accept
+
iifname { ${trustedInterfaces} } pkttype { broadcast, multicast } accept
+
tcp flags & (fin|syn|rst|ack) != syn ct state new counter drop
+
tcp flags & (fin|syn|rst|psh|ack|urg) == fin|syn|rst|psh|ack|urg counter drop
+
tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 counter drop
+
ip protocol icmp \
+
icmp type { destination-unreachable, echo-reply, echo-request, source-quench, time-exceeded } \
+
accept
+
ip6 nexthdr icmpv6 accept
+
udp dport 546 ct state { new, untracked } accept
+
udp dport dhcpv6-client accept
+
udp dport { http, https } ct state new accept
+
tcp dport { http, https } ct state new accept
+
udp dport 41641 ct state new accept
+
reject with icmpx type port-unreachable
+
}
-
chain output {
-
type filter hook output priority 0; policy accept;
-
}
-
'';
-
};
+
chain forward {
+
type filter hook forward priority 0; policy drop;
+
ip6 nexthdr ipv6-icmp accept
+
udp dport dhcpv6-client accept
+
${blockForwardRules}
+
iifname { ${trustedInterfaces} } accept
+
oifname { ${trustedInterfaces} } ct state { established, related } accept
+
ct state invalid drop
+
}
-
tables.tagging = {
-
family = "netdev";
-
content = ''
-
chain lan {
-
type filter hook ingress device intern0 priority -150; policy accept;
-
jump tags
-
}
+
chain output {
+
type filter hook output priority 0; policy accept;
+
ip6 nexthdr ipv6-icmp accept
+
udp dport dhcpv6-client accept
+
iifname lo accept
+
ct state invalid drop
+
}
+
'';
+
};
-
chain wan {
-
type filter hook ingress device extern0 priority -149; policy accept;
-
jump tags
-
}
+
tables.arp_filter = {
+
family = "arp";
+
content = ''
+
chain input {
+
type filter hook input priority 0; policy accept;
+
iifname != { ${trustedInterfaces} } limit rate 1/second burst 2 packets accept
+
}
-
chain tags {
-
ip dscp set cs0
-
ip6 dscp set cs0
+
chain output {
+
type filter hook output priority 0; policy accept;
+
}
+
'';
+
};
-
ip protocol udp udp sport ntp ip dscp set cs5
-
ip6 nexthdr udp udp sport ntp ip6 dscp set cs5
+
tables.tagging = {
+
family = "netdev";
+
content = ''
+
chain lan {
+
type filter hook ingress device ${cfg.interfaces.internal} priority -150; policy accept;
+
jump tags
+
}
+
+
chain wan {
+
type filter hook ingress device ${cfg.interfaces.external} priority -149; policy accept;
+
jump tags
+
}
+
+
chain tags {
+
ip dscp set cs0
+
ip6 dscp set cs0
-
ip saddr {1.1.1.1, 1.0.0.1} ip dscp set cs5
-
ip daddr {1.1.1.1, 1.0.0.1} ip dscp set cs5
+
ip protocol udp udp sport ntp ip dscp set cs5
+
ip6 nexthdr udp udp sport ntp ip6 dscp set cs5
+
+
ip saddr {1.1.1.1, 1.0.0.1} ip dscp set cs5
+
ip daddr {1.1.1.1, 1.0.0.1} ip dscp set cs5
-
tcp dport {http, https} ip dscp set cs3
-
tcp sport {http, https} ip dscp set cs3
-
ip6 nexthdr tcp tcp dport {http, https} ip6 dscp set cs3
-
ip6 nexthdr tcp tcp sport {http, https} ip6 dscp set cs3
+
tcp dport {http, https} ip dscp set cs3
+
tcp sport {http, https} ip dscp set cs3
+
ip6 nexthdr tcp tcp dport {http, https} ip6 dscp set cs3
+
ip6 nexthdr tcp tcp sport {http, https} ip6 dscp set cs3
-
udp dport 41641 ip dscp set cs4 # tailscale
-
udp sport 41641 ip dscp set cs4 # tailscale
+
udp dport 41641 ip dscp set cs4 # tailscale
+
udp sport 41641 ip dscp set cs4 # tailscale
-
# mark some VOIP traffic as flash override (low delay)
-
udp dport {3478-3479, 19302-19309} ip dscp set cs4
-
udp sport {3478-3479, 19302-19309} ip dscp set cs4
-
ip6 nexthdr udp udp dport {3478-3479, 19302-19309} ip6 dscp set cs4
-
ip6 nexthdr udp udp sport {3478-3479, 19302-19309} ip6 dscp set cs4
-
udp dport {7000-9000, 27000-27200} ip dscp set cs4
-
udp sport {7000-9000, 27000-27200} ip dscp set cs4
-
ip6 nexthdr udp udp dport {7000-9000, 27000-27200} ip6 dscp set cs4
-
ip6 nexthdr udp udp sport {7000-9000, 27000-27200} ip6 dscp set cs4
-
}
-
'';
+
# mark some VOIP traffic as flash override (low delay)
+
udp dport {3478-3479, 19302-19309} ip dscp set cs4
+
udp sport {3478-3479, 19302-19309} ip dscp set cs4
+
ip6 nexthdr udp udp dport {3478-3479, 19302-19309} ip6 dscp set cs4
+
ip6 nexthdr udp udp sport {3478-3479, 19302-19309} ip6 dscp set cs4
+
udp dport {7000-9000, 27000-27200} ip dscp set cs4
+
udp sport {7000-9000, 27000-27200} ip dscp set cs4
+
ip6 nexthdr udp udp dport {7000-9000, 27000-27200} ip6 dscp set cs4
+
ip6 nexthdr udp udp sport {7000-9000, 27000-27200} ip6 dscp set cs4
+
}
+
'';
+
};
};
};
}
-12
modules/router/ntp.nix
···
-
{ ... }:
-
-
{
-
services.ntp = {
-
enable = true;
-
extraConfig = ''
-
interface listen lo
-
interface listen intern0
-
interface ignore extern0
-
'';
-
};
-
}
-23
modules/router/stubby.nix
···
-
{ ... }:
-
-
{
-
services.stubby = {
-
enable = true;
-
settings = {
-
resolution_type = "GETDNS_RESOLUTION_STUB";
-
dns_transport_list = [ "GETDNS_TRANSPORT_TLS" ];
-
tls_authentication = "GETDNS_AUTHENTICATION_REQUIRED";
-
tls_query_padding_blocksize = 128;
-
edns_client_subnet_private = 1;
-
round_robin_upstreams = 1;
-
tls_connection_retries = 5;
-
listen_addresses = [ "127.0.0.1@53000" "0::1@53000" ];
-
appdata_dir = "/var/cache/stubby";
-
trust_anchors_backoff_time = 2500;
-
upstream_recursive_servers = [
-
{ address_data = "1.1.1.1"; tls_auth_name = "cloudflare-dns.com"; }
-
{ address_data = "1.0.0.1"; tls_auth_name = "cloudflare-dns.com"; }
-
];
-
};
-
};
-
}
+29
modules/router/timeserver.nix
···
+
{ lib, config, ... }:
+
+
with lib;
+
let
+
cfg = config.modules.router;
+
+
ntpExtraConfig = ''
+
interface listen lo
+
interface listen ${cfg.interfaces.internal}
+
interface ignore ${cfg.interfaces.external}
+
'';
+
in {
+
options.modules.router = {
+
timeserver.enable = mkOption {
+
default = cfg.enable;
+
description = "Whether to enable NTP Service";
+
type = types.bool;
+
};
+
};
+
+
config = mkIf cfg.timeserver.enable {
+
networking.timeServers = [ "time.cloudflare.com" ];
+
+
services.ntp = {
+
enable = true;
+
extraConfig = ntpExtraConfig;
+
};
+
};
+
}
+34
modules/router/upnp.nix
···
+
{ lib, config, ... }:
+
+
with lib;
+
let
+
cfg = config.modules.router;
+
in {
+
options.modules.router = {
+
upnp = {
+
enable = mkOption {
+
default = cfg.enable;
+
description = "Whether to enable UPNP";
+
type = types.bool;
+
};
+
};
+
};
+
+
config = mkIf cfg.upnp.enable {
+
services.miniupnpd = {
+
enable = true;
+
upnp = true;
+
natpmp = true;
+
internalIPs = [ cfg.interfaces.internal ];
+
externalInterface = cfg.interfaces.external;
+
appendConfig = ''
+
secure_mode=yes
+
notify_interval=60
+
clean_ruleset_interval=600
+
uuid=78b8b903-83c1-4036-8fcd-f64aee25baca
+
allow 1024-65535 10.0.0.0/24 1024-65535
+
deny 0-65535 0.0.0.0/0 0-65535
+
'';
+
};
+
};
+
}
+103 -46
modules/server/caddy.nix
···
-
{ config, ... }:
+
{ lib, config, hostname, ... }:
+
+
with lib;
+
let
+
cfgRoot = config.modules.server;
+
cfg = config.modules.server.caddy;
+
+
exposeType = types.submodule {
+
options = {
+
path = mkOption {
+
type = types.str;
+
example = "/share/files";
+
};
+
};
+
};
+
+
domain = config.networking.domain;
+
tailscaleEnabled = cfgRoot.tailscale.enable;
+
vaultwardenEnabled = cfgRoot.vaultwarden.enable;
+
jellyfinEnabled = cfgRoot.jellyfin.enable;
+
hassEnabled = cfgRoot.home-assistant.enable;
+
+
vaultwardenHandlerConfig = if vaultwardenEnabled then ''
+
handle_path /vault {
+
redir * /vault/
+
}
+
+
handle_path /vault/* {
+
reverse_proxy /notifications/hub/negotiate 127.0.0.1:8000
+
reverse_proxy /notifications/hub 127.0.0.1:8001
+
reverse_proxy localhost:8000 {
+
header_up X-Real-IP {remote_host}
+
}
+
}
+
'' else "";
+
+
jellyfinHandlerConfig = if jellyfinEnabled then ''
+
handle_path /media {
+
redir * /media/
+
}
-
{
-
services.tailscale.permitCertUid = config.services.caddy.user;
+
reverse_proxy /media/* localhost:8096 {
+
header_up X-Real-IP {remote_host}
+
}
+
'' else "";
-
services.caddy = {
-
enable = config.services.tailscale.enable;
-
email = "phil@kitten.sh";
-
extraConfig = ''
-
(network_paths) {
-
handle_path /home {
-
redir * https://cola.fable-pancake.ts.net:8123
-
}
+
hassHandlerConfig = if hassEnabled && tailscaleEnabled then ''
+
handle_path /home {
+
redir * https://${hostname}.${domain}:8123
+
}
+
'' else "";
-
handle_path /vault {
-
redir * /vault/
-
}
+
tailscaleConfig = if tailscaleEnabled then ''
+
${hostname}.${domain} {
+
import network_paths
+
}
+
'' else "";
-
handle_path /vault/* {
-
reverse_proxy /notifications/hub/negotiate 127.0.0.1:8000
-
reverse_proxy /notifications/hub 127.0.0.1:8001
-
reverse_proxy localhost:8000 {
-
header_up X-Real-IP {remote_host}
-
}
-
}
+
exposeConfig = let
+
configs = attrsets.mapAttrs (name: expose: ''
+
handle_path /${name} {
+
redir * /${name}/
+
}
-
handle_path /media {
-
redir * /media/
+
handle_path /${name}/* {
+
file_server {
+
browse
+
root ${expose.path}
+
hide .*
}
-
reverse_proxy /media/* localhost:8096 {
-
header_up X-Real-IP {remote_host}
-
}
+
@file path *.*
-
handle_path /files {
-
redir * /files/
+
handle_path /* {
+
header @file +Content-Disposition attachment
}
+
}
+
'') cfg.exposeFolders;
+
in string.concatMapStringsSep "\n\n" configs;
+
in {
+
options.modules.server.caddy = {
+
enable = mkOption {
+
default = false;
+
example = true;
+
description = "Whether to enable Caddy.";
+
type = types.bool;
+
};
-
handle_path /files/* {
-
file_server {
-
browse
-
root /share/files
-
hide .*
-
}
+
exposeFolders = mkOption {
+
default = {};
+
description = "Folders to expose via Cadddy.";
+
type = types.nullOr types.attrsOf exposeType;
+
};
+
};
-
@file path *.*
+
config = mkIf cfg.enable && cfgRoot.enable {
+
services.tailscale = mkIf tailscaleEnabled {
+
permitCertUid = config.services.caddy.user;
+
};
-
handle_path /* {
-
header @file +Content-Disposition attachment
-
}
+
services.caddy = {
+
enable = true;
+
email = "phil@kitten.sh";
+
extraConfig = ''
+
(network_paths) {
+
${vaultwardenHandlerConfig}
+
${jellyfinHandlerConfig}
+
${hassHandlerConfig}
+
${exposeConfig}
}
-
}
-
cola.fable-pancake.ts.net {
-
import network_paths
-
}
+
${tailscaleConfig}
-
:80 {
-
import network_paths
-
}
-
'';
+
:80 {
+
import network_paths
+
}
+
'';
+
};
};
}
+15 -2
modules/server/default.nix
···
-
{ helpers, ... }:
+
{ lib, helpers, ... }:
+
+
with lib; {
+
options.modules.server = {
+
enable = mkOption {
+
default = false;
+
example = true;
+
description = "Whether to enable Server options.";
+
type = types.bool;
+
};
+
};
-
helpers.linuxAttrs {
+
config.modules.server = {
+
enable = if helpers.isLinux then (mkDefault false) else (mkForce false);
+
};
+
} // helpers.linuxAttrs {
imports = [
./sshd.nix
./tailscale.nix
+24 -9
modules/server/hd-idle.nix
···
-
{ pkgs, ... }:
+
{ lib, config, pkgs, ... }:
+
+
with lib;
+
let
+
cfgRoot = config.modules.server;
+
cfg = config.modules.server.sshd;
+
in {
+
options.modules.server.hdIdle = {
+
enable = mkOption {
+
default = false;
+
example = true;
+
description = "Whether to enable hard-drive idling.";
+
type = types.bool;
+
};
+
};
-
{
-
systemd.services.hd-idle = {
-
description = "External HD spin down daemon";
-
wantedBy = [ "multi-user.target" ];
-
serviceConfig = {
-
Type = "simple";
-
Restart = "always";
-
ExecStart = "${pkgs.hd-idle}/bin/hd-idle -i 600";
+
config = mkIf cfg.enable && cfgRoot.enable {
+
systemd.services.hd-idle = {
+
description = "External HD spin down daemon";
+
wantedBy = [ "multi-user.target" ];
+
serviceConfig = {
+
Type = "simple";
+
Restart = "always";
+
ExecStart = "${pkgs.hd-idle}/bin/hd-idle -i 600";
+
};
};
};
}
+69 -41
modules/server/home-assistant.nix
···
-
{ config, ... }:
+
{ lib, config, pkgs, ... }:
-
{
-
users = {
-
groups.hass.gid = config.ids.gids.hass;
-
users.hass = {
-
uid = config.ids.uids.hass;
-
group = "hass";
+
with lib;
+
let
+
inherit (pkgs) stdenv;
+
+
cfgRoot = config.modules.server;
+
cfg = config.modules.server.home-assistant;
+
+
containerImage = if stdenv.isAarch64
+
then "ghcr.io/home-assistant/home-assistant:${cfg.revision}"
+
else "ghcr.io/home-assistant/aarch64-home-assistant:${cfg.revision}";
+
in {
+
options.modules.server.home-assistant = {
+
enable = mkOption {
+
default = false;
+
example = true;
+
description = "Whether to enable Home Assistant.";
+
type = types.bool;
+
};
+
+
revision = mkOption {
+
default = "2024.9.2";
+
example = "2024.9.2";
+
description = "Home Assistant Revision";
+
type = types.str;
};
};
-
virtualisation.oci-containers = {
-
containers.hass = rec {
-
autoStart = true;
-
volumes = [
-
"/var/lib/home-assistant:/config"
-
"/etc/localtime:/etc/localtime:ro"
-
"/sys:/sys:ro"
-
];
-
user = "${environment.PUID}:${environment.PGID}";
-
environment = {
-
TZ = "Europe/London";
-
PUID = "${toString config.ids.uids.hass}";
-
PGID = "${toString config.ids.gids.hass}";
-
UMASK = "007";
+
config = mkIf cfg.enable && cfgRoot.enable {
+
users = {
+
groups.hass.gid = config.ids.gids.hass;
+
users.hass = {
+
uid = config.ids.uids.hass;
+
group = "hass";
};
-
image = "ghcr.io/home-assistant/home-assistant:stable";
-
extraOptions = [
-
"--cap-drop=ALL"
-
"--cap-add=CHOWN"
-
"--cap-add=DAC_OVERRIDE"
-
"--cap-add=FSETID"
-
"--cap-add=FOWNER"
-
"--cap-add=SETGID"
-
"--cap-add=SETUID"
-
"--cap-add=SYS_CHROOT"
-
"--cap-add=KILL"
-
"--cap-add=NET_RAW"
-
"--cap-add=NET_ADMIN"
-
"--security-opt=no-new-privileges"
-
"--userns=keep-id"
-
"--hostuser=hass"
-
"--runtime=runc"
-
"--device=/dev/ttyUSB0"
-
"--network=host"
-
];
+
};
+
+
virtualisation.oci-containers = {
+
containers.hass = rec {
+
autoStart = true;
+
volumes = [
+
"/var/lib/home-assistant:/config"
+
"/etc/localtime:/etc/localtime:ro"
+
"/sys:/sys:ro"
+
];
+
user = "${environment.PUID}:${environment.PGID}";
+
environment = {
+
TZ = "Europe/London";
+
PUID = "${toString config.ids.uids.hass}";
+
PGID = "${toString config.ids.gids.hass}";
+
UMASK = "007";
+
};
+
image = containerImage;
+
extraOptions = [
+
"--cap-drop=ALL"
+
"--cap-add=CHOWN"
+
"--cap-add=DAC_OVERRIDE"
+
"--cap-add=FSETID"
+
"--cap-add=FOWNER"
+
"--cap-add=SETGID"
+
"--cap-add=SETUID"
+
"--cap-add=SYS_CHROOT"
+
"--cap-add=KILL"
+
"--cap-add=NET_RAW"
+
"--cap-add=NET_ADMIN"
+
"--security-opt=no-new-privileges"
+
"--userns=keep-id"
+
"--hostuser=hass"
+
"--runtime=runc"
+
"--device=/dev/ttyUSB0"
+
"--network=host"
+
];
+
};
};
};
}
+60 -43
modules/server/jellyfin.nix
···
-
{ lib, pkgs, user, ... }:
+
{ lib, config, pkgs, user, ... }:
+
with lib;
let
+
cfgRoot = config.modules.server;
+
cfg = config.modules.server.jellyfin;
+
group = "share";
in {
-
hardware.graphics.enable = lib.mkDefault true;
+
options.modules.server.jellyfin = {
+
enable = mkOption {
+
default = false;
+
example = true;
+
description = "Whether to enable Jellyfin server.";
+
type = types.bool;
+
};
-
age.secrets."rclone.conf" = {
-
symlink = true;
-
path = "/run/secrets/rclone.conf";
-
file = ./encrypt/rclone.conf.age;
-
owner = "jellyfin";
-
group = "${group}";
+
sync = mkEnableOption "Whether to sync files from remotes";
};
-
users.users."${user}".extraGroups = [ "${group}" ];
+
config = mkIf cfg.enable && cfgRoot.enable {
+
hardware.graphics.enable = mkDefault true;
-
users.groups.share.gid = 1001;
+
users.users."${user}".extraGroups = [ "${group}" ];
-
services.jellyfin = {
-
enable = true;
-
openFirewall = false;
-
group = "${group}";
-
};
+
users.groups.share.gid = 1001;
-
systemd.services."rclone-sync@" = {
-
wants = [ "network-online.target" ];
-
description = "Sync files between different remotes via rclone";
-
stopIfChanged = false;
-
serviceConfig = {
-
Type = "simple";
-
User = "jellyfin";
-
Group = "${group}";
-
ExecStart = ''
-
${pkgs.rclone}/bin/rclone \
-
--config /run/secrets/rclone.conf \
-
-P copy \
-
-u \
-
--max-age 24h \
-
--multi-thread-streams 0 \
-
putio:%I /share/media/%I
-
'';
+
services.jellyfin = {
+
enable = true;
+
openFirewall = false;
+
group = "${group}";
+
};
+
} // mkIf cfg.sync {
+
age.secrets."rclone.conf" = {
+
symlink = true;
+
path = "/run/secrets/rclone.conf";
+
file = ./encrypt/rclone.conf.age;
+
owner = "jellyfin";
+
group = "${group}";
+
};
+
+
systemd.services."rclone-sync@" = {
+
wants = [ "network-online.target" ];
+
description = "Sync files between different remotes via rclone";
+
stopIfChanged = false;
+
serviceConfig = {
+
Type = "simple";
+
User = "jellyfin";
+
Group = "${group}";
+
ExecStart = ''
+
${pkgs.rclone}/bin/rclone \
+
--config /run/secrets/rclone.conf \
+
-P copy \
+
-u \
+
--max-age 24h \
+
--multi-thread-streams 0 \
+
putio:%I /share/media/%I
+
'';
+
};
};
-
};
-
systemd.timers."rclone-sync@" = {
-
description = "Sync files between different remotes via rclone periodically";
-
timerConfig = {
-
OnBootSec = "15min";
-
OnUnitActiveSec="8h";
-
Persistent = true;
+
systemd.timers."rclone-sync@" = {
+
description = "Sync files between different remotes via rclone periodically";
+
timerConfig = {
+
OnBootSec = "15min";
+
OnUnitActiveSec="8h";
+
Persistent = true;
+
};
};
-
};
-
systemd.targets.rclone-sync = {
-
wantedBy = [ "multi-user.target" ];
-
wants = [ "rclone-sync@movies.timer" "rclone-sync@series.timer" ];
+
systemd.targets.rclone-sync = {
+
wantedBy = [ "multi-user.target" ];
+
wants = [ "rclone-sync@movies.timer" "rclone-sync@series.timer" ];
+
};
};
}
+25 -8
modules/server/podman.nix
···
-
{ ... }:
+
{ lib, config, ... }:
-
{
-
networking.firewall.trustedInterfaces = [ "podman0" ];
+
with lib;
+
let
+
cfgRoot = config.modules.server;
+
cfg = config.modules.server.podman;
+
in {
+
options.modules.server.podman = {
+
enable = mkOption {
+
default = false;
+
example = true;
+
description = "Whether to enable Podman.";
+
type = types.bool;
+
};
+
};
-
virtualisation.podman = {
-
enable = true;
-
autoPrune.enable = true;
-
defaultNetwork.settings = {
-
dns_enabled = true;
+
config = mkIf cfg.enable && cfgRoot.enable {
+
networking.firewall.trustedInterfaces = [ "podman0" ];
+
virtualisation = {
+
oci-containers.backend = "podman";
+
podman = {
+
enable = true;
+
autoPrune.enable = true;
+
defaultNetwork.settings = {
+
dns_enabled = true;
+
};
+
};
};
};
}
+23 -8
modules/server/sshd.nix
···
-
{ user, ... }:
+
{ lib, config, user, ... }:
+
+
with lib;
+
let
+
cfgRoot = config.modules.server;
+
cfg = config.modules.server.sshd;
+
in {
+
options.modules.server.sshd = {
+
enable = mkOption {
+
default = false;
+
example = true;
+
description = "Whether to enable SSH server.";
+
type = types.bool;
+
};
+
};
-
{
-
users.users."${user}".openssh.authorizedKeys.keys = [
-
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDZVcY+kkbEtIiYjSyIMeIJNZjUpK+kgpMQEIOqtG5GIkTV5rb9DOoruAYy1/2HPpAaDUl7ISorBc4g0v/98cEaF04PIOWpm+HctLhPNyI0f7TClQIFNU8PLO5bMzAvIdJQmJavd42cVZmz44N8C12nL3mzCIaLGsVW/iAc2H2viHoOT3ZYxhq1f0kaDhLYjaserNgLqX12E3q5f3z1HkAg2ivRt5NHs4t4N5L6dqS/GnLAaK9rT1yCuIPQT4+XvKycaos/dMLWSPzz3ROV9mATg2uzQx9DiQd7s0pQ4UjUNL/XHlVj0TnQAS6fioVlkfb6dAxzIm9D+O4NI6b2m23Jo2XXoChKkRtVbBX/bJH8YZS2QdIlwlm57yyEbipCFjha8/GH2LUSqEkAZpbDFkIl77aSDX/D+l5svXIZke3PUmL9VX31UglP6/1hqFjMNvZHMbf+bjpjw2UILPph3QogMw8LeSfndFDDtkCDuP25MyjWi4h2QGVc8ibtQnDu3Lj8HhdQ2dOXPuHgMnty9YZXWfGaStIIsS26ZiXbkvRG5e8rlIXQbz8V1aS9851ODOeoXAU87aAG8MKiWJgtrcJRtBcZJHTZHk/I/fSKsyARWz8xtfrIOsCLSWWiY0lpCUYTCrZ4uh9jFEkYda9S8efh7QmOLXraqn6Gw+psKiU9Fw=="
-
];
+
config = mkIf cfg.enable && cfgRoot.enable {
+
users.users."${user}".openssh.authorizedKeys.keys = [
+
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDZVcY+kkbEtIiYjSyIMeIJNZjUpK+kgpMQEIOqtG5GIkTV5rb9DOoruAYy1/2HPpAaDUl7ISorBc4g0v/98cEaF04PIOWpm+HctLhPNyI0f7TClQIFNU8PLO5bMzAvIdJQmJavd42cVZmz44N8C12nL3mzCIaLGsVW/iAc2H2viHoOT3ZYxhq1f0kaDhLYjaserNgLqX12E3q5f3z1HkAg2ivRt5NHs4t4N5L6dqS/GnLAaK9rT1yCuIPQT4+XvKycaos/dMLWSPzz3ROV9mATg2uzQx9DiQd7s0pQ4UjUNL/XHlVj0TnQAS6fioVlkfb6dAxzIm9D+O4NI6b2m23Jo2XXoChKkRtVbBX/bJH8YZS2QdIlwlm57yyEbipCFjha8/GH2LUSqEkAZpbDFkIl77aSDX/D+l5svXIZke3PUmL9VX31UglP6/1hqFjMNvZHMbf+bjpjw2UILPph3QogMw8LeSfndFDDtkCDuP25MyjWi4h2QGVc8ibtQnDu3Lj8HhdQ2dOXPuHgMnty9YZXWfGaStIIsS26ZiXbkvRG5e8rlIXQbz8V1aS9851ODOeoXAU87aAG8MKiWJgtrcJRtBcZJHTZHk/I/fSKsyARWz8xtfrIOsCLSWWiY0lpCUYTCrZ4uh9jFEkYda9S8efh7QmOLXraqn6Gw+psKiU9Fw=="
+
];
-
services.openssh = {
-
enable = true;
-
openFirewall = false;
+
services.openssh = {
+
enable = true;
+
openFirewall = false;
+
};
};
}
+39 -19
modules/server/tailscale.nix
···
-
{ user, ... }:
+
{ lib, config, hostname, ... }:
-
{
-
networking = {
-
domain = "fable-pancake.ts.net";
-
firewall.trustedInterfaces = [ "tailscale0" ];
-
hosts."10.0.0.1" = [ "cola.fable-pancake.ts.net" "cola" ];
-
};
+
with lib;
+
let
+
cfgRoot = config.modules.server;
+
cfg = config.modules.server.tailscale;
+
in {
+
options.modules.server.tailscale = {
+
enable = mkOption {
+
default = false;
+
example = true;
+
description = "Whether to enable Tailscale.";
+
type = types.bool;
+
};
-
age.secrets."tailscale" = {
-
symlink = true;
-
path = "/run/secrets/tailscale";
-
file = ./encrypt/tailscale.age;
+
authKeySecret = {
+
description = "Age Secret of auth keyfile for Tailscale.";
+
type = types.path;
+
};
};
-
services.tailscale = {
-
enable = true;
-
useRoutingFeatures = "both";
-
extraUpFlags = [ "--advertise-exit-node" "--ssh" ];
-
extraDaemonFlags = [ "--no-logs-no-support" ];
-
authKeyFile = "/run/secrets/tailscale";
-
};
+
config = mkIf cfg.enable && cfgRoot.enable {
+
networking = {
+
domain = "fable-pancake.ts.net";
+
firewall.trustedInterfaces = [ "tailscale0" ];
+
hosts."10.0.0.1" = [ "${hostname}.fable-pancake.ts.net" hostname ];
+
};
+
+
age.secrets."tailscale" = {
+
symlink = true;
+
path = "/run/secrets/tailscale";
+
file = cfg.authKeySecret;
+
};
+
+
services.tailscale = {
+
enable = true;
+
useRoutingFeatures = "both";
+
extraUpFlags = [ "--advertise-exit-node" "--ssh" ];
+
extraDaemonFlags = [ "--no-logs-no-support" ];
+
authKeyFile = "/run/secrets/tailscale";
+
};
-
systemd.services.tailscaled.serviceConfig.Environment = [ "TS_DEBUG_DISABLE_PORTLIST=true" ];
+
systemd.services.tailscaled.serviceConfig.Environment = [ "TS_DEBUG_DISABLE_PORTLIST=true" ];
+
};
}
+31 -16
modules/server/vaultwarden.nix
···
-
{ ... }:
+
{ lib, config, hostname, ... }:
-
{
-
services.vaultwarden = {
-
enable = true;
-
dbBackend = "sqlite";
-
config = {
-
IP_HEADER = "X-Real-IP";
-
ADMIN_TOKEN = "$argon2id$v=19$m=65540,t=3,p=4$+5A5H6YiN6OxyrFggkrft8Mm+sxgh/tL3USbaYFZ/h8$qj8NjE+COL4WXjmjkPWSQk7iLfhaBfBtV6k06Bql3CQ";
-
PASSWORD_HINTS_ALLOWED = "false";
-
SIGNUPS_ALLOWED = "false";
-
DOMAIN = "https://cola.fable-pancake.ts.net";
-
WEBSOCKET_ADDRESS = "127.0.0.1";
-
ROCKET_ADDRESS = "127.0.0.1";
-
WEBSOCKET_PORT = "8001";
-
ROCKET_PORT = "8000";
-
ROCKET_LIMITS = "{json=10485760}";
+
with lib;
+
let
+
cfgRoot = config.modules.server;
+
cfg = config.modules.server.vaultwarden;
+
in {
+
options.modules.server.vaultwarden = {
+
enable = mkOption {
+
default = false;
+
example = true;
+
description = "Whether to enable Vaultwarden.";
+
type = types.bool;
+
};
+
};
+
+
config = mkIf cfg.enable && cfgRoot.enable {
+
services.vaultwarden = {
+
enable = true;
+
dbBackend = "sqlite";
+
config = {
+
IP_HEADER = "X-Real-IP";
+
ADMIN_TOKEN = "$argon2id$v=19$m=65540,t=3,p=4$+5A5H6YiN6OxyrFggkrft8Mm+sxgh/tL3USbaYFZ/h8$qj8NjE+COL4WXjmjkPWSQk7iLfhaBfBtV6k06Bql3CQ";
+
PASSWORD_HINTS_ALLOWED = "false";
+
SIGNUPS_ALLOWED = "false";
+
DOMAIN = "https://${hostname}.fable-pancake.ts.net";
+
WEBSOCKET_ADDRESS = "127.0.0.1";
+
ROCKET_ADDRESS = "127.0.0.1";
+
WEBSOCKET_PORT = "8001";
+
ROCKET_PORT = "8000";
+
ROCKET_LIMITS = "{json=10485760}";
+
};
};
};
}
+5 -5
secrets.nix
···
{
"./modules/base/encrypt/nix-access-tokens.conf.age".publicKeys = keys;
-
"./modules/desktop/fonts/encrypt/DankMono-Regular.otf.age".publicKeys = keys;
-
"./modules/desktop/fonts/encrypt/DankMono-Bold.otf.age".publicKeys = keys;
-
"./modules/desktop/fonts/encrypt/DankMono-Italic.otf.age".publicKeys = keys;
-
"./modules/desktop/fonts/encrypt/codicon.otf.age".publicKeys = keys;
-
"./modules/desktop/fonts/encrypt/faicon.ttf.age".publicKeys = keys;
+
"./modules/fonts/encrypt/DankMono-Regular.otf.age".publicKeys = keys;
+
"./modules/fonts/encrypt/DankMono-Bold.otf.age".publicKeys = keys;
+
"./modules/fonts/encrypt/DankMono-Italic.otf.age".publicKeys = keys;
+
"./modules/fonts/encrypt/codicon.otf.age".publicKeys = keys;
+
"./modules/fonts/encrypt/faicon.ttf.age".publicKeys = keys;
"./modules/server/encrypt/tailscale.age".publicKeys = keys;
"./modules/server/encrypt/rclone.conf.age".publicKeys = keys;