me like nix
1{
2 pkgs,
3 config,
4 inputs,
5 ...
6}:
7
8let
9 jellyfinKodiSyncQueue = pkgs.fetchzip {
10 url = "https://repo.jellyfin.org/releases/plugin/kodi-sync-queue/kodi-sync-queue_15.0.0.0.zip";
11 stripRoot = false;
12 hash = "sha256-xtlG3UQ/WClt/Hvxe+oId2CeJ+PWMDXBUJXh5+k+mZQ=";
13 };
14in
15{
16 imports = [
17 # Include the results of the hardware scan.
18 ./hardware-configuration.nix
19 ../common/common.nix
20 ];
21
22 networking.hostName = "mira"; # Define your hostname.
23
24 # ZRAM swap to prevent OOM freezes
25 zramSwap = {
26 enable = true;
27 memoryPercent = 50;
28 };
29 # networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
30
31 # Configure network proxy if necessary
32 # networking.proxy.default = "http://user:password@proxy:port/";
33 # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
34
35 # this is like a network devices discovery thing
36 services.avahi = {
37 enable = true;
38 nssmdns4 = true;
39 openFirewall = true;
40 };
41
42 services.copyparty.enable = true;
43
44 services.openssh = {
45 enable = true;
46 settings = {
47 PasswordAuthentication = false;
48 KbdInteractiveAuthentication = false;
49 PermitRootLogin = "no";
50 AllowUsers = [ "sean" ];
51 };
52 };
53
54 users.users.sean.openssh.authorizedKeys.keys = [
55 "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc="
56 "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16"
57 ];
58
59 # List services that you want to enable:
60 services.flaresolverr.enable = true;
61 nixarr = {
62 enable = true;
63 mediaDir = "/mnt/storage1/nixarr/media";
64 vpn = {
65 enable = true;
66 wgConf = "/mnt/storage1/nixarr/wireguard.conf";
67 };
68
69 jellyfin = {
70 enable = true;
71 openFirewall = true;
72 };
73
74 transmission = {
75 enable = true;
76 vpn.enable = true;
77 };
78 sabnzbd = {
79 enable = true;
80 vpn.enable = true;
81 openFirewall = true;
82 };
83
84 prowlarr.enable = true;
85 radarr.enable = true;
86 sonarr.enable = true;
87 jellyseerr = {
88 enable = true;
89 openFirewall = true;
90 };
91
92 recyclarr = {
93 enable = true;
94 configuration = {
95 sonarr = {
96 series = {
97 base_url = "http://localhost:8989";
98 api_key = "!env_var SONARR_API_KEY";
99 quality_definition = {
100 type = "series";
101 };
102 delete_old_custom_formats = true;
103 custom_formats = [
104 {
105 trash_ids = [
106 "85c61753df5da1fb2aab6f2a47426b09" # BR-DISK
107 "9c11cd3f07101cdba90a2d81cf0e56b4" # LQ
108 ];
109 assign_scores_to = [
110 {
111 name = "WEB-DL (1080p)";
112 score = -10000;
113 }
114 ];
115 }
116 ];
117 };
118 };
119 radarr = {
120 movies = {
121 base_url = "http://localhost:7878";
122 api_key = "!env_var RADARR_API_KEY";
123 quality_definition = {
124 type = "movie";
125 };
126 delete_old_custom_formats = true;
127 custom_formats = [
128 {
129 trash_ids = [
130 "570bc9ebecd92723d2d21500f4be314c" # Remaster
131 "eca37840c13c6ef2dd0262b141a5482f" # 4K Remaster
132 ];
133 assign_scores_to = [
134 {
135 name = "HD Bluray + WEB";
136 score = 25;
137 }
138 ];
139 }
140 ];
141 };
142 };
143 };
144 };
145 };
146
147 # Install Kodi Sync Queue plugin into Jellyfin
148 systemd.services.jellyfin.serviceConfig.ExecStartPre =
149 let
150 pluginDir = "/data/.state/nixarr/jellyfin/data/plugins/Kodi Sync Queue/15.0.0.0";
151 in
152 pkgs.writeShellScript "install-jellyfin-plugins" ''
153 mkdir -p "${pluginDir}"
154 cp -f ${jellyfinKodiSyncQueue}/*.dll ${jellyfinKodiSyncQueue}/meta.json "${pluginDir}/"
155 '';
156
157 # MQTT broker for Home Assistant (Tasmota devices, Frigate)
158 services.mosquitto = {
159 enable = true;
160 listeners = [
161 {
162 acl = [ "pattern readwrite #" ];
163 omitPasswordAuth = true;
164 settings.allow_anonymous = true;
165 }
166 ];
167 };
168
169 # Frigate NVR for camera recording and AI object detection
170 services.frigate = {
171 enable = true;
172 hostname = "frigate";
173 settings = {
174 mqtt = {
175 enabled = true;
176 host = "localhost";
177 port = 1883;
178 };
179
180 detectors = {
181 cpu = {
182 type = "cpu";
183 num_threads = 4;
184 };
185 };
186
187 cameras = {
188 picam = {
189 enabled = true;
190 ffmpeg = {
191 hwaccel_args = "preset-nvidia-h264";
192 inputs = [
193 {
194 path = "rtsp://pi:8554/picam";
195 roles = [ "detect" "record" ];
196 }
197 ];
198 };
199 detect = {
200 enabled = true;
201 width = 1920;
202 height = 1080;
203 fps = 5;
204 };
205 record = {
206 enabled = true;
207 retain = {
208 days = 7;
209 mode = "active_objects";
210 };
211 };
212 snapshots = {
213 enabled = true;
214 bounding_box = true;
215 retain = {
216 default = 14;
217 };
218 };
219 objects = {
220 track = [ "person" "dog" "cat" "car" ];
221 };
222 zones = {
223 driveway = {
224 coordinates = "0,0.243,1,0.544,1,1,0,1,0,0.75";
225 objects = [ "person" "car" "dog" "cat" ];
226 };
227 };
228 };
229
230 pizerocam = {
231 enabled = true;
232 ffmpeg = {
233 hwaccel_args = "preset-nvidia-h264";
234 inputs = [
235 {
236 path = "rtsp://pizero:8554/pizerocam";
237 roles = [ "record" ];
238 }
239 ];
240 };
241 detect = {
242 enabled = false;
243 };
244 record = {
245 enabled = true;
246 retain = {
247 days = 2;
248 mode = "all";
249 };
250 };
251 };
252 };
253
254 record = {
255 enabled = true;
256 retain = {
257 days = 7;
258 mode = "active_objects";
259 };
260 };
261 };
262 };
263
264 # Home Assistant service
265 services.home-assistant = {
266 enable = true;
267 customComponents = with pkgs.home-assistant-custom-components; [
268 frigate
269 ];
270 extraComponents = [
271 "esphome"
272 "met"
273 "radio_browser"
274 "homekit"
275 "homekit_controller"
276 "isal"
277 "mqtt"
278 "tasmota"
279 "wiz"
280 "google_translate" # TTS - was missing gtts module
281 "ecobee" # Was missing pyecobee module
282 "ibeacon" # Was missing ibeacon_ble module
283 "go2rtc" # Camera streaming
284 "generic" # Generic camera integration
285 ];
286 config = {
287 homeassistant = {
288 time_zone = "America/Toronto";
289 };
290 default_config = { };
291 zeroconf = { };
292 # MQTT configuration - broker must be set up via UI
293 mqtt = { };
294 # Automations
295 automation = [
296 {
297 id = "1761448856909";
298 alias = "Lower heat at night";
299 trigger = [
300 {
301 platform = "time";
302 at = "23:00:00";
303 }
304 ];
305 condition = [ ];
306 action = [
307 {
308 action = "climate.set_temperature";
309 target.device_id = "bfe22d32a4532f8ae991d6daffb48267";
310 data = {
311 hvac_mode = "heat";
312 temperature = 18;
313 };
314 }
315 ];
316 mode = "single";
317 }
318 {
319 id = "1766200000001";
320 alias = "Raise heat in morning";
321 trigger = [
322 {
323 platform = "time";
324 at = "06:00:00";
325 }
326 ];
327 condition = [ ];
328 action = [
329 {
330 action = "climate.set_temperature";
331 target.device_id = "bfe22d32a4532f8ae991d6daffb48267";
332 data = {
333 hvac_mode = "heat";
334 temperature = 21;
335 };
336 }
337 ];
338 mode = "single";
339 }
340 {
341 id = "1766153071796";
342 alias = "Close Garage Door";
343 trigger = [
344 {
345 platform = "device";
346 device_id = "d8dedd8cd0ce1488d9830c455bb0a761";
347 domain = "cover";
348 entity_id = "cf36763543169888aa106b1acb02ad72";
349 type = "opened";
350 for = {
351 hours = 0;
352 minutes = 10;
353 seconds = 0;
354 };
355 }
356 ];
357 condition = [ ];
358 action = [
359 {
360 device_id = "d8dedd8cd0ce1488d9830c455bb0a761";
361 domain = "cover";
362 entity_id = "cf36763543169888aa106b1acb02ad72";
363 type = "close";
364 }
365 ];
366 mode = "single";
367 }
368 ];
369 };
370 };
371
372 # Invidious (self-hosted YouTube frontend for Kodi addon)
373 services.invidious = {
374 enable = true;
375 port = 3001;
376 address = "0.0.0.0";
377 settings = {
378 registration_enabled = false;
379 local = true;
380 invidious_companion = [
381 {
382 private_url = "http://127.0.0.1:8282/companion";
383 }
384 ];
385 invidious_companion_key = "k9x2mP4qR7wL3nYz";
386 };
387 };
388
389 # Invidious Companion (replaces deprecated inv-sig-helper)
390 virtualisation.oci-containers.containers.invidious-companion = {
391 image = "quay.io/invidious/invidious-companion:latest";
392 ports = [ "127.0.0.1:8282:8282" ];
393 environment = {
394 SERVER_SECRET_KEY = "k9x2mP4qR7wL3nYz";
395 };
396 extraOptions = [
397 "--read-only"
398 "--cap-drop=ALL"
399 ];
400 };
401
402 # Enable the OpenSSH daemon.
403 # services.openssh.enable = true;
404
405 security.pam.loginLimits = [
406 {
407 domain = "*";
408 type = "soft";
409 item = "nofile";
410 value = "8192";
411 }
412 ];
413
414 # trmnl-rs server
415 systemd.services.trmnl-rs = {
416 description = "TRMNL Server";
417 wantedBy = [ "multi-user.target" ];
418 wants = [ "network-online.target" ];
419 after = [ "network-online.target" "nss-lookup.target" ];
420 serviceConfig = {
421 ExecStart = "${inputs.trmnl-rs.packages.x86_64-linux.default}/bin/server";
422 Restart = "on-failure";
423 RestartSec = 5;
424 DynamicUser = true;
425 StateDirectory = "trmnl-rs";
426 WorkingDirectory = "/var/lib/trmnl-rs";
427 };
428 };
429
430 # Open ports in the firewall.
431 networking.firewall.allowedTCPPorts = [
432 8096 # jellyfin
433 5055 # jellyseer
434 3000 # vite dev port
435 3001 # Invidious
436 1883 # MQTT for Tasmota devices
437 2300 # trmnl
438 5000 # Frigate web UI
439 8971 # Frigate API
440 config.services.home-assistant.config.http.server_port
441 ];
442 networking.firewall.allowedUDPPorts = [
443 ];
444 # networking.firewall.enable = false;
445
446 # This value determines the NixOS release from which the default
447 # settings for stateful data, like file locations and database versions
448 # on your system were taken. It‘s perfectly fine and recommended to leave
449 # this value at the release version of the first install of this system.
450 # Before changing this value read the documentation for this option
451 # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
452 system.stateVersion = "25.05"; # Did you read the comment?
453
454}