···
23
23
"type": "github"
24
24
}
25
25
},
26
26
+
"argononed": {
27
27
+
"flake": false,
28
28
+
"locked": {
29
29
+
"lastModified": 1729566243,
30
30
+
"narHash": "sha256-DPNI0Dpk5aym3Baf5UbEe5GENDrSmmXVdriRSWE+rgk=",
31
31
+
"owner": "nvmd",
32
32
+
"repo": "argononed",
33
33
+
"rev": "16dbee54d49b66d5654d228d1061246b440ef7cf",
34
34
+
"type": "github"
35
35
+
},
36
36
+
"original": {
37
37
+
"owner": "nvmd",
38
38
+
"repo": "argononed",
39
39
+
"type": "github"
40
40
+
}
41
41
+
},
26
42
"catppuccin": {
27
43
"inputs": {
28
44
"nixpkgs": "nixpkgs"
···
131
147
"original": {
132
148
"owner": "nix-community",
133
149
"repo": "fenix",
150
150
+
"type": "github"
151
151
+
}
152
152
+
},
153
153
+
"flake-compat": {
154
154
+
"locked": {
155
155
+
"lastModified": 1767039857,
156
156
+
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
157
157
+
"owner": "edolstra",
158
158
+
"repo": "flake-compat",
159
159
+
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
160
160
+
"type": "github"
161
161
+
},
162
162
+
"original": {
163
163
+
"owner": "edolstra",
164
164
+
"repo": "flake-compat",
134
165
"type": "github"
135
166
}
136
167
},
···
322
353
"type": "github"
323
354
}
324
355
},
356
356
+
"nixos-images": {
357
357
+
"inputs": {
358
358
+
"nixos-stable": [
359
359
+
"nixos-raspberrypi",
360
360
+
"nixpkgs"
361
361
+
],
362
362
+
"nixos-unstable": [
363
363
+
"nixos-raspberrypi",
364
364
+
"nixpkgs"
365
365
+
]
366
366
+
},
367
367
+
"locked": {
368
368
+
"lastModified": 1747747741,
369
369
+
"narHash": "sha256-LUOH27unNWbGTvZFitHonraNx0JF/55h30r9WxqrznM=",
370
370
+
"owner": "nvmd",
371
371
+
"repo": "nixos-images",
372
372
+
"rev": "cbbd6db325775096680b65e2a32fb6187c09bbb4",
373
373
+
"type": "github"
374
374
+
},
375
375
+
"original": {
376
376
+
"owner": "nvmd",
377
377
+
"ref": "sdimage-installer",
378
378
+
"repo": "nixos-images",
379
379
+
"type": "github"
380
380
+
}
381
381
+
},
382
382
+
"nixos-raspberrypi": {
383
383
+
"inputs": {
384
384
+
"argononed": "argononed",
385
385
+
"flake-compat": "flake-compat",
386
386
+
"nixos-images": "nixos-images",
387
387
+
"nixpkgs": "nixpkgs_5"
388
388
+
},
389
389
+
"locked": {
390
390
+
"lastModified": 1769297295,
391
391
+
"narHash": "sha256-WNI4AzwrYPeKzI/maEE+pRm8y+l9+zELkmVzMzOrOfY=",
392
392
+
"owner": "nvmd",
393
393
+
"repo": "nixos-raspberrypi",
394
394
+
"rev": "251861d8e7e4806e185a8e41e09b3ad1e6e088e6",
395
395
+
"type": "github"
396
396
+
},
397
397
+
"original": {
398
398
+
"owner": "nvmd",
399
399
+
"ref": "main",
400
400
+
"repo": "nixos-raspberrypi",
401
401
+
"type": "github"
402
402
+
}
403
403
+
},
325
404
"nixpkgs": {
326
405
"locked": {
327
406
"lastModified": 1770197578,
···
419
498
},
420
499
"nixpkgs_5": {
421
500
"locked": {
501
501
+
"lastModified": 1769296100,
502
502
+
"narHash": "sha256-T5VGtbz2X+waw2P1dYvLa56osxj3cNZArfa5aRBVT+s=",
503
503
+
"owner": "nvmd",
504
504
+
"repo": "nixpkgs",
505
505
+
"rev": "87f37c7a374071ec0924b99b846c1a7cc44a8c6c",
506
506
+
"type": "github"
507
507
+
},
508
508
+
"original": {
509
509
+
"owner": "nvmd",
510
510
+
"ref": "modules-with-keys-25.11",
511
511
+
"repo": "nixpkgs",
512
512
+
"type": "github"
513
513
+
}
514
514
+
},
515
515
+
"nixpkgs_6": {
516
516
+
"locked": {
422
517
"lastModified": 1770562336,
423
518
"narHash": "sha256-ub1gpAONMFsT/GU2hV6ZWJjur8rJ6kKxdm9IlCT0j84=",
424
519
"owner": "NixOS",
···
460
555
"niri": "niri",
461
556
"nixarr": "nixarr",
462
557
"nixos-hardware": "nixos-hardware",
463
463
-
"nixpkgs": "nixpkgs_5",
558
558
+
"nixos-raspberrypi": "nixos-raspberrypi",
559
559
+
"nixpkgs": "nixpkgs_6",
464
560
"organelle-hello": "organelle-hello",
465
561
"trmnl-rs": "trmnl-rs",
466
562
"zen-browser": "zen-browser"
···
31
31
url = "github:seanaye/trmnl-rs";
32
32
inputs.nixpkgs.follows = "nixpkgs";
33
33
};
34
34
+
nixos-raspberrypi.url = "github:nvmd/nixos-raspberrypi/main";
34
35
};
35
36
36
37
outputs =
···
45
46
nixos-hardware,
46
47
agenix,
47
48
organelle-hello,
49
49
+
nixos-raspberrypi,
48
50
...
49
51
}@inputs:
50
52
{
···
157
159
})
158
160
];
159
161
specialArgs = { inherit inputs; };
162
162
+
};
163
163
+
164
164
+
kodi-pi = nixos-raspberrypi.lib.nixosSystemFull {
165
165
+
specialArgs = { inherit nixos-raspberrypi inputs; };
166
166
+
modules = [
167
167
+
({ nixos-raspberrypi, ... }: {
168
168
+
imports = with nixos-raspberrypi.nixosModules; [
169
169
+
raspberry-pi-5.base
170
170
+
raspberry-pi-5.page-size-16k
171
171
+
raspberry-pi-5.display-vc4
172
172
+
];
173
173
+
})
174
174
+
# Disable SDL3 test suite (testprocess fails in Nix sandbox)
175
175
+
({ pkgs, ... }: {
176
176
+
nixpkgs.overlays = [
177
177
+
(final: prev: {
178
178
+
sdl3 = prev.sdl3.overrideAttrs (old: {
179
179
+
doCheck = false;
180
180
+
});
181
181
+
})
182
182
+
];
183
183
+
})
184
184
+
agenix.nixosModules.default
185
185
+
./hosts/kodi-pi/configuration.nix
186
186
+
];
160
187
};
161
188
};
162
189
};
···
1
1
+
{ pkgs, lib, ... }:
2
2
+
3
3
+
let
4
4
+
# Patch Invidious addon to prepend instance URL to relative stream/dash URLs
5
5
+
patchedInvidious = pkgs.kodiPackages.invidious.overrideAttrs (old: {
6
6
+
postPatch = (old.postPatch or "") + ''
7
7
+
substituteInPlace resources/lib/invidious_plugin.py \
8
8
+
--replace-fail \
9
9
+
'url = video_info["dashUrl"]' \
10
10
+
'url = video_info["dashUrl"]; url = self.api_client.instance_url + url if url.startswith("/") else url' \
11
11
+
--replace-fail \
12
12
+
'url = video_info["formatStreams"][-1]["url"]' \
13
13
+
'url = video_info["formatStreams"][-1]["url"]; url = self.api_client.instance_url + url if url.startswith("/") else url'
14
14
+
'';
15
15
+
});
16
16
+
17
17
+
myKodi = pkgs.kodi-gbm.withPackages (kodiPkgs: with kodiPkgs; [
18
18
+
jellyfin
19
19
+
patchedInvidious
20
20
+
]);
21
21
+
22
22
+
kodiAdvancedSettings = pkgs.writeText "advancedsettings.xml" ''
23
23
+
<advancedsettings version="1.0">
24
24
+
<videoplayer>
25
25
+
<useprimedecoder>true</useprimedecoder>
26
26
+
<useprimedecoderforhw>true</useprimedecoderforhw>
27
27
+
</videoplayer>
28
28
+
</advancedsettings>
29
29
+
'';
30
30
+
in
31
31
+
{
32
32
+
imports = [ ../pi-common/wifi.nix ];
33
33
+
34
34
+
networking.hostName = "kodi-pi";
35
35
+
36
36
+
# Use the new generational bootloader (matches nixos-raspberrypi installer)
37
37
+
boot.loader.raspberry-pi.bootloader = "kernel";
38
38
+
39
39
+
# Force 4K30 output when EDID read fails (TV may not be ready at boot)
40
40
+
boot.kernelParams = [ "video=HDMI-A-1:3840x2160@30D" ];
41
41
+
42
42
+
# Filesystems (matching nixos-raspberrypi installer layout)
43
43
+
fileSystems."/" = {
44
44
+
device = "/dev/disk/by-label/NIXOS_SD";
45
45
+
fsType = "ext4";
46
46
+
options = [ "noatime" ];
47
47
+
};
48
48
+
fileSystems."/boot/firmware" = {
49
49
+
device = "/dev/disk/by-label/FIRMWARE";
50
50
+
fsType = "vfat";
51
51
+
options = [ "noatime" "noauto" "x-systemd.automount" "x-systemd.idle-timeout=1min" ];
52
52
+
};
53
53
+
54
54
+
# Graphics
55
55
+
hardware.graphics.enable = true;
56
56
+
57
57
+
# Pi firmware config.txt settings for Kodi
58
58
+
hardware.raspberry-pi.config.all.options = {
59
59
+
gpu_mem = { enable = true; value = 256; };
60
60
+
hdmi_force_hotplug = { enable = true; value = true; };
61
61
+
};
62
62
+
63
63
+
# Audio: ALSA only for HDMI passthrough
64
64
+
services.pipewire.enable = false;
65
65
+
services.pulseaudio.enable = false;
66
66
+
67
67
+
# Kodi via GBM (direct DRM, no compositor needed)
68
68
+
systemd.services.kodi = {
69
69
+
description = "Kodi media center";
70
70
+
wantedBy = [ "multi-user.target" ];
71
71
+
after = [ "network-online.target" "sound.target" "systemd-user-sessions.service" ];
72
72
+
wants = [ "network-online.target" ];
73
73
+
serviceConfig = {
74
74
+
Type = "simple";
75
75
+
User = "kodi";
76
76
+
ExecStartPre = pkgs.writeShellScript "kodi-setup" ''
77
77
+
mkdir -p /home/kodi/.kodi/userdata
78
78
+
cp -f ${kodiAdvancedSettings} /home/kodi/.kodi/userdata/advancedsettings.xml
79
79
+
80
80
+
# Patch Jellyfin addon settings (Pi 5 has HW HEVC decoder, no need to transcode)
81
81
+
JFSETTINGS=/home/kodi/.kodi/userdata/addon_data/plugin.video.jellyfin/settings.xml
82
82
+
if [ -f "$JFSETTINGS" ]; then
83
83
+
${pkgs.gnused}/bin/sed -i \
84
84
+
-e 's|"transcode_h265">true|"transcode_h265">false|' \
85
85
+
-e 's|"transcodeHi10P">true|"transcodeHi10P">false|' \
86
86
+
-e 's|"kodiCompanion">false|"kodiCompanion">true|' \
87
87
+
"$JFSETTINGS"
88
88
+
fi
89
89
+
90
90
+
# Configure Invidious addon to use a specific instance (auto-detect is unreliable)
91
91
+
INVSETTINGS=/home/kodi/.kodi/userdata/addon_data/plugin.video.invidious/settings.xml
92
92
+
if [ -f "$INVSETTINGS" ]; then
93
93
+
${pkgs.gnused}/bin/sed -i \
94
94
+
-e 's|"auto_instance">true|"auto_instance">false|' \
95
95
+
-e 's|"instance_url"[^<]*<|"instance_url">http://mira:3001<|' \
96
96
+
-e 's|"disable_dash"[^>]*>[^<]*<|"disable_dash">false<|' \
97
97
+
"$INVSETTINGS"
98
98
+
else
99
99
+
mkdir -p /home/kodi/.kodi/userdata/addon_data/plugin.video.invidious
100
100
+
cat > "$INVSETTINGS" <<'INVEOF'
101
101
+
<settings version="2">
102
102
+
<setting id="auto_instance">false</setting>
103
103
+
<setting id="instance_url">http://mira:3001</setting>
104
104
+
<setting id="disable_dash">false</setting>
105
105
+
</settings>
106
106
+
INVEOF
107
107
+
fi
108
108
+
109
109
+
'';
110
110
+
ExecStart = "${myKodi}/bin/kodi-standalone";
111
111
+
Restart = "always";
112
112
+
TimeoutStopSec = "15s";
113
113
+
TimeoutStopFailureMode = "kill";
114
114
+
};
115
115
+
};
116
116
+
117
117
+
# Kodi user
118
118
+
users.users.kodi = {
119
119
+
isNormalUser = true;
120
120
+
extraGroups = [ "video" "audio" "input" "render" ];
121
121
+
};
122
122
+
123
123
+
# Admin user
124
124
+
users.users.sean = {
125
125
+
isNormalUser = true;
126
126
+
extraGroups = [ "wheel" ];
127
127
+
openssh.authorizedKeys.keys = [
128
128
+
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc="
129
129
+
"sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16"
130
130
+
];
131
131
+
};
132
132
+
133
133
+
# SSH
134
134
+
services.openssh = {
135
135
+
enable = true;
136
136
+
settings = {
137
137
+
PasswordAuthentication = false;
138
138
+
PermitRootLogin = "no";
139
139
+
};
140
140
+
};
141
141
+
142
142
+
# Accept unsigned store paths from local builds
143
143
+
nix.settings.require-sigs = false;
144
144
+
145
145
+
# Binary caches
146
146
+
nix.settings.substituters = [
147
147
+
"https://nixos-raspberrypi.cachix.org"
148
148
+
"https://seanaye.cachix.org"
149
149
+
];
150
150
+
nix.settings.trusted-public-keys = [
151
151
+
"nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI="
152
152
+
"seanaye.cachix.org-1:0Qf3cZ1SwnTwqaiNGltYySksjGHnemzRPiodThnvibA="
153
153
+
];
154
154
+
155
155
+
# Networking
156
156
+
networking.useDHCP = true;
157
157
+
security.sudo.wheelNeedsPassword = false;
158
158
+
159
159
+
# Firewall
160
160
+
networking.firewall.allowedTCPPorts = [
161
161
+
22 # SSH
162
162
+
8080 # Kodi web remote
163
163
+
];
164
164
+
165
165
+
environment.systemPackages = [ myKodi ];
166
166
+
167
167
+
system.stateVersion = "24.11";
168
168
+
}