main
1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11 cfg = config.services.jellyfin-auto-collections;
12
13 # Convert schedule shortcuts to systemd OnCalendar format
14 scheduleToCalendar =
15 schedule:
16 if schedule == "hourly" then
17 "hourly"
18 else if schedule == "daily" then
19 "daily"
20 else if schedule == "weekly" then
21 "weekly"
22 else if schedule == "monthly" then
23 "monthly"
24 else
25 schedule;
26
27 # Generate complete YAML config
28 # We'll use a template and substitute secrets at runtime
29 configYaml = lib.generators.toYAML { } (
30 {
31 jellyfin = {
32 server_url = cfg.jellyfinUrl;
33 api_key = "JELLYFIN_API_KEY_PLACEHOLDER";
34 user_id = cfg.userId;
35 };
36 }
37 // lib.optionalAttrs (cfg.jellyseerr.enable && cfg.jellyseerr.passwordFile != null) {
38 jellyseerr = {
39 server_url = cfg.jellyseerr.serverUrl;
40 inherit (cfg.jellyseerr) email;
41 password = "JELLYSEERR_PASSWORD_PLACEHOLDER";
42 user_type = cfg.jellyseerr.userType;
43 };
44 }
45 // cfg.settings
46 );
47
48 # Script to generate config file and run the application
49 startScript = pkgs.writeShellScript "jellyfin-auto-collections-start" ''
50 # Load API key from file if specified
51 ${lib.optionalString (cfg.apiKeyFile != null) ''
52 JELLYFIN_API_KEY=$(cat ${cfg.apiKeyFile})
53 ''}
54
55 # Load Jellyseerr password from file if specified
56 ${lib.optionalString (cfg.jellyseerr.enable && cfg.jellyseerr.passwordFile != null) ''
57 JELLYSEERR_PASSWORD=$(cat ${cfg.jellyseerr.passwordFile})
58 ''}
59
60 # Generate config.yml by substituting the placeholders
61 cat > ${cfg.dataDir}/config.yml << 'EOF'
62 ${configYaml}
63 EOF
64
65 # Replace the placeholders with actual secrets
66 sed -i "s/JELLYFIN_API_KEY_PLACEHOLDER/$JELLYFIN_API_KEY/g" ${cfg.dataDir}/config.yml
67 ${lib.optionalString (cfg.jellyseerr.enable && cfg.jellyseerr.passwordFile != null) ''
68 sed -i "s/JELLYSEERR_PASSWORD_PLACEHOLDER/$JELLYSEERR_PASSWORD/g" ${cfg.dataDir}/config.yml
69 ''}
70
71 chmod 600 ${cfg.dataDir}/config.yml
72
73 # Run the main script with config file
74 exec ${cfg.package}/bin/jellyfin-auto-collections --config ${cfg.dataDir}/config.yml
75 '';
76in
77{
78 options.services.jellyfin-auto-collections = {
79 enable = mkEnableOption "Jellyfin Auto Collections service";
80
81 package = mkOption {
82 type = types.package;
83 default = pkgs.jellyfin-auto-collections;
84 defaultText = literalExpression "pkgs.jellyfin-auto-collections";
85 description = "The jellyfin-auto-collections package to use.";
86 };
87
88 schedule = mkOption {
89 type = types.str;
90 default = "daily";
91 description = ''
92 When to run the collection update. Can be "hourly", "daily", "weekly", "monthly", or a systemd OnCalendar format.
93 See systemd.time(7) for OnCalendar format details.
94 '';
95 example = "daily";
96 };
97
98 jellyfinUrl = mkOption {
99 type = types.str;
100 example = "http://localhost:8096";
101 description = "URL of the Jellyfin server";
102 };
103
104 apiKeyFile = mkOption {
105 type = types.nullOr types.path;
106 default = null;
107 description = ''
108 Path to a file containing the Jellyfin API key.
109 The file should contain only the API key.
110 '';
111 };
112
113 userId = mkOption {
114 type = types.str;
115 description = "Jellyfin user ID";
116 };
117
118 jellyseerr = {
119 enable = mkEnableOption "Jellyseerr integration";
120
121 serverUrl = mkOption {
122 type = types.str;
123 default = "http://localhost:5055";
124 description = "URL of the Jellyseerr server";
125 };
126
127 email = mkOption {
128 type = types.str;
129 description = "Jellyseerr user email";
130 };
131
132 passwordFile = mkOption {
133 type = types.nullOr types.path;
134 default = null;
135 description = ''
136 Path to a file containing the Jellyseerr password.
137 The file should contain only the password.
138 '';
139 };
140
141 userType = mkOption {
142 type = types.str;
143 default = "local";
144 description = "Jellyseerr user type (local or plex)";
145 };
146 };
147
148 settings = mkOption {
149 type = types.attrs;
150 default = { };
151 description = ''
152 Configuration for Jellyfin Auto Collections.
153 This will be converted to a config file.
154 '';
155 example = literalExpression ''
156 {
157 jellyfin = {
158 url = "http://localhost:8096";
159 user_id = "your-user-id";
160 };
161 lists = [
162 {
163 type = "imdb";
164 url = "https://www.imdb.com/list/ls123456789/";
165 name = "IMDb Top 250";
166 }
167 ];
168 }
169 '';
170 };
171
172 dataDir = mkOption {
173 type = types.path;
174 default = "/var/lib/jellyfin-auto-collections";
175 description = "Directory to store jellyfin-auto-collections data and cache";
176 };
177
178 user = mkOption {
179 type = types.str;
180 default = "jellyfin-auto-collections";
181 description = "User account under which jellyfin-auto-collections runs.";
182 };
183
184 group = mkOption {
185 type = types.str;
186 default = "jellyfin-auto-collections";
187 description = "Group under which jellyfin-auto-collections runs.";
188 };
189 };
190
191 config = mkIf cfg.enable {
192 # Create the user and group
193 users.users.${cfg.user} = {
194 isSystemUser = true;
195 inherit (cfg) group;
196 home = cfg.dataDir;
197 createHome = true;
198 };
199
200 users.groups.${cfg.group} = { };
201
202 # Systemd service
203 systemd.services.jellyfin-auto-collections = {
204 description = "Jellyfin Auto Collections";
205 after = [ "network.target" ];
206
207 serviceConfig = {
208 Type = "oneshot";
209 User = cfg.user;
210 Group = cfg.group;
211 WorkingDirectory = cfg.dataDir;
212
213 ExecStart = "${startScript}";
214
215 # Security hardening
216 PrivateTmp = true;
217 ProtectSystem = "strict";
218 ProtectHome = true;
219 ReadWritePaths = [ cfg.dataDir ];
220 NoNewPrivileges = true;
221 ProtectKernelTunables = true;
222 ProtectKernelModules = true;
223 ProtectControlGroups = true;
224 RestrictRealtime = true;
225 RestrictSUIDSGID = true;
226 PrivateMounts = true;
227 LockPersonality = true;
228 };
229
230 environment = {
231 JELLYFIN_SERVER_URL = cfg.jellyfinUrl;
232 JELLYFIN_USER_ID = cfg.userId;
233 HOME = cfg.dataDir;
234 };
235 };
236
237 # Systemd timer
238 systemd.timers.jellyfin-auto-collections = {
239 description = "Timer for Jellyfin Auto Collections";
240 wantedBy = [ "timers.target" ];
241
242 timerConfig = {
243 OnCalendar = scheduleToCalendar cfg.schedule;
244 Persistent = true;
245 RandomizedDelaySec = "5m";
246 };
247 };
248 };
249}