me like nix
1{ inputs, config, ... }:
2
3let
4 nm = config.flake.modules.nixos;
5in
6{
7 flake.nixosConfigurations.kodi-pi = inputs.nixos-raspberrypi.lib.nixosSystemFull {
8 specialArgs = {
9 inherit inputs;
10 inherit (inputs) nixos-raspberrypi;
11 };
12 modules = [
13 (
14 { nixos-raspberrypi, ... }:
15 {
16 imports = with nixos-raspberrypi.nixosModules; [
17 raspberry-pi-5.base
18 raspberry-pi-5.page-size-16k
19 raspberry-pi-5.display-vc4
20 ];
21 }
22 )
23 # Disable SDL3 test suite (testprocess fails in Nix sandbox)
24 (
25 { pkgs, ... }:
26 {
27 nixpkgs.overlays = [
28 (final: prev: {
29 sdl3 = prev.sdl3.overrideAttrs (old: {
30 doCheck = false;
31 });
32 })
33 ];
34 }
35 )
36 inputs.agenix.nixosModules.default
37
38 # Aspect modules
39 nm.pi-wifi
40
41 # Kodi Pi 5 specific settings
42 (
43 { pkgs, lib, ... }:
44 let
45 patchedInvidious = pkgs.kodiPackages.invidious.overrideAttrs (old: {
46 postPatch = (old.postPatch or "") + ''
47 substituteInPlace resources/lib/invidious_plugin.py \
48 --replace-fail \
49 'url = video_info["dashUrl"]' \
50 'url = video_info["dashUrl"]; url = self.api_client.instance_url + url if url.startswith("/") else url' \
51 --replace-fail \
52 'url = video_info["formatStreams"][-1]["url"]' \
53 'url = video_info["formatStreams"][-1]["url"]; url = self.api_client.instance_url + url if url.startswith("/") else url'
54 '';
55 });
56
57 myKodi = pkgs.kodi-gbm.withPackages (kodiPkgs: with kodiPkgs; [
58 jellyfin
59 patchedInvidious
60 ]);
61
62 kodiAdvancedSettings = pkgs.writeText "advancedsettings.xml" ''
63 <advancedsettings version="1.0">
64 <videoplayer>
65 <useprimedecoder>true</useprimedecoder>
66 <useprimedecoderforhw>true</useprimedecoderforhw>
67 </videoplayer>
68 </advancedsettings>
69 '';
70 in
71 {
72 networking.hostName = "kodi-pi";
73
74 boot.loader.raspberry-pi.bootloader = "kernel";
75 boot.kernelParams = [ "video=HDMI-A-1:3840x2160@30D" ];
76
77 fileSystems."/" = {
78 device = "/dev/disk/by-label/NIXOS_SD";
79 fsType = "ext4";
80 options = [ "noatime" ];
81 };
82 fileSystems."/boot/firmware" = {
83 device = "/dev/disk/by-label/FIRMWARE";
84 fsType = "vfat";
85 options = [
86 "noatime"
87 "noauto"
88 "x-systemd.automount"
89 "x-systemd.idle-timeout=1min"
90 ];
91 };
92
93 hardware.graphics.enable = true;
94
95 hardware.raspberry-pi.config.all.options = {
96 gpu_mem = {
97 enable = true;
98 value = 256;
99 };
100 hdmi_force_hotplug = {
101 enable = true;
102 value = true;
103 };
104 };
105
106 # ALSA only for HDMI passthrough
107 services.pipewire.enable = false;
108 services.pulseaudio.enable = false;
109
110 systemd.services.kodi = {
111 description = "Kodi media center";
112 wantedBy = [ "multi-user.target" ];
113 after = [ "network-online.target" "sound.target" "systemd-user-sessions.service" ];
114 wants = [ "network-online.target" ];
115 serviceConfig = {
116 Type = "simple";
117 User = "kodi";
118 ExecStartPre = pkgs.writeShellScript "kodi-setup" ''
119 mkdir -p /home/kodi/.kodi/userdata
120 cp -f ${kodiAdvancedSettings} /home/kodi/.kodi/userdata/advancedsettings.xml
121
122 JFSETTINGS=/home/kodi/.kodi/userdata/addon_data/plugin.video.jellyfin/settings.xml
123 if [ -f "$JFSETTINGS" ]; then
124 ${pkgs.gnused}/bin/sed -i \
125 -e 's|"transcode_h265">true|"transcode_h265">false|' \
126 -e 's|"transcodeHi10P">true|"transcodeHi10P">false|' \
127 -e 's|"kodiCompanion">false|"kodiCompanion">true|' \
128 "$JFSETTINGS"
129 fi
130
131 INVSETTINGS=/home/kodi/.kodi/userdata/addon_data/plugin.video.invidious/settings.xml
132 if [ -f "$INVSETTINGS" ]; then
133 ${pkgs.gnused}/bin/sed -i \
134 -e 's|"auto_instance">true|"auto_instance">false|' \
135 -e 's|"instance_url"[^<]*<|"instance_url">http://mira:3001<|' \
136 -e 's|"disable_dash"[^>]*>[^<]*<|"disable_dash">false<|' \
137 "$INVSETTINGS"
138 else
139 mkdir -p /home/kodi/.kodi/userdata/addon_data/plugin.video.invidious
140 cat > "$INVSETTINGS" <<'INVEOF'
141<settings version="2">
142 <setting id="auto_instance">false</setting>
143 <setting id="instance_url">http://mira:3001</setting>
144 <setting id="disable_dash">false</setting>
145</settings>
146INVEOF
147 fi
148 '';
149 ExecStart = "${myKodi}/bin/kodi-standalone";
150 Restart = "always";
151 TimeoutStopSec = "15s";
152 TimeoutStopFailureMode = "kill";
153 };
154 };
155
156 users.users.kodi = {
157 isNormalUser = true;
158 extraGroups = [
159 "video"
160 "audio"
161 "input"
162 "render"
163 ];
164 };
165
166 users.users.sean = {
167 isNormalUser = true;
168 extraGroups = [ "wheel" ];
169 openssh.authorizedKeys.keys = [
170 "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc="
171 "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16"
172 ];
173 };
174
175 services.openssh = {
176 enable = true;
177 settings = {
178 PasswordAuthentication = false;
179 PermitRootLogin = "no";
180 };
181 };
182
183 nix.settings.require-sigs = false;
184 nix.settings.substituters = [
185 "https://nixos-raspberrypi.cachix.org"
186 "https://seanaye.cachix.org"
187 ];
188 nix.settings.trusted-public-keys = [
189 "nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI="
190 "seanaye.cachix.org-1:0Qf3cZ1SwnTwqaiNGltYySksjGHnemzRPiodThnvibA="
191 ];
192
193 networking.useDHCP = true;
194 security.sudo.wheelNeedsPassword = false;
195
196 networking.firewall.allowedTCPPorts = [
197 22
198 8080 # Kodi web remote
199 ];
200
201 environment.systemPackages = [ myKodi ];
202
203 system.stateVersion = "24.11";
204 }
205 )
206 ];
207 };
208}