Compare commits

..

10 commits

Author SHA1 Message Date
6deb36d920
chore(ssh): Update keys 2025-10-18 18:59:17 +02:00
652a9da077
flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/7df7ff7d8e00218376575f0acdcc5d66741351ee?narHash=sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs%3D' (2025-10-02)
  → 'github:NixOS/nixpkgs/544961dfcce86422ba200ed9a0b00dd4b1486ec5?narHash=sha256-EVAqOteLBFmd7pKkb0%2BFIUyzTF61VKi7YmvP1tw4nEw%3D' (2025-10-15)
• Updated input 'sops-nix':
    'github:Mic92/sops-nix/9fcfabe085281dd793589bdc770a2e577a3caa5d?narHash=sha256-f9QC2KKiNReZDG2yyKAtDZh0rSK2Xp1wkPzKbHeQVRU%3D' (2025-09-29)
  → 'github:Mic92/sops-nix/ab8d56e85b8be14cff9d93735951e30c3e86a437?narHash=sha256-8mN3kqyqa2PKY0wwZ2UmMEYMcxvNTwLaOrrDsw6Qi4E%3D' (2025-10-13)
2025-10-18 17:09:08 +02:00
2c195bf807
chore(arr): Use functions 2025-10-13 20:35:46 +02:00
b2e904306b
feat(traefik): Add external services 2025-10-11 15:44:29 +02:00
297a6df29e
feat: Add gitignore 2025-10-11 15:40:43 +02:00
81a07af152
nix flake update 2025-10-04 20:26:01 +02:00
4ab3848c83
fix(Development): Replace authorized_keys 2025-10-04 20:25:09 +02:00
a37c5ae83a
fix(sops): Add Tibo-NixFat 2025-10-04 20:21:59 +02:00
12015f8589
feat(sops): Setup sops 2025-10-04 19:59:03 +02:00
f1ba0a98e8
Updates 2025-10-01 16:41:28 +02:00
19 changed files with 215 additions and 488 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

33
flake.lock generated
View file

@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1735291276, "lastModified": 1760524057,
"narHash": "sha256-NYVcA06+blsLG6wpAbSPTCyLvxD/92Hy4vlY9WxFI1M=", "narHash": "sha256-EVAqOteLBFmd7pKkb0+FIUyzTF61VKi7YmvP1tw4nEw=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "634fd46801442d760e09493a794c4f15db2d0cbb", "rev": "544961dfcce86422ba200ed9a0b00dd4b1486ec5",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -37,9 +37,30 @@
"inputs": { "inputs": {
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"sops-nix": "sops-nix",
"utils": "utils" "utils": "utils"
} }
}, },
"sops-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1760393368,
"narHash": "sha256-8mN3kqyqa2PKY0wwZ2UmMEYMcxvNTwLaOrrDsw6Qi4E=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "ab8d56e85b8be14cff9d93735951e30c3e86a437",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "sops-nix",
"type": "github"
}
},
"systems": { "systems": {
"locked": { "locked": {
"lastModified": 1681028828, "lastModified": 1681028828,
@ -62,11 +83,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1722363685, "lastModified": 1738591040,
"narHash": "sha256-XCf2PIAT6lH7BwytgioPmVf/wkzXjSKScC4KzcZgb64=", "narHash": "sha256-4WNeriUToshQ/L5J+dTSWC5OJIwT39SEP7V7oylndi8=",
"owner": "gytis-ivaskevicius", "owner": "gytis-ivaskevicius",
"repo": "flake-utils-plus", "repo": "flake-utils-plus",
"rev": "6b10f51ff73a66bb29f3bc8151a59d217713f496", "rev": "afcb15b845e74ac5e998358709b2b5fe42a948d1",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

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

View file

@ -16,6 +16,7 @@
apps = { apps = {
speedtest.enable = true; speedtest.enable = true;
technitiumDNS.enable = true; technitiumDNS.enable = true;
traefik.enable = true;
}; };
virtualisation.guest.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"; system.stateVersion = "24.05";
}; };
} }

View file

@ -5,6 +5,13 @@
homelab = { homelab = {
apps.gitea.enable = true; apps.gitea.enable = true;
virtualisation.guest.enable = true; virtualisation.guest.enable = true;
users.admin = {
enable = true;
authorizedKeys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFrp6aM62Bf7bj1YM5AlAWuNrANU3N5e8+LtbbpmZPKS"
];
};
}; };
networking = { networking = {

View file

@ -59,6 +59,7 @@ prefixLength = 24;
}; };
"cloud.depeuter.dev" = { }; "cloud.depeuter.dev" = { };
"git.depeuter.dev" = { }; "git.depeuter.dev" = { };
"home.depeuter.dev" = { };
"jelly.depeuter.dev" = { }; "jelly.depeuter.dev" = { };
"vault.depeuter.dev" = { }; "vault.depeuter.dev" = { };
}; };
@ -136,10 +137,27 @@ prefixLength = 24;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 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"; "tasks.depeuter.dev".locations."/".return = "301 https://cloud.depeuter.dev/apps/tasks";
"notes.depeuter.dev".locations."/".return = "301 https://cloud.depeuter.dev/apps/notes"; "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" = { "jelly.depeuter.dev" = {
enableACME = true; enableACME = true;
forceSSL = true; forceSSL = true;
@ -176,7 +194,7 @@ prefixLength = 24;
}; };
}; };
extraConfig = '' extraConfig = ''
client_max_body_size 20M; client_max_body_size 512M;
# Security / XSS Mitigation Headers # Security / XSS Mitigation Headers
# NOTE: X-Frame-Options may cause issues with the webOS app # 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-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 512M; client_max_body_size 10G;
keepalive_timeout 600s; keepalive_timeout 600s;
proxy_buffers 4 256k; # Number and size of buffers for reading response proxy_buffers 4 256k; # Number and size of buffers for reading response
proxy_buffer_size 256k; # Buffer for the first part of the response proxy_buffer_size 256k; # Buffer for the first part of the response
@ -220,10 +238,18 @@ prefixLength = 24;
enableACME = true; enableACME = true;
forceSSL = true; forceSSL = true;
locations = { locations = {
"/".proxyPass = "http://192.168.0.22:10102"; "/" = {
proxyPass = "http://192.168.0.22:10102";
proxyWebSockets = true;
};
"~ ^/admin".return = 403; "~ ^/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 = { homelab = {
apps.technitiumDNS.enable = true; apps = {
technitiumDNS.enable = true;
traefik.enable = true;
};
users.deploy.enable = true; users.deploy.enable = true;
}; };
@ -34,12 +37,11 @@
hardware = { hardware = {
enableRedistributableFirmware = true; enableRedistributableFirmware = true;
enableAllFirmware = true; enableAllFirmware = true;
pulseaudio.enable = true; graphics.enable = true;
opengl.enable = true;
}; };
# Select internationalisation properties. # Select internationalisation properties.
i18n.defaultLocale = "en_GB.utf8"; i18n.defaultLocale = "en_GB.UTF-8";
networking = { networking = {
hostName = "Niko"; hostName = "Niko";
@ -79,6 +81,8 @@
user = config.users.users.jellyfin-mpv-shim.name; user = config.users.users.jellyfin-mpv-shim.name;
}; };
pulseaudio.enable = true;
tailscale = { tailscale = {
enable = true; enable = true;
useRoutingFeatures = "server"; useRoutingFeatures = "server";
@ -94,8 +98,6 @@
# resolved.enable = true; # resolved.enable = true;
}; };
sound.enable = true;
# Define a user account. Don't forget to set a password with 'passwd'. # Define a user account. Don't forget to set a password with 'passwd'.
users.users.jellyfin-mpv-shim = { users.users.jellyfin-mpv-shim = {
description = "Jellyfin MPV Shim User"; description = "Jellyfin MPV Shim User";
@ -114,67 +116,4 @@
systemd.services."cage-tty1".serviceConfig.Restart = "always"; systemd.services."cage-tty1".serviceConfig.Restart = "always";
system.stateVersion = "24.05"; 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

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

View file

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

View file

@ -9,6 +9,13 @@
name = "Hugo's Vault"; name = "Hugo's Vault";
}; };
virtualisation.guest.enable = true; virtualisation.guest.enable = true;
users.admin = {
enable = true;
authorizedKeys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJnihoyozOCnm6T9OzL2xoMeMZckBYR2w43us68ABA93"
];
};
}; };
networking = { networking = {

View file

@ -12,7 +12,16 @@ let
PGID = toString config.users.groups.media.gid; PGID = toString config.users.groups.media.gid;
UMASK = "002"; UMASK = "002";
in { 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"; enable = lib.mkEnableOption "Arr Stack using Docker";
exposePorts = lib.mkOption { exposePorts = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
@ -21,46 +30,11 @@ in {
default = ! config.homelab.apps.traefik.enable; default = ! config.homelab.apps.traefik.enable;
}; };
bazarr = { bazarr = mkAppOption "Bazarr";
enable = lib.mkEnableOption "Bazarr using Docker"; prowlarr = mkAppOption "Prowlarr";
exposePorts = lib.mkOption { qbittorrent = mkAppOption "qBittorrent";
type = lib.types.bool; radarr = mkAppOption "Radarr";
description = "Expose Bazarr port"; sonarr = mkAppOption "Sonarr";
default = cfg.exposePorts;
};
};
prowlarr = {
enable = lib.mkEnableOption "Prowlarr using Docker";
exposePorts = lib.mkOption {
type = lib.types.bool;
description = "Expose Prowlarr port";
default = cfg.exposePorts;
};
};
qbittorrent = {
enable = lib.mkEnableOption "qBittorrent using Docker";
exposePorts = lib.mkOption {
type = lib.types.bool;
description = "Expose qBittorrent port";
default = cfg.exposePorts;
};
};
radarr = {
enable = lib.mkEnableOption "Radarr using Docker";
exposePorts = lib.mkOption {
type = lib.types.bool;
description = "Expose Radarr port";
default = cfg.exposePorts;
};
};
sonarr = {
enable = lib.mkEnableOption "Sonarr using Docker";
exposePorts = lib.mkOption {
type = lib.types.bool;
description = "Expose Sonarr port";
default = cfg.exposePorts;
};
};
}; };
config = { config = {
@ -87,9 +61,9 @@ in {
virtualisation.containers.enable = lib.mkIf inUse true; virtualisation.containers.enable = lib.mkIf inUse true;
}; };
fileSystems = lib.mkIf inUse { fileSystems = let
"/srv/bazarr-backup" = lib.mkIf cfg.bazarr.enable { mkFileSystem = device: {
device = "192.168.0.11:/mnt/BIG/BACKUP/BAZARR"; inherit device;
fsType = "nfs"; fsType = "nfs";
options = [ options = [
"rw" "rw"
@ -102,75 +76,14 @@ in {
]; ];
}; };
"/srv/prowlarr-backup" = lib.mkIf cfg.prowlarr.enable { hugoBackup = "192.168.0.11:/mnt/BIG/BACKUP";
device = "192.168.0.11:/mnt/BIG/BACKUP/PROWLARR"; in lib.mkIf inUse {
fsType = "nfs"; "/srv/bazarr-backup" = lib.mkIf cfg.bazarr.enable (mkFileSystem "${hugoBackup}/BAZARR");
options = [ "/srv/prowlarr-backup" = lib.mkIf cfg.bazarr.enable (mkFileSystem "${hugoBackup}/PROWLARR");
"rw" "/srv/qbittorrent" = lib.mkIf cfg.qbittorrent.enable (mkFileSystem "192.168.0.11:/mnt/SMALL/CONFIG/QBITTORRENT");
"auto" "/srv/radarr-backup" = lib.mkIf cfg.radarr.enable (mkFileSystem "${hugoBackup}/RADARR");
"nfsvers=4.2" "/srv/sonarr-backup" = lib.mkIf cfg.sonarr.enable (mkFileSystem "${hugoBackup}/SONARR");
"rsize=1048576" "wsize=1048576" "/srv/torrent" = mkFileSystem "192.168.0.11:/mnt/SMALL/MEDIA/TORRENT";
"hard"
"timeo=600" "retrans=2"
"_netdev" "nosuid" "tcp"
];
};
"/srv/qbittorrent" = lib.mkIf cfg.qbittorrent.enable {
device = "192.168.0.11:/mnt/SMALL/CONFIG/QBITTORRENT";
fsType = "nfs";
options = [
"rw"
"auto"
"nfsvers=4.2"
"rsize=1048576" "wsize=1048576"
"hard"
"timeo=600" "retrans=2"
"_netdev" "nosuid" "tcp"
];
};
"/srv/radarr-backup" = lib.mkIf cfg.radarr.enable {
device = "192.168.0.11:/mnt/BIG/BACKUP/RADARR";
fsType = "nfs";
options = [
"rw"
"auto"
"nfsvers=4.2"
"rsize=1048576" "wsize=1048576"
"hard"
"timeo=600" "retrans=2"
"_netdev" "nosuid" "tcp"
];
};
"/srv/sonarr-backup" = lib.mkIf cfg.sonarr.enable {
device = "192.168.0.11:/mnt/BIG/BACKUP/SONARR";
fsType = "nfs";
options = [
"rw"
"auto"
"nfsvers=4.2"
"rsize=1048576" "wsize=1048576"
"hard"
"timeo=600" "retrans=2"
"_netdev" "nosuid" "tcp"
];
};
"/srv/torrent" = {
device = "192.168.0.11:/mnt/SMALL/MEDIA/TORRENT";
fsType = "nfs";
options = [
"rw"
"auto"
"nfsvers=4.2"
"rsize=1048576" "wsize=1048576"
"hard"
"timeo=600" "retrans=2"
"_netdev" "nosuid" "tcp"
];
};
}; };
# Make sure the Docker network exists. # Make sure the Docker network exists.
@ -195,45 +108,24 @@ in {
}; };
# Create a user for each app. # Create a user for each app.
users.users = { users.users = let
bazarr = lib.mkIf cfg.bazarr.enable { mkUser = uid: {
uid = lib.mkForce 3003; uid = lib.mkForce uid;
isSystemUser = true; isSystemUser = true;
group = config.users.groups.media.name; group = config.users.groups.media.name;
home = "/var/empty"; home = "/var/empty";
shell = null; shell = null;
}; };
prowlarr = lib.mkIf cfg.prowlarr.enable { in {
uid = lib.mkForce 3004; bazarr = lib.mkIf cfg.bazarr.enable (mkUser 3003);
isSystemUser = true; prowlarr = lib.mkIf cfg.prowlarr.enable (mkUser 3004);
group = config.users.groups.media.name; qbittorrent = lib.mkIf cfg.qbittorrent.enable (mkUser 3005) // {
home = "/var/empty";
shell = null;
};
qbittorrent = lib.mkIf cfg.qbittorrent.enable {
uid = lib.mkForce 3005;
isSystemUser = true;
group = config.users.groups.media.name;
extraGroups = [ extraGroups = [
config.users.groups.apps.name config.users.groups.apps.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;
}; };
radarr = lib.mkIf cfg.radarr.enable (mkUser 3006);
sonarr = lib.mkIf cfg.sonarr.enable (mkUser 3007);
}; };
virtualisation.oci-containers.containers = let virtualisation.oci-containers.containers = let

View file

@ -27,7 +27,7 @@ in {
"rw" "rw"
"auto" "auto"
"nfsvers=4.2" "nfsvers=4.2"
"sync" "hard" "timeo=600" "async" "soft" "timeo=600"
"retrans=2" "retrans=2"
"_netdev" "_netdev"
"nosuid" "nosuid"
@ -53,24 +53,41 @@ in {
virtualisation.oci-containers.containers.freshrss = { virtualisation.oci-containers.containers.freshrss = {
hostname = "freshrss"; hostname = "freshrss";
image = "freshrss/freshrss:1.24.0"; image = "freshrss/freshrss:1.25.0";
autoStart = true; autoStart = true;
user = "0:33"; user = "0:33";
ports = [ ports = [
"${toString port}:${toString port}/tcp" "${toString port}:80/tcp"
]; ];
extraOptions = [ extraOptions = [
"--network=${networkName}" "--network=${networkName}"
]; ];
environment = { environment = {
TZ = config.time.timeZone; TZ = config.time.timeZone;
CRON_TIME = "3,18,33,48"; # Alternatively, configure cron inside container. CRON_MIN = "3,18,33,48"; # Alternatively, configure cron inside container.
LISTEN = "0.0.0.0:${toString port}"; SERVER_DNS = "rss.depeuter.dev";
TRUSTED_PROXY = "172.16.0.1/12 192.168.0.1/16";
}; };
volumes = [ volumes = [
"/srv/freshrss/www/freshrss/data:/var/www/FreshRSS/data" "/srv/freshrss/www/freshrss/data:/var/www/FreshRSS/data"
"/srv/freshrss/www/freshrss/extensions:/var/www/FreshRSS/extensions" "/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 = { gitea = {
hostname = "gitea"; hostname = "gitea";
image = "codeberg.org/forgejo/forgejo:8.0.3-rootless"; image = "codeberg.org/forgejo/forgejo:11.0.1-rootless";
autoStart = true; autoStart = true;
user = "${toString UID}:${toString GID}"; user = "${toString UID}:${toString GID}";
ports = [ ports = [
@ -576,7 +576,7 @@ in {
#FORGEJO__picture__AVATAR_RENDERED_SIZE_FACTOR = "2"; #FORGEJO__picture__AVATAR_RENDERED_SIZE_FACTOR = "2";
# Maximum allowed file size for uploaded avatars. # Maximum allowed file size for uploaded avatars.
# This is to limit the amount of RAM used when resizing the image. # 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. # 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"; #FORGEJO__picture__AVATAR_MAX_ORIGIN_SIZE = "262144";
# Chinese users can choose "duoshuo" # Chinese users can choose "duoshuo"

View file

@ -1,238 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.homelab.apps.solidtime;
networkName = "solidtime";
internalNetworkName = "solidtime-internal";
version = "0.8.0";
port = 8000;
user = "1000:1000";
# dbExternalPort = ...;
dbInternalPort = 5432;
gotenbergPort = 3000;
inherit (config.virtualisation.oci-containers) containers;
volumes = [
"solidtime-storage:/var/www/html/storage"
"solidtime-logs:/var/www/html/storage/logs"
"solidtime-app:/var/www/html/storage/app"
];
# laravel.env
laravelEnv = {
APP_NAME = "Solidtime";
VITE_APP_NAME = laravelEnv.APP_NAME;
APP_ENV = "production";
APP_DEBUG = "false";
APP_URL = "http://localhost:${toString port}";
APP_FORCE_HTTPS = "false";
APP_ENABLE_REGISTRATION = "false";
TRUSTED_PROXIES = "0.0.0.0/0,2000:0:0:0:0:0:0:0/3";
# Logging
LOG_CHANNEL = "stderr_daily";
LOG_LEVEL = "debug";
# Database
DB_CONNECTION = "pgsql";
DB_HOST = containers.solidtimeDb.hostname;
DB_PORT = toString dbInternalPort;
DB_SSL_MODE = "require";
DB_DATABASE = "solidtime";
DB_USERNAME = "solidtime";
DB_PASSWORD = "ChangeMe";
# Mail
#MAIL_MAILER = "smtp";
#MAIL_HOST = "smtp.gmail.com";
#MAIL_PORT = "465";
#MAIL_ENCRYPTION = "tls";
#MAIL_FROM_ADDRESS = "no-reply@time.depeuter.dev";
MAIL_FROM_NAME = laravelEnv.APP_NAME;
#MAIL_USERNAME = "kmtl.hugo@gmail.com";
#MAIL_PASSWORD = "fhfxoequhhqidrhd";
# Queue
QUEUE_CONNECTION = "database";
# File storage
FILESYSTEM_DISK = "local";
PUBLIC_FILESYSTEM_DISK = "public";
# Services
GOTENBERG_URL = "http://${containers.solidtimeGotenberg.hostname}:${toString gotenbergPort}";
};
in {
options.homelab.apps.solidtime.enable = lib.mkEnableOption "Solidtime time tracker using Docker";
config = lib.mkIf cfg.enable {
homelab.virtualisation.containers.enable = true;
# Make sure the Docker network exists.
systemd.services = {
"docker-${networkName}-create-network" = {
description = "Create Docker network for ${networkName}";
requiredBy = [
"${containers.solidtime.serviceName}.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
'';
};
"docker-${internalNetworkName}-create-network" = {
description = "Create Docker network for ${internalNetworkName}";
requiredBy = [
"${containers.solidtime.serviceName}.service"
"${containers.solidtimeScheduler.serviceName}.service"
"${containers.solidtimeQueue.serviceName}.service"
"${containers.solidtimeDb.serviceName}.service"
"${containers.solidtimeGotenberg.serviceName}.service"
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
if ! ${pkgs.docker}/bin/docker network ls | grep -q ${internalNetworkName}; then
${pkgs.docker}/bin/docker network create ${internalNetworkName}
fi
'';
};
};
virtualisation.oci-containers.containers = {
solidtime = {
hostname = "solidtime";
image = "solidtime/solidtime:${version}";
autoStart = true;
inherit user;
ports = [
# Open ports if you don't use Traefik
"${toString port}:8000"
];
extraOptions = [
"--network=${networkName}"
"--network=${internalNetworkName}"
# Healthecks
# test: [ "CMD-SHELL", "curl --fail http://localhost:8000/health-check/up || exit 1" ]
''--health-cmd=curl --fail http://localhost:8000/health-check/up || exit 1''
];
inherit volumes;
dependsOn = [
"solidtimeDb"
];
environmentFiles = [
"/home/admin/.solidtime.env"
];
environment = laravelEnv // {
CONTAINER_MODE = "http";
};
labels = {
"traefik.enable" = "true";
"traefik.http.routers.solidtime.rule" = "Host(`time.${config.networking.hostName}.depeuter.dev`)";
"traefik.http.services.solidtime.loadbalancer.server.port" = toString port;
};
};
solidtimeScheduler = {
hostname = "scheduler";
image = "solidtime/solidtime:${version}";
inherit user;
autoStart = true;
extraOptions = [
"--network=${internalNetworkName}"
# Healthchecks
# test: [ "CMD-SHELL", "supervisorctl status scheduler:scheduler_00" ]
''--health-cmd="supervisorctl status scheduler:scheduler_00"''
];
inherit volumes;
dependsOn = [
"solidtimeDb"
];
environmentFiles = [
"/home/admin/.solidtime.env"
];
environment = laravelEnv // {
CONTAINER_MODE = "scheduler";
};
};
solidtimeQueue = {
hostname = "queue";
image = "solidtime/solidtime:${version}";
inherit user;
autoStart = true;
extraOptions = [
"--network=${internalNetworkName}"
# Healthchecks
# test: [ "CMD-SHELL", "supervisorctl status worker:worker_00" ]
''--health-cmd="supervisorctl status worker:worker_00"''
];
inherit volumes;
dependsOn = [
"solidtimeDb"
];
environmentFiles = [
"/home/admin/.solidtime.env"
];
environment = laravelEnv // {
CONTAINER_MODE = "worker";
WORKER_COMMAND = "php /var/www/html/artisan queue:work";
};
};
solidtimeDb = {
hostname = "database";
image = "postgres:15";
autoStart = true;
ports = [
# "${toString dbExternalPort}:${toString dbInternalPort}"
];
extraOptions = [
"--network=${internalNetworkName}"
# Healthchecks
# test: - CMD - pg_isready - '-q' - '-d' - '${DB_DATABASE}' - '-U' - '${DB_USERNAME}' retries: 3 timeout: 5s
''--health-cmd="pg_isready -q -d ${laravelEnv.DB_DATABASE} -U ${laravelEnv.DB_USERNAME}"''
"--health-retries=3"
"--health-timeout=5s"
];
volumes = [
"solidtime-db:/var/lib/postgresql/data"
];
environment = {
PGPASSWORD = laravelEnv.DB_PASSWORD;
POSTGRES_DB = laravelEnv.DB_DATABASE;
POSTGRES_USER = laravelEnv.DB_USERNAME;
POSTGRES_PASSWORD = laravelEnv.DB_PASSWORD;
};
};
solidtimeGotenberg = {
hostname = "gotenberg";
image = "gotenberg/gotenberg:8";
autoStart = true;
extraOptions = [
"--network=${internalNetworkName}"
# Healthchecks
# test: [ "CMD", "curl", "--silent", "--fail", "http://localhost:3000/health" ]
''--health-cmd="curl --silent --fail http://localhost:${toString gotenbergPort}/health"''
];
};
};
};
}

View file

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

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

@ -3,24 +3,30 @@
let let
cfg = config.homelab.users.admin; cfg = config.homelab.users.admin;
in { in {
options.homelab.users.admin.enable = lib.mkEnableOption "user System Administrator"; options.homelab.users.admin = {
enable = lib.mkEnableOption "user System Administrator";
authorizedKeys = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [
# HomeLab > NixOS > admin > ssh
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGWIOOEqTy8cWKpENVbzD4p7bsQgQb/Dgpzk8i0dZ00T"
];
};
};
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
nix.settings.trusted-users = [ nix.settings.trusted-users = [
config.users.users.admin.name config.users.users.gh0st.name
]; ];
users.users.admin = { users.users.gh0st = {
description = "System Administrator"; description = "System Administrator";
isNormalUser = true; isNormalUser = true;
extraGroups = [ extraGroups = [
config.users.groups.wheel.name # Enable 'sudo' for the user. config.users.groups.wheel.name # Enable 'sudo' for the user.
]; ];
initialPassword = "ChangeMe"; initialPassword = "ChangeMe";
openssh.authorizedKeys.keys = [ openssh.authorizedKeys.keys = cfg.authorizedKeys;
# TODO ChangeMe
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrG+ldRBdCeHEXrsy/qHXIJYg8xQXVuiUR0DxhFjYNg"
];
packages = with pkgs; [ packages = with pkgs; [
curl curl
git git

View file

@ -13,13 +13,8 @@ in {
"docker" # Allow access to the docker socket. "docker" # Allow access to the docker socket.
]; ];
openssh.authorizedKeys.keys = [ openssh.authorizedKeys.keys = [
# TODO ChangeMe
# Tibo-NixFat
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrG+ldRBdCeHEXrsy/qHXIJYg8xQXVuiUR0DxhFjYNg"
# Hugo # Hugo
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDAxR813vqq5zbu1NHrIybu5Imlu3k0rDCGxHiuGEhPoVV9c5FpnKNGLCi3ctm15ZcVBX4HcponYsKRBsCzM2pI4uXjxhHkLzbss5LttFuSzv5v/QHfLW1bvyJEMBEPxguGqAydAeWrBFdI9uHBEXeb325uKxMKBZHYvvpyAQ115c1wKy1bL8BfR0LTkhsFqexRvI86q59AVrAU/KFf6RXO0T9QA6H/vyWLlIPc7Ta+tSWwQ68bMmS5Pwn8q58tOAOAd6Lpt4TqUDJSppPjLEPKyKC6ShwMdEjwmwpEG0hxfsvaU8XERyQbSbEE9sLHRA2LoEdtMx3J8nzX3AwYUNspsqIv6NQZksnVqJ8OfL45ngUFcSJ6kBsUvCZfzEUGUTJ6Js0v84NOIXxNG/ZfPsk6ArXm3dvj2TYeK8llO6wpJnMMyztmmiODWoj9tepZSij44IgVM5wdWYIK/RZoYTsCQbmvJFfB8jhyJnf/7F19Vo5+LwhmCOsQh/KEK0F1DVc= admin@Hugo" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICms6vjhE9kOlqV5GBPGInwUHAfCSVHLI2Gtzee0VXPh"
]; ];
}; };
}; };