Configuration for my NixOS based systems and Home Manager
1{ config
2, lib
3, pkgs
4, unstable
5, ...
6}:
7{
8
9 services.zfs = {
10 autoScrub.enable = true;
11 };
12 services.nfs.server.enable = true;
13 # Some programs need SUID wrappers, can be configured further or are
14 # started in user sessions.
15 # programs.mtr.enable = true;
16 programs.gnupg.agent = {
17 enable = true;
18 enableSSHSupport = false;
19 };
20
21 # Fish shell, the best
22 programs.fish.enable = true;
23
24 # MOSH, SSH over flakey connections
25 programs.mosh.enable = true;
26
27 # List services that you want to enable:
28
29 # Enable the OpenSSH daemon.
30 services.openssh = {
31 enable = true;
32 openFirewall = true;
33 settings.PasswordAuthentication = false;
34 };
35
36 # This option is for enabling the bolt daemon for managing Thunderbolt/USB4 Devices.
37 services.hardware.bolt.enable = true;
38
39 # Tailscale
40 services.tailscale = {
41 enable = true;
42 useRoutingFeatures = "client";
43 };
44
45 # Containers and VMs
46 virtualisation = {
47 podman = {
48 enable = true;
49 dockerCompat = true;
50 defaultNetwork.settings.dns_enabled = true;
51 };
52 };
53
54 # Samba, for shares
55 # TODO
56 services.samba = {
57 enable = true;
58 openFirewall = true;
59 nmbd.enable = true;
60 winbindd.enable = true;
61 settings = {
62 global = {
63 workgroup = "WORKGROUP";
64 "server string" = "misaki";
65 security = "user";
66 "use sendfile" = "yes";
67 "hosts allow" = "192.168.1. 127.0.0.1 localhost";
68 "hosts deny" = "0.0.0.0/0";
69 "guest account" = "nobody";
70 "map to guest" = "bad user";
71 deadtime = 30;
72 };
73 shokuhou = {
74 path = "/srv/shokuhou";
75 browseable = "yes";
76 "read only" = "no";
77 "guest ok" = "no";
78 "create mask" = "0644";
79 "directory mask" = "0755";
80 "force user" = "noah";
81 "force group" = "nas";
82 };
83 mentalout = {
84 path = "/srv/mentalout";
85 browseable = "yes";
86 "read only" = "no";
87 "guest ok" = "no";
88 "create mask" = "0644";
89 "directory mask" = "0755";
90 "force user" = "noah";
91 "force group" = "nas";
92 };
93 };
94 };
95 services.samba-wsdd = {
96 enable = true;
97 openFirewall = true;
98 };
99
100 services.coredns = {
101 enable = true;
102 config = lib.readFile ./coredns/config;
103 };
104
105 services.nats = {
106 enable = true;
107 jetstream = true;
108 user = "nats";
109 group = "nats";
110 serverName = "misaki";
111 dataDir = "/srv/shokuhou/applications/nats";
112 validateConfig = false;
113 settings = {
114 authorization = {
115 users = [
116 {
117 user = "seedbox@packetlost.dev";
118 permissions = {
119 publish = [
120 "torrents"
121 "torrents.>"
122 "$JS.API.INFO"
123 #"$JS.API.STREAM.INFO.>"
124 "$KV.torrents.>"
125 #"$JS.API.STREAM.*.*.OBJ_torrents"
126 "$JS.API.*.*.OBJ_torrents"
127 "$JS.API.STREAM.MSG.GET.OBJ_torrents"
128 "$JS.API.*.*.OBJ_torrents.>"
129 "$O.torrents.>"
130 ];
131 subscribe = [
132 "torrents.>"
133 "_INBOX.>"
134 ];
135 allow_responses = false;
136 };
137 }
138 { user = "odin@packetlost.dev"; }
139 { user = "misaki@packetlost.dev"; }
140 { user = "noah@packetlost.dev"; }
141 { user = "touma-nixos@packetlost.dev"; }
142 ];
143 };
144 tls = {
145 cert_file = "/srv/nats/nats.packetlost.dev/cert.pem";
146 key_file = "/srv/nats/nats.packetlost.dev/key.pem";
147 ca_file = "/srv/nats/minica.pem";
148 verify_and_map = true;
149 };
150 jetstream = {
151 # 50GB
152 max_file_store = 53687091200;
153 max_mem = 8589934592;
154 };
155 };
156 };
157
158 # Minio's object storage has been mostly replaced with NATS. If I specifically need a
159 # S3-like API, this will be revived.
160 services.minio = {
161 enable = false;
162 listenAddress = ":9003";
163 consoleAddress = ":9004";
164 dataDir = [
165 /srv/shokuhou/applications/minio
166 ];
167 };
168
169 services.netatalk = {
170 enable = true;
171 settings = {
172 time-machine = {
173 path = "/srv/shokuhou/backup/timemachine";
174 "valid users" = "noah";
175 "time machine" = true;
176 };
177 };
178 };
179
180 services.webdav.enable = false;
181 services.sftpgo = {
182 enable = false;
183 dataDir = /srv/shokuhou/documents/sftpgo;
184 group = "nas";
185 };
186 services.syncthing = {
187 enable = false;
188 openDefaultPorts = true;
189 # disable the sync folder creation
190 extraFlags = [ "--no-default-folder" ];
191 settings = {
192 folders = {
193 "Sync" = {
194 path = "/srv/shokuhou/documents/sync";
195 };
196 };
197 };
198 };
199
200 services.grafana = {
201 enable = false;
202 settings.server.http_port = 2342;
203 settings.server.domain = "grafana.packetlost.dev";
204 settings.server.http_addr = "127.0.0.1";
205 };
206
207 services.prometheus = {
208 enable = false;
209 port = 9001;
210 exporters = {
211 node = {
212 enable = true;
213 enabledCollectors = [ "systemd" ];
214 port = 9002;
215 };
216 };
217
218 scrapeConfigs = [
219 {
220 job_name = "chrysalis";
221 static_configs = [
222 { targets = [ "127.0.0.1:${builtins.toString config.services.prometheus.exporters.node.port}" ]; }
223 ];
224 }
225 ];
226 };
227
228 # TODO: figure out how to appropriately configure this
229 services.step-ca = {
230 enable = false;
231 openFirewall = true;
232 port = 8443;
233 address = "0.0.0.0";
234 intermediatePasswordFile = /etc/nixos/step-ca-intermediate-ca-password;
235 settings = builtins.fromJSON (builtins.readFile /home/noah/.step/config/ca.json);
236 };
237
238 age.secrets.acme = {
239 file = ./secrets/porkbun-api-key.age;
240 owner = "root";
241 group = "acme";
242 };
243
244 # TODO: re-enable this once Agenix is set up
245 security.acme = {
246 acceptTerms = true;
247 defaults.email = "noah@packetlost.dev";
248 certs."plex.packetlost.dev" = {
249 dnsProvider = "porkbun";
250 group = "httpd";
251 environmentFile = config.age.secrets.acme.path;
252 };
253 certs."img.ngp.computer" = {
254 group = "httpd";
255 dnsProvider = "porkbun";
256 environmentFile = config.age.secrets.acme.path;
257 };
258 certs."photos.ngp.computer" = {
259 group = "httpd";
260 dnsProvider = "porkbun";
261 environmentFile = config.age.secrets.acme.path;
262 };
263 certs."jellyfin.packetlost.dev" = {
264 group = "httpd";
265 dnsProvider = "porkbun";
266 environmentFile = config.age.secrets.acme.path;
267 };
268 };
269
270 # A test email server that only works on LAN
271 services.maddy = {
272 enable = true;
273 openFirewall = true;
274 primaryDomain = "misaki.local";
275 ensureAccounts = [
276 "noah@misaki.local"
277 "postmaster@misaki.local"
278 "test@misaki.local"
279 ];
280 ensureCredentials = {
281 "noah@misaki.local".passwordFile = "${pkgs.writeText "noah" "Password123"}";
282 "postmaster@misaki.local".passwordFile = "${pkgs.writeText "noah" "Password123"}";
283 "test@misaki.local".passwordFile = "${pkgs.writeText "test" "Password123"}";
284 };
285 };
286
287 services.nix-serve = {
288 enable = true;
289 secretKeyFile = "/srv/cache/cache-priv-key.pem";
290 #openFirewall = true;
291 };
292
293 services.plex = {
294 enable = true;
295 openFirewall = false; # we proxy this with nginx
296 group = "nas";
297 user = "noah";
298 package = unstable.plex;
299 };
300
301 services.jellyfin = {
302 enable = true;
303 openFirewall = true;
304 user = "noah";
305 group = "nas";
306 logDir = "/srv/shokuhou/applications/jellyfin/log";
307 cacheDir = "/srv/shokuhou/applications/jellyfin/cache";
308 dataDir = "/srv/shokuhou/applications/jellyfin/data";
309 configDir = "/srv/shokuhou/applications/jellyfin/config";
310 };
311
312 # services.gitea = {
313 # enable = true;
314 # user = "git";
315 # domain = "git.packetlost.dev";
316 # };
317
318 # Litterbox, collect my IRC logs
319 systemd = {
320 services = {
321 "litterbox@" = {
322 path = [ pkgs.litterbox ];
323 serviceConfig = {
324 StartLimitIntervalSec = 5;
325 StartLimitBurst = 10;
326 Restart = "on-failure";
327 RestartSec = "10s";
328 Type = "simple";
329 ExecStart = "${pkgs.litterbox}/bin/litterbox /srv/litterbox/%i.conf";
330 ExecReload = "kill -USR1 $MAINPID";
331 User = "noah";
332 Group = "litterbox";
333 };
334 };
335
336 #"litterbox@libera.irc.packetlost.dev" = {
337 # overrideStrategy = "asDropin";
338 # wantedBy = [ "multi-user.target" ];
339 #};
340 "update-downstream-src" = {
341 path = with pkgs; [
342 rc
343 coreutils
344 git
345 openssh
346 ];
347 script = "exec ${./scripts/update-src}";
348 serviceConfig = {
349 Type = "oneshot";
350 User = "noah";
351 WorkingDirectory = "/srv/src";
352 };
353 };
354 };
355 timers = {
356 "update-downstream-src" = {
357 wantedBy = [ "timers.target" ];
358 timerConfig = {
359 OnCalendar = "daily";
360 Persistent = true;
361 };
362 };
363 };
364 };
365
366 services.teamspeak3 = {
367 enable = true;
368 openFirewall = true;
369 };
370
371 services.immich = {
372 enable = true;
373 package = unstable.immich;
374 accelerationDevices = [ "/dev/dri/renderD128" ];
375 mediaLocation = "/srv/shokuhou/pictures/immich";
376 };
377 users.users.immich.extraGroups = [
378 "video"
379 "render"
380 "nas"
381 ];
382
383 # Nginx Reverse SSL Proxy
384 services.nginx = {
385 enable = true;
386 group = "nas";
387 user = "noah";
388
389 # This is disabled for now
390 #virtualHosts."${config.services.grafana.settings.server.domain}" = {
391 # locations."/" = {
392 # proxyPass = "http://127.0.0.1:${builtins.toString config.services.grafana.settings.server.http_port}";
393 # proxyWebsockets = true;
394 # };
395 #};
396
397 virtualHosts."cache.packetlost.dev" = {
398 locations."/".proxyPass =
399 "http://${config.services.nix-serve.bindAddress}:${toString config.services.nix-serve.port}";
400 };
401 virtualHosts."photos.ngp.computer" = {
402 enableACME = false;
403 useACMEHost = "photos.ngp.computer";
404 acmeRoot = null;
405 forceSSL = true;
406 locations."/" = {
407 proxyPass = "http://[::1]:${toString config.services.immich.port}";
408 proxyWebsockets = true;
409 recommendedProxySettings = true;
410 extraConfig = ''
411 client_max_body_size 50000M;
412 proxy_read_timeout 600s;
413 proxy_send_timeout 600s;
414 send_timeout 600s;
415 '';
416 };
417 };
418 virtualHosts."img.ngp.computer" = {
419 forceSSL = true;
420 enableACME = false;
421 useACMEHost = "img.ngp.computer";
422 acmeRoot = null;
423 root = "/srv/shokuhou/pictures/public";
424 extraConfig = ''
425 sendfile on;
426 autoindex_exact_size on;
427 tcp_nopush on;
428 '';
429 locations."/" = {
430 extraConfig = ''
431 autoindex on;
432 autoindex_exact_size on;
433 alias /srv/shokuhou/pictures/public/$1;
434 '';
435 };
436 # Don't use this unless you want the contents of this folder to be in the Nix
437 # Store and only updated when switching
438 #root = "/srv/shokuhou/pictures/public";
439 #extraConfig = ''
440 # sendfile on;
441 # sendfile_max_chunk 1m;
442 # tcp_nopush on;
443 # root /srv/shokuhou/pictures/public;
444 # location ~ /.* {
445 # }
446 #'';
447 };
448 virtualHosts."jellyfin.packetlost.dev" = {
449 forceSSL = true;
450 enableACME = false;
451 useACMEHost = "jellyfin.packetlost.dev";
452 acmeRoot = null;
453 http2 = true;
454 locations."/" = {
455 proxyPass = "http://localhost:8096/";
456 };
457 };
458
459 # give a name to the virtual host. It also becomes the server name.
460 virtualHosts."plex.packetlost.dev" = {
461 # Since we want a secure connection, we force SSL
462 forceSSL = true;
463 enableACME = false;
464 useACMEHost = "plex.packetlost.dev";
465 acmeRoot = null;
466
467 # http2 can more performant for streaming: https://blog.cloudflare.com/introducing-http2/
468 http2 = true;
469
470 # Provide the ssl cert and key for the vhost
471 # These are filled in automatically with ACME
472 extraConfig = ''
473
474 #Some players don't reopen a socket and playback stops totally instead of resuming after an extended pause
475 send_timeout 100m;
476
477 # Why this is important: https://blog.cloudflare.com/ocsp-stapling-how-cloudflare-just-made-ssl-30/
478 ssl_stapling on;
479 ssl_stapling_verify on;
480
481 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
482 ssl_prefer_server_ciphers on;
483 #Intentionally not hardened for security for player support and encryption video streams has a lot of overhead with something like AES-256-GCM-SHA384.
484 ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
485
486 # Forward real ip and host to Plex
487 proxy_set_header X-Real-IP $remote_addr;
488 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
489 proxy_set_header X-Forwarded-Proto $scheme;
490 proxy_set_header Host $server_addr;
491 proxy_set_header Referer $server_addr;
492 proxy_set_header Origin $server_addr;
493
494 # Plex has A LOT of javascript, xml and html. This helps a lot, but if it causes playback issues with devices turn it off.
495 gzip on;
496 gzip_vary on;
497 gzip_min_length 1000;
498 gzip_proxied any;
499 gzip_types text/plain text/css text/xml application/xml text/javascript application/x-javascript image/svg+xml;
500 gzip_disable "MSIE [1-6]\.";
501
502 # Nginx default client_max_body_size is 1MB, which breaks Camera Upload feature from the phones.
503 # Increasing the limit fixes the issue. Anyhow, if 4K videos are expected to be uploaded, the size might need to be increased even more
504 client_max_body_size 100M;
505
506 # Plex headers
507 proxy_set_header X-Plex-Client-Identifier $http_x_plex_client_identifier;
508 proxy_set_header X-Plex-Device $http_x_plex_device;
509 proxy_set_header X-Plex-Device-Name $http_x_plex_device_name;
510 proxy_set_header X-Plex-Platform $http_x_plex_platform;
511 proxy_set_header X-Plex-Platform-Version $http_x_plex_platform_version;
512 proxy_set_header X-Plex-Product $http_x_plex_product;
513 proxy_set_header X-Plex-Token $http_x_plex_token;
514 proxy_set_header X-Plex-Version $http_x_plex_version;
515 proxy_set_header X-Plex-Nocache $http_x_plex_nocache;
516 proxy_set_header X-Plex-Provides $http_x_plex_provides;
517 proxy_set_header X-Plex-Device-Vendor $http_x_plex_device_vendor;
518 proxy_set_header X-Plex-Model $http_x_plex_model;
519
520 # Websockets
521 proxy_http_version 1.1;
522 proxy_set_header Upgrade $http_upgrade;
523 proxy_set_header Connection "upgrade";
524
525 # Buffering off send to the client as soon as the data is received from Plex.
526 proxy_redirect off;
527 proxy_buffering off;
528 '';
529
530 locations."/" = {
531 proxyPass = "http://localhost:32400/";
532 };
533 };
534 };
535}