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 # Detector configuration - CPU for now (can add GPU later)
181 detectors = {
182 cpu = {
183 type = "cpu";
184 num_threads = 4;
185 };
186 };
187
188 # Camera configuration - Pi Camera via go2rtc
189 cameras = {
190 picam = {
191 enabled = true;
192 ffmpeg = {
193 inputs = [
194 {
195 path = "rtsp://pi:8554/picam";
196 roles = [ "detect" "record" ];
197 }
198 ];
199 };
200 detect = {
201 enabled = true;
202 width = 1920;
203 height = 1080;
204 fps = 5; # Lower FPS for detection to reduce CPU
205 };
206 record = {
207 enabled = true;
208 retain = {
209 days = 7;
210 mode = "active_objects";
211 };
212 };
213 snapshots = {
214 enabled = true;
215 bounding_box = true;
216 retain = {
217 default = 14;
218 };
219 };
220 objects = {
221 track = [ "person" "dog" "cat" "car" ];
222 };
223 };
224
225 pizerocam = {
226 enabled = true;
227 ffmpeg = {
228 inputs = [
229 {
230 path = "rtsp://pizero:8554/pizerocam";
231 roles = [ "detect" "record" ];
232 }
233 ];
234 };
235 detect = {
236 enabled = true;
237 width = 1280;
238 height = 720;
239 fps = 5;
240 };
241 record = {
242 enabled = true;
243 retain = {
244 days = 7;
245 mode = "active_objects";
246 };
247 };
248 snapshots = {
249 enabled = true;
250 bounding_box = true;
251 retain = {
252 default = 14;
253 };
254 };
255 objects = {
256 track = [ "person" "dog" "cat" "car" ];
257 };
258 };
259 };
260
261 # Recording storage
262 record = {
263 enabled = true;
264 retain = {
265 days = 7;
266 mode = "active_objects";
267 };
268 };
269 };
270 };
271
272 # Home Assistant service
273 services.home-assistant = {
274 enable = true;
275 customComponents = with pkgs.home-assistant-custom-components; [
276 frigate
277 ];
278 extraComponents = [
279 "esphome"
280 "met"
281 "radio_browser"
282 "homekit"
283 "homekit_controller"
284 "isal"
285 "mqtt"
286 "tasmota"
287 "wiz"
288 "google_translate" # TTS - was missing gtts module
289 "ecobee" # Was missing pyecobee module
290 "ibeacon" # Was missing ibeacon_ble module
291 "go2rtc" # Camera streaming
292 "generic" # Generic camera integration
293 ];
294 config = {
295 homeassistant = {
296 time_zone = "America/Toronto";
297 };
298 default_config = { };
299 zeroconf = { };
300 # MQTT configuration - broker must be set up via UI
301 mqtt = { };
302 # Automations
303 automation = [
304 {
305 id = "1761448856909";
306 alias = "Lower heat at night";
307 trigger = [
308 {
309 platform = "time";
310 at = "23:00:00";
311 }
312 ];
313 condition = [ ];
314 action = [
315 {
316 action = "climate.set_temperature";
317 target.device_id = "bfe22d32a4532f8ae991d6daffb48267";
318 data = {
319 hvac_mode = "heat";
320 temperature = 18;
321 };
322 }
323 ];
324 mode = "single";
325 }
326 {
327 id = "1766200000001";
328 alias = "Raise heat in morning";
329 trigger = [
330 {
331 platform = "time";
332 at = "06:00:00";
333 }
334 ];
335 condition = [ ];
336 action = [
337 {
338 action = "climate.set_temperature";
339 target.device_id = "bfe22d32a4532f8ae991d6daffb48267";
340 data = {
341 hvac_mode = "heat";
342 temperature = 21;
343 };
344 }
345 ];
346 mode = "single";
347 }
348 {
349 id = "1766153071796";
350 alias = "Close Garage Door";
351 trigger = [
352 {
353 platform = "device";
354 device_id = "d8dedd8cd0ce1488d9830c455bb0a761";
355 domain = "cover";
356 entity_id = "cf36763543169888aa106b1acb02ad72";
357 type = "opened";
358 for = {
359 hours = 0;
360 minutes = 10;
361 seconds = 0;
362 };
363 }
364 ];
365 condition = [ ];
366 action = [
367 {
368 device_id = "d8dedd8cd0ce1488d9830c455bb0a761";
369 domain = "cover";
370 entity_id = "cf36763543169888aa106b1acb02ad72";
371 type = "close";
372 }
373 ];
374 mode = "single";
375 }
376 ];
377 };
378 };
379
380 # Invidious (self-hosted YouTube frontend for Kodi addon)
381 services.invidious = {
382 enable = true;
383 port = 3001;
384 address = "0.0.0.0";
385 settings = {
386 registration_enabled = false;
387 local = true;
388 invidious_companion = [{
389 private_url = "http://127.0.0.1:8282/companion";
390 }];
391 invidious_companion_key = "k9x2mP4qR7wL3nYz";
392 };
393 };
394
395 # Invidious Companion (replaces deprecated inv-sig-helper)
396 virtualisation.oci-containers.containers.invidious-companion = {
397 image = "quay.io/invidious/invidious-companion:latest";
398 ports = [ "127.0.0.1:8282:8282" ];
399 environment = {
400 SERVER_SECRET_KEY = "k9x2mP4qR7wL3nYz";
401 };
402 extraOptions = [ "--read-only" "--cap-drop=ALL" ];
403 };
404
405 # Enable the OpenSSH daemon.
406 # services.openssh.enable = true;
407
408 security.pam.loginLimits = [
409 {
410 domain = "*";
411 type = "soft";
412 item = "nofile";
413 value = "8192";
414 }
415 ];
416
417 # trmnl-rs server
418 systemd.services.trmnl-rs = {
419 description = "TRMNL Server";
420 wantedBy = [ "multi-user.target" ];
421 after = [ "network.target" ];
422 serviceConfig = {
423 ExecStart = "${inputs.trmnl-rs.packages.x86_64-linux.default}/bin/server";
424 Restart = "on-failure";
425 DynamicUser = true;
426 StateDirectory = "trmnl-rs";
427 WorkingDirectory = "/var/lib/trmnl-rs";
428 };
429 };
430
431 # Open ports in the firewall.
432 networking.firewall.allowedTCPPorts = [
433 8096 # jellyfin
434 5055 # jellyseer
435 3000 # vite dev port
436 3001 # Invidious
437 1883 # MQTT for Tasmota devices
438 2300 # trmnl
439 5000 # Frigate web UI
440 8971 # Frigate API
441 config.services.home-assistant.config.http.server_port
442 ];
443 networking.firewall.allowedUDPPorts = [
444 ];
445 # networking.firewall.enable = false;
446
447 # This value determines the NixOS release from which the default
448 # settings for stateful data, like file locations and database versions
449 # on your system were taken. It‘s perfectly fine and recommended to leave
450 # this value at the release version of the first install of this system.
451 # Before changing this value read the documentation for this option
452 # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
453 system.stateVersion = "25.05"; # Did you read the comment?
454
455}