Ports focalor and valefar into ark. Valefar system build and NAT test pass; deployed live as generation 110 on 2026-09-28 without reboot. SSH, PVE/VMs, ZFS, mesh, Searx, CUPS, Docker etcd, and media services verified. Focalor evaluates, but full CUDA build remains pending; focalor not deployed.
+2852
-14
Diff
Round #1
+491
-13
flake.lock
+491
-13
flake.lock
···
1
1
{
2
2
"nodes": {
3
+
"agenix": {
4
+
"inputs": {
5
+
"darwin": "darwin",
6
+
"home-manager": "home-manager",
7
+
"nixpkgs": "nixpkgs",
8
+
"systems": "systems"
9
+
},
10
+
"locked": {
11
+
"lastModified": 1770165109,
12
+
"narHash": "sha256-9VnK6Oqai65puVJ4WYtCTvlJeXxMzAp/69HhQuTdl/I=",
13
+
"owner": "ryantm",
14
+
"repo": "agenix",
15
+
"rev": "b027ee29d959fda4b60b57566d64c98a202e0feb",
16
+
"type": "github"
17
+
},
18
+
"original": {
19
+
"owner": "ryantm",
20
+
"repo": "agenix",
21
+
"rev": "b027ee29d959fda4b60b57566d64c98a202e0feb",
22
+
"type": "github"
23
+
}
24
+
},
3
25
"bun2nix": {
4
26
"inputs": {
5
27
"flake-parts": [
···
34
56
"type": "github"
35
57
}
36
58
},
59
+
"catppuccin": {
60
+
"inputs": {
61
+
"nixpkgs": "nixpkgs_2"
62
+
},
63
+
"locked": {
64
+
"lastModified": 1789553013,
65
+
"narHash": "sha256-W5dvgFOuVs24X3G5tUb8C2IHU7ICXNvyGPiWWFjfbuo=",
66
+
"owner": "catppuccin",
67
+
"repo": "nix",
68
+
"rev": "89b3eacf59d6b5eefbc2d69c3a4eb5aaf66d63bc",
69
+
"type": "github"
70
+
},
71
+
"original": {
72
+
"owner": "catppuccin",
73
+
"repo": "nix",
74
+
"type": "github"
75
+
}
76
+
},
37
77
"chaotic": {
38
78
"inputs": {
39
79
"flake-schemas": "flake-schemas",
40
-
"home-manager": "home-manager",
41
-
"nixpkgs": "nixpkgs"
80
+
"home-manager": "home-manager_2",
81
+
"nixpkgs": "nixpkgs_3"
42
82
},
43
83
"locked": {
44
84
"lastModified": 1788855501,
···
70
110
"type": "github"
71
111
}
72
112
},
113
+
"darwin": {
114
+
"inputs": {
115
+
"nixpkgs": [
116
+
"agenix",
117
+
"nixpkgs"
118
+
]
119
+
},
120
+
"locked": {
121
+
"lastModified": 1744478979,
122
+
"narHash": "sha256-dyN+teG9G82G+m+PX/aSAagkC+vUv0SgUw3XkPhQodQ=",
123
+
"owner": "lnl7",
124
+
"repo": "nix-darwin",
125
+
"rev": "43975d782b418ebf4969e9ccba82466728c2851b",
126
+
"type": "github"
127
+
},
128
+
"original": {
129
+
"owner": "lnl7",
130
+
"ref": "master",
131
+
"repo": "nix-darwin",
132
+
"type": "github"
133
+
}
134
+
},
135
+
"flake-compat": {
136
+
"flake": false,
137
+
"locked": {
138
+
"lastModified": 1767039857,
139
+
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
140
+
"owner": "NixOS",
141
+
"repo": "flake-compat",
142
+
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
143
+
"type": "github"
144
+
},
145
+
"original": {
146
+
"owner": "NixOS",
147
+
"repo": "flake-compat",
148
+
"type": "github"
149
+
}
150
+
},
151
+
"flake-compat_2": {
152
+
"locked": {
153
+
"lastModified": 1767039857,
154
+
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
155
+
"owner": "edolstra",
156
+
"repo": "flake-compat",
157
+
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
158
+
"type": "github"
159
+
},
160
+
"original": {
161
+
"owner": "edolstra",
162
+
"repo": "flake-compat",
163
+
"type": "github"
164
+
}
165
+
},
73
166
"flake-parts": {
74
167
"inputs": {
75
168
"nixpkgs-lib": [
···
91
184
"type": "github"
92
185
}
93
186
},
187
+
"flake-parts_2": {
188
+
"inputs": {
189
+
"nixpkgs-lib": "nixpkgs-lib"
190
+
},
191
+
"locked": {
192
+
"lastModified": 1788450739,
193
+
"narHash": "sha256-glZLQlzIn1fXH6PazR2iUmTo7kzzyYSshrWhLS9TqCU=",
194
+
"owner": "hercules-ci",
195
+
"repo": "flake-parts",
196
+
"rev": "31729ca8cbdb4fa927b34e5f4353e6a83f39e993",
197
+
"type": "github"
198
+
},
199
+
"original": {
200
+
"owner": "hercules-ci",
201
+
"repo": "flake-parts",
202
+
"type": "github"
203
+
}
204
+
},
205
+
"flake-parts_3": {
206
+
"inputs": {
207
+
"nixpkgs-lib": "nixpkgs-lib_2"
208
+
},
209
+
"locked": {
210
+
"lastModified": 1772408722,
211
+
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
212
+
"owner": "hercules-ci",
213
+
"repo": "flake-parts",
214
+
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
215
+
"type": "github"
216
+
},
217
+
"original": {
218
+
"owner": "hercules-ci",
219
+
"repo": "flake-parts",
220
+
"type": "github"
221
+
}
222
+
},
94
223
"flake-schemas": {
95
224
"locked": {
96
225
"lastModified": 1780327564,
···
107
236
},
108
237
"flake-utils": {
109
238
"inputs": {
110
-
"systems": "systems_2"
239
+
"systems": "systems_3"
111
240
},
112
241
"locked": {
113
242
"lastModified": 1731533236,
···
123
252
"type": "github"
124
253
}
125
254
},
255
+
"git-hooks": {
256
+
"inputs": {
257
+
"flake-compat": [
258
+
"nix-gaming",
259
+
"flake-compat"
260
+
],
261
+
"nixpkgs": [
262
+
"nix-gaming",
263
+
"nixpkgs"
264
+
]
265
+
},
266
+
"locked": {
267
+
"lastModified": 1790091288,
268
+
"narHash": "sha256-2dUuLTiQrf2gUFVLlayDq/hgluitplAMv6Y9bYwQQ+c=",
269
+
"owner": "cachix",
270
+
"repo": "git-hooks.nix",
271
+
"rev": "0d3997c4d3253505f77c9bcea63904bb575da3c5",
272
+
"type": "github"
273
+
},
274
+
"original": {
275
+
"owner": "cachix",
276
+
"repo": "git-hooks.nix",
277
+
"type": "github"
278
+
}
279
+
},
126
280
"home-manager": {
281
+
"inputs": {
282
+
"nixpkgs": [
283
+
"agenix",
284
+
"nixpkgs"
285
+
]
286
+
},
287
+
"locked": {
288
+
"lastModified": 1745494811,
289
+
"narHash": "sha256-YZCh2o9Ua1n9uCvrvi5pRxtuVNml8X2a03qIFfRKpFs=",
290
+
"owner": "nix-community",
291
+
"repo": "home-manager",
292
+
"rev": "abfad3d2958c9e6300a883bd443512c55dfeb1be",
293
+
"type": "github"
294
+
},
295
+
"original": {
296
+
"owner": "nix-community",
297
+
"repo": "home-manager",
298
+
"type": "github"
299
+
}
300
+
},
301
+
"home-manager_2": {
127
302
"inputs": {
128
303
"nixpkgs": [
129
304
"chaotic",
···
144
319
"type": "github"
145
320
}
146
321
},
322
+
"home-manager_3": {
323
+
"inputs": {
324
+
"nixpkgs": [
325
+
"zen-browser",
326
+
"nixpkgs"
327
+
]
328
+
},
329
+
"locked": {
330
+
"lastModified": 1789430117,
331
+
"narHash": "sha256-t+U1mijItLJ64xZOifpfQ17kpAL73nU7pDI48ScacVc=",
332
+
"owner": "nix-community",
333
+
"repo": "home-manager",
334
+
"rev": "cda90fd8838825c689fde9d3f3b4e937937790df",
335
+
"type": "github"
336
+
},
337
+
"original": {
338
+
"owner": "nix-community",
339
+
"repo": "home-manager",
340
+
"type": "github"
341
+
}
342
+
},
147
343
"llm-agents": {
148
344
"inputs": {
149
345
"bun2nix": "bun2nix",
150
346
"flake-parts": "flake-parts",
151
-
"nixpkgs": "nixpkgs_2",
152
-
"systems": "systems",
347
+
"nixpkgs": "nixpkgs_4",
348
+
"systems": "systems_2",
153
349
"treefmt-nix": "treefmt-nix"
154
350
},
155
351
"locked": {
···
169
365
"llm-bridge": {
170
366
"inputs": {
171
367
"flake-utils": "flake-utils",
172
-
"nixpkgs": "nixpkgs_3"
368
+
"nixpkgs": "nixpkgs_5"
173
369
},
174
370
"locked": {
175
371
"lastModified": 1790067159,
···
187
383
},
188
384
"meowtd": {
189
385
"inputs": {
190
-
"nixpkgs": "nixpkgs_4"
386
+
"nixpkgs": "nixpkgs_6"
191
387
},
192
388
"locked": {
193
389
"lastModified": 1785527457,
···
203
399
"url": "https://git.koi.rip/koi/meowtd"
204
400
}
205
401
},
402
+
"niri": {
403
+
"inputs": {
404
+
"nixpkgs": [
405
+
"nixpkgs"
406
+
]
407
+
},
408
+
"locked": {
409
+
"lastModified": 1790353586,
410
+
"narHash": "sha256-oEvDG8PTqiy6lvKKWj9D+XZyPzYcl4rm5Qs7qKR0pSk=",
411
+
"owner": "niri-wm",
412
+
"repo": "niri",
413
+
"rev": "1f03391ea644c2a43597de7f637269e26d1e1b49",
414
+
"type": "github"
415
+
},
416
+
"original": {
417
+
"owner": "niri-wm",
418
+
"repo": "niri",
419
+
"type": "github"
420
+
}
421
+
},
422
+
"nix-gaming": {
423
+
"inputs": {
424
+
"flake-compat": "flake-compat",
425
+
"flake-parts": "flake-parts_2",
426
+
"git-hooks": "git-hooks",
427
+
"nixpkgs": [
428
+
"nixpkgs"
429
+
]
430
+
},
431
+
"locked": {
432
+
"lastModified": 1790483338,
433
+
"narHash": "sha256-srUVCAD22Bcbg6GJbZEijDI22HMD6Lo2qOoUKtH22dg=",
434
+
"owner": "fufexan",
435
+
"repo": "nix-gaming",
436
+
"rev": "b193ce18777d01469dbeb3b9c4110de2426e0edf",
437
+
"type": "github"
438
+
},
439
+
"original": {
440
+
"owner": "fufexan",
441
+
"repo": "nix-gaming",
442
+
"type": "github"
443
+
}
444
+
},
206
445
"nixpkgs": {
446
+
"locked": {
447
+
"lastModified": 1754028485,
448
+
"narHash": "sha256-IiiXB3BDTi6UqzAZcf2S797hWEPCRZOwyNThJIYhUfk=",
449
+
"owner": "NixOS",
450
+
"repo": "nixpkgs",
451
+
"rev": "59e69648d345d6e8fef86158c555730fa12af9de",
452
+
"type": "github"
453
+
},
454
+
"original": {
455
+
"owner": "NixOS",
456
+
"ref": "nixos-25.05",
457
+
"repo": "nixpkgs",
458
+
"type": "github"
459
+
}
460
+
},
461
+
"nixpkgs-lib": {
462
+
"locked": {
463
+
"lastModified": 1788057806,
464
+
"narHash": "sha256-DTQSMxzDWmT0zhguthvegnVkn7CFqGCv4IHCzk5ZUpM=",
465
+
"owner": "nix-community",
466
+
"repo": "nixpkgs.lib",
467
+
"rev": "596e2e3940e09b2abbeb03f75fa1828c57fcd72c",
468
+
"type": "github"
469
+
},
470
+
"original": {
471
+
"owner": "nix-community",
472
+
"repo": "nixpkgs.lib",
473
+
"type": "github"
474
+
}
475
+
},
476
+
"nixpkgs-lib_2": {
477
+
"locked": {
478
+
"lastModified": 1772328832,
479
+
"narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=",
480
+
"owner": "nix-community",
481
+
"repo": "nixpkgs.lib",
482
+
"rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742",
483
+
"type": "github"
484
+
},
485
+
"original": {
486
+
"owner": "nix-community",
487
+
"repo": "nixpkgs.lib",
488
+
"type": "github"
489
+
}
490
+
},
491
+
"nixpkgs-libvncserver": {
492
+
"locked": {
493
+
"lastModified": 1750111231,
494
+
"narHash": "sha256-3a7Tha/RwYlzH/v3PJrG7+HjOj4c6YOv2K8sqdGsHVQ=",
495
+
"owner": "NixOS",
496
+
"repo": "nixpkgs",
497
+
"rev": "e6f23dc08d3624daab7094b701aa3954923c6bbb",
498
+
"type": "github"
499
+
},
500
+
"original": {
501
+
"owner": "NixOS",
502
+
"repo": "nixpkgs",
503
+
"rev": "e6f23dc08d3624daab7094b701aa3954923c6bbb",
504
+
"type": "github"
505
+
}
506
+
},
507
+
"nixpkgs-stable": {
508
+
"locked": {
509
+
"lastModified": 1787753485,
510
+
"narHash": "sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg=",
511
+
"owner": "NixOS",
512
+
"repo": "nixpkgs",
513
+
"rev": "062346a6d85bc4b49dfaa61c986e9c5be21217d1",
514
+
"type": "github"
515
+
},
516
+
"original": {
517
+
"owner": "NixOS",
518
+
"ref": "nixos-26.05",
519
+
"repo": "nixpkgs",
520
+
"type": "github"
521
+
}
522
+
},
523
+
"nixpkgs_2": {
524
+
"locked": {
525
+
"lastModified": 1789044656,
526
+
"narHash": "sha256-sDGcZgdRVR58ceKz0RmkBtdUomBvu5vzbf6Aw5rUCo0=",
527
+
"rev": "1927682e0d808b4a695910f76562b11e2ddecab4",
528
+
"type": "tarball",
529
+
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1070934.1927682e0d80/nixexprs.tar.xz"
530
+
},
531
+
"original": {
532
+
"type": "tarball",
533
+
"url": "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz"
534
+
}
535
+
},
536
+
"nixpkgs_3": {
207
537
"locked": {
208
538
"lastModified": 1788752844,
209
539
"narHash": "sha256-VaWGJ6+cIYN2erfSecbRV+4ljI185Ty2wUrXyvQbgOw=",
···
219
549
"type": "github"
220
550
}
221
551
},
222
-
"nixpkgs_2": {
552
+
"nixpkgs_4": {
223
553
"locked": {
224
554
"lastModified": 1788894124,
225
555
"narHash": "sha256-guyexwrrF5GBKqjO0eg9LNIvrXn4j2frNak63sDr8zg=",
···
235
565
"type": "github"
236
566
}
237
567
},
238
-
"nixpkgs_3": {
568
+
"nixpkgs_5": {
239
569
"locked": {
240
570
"lastModified": 1779560665,
241
571
"narHash": "sha256-tpyBcxPpcQb8ukyNF7DoCwfSY3VPsxHoYwj00Cayv5o=",
···
251
581
"type": "github"
252
582
}
253
583
},
254
-
"nixpkgs_4": {
584
+
"nixpkgs_6": {
255
585
"locked": {
256
586
"lastModified": 1781074563,
257
587
"narHash": "sha256-md8WlXOlfnIeHeOScMTTHFyf2d6iaTwPl2apR5EQ3P4=",
···
266
596
"type": "indirect"
267
597
}
268
598
},
269
-
"nixpkgs_5": {
599
+
"nixpkgs_7": {
270
600
"locked": {
271
601
"lastModified": 1788881743,
272
602
"narHash": "sha256-151taSq/21cxagiIu7hyMVl68+FbHfwBP4lh6TB6KOM=",
···
279
609
"url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz"
280
610
}
281
611
},
612
+
"nixpkgs_8": {
613
+
"locked": {
614
+
"lastModified": 1790323409,
615
+
"narHash": "sha256-m4DGo58Fza5ImOegtWOeE18nhpSO1IGzsVA6Lpsb4zw=",
616
+
"rev": "e94cb152ed51bd6e24eb4a41f1460252beb52cd2",
617
+
"type": "tarball",
618
+
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1079315.e94cb152ed51/nixexprs.tar.zst"
619
+
},
620
+
"original": {
621
+
"type": "tarball",
622
+
"url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.zst"
623
+
}
624
+
},
625
+
"noctalia": {
626
+
"inputs": {
627
+
"nixpkgs": "nixpkgs_8"
628
+
},
629
+
"locked": {
630
+
"lastModified": 1790523212,
631
+
"narHash": "sha256-Z8SuI0YgAZaF0sumKPBF85PxPtTjpo8jvtphbgoITv8=",
632
+
"owner": "noctalia-dev",
633
+
"repo": "noctalia",
634
+
"rev": "08c30392b1350b4f3d3fb77e23732560559f1638",
635
+
"type": "github"
636
+
},
637
+
"original": {
638
+
"owner": "noctalia-dev",
639
+
"ref": "cachix",
640
+
"repo": "noctalia",
641
+
"type": "github"
642
+
}
643
+
},
644
+
"proxmox-nixos": {
645
+
"inputs": {
646
+
"flake-compat": "flake-compat_2",
647
+
"nixpkgs-libvncserver": "nixpkgs-libvncserver",
648
+
"nixpkgs-stable": "nixpkgs-stable",
649
+
"utils": "utils"
650
+
},
651
+
"locked": {
652
+
"lastModified": 1789217472,
653
+
"narHash": "sha256-5+iviW11rRXppx5/oTkErbauwk+9e3XhENhdKTG6QJI=",
654
+
"owner": "SaumonNet",
655
+
"repo": "proxmox-nixos",
656
+
"rev": "fc773dcf59bf188fcc806c78af635e5917ca2983",
657
+
"type": "github"
658
+
},
659
+
"original": {
660
+
"owner": "SaumonNet",
661
+
"repo": "proxmox-nixos",
662
+
"type": "github"
663
+
}
664
+
},
282
665
"ratlogin": {
283
666
"inputs": {
284
667
"crane": "crane",
···
304
687
},
305
688
"root": {
306
689
"inputs": {
690
+
"agenix": "agenix",
691
+
"catppuccin": "catppuccin",
307
692
"chaotic": "chaotic",
308
693
"llm-agents": "llm-agents",
309
694
"llm-bridge": "llm-bridge",
310
695
"meowtd": "meowtd",
311
-
"nixpkgs": "nixpkgs_5",
312
-
"ratlogin": "ratlogin"
696
+
"niri": "niri",
697
+
"nix-gaming": "nix-gaming",
698
+
"nixpkgs": "nixpkgs_7",
699
+
"noctalia": "noctalia",
700
+
"proxmox-nixos": "proxmox-nixos",
701
+
"ratlogin": "ratlogin",
702
+
"vscode-server": "vscode-server",
703
+
"zen-browser": "zen-browser"
313
704
}
314
705
},
315
706
"rust-overlay": {
···
363
754
"type": "github"
364
755
}
365
756
},
757
+
"systems_3": {
758
+
"locked": {
759
+
"lastModified": 1681028828,
760
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
761
+
"owner": "nix-systems",
762
+
"repo": "default",
763
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
764
+
"type": "github"
765
+
},
766
+
"original": {
767
+
"owner": "nix-systems",
768
+
"repo": "default",
769
+
"type": "github"
770
+
}
771
+
},
772
+
"systems_4": {
773
+
"locked": {
774
+
"lastModified": 1681028828,
775
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
776
+
"owner": "nix-systems",
777
+
"repo": "default",
778
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
779
+
"type": "github"
780
+
},
781
+
"original": {
782
+
"owner": "nix-systems",
783
+
"repo": "default",
784
+
"type": "github"
785
+
}
786
+
},
366
787
"tranquil": {
367
788
"inputs": {
368
789
"nixpkgs": [
···
405
826
"repo": "treefmt-nix",
406
827
"type": "github"
407
828
}
829
+
},
830
+
"utils": {
831
+
"inputs": {
832
+
"systems": "systems_4"
833
+
},
834
+
"locked": {
835
+
"lastModified": 1731533236,
836
+
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
837
+
"owner": "numtide",
838
+
"repo": "flake-utils",
839
+
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
840
+
"type": "github"
841
+
},
842
+
"original": {
843
+
"owner": "numtide",
844
+
"repo": "flake-utils",
845
+
"type": "github"
846
+
}
847
+
},
848
+
"vscode-server": {
849
+
"inputs": {
850
+
"flake-parts": "flake-parts_3"
851
+
},
852
+
"locked": {
853
+
"lastModified": 1784312229,
854
+
"narHash": "sha256-2uHCSUw341o3my1R0U0YCfbnMEazylxb58evWsjGL50=",
855
+
"owner": "nix-community",
856
+
"repo": "nixos-vscode-server",
857
+
"rev": "2f984dfbe7e5271b5c413d3e734374cc1306c921",
858
+
"type": "github"
859
+
},
860
+
"original": {
861
+
"owner": "nix-community",
862
+
"repo": "nixos-vscode-server",
863
+
"type": "github"
864
+
}
865
+
},
866
+
"zen-browser": {
867
+
"inputs": {
868
+
"home-manager": "home-manager_3",
869
+
"nixpkgs": [
870
+
"nixpkgs"
871
+
]
872
+
},
873
+
"locked": {
874
+
"lastModified": 1790568199,
875
+
"narHash": "sha256-h3AHjsOZr9iBEkNfK+Zh7/DoN5iMRpJd/6h/5NxCz9I=",
876
+
"owner": "0xc000022070",
877
+
"repo": "zen-browser-flake",
878
+
"rev": "e50ed94ebf28bae95397e482dbb1c020f50c20c1",
879
+
"type": "github"
880
+
},
881
+
"original": {
882
+
"owner": "0xc000022070",
883
+
"repo": "zen-browser-flake",
884
+
"type": "github"
885
+
}
408
886
}
409
887
},
410
888
"root": "root",
+12
flake.nix
+12
flake.nix
···
17
17
inputs.ratlogin.url = "git+https://tangled.org/ptr.pet/ratlogin";
18
18
inputs.ratlogin.inputs.nixpkgs.follows = "nixpkgs";
19
19
20
+
inputs.agenix.url = "github:ryantm/agenix/b027ee29d959fda4b60b57566d64c98a202e0feb";
21
+
inputs.proxmox-nixos.url = "github:SaumonNet/proxmox-nixos";
22
+
inputs.vscode-server.url = "github:nix-community/nixos-vscode-server";
23
+
inputs.catppuccin.url = "github:catppuccin/nix";
24
+
inputs.noctalia.url = "github:noctalia-dev/noctalia/cachix";
25
+
inputs.niri.url = "github:niri-wm/niri";
26
+
inputs.niri.inputs.nixpkgs.follows = "nixpkgs";
27
+
inputs.nix-gaming.url = "github:fufexan/nix-gaming";
28
+
inputs.nix-gaming.inputs.nixpkgs.follows = "nixpkgs";
29
+
inputs.zen-browser.url = "github:0xc000022070/zen-browser-flake";
30
+
inputs.zen-browser.inputs.nixpkgs.follows = "nixpkgs";
31
+
20
32
outputs =
21
33
flakeInputs:
22
34
let
+6
hosts/default.nix
+6
hosts/default.nix
···
36
36
chernobog = allPkgsSets.x86_64-linux;
37
37
trimounts = allPkgsSets.x86_64-linux;
38
38
pupos = allPkgsSets.x86_64-linux;
39
+
focalor = allPkgsSets.x86_64-linux;
40
+
valefar = allPkgsSets.x86_64-linux // {
41
+
pkgs = allPkgsSets.x86_64-linux.pkgs.appendOverlays [
42
+
allPkgsSets.x86_64-linux.inputs.proxmox-nixos.overlays.x86_64-linux
43
+
];
44
+
};
39
45
};
40
46
in
41
47
lib.mapAttrs mkSystem systems
+115
hosts/focalor/default.nix
+115
hosts/focalor/default.nix
···
1
+
{
2
+
inputs,
3
+
lib,
4
+
pkgs,
5
+
tlib,
6
+
...
7
+
}:
8
+
let
9
+
system = pkgs.stdenv.hostPlatform.system;
10
+
in
11
+
{
12
+
imports = [
13
+
"${inputs.home}/nixos"
14
+
inputs.catppuccin.nixosModules.catppuccin
15
+
inputs.nix-gaming.nixosModules.platformOptimizations
16
+
inputs.noctalia.nixosModules.default
17
+
inputs.vscode-server.nixosModules.default
18
+
../../modules
19
+
../../modules/stylix-null.nix
20
+
../../users/regent
21
+
./hardware.nix
22
+
]
23
+
++ (tlib.importFolder (toString ./modules));
24
+
25
+
home-manager = {
26
+
useGlobalPkgs = true;
27
+
backupFileExtension = "HMBackup";
28
+
extraSpecialArgs = { inherit inputs system; };
29
+
users.regent.imports = [
30
+
../../users/regent/home.nix
31
+
inputs.catppuccin.homeModules.catppuccin
32
+
inputs.noctalia.homeModules.default
33
+
];
34
+
};
35
+
36
+
modules.llama-cpp.enable = true;
37
+
38
+
system.stateVersion = "25.05";
39
+
catppuccin = {
40
+
enable = false;
41
+
autoEnable = false;
42
+
};
43
+
44
+
boot = {
45
+
binfmt.emulatedSystems = [ "aarch64-linux" ];
46
+
kernelModules = [ "nct6775" ];
47
+
loader = {
48
+
systemd-boot.enable = true;
49
+
efi.canTouchEfiVariables = true;
50
+
};
51
+
supportedFilesystems = [ "nfs" ];
52
+
};
53
+
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
54
+
nix.settings = {
55
+
trusted-users = lib.mkForce [ "root" ];
56
+
extra-platforms = [ "aarch64-linux" ];
57
+
extra-substituters = [
58
+
"https://noctalia.cachix.org"
59
+
"https://cache.numtide.com"
60
+
];
61
+
extra-trusted-public-keys = [
62
+
"noctalia.cachix.org-1:pCOR47nnMEo5thcxNDtzWpOxNFQsBRglJzxWPp3dkU4="
63
+
"niks3.numtide.com-1:DTx8wZduET09hRmMtKdQDxNNthLQETkc/yaX7M4qK0g="
64
+
];
65
+
};
66
+
67
+
time.timeZone = "America/New_York";
68
+
i18n.defaultLocale = "en_US.UTF-8";
69
+
environment.variables.EDITOR = lib.mkForce "vim";
70
+
programs.nix-ld.enable = true;
71
+
72
+
environment.systemPackages = with pkgs; [
73
+
inputs.agenix.packages.${system}.default
74
+
(prismlauncher.override {
75
+
jdks = [
76
+
jdk8
77
+
jdk11
78
+
jdk17
79
+
jdk21
80
+
jdk25
81
+
];
82
+
})
83
+
temurin-bin
84
+
signal-desktop
85
+
google-chrome
86
+
osu-lazer-bin
87
+
qpwgraph
88
+
easyeffects
89
+
pavucontrol
90
+
inputs.llm-agents.packages.${system}.omp
91
+
inputs.llm-agents.packages.${system}.pi
92
+
inputs.llm-agents.packages.${system}.beads
93
+
imagemagick
94
+
smartmontools
95
+
ffmpeg
96
+
nodejs_26
97
+
vim
98
+
wget
99
+
fastfetch
100
+
lsof
101
+
btop
102
+
git
103
+
openssl
104
+
stdenv
105
+
gnumake
106
+
parted
107
+
zfs
108
+
nixos-generators
109
+
sqlite
110
+
bun
111
+
inputs.llm-agents.packages.${system}.prime-agent
112
+
unzip
113
+
uv
114
+
];
115
+
}
+58
hosts/focalor/hardware.nix
+58
hosts/focalor/hardware.nix
···
1
+
{
2
+
config,
3
+
lib,
4
+
modulesPath,
5
+
...
6
+
}:
7
+
{
8
+
imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
9
+
10
+
boot.initrd.availableKernelModules = [
11
+
"nvme"
12
+
"xhci_pci"
13
+
"ahci"
14
+
"uas"
15
+
"usbhid"
16
+
"sd_mod"
17
+
];
18
+
boot.kernelModules = [ "kvm-amd" ];
19
+
20
+
fileSystems = {
21
+
"/" = {
22
+
device = "/dev/sda2";
23
+
fsType = "btrfs";
24
+
options = [
25
+
"subvol=root"
26
+
"compress=zstd:3"
27
+
"noatime"
28
+
];
29
+
};
30
+
"/home" = {
31
+
device = "/dev/sda2";
32
+
fsType = "btrfs";
33
+
options = [
34
+
"subvol=home"
35
+
"compress=zstd:3"
36
+
"noatime"
37
+
];
38
+
};
39
+
"/nix" = {
40
+
device = "/dev/sda2";
41
+
fsType = "btrfs";
42
+
options = [
43
+
"subvol=nix"
44
+
"compress=zstd:3"
45
+
"noatime"
46
+
];
47
+
};
48
+
"/boot" = {
49
+
device = "/dev/disk/by-uuid/3F27-30E5";
50
+
fsType = "vfat";
51
+
options = [ "umask=0077" ];
52
+
};
53
+
};
54
+
55
+
swapDevices = [ ];
56
+
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
57
+
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
58
+
}
+14
hosts/focalor/modules/dawn.nix
+14
hosts/focalor/modules/dawn.nix
···
1
+
{ pkgs, ... }:
2
+
{
3
+
users.users.dawn = {
4
+
isNormalUser = true;
5
+
createHome = true;
6
+
home = "/home/dawn";
7
+
extraGroups = [ "wheel" ];
8
+
shell = pkgs.bashInteractive;
9
+
hashedPassword = "$y$j9T$TxLlqj0RWsBtIrEhOTyqh1$mfvSCn5j7VAUymWe2/qUTB7.JdwXbqF5qWqUjqQCMu3";
10
+
openssh.authorizedKeys.keys = [
11
+
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDUeDBW4hnHgnT0SjVFeGDztht9owObSmiyWXmmIEGQp2IMPqFpCxkOU61osvfCf4ZT92Ok9iJTohLwFBvHJRD/+CH8/b54sgFAx1lLObkJOMLz4iWZ5y4fNtBYuIA2McQSQoMDpDz6TDym7v7HF7zoUyBmfHMT/9WiX/z6Ft9hY63eh9DcF7WVURzeUvXLApt9wUYUxxdC2KZ/VrDPrIOxCcOgj3le+1zTiD8zwfAGhkzRD3IEx0yCYK6oztrh6WTwA5ZW+cLziH2sVEvSHFa2O398gIvZpzsdYTcQt06d/oyZIvftcpxD8IvjpGgHEsN/mAg0ovexyqAVk+TV/1XySKaoPPVCekap0R50CVD9kEk+GlD78XBYi++aAMIq0/D+NOXkgksfODt3yJPPzQx4KH8gcn0dQJM5zeyTwDfclzMRqCwL1eVHY00EbtG9IcLmMsWk/lM6vpHfyHqHlqNJ3CnUuDBccz9p5ORC1cuj4r9CmXPPmh7OYk7gGiQb4oxuqsYClzp93qmU7qMvGwmxBJaVagNIJgBqb5fsne0OMlcer5CH4L31ozszkSkzCXtFWNoTdgQHU1J3DxxL9WQJCfKku4EPJadYOh80USnauOke5CqfsGtf6uMq4l5Ylcc1QcNhRqxpeTLAIZx0EYDhmQ4eGjAZbiv6ddUp9dAdiQ=="
12
+
];
13
+
};
14
+
}
+155
hosts/focalor/modules/desktop.nix
+155
hosts/focalor/modules/desktop.nix
···
1
+
{
2
+
config,
3
+
inputs,
4
+
lib,
5
+
pkgs,
6
+
...
7
+
}:
8
+
let
9
+
system = pkgs.stdenv.hostPlatform.system;
10
+
in
11
+
{
12
+
programs = {
13
+
niri = {
14
+
enable = true;
15
+
package = inputs.niri.packages.${system}.niri;
16
+
};
17
+
noctalia = {
18
+
enable = true;
19
+
recommendedServices.enable = true;
20
+
};
21
+
steam = {
22
+
enable = true;
23
+
platformOptimizations.enable = true;
24
+
};
25
+
gamescope.enable = true;
26
+
dconf.enable = true;
27
+
obs-studio = {
28
+
enable = true;
29
+
enableVirtualCamera = true;
30
+
plugins = [ pkgs.obs-studio-plugins.droidcam-obs ];
31
+
};
32
+
};
33
+
34
+
services = {
35
+
greetd = {
36
+
enable = true;
37
+
settings.default_session = {
38
+
command = "${pkgs.tuigreet}/bin/tuigreet --time --remember --cmd niri-session";
39
+
user = "greeter";
40
+
};
41
+
};
42
+
xserver.enable = true;
43
+
displayManager.sddm.enable = true;
44
+
displayManager.defaultSession = lib.mkForce "niri";
45
+
desktopManager.plasma6.enable = true;
46
+
xrdp = {
47
+
enable = true;
48
+
defaultWindowManager = "startplasma-x11";
49
+
openFirewall = true;
50
+
};
51
+
dbus.enable = true;
52
+
gvfs.enable = true;
53
+
gnome.gnome-keyring.enable = true;
54
+
upower.enable = true;
55
+
power-profiles-daemon.enable = true;
56
+
blueman.enable = true;
57
+
pipewire = {
58
+
enable = true;
59
+
alsa.enable = true;
60
+
alsa.support32Bit = true;
61
+
pulse.enable = true;
62
+
};
63
+
};
64
+
65
+
security = {
66
+
polkit.enable = true;
67
+
rtkit.enable = true;
68
+
pam.services.greetd.enableGnomeKeyring = true;
69
+
};
70
+
71
+
hardware = {
72
+
bluetooth = {
73
+
enable = true;
74
+
powerOnBoot = true;
75
+
};
76
+
graphics.enable = true;
77
+
nvidia = {
78
+
modesetting.enable = true;
79
+
powerManagement.enable = false;
80
+
powerManagement.finegrained = false;
81
+
open = true;
82
+
nvidiaSettings = true;
83
+
package = config.boot.kernelPackages.nvidiaPackages.latest;
84
+
};
85
+
};
86
+
services.xserver.videoDrivers = [ "nvidia" ];
87
+
88
+
environment = {
89
+
variables = {
90
+
GBM_BACKEND = "nvidia-drm";
91
+
__GLX_VENDOR_LIBRARY_NAME = "nvidia";
92
+
};
93
+
sessionVariables.NIXOS_OZONE_WL = "1";
94
+
systemPackages = with pkgs; [
95
+
kitty
96
+
vscode
97
+
zed-editor
98
+
fastfetch
99
+
hyfetch
100
+
pamixer
101
+
zellij
102
+
firefox
103
+
chromium
104
+
kpcli
105
+
eyedropper
106
+
krita
107
+
thunar
108
+
libreoffice
109
+
signal-desktop
110
+
haruna
111
+
(symlinkJoin {
112
+
name = "equibop-wrapped";
113
+
paths = [ equibop ];
114
+
nativeBuildInputs = [ makeWrapper ];
115
+
postBuild = ''
116
+
wrapProgram $out/bin/equibop \
117
+
--add-flags "--disable-features=WebRtcAllowInputVolumeAdjustment"
118
+
'';
119
+
})
120
+
inputs.zen-browser.packages.${system}.default
121
+
grim
122
+
slurp
123
+
wl-clipboard
124
+
xwayland-satellite
125
+
];
126
+
};
127
+
128
+
fonts = {
129
+
packages = with pkgs; [
130
+
nerd-fonts.fira-code
131
+
comic-neue
132
+
comic-mono
133
+
corefonts
134
+
];
135
+
fontconfig.defaultFonts = {
136
+
sansSerif = [
137
+
"Comic Neue"
138
+
"Comic Sans MS"
139
+
];
140
+
serif = [
141
+
"Comic Neue"
142
+
"Comic Sans MS"
143
+
];
144
+
monospace = [ "Comic Mono" ];
145
+
};
146
+
};
147
+
148
+
xdg.portal = {
149
+
enable = true;
150
+
extraPortals = with pkgs; [
151
+
xdg-desktop-portal-gtk
152
+
xdg-desktop-portal-gnome
153
+
];
154
+
};
155
+
}
+48
hosts/focalor/modules/llama-cpp.nix
+48
hosts/focalor/modules/llama-cpp.nix
···
1
+
{
2
+
config,
3
+
lib,
4
+
pkgs,
5
+
...
6
+
}:
7
+
8
+
let
9
+
cfg = config.modules.llama-cpp;
10
+
llamaCppCuda = pkgs.llama-cpp.override { cudaSupport = true; };
11
+
in
12
+
{
13
+
options.modules.llama-cpp.enable = lib.mkEnableOption "the llama.cpp Gemma service";
14
+
15
+
config = lib.mkIf cfg.enable {
16
+
environment.systemPackages = [ llamaCppCuda ];
17
+
18
+
networking.firewall.interfaces.br0.allowedTCPPorts = [ 8080 ];
19
+
20
+
systemd.services.llama-gemma = {
21
+
description = "Gemma 4 12B Unified via llama.cpp";
22
+
after = [ "network-online.target" ];
23
+
wants = [ "network-online.target" ];
24
+
wantedBy = [ "multi-user.target" ];
25
+
26
+
environment = {
27
+
HOME = "/home/regent";
28
+
XDG_CACHE_HOME = "/home/regent/.cache";
29
+
CUDA_VISIBLE_DEVICES = "1";
30
+
LD_LIBRARY_PATH = "/run/opengl-driver/lib:/run/opengl-driver-32/lib";
31
+
};
32
+
33
+
serviceConfig = {
34
+
Type = "simple";
35
+
User = "regent";
36
+
Group = "users";
37
+
WorkingDirectory = "/home/regent";
38
+
ExecStart = ''
39
+
/home/regent/Developer/llama.cpp-gemma4/build-cuda-gcc14/bin/llama-server -m /home/regent/models/gemma-4-12b-unsloth-2026-07-17/gemma-4-12b-it-Q4_K_M.gguf --mmproj /home/regent/models/gemma-4-12b-unsloth-2026-07-17/mmproj-F16.gguf --lora-scaled /home/regent/Developer/web-extract-sft/artifacts/runs/query-preview-gemma4-12b-r16a8-v11/adapters/step192-f16.gguf:0.5 --model-draft /home/regent/models/gemma-4-12b/gemma-4-12B-it-qat-assistant-MTP-Q8_0.gguf --spec-type draft-mtp --spec-draft-n-max 4 --alias gemma-4-12b,gemma4 --host 10.0.0.13 --port 8080 --device CUDA0 --split-mode none --n-gpu-layers all --ctx-size 12288 --flash-attn on --parallel 1 --threads 2 --threads-batch 4 --batch-size 2048 --ubatch-size 512 --cont-batching --jinja --reasoning off --cache-ram 0 --metrics --no-webui
40
+
'';
41
+
Restart = "on-failure";
42
+
RestartSec = "5s";
43
+
TimeoutStartSec = "infinity";
44
+
LimitNOFILE = 1048576;
45
+
};
46
+
};
47
+
};
48
+
}
+79
hosts/focalor/modules/network.nix
+79
hosts/focalor/modules/network.nix
···
1
+
{
2
+
networking = {
3
+
hostName = "focalor";
4
+
hostId = "84bdc587";
5
+
useDHCP = false;
6
+
nameservers = [
7
+
"10.0.0.210"
8
+
"1.1.1.1"
9
+
];
10
+
firewall = {
11
+
enable = true;
12
+
trustedInterfaces = [ "tailscale0" ];
13
+
allowedTCPPorts = [
14
+
22
15
+
3002
16
+
];
17
+
};
18
+
networkmanager = {
19
+
enable = true;
20
+
unmanaged = [
21
+
"interface-name:enp5s0"
22
+
"interface-name:br0"
23
+
];
24
+
};
25
+
};
26
+
27
+
systemd.network = {
28
+
enable = true;
29
+
wait-online.extraArgs = [ "--interface=enp4s0" ];
30
+
netdevs.br0.netdevConfig = {
31
+
Name = "br0";
32
+
Kind = "bridge";
33
+
};
34
+
networks = {
35
+
"10-lan" = {
36
+
matchConfig.Name = [
37
+
"enp5s0"
38
+
"vm-*"
39
+
];
40
+
networkConfig.Bridge = "br0";
41
+
};
42
+
"10-lan-bridge" = {
43
+
matchConfig.Name = "br0";
44
+
networkConfig = {
45
+
Address = [ "10.0.0.13/24" ];
46
+
Gateway = "10.0.0.1";
47
+
DNS = [
48
+
"10.0.0.210"
49
+
"1.1.1.1"
50
+
];
51
+
IPv6AcceptRA = true;
52
+
};
53
+
linkConfig.RequiredForOnline = "routable";
54
+
};
55
+
};
56
+
};
57
+
58
+
services = {
59
+
openssh.enable = true;
60
+
printing.enable = true;
61
+
tailscale = {
62
+
enable = true;
63
+
useRoutingFeatures = "both";
64
+
extraUpFlags = [ "--login-server=https://vpn.klbr.net" ];
65
+
};
66
+
resolved = {
67
+
enable = true;
68
+
settings.Resolve = {
69
+
DNSSEC = "true";
70
+
Domains = [ "~." ];
71
+
FallbackDNS = [
72
+
"10.0.0.210"
73
+
"1.0.0.1#one.one.one.one"
74
+
];
75
+
DNSOverTLS = "true";
76
+
};
77
+
};
78
+
};
79
+
}
+61
hosts/focalor/modules/services.nix
+61
hosts/focalor/modules/services.nix
···
1
+
{ pkgs, ... }:
2
+
{
3
+
services = {
4
+
syncthing = {
5
+
enable = true;
6
+
openDefaultPorts = true;
7
+
user = "regent";
8
+
dataDir = "/home/regent";
9
+
configDir = "/home/regent/.config/syncthing";
10
+
};
11
+
vscode-server = {
12
+
enable = true;
13
+
nodejsPackage = pkgs.nodejs_24;
14
+
};
15
+
udev.extraRules = ''
16
+
KERNEL=="hidraw*", ATTRS{idVendor}=="1b1c", MODE="0666"
17
+
'';
18
+
};
19
+
20
+
systemd.services = {
21
+
custom-fan-control = {
22
+
description = "Custom Ryzen CPU Fan Control Daemon";
23
+
wantedBy = [ "multi-user.target" ];
24
+
after = [ "multi-user.target" ];
25
+
serviceConfig = {
26
+
Type = "simple";
27
+
ExecStart = "${pkgs.python3}/bin/python -u /home/regent/Developer/fan_control.py";
28
+
Restart = "always";
29
+
RestartSec = 5;
30
+
};
31
+
};
32
+
osu-keysounds = {
33
+
description = "osu! Typing Sounds Daemon";
34
+
wantedBy = [ "multi-user.target" ];
35
+
after = [
36
+
"sound.target"
37
+
"pipewire.service"
38
+
];
39
+
serviceConfig = {
40
+
Type = "simple";
41
+
User = "regent";
42
+
SupplementaryGroups = [ "input" ];
43
+
Environment = [
44
+
"XDG_RUNTIME_DIR=/run/user/1000"
45
+
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus"
46
+
"HOME=/home/regent"
47
+
"OSUCLACK_VOLUME=1.0"
48
+
];
49
+
WorkingDirectory = "/home/regent/Developer";
50
+
ExecStart = "/home/regent/Developer/osuclack";
51
+
Restart = "always";
52
+
RestartSec = 3;
53
+
};
54
+
};
55
+
};
56
+
57
+
virtualisation.docker = {
58
+
enable = true;
59
+
enableOnBoot = true;
60
+
};
61
+
}
+64
hosts/valefar/boot-resilience.nix
+64
hosts/valefar/boot-resilience.nix
···
1
+
# Keep valefar reachable over the network through a bad boot: it lives
2
+
# headless, so a boot that stalls at a local console is a boot we cannot fix.
3
+
{ config, lib, ... }:
4
+
let
5
+
lanMac = "54:fb:66:01:74:ca";
6
+
in
7
+
{
8
+
# Vaultwarden, sonarr, prowlarr, radarr and several host-network containers
9
+
# bind 100.64.0.11, which only exists once tailscaled is up. Without this
10
+
# they fail with EADDRNOTAVAIL at boot and vaultwarden hits its start limit.
11
+
boot.kernel.sysctl = {
12
+
"net.ipv4.ip_nonlocal_bind" = 1;
13
+
"net.ipv6.ip_nonlocal_bind" = 1;
14
+
};
15
+
16
+
# These write under /storage. Order them after zfs-mount and refuse to
17
+
# start if /storage is not actually mounted, rather than writing into the
18
+
# root filesystem underneath the mountpoint.
19
+
systemd.services =
20
+
lib.genAttrs
21
+
[
22
+
"docker"
23
+
"jellyfin"
24
+
"sonarr"
25
+
"radarr"
26
+
"container@pia-qbittorrent"
27
+
"radio"
28
+
]
29
+
(_: {
30
+
after = [ "zfs-mount.service" ];
31
+
requires = [ "zfs-mount.service" ];
32
+
unitConfig.AssertPathIsMountPoint = "/storage";
33
+
});
34
+
35
+
# A failed fstab mount would otherwise stop in emergency.target with no
36
+
# network. Carry on to multi-user so sshd and tailscale come up.
37
+
systemd.enableEmergencyMode = false;
38
+
39
+
# Stage-1 SSH on the LAN (port 2222, root, regent's keys) for boots that
40
+
# stall before switch-root. Host key is generated once on the host:
41
+
# ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key
42
+
boot.initrd.systemd.enable = true;
43
+
boot.initrd.availableKernelModules = [ "r8169" ];
44
+
boot.initrd.network = {
45
+
enable = true;
46
+
# Drop the stage-1 address so stage 2 can enslave the NIC to vmbr0 cleanly.
47
+
flushBeforeStage2 = true;
48
+
ssh = {
49
+
enable = true;
50
+
port = 2222;
51
+
hostKeys = [ "/etc/secrets/initrd/ssh_host_ed25519_key" ];
52
+
authorizedKeys = config.users.users.regent.openssh.authorizedKeys.keys;
53
+
};
54
+
};
55
+
boot.initrd.systemd.network = {
56
+
enable = true;
57
+
networks."10-lan" = {
58
+
matchConfig.PermanentMACAddress = lanMac;
59
+
address = [ "10.0.0.30/24" ];
60
+
gateway = [ "10.0.0.1" ];
61
+
linkConfig.RequiredForOnline = "routable";
62
+
};
63
+
};
64
+
}
+430
hosts/valefar/default.nix
+430
hosts/valefar/default.nix
···
1
+
{
2
+
config,
3
+
lib,
4
+
pkgs,
5
+
inputs,
6
+
...
7
+
}:
8
+
{
9
+
imports = [
10
+
"${inputs.agenix}/modules/age.nix"
11
+
"${inputs.home}/nixos"
12
+
inputs.proxmox-nixos.nixosModules.proxmox-ve
13
+
../../modules
14
+
../../users/regent
15
+
./hardware.nix
16
+
./secrets.nix
17
+
./boot-resilience.nix
18
+
./nat-guard.nix
19
+
./pia-qbittorrent.nix
20
+
./pia-exit.nix
21
+
./wg-mesh.nix
22
+
];
23
+
24
+
services.proxmox-ve = {
25
+
enable = true;
26
+
ipAddress = "10.0.0.30";
27
+
bridges = [ "vmbr0" ];
28
+
};
29
+
30
+
nix.gc = {
31
+
automatic = lib.mkForce true;
32
+
dates = "weekly";
33
+
options = "--delete-older-than 14d";
34
+
};
35
+
nix.settings = {
36
+
auto-optimise-store = true;
37
+
trusted-users = lib.mkForce [
38
+
"root"
39
+
"regent"
40
+
];
41
+
substituters = [ "https://cache.saumon.network/proxmox-nixos" ];
42
+
trusted-public-keys = [ "proxmox-nixos:D9RYSWpQQC/msZUWphOY2I5RLH5Dd6yQcaHIuug7dWM=" ];
43
+
};
44
+
services.journald.settings.Journal.SystemMaxUse = "1G";
45
+
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
46
+
time.timeZone = "America/New_York";
47
+
i18n.defaultLocale = "en_US.UTF-8";
48
+
programs.nix-ld.enable = true;
49
+
services.openssh.enable = true;
50
+
services.tailscale = {
51
+
enable = true;
52
+
useRoutingFeatures = "both";
53
+
extraUpFlags = [ "--login-server=https://vpn.klbr.net" ];
54
+
};
55
+
boot.loader = {
56
+
systemd-boot.enable = true;
57
+
efi.canTouchEfiVariables = true;
58
+
};
59
+
fileSystems."/boot".options = [ "umask=0077" ];
60
+
hardware.graphics.enable = true;
61
+
services.xserver.videoDrivers = [ "nvidia" ];
62
+
hardware.nvidia = {
63
+
modesetting.enable = true;
64
+
powerManagement.enable = false;
65
+
powerManagement.finegrained = false;
66
+
open = true;
67
+
nvidiaSettings = true;
68
+
package = config.boot.kernelPackages.nvidiaPackages.latest;
69
+
};
70
+
environment.variables = {
71
+
GBM_BACKEND = "nvidia-drm";
72
+
__GLX_VENDOR_LIBRARY_NAME = "nvidia";
73
+
};
74
+
75
+
services.printing.enable = true;
76
+
77
+
services.searx = {
78
+
enable = true;
79
+
environmentFile = config.age.secrets."searx.env".path;
80
+
redisCreateLocally = false;
81
+
settings = {
82
+
use_default_settings = true;
83
+
server = {
84
+
secret_key = "$SEARX_SECRET_KEY";
85
+
port = 8888;
86
+
bind_address = "0.0.0.0";
87
+
limiter = false;
88
+
image_proxy = true;
89
+
base_url = "http://valefar:8888/";
90
+
};
91
+
engines =
92
+
map
93
+
(name: {
94
+
inherit name;
95
+
disabled = false;
96
+
})
97
+
[
98
+
"bing"
99
+
"duckduckgo"
100
+
"brave"
101
+
"startpage"
102
+
"qwant"
103
+
"mojeek"
104
+
"marginalia"
105
+
"wikipedia"
106
+
]
107
+
++ [
108
+
{
109
+
name = "google";
110
+
disabled = false;
111
+
use_mobile_ui = true;
112
+
}
113
+
];
114
+
search = {
115
+
safe_search = 0;
116
+
formats = [
117
+
"html"
118
+
"json"
119
+
];
120
+
};
121
+
};
122
+
};
123
+
124
+
services.pocket-id = {
125
+
enable = true;
126
+
dataDir = "/var/lib/pocket-id";
127
+
credentials = {
128
+
ENCRYPTION_KEY = config.age.secrets."pocket-id-encryption-key".path;
129
+
MAXMIND_LICENSE_KEY = config.age.secrets."pocket-id-maxmind-license-key".path;
130
+
};
131
+
settings = {
132
+
APP_URL = "https://pocketid.nekomimi.pet";
133
+
DB_CONNECTION_STRING = "pocket-id.db";
134
+
GEOLITE_DB_PATH = "GeoLite2-City.mmdb";
135
+
PORT = 3000;
136
+
TRUST_PROXY = true;
137
+
UPLOAD_PATH = "uploads";
138
+
};
139
+
};
140
+
141
+
networking = {
142
+
useNetworkd = true;
143
+
useDHCP = false;
144
+
hostName = "valefar";
145
+
hostId = "2a07da90";
146
+
firewall.enable = false;
147
+
};
148
+
systemd.network = {
149
+
enable = true;
150
+
links."10-lan" = {
151
+
matchConfig.PermanentMACAddress = "54:fb:66:01:74:ca";
152
+
linkConfig = {
153
+
NamePolicy = "keep kernel database onboard slot path";
154
+
AlternativeNamesPolicy = "database onboard slot path";
155
+
MACAddressPolicy = "persistent";
156
+
WakeOnLan = "magic";
157
+
};
158
+
};
159
+
networks = {
160
+
"10-lan" = {
161
+
matchConfig.PermanentMACAddress = "54:fb:66:01:74:ca";
162
+
networkConfig = {
163
+
Bridge = "vmbr0";
164
+
DHCP = "no";
165
+
LinkLocalAddressing = "no";
166
+
IPv6AcceptRA = false;
167
+
};
168
+
};
169
+
"11-lan-by-name" = {
170
+
matchConfig.Name = "enp5s0";
171
+
networkConfig = {
172
+
Bridge = "vmbr0";
173
+
DHCP = "no";
174
+
LinkLocalAddressing = "no";
175
+
IPv6AcceptRA = false;
176
+
};
177
+
};
178
+
"10-lan-bridge" = {
179
+
matchConfig.Name = "vmbr0";
180
+
networkConfig = {
181
+
Address = [
182
+
"10.0.0.30/24"
183
+
"2601:5c2:8400:26c0::30/64"
184
+
];
185
+
Gateway = "10.0.0.1";
186
+
DNS = [
187
+
"10.0.0.210"
188
+
"1.1.1.1"
189
+
"1.0.0.1"
190
+
];
191
+
IPv6AcceptRA = true;
192
+
};
193
+
routes = [
194
+
{
195
+
Destination = "0.0.0.0/0";
196
+
Gateway = "10.0.0.1";
197
+
}
198
+
];
199
+
linkConfig.RequiredForOnline = "routable";
200
+
};
201
+
};
202
+
netdevs.br0.netdevConfig = {
203
+
Name = "vmbr0";
204
+
Kind = "bridge";
205
+
};
206
+
};
207
+
services.resolved = {
208
+
enable = true;
209
+
settings.Resolve = {
210
+
DNSSEC = "false";
211
+
Domains = [ "~." ];
212
+
FallbackDNS = [
213
+
"10.0.0.210"
214
+
"1.1.1.1"
215
+
];
216
+
DNSOverTLS = "false";
217
+
};
218
+
};
219
+
220
+
boot = {
221
+
supportedFilesystems = [ "zfs" ];
222
+
kernelModules = [
223
+
"nct6775"
224
+
"coretemp"
225
+
];
226
+
zfs = {
227
+
extraPools = [ "storage" ];
228
+
devNodes = "/dev/disk/by-id";
229
+
forceImportAll = true;
230
+
forceImportRoot = true;
231
+
};
232
+
};
233
+
systemd.services.zfs-import-cache.enable = false;
234
+
services.zfs = {
235
+
autoScrub.enable = true;
236
+
trim.enable = true;
237
+
};
238
+
239
+
services.jellyfin = {
240
+
enable = true;
241
+
dataDir = "/storage/jellyfin";
242
+
};
243
+
services.prowlarr = {
244
+
enable = true;
245
+
settings.server = {
246
+
bindaddress = "100.64.0.11";
247
+
port = 9696;
248
+
};
249
+
};
250
+
services.sonarr = {
251
+
enable = true;
252
+
dataDir = "/storage/sonarr";
253
+
settings = {
254
+
server = {
255
+
bindAddress = "100.64.0.11";
256
+
port = 8989;
257
+
};
258
+
update = {
259
+
automatically = false;
260
+
mechanism = "external";
261
+
};
262
+
log.analyticsEnabled = false;
263
+
};
264
+
};
265
+
services.radarr = {
266
+
enable = true;
267
+
dataDir = "/storage/radarr";
268
+
settings = {
269
+
server = {
270
+
bindAddress = "100.64.0.11";
271
+
port = 7878;
272
+
};
273
+
update = {
274
+
automatically = false;
275
+
mechanism = "external";
276
+
};
277
+
log.analyticsEnabled = false;
278
+
};
279
+
};
280
+
systemd.services.radarr.serviceConfig.PrivateUsers = lib.mkForce false;
281
+
systemd.services.sonarr.serviceConfig.PrivateUsers = lib.mkForce false;
282
+
users.users.radarr = {
283
+
isSystemUser = true;
284
+
group = "radarr";
285
+
home = "/storage/radarr";
286
+
uid = config.ids.uids.radarr;
287
+
extraGroups = [ "jellyfin" ];
288
+
};
289
+
users.groups.radarr.gid = config.ids.gids.radarr;
290
+
users.users.sonarr = {
291
+
isSystemUser = true;
292
+
group = "sonarr";
293
+
home = "/storage/sonarr";
294
+
uid = config.ids.uids.sonarr;
295
+
extraGroups = [ "jellyfin" ];
296
+
};
297
+
users.groups.sonarr.gid = config.ids.gids.sonarr;
298
+
services.vaultwarden = {
299
+
enable = true;
300
+
config = {
301
+
DOMAIN = "https://vault.nekomimi.pet";
302
+
ROCKET_ADDRESS = "100.64.0.11";
303
+
ROCKET_PORT = 8222;
304
+
SIGNUPS_ALLOWED = false;
305
+
SSO_ENABLED = true;
306
+
SSO_ONLY = true;
307
+
SSO_PKCE = true;
308
+
SSO_SCOPES = "email profile groups offline_access";
309
+
SSO_AUTHORITY = "https://pocketid.nekomimi.pet";
310
+
};
311
+
environmentFile = config.age.secrets."vaultwarden-oidc.env".path;
312
+
};
313
+
314
+
systemd.tmpfiles.rules = [
315
+
"d /storage/tm_share 0755 regent users"
316
+
"d /storage/media 0755 jellyfin jellyfin -"
317
+
"d /storage/media/.incoming 2775 jellyfin jellyfin -"
318
+
"d /storage/media/tv 2775 jellyfin jellyfin -"
319
+
];
320
+
services.samba = {
321
+
enable = true;
322
+
settings = {
323
+
global = {
324
+
"workgroup" = "WORKGROUP";
325
+
"server string" = "valefar";
326
+
"netbios name" = "valefar";
327
+
"security" = "user";
328
+
"hosts allow" = "100.64.0.0/10 10.0.0.0/24 127.0.0.1 localhost";
329
+
"hosts deny" = "0.0.0.0/0";
330
+
"guest account" = "nobody";
331
+
"map to guest" = "bad user";
332
+
};
333
+
tm_share = {
334
+
path = "/storage/tm_share";
335
+
"valid users" = "regent";
336
+
public = "yes";
337
+
writeable = "yes";
338
+
"force user" = "regent";
339
+
"fruit:aapl" = "yes";
340
+
"fruit:time machine" = "yes";
341
+
"vfs objects" = "catia fruit streams_xattr";
342
+
};
343
+
};
344
+
};
345
+
services.netatalk = {
346
+
enable = true;
347
+
settings.time-machine = {
348
+
path = "/storage/timemachine";
349
+
"valid users" = "regent";
350
+
"time machine" = true;
351
+
};
352
+
};
353
+
services.avahi = {
354
+
enable = true;
355
+
nssmdns4 = true;
356
+
publish = {
357
+
enable = true;
358
+
userServices = true;
359
+
};
360
+
extraServiceFiles.timemachine = ''
361
+
<?xml version="1.0" standalone='no'?>
362
+
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
363
+
<service-group>
364
+
<name replace-wildcards="yes">%h</name>
365
+
<service>
366
+
<type>_smb._tcp</type>
367
+
<port>445</port>
368
+
</service>
369
+
<service>
370
+
<type>_device-info._tcp</type>
371
+
<port>0</port>
372
+
<txt-record>model=TimeCapsule8,119</txt-record>
373
+
</service>
374
+
<service>
375
+
<type>_adisk._tcp</type>
376
+
<txt-record>dk0=adVN=tm_share,adVF=0x82</txt-record>
377
+
<txt-record>sys=waMa=0,adVF=0x100</txt-record>
378
+
</service>
379
+
</service-group>
380
+
'';
381
+
};
382
+
383
+
users.users.niri = {
384
+
isSystemUser = true;
385
+
uid = 988;
386
+
group = "users";
387
+
shell = pkgs.bashInteractive;
388
+
extraGroups = [ "wheel" ];
389
+
openssh.authorizedKeys.keys = [
390
+
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ7Y9Je7H3gC72cgdEH4wifUDsmhKMeU5Z4oL1s1WcSE niri@nekomimi.pet"
391
+
];
392
+
};
393
+
systemd.services.radio = {
394
+
description = "faint signal fm";
395
+
wantedBy = [ "multi-user.target" ];
396
+
wants = [ "network-online.target" ];
397
+
after = [ "network-online.target" ];
398
+
environment.NO_COLOR = "1";
399
+
path = [
400
+
pkgs.ffmpeg
401
+
pkgs.yt-dlp
402
+
];
403
+
serviceConfig = {
404
+
User = "regent";
405
+
Group = "users";
406
+
WorkingDirectory = "/home/regent/radio";
407
+
ExecStart = "/home/regent/radio/target/release/radio";
408
+
Restart = "always";
409
+
RestartSec = "5s";
410
+
};
411
+
};
412
+
services.syncthing = {
413
+
guiAddress = "0.0.0.0:8384";
414
+
enable = true;
415
+
};
416
+
virtualisation.docker = {
417
+
enable = true;
418
+
enableOnBoot = true;
419
+
};
420
+
environment.systemPackages = with pkgs; [
421
+
code-server
422
+
ffmpeg
423
+
yt-dlp
424
+
nodejs
425
+
python3
426
+
smartmontools
427
+
];
428
+
429
+
system.stateVersion = "24.11";
430
+
}
+44
hosts/valefar/hardware.nix
+44
hosts/valefar/hardware.nix
···
1
+
{
2
+
config,
3
+
lib,
4
+
modulesPath,
5
+
...
6
+
}:
7
+
8
+
{
9
+
imports = [
10
+
(modulesPath + "/installer/scan/not-detected.nix")
11
+
];
12
+
13
+
boot.initrd.availableKernelModules = [
14
+
"xhci_pci"
15
+
"ahci"
16
+
"mpt3sas"
17
+
"nvme"
18
+
"usbhid"
19
+
"uas"
20
+
"sd_mod"
21
+
];
22
+
boot.kernelModules = [ "kvm-amd" ];
23
+
24
+
fileSystems."/" = {
25
+
device = "/dev/disk/by-uuid/e02d1d07-3bc8-4d1d-a301-6d589f4b4b6d";
26
+
fsType = "ext4";
27
+
};
28
+
29
+
fileSystems."/boot" = {
30
+
device = "/dev/disk/by-uuid/B3DE-0187";
31
+
fsType = "vfat";
32
+
options = [
33
+
"fmask=0022"
34
+
"dmask=0022"
35
+
];
36
+
};
37
+
38
+
swapDevices = [
39
+
{ device = "/dev/disk/by-uuid/c8f24f31-49e0-486c-9f63-1d31b2e36ce9"; }
40
+
];
41
+
42
+
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
43
+
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
44
+
}
+107
hosts/valefar/nat-guard.nix
+107
hosts/valefar/nat-guard.nix
···
1
+
{ config, lib, ... }:
2
+
let
3
+
nat = config.networking.nat;
4
+
outgoing = lib.optionals (nat.externalInterface != null) [
5
+
"-o"
6
+
nat.externalInterface
7
+
];
8
+
translation =
9
+
if nat.externalIP == null then
10
+
[
11
+
"-j"
12
+
"MASQUERADE"
13
+
]
14
+
else
15
+
[
16
+
"-j"
17
+
"SNAT"
18
+
"--to-source"
19
+
nat.externalIP
20
+
];
21
+
in
22
+
{
23
+
config =
24
+
lib.mkIf (nat.enable && !config.networking.firewall.enable && !config.networking.nftables.enable)
25
+
{
26
+
assertions = [
27
+
{
28
+
assertion = lib.all (name: builtins.stringLength name < 16) (
29
+
nat.internalInterfaces ++ lib.optional (nat.externalInterface != null) nat.externalInterface
30
+
);
31
+
message = "valefar NAT interface names must be at most 15 bytes; use internalIPs for long container names.";
32
+
}
33
+
];
34
+
35
+
systemd.services.nat = {
36
+
wantedBy = [ "multi-user.target" ];
37
+
# Restart in the new generation, not stop before activation then start later.
38
+
stopIfChanged = false;
39
+
serviceConfig = {
40
+
Restart = "on-failure";
41
+
RestartSec = "5s";
42
+
};
43
+
};
44
+
45
+
# A successful oneshot can be stopped or retain stale kernel rules without
46
+
# becoming failed. Check the live rules against the merged NAT configuration.
47
+
systemd.services.nat-healthcheck = {
48
+
description = "Check and repair container NAT";
49
+
after = [ "nat.service" ];
50
+
path = [
51
+
config.networking.firewall.package
52
+
config.systemd.package
53
+
];
54
+
serviceConfig = {
55
+
Type = "oneshot";
56
+
TimeoutStartSec = "60s";
57
+
};
58
+
enableStrictShellChecks = true;
59
+
script = ''
60
+
check_nat() {
61
+
systemctl is-active --quiet nat.service || return 1
62
+
iptables -w 5 -t nat -C POSTROUTING -j nixos-nat-post || return 1
63
+
iptables -w 5 -t filter -C FORWARD -j nixos-filter-forward || return 1
64
+
${lib.concatMapStringsSep "\n" (subnet: ''
65
+
iptables -w 5 -t nat -C nixos-nat-post ${
66
+
lib.escapeShellArgs (
67
+
[
68
+
"-s"
69
+
subnet
70
+
]
71
+
++ outgoing
72
+
++ translation
73
+
)
74
+
} || return 1
75
+
iptables -w 5 -t filter -C nixos-filter-forward ${
76
+
lib.escapeShellArgs (
77
+
[
78
+
"-s"
79
+
subnet
80
+
]
81
+
++ outgoing
82
+
++ [
83
+
"-j"
84
+
"ACCEPT"
85
+
]
86
+
)
87
+
} || return 1
88
+
'') nat.internalIPs}
89
+
}
90
+
91
+
if ! check_nat; then
92
+
echo "container NAT is inactive or incomplete; restarting nat.service" >&2
93
+
systemctl restart nat.service
94
+
check_nat
95
+
fi
96
+
'';
97
+
};
98
+
systemd.timers.nat-healthcheck = {
99
+
wantedBy = [ "timers.target" ];
100
+
timerConfig = {
101
+
OnBootSec = "30s";
102
+
OnUnitInactiveSec = "60s";
103
+
AccuracySec = "1s";
104
+
};
105
+
};
106
+
};
107
+
}
+297
hosts/valefar/pia-exit.nix
+297
hosts/valefar/pia-exit.nix
···
1
+
{
2
+
config,
3
+
lib,
4
+
pkgs,
5
+
...
6
+
}:
7
+
let
8
+
wgAuth = config.age.secrets."pia-wireguard-auth.env".path;
9
+
piaSource = ./pia-manual;
10
+
# PIA rotates and repurposes endpoint fleets without notice. 2026-09-21:
11
+
# the old WireGuard endpoint was retired and OpenVPN tcp/443 adopted.
12
+
# 2026-09-24: the entire 45.88.217.x DC fleet vanished and its :443 began
13
+
# answering as a plain nginx front, so openvpn looped on "Bad encapsulated
14
+
# packet length" (it was reading ASCII "HT" from an HTTP response).
15
+
# Rotation recipe: fetch https://serverlist.piaservers.net/vpninfo/servers/v6
16
+
# (JSON is line 1), pick an "ovpntcp" server, verify it handshakes, change
17
+
# `endpoint` here and in pia-qbittorrent.nix, rebuild.
18
+
endpoint = "206.206.95.51"; # us-washingtondc / ovpntcp
19
+
gateway = "192.168.173.1";
20
+
headscale = "94.237.26.47"; # vpn.klbr.net
21
+
# One-time Tailnet enrollment bootstrap. Kept mounted so a wiped node state
22
+
# re-enrolls itself. Read-only; the autoconnect script only reads it when the
23
+
# node has no valid state, so it is not consulted on a healthy boot.
24
+
authKey = "/home/regent/.pia-exit-authkey";
25
+
# Host-side bootstrap: token + OpenVPN config + credentials land under
26
+
# /run/pia-exit and are bind-mounted read-only into the guest. The container
27
+
# never needs clear-net DNS or HTTPS.
28
+
prepare = pkgs.writeShellScript "pia-exit-openvpn-prepare" ''
29
+
set -euo pipefail
30
+
umask 077
31
+
export PATH=${
32
+
lib.makeBinPath [
33
+
pkgs.bash
34
+
pkgs.coreutils
35
+
pkgs.curl
36
+
pkgs.jq
37
+
]
38
+
}
39
+
set -a
40
+
. ${wgAuth}
41
+
set +a
42
+
token=$(curl --fail --silent --show-error --max-time 30 \
43
+
--form "username=$PIA_USER" --form "password=$PIA_PASS" \
44
+
https://www.privateinternetaccess.com/api/client/v2/token \
45
+
| jq -er '.token | strings | select(length > 0)')
46
+
# OpenVPN token auth: username is the first 62 chars, password the rest.
47
+
printf '%s\n%s\n' "''${token:0:62}" "''${token:62}" > /run/pia-exit/pia-creds
48
+
{
49
+
echo "client"
50
+
echo "dev pia"
51
+
echo "dev-type tun"
52
+
echo "proto tcp"
53
+
echo "remote ${endpoint} 443"
54
+
echo "resolv-retry infinite"
55
+
echo "nobind"
56
+
echo "persist-key"
57
+
echo "persist-tun"
58
+
echo "remote-cert-tls server"
59
+
echo "redirect-gateway def1"
60
+
echo "reneg-sec 0"
61
+
echo "verb 1"
62
+
echo "auth-user-pass /etc/openvpn/pia-creds"
63
+
echo "<ca>"
64
+
cat ${piaSource}/ca.rsa.4096.crt
65
+
echo "</ca>"
66
+
} > /run/pia-exit/pia.ovpn
67
+
'';
68
+
in
69
+
{
70
+
# Host fetches PIA bootstrap material; the container never needs clear-net DNS/HTTPS.
71
+
#
72
+
# NixOS adds the host-side gateway and brings the veth up in ITS OWN postStart,
73
+
# after guest readiness. So the tunnel must NOT live in the guest's boot path:
74
+
# start it from here instead, after the host side exists.
75
+
systemd.services."container@pia-exit" = {
76
+
wants = [ "network-online.target" ];
77
+
after = [ "network-online.target" ];
78
+
preStart = lib.mkBefore "${prepare}";
79
+
postStart = lib.mkAfter ''
80
+
${config.systemd.package}/bin/systemctl -M pia-exit start --no-block tailscaled-autoconnect.service
81
+
${config.systemd.package}/bin/systemctl -M pia-exit start --no-block pia-openvpn.service
82
+
'';
83
+
serviceConfig.RuntimeDirectory = "pia-exit";
84
+
serviceConfig.RuntimeDirectoryMode = "0700";
85
+
serviceConfig.TimeoutStartSec = lib.mkForce "4min";
86
+
};
87
+
# NixOS owns this veth; stop networkd's generic container DHCP/NAT handling.
88
+
systemd.network.networks."40-pia-exit" = {
89
+
matchConfig.Name = "ve-pia-exit";
90
+
linkConfig.Unmanaged = true;
91
+
};
92
+
containers.pia-exit = {
93
+
autoStart = true;
94
+
privateNetwork = true;
95
+
enableTun = true;
96
+
hostAddress = gateway;
97
+
localAddress = "192.168.173.2";
98
+
bindMounts = {
99
+
"/etc/openvpn/pia.ovpn" = {
100
+
hostPath = "/run/pia-exit/pia.ovpn";
101
+
isReadOnly = true;
102
+
};
103
+
"/etc/openvpn/pia-creds" = {
104
+
hostPath = "/run/pia-exit/pia-creds";
105
+
isReadOnly = true;
106
+
};
107
+
# Enrollment key, read-only. Kept so a wiped node state can re-enroll itself.
108
+
"/run/tailscale-authkey" = {
109
+
hostPath = authKey;
110
+
isReadOnly = true;
111
+
};
112
+
};
113
+
config =
114
+
{
115
+
config,
116
+
lib,
117
+
pkgs,
118
+
...
119
+
}:
120
+
{
121
+
networking.useDHCP = false;
122
+
networking.enableIPv6 = false;
123
+
# Static resolver: resolvconf would otherwise inherit the host's 127.0.0.53
124
+
# stub, and systemd-resolved is not running in here.
125
+
networking.nameservers = [ "1.1.1.1" ];
126
+
networking.resolvconf.enable = false;
127
+
environment.etc."resolv.conf".text = "nameserver 1.1.1.1\n";
128
+
# Pin the coordination server so enrollment never depends on DNS at all.
129
+
networking.hosts."${headscale}" = [ "vpn.klbr.net" ];
130
+
networking.interfaces.eth0.ipv4.routes = [
131
+
{
132
+
address = endpoint;
133
+
prefixLength = 32;
134
+
via = gateway;
135
+
}
136
+
];
137
+
environment.systemPackages = with pkgs; [
138
+
bash
139
+
coreutils
140
+
curl
141
+
gnugrep
142
+
gnused
143
+
jq
144
+
openvpn
145
+
iproute2
146
+
];
147
+
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
148
+
networking.firewall = {
149
+
enable = true;
150
+
checkReversePath = "loose";
151
+
allowedUDPPorts = [ 41641 ];
152
+
# Default-deny OUTPUT. Everything leaves through the tunnel, except a
153
+
# narrow control plane so the node can enroll and stay reachable.
154
+
extraCommands = ''
155
+
${pkgs.iptables}/bin/iptables-restore --noflush <<'RULES'
156
+
*filter
157
+
:OUTPUT DROP [0:0]
158
+
:FORWARD DROP [0:0]
159
+
-F OUTPUT
160
+
-F FORWARD
161
+
-A OUTPUT -o lo -j ACCEPT
162
+
-A OUTPUT -o pia -j ACCEPT
163
+
-A OUTPUT -o tailscale0 -j ACCEPT
164
+
-A OUTPUT -o eth0 -d ${endpoint}/32 -p tcp --dport 443 -j ACCEPT
165
+
-A OUTPUT -o eth0 -d ${headscale}/32 -p tcp --dport 443 -j ACCEPT
166
+
-A OUTPUT -o eth0 -d ${headscale}/32 -p udp --dport 3478 -j ACCEPT
167
+
-A OUTPUT -o eth0 -d 1.1.1.1/32 -p udp --dport 53 -j ACCEPT
168
+
-A OUTPUT -o eth0 -d 1.1.1.1/32 -p tcp --dport 53 -j ACCEPT
169
+
-A OUTPUT -o eth0 -d ${gateway}/32 -j ACCEPT
170
+
-A FORWARD -i tailscale0 -o pia -j ACCEPT
171
+
-A FORWARD -i pia -o tailscale0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
172
+
COMMIT
173
+
*nat
174
+
:POSTROUTING ACCEPT [0:0]
175
+
-A POSTROUTING -o pia -s 100.64.0.0/10 -j MASQUERADE
176
+
COMMIT
177
+
RULES
178
+
${pkgs.iptables}/bin/ip6tables -P OUTPUT DROP
179
+
${pkgs.iptables}/bin/ip6tables -P FORWARD DROP
180
+
'';
181
+
};
182
+
# Enrollment also needs the host-side gateway, which NixOS only wires after
183
+
# guest readiness. Keep it out of the boot transaction; the host starts it.
184
+
systemd.services.tailscaled-autoconnect.wantedBy = lib.mkForce [ ];
185
+
services.tailscale = {
186
+
enable = true;
187
+
useRoutingFeatures = "server";
188
+
authKeyFile = "/run/tailscale-authkey";
189
+
extraUpFlags = [
190
+
"--login-server=https://vpn.klbr.net"
191
+
"--hostname=pia-exit"
192
+
"--accept-dns=false"
193
+
"--netfilter-mode=off"
194
+
"--advertise-exit-node"
195
+
];
196
+
};
197
+
# Deliberately NOT wantedBy multi-user.target: the host veth is not wired
198
+
# until after guest readiness. Started from the host postStart instead.
199
+
systemd.services.pia-openvpn = {
200
+
requires = [ "firewall.service" ];
201
+
after = [
202
+
"network-online.target"
203
+
"firewall.service"
204
+
];
205
+
wants = [ "network-online.target" ];
206
+
path = [
207
+
pkgs.curl
208
+
pkgs.iproute2
209
+
pkgs.gnugrep
210
+
];
211
+
serviceConfig = {
212
+
Type = "simple";
213
+
Restart = "always";
214
+
RestartSec = "15s";
215
+
# The endpoint must leave via eth0; the static pin is applied by
216
+
# networkd at guest boot. Wait it out rather than race it.
217
+
ExecStartPre = pkgs.writeShellScript "pia-openvpn-pin" ''
218
+
for i in $(seq 1 30); do
219
+
if ip -4 route get ${endpoint} 2>/dev/null | grep -q " dev eth0"; then
220
+
exit 0
221
+
fi
222
+
sleep 1
223
+
done
224
+
exit 1
225
+
'';
226
+
ExecStart = "${pkgs.openvpn}/bin/openvpn --config /etc/openvpn/pia.ovpn";
227
+
};
228
+
# Fail closed: the unit only counts as up once the tunnel really
229
+
# carries traffic. A failing probe restarts openvpn (Restart=always).
230
+
postStart = ''
231
+
ok=0
232
+
for i in $(seq 1 30); do
233
+
if curl --fail --silent --max-time 5 --interface pia \
234
+
https://1.1.1.1/cdn-cgi/trace | grep -q '^ip='; then
235
+
ok=1
236
+
break
237
+
fi
238
+
sleep 1
239
+
done
240
+
[ "$ok" = 1 ] || exit 1
241
+
systemctl start --no-block pia-tailscale-kick.service
242
+
'';
243
+
};
244
+
# tailscaled holds one long-lived control connection to the headscale
245
+
# server. When the tunnel's default route flips (endpoint rotation or
246
+
# reconnect), that TCP conn silently dies and the backend wedges in
247
+
# NoState forever. Give autoconnect its full 90s window first, then
248
+
# restart tailscaled once if the backend did not reach Running.
249
+
systemd.services.pia-tailscale-kick = {
250
+
description = "Restart tailscaled when its control connection goes stale across a tunnel flip";
251
+
after = [
252
+
"pia-openvpn.service"
253
+
"tailscaled.service"
254
+
];
255
+
path = [
256
+
pkgs.tailscale
257
+
pkgs.jq
258
+
];
259
+
serviceConfig = {
260
+
Type = "oneshot";
261
+
};
262
+
script = ''
263
+
sleep 95
264
+
state=$(tailscale status --json --peers=false 2>/dev/null | jq -r '.BackendState')
265
+
if [ "$state" != "Running" ]; then
266
+
systemctl restart tailscaled.service
267
+
fi
268
+
for i in $(seq 1 30); do
269
+
state=$(tailscale status --json --peers=false 2>/dev/null | jq -r '.BackendState')
270
+
[ "$state" = "Running" ] && exit 0
271
+
sleep 2
272
+
done
273
+
exit 1
274
+
'';
275
+
};
276
+
# Tailnet traffic must not follow the tunnel's redirect-gateway routes
277
+
# (0.0.0.0/1, 128.0.0.0/1 in main); the /10 route is more specific and
278
+
# wins, keeping tailscale0 reachable for exit-node clients.
279
+
systemd.services.pia-exit-tailnet-route = {
280
+
description = "Route tailnet traffic out tailscale0, ahead of the tunnel default";
281
+
after = [ "tailscaled.service" ];
282
+
wants = [ "tailscaled.service" ];
283
+
wantedBy = [ "multi-user.target" ];
284
+
path = [ pkgs.iproute2 ];
285
+
serviceConfig = {
286
+
Type = "oneshot";
287
+
RemainAfterExit = true;
288
+
};
289
+
script = ''
290
+
ip route replace 100.64.0.0/10 dev tailscale0 table main
291
+
'';
292
+
};
293
+
system.stateVersion = "26.05";
294
+
};
295
+
};
296
+
networking.nat.internalIPs = [ "192.168.173.0/24" ];
297
+
}
+1
hosts/valefar/pia-manual/SOURCE
+1
hosts/valefar/pia-manual/SOURCE
···
1
+
vendor copy from pia-foss/manual-connections master, retrieved 2026-09-12; source: https://github.com/pia-foss/manual-connections
+43
hosts/valefar/pia-manual/ca.rsa.4096.crt
+43
hosts/valefar/pia-manual/ca.rsa.4096.crt
···
1
+
-----BEGIN CERTIFICATE-----
2
+
MIIHqzCCBZOgAwIBAgIJAJ0u+vODZJntMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYD
3
+
VQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNV
4
+
BAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIElu
5
+
dGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3Mx
6
+
IDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkB
7
+
FiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzQw
8
+
MzNaFw0zNDA0MTIxNzQwMzNaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
9
+
EzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQg
10
+
QWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UE
11
+
AxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50
12
+
ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVy
13
+
bmV0YWNjZXNzLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALVk
14
+
hjumaqBbL8aSgj6xbX1QPTfTd1qHsAZd2B97m8Vw31c/2yQgZNf5qZY0+jOIHULN
15
+
De4R9TIvyBEbvnAg/OkPw8n/+ScgYOeH876VUXzjLDBnDb8DLr/+w9oVsuDeFJ9K
16
+
V2UFM1OYX0SnkHnrYAN2QLF98ESK4NCSU01h5zkcgmQ+qKSfA9Ny0/UpsKPBFqsQ
17
+
25NvjDWFhCpeqCHKUJ4Be27CDbSl7lAkBuHMPHJs8f8xPgAbHRXZOxVCpayZ2SND
18
+
fCwsnGWpWFoMGvdMbygngCn6jA/W1VSFOlRlfLuuGe7QFfDwA0jaLCxuWt/BgZyl
19
+
p7tAzYKR8lnWmtUCPm4+BtjyVDYtDCiGBD9Z4P13RFWvJHw5aapx/5W/CuvVyI7p
20
+
Kwvc2IT+KPxCUhH1XI8ca5RN3C9NoPJJf6qpg4g0rJH3aaWkoMRrYvQ+5PXXYUzj
21
+
tRHImghRGd/ydERYoAZXuGSbPkm9Y/p2X8unLcW+F0xpJD98+ZI+tzSsI99Zs5wi
22
+
jSUGYr9/j18KHFTMQ8n+1jauc5bCCegN27dPeKXNSZ5riXFL2XX6BkY68y58UaNz
23
+
meGMiUL9BOV1iV+PMb7B7PYs7oFLjAhh0EdyvfHkrh/ZV9BEhtFa7yXp8XR0J6vz
24
+
1YV9R6DYJmLjOEbhU8N0gc3tZm4Qz39lIIG6w3FDAgMBAAGjggFUMIIBUDAdBgNV
25
+
HQ4EFgQUrsRtyWJftjpdRM0+925Y6Cl08SUwggEfBgNVHSMEggEWMIIBEoAUrsRt
26
+
yWJftjpdRM0+925Y6Cl08SWhge6kgeswgegxCzAJBgNVBAYTAlVTMQswCQYDVQQI
27
+
EwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRl
28
+
cm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAw
29
+
HgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0
30
+
ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRl
31
+
aW50ZXJuZXRhY2Nlc3MuY29tggkAnS7684Nkme0wDAYDVR0TBAUwAwEB/zANBgkq
32
+
hkiG9w0BAQ0FAAOCAgEAJsfhsPk3r8kLXLxY+v+vHzbr4ufNtqnL9/1Uuf8NrsCt
33
+
pXAoyZ0YqfbkWx3NHTZ7OE9ZRhdMP/RqHQE1p4N4Sa1nZKhTKasV6KhHDqSCt/dv
34
+
Em89xWm2MVA7nyzQxVlHa9AkcBaemcXEiyT19XdpiXOP4Vhs+J1R5m8zQOxZlV1G
35
+
tF9vsXmJqWZpOVPmZ8f35BCsYPvv4yMewnrtAC8PFEK/bOPeYcKN50bol22QYaZu
36
+
LfpkHfNiFTnfMh8sl/ablPyNY7DUNiP5DRcMdIwmfGQxR5WEQoHL3yPJ42LkB5zs
37
+
6jIm26DGNXfwura/mi105+ENH1CaROtRYwkiHb08U6qLXXJz80mWJkT90nr8Asj3
38
+
5xN2cUppg74nG3YVav/38P48T56hG1NHbYF5uOCske19F6wi9maUoto/3vEr0rnX
39
+
JUp2KODmKdvBI7co245lHBABWikk8VfejQSlCtDBXn644ZMtAdoxKNfR2WTFVEwJ
40
+
iyd1Fzx0yujuiXDROLhISLQDRjVVAvawrAtLZWYK31bY7KlezPlQnl/D9Asxe85l
41
+
8jO5+0LdJ6VyOs/Hd4w52alDW/MFySDZSfQHMTIc30hLBJ8OnCEIvluVQQ2UQvoW
42
+
+no177N9L2Y+M9TcTA62ZyMXShHQGeh20rb4kK8f+iFX8NxtdHVSkxMEFSfDDyQ=
43
+
-----END CERTIFICATE-----
+241
hosts/valefar/pia-qbittorrent.nix
+241
hosts/valefar/pia-qbittorrent.nix
···
1
+
{
2
+
config,
3
+
lib,
4
+
pkgs,
5
+
...
6
+
}:
7
+
let
8
+
wgAuth = config.age.secrets."pia-wireguard-auth.env".path;
9
+
piaSource = ./pia-manual;
10
+
# Same story as pia-exit.nix: PIA rotates endpoint fleets without notice;
11
+
# rotate `endpoint` in lockstep with pia-exit.nix.
12
+
endpoint = "206.206.95.51"; # us-washingtondc / ovpntcp
13
+
gateway = "192.168.172.1";
14
+
# Host-side bootstrap: token + OpenVPN config + credentials under
15
+
# /run/pia-qbittorrent, bind-mounted read-only into the guest.
16
+
prepare = pkgs.writeShellScript "pia-qbittorrent-openvpn-prepare" ''
17
+
set -euo pipefail
18
+
umask 077
19
+
export PATH=${
20
+
lib.makeBinPath [
21
+
pkgs.bash
22
+
pkgs.coreutils
23
+
pkgs.curl
24
+
pkgs.jq
25
+
]
26
+
}
27
+
set -a
28
+
. ${wgAuth}
29
+
set +a
30
+
token=$(curl --fail --silent --show-error --max-time 30 \
31
+
--form "username=$PIA_USER" --form "password=$PIA_PASS" \
32
+
https://www.privateinternetaccess.com/api/client/v2/token \
33
+
| jq -er '.token | strings | select(length > 0)')
34
+
# OpenVPN token auth: username is the first 62 chars, password the rest.
35
+
printf '%s\n%s\n' "''${token:0:62}" "''${token:62}" > /run/pia-qbittorrent/pia-creds
36
+
{
37
+
echo "client"
38
+
echo "dev pia"
39
+
echo "dev-type tun"
40
+
echo "proto tcp"
41
+
echo "remote ${endpoint} 443"
42
+
echo "resolv-retry infinite"
43
+
echo "nobind"
44
+
echo "persist-key"
45
+
echo "persist-tun"
46
+
echo "remote-cert-tls server"
47
+
echo "redirect-gateway def1"
48
+
echo "reneg-sec 0"
49
+
echo "verb 1"
50
+
echo "auth-user-pass /etc/openvpn/pia-creds"
51
+
echo "<ca>"
52
+
cat ${piaSource}/ca.rsa.4096.crt
53
+
echo "</ca>"
54
+
} > /run/pia-qbittorrent/pia.ovpn
55
+
'';
56
+
in
57
+
{
58
+
# Bootstrap on the host: the container never needs clear-net DNS or HTTPS.
59
+
systemd.services."container@pia-qbittorrent" = {
60
+
wants = [ "network-online.target" ];
61
+
after = [ "network-online.target" ];
62
+
preStart = lib.mkBefore "${prepare}";
63
+
# NixOS adds the host-side gateway in its postStart, after guest readiness.
64
+
postStart = lib.mkAfter "${config.systemd.package}/bin/systemctl -M pia-qbittorrent start pia-openvpn.service";
65
+
serviceConfig.RuntimeDirectory = "pia-qbittorrent";
66
+
serviceConfig.RuntimeDirectoryMode = "0700";
67
+
serviceConfig.TimeoutStartSec = lib.mkForce "2min";
68
+
};
69
+
# NixOS owns this veth; prevent networkd's generic container DHCP/NAT setup.
70
+
# networkd matches the full altname too; Unmanaged=true reports Network File: n/a.
71
+
# Unlike this match, iptables must never use the overlong altname.
72
+
systemd.network.networks."40-pia-qbittorrent" = {
73
+
matchConfig.Name = "ve-pia-qbittorrent";
74
+
linkConfig.Unmanaged = true;
75
+
};
76
+
containers.pia-qbittorrent = {
77
+
autoStart = true;
78
+
privateNetwork = true;
79
+
enableTun = true;
80
+
hostAddress = gateway;
81
+
localAddress = "192.168.172.2";
82
+
forwardPorts = [
83
+
{
84
+
containerPort = 8080;
85
+
hostPort = 8081;
86
+
protocol = "tcp";
87
+
}
88
+
];
89
+
bindMounts."/etc/openvpn/pia.ovpn" = {
90
+
hostPath = "/run/pia-qbittorrent/pia.ovpn";
91
+
isReadOnly = true;
92
+
};
93
+
bindMounts."/etc/openvpn/pia-creds" = {
94
+
hostPath = "/run/pia-qbittorrent/pia-creds";
95
+
isReadOnly = true;
96
+
};
97
+
# qBittorrent's ONLY writable host path: the constrained staging shelf.
98
+
bindMounts."/downloads" = {
99
+
hostPath = "/storage/media/.incoming";
100
+
isReadOnly = false;
101
+
};
102
+
config =
103
+
{
104
+
config,
105
+
lib,
106
+
pkgs,
107
+
...
108
+
}:
109
+
{
110
+
networking.useDHCP = false;
111
+
networking.enableIPv6 = false;
112
+
networking.nameservers = [ "1.1.1.1" ];
113
+
networking.resolvconf.enable = false;
114
+
environment.etc."resolv.conf".text = "nameserver 1.1.1.1\n";
115
+
environment.systemPackages = with pkgs; [
116
+
bash
117
+
coreutils
118
+
curl
119
+
gnugrep
120
+
gnused
121
+
jq
122
+
openvpn
123
+
];
124
+
networking.interfaces.eth0.ipv4.routes = [
125
+
{
126
+
address = endpoint;
127
+
prefixLength = 32;
128
+
via = gateway;
129
+
}
130
+
];
131
+
# The bind-mounted staging shelf is Jellyfin-owned on the host (gid 983).
132
+
# Match that numeric group inside the container; qBittorrent only sees
133
+
# the .incoming bind mount, never the final library.
134
+
users.groups.qbittorrent = { };
135
+
users.groups.jellyfin.gid = 983;
136
+
users.users.qbittorrent = {
137
+
isSystemUser = true;
138
+
group = "qbittorrent";
139
+
extraGroups = [ "jellyfin" ];
140
+
home = "/var/lib/qbittorrent";
141
+
createHome = true;
142
+
};
143
+
networking.firewall = {
144
+
enable = true;
145
+
allowedTCPPorts = [ 8080 ];
146
+
# Replace OUTPUT atomically; retain default-deny even on firewall stop.
147
+
# Do not allow arbitrary ESTABLISHED flows to fall back onto eth0.
148
+
extraCommands = ''
149
+
${pkgs.iptables}/bin/iptables-restore --noflush <<'RULES'
150
+
*filter
151
+
:OUTPUT DROP [0:0]
152
+
:FORWARD DROP [0:0]
153
+
-F OUTPUT
154
+
-A OUTPUT -o lo -j ACCEPT
155
+
-A OUTPUT -o pia -j ACCEPT
156
+
-A OUTPUT -o eth0 -d ${endpoint}/32 -p tcp --dport 443 -j ACCEPT
157
+
-A OUTPUT -o eth0 -d 100.64.0.0/10 -p tcp --sport 8080 -m conntrack --ctstate ESTABLISHED --ctdir REPLY -j ACCEPT
158
+
-A OUTPUT -o eth0 -d ${gateway}/32 -p tcp --sport 8080 -m conntrack --ctstate ESTABLISHED --ctdir REPLY -j ACCEPT
159
+
COMMIT
160
+
RULES
161
+
${pkgs.iptables}/bin/ip6tables -P OUTPUT DROP
162
+
${pkgs.iptables}/bin/ip6tables -P FORWARD DROP
163
+
'';
164
+
};
165
+
systemd.services.qbittorrent = {
166
+
wantedBy = [ "pia-openvpn.service" ];
167
+
requires = [ "pia-openvpn.service" ];
168
+
after = [ "pia-openvpn.service" ];
169
+
partOf = [ "pia-openvpn.service" ];
170
+
serviceConfig = {
171
+
User = "qbittorrent";
172
+
Group = "qbittorrent";
173
+
StateDirectory = "qbittorrent";
174
+
WorkingDirectory = "/var/lib/qbittorrent";
175
+
ExecStart = "${pkgs.qbittorrent-nox}/bin/qbittorrent-nox --profile=/var/lib/qbittorrent --webui-port=8080";
176
+
Restart = "on-failure";
177
+
RestartSec = "5s";
178
+
};
179
+
};
180
+
systemd.services.pia-openvpn = {
181
+
requires = [ "firewall.service" ];
182
+
after = [
183
+
"network-online.target"
184
+
"firewall.service"
185
+
];
186
+
wants = [ "network-online.target" ];
187
+
path = [
188
+
pkgs.curl
189
+
pkgs.iproute2
190
+
pkgs.gnugrep
191
+
];
192
+
serviceConfig = {
193
+
Type = "simple";
194
+
Restart = "always";
195
+
RestartSec = "15s";
196
+
ExecStartPre = pkgs.writeShellScript "pia-openvpn-pin" ''
197
+
for i in $(seq 1 30); do
198
+
if ip -4 route get ${endpoint} 2>/dev/null | grep -q " dev eth0"; then
199
+
exit 0
200
+
fi
201
+
sleep 1
202
+
done
203
+
exit 1
204
+
'';
205
+
ExecStart = "${pkgs.openvpn}/bin/openvpn --config /etc/openvpn/pia.ovpn";
206
+
# Replies to tailnet clients must leave via eth0, not the tunnel:
207
+
# the host forwards webui connections from tailnet sources, and the
208
+
# host's conntrack only reverses flows that come back through it.
209
+
# A dedicated table escapes redirect-gateway's 0.0.0.0/1 in main.
210
+
ExecStartPost = pkgs.writeShellScript "pia-openvpn-tailnet-route" ''
211
+
ip rule add to 100.64.0.0/10 lookup 100 priority 100 2>/dev/null || true
212
+
ip route replace default via ${gateway} dev eth0 table 100
213
+
'';
214
+
ExecStopPost = pkgs.writeShellScript "pia-openvpn-tailnet-undo" ''
215
+
ip rule del to 100.64.0.0/10 lookup 100 priority 100 2>/dev/null || true
216
+
'';
217
+
};
218
+
# Fail closed: the unit only counts as up once the tunnel really
219
+
# carries traffic, and qbittorrent requires this unit, so the client
220
+
# never starts on a dead tunnel.
221
+
postStart = ''
222
+
for i in $(seq 1 30); do
223
+
if curl --fail --silent --max-time 5 --interface pia \
224
+
https://1.1.1.1/cdn-cgi/trace | grep -q '^ip='; then
225
+
exit 0
226
+
fi
227
+
sleep 1
228
+
done
229
+
exit 1
230
+
'';
231
+
};
232
+
system.stateVersion = "26.05";
233
+
};
234
+
};
235
+
# systemd shortens long veth names; match the private subnet, not that name.
236
+
networking.nat = {
237
+
enable = true;
238
+
externalInterface = "vmbr0";
239
+
internalIPs = [ "192.168.172.0/24" ];
240
+
};
241
+
}
+26
hosts/valefar/secrets.nix
+26
hosts/valefar/secrets.nix
···
1
+
{
2
+
age.secrets = {
3
+
"pocket-id-encryption-key" = {
4
+
file = ../../secrets/pocket-id-encryption-key.age;
5
+
mode = "0400";
6
+
};
7
+
"pocket-id-maxmind-license-key" = {
8
+
file = ../../secrets/pocket-id-maxmind-license-key.age;
9
+
mode = "0400";
10
+
};
11
+
"pia-wireguard-auth.env" = {
12
+
file = ../../secrets/pia-wireguard-auth.env.age;
13
+
mode = "0400";
14
+
};
15
+
"searx.env" = {
16
+
file = ../../secrets/searx.env.age;
17
+
mode = "0400";
18
+
};
19
+
"vaultwarden-oidc.env" = {
20
+
file = ../../secrets/vaultwarden-oidc.env.age;
21
+
owner = "vaultwarden";
22
+
group = "vaultwarden";
23
+
mode = "0400";
24
+
};
25
+
};
26
+
}
+70
hosts/valefar/tests/nat-guard.nix
+70
hosts/valefar/tests/nat-guard.nix
···
1
+
# Run with the flake's pinned nixpkgs:
2
+
# nix build --impure --expr 'let f = builtins.getFlake (toString ./.); in import ./hosts/valefar/tests/nat-guard.nix { pkgs = f.inputs.nixpkgs.legacyPackages.x86_64-linux; }'
3
+
{ pkgs }:
4
+
pkgs.testers.runNixOSTest {
5
+
name = "valefar-nat-guard";
6
+
nodes.machine = { lib, ... }: {
7
+
imports = [ ../nat-guard.nix ];
8
+
networking.firewall.enable = false;
9
+
networking.nat = {
10
+
enable = true;
11
+
externalInterface = "eth0";
12
+
internalIPs = [
13
+
"192.168.172.0/24"
14
+
"192.168.173.0/24"
15
+
];
16
+
};
17
+
systemd.services.nat = {
18
+
preStart = "test ! -e /run/fail-nat";
19
+
serviceConfig.RestartSec = lib.mkForce "1s";
20
+
};
21
+
systemd.timers.nat-healthcheck.timerConfig = {
22
+
OnBootSec = lib.mkForce "1s";
23
+
OnUnitInactiveSec = lib.mkForce "2s";
24
+
};
25
+
};
26
+
testScript = ''
27
+
start_all()
28
+
rules = " && ".join([
29
+
"systemctl is-active --quiet nat.service",
30
+
"iptables -t nat -C POSTROUTING -j nixos-nat-post",
31
+
"iptables -t filter -C FORWARD -j nixos-filter-forward",
32
+
*[
33
+
f"iptables -t nat -C nixos-nat-post -s {subnet} -o eth0 -j MASQUERADE"
34
+
for subnet in ["192.168.172.0/24", "192.168.173.0/24"]
35
+
],
36
+
])
37
+
38
+
with subtest("boot installs both subnets"):
39
+
machine.wait_for_unit("nat.service")
40
+
machine.wait_for_unit("nat-healthcheck.timer")
41
+
machine.succeed(rules)
42
+
43
+
with subtest("healthy checks do not restart NAT"):
44
+
invocation = machine.succeed("systemctl show nat -p InvocationID --value")
45
+
machine.succeed("systemctl start nat-healthcheck")
46
+
assert machine.succeed("systemctl show nat -p InvocationID --value") == invocation
47
+
48
+
with subtest("timer recovers a successfully stopped unit"):
49
+
machine.succeed("systemctl stop nat")
50
+
machine.wait_until_succeeds(rules)
51
+
52
+
with subtest("timer repairs a missing subnet in an active unit"):
53
+
machine.succeed("iptables -t nat -D nixos-nat-post -s 192.168.173.0/24 -o eth0 -j MASQUERADE")
54
+
machine.wait_until_succeeds(rules)
55
+
56
+
with subtest("timer repairs a detached chain"):
57
+
machine.succeed("iptables -t nat -D POSTROUTING -j nixos-nat-post")
58
+
machine.wait_until_succeeds(rules)
59
+
60
+
with subtest("failed repair is visible and retried"):
61
+
machine.succeed("systemctl stop nat-healthcheck.timer; touch /run/fail-nat")
62
+
machine.fail("systemctl restart nat")
63
+
machine.fail("systemctl start nat-healthcheck")
64
+
machine.succeed("systemctl is-failed nat-healthcheck")
65
+
machine.succeed("mv /run/fail-nat /run/nat-failure-tested")
66
+
machine.wait_until_succeeds(rules)
67
+
machine.succeed("systemctl start nat-healthcheck.timer")
68
+
machine.wait_until_succeeds("test $(systemctl show nat-healthcheck -p Result --value) = success")
69
+
'';
70
+
}
+63
hosts/valefar/wg-mesh.nix
+63
hosts/valefar/wg-mesh.nix
···
1
+
# wisp.place mesh: full-mesh WireGuard between the wisp servers, 10.88.0.0/24.
2
+
# valefar sits behind home NAT, so it dials every peer and keeps the paths open.
3
+
# Names resolve through *.mesh.wisp.place; the peer list lives in ~/fleet/mesh.md.
4
+
{ pkgs, ... }:
5
+
{
6
+
environment.systemPackages = [ pkgs.wireguard-tools ];
7
+
8
+
# Containers publish ports on 10.88.0.10; start docker once wg0 exists.
9
+
systemd.services.docker = {
10
+
after = [ "wg-quick-wg0.service" ];
11
+
wants = [ "wg-quick-wg0.service" ];
12
+
};
13
+
14
+
networking.wg-quick.interfaces.wg0 = {
15
+
address = [ "10.88.0.10/24" ];
16
+
listenPort = 51820;
17
+
privateKeyFile = "/etc/wireguard/wg0.key";
18
+
peers = [
19
+
{
20
+
# baal
21
+
publicKey = "ooHJ1tE5WVfrC4bAX/japIwNahg71tTSNy94k9d5A0Q=";
22
+
allowedIPs = [ "10.88.0.1/32" ];
23
+
endpoint = "150.136.127.67:51820";
24
+
persistentKeepalive = 25;
25
+
}
26
+
{
27
+
# stolas
28
+
publicKey = "BYA0fbXxWvgK4uYoYiNGLscki4lTGAsKn98AKlw32B8=";
29
+
allowedIPs = [ "10.88.0.2/32" ];
30
+
endpoint = "152.53.121.97:51820";
31
+
persistentKeepalive = 25;
32
+
}
33
+
{
34
+
# sjo1
35
+
publicKey = "myd1WecGLdP/DtU198anvcPtEk8Iusa1CASru5kall0=";
36
+
allowedIPs = [ "10.88.0.3/32" ];
37
+
endpoint = "152.44.44.138:51820";
38
+
persistentKeepalive = 25;
39
+
}
40
+
{
41
+
# sin1
42
+
publicKey = "9mAzm703TerlCQoZP61dyJNnMATGVgVuNtdi+JmKWAo=";
43
+
allowedIPs = [ "10.88.0.4/32" ];
44
+
endpoint = "213.163.207.16:51820";
45
+
persistentKeepalive = 25;
46
+
}
47
+
{
48
+
# sharkgirl
49
+
publicKey = "3rgSbuy6oz8ePF55yF8ZBS03MDhj3aOq83/PgO+DYng=";
50
+
allowedIPs = [ "10.88.0.5/32" ];
51
+
endpoint = "15.204.225.63:51820";
52
+
persistentKeepalive = 25;
53
+
}
54
+
{
55
+
# vine (status page)
56
+
publicKey = "Iucesg9GHk9AkjewUIdgOiIBMdLBVzfvrTqLQiNecRI=";
57
+
allowedIPs = [ "10.88.0.6/32" ];
58
+
endpoint = "144.225.80.116:51820";
59
+
persistentKeepalive = 25;
60
+
}
61
+
];
62
+
};
63
+
}
+22
secrets/pia-wireguard-auth.env.age
+22
secrets/pia-wireguard-auth.env.age
···
1
+
age-encryption.org/v1
2
+
-> ssh-ed25519 i9wBeA oUdGLb/WpJmjxW7QHnKrcoXdEaX3vlAIh3PMuEMTFXU
3
+
xM29aQByEm1QlZe0RAbNfs4w8nVFVnFnoEADqKJLZMQ
4
+
-> ssh-ed25519 rgtFBg Dh9lxSdhL5/GwesWVz6iic1oB9hSQaY9a1lJ+MzbXRI
5
+
DGVzyVeCHLz35iVwQtIQ7WWNbkOSvrtXh43JM0B62x8
6
+
-> ssh-rsa J32+GA
7
+
TMQkZcTbx4u9aOfjrGxMRqI+yZdGJlgG1M1Zl+cOAZQxfGlZcFMRmpK+XfiD72No
8
+
k5lGctvMFe/yCjrOwoNN/NiE/JmGVjjXh93V3IoSI743C8KAmoHsgt7XgF0bwwuq
9
+
+HU2uWp6KjoraZQ8XaQCiD9w87HQqptMFhMUVRXxxDPVVGF8sO9SdN5EX5hpOb9t
10
+
eiA8MtyYOnSkwXhsep6Uwac+/OPoc7TOaMWvtIkT1NsMECB4xZ3B8FcFDjwO+jtT
11
+
1eUe0lazmGNkyUf9dSGApfA/c02IUKVqb9drlvYHw0Uj03WDHPLqnGB32i++/Ks+
12
+
Ui/c93wJG743YpeqPadSeZ0UHWFoEPKzgG7NU1rxHq+mvpgMRx/Guc2pkBf9MSrb
13
+
9xL29TMIOxsWiJLjxsun2u9r9AfZoHU/fUJ8Lt1RAOIwV28Jh8tsmF5e+ZaN9NKM
14
+
CkIva/aJOjrGtAknbzPTeX6PY9O7D5YQkzEwUD90Qj4d0CmlLxfbTn6RdsVxsPXR
15
+
gDV77GqCOYKkScsE+QiRwcjPzWR4kv6Su2IOV77/cHewftgfdCYzMytiDSTI4uDq
16
+
1SQRB2HhjHiT9K1otbCJT2dQNPsLxgkZsKWEgmMoCVGl82IOKOT3EEDz6AFiULRD
17
+
bgcMF+S94WbtS8Q/PBdPjIpC54+HzPswlusoOSr1GWI
18
+
-> ssh-ed25519 du7llw qFql+PH0TSCLdumpFTVsTNUOsKQbJO6K7nUUXC4Vrzo
19
+
NvA2H0/6MQ63J2gPqG0mf8txq+/UNNstXdTHyXlrC/U
20
+
--- okqLzInxo2gNlLt6QaamSRGmeYwmMhu0bSTmVYfOPSs
21
+
W�͎%���
22
+
�}�Jw��i�,bl����-����4s�k������RˌG�&&q��j�t����a��P�
secrets/pocket-id-encryption-key.age
secrets/pocket-id-encryption-key.age
This is a binary file and will not be displayed.
secrets/pocket-id-maxmind-license-key.age
secrets/pocket-id-maxmind-license-key.age
This is a binary file and will not be displayed.
+29
secrets/regent.nix
+29
secrets/regent.nix
···
1
+
let
2
+
regent = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ0pU82lV9dSjkgYbdh9utZ5CDM2dPN70S5fBqN1m3Pb regent@orobas.local";
3
+
users = [ regent ];
4
+
5
+
valefar = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIPu8CVFsnUxhvABEqv4+EBBOL8tva5HJFoV3hElAlD0";
6
+
buer = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMVhjwDcO8eleSoR8a37ZGGPvkHEgV+c8SYcy07SayPB";
7
+
focalor = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA518oTmTp5VG60/dBrLu7rlV1hh8muhMattoiGfmrei";
8
+
systems = [
9
+
valefar
10
+
buer
11
+
focalor
12
+
];
13
+
in
14
+
{
15
+
"pocket-id-encryption-key.age".publicKeys = users ++ systems;
16
+
"pocket-id-maxmind-license-key.age".publicKeys = users ++ systems;
17
+
18
+
"searx.env.age".publicKeys = [ valefar ];
19
+
20
+
"vaultwarden-oidc.env.age".publicKeys = [
21
+
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIPu8CVFsnUxhvABEqv4+EBBOL8tva5HJFoV3hElAlD0 root@valefar"
22
+
];
23
+
"pia-wireguard-auth.env.age".publicKeys = [
24
+
regent
25
+
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ7Y9Je7H3gC72cgdEH4wifUDsmhKMeU5Z4oL1s1WcSE niri@nekomimi.pet"
26
+
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCM1KHYX8icUv9XxV0QDgsaXE05nA8gaZ0O3OwP/vH0olpeXc13FxoFKvNVYo1aWCo8xjv9G01JrWNhBRFf76LyOJhL1cm3Psorcx0V7sdbRy9gWnPRR5tA58wKr51yvz/4I+KsrO537cTAVZQky9J2wDh9jDpiNQJN80Kv9RdrFc3BVrEXUrsE3YtsNiSg4YoAPD4vhLupVohMPoh/w3dtdTQBu+YF+1rjMJ9Xf22DaVqz6l95T/Zui1smQnIZbjXRibJ8RR3Kla7K94nqVvNMVURiK71vXh+bJvFE2HyDDNsVteAv2DxpXQJpojxGQUXvR1LGQP9Lm5yhdtTqZMeuTDOKCCTy3yMGX4tX09ZyP5S0WwgTh2vE33C4+gUFa4wSeGZj7IH2t4ivgxK17fkGIeMcPpPuwkIWbinCp5AXPBqly5OBry6Qyg8SN1jb+d6pW3sn3slsikH53B4Qx5PP1ywCSuKnboOn9pQqZt7uvs75W2oyYxdmh/SNx//dcNX16YLYJ1KfaGpxerT6AjfiHSB8Vp8n6N7bVmizsqg0nkU+SmF8gh3UKidyg2RzL+zcJcxp+APiumM0rs3fXFNBuT8VncOPbiyjSw/NUt3EiuBGIap5mnJF+zW965YqZAo+Upivqltdah0E9WJrF4t7Z5+FyuLi3N5ovyROEEAlNw== root@valefar"
27
+
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIPu8CVFsnUxhvABEqv4+EBBOL8tva5HJFoV3hElAlD0 root@valefar"
28
+
];
29
+
}
+1
-1
secrets/secrets.nix
+1
-1
secrets/secrets.nix
secrets/vaultwarden-oidc.env.age
secrets/vaultwarden-oidc.env.age
This is a binary file and will not be displayed.
+42
users/regent/default.nix
+42
users/regent/default.nix
···
1
+
{ lib, pkgs, ... }:
2
+
{
3
+
users.mutableUsers = lib.mkForce true;
4
+
5
+
users.users.regent = {
6
+
isNormalUser = true;
7
+
shell = pkgs.zsh;
8
+
extraGroups = [
9
+
"docker"
10
+
"wheel"
11
+
"input"
12
+
];
13
+
packages = with pkgs; [ tree ];
14
+
openssh.authorizedKeys.keys = [
15
+
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGI8jgqru/3LFgk12C9Zc/NL5di5+jGocQZi/dA73ZRr regent@regentsmacbookair.dns.sharkgirl.pet"
16
+
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCS9VBRE13jojnqVjuUZWTcOK8GokDDlk2U0i61vEJizVzNowGnIAbwq0cOaFEBX4JBkOa4I8Ku2Pw7fODuoehSK/t7FrfXExk2PBT3k0mfzqQYxfq5bzae7AWr7n/sKUBTtvHSACfidxzQpV7VSgW68jqdOt6h7FHSeS2jac7wUNPobL0uCkFB4FiEQOnIqlRGSSabVemL7bC9H9lUyOODSTthiq9S3pPYknyHDRKUtSCSw4pfpasr4bxDVSW99h3GBcW0hZbpw5bwlxQlwbclxQDnn7XJhWpq6zL/2ScVGJgd94z7FshKoF5IFTk6e7a/Ouv4Ato4hRLxEe5u70CH ssh-key-2023-07-11"
17
+
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ7Y9Je7H3gC72cgdEH4wifUDsmhKMeU5Z4oL1s1WcSE niri@nekomimi.pet"
18
+
];
19
+
};
20
+
21
+
users.users.root = {
22
+
openssh.authorizedKeys.keys = [
23
+
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCS9VBRE13jojnqVjuUZWTcOK8GokDDlk2U0i61vEJizVzNowGnIAbwq0cOaFEBX4JBkOa4I8Ku2Pw7fODuoehSK/t7FrfXExk2PBT3k0mfzqQYxfq5bzae7AWr7n/sKUBTtvHSACfidxzQpV7VSgW68jqdOt6h7FHSeS2jac7wUNPobL0uCkFB4FiEQOnIqlRGSSabVemL7bC9H9lUyOODSTthiq9S3pPYknyHDRKUtSCSw4pfpasr4bxDVSW99h3GBcW0hZbpw5bwlxQlwbclxQDnn7XJhWpq6zL/2ScVGJgd94z7FshKoF5IFTk6e7a/Ouv4Ato4hRLxEe5u70CH ssh-key-2023-07-11"
24
+
];
25
+
};
26
+
27
+
programs.git = {
28
+
enable = true;
29
+
config = {
30
+
user.name = "waveringana";
31
+
user.email = "ana@nekomimi.pet";
32
+
init = {
33
+
defaultBranch = "main";
34
+
};
35
+
};
36
+
};
37
+
38
+
security.sudo.enable = true;
39
+
security.sudo.wheelNeedsPassword = false;
40
+
41
+
programs.zsh.enable = true;
42
+
}
+324
users/regent/home.nix
+324
users/regent/home.nix
···
1
+
{ lib, pkgs, ... }:
2
+
{
3
+
4
+
programs.noctalia = {
5
+
enable = true;
6
+
settings = {
7
+
theme = {
8
+
mode = "dark";
9
+
source = "builtin";
10
+
builtin = "Catppuccin";
11
+
};
12
+
};
13
+
};
14
+
15
+
home.username = "regent";
16
+
home.homeDirectory = "/home/regent";
17
+
home.stateVersion = "25.05";
18
+
home.sessionVariables = {
19
+
EDITOR = lib.mkForce "nvim";
20
+
VISUAL = lib.mkForce "nvim";
21
+
};
22
+
23
+
catppuccin = {
24
+
enable = true;
25
+
autoEnable = true;
26
+
flavor = "mocha";
27
+
accent = "rosewater";
28
+
ghostty.enable = true;
29
+
nvim.enable = true;
30
+
};
31
+
32
+
programs.ghostty = {
33
+
enable = true;
34
+
settings = {
35
+
font-size = 18;
36
+
font-family = "Comic Mono";
37
+
theme = "catppuccin-mocha";
38
+
background = "#1e1e1e";
39
+
background-opacity = 0.8;
40
+
background-blur = 8;
41
+
};
42
+
};
43
+
44
+
programs.zsh = {
45
+
enable = true;
46
+
enableCompletion = true;
47
+
autosuggestion.enable = true;
48
+
syntaxHighlighting.enable = true;
49
+
shellAliases = {
50
+
zed = "zeditor";
51
+
};
52
+
oh-my-zsh = {
53
+
enable = true;
54
+
plugins = [ "git" ];
55
+
theme = "robbyrussell";
56
+
};
57
+
};
58
+
59
+
programs.neovim = {
60
+
enable = true;
61
+
defaultEditor = true;
62
+
viAlias = true;
63
+
vimAlias = true;
64
+
withPython3 = false;
65
+
withRuby = false;
66
+
extraPackages = with pkgs; [
67
+
bash-language-server
68
+
lua-language-server
69
+
nil
70
+
nixfmt
71
+
pyright
72
+
ripgrep
73
+
rust-analyzer
74
+
stylua
75
+
typescript-language-server
76
+
];
77
+
plugins = with pkgs.vimPlugins; [
78
+
cmp-nvim-lsp
79
+
gitsigns-nvim
80
+
lualine-nvim
81
+
luasnip
82
+
nvim-cmp
83
+
nvim-lspconfig
84
+
nvim-treesitter.withAllGrammars
85
+
plenary-nvim
86
+
telescope-fzf-native-nvim
87
+
telescope-nvim
88
+
which-key-nvim
89
+
];
90
+
initLua = ''
91
+
vim.g.mapleader = " "
92
+
vim.g.maplocalleader = " "
93
+
vim.opt.number = true
94
+
vim.opt.relativenumber = true
95
+
vim.opt.mouse = "a"
96
+
vim.opt.clipboard = "unnamedplus"
97
+
vim.opt.breakindent = true
98
+
vim.opt.undofile = true
99
+
vim.opt.ignorecase = true
100
+
vim.opt.smartcase = true
101
+
vim.opt.signcolumn = "yes"
102
+
vim.opt.updatetime = 250
103
+
vim.opt.timeoutlen = 300
104
+
vim.opt.splitright = true
105
+
vim.opt.splitbelow = true
106
+
vim.opt.expandtab = true
107
+
vim.opt.shiftwidth = 2
108
+
vim.opt.tabstop = 2
109
+
110
+
require("lualine").setup({ options = { theme = "catppuccin" } })
111
+
require("gitsigns").setup()
112
+
require("which-key").setup()
113
+
114
+
local telescope = require("telescope")
115
+
telescope.setup({ defaults = { path_display = { "smart" } } })
116
+
pcall(telescope.load_extension, "fzf")
117
+
local builtin = require("telescope.builtin")
118
+
vim.keymap.set("n", "<leader>ff", builtin.find_files, { desc = "Find files" })
119
+
vim.keymap.set("n", "<leader>fg", builtin.live_grep, { desc = "Live grep" })
120
+
vim.keymap.set("n", "<leader>fb", builtin.buffers, { desc = "Buffers" })
121
+
vim.keymap.set("n", "<leader>fh", builtin.help_tags, { desc = "Help" })
122
+
123
+
local cmp = require("cmp")
124
+
local luasnip = require("luasnip")
125
+
cmp.setup({
126
+
snippet = { expand = function(args) luasnip.lsp_expand(args.body) end },
127
+
mapping = cmp.mapping.preset.insert({
128
+
["<C-Space>"] = cmp.mapping.complete(),
129
+
["<CR>"] = cmp.mapping.confirm({ select = true }),
130
+
["<Tab>"] = cmp.mapping.select_next_item(),
131
+
["<S-Tab>"] = cmp.mapping.select_prev_item(),
132
+
}),
133
+
sources = cmp.config.sources({ { name = "nvim_lsp" } }),
134
+
})
135
+
136
+
local capabilities = require("cmp_nvim_lsp").default_capabilities()
137
+
for _, server in ipairs({ "bashls", "lua_ls", "nil_ls", "pyright", "rust_analyzer", "ts_ls" }) do
138
+
vim.lsp.config(server, { capabilities = capabilities })
139
+
vim.lsp.enable(server)
140
+
end
141
+
vim.keymap.set("n", "gd", vim.lsp.buf.definition, { desc = "Go to definition" })
142
+
vim.keymap.set("n", "gr", vim.lsp.buf.references, { desc = "References" })
143
+
vim.keymap.set("n", "K", vim.lsp.buf.hover, { desc = "Hover documentation" })
144
+
vim.keymap.set("n", "<leader>rn", vim.lsp.buf.rename, { desc = "Rename" })
145
+
vim.keymap.set("n", "<leader>ca", vim.lsp.buf.code_action, { desc = "Code action" })
146
+
vim.keymap.set("n", "<leader>f", function() vim.lsp.buf.format({ async = true }) end, { desc = "Format" })
147
+
'';
148
+
};
149
+
150
+
home.pointerCursor = {
151
+
enable = true;
152
+
gtk.enable = true;
153
+
package = pkgs.phinger-cursors;
154
+
name = "Phinger-cursors-light";
155
+
size = 32;
156
+
};
157
+
158
+
gtk = {
159
+
enable = true;
160
+
font = {
161
+
name = "Comic Neue";
162
+
size = 11;
163
+
};
164
+
};
165
+
166
+
xdg.configFile."niri/config.kdl".text = ''
167
+
input {
168
+
keyboard {
169
+
xkb {
170
+
layout "us"
171
+
}
172
+
}
173
+
touchpad {
174
+
tap
175
+
natural-scroll
176
+
}
177
+
mouse {
178
+
accel-speed -0.25
179
+
accel-profile "flat"
180
+
}
181
+
focus-follows-mouse max-scroll-amount="0%"
182
+
}
183
+
184
+
window-rule {
185
+
geometry-corner-radius 20
186
+
187
+
clip-to-geometry true
188
+
}
189
+
190
+
window-rule {
191
+
match app-id=r#"(?i)steam_app|aoe2|wine"#
192
+
geometry-corner-radius 0
193
+
clip-to-geometry false
194
+
}
195
+
196
+
window-rule {
197
+
match app-id="dev.noctalia.Noctalia"
198
+
open-floating true
199
+
default-column-width { fixed 1080; }
200
+
default-window-height { fixed 920; }
201
+
}
202
+
203
+
debug {
204
+
render-drm-device "/dev/dri/renderD129"
205
+
ignore-drm-device "/dev/dri/renderD128"
206
+
honor-xdg-activation-with-invalid-serial
207
+
}
208
+
209
+
layer-rule {
210
+
match namespace="^noctalia-backdrop"
211
+
place-within-backdrop true
212
+
}
213
+
214
+
layer-rule {
215
+
match namespace="^noctalia-wallpaper"
216
+
place-within-backdrop true
217
+
}
218
+
219
+
overview {
220
+
workspace-shadow {
221
+
off
222
+
}
223
+
}
224
+
225
+
window-rule {
226
+
exclude app-id="com\\.mitchellh\\.ghostty"
227
+
background-effect {
228
+
blur true
229
+
xray false
230
+
}
231
+
}
232
+
233
+
layer-rule {
234
+
match namespace="^noctalia-(bar-[^\"]+|notification|dock|panel|attached-panel|osd)$"
235
+
background-effect {
236
+
xray false
237
+
}
238
+
}
239
+
240
+
blur {
241
+
passes 2
242
+
offset 3.0
243
+
noise 0.03
244
+
saturation 1.0
245
+
}
246
+
247
+
layout {
248
+
background-color "transparent"
249
+
gaps 12
250
+
center-focused-column "never"
251
+
default-column-width {
252
+
proportion 0.5
253
+
}
254
+
focus-ring {
255
+
width 2
256
+
active-color "#89b4fa"
257
+
inactive-color "#45475a"
258
+
}
259
+
border {
260
+
off
261
+
}
262
+
}
263
+
264
+
prefer-no-csd
265
+
screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
266
+
spawn-at-startup "noctalia"
267
+
spawn-at-startup "xwayland-satellite"
268
+
269
+
// Match by make/model/serial: connector names shift across GPU/driver
270
+
// updates (HDMI-A-3 to HDMI-A-1, DP-4 to DP-2) and silently orphan the blocks.
271
+
output "Microstep MAG 341C OLED 0x01010101" {
272
+
mode "3440x1440@174.962"
273
+
position x=0 y=0
274
+
}
275
+
276
+
output "ASUSTek COMPUTER INC ASUS PA279CV R9LMTF061509" {
277
+
mode "3840x2160@59.997"
278
+
scale 1.5
279
+
position x=3440 y=0
280
+
}
281
+
282
+
binds {
283
+
Mod+Return { spawn "ghostty"; }
284
+
Mod+Space { spawn-sh "noctalia msg panel-toggle launcher"; }
285
+
Mod+D { spawn-sh "noctalia msg panel-toggle launcher"; }
286
+
Mod+S { spawn-sh "noctalia msg panel-toggle control-center"; }
287
+
Mod+Comma { spawn-sh "noctalia msg settings-toggle"; }
288
+
Mod+Alt+L { spawn "noctalia" "ipc" "call" "lockScreen" "lock"; }
289
+
Mod+Shift+Escape { quit; }
290
+
Mod+Q { close-window; }
291
+
Mod+H { focus-column-left; }
292
+
Mod+J { focus-window-down; }
293
+
Mod+K { focus-window-up; }
294
+
Mod+L { focus-column-right; }
295
+
Mod+Shift+Q { move-column-left; }
296
+
Mod+Shift+J { move-window-down; }
297
+
Mod+Shift+K { move-window-up; }
298
+
Mod+Shift+E { move-column-right; }
299
+
Mod+Left { focus-column-left; }
300
+
Mod+Down { focus-window-down; }
301
+
Mod+Up { focus-window-up; }
302
+
Mod+Right { focus-column-right; }
303
+
Mod+Shift+Left { move-column-left; }
304
+
Mod+Shift+Down { move-window-down; }
305
+
Mod+Shift+Up { move-window-up; }
306
+
Mod+Shift+Right { move-column-right; }
307
+
Mod+1 { focus-workspace 1; }
308
+
Mod+2 { focus-workspace 2; }
309
+
Mod+3 { focus-workspace 3; }
310
+
Mod+4 { focus-workspace 4; }
311
+
Mod+5 { focus-workspace 5; }
312
+
Mod+Shift+X { maximize-column; }
313
+
Mod+Shift+F { fullscreen-window; }
314
+
Mod+C { center-column; }
315
+
Mod+V { toggle-window-floating; }
316
+
Print { screenshot; }
317
+
XF86AudioRaiseVolume allow-when-locked=true { spawn-sh "noctalia msg volume-up"; }
318
+
XF86AudioLowerVolume allow-when-locked=true { spawn-sh "noctalia msg volume-down"; }
319
+
XF86AudioMute allow-when-locked=true { spawn-sh "noctalia msg volume-mute"; }
320
+
XF86MonBrightnessUp { spawn-sh "noctalia msg brightness-up"; }
321
+
XF86MonBrightnessDown { spawn-sh "noctalia msg brightness-down"; }
322
+
}
323
+
'';
324
+
}
+9
secrets/searx.env.age
+9
secrets/searx.env.age
···
1
+
-----BEGIN AGE ENCRYPTED FILE-----
2
+
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGR1N2xsdyBwTG81
3
+
WlAremsybnJJKytTL1VXOWhRNlNQQ0IxTWtxL0lnQVI0VUlXOW0wClQvRll2Q1lL
4
+
RkRCckp2ZTFpTWxIVUlPV0s0NWhwZU9nRXJoRUJUN1VySHMKLS0tIG1YZExFemJy
5
+
cFBOWjAvcDVIemRsazlOaDBkWGZXSE5EZm54ZE5LVnR6RUUKJHUeCHZPsNCsOrix
6
+
9ESiqGnKgZs3vxGXlEeNjvXT5uAkOkP1AVhAItk8lUsK9uwttS1aB1/IHBNP2Tpl
7
+
F95BpbMYqo5bIqMDhCdP3a3EhOIR4Xd+ciqS4gRXR5vwHZBGZGtFQjztiONXmM5I
8
+
qADJN5nY
9
+
-----END AGE ENCRYPTED FILE-----
History
2 rounds
0 comments
Expand 0 comments
Pull request successfully merged