1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.services.calibre-web;
10 dataDir = if lib.hasPrefix "/" cfg.dataDir then cfg.dataDir else "/var/lib/${cfg.dataDir}";
11
12 inherit (lib)
13 concatStringsSep
14 mkEnableOption
15 mkIf
16 mkOption
17 optional
18 optionals
19 optionalString
20 types
21 ;
22in
23{
24 options = {
25 services.calibre-web = {
26 enable = mkEnableOption "Calibre-Web";
27
28 package = lib.mkPackageOption pkgs "calibre-web" { };
29
30 listen = {
31 ip = mkOption {
32 type = types.str;
33 default = "::1";
34 description = ''
35 IP address that Calibre-Web should listen on.
36 '';
37 };
38
39 port = mkOption {
40 type = types.port;
41 default = 8083;
42 description = ''
43 Listen port for Calibre-Web.
44 '';
45 };
46 };
47
48 dataDir = mkOption {
49 type = types.str;
50 default = "calibre-web";
51 description = ''
52 Where Calibre-Web stores its data.
53 Either an absolute path, or the directory name below {file}`/var/lib`.
54 '';
55 };
56
57 user = mkOption {
58 type = types.str;
59 default = "calibre-web";
60 description = "User account under which Calibre-Web runs.";
61 };
62
63 group = mkOption {
64 type = types.str;
65 default = "calibre-web";
66 description = "Group account under which Calibre-Web runs.";
67 };
68
69 openFirewall = mkOption {
70 type = types.bool;
71 default = false;
72 description = ''
73 Open ports in the firewall for the server.
74 '';
75 };
76
77 options = {
78 calibreLibrary = mkOption {
79 type = types.nullOr types.path;
80 default = null;
81 description = ''
82 Path to Calibre library.
83 '';
84 };
85
86 enableBookConversion = mkOption {
87 type = types.bool;
88 default = false;
89 description = ''
90 Configure path to the Calibre's ebook-convert in the DB.
91 '';
92 };
93
94 enableKepubify = mkEnableOption "kebup conversion support";
95
96 enableBookUploading = mkOption {
97 type = types.bool;
98 default = false;
99 description = ''
100 Allow books to be uploaded via Calibre-Web UI.
101 '';
102 };
103
104 reverseProxyAuth = {
105 enable = mkOption {
106 type = types.bool;
107 default = false;
108 description = ''
109 Enable authorization using auth proxy.
110 '';
111 };
112
113 header = mkOption {
114 type = types.str;
115 default = "";
116 description = ''
117 Auth proxy header name.
118 '';
119 };
120 };
121 };
122 };
123 };
124
125 config = mkIf cfg.enable {
126 systemd.tmpfiles.settings = lib.optionalAttrs (lib.hasPrefix "/" cfg.dataDir) {
127 "10-calibre-web".${dataDir}.d = {
128 inherit (cfg) user group;
129 mode = "0700";
130 };
131 };
132
133 systemd.services.calibre-web =
134 let
135 appDb = "${dataDir}/app.db";
136 gdriveDb = "${dataDir}/gdrive.db";
137 calibreWebCmd = "${cfg.package}/bin/calibre-web -p ${appDb} -g ${gdriveDb}";
138
139 settings = concatStringsSep ", " (
140 [
141 "config_port = ${toString cfg.listen.port}"
142 "config_uploading = ${if cfg.options.enableBookUploading then "1" else "0"}"
143 "config_allow_reverse_proxy_header_login = ${
144 if cfg.options.reverseProxyAuth.enable then "1" else "0"
145 }"
146 "config_reverse_proxy_login_header_name = '${cfg.options.reverseProxyAuth.header}'"
147 ]
148 ++ optional (
149 cfg.options.calibreLibrary != null
150 ) "config_calibre_dir = '${cfg.options.calibreLibrary}'"
151 ++ optionals cfg.options.enableBookConversion [
152 "config_converterpath = '${pkgs.calibre}/bin/ebook-convert'"
153 "config_binariesdir = '${pkgs.calibre}/bin/'"
154 ]
155 ++ optional cfg.options.enableKepubify "config_kepubifypath = '${pkgs.kepubify}/bin/kepubify'"
156 );
157 in
158 {
159 description = "Web app for browsing, reading and downloading eBooks stored in a Calibre database";
160 after = [ "network.target" ];
161 wantedBy = [ "multi-user.target" ];
162
163 serviceConfig =
164 {
165 Type = "simple";
166 User = cfg.user;
167 Group = cfg.group;
168
169 ExecStartPre = pkgs.writeShellScript "calibre-web-pre-start" (
170 ''
171 __RUN_MIGRATIONS_AND_EXIT=1 ${calibreWebCmd}
172
173 ${pkgs.sqlite}/bin/sqlite3 ${appDb} "update settings set ${settings}"
174 ''
175 + optionalString (cfg.options.calibreLibrary != null) ''
176 test -f "${cfg.options.calibreLibrary}/metadata.db" || { echo "Invalid Calibre library"; exit 1; }
177 ''
178 );
179
180 ExecStart = "${calibreWebCmd} -i ${cfg.listen.ip}";
181 Restart = "on-failure";
182 }
183 // lib.optionalAttrs (!(lib.hasPrefix "/" cfg.dataDir)) {
184 StateDirectory = cfg.dataDir;
185 };
186 };
187
188 networking.firewall = mkIf cfg.openFirewall {
189 allowedTCPPorts = [ cfg.listen.port ];
190 };
191
192 users.users = mkIf (cfg.user == "calibre-web") {
193 calibre-web = {
194 isSystemUser = true;
195 group = cfg.group;
196 };
197 };
198
199 users.groups = mkIf (cfg.group == "calibre-web") {
200 calibre-web = { };
201 };
202 };
203
204 meta.maintainers = with lib.maintainers; [ pborzenkov ];
205}