Compare commits

...

No commits in common. "cef3a949feae5ef6e87dee00cf44def0b2dd2ed7" and "2c195bf8077c90b2330ba6d43e3de1b5048bfa92" have entirely different histories.

35 changed files with 1129 additions and 301 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.idea

10
.sops.yaml Normal file
View file

@ -0,0 +1,10 @@
keys:
- &tdpeuter_Tibo-NixFatDesk age1fva6s64s884z0q2w7de024sp69ucvqu0pg9shrhhqsn3ewlpjfpsh6md7y
- &tdpeuter_Tibo-NixTop age1qzutny0mqpcccqw6myyfntu6wcskruu9ghzvt6r4te7afkqwnguq05ex37
creation_rules:
- path_regex: secrets/[^/]+\.(yaml|json|env|ini)$
key_groups:
- age:
- *tdpeuter_Tibo-NixFatDesk
- *tdpeuter_Tibo-NixTop

39
flake.lock generated
View file

@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1730785428,
"narHash": "sha256-Zwl8YgTVJTEum+L+0zVAWvXAGbWAuXHax3KzuejaDyo=",
"lastModified": 1759381078,
"narHash": "sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4aa36568d413aca0ea84a1684d2d46f55dbabad7",
"rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee",
"type": "github"
},
"original": {
@ -37,9 +37,30 @@
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"sops-nix": "sops-nix",
"utils": "utils"
}
},
"sops-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1759188042,
"narHash": "sha256-f9QC2KKiNReZDG2yyKAtDZh0rSK2Xp1wkPzKbHeQVRU=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "9fcfabe085281dd793589bdc770a2e577a3caa5d",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "sops-nix",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
@ -62,11 +83,11 @@
]
},
"locked": {
"lastModified": 1722363685,
"narHash": "sha256-XCf2PIAT6lH7BwytgioPmVf/wkzXjSKScC4KzcZgb64=",
"lastModified": 1738591040,
"narHash": "sha256-4WNeriUToshQ/L5J+dTSWC5OJIwT39SEP7V7oylndi8=",
"owner": "gytis-ivaskevicius",
"repo": "flake-utils-plus",
"rev": "6b10f51ff73a66bb29f3bc8151a59d217713f496",
"rev": "afcb15b845e74ac5e998358709b2b5fe42a948d1",
"type": "github"
},
"original": {

View file

@ -5,6 +5,10 @@
nixpkgs.url = "nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
utils = {
url = "github:gytis-ivaskevicius/flake-utils-plus";
inputs.flake-utils.follows = "flake-utils";
@ -13,11 +17,11 @@
outputs = inputs@{
self, nixpkgs,
flake-utils, utils,
flake-utils, sops-nix, utils,
...
}:
let
system = "x86_64-linux";
system = utils.lib.system.x86_64-linux;
in
utils.lib.mkFlake {
inherit self inputs;
@ -28,20 +32,30 @@
modules = [
./modules
./users
sops-nix.nixosModules.sops
];
};
hosts = {
# Physical hosts
Niko.modules = [ ./hosts/Niko ];
# Virtual machines
# Single-service
Ingress.modules = [ ./hosts/Ingress ];
Gitea.modules = [ ./hosts/Gitea ];
Vaultwarden.modules = [ ./hosts/Vaultwarden ];
# Production multi-service
Binnenpost.modules = [ ./hosts/Binnenpost ];
Production.modules = [ ./hosts/Production ];
ProductionGPU.modules = [ ./hosts/ProductionGPU ];
ProductionArr.modules = [ ./hosts/ProductionArr ];
ACE.modules = [ ./hosts/ACE ];
# Others
Template.modules = [ ./hosts/Template ];
Development.modules = [ ./hosts/Development ];
Testing.modules = [ ./hosts/Testing ];

View file

@ -16,6 +16,7 @@
apps = {
speedtest.enable = true;
technitiumDNS.enable = true;
traefik.enable = true;
};
virtualisation.guest.enable = true;
};
@ -76,6 +77,14 @@
};
};
virtualisation.oci-containers.containers.traefik.labels = {
"traefik.http.routers.roxanne.rule" = "Host(`roxanne.depeuter.dev`)";
"traefik.http.services.roxanne.loadbalancer.server.url" = "https://192.168.0.13:8006";
"traefik.http.routers.hugo.rule" = "Host(`hugo.depeuter.dev`)";
"traefik.http.services.hugo.loadbalancer.server.url" = "https://192.168.0.11:444";
};
system.stateVersion = "24.05";
};
}

View file

@ -3,8 +3,10 @@
{
config = {
homelab = {
apps.arr = {
qbittorrent.enable = true;
apps = {
bind9.enable = true;
traefik.enable = true;
plex.enable = true;
};
virtualisation.guest.enable = true;
};

View file

@ -59,6 +59,7 @@ prefixLength = 24;
};
"cloud.depeuter.dev" = { };
"git.depeuter.dev" = { };
"home.depeuter.dev" = { };
"jelly.depeuter.dev" = { };
"vault.depeuter.dev" = { };
};
@ -136,10 +137,27 @@ prefixLength = 24;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
'';
};
"calendar.depeuter.dev".locations."/".return = "301 https://cloud.depeuter.dev/apps/calendar";
"calendar.depeuter.dev" = {
useACMEHost = "depeuter.dev";
locations."/".return = "301 https://cloud.depeuter.dev/apps/calendar";
};
"tasks.depeuter.dev".locations."/".return = "301 https://cloud.depeuter.dev/apps/tasks";
"notes.depeuter.dev".locations."/".return = "301 https://cloud.depeuter.dev/apps/notes";
"home.depeuter.dev" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://192.168.0.21:8123";
extraConfig = ''
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
'';
};
};
"jelly.depeuter.dev" = {
enableACME = true;
forceSSL = true;
@ -176,7 +194,7 @@ prefixLength = 24;
};
};
extraConfig = ''
client_max_body_size 20M;
client_max_body_size 512M;
# Security / XSS Mitigation Headers
# NOTE: X-Frame-Options may cause issues with the webOS app
@ -206,7 +224,7 @@ prefixLength = 24;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 512M;
client_max_body_size 10G;
keepalive_timeout 600s;
proxy_buffers 4 256k; # Number and size of buffers for reading response
proxy_buffer_size 256k; # Buffer for the first part of the response
@ -220,10 +238,18 @@ prefixLength = 24;
enableACME = true;
forceSSL = true;
locations = {
"/".proxyPass = "http://192.168.0.22:10102";
"/" = {
proxyPass = "http://192.168.0.22:10102";
proxyWebSockets = true;
};
"~ ^/admin".return = 403;
};
};
"rss.depeuter.dev" = {
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://192.168.92:${toString config.homelab.apps.freshrss.port}";
};
};
};
};

View file

@ -7,7 +7,10 @@
];
homelab = {
apps.technitiumDNS.enable = true;
apps = {
technitiumDNS.enable = true;
traefik.enable = true;
};
users.deploy.enable = true;
};
@ -34,12 +37,11 @@
hardware = {
enableRedistributableFirmware = true;
enableAllFirmware = true;
pulseaudio.enable = true;
opengl.enable = true;
graphics.enable = true;
};
# Select internationalisation properties.
i18n.defaultLocale = "en_GB.utf8";
i18n.defaultLocale = "en_GB.UTF-8";
networking = {
hostName = "Niko";
@ -79,6 +81,8 @@
user = config.users.users.jellyfin-mpv-shim.name;
};
pulseaudio.enable = true;
tailscale = {
enable = true;
useRoutingFeatures = "server";
@ -94,8 +98,6 @@
# resolved.enable = true;
};
sound.enable = true;
# Define a user account. Don't forget to set a password with 'passwd'.
users.users.jellyfin-mpv-shim = {
description = "Jellyfin MPV Shim User";
@ -114,67 +116,4 @@
systemd.services."cage-tty1".serviceConfig.Restart = "always";
system.stateVersion = "24.05";
virtualisation = {
# Enable Android emulator
# waydroid.enable = true;
docker = {
enable = true;
autoPrune.enable = true;
};
oci-containers = {
backend = "docker";
containers = {
reverse-proxy = {
hostname = "traefik";
image = "traefik:v3.0";
cmd = [
"--api.insecure=true"
# Add Docker provider
"--providers.docker=true"
"--providers.docker.exposedByDefault=false"
# Add web entrypoint
"--entrypoints.web.address=:80/tcp"
"--entrypoints.web.http.redirections.entrypoint.to=websecure"
"--entrypoints.web.http.redirections.entrypoint.scheme=https"
# Add websecure entrypoint
"--entrypoints.websecure.address=:443/tcp"
"--entrypoints.websecure.http.tls=true"
"--entrypoints.websecure.http.tls.certResolver=letsencrypt"
"--entrypoints.websecure.http.tls.domains[0].main=depeuter.dev"
"--entrypoints.websecure.http.tls.domains[0].sans=*.depeuter.dev"
"--entrypoints.websecure.http.tls.domains[1].sans=*.niko.depeuter.dev"
# Certificates
"--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
"--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"
"--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be"
"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
];
ports = [
"80:80/tcp"
"443:443/tcp"
# "8080:8080/tcp" # The Web UI (enabled by --api.insecure=true)
];
environment = {
# TODO Hide this!
"CLOUDFLARE_DNS_API_TOKEN" = "6Vz64Op_a6Ls1ljGeBxFoOVfQ-yB-svRbf6OyPv2";
};
environmentFiles = [
];
volumes = [
"/var/run/docker.sock:/var/run/docker.sock:ro" # So that Traefik can listen to the Docker events
"letsencrypt:/letsencrypt"
];
labels = {
"traefik.enable" = "true";
"traefik.http.routers.traefik.rule" = "Host(`traefik.niko.depeuter.dev`)";
"traefik.http.services.traefik.loadbalancer.server.port" = "8080";
};
autoStart = true;
};
};
};
};
}

View file

@ -0,0 +1,48 @@
{ config, pkgs, lib, system, ... }:
{
config = {
homelab = {
apps = {
calibre.enable = true;
traefik.enable = true;
};
virtualisation.guest.enable = true;
};
networking = {
hostId = "aaaa2100";
domain = "roxanne.depeuter.dev";
useDHCP = false;
enableIPv6 = true;
defaultGateway = {
address = "192.168.0.1";
interface = "ens18";
};
# Open ports in the firewall.
firewall = {
enable = true;
};
interfaces.ens18 = {
ipv4.addresses = [
{
address = "192.168.0.31";
prefixLength = 24;
}
];
};
nameservers = [
"1.1.1.1" # Cloudflare
"1.0.0.1" # Cloudflare
];
};
system.stateVersion = "24.05";
};
}

View file

@ -0,0 +1,48 @@
{ config, pkgs, lib, system, ... }:
{
config = {
homelab = {
apps = {
arr.enable = true;
traefik.enable = true;
};
virtualisation.guest.enable = true;
};
networking = {
hostId = "aaaa2300";
domain = "roxanne.depeuter.dev";
useDHCP = false;
enableIPv6 = true;
defaultGateway = {
address = "192.168.0.1";
interface = "ens18";
};
# Open ports in the firewall.
firewall = {
enable = true;
};
interfaces.ens18 = {
ipv4.addresses = [
{
address = "192.168.0.33";
prefixLength = 24;
}
];
};
nameservers = [
"1.1.1.1" # Cloudflare
"1.0.0.1" # Cloudflare
];
};
system.stateVersion = "24.05";
};
}

View file

@ -17,7 +17,7 @@
defaultGateway = {
address = "192.168.0.1";
interface = "enp6s18";
interface = "ens18";
};
# Open ports in the firewall.
@ -25,7 +25,7 @@
enable = true;
};
interfaces.enp6s18 = {
interfaces.ens18 = {
ipv4.addresses = [
{
address = "192.168.0.94";
@ -40,7 +40,7 @@
];
};
system.stateVersion = "unstable";
system.stateVersion = "24.11";
### Nvidia GPU support ###
@ -64,7 +64,7 @@
};
hardware = {
opengl = {
graphics = {
enable = true;
# driSupport = true;
# driSupport32Bit = true;

View file

@ -3,10 +3,11 @@
{
config = {
homelab = {
virtualisation = {
containers.enable = true;
guest.enable = true;
apps = {
freshrss.enable = true;
traefik.enable = true;
};
virtualisation.guest.enable = true;
};
networking = {

View file

@ -3,7 +3,11 @@
{
config = {
homelab = {
apps.vaultwarden.enable = true;
apps.vaultwarden = {
enable = true;
domain = "https://vault.depeuter.dev";
name = "Hugo's Vault";
};
virtualisation.guest.enable = true;
};

View file

@ -4,61 +4,86 @@ let
cfg = config.homelab.apps.arr;
networkName = "arrStack";
appNames = [ "bazarr" "lidarr" "prowlarr" "qbittorrent" "radarr" "sonarr" ];
proxyNet = config.homelab.apps.traefik.sharedNetworkName;
appNames = [ "bazarr" "prowlarr" "qbittorrent" "radarr" "sonarr" ];
inUse = builtins.any (app: cfg.${app}.enable) appNames;
PGID = toString config.users.groups.media.gid;
UMASK = "002";
in {
options.homelab.apps.arr = {
options.homelab.apps.arr = let
mkAppOption = appName: {
enable = lib.mkEnableOption "${appName} using Docker";
exposePorts = lib.mkOption {
type = lib.types.bool;
description = "Expose ${appName} port";
default = cfg.exposePorts;
};
};
in {
enable = lib.mkEnableOption "Arr Stack using Docker";
exposePorts = lib.mkOption {
type = lib.types.bool;
description = "Expose all app ports";
# Only expose ports by default if Traefik is not in use.
default = ! config.homelab.apps.traefik.enable;
};
bazarr.enable = lib.mkEnableOption "Bazarr using Docker";
lidarr.enable = lib.mkEnableOption "Lidarr using Docker";
prowlarr.enable = lib.mkEnableOption "Prowlarr using Docker";
qbittorrent.enable = lib.mkEnableOption "qBittorrent using Docker";
radarr.enable = lib.mkEnableOption "Radarr using Docker";
sonarr.enable = lib.mkEnableOption "Sonarr using Docker";
bazarr = mkAppOption "Bazarr";
prowlarr = mkAppOption "Prowlarr";
qbittorrent = mkAppOption "qBittorrent";
radarr = mkAppOption "Radarr";
sonarr = mkAppOption "Sonarr";
};
config = {
homelab = {
users.media.enable = lib.mkIf inUse true;
users = lib.mkIf inUse {
apps.enable = true;
media.enable = true;
};
# "Master switch": Enable all apps.
apps.arr = {
bazarr.enable = lib.mkIf cfg.enable true;
lidarr.enable = lib.mkIf cfg.enable true;
prowlarr.enable = lib.mkIf cfg.enable true;
qbittorrent.enable = lib.mkIf cfg.enable true;
radarr.enable = lib.mkIf cfg.enable true;
sonarr.enable = lib.mkIf cfg.enable true;
};
apps.arr = lib.mkIf cfg.enable {
bazarr.enable = true;
prowlarr.enable = true;
qbittorrent.enable = true;
radarr.enable = true;
sonarr.enable = true;
};
fileSystems = lib.mkIf inUse {
"/srv/video" = {
device = "192.168.0.11:/mnt/SMALL/MEDIA/VIDEO";
fileSystems.media.video = {
enable = true;
permissions = [ "read" "write" ];
};
virtualisation.containers.enable = lib.mkIf inUse true;
};
fileSystems = let
mkFileSystem = device: {
inherit device;
fsType = "nfs";
options = [
"rw"
"auto"
"nfsvers=4.2"
"async" "soft"
"timeo=100" "retry=50" "actimeo=1800" "lookupcache=all"
"nosuid" "tcp"
"rsize=1048576" "wsize=1048576"
"hard"
"timeo=600" "retrans=2"
"_netdev" "nosuid" "tcp"
];
};
"/srv/qbittorrent" = {
device = "192.168.0.11:/mnt/SMALL/CONFIG/QBITTORRENT/qBittorrent";
fsType = "nfs";
options = [
"rw"
"nfsvers=4.2"
"async" "soft"
"nosuid" "tcp"
];
};
hugoBackup = "192.168.0.11:/mnt/BIG/BACKUP";
in lib.mkIf inUse {
"/srv/bazarr-backup" = lib.mkIf cfg.bazarr.enable (mkFileSystem "${hugoBackup}/BAZARR");
"/srv/prowlarr-backup" = lib.mkIf cfg.bazarr.enable (mkFileSystem "${hugoBackup}/PROWLARR");
"/srv/qbittorrent" = lib.mkIf cfg.qbittorrent.enable (mkFileSystem "192.168.0.11:/mnt/SMALL/CONFIG/QBITTORRENT");
"/srv/radarr-backup" = lib.mkIf cfg.radarr.enable (mkFileSystem "${hugoBackup}/RADARR");
"/srv/sonarr-backup" = lib.mkIf cfg.sonarr.enable (mkFileSystem "${hugoBackup}/SONARR");
"/srv/torrent" = mkFileSystem "192.168.0.11:/mnt/SMALL/MEDIA/TORRENT";
};
# Make sure the Docker network exists.
@ -66,7 +91,6 @@ in {
description = "Create Docker network for ${networkName}";
requiredBy = [
"docker-bazarr.service"
"docker-lidarr.service"
"docker-prowlarr.service"
"docker-qbittorrent.service"
"docker-radarr.service"
@ -84,109 +108,77 @@ in {
};
# Create a user for each app.
users.users = {
bazarr = lib.mkIf cfg.bazarr.enable {
uid = lib.mkForce 3003;
users.users = let
mkUser = uid: {
uid = lib.mkForce uid;
isSystemUser = true;
group = config.users.groups.media.name;
home = "/var/empty";
shell = null;
};
lidarr = lib.mkIf cfg.lidarr.enable {
uid = lib.mkForce 3002;
isSystemUser = true;
group = config.users.groups.media.name;
home = "/var/empty";
shell = null;
};
prowlarr = lib.mkIf cfg.prowlarr.enable {
uid = lib.mkForce 3004;
isSystemUser = true;
group = config.users.groups.media.name;
home = "/var/empty";
shell = null;
};
qbittorrent = lib.mkIf cfg.qbittorrent.enable {
uid = lib.mkForce 3005;
isSystemUser = true;
group = config.users.groups.media.name;
home = "/var/empty";
shell = null;
};
radarr = lib.mkIf cfg.radarr.enable {
uid = lib.mkForce 3006;
isSystemUser = true;
group = config.users.groups.media.name;
home = "/var/empty";
shell = null;
};
sonarr = lib.mkIf cfg.sonarr.enable {
uid = lib.mkForce 3007;
isSystemUser = true;
group = config.users.groups.media.name;
home = "/var/empty";
shell = null;
in {
bazarr = lib.mkIf cfg.bazarr.enable (mkUser 3003);
prowlarr = lib.mkIf cfg.prowlarr.enable (mkUser 3004);
qbittorrent = lib.mkIf cfg.qbittorrent.enable (mkUser 3005) // {
extraGroups = [
config.users.groups.apps.name
];
};
radarr = lib.mkIf cfg.radarr.enable (mkUser 3006);
sonarr = lib.mkIf cfg.sonarr.enable (mkUser 3007);
};
virtualisation.oci-containers.containers = {
bazarr = lib.mkIf cfg.bazarr.enable {
virtualisation.oci-containers.containers = let
videoHostPath = config.homelab.fileSystems.media.video.hostPath;
in {
bazarr = let
port = 6767;
in lib.mkIf cfg.bazarr.enable {
hostname = "bazarr";
image = "ghcr.io/hotio/bazarr:release-1.4.4";
image = "ghcr.io/hotio/bazarr:release-1.5.2";
autoStart = true;
ports = [
"6767:6767/tcp"
"6767:6767/udp"
ports = lib.mkIf cfg.bazarr.exposePorts [
"${toString port}:${toString port}/tcp"
"${toString port}:${toString port}/udp"
];
extraOptions = [
"--network=${networkName}"
"--mount" ''type=volume,source=bazarr-backup,target=/backup,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/mnt/BIG/BACKUP/BAZARR,"volume-opt=o=addr=192.168.0.11,rw,nfsvers=4.2,async,nosuid"''
"--network=${proxyNet}"
];
environment = {
PUID = toString config.users.users.bazarr.uid;
inherit PGID UMASK;
TZ = config.time.timeZone;
WEBUI_PORTS = "6767/tcp,6767/udp";
WEBUI_PORTS = "${toString port}/tcp,${toString port}/udp";
};
volumes = [
"bazarr-config:/config"
"/srv/video:/data"
"/srv/bazarr-backup:/config/backup"
"${videoHostPath}/Films:/media/movies"
"${videoHostPath}/Series:/media/series"
];
labels = {
"traefik.enable" = "true";
"traefik.docker.network" = proxyNet;
"traefik.http.routers.bazarr.rule" = "Host(`bazarr.depeuter.dev`)";
"traefik.http.services.bazarr.loadbalancer.server.port" = toString port;
};
};
lidarr = lib.mkIf cfg.lidarr.enable {
hostname = "lidarr";
image = "ghcr.io/hotio/lidarr:release-2.5.3.4341";
autoStart = true;
ports = [
"8686:8686/tcp"
];
extraOptions = [
"--network=${networkName}"
"--mount" ''type=volume,source=lidarr-backup,target=/backup,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/mnt/BIG/BACKUP/LIDARR,"volume-opt=o=addr=192.168.0.11,rw,nfsvers=4.2,async,nosuid"''
];
environment = {
PUID = toString config.users.users.lidarr.uid;
inherit PGID UMASK;
TZ = config.time.timeZone;
};
volumes = [
"lidarr-config:/config"
# TODO "data:/data"
];
};
prowlarr = lib.mkIf cfg.prowlarr.enable {
prowlarr = let
port = 9696;
in lib.mkIf cfg.prowlarr.enable {
hostname = "prowlarr";
image = "ghcr.io/hotio/prowlarr:release-1.23.1.4708";
image = "ghcr.io/hotio/prowlarr:release-2.0.5.5160";
autoStart = true;
ports = [
"9696:9696/tcp"
ports = lib.mkIf cfg.prowlarr.exposePorts [
"${toString port}:${toString port}/tcp"
];
extraOptions = [
"--network=${networkName}"
"--network=${proxyNet}"
];
environment = {
PUID = toString config.users.users.prowlarr.uid;
@ -194,44 +186,63 @@ in {
TZ = config.time.timeZone;
};
volumes = [
# TODO "config:/config"
"prowlarr-config:/config"
"/srv/prowlarr-backup:/config/Backups"
];
labels = {
"traefik.enable" = "true";
"traefik.docker.network" = proxyNet;
"traefik.http.routers.prowlarr.rule" = "Host(`prowlarr.depeuter.dev`)";
"traefik.http.services.prowlarr.loadbalancer.server.port" = toString port;
};
};
qbittorrent = lib.mkIf cfg.qbittorrent.enable {
qbittorrent = let
port = 10095;
in lib.mkIf cfg.qbittorrent.enable {
hostname = "qbittorrent";
image = "ghcr.io/hotio/qbittorrent:release-4.6.7";
image = "ghcr.io/hotio/qbittorrent:release-5.1.2";
autoStart = true;
ports = [
"10095:10095/udp"
"10095:10095/tcp"
ports = lib.mkIf cfg.qbittorrent.exposePorts [
"${toString port}:${toString port}/tcp"
"${toString port}:${toString port}/udp"
];
extraOptions = [
"--network=${networkName}"
"--mount" ''type=volume,source=torrents,target=/data,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/mnt/SMALL/MEDIA/TORRENT,"volume-opt=o=addr=192.168.0.11,rw,nfsvers=4.2,async,nosuid"''
"--network=${proxyNet}"
];
environment = {
PUID = toString config.users.users.qbittorrent.uid;
inherit PGID UMASK;
TZ = config.time.timeZone;
WEBUI_PORTS = "10095/tcp,10095/udp";
WEBUI_PORTS = "${toString port}/tcp,${toString port}/udp";
};
volumes = [
"/srv/qbittorrent:/config/config"
"/srv/video:/media/video"
"/srv/qbittorrent:/config"
"/srv/torrent:/media/cache"
];
labels = {
"traefik.enable" = "true";
"traefik.docker.network" = proxyNet;
"traefik.http.routers.qbittorrent.rule" = "Host(`qb.depeuter.dev`)";
"traefik.http.services.qbittorrent.loadbalancer.server.port" = toString port;
};
};
radarr = lib.mkIf cfg.radarr.enable {
radarr = let
port = 7878;
in lib.mkIf cfg.radarr.enable {
hostname = "radarr";
image = "ghcr.io/hotio/radarr:release-5.9.1.9070";
image = "ghcr.io/hotio/radarr:testing-5.28.0.10205";
autoStart = true;
ports = [
"7878:7878/tcp"
ports = lib.mkIf cfg.radarr.exposePorts [
"${toString port}:${toString port}/tcp"
];
extraOptions = [
"--network=${networkName}"
"--network=${proxyNet}"
];
environment = {
PUID = toString config.users.users.radarr.uid;
@ -239,20 +250,33 @@ in {
TZ = config.time.timeZone;
};
volumes = [
# TODO "config:/config"
# TODO "data:/data"
"radarr-config:/config"
"/srv/radarr-backup:/config/Backups"
"/srv/torrent:/media/cache"
"${videoHostPath}/Films:/media/movies"
];
labels = {
"traefik.enable" = "true";
"traefik.docker.network" = proxyNet;
"traefik.http.routers.radarr.rule" = "Host(`radarr.depeuter.dev`)";
"traefik.http.services.radarr.loadbalancer.server.port" = toString port;
};
};
sonarr = lib.mkIf cfg.sonarr.enable {
sonarr = let
port = 8989;
in lib.mkIf cfg.sonarr.enable {
hostname = "sonarr";
image = "ghcr.io/hotio/sonarr:release-4.0.9.2244";
image = "ghcr.io/hotio/sonarr:release-4.0.15.2941";
autoStart = true;
ports = [
"8989:8989/tcp"
ports = lib.mkIf cfg.sonarr.exposePorts [
"${toString port}:${toString port}/tcp"
];
extraOptions = [
"--network=${networkName}"
"--network=${proxyNet}"
];
environment = {
PUID = toString config.users.users.sonarr.uid;
@ -260,9 +284,19 @@ in {
TZ = config.time.timeZone;
};
volumes = [
# TODO "config:/config"
# TODO "data:/data"
"sonarr-config:/config"
"/srv/sonarr-backup:/config/Backups"
"/srv/torrent:/media/cache"
"${videoHostPath}/Series:/media/series"
];
labels = {
"traefik.enable" = "true";
"traefik.docker.network" = proxyNet;
"traefik.http.routers.sonarr.rule" = "Host(`sonarr.depeuter.dev`)";
"traefik.http.services.sonarr.loadbalancer.server.port" = toString port;
};
};
};
};

View file

@ -0,0 +1,45 @@
$TTL 604800
@ IN SOA ns1 admin (
15 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
; Name servers - NS records
IN NS ns1
; IN NS ns2
ns1 IN A 192.168.0.91
;ns2 IN A 192.158.0.X
; Hostnames
hugo.kmtl IN A 192.168.0.11
ingress.kmtl IN A 192.168.0.10
ingress.kmtl IN AAAA fe80::be24:11ff:fed6:842a
; Core services
cloud IN A 192.168.0.10
git IN A 78.23.37.117
home IN A 192.168.0.10
jelly IN CNAME ingress.kmtl
vault IN A 192.168.0.10
; Production VM
books IN A 192.168.0.31
calibre IN A 192.168.0.31
; Production VM - Arr
bazarr IN A 192.168.0.33
prowlarr IN A 192.168.0.33
qb IN A 192.168.0.33
radarr IN A 192.168.0.33
sonarr IN A 192.168.0.33
; Development VM
plex IN A 192.168.0.91
; Catchalls
*.production IN A 192.168.0.31
*.development IN A 192.168.0.91

View file

@ -0,0 +1,54 @@
{ config, lib, ... }:
let
cfg = config.homelab.apps.bind9;
in {
options.homelab.apps.bind9.enable = lib.mkEnableOption "ISC BIND 9 (Docker)";
config = lib.mkIf cfg.enable {
homelab.virtualisation.containers.enable = true;
environment.etc = {
"bind/named.conf" = {
source = ./named.conf;
mode = "0555";
};
"bind/named.conf.options" = {
source = ./named.conf.options;
mode = "0555";
};
"bind/named.conf.local" = {
source = ./named.conf.local;
mode = "0555";
};
"bind/zones/db.depeuter.dev" = {
source = ./db.depeuter.dev;
mode = "0555";
};
};
virtualisation.oci-containers.containers.bind9 = {
hostname = "bind9";
#image = "internetsystemsconsortium/bind9:9.20"; # Current stable
image = "ubuntu/bind9"; # Current stable
autoStart = true;
ports = [
"53:53/udp"
"53:53/tcp"
"953:953/tcp"
];
extraOptions = [
];
environment = {
};
volumes = [
"/etc/bind:/etc/bind" # For configuration, your `named.conf` lives here
"bind9-cache:/var/cache/bind"
#"...:/var/lib/bind" # Secondary zones
"bind9-logs:/var/log" # Logfiles
];
labels = {
};
};
};
}

View file

@ -0,0 +1,2 @@
include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";

View file

@ -0,0 +1,4 @@
zone "depeuter.dev" {
type primary;
file "/etc/bind/zones/db.depeuter.dev";
};

View file

@ -0,0 +1,35 @@
http local {
endpoints { "/dns-query"; };
};
acl bogusnets {
};
acl trusted {
192.168.0.0/16;
};
options {
directory "/var/cache/bind";
version "not currently available";
listen-on { any; };
listen-on-v6 { any; };
listen-on tls ephemeral { any; };
listen-on-v6 tls ephemeral { any; };
listen-on tls ephemeral http local { any; };
listen-on-v6 tls ephemeral http local { any; };
recursion yes;
forwarders {
9.9.9.9;
149.112.112.112;
};
forward only;
allow-query { any; };
allow-recursion { any; };
allow-transfer { none; };
blackhole { bogusnets; };
};

View file

@ -1,11 +1,39 @@
{ config, lib, ... }:
{ config, lib, pkgs, ... }:
let
cfg = config.homelab.apps.calibre;
in {
options.homelab.apps.calibre.enable = lib.mkEnableOption "Calibre";
config = lib.mkIf cfg.enable {
PUID = toString config.users.users.calibre.uid;
PGID = toString config.users.groups.media.gid;
books = "/srv/books";
calibre-config = "/srv/calibre-config";
calibre-web-config = "/srv/calibre-web-config";
networkName = "calibre";
proxyNet = config.homelab.apps.traefik.sharedNetworkName;
in {
options.homelab.apps.calibre = {
enable = lib.mkEnableOption "Calibre (Desktop + Web)";
desktop.enable = lib.mkEnableOption "Calibre Desktop (KasmVNC)";
web.enable = lib.mkEnableOption "Calibre Web";
};
config = lib.mkMerge [
{
homelab.apps.calibre = lib.mkIf cfg.enable {
desktop.enable = true;
web.enable = true;
};
}
# Common
(lib.mkIf (cfg.desktop.enable || cfg.web.enable) {
homelab = {
users.media.enable = true;
virtualisation.containers.enable = true;
};
users.users.calibre = {
uid = lib.mkForce 3010;
isSystemUser = true;
@ -13,5 +41,149 @@ in {
home = "/var/empty";
shell = null;
};
fileSystems."${books}" = {
device = "192.168.0.11:/mnt/SMALL/MEDIA/BOOKS";
fsType = "nfs";
options = [
"rw"
"auto"
"nfsvers=4.2"
"rsize=1048576" "wsize=1048576"
"soft"
"timeo=600" "retrans=2"
"_netdev" "nosuid" "tcp"
];
};
# Make sure the Docker network exists.
systemd.services."docker-${networkName}-create-network" = {
requiredBy = [
"docker-calibre.service"
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then
${pkgs.docker}/bin/docker network create ${networkName}
fi
'';
};
})
# Calibre desktop
(lib.mkIf cfg.desktop.enable {
fileSystems."${calibre-config}" = {
device = "192.168.0.11:/mnt/SMALL/CONFIG/CALIBRE";
fsType = "nfs";
options = [
"rw"
"auto"
"nfsvers=4.2"
"rsize=1048576" "wsize=1048576"
"soft"
"timeo=600" "retrans=2"
"_netdev" "nosuid" "tcp"
];
};
virtualisation.oci-containers.containers.calibre = let
innerPort = 8080;
in {
hostname = "calibre";
image = "lscr.io/linuxserver/calibre:v8.10.0-ls354";
autoStart = true;
ports = [
# Open ports if you don't use Traefik
"9480:${toString innerPort}" # Calibre desktop GUI
#"9481:8181" # Calibre desktop GUI HTTPS
#"9581:8081" # Calibre webserver gui
];
extraOptions = [
"--network=${networkName}"
"--network=${proxyNet}"
# syscalls are unkown to Docker
#"--security-opt" "seccomp=unconfined"
];
environment = {
inherit PUID PGID;
#UMASK = "022";
TZ = config.time.timeZone;
#PASSWORD = "";
#CLI_ARGS = "";
};
volumes = [
"${calibre-config}:/config"
"${books}:/media/books"
];
labels = {
"traefik.enable" = "true";
"traefik.docker.network" = proxyNet;
"traefik.http.routers.calibre.rule" = "Host(`calibre.depeuter.dev`)";
"traefik.http.services.calibre.loadbalancer.server.port" = toString innerPort;
};
};
})
# Calibre Web
(lib.mkIf cfg.web.enable {
fileSystems."${calibre-web-config}" = {
device = "192.168.0.11:/mnt/SMALL/CONFIG/CALIBRE-WEB";
fsType = "nfs";
options = [
"rw"
"auto"
"nfsvers=4.2"
"rsize=1048576" "wsize=1048576"
"soft"
"timeo=600" "retrans=2"
"_netdev" "nosuid" "tcp"
];
};
virtualisation.oci-containers.containers.calibre-web = let
innerPort = 8083;
in {
hostname = "calibre-web";
image = "lscr.io/linuxserver/calibre-web:0.6.25-ls346";
autoStart = true;
ports = [
# Open ports if you don't use Traefik
"8083:${toString innerPort}" # Web UI
];
extraOptions = [
"--network=${networkName}"
"--network=${proxyNet}"
];
environment = {
inherit PUID PGID;
#UMASK = "022";
TZ = config.time.timeZone;
# (x86-64 only) Adds the ability to perform ebook conversion
DOCKER_MODS = "linuxserver/mods:universal-calibre";
# Allow Google Oauth
#OAUTHLIB_RELAX_TOKEN_SCOPE = "1";
};
volumes = [
"${calibre-web-config}:/config"
"${books}:/media/books"
];
labels = {
"traefik.enable" = "true";
"traefik.docker.network" = proxyNet;
"traefik.http.routers.calibre-web.rule" = "Host(`books.depeuter.dev`)";
"traefik.http.services.calibre-web.loadbalancer.server.port" = toString innerPort;
};
};
})
];
}

View file

@ -0,0 +1,28 @@
{ config, lib, pkgs, ... }:
let
cfg = config.homelab.apps.changedetection;
in {
options.homelab.apps.changedetection.enable = lib.mkEnableOption "Changedetection.io";
config = lib.mkIf cfg.enable {
homelab.virtualisation.containers.enable = true;
virtualisation.oci-containers.containers.changedetection = {
hostname = "changedetection";
image = "ghcr.io/dgtlmoon/changedetection.io";
autoStart = true;
ports = [
"5000:5000/tcp"
];
extraOptions = [
];
volumes = [
"changedetection:/datastore"
];
environment = {
LOGGER_LEVEL = "WARNING";
};
};
};
}

View file

@ -1,12 +1,16 @@
{
imports = [
./arr
./bind9
./calibre
./changedetection
./freshrss
./gitea
./jellyfin
./plex
./speedtest
./technitium-dns
./traefik
./vaultwarden
];
}

View file

@ -0,0 +1,93 @@
{ config, lib, pkgs, ... }:
let
cfg = config.homelab.apps.freshrss;
networkName = "freshrss";
in {
options.homelab.apps.freshrss = {
enable = lib.mkEnableOption "FreshRSS";
port = lib.mkOption {
type = lib.types.int;
default = 9080;
description = "FreshRSS WebUI port";
};
};
config = let
inherit (config.homelab.apps.freshrss) port;
in
lib.mkIf cfg.enable {
homelab.virtualisation.containers.enable = true;
fileSystems."/srv/freshrss" = {
device = "192.168.0.11:/mnt/SMALL/CONFIG/FRESHRSS";
fsType = "nfs";
options = [
"rw"
"auto"
"nfsvers=4.2"
"async" "soft" "timeo=600"
"retrans=2"
"_netdev"
"nosuid"
"tcp"
];
};
systemd.services."docker-${networkName}-create-network" = {
description = "Create Docker network for ${networkName}";
requiredBy = [
"docker-freshrss.service"
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then
${pkgs.docker}/bin/docker network create ${networkName}
fi
'';
};
virtualisation.oci-containers.containers.freshrss = {
hostname = "freshrss";
image = "freshrss/freshrss:1.25.0";
autoStart = true;
user = "0:33";
ports = [
"${toString port}:80/tcp"
];
extraOptions = [
"--network=${networkName}"
];
environment = {
TZ = config.time.timeZone;
CRON_MIN = "3,18,33,48"; # Alternatively, configure cron inside container.
SERVER_DNS = "rss.depeuter.dev";
TRUSTED_PROXY = "172.16.0.1/12 192.168.0.1/16";
};
volumes = [
"/srv/freshrss/www/freshrss/data:/var/www/FreshRSS/data"
"/srv/freshrss/www/freshrss/extensions:/var/www/FreshRSS/extensions"
];
labels = {
"traefik.enable" = "true";
"traefik.http.middlewares.freshrssM1.compress" = "true";
"traefik.http.middlewares.freshrssM2.headers.browserXssFilter" = "true";
"traefik.http.middlewares.freshrssM2.headers.forceSTSHeader" = "true";
"traefik.http.middlewares.freshrssM2.headers.frameDeny" = "true";
"traefik.http.middlewares.freshrssM2.headers.referrerPolicy" = "no-referrer-when-downgrade";
"traefik.http.middlewares.freshrssM2.headers.stsSeconds" = "31536000";
"traefik.http.routers.freshrss.entryPoints" = "websecure";
"traefik.http.routers.freshrss.tls" = "true";
"traefik.http.services.freshrss.loadbalancer.server.port" = "80";
"traefik.http.routers.freshrss.middlewares" = "freshrssM1,freshrssM2";
"traefik.http.routers.freshrss.rule" = "Host(`rss.depeuter.dev`)";
};
};
};
}

View file

@ -124,7 +124,7 @@ in {
gitea = {
hostname = "gitea";
image = "codeberg.org/forgejo/forgejo:8.0.3-rootless";
image = "codeberg.org/forgejo/forgejo:11.0.1-rootless";
autoStart = true;
user = "${toString UID}:${toString GID}";
ports = [
@ -379,6 +379,25 @@ in {
# A relative path is interpreted as _`AppWorkPath`_/%(ROOT)s
FORGEJO__repository__ROOT = repoDir;
# ...
# Force every new repository to be private.
FORGEJO__repository__FORCE_PRIVATE = "false";
# Default private when creating a new repository with push-to-create.
FORGEJO__repository__DEFAULT_PUSH_TO_CREATE = "true";
# ...
# Allow users to push local repositories to Forgejo and have them automatically created for a user.
FORGEJO__repository__ENABLE_PUSH_CREATE_USER = "true";
# Allow users to push local repositories to Forgejo and have them automatically created for an org.
FORGEJO__repository__ENABLE_PUSH_CREATE_ORG = "false";
# Comma separated list of globally disabled repo units.
FORGEJO__repository__DISABLED_REPO_UNITS = "";
# Comma separated list of default new repo units.
FORGEJO__repository__DEFAULT_REPO_UNITS = "repo.code,repo.issues,repo.pulls,repo.releases,repo.actions";
# Comma separated list of default forked repo units.
FORGEJO__repository__DEFAULT_FORK_REPO_UNITS = "repo.code,repo.pulls";
# Prefix archive files by placing them in a directory named after the repository.
FORGEJO__repository__PREFIX_ARCHIVE_FILES = "true";
# Disable migrating feature.
FORGEJO__repository__DISABLE_MIGRATIONS = "false";
# Disable stars feature.
FORGEJO__repository__DISABLE_STARS = "true";
# Disable repository forking.
@ -557,7 +576,7 @@ in {
#FORGEJO__picture__AVATAR_RENDERED_SIZE_FACTOR = "2";
# Maximum allowed file size for uploaded avatars.
# This is to limit the amount of RAM used when resizing the image.
#FORGEJO__picture__AVATAR_MAX_FILE_SIZE = "1048576";
FORGEJO__picture__AVATAR_MAX_FILE_SIZE = "1048576";
# If the uploaded file is not larger than this byte size, the image will be used as is, without resizing/converting.
#FORGEJO__picture__AVATAR_MAX_ORIGIN_SIZE = "262144";
# Chinese users can choose "duoshuo"

View file

@ -4,6 +4,7 @@ let
cfg = config.homelab.apps.jellyfin;
networkName = "jellyfin";
inherit (config.homelab.fileSystems) media;
UID = 3008;
GID = config.users.groups.media.gid;
@ -12,6 +13,11 @@ in {
config = lib.mkIf cfg.enable {
homelab = {
fileSystems.media.video = {
enable = true;
permissions = [ "read" ];
};
users = {
apps.enable = true;
media.enable = true;
@ -32,18 +38,6 @@ in {
];
};
"/srv/video" = {
device = "192.168.0.11:/mnt/SMALL/MEDIA/VIDEO";
fsType = "nfs";
options = [
"ro"
"nfsvers=4.2"
"async" "soft"
"timeo=100" "retry=50" "actimeo=1800" "lookupcache=all"
"nosuid" "tcp"
];
};
"/srv/homevideo" = {
device = "192.168.0.11:/mnt/BIG/MEDIA/HOMEVIDEO/ARCHIVE";
fsType = "nfs";
@ -101,7 +95,7 @@ in {
virtualisation.oci-containers.containers = {
jellyfin = {
hostname = "jellyfin";
image = "jellyfin/jellyfin:10.10.0";
image = "jellyfin/jellyfin:10.10.7";
user = "${toString UID}:${toString GID}";
autoStart = true;
ports = [
@ -117,7 +111,7 @@ in {
"cache:/cache"
"/srv/audio:/media/audio"
"/srv/video:/media/video"
"${media.video.hostPath}:/media/video"
"/srv/homevideo:/media/homevideo"
"/srv/photo:/media/photo"
];
@ -126,11 +120,28 @@ in {
};
};
feishin = {
hostname = "feishin";
image = "ghcr.io/jeffvli/feishin:0.7.1";
jellyfin-vue = {
hostname = "jellyfin-vue";
image = "ghcr.io/jellyfin/jellyfin-vue:unstable";
autoStart = true;
ports = [
"9180:9180/tcp" # Web player (HTTP)
"8080:80/tcp"
];
extraOptions = [
"--network=${networkName}"
];
labels = {
};
};
feishin = let
feishinPort = "9180";
in {
hostname = "feishin";
image = "ghcr.io/jeffvli/feishin:0.19.0";
autoStart = true;
ports = [
"${feishinPort}:9180/tcp" # Web player (HTTP)
];
extraOptions = [
"--network=${networkName}"
@ -147,8 +158,11 @@ in {
TZ = config.time.timeZone;
};
labels = {
"traefik.enable" = "true";
"traefik.http.routers.feishin.rule" = "Host(`play.jelly.depeuter.dev`)";
"traefik.http.services.feishin.loadbalancer.server.port" = feishinPort;
"traefik.tls.options.default.minVersion" = "VersionTLS13";
};
autoStart = true;
};
};
};

View file

@ -6,18 +6,31 @@ in {
options.homelab.apps.plex.enable = lib.mkEnableOption "Plex";
config = lib.mkIf cfg.enable {
homelab = {
users = {
apps.enable = true;
media.enable = true;
};
fileSystems.media.video.enable = true;
virtualisation.containers.enable = true;
};
users.users.plex = {
uid = lib.mkForce 3009;
isSystemUser = true;
group = config.users.groups.media;
group = config.users.groups.apps.name;
extraGroups = [
config.users.groups.media.name
];
home = "/var/empty";
shell = null;
};
virtualisation.oci-containers.containers = {
plex = {
virtualisation.oci-containers.containers.plex = let
videoHostPath = config.homelab.fileSystems.media.video.hostPath;
in {
hostname = "plex";
image = "plexinc/pms-docker:1.41.0.8992-8463ad060";
image = "plexinc/pms-docker:1.41.6.9685-d301f511a";
autoStart = true;
ports = [
"32400:32400/tcp" # Plex Media Server
@ -30,20 +43,26 @@ in {
# "8324:8324/tcp" # Controlling Plex for Roku via Plex Companion
];
environment = {
ADVERTISE_AP = "..."; # TODO Configure ip
#ADVERTISE_AP = "..."; # TODO Configure ip
ALLOWED_NETWORKS = "192.168.0.0/24,172.16.0.0/16";
CHANGE_CONFIG_DIR_OWNERSHIP = "false";
HOSTNAME = "PlexServer";
PLEX_CLAIM = "..."; # TODO Add token
PLEX_UID = config.users.users.plex.uid;
PLEX_GID = config.users.groups.media.gid;
HOSTNAME = "Hugo-Plex";
PLEX_CLAIM = "claim-d5MqsjMeCZrUF6oUvssr";
PLEX_UID = toString config.users.users.plex.uid;
PLEX_GID = toString config.users.groups.media.gid;
TZ = config.time.timeZone;
};
volumes = [
# TODO "config:/var/lib/plexmediaserver"
# TODO "transcode-temp:/transcode"
# TODO "media:/data"
# TODO Backup over NFS
"plex-config:/config"
"plex-transcode:/transcode"
"${videoHostPath}:/data/video:ro"
];
labels = {
"traefik.enable" = "true";
"traefik.http.routers.plex.rule" = "Host(`plex.depeuter.dev`)";
"traefik.http.services.plex.loadbalancer.server.port" = "32400";
};
};
};

View file

@ -0,0 +1,90 @@
{ config, lib, pkgs, ... }:
let
cfg = config.homelab.apps.traefik;
port = 8080;
in {
options.homelab.apps.traefik = {
enable = lib.mkEnableOption "Traefik Reverse Proxy";
sharedNetworkName = lib.mkOption {
type = lib.types.str;
default = "traefik";
description = "The name of the shared network to connect the container to.";
};
};
config = lib.mkIf cfg.enable {
homelab.virtualisation.containers.enable = true;
# Make sure the Docker network exists.
systemd.services."docker-${cfg.sharedNetworkName}-create-network" = {
description = "Create Docker network for ${cfg.sharedNetworkName}";
requiredBy = [
"docker-traefik.service"
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
if ! ${pkgs.docker}/bin/docker network ls | grep -q ${cfg.sharedNetworkName}; then
${pkgs.docker}/bin/docker network create ${cfg.sharedNetworkName}
fi
'';
};
virtualisation.oci-containers.containers.traefik = {
hostname = "traefik";
image = "traefik:v3.4.3";
autoStart = true;
ports = [
"80:80/tcp"
"443:443/tcp"
"${toString port}:${toString port}/tcp" # Web UI (enabled by --api.insecure=true)
];
extraOptions = [
"--network=${cfg.sharedNetworkName}"
];
environmentFiles = [
/home/admin/.cloudflare.secret
];
cmd = [
"--api.insecure=true"
# Add Docker provider
"--providers.docker=true"
"--providers.docker.exposedByDefault=false"
# Add web entrypoint
"--entrypoints.web.address=:80/tcp"
"--entrypoints.web.http.redirections.entrypoint.to=websecure"
"--entrypoints.web.http.redirections.entrypoint.scheme=https"
# Add websecure entrypoint
"--entrypoints.websecure.address=:443/tcp"
"--entrypoints.websecure.http.tls=true"
"--entrypoints.websecure.http.tls.certResolver=letsencrypt"
"--entrypoints.websecure.http.tls.domains[0].main=depeuter.dev"
"--entrypoints.websecure.http.tls.domains[0].sans=*.depeuter.dev"
"--entrypoints.websecure.http.tls.domains[1].sans=*.${config.networking.hostName}.depeuter.dev"
# Certificates
"--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
"--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"
"--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be"
"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
];
volumes = [
"letsencryp:/letsencrypt"
"/var/run/docker.sock:/var/run/docker.sock:ro"
];
labels = {
"traefik.enable" = "true";
"traefik.http.routers.traefik.rule" = "Host(`traefik.${config.networking.hostName}.depeuter.dev`)";
"traefik.http.services.traefik.loadbalancer.server.port" = toString port;
};
};
};
}

View file

@ -5,7 +5,24 @@ let
networkName = "vaultwarden";
in {
options.homelab.apps.vaultwarden.enable = lib.mkEnableOption "Vaultwarden";
options.homelab.apps.vaultwarden = {
enable = lib.mkEnableOption "Vaultwarden";
port = lib.mkOption {
type = lib.types.int;
default = 10102;
description = "Vaultwarden WebUI port";
};
domain = lib.mkOption {
type = lib.types.string;
example = "https://vault.depeuter.dev";
description = "Domain to configure Vaultwarden on";
};
name = lib.mkOption {
type = lib.types.string;
example = "Hugo's Vault";
description = "Service name to use for invitations and mail";
};
};
config = lib.mkIf cfg.enable {
homelab = {
@ -33,13 +50,16 @@ in {
'';
};
virtualisation.oci-containers.containers = {
vaultwarden-db = {
hostname = "vaultwarden-db";
virtualisation.oci-containers.containers = let
dbHostname = "vaultwarden-db";
dbPort = 5432;
in {
vaultwardenDb = {
hostname = dbHostname;
image = "postgres:15.8-alpine";
autoStart = true;
ports = [
"5432:5432/tcp"
"${toString dbPort}:5432/tcp"
];
extraOptions = [
"--network=${networkName}"
@ -57,16 +77,16 @@ in {
dataDir = "/data";
in {
hostname = "vaultwarden";
image = "vaultwarden/server:1.30.5-alpine";
image = "vaultwarden/server:1.34.3-alpine";
autoStart = true;
ports = [
"10102:80/tcp"
"${toString cfg.port}:80/tcp"
];
extraOptions = [
"--network=${networkName}"
];
dependsOn = [
"vaultwarden-db"
"vaultwardenDb"
];
volumes = [
"vaultwarden:${dataDir}"
@ -115,7 +135,7 @@ in {
## Details:
## - https://docs.diesel.rs/2.1.x/diesel/pg/struct.PgConnection.html
## - https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
DATABASE_URL = "postgresql://vaultwarden:ChangeMe@vaultwarden-db:5432/vaultwarden";
DATABASE_URL = "postgresql://vaultwarden:ChangeMe@${dbHostname}:${toString dbPort}/vaultwarden";
## Enable WAL for the DB
## Set to false to avoid enabling WAL during startup.
@ -244,7 +264,7 @@ in {
## For development
# DOMAIN=http://localhost
## For public server
DOMAIN = "https://vault.depeuter.dev";
DOMAIN = cfg.domain;
## For public server (URL with port number)
# DOMAIN=https://vw.domain.tld:8443
## For public server (URL with path)
@ -298,7 +318,7 @@ in {
## Note that setting this option to true prevents logins until the email address has been verified!
## The welcome email will include a verification link, and login attempts will periodically
## trigger another verification email to be sent.
SIGNUPS_VERIFY = "true";
SIGNUPS_VERIFY = "false";
## If SIGNUPS_VERIFY is set to true, this limits how many seconds after the last time
## an email verification link has been sent another verification email will be sent
@ -328,7 +348,7 @@ in {
## Invitations org admins to invite users, even when signups are disabled
# INVITATIONS_ALLOWED=true
## Name shown in the invitation emails that don't come from a specific organization
INVITATION_ORG_NAME = "Hugo's Vault";
INVITATION_ORG_NAME = cfg.name;
## The number of hours after which an organization invite token, emergency access invite token,
## email verification token and deletion request token will expire (must be at least 1)
@ -571,7 +591,7 @@ in {
## Note: if SMTP_USERNAME is specified, SMTP_PASSWORD is mandatory
SMTP_HOST = "smtp.gmail.com";
SMTP_FROM = "vault@depeuter.dev";
SMTP_FROM_NAME = "Hugo's Vault";
SMTP_FROM_NAME = cfg.name;
# SMTP_USERNAME=username
# SMTP_PASSWORD=password
# SMTP_TIMEOUT=15

View file

@ -1,6 +1,7 @@
{
imports = [
./apps
./fileSystems
./services
./virtualisation

View file

@ -0,0 +1,5 @@
{
imports = [
./media
];
}

View file

@ -0,0 +1,5 @@
{
imports = [
./video
];
}

View file

@ -0,0 +1,42 @@
{ config, lib, ... }:
let
cfg = config.homelab.fileSystems.media.video;
remotePath = "/mnt/SMALL/MEDIA/VIDEO";
maxPermissions = permissions:
if builtins.elem "write" permissions then "rw"
else "ro";
permissionsOption = maxPermissions cfg.permissions;
in {
options.homelab.fileSystems.media.video = {
enable = lib.mkEnableOption "MEDIA/VIDEO dataset";
hostPath = lib.mkOption {
type = lib.types.path;
default = "/srv/video";
description = "Mountpath on host";
};
permissions = lib.mkOption {
type = lib.types.listOf (lib.types.enum [ "read" "write" ]);
default = [ "read" ];
description = "Mount options permissions";
};
};
config = lib.mkIf cfg.enable {
fileSystems."${cfg.hostPath}" = {
device = "192.168.0.11:${remotePath}";
fsType = "nfs";
options = [
permissionsOption
"auto"
"nfsvers=4.2"
"async" "soft"
"rsize=1048576" "wsize=1048576"
"timeo=600" "retry=50" "retrans=2" "actimeo=1800" "lookupcache=all"
"_netdev" "nosuid" "tcp"
];
};
};
}

View file

@ -44,6 +44,6 @@ in {
];
};
};
};
}

19
secrets/secrets.yaml Normal file
View file

@ -0,0 +1,19 @@
users:
admin:
authorized_keys:
NixOS: ENC[AES256_GCM,data:sj2hkUkWp628KuXp+AnncLdawHpxb9fH1ZHnIisP0x9Tght9+/X2sWHpuMSeqi2i/R8B+Wgte66QkuwAOB0j+oB9N+66EhehmWZlK5hD/22p,iv:z18U+LvAQgPDfBBewE3lJmWZd0NGCPwJIe/h3tupuZc=,tag:ZJar3spO66JbDXygdTHh2w==,type:str]
sops:
age:
- recipient: age1qzutny0mqpcccqw6myyfntu6wcskruu9ghzvt6r4te7afkqwnguq05ex37
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjUSt2REk2Mmd0bk9ubjJk
dXFiY2JNR1dyZW9qTUdzaWZhY3c3amVwQzA0CkZHNVpZVjhsWXhVQVNaR0xONzhh
Y0lQaWNaNmpYYVdrRnZIZUhvUFUzcWMKLS0tIDAvSmF0VmpxcnZEQStXUjNCUE5Z
RnA2Lzk2WHFxOEh6dHN0aGhVSVpLTW8KA7IOvGDMBtgo4pe0Sw3Lol243xCDAJ4i
PhcJFiUObVRFZN7ISlULnOlTO3pT9jWvvmC5rDZWId3PQ8qjPvnOUg==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-10-04T17:33:22Z"
mac: ENC[AES256_GCM,data:I7I7uDFEWfw9+4KROtjHMVhaxYrVK5QmLfFZShSajF0A2Zxu9lg+fDGiMHk40JC5zD31P70QS/ipye1mBGQbCbLEA7uBUhNzZ7G1g58cIXF6vSGmt0fovm0MVSxEJ44r05fx6uT4OJu5BYVxYSlG84gTj9rCFXxxcBJMrh+6yaI=,iv:c1vudsp9bg0Pc2ddRyvWn6Tf0LhqNuEjxG9D4PpHqxs=,tag:K/1PSHhrTdsNPcPmRv/2Ew==,type:str]
unencrypted_suffix: _unencrypted
version: 3.10.2

View file

@ -18,8 +18,8 @@ in {
];
initialPassword = "ChangeMe";
openssh.authorizedKeys.keys = [
# TODO ChangeMe
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrG+ldRBdCeHEXrsy/qHXIJYg8xQXVuiUR0DxhFjYNg"
# HomeLab > NixOS > admin > ssh
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGWIOOEqTy8cWKpENVbzD4p7bsQgQb/Dgpzk8i0dZ00T"
];
packages = with pkgs; [
curl