1{
2 config,
3 pkgs,
4 lib,
5 ...
6}:
7
8let
9 cfg = config.services.suwayomi-server;
10 inherit (lib)
11 mkOption
12 mkEnableOption
13 mkIf
14 types
15 ;
16
17 format = pkgs.formats.hocon { };
18in
19{
20 options = {
21 services.suwayomi-server = {
22 enable = mkEnableOption "Suwayomi, a free and open source manga reader server that runs extensions built for Tachiyomi";
23
24 package = lib.mkPackageOption pkgs "suwayomi-server" { };
25
26 dataDir = mkOption {
27 type = types.path;
28 default = "/var/lib/suwayomi-server";
29 example = "/var/data/mangas";
30 description = ''
31 The path to the data directory in which Suwayomi-Server will download scans.
32 '';
33 };
34
35 user = mkOption {
36 type = types.str;
37 default = "suwayomi";
38 example = "root";
39 description = ''
40 User account under which Suwayomi-Server runs.
41 '';
42 };
43
44 group = mkOption {
45 type = types.str;
46 default = "suwayomi";
47 example = "medias";
48 description = ''
49 Group under which Suwayomi-Server runs.
50 '';
51 };
52
53 openFirewall = mkOption {
54 type = types.bool;
55 default = false;
56 description = ''
57 Whether to open the firewall for the port in {option}`services.suwayomi-server.settings.server.port`.
58 '';
59 };
60
61 settings = mkOption {
62 type = types.submodule {
63 freeformType = format.type;
64 options = {
65 server = {
66 ip = mkOption {
67 type = types.str;
68 default = "0.0.0.0";
69 example = "127.0.0.1";
70 description = ''
71 The ip that Suwayomi will bind to.
72 '';
73 };
74
75 port = mkOption {
76 type = types.port;
77 default = 8080;
78 example = 4567;
79 description = ''
80 The port that Suwayomi will listen to.
81 '';
82 };
83
84 basicAuthEnabled = mkEnableOption ''
85 basic access authentication for Suwayomi-Server.
86 Enabling this option is useful when hosting on a public network/the Internet
87 '';
88
89 basicAuthUsername = mkOption {
90 type = types.nullOr types.str;
91 default = null;
92 description = ''
93 The username value that you have to provide when authenticating.
94 '';
95 };
96
97 # NOTE: this is not a real upstream option
98 basicAuthPasswordFile = mkOption {
99 type = types.nullOr types.path;
100 default = null;
101 example = "/var/secrets/suwayomi-server-password";
102 description = ''
103 The password file containing the value that you have to provide when authenticating.
104 '';
105 };
106
107 downloadAsCbz = mkOption {
108 type = types.bool;
109 default = false;
110 description = ''
111 Download chapters as `.cbz` files.
112 '';
113 };
114
115 extensionRepos = mkOption {
116 type = types.listOf types.str;
117 default = [ ];
118 example = [
119 "https://raw.githubusercontent.com/MY_ACCOUNT/MY_REPO/repo/index.min.json"
120 ];
121 description = ''
122 URL of repositories from which the extensions can be installed.
123 '';
124 };
125
126 localSourcePath = mkOption {
127 type = types.path;
128 default = cfg.dataDir;
129 defaultText = lib.literalExpression "suwayomi-server.dataDir";
130 example = "/var/data/local_mangas";
131 description = ''
132 Path to the local source folder.
133 '';
134 };
135
136 systemTrayEnabled = mkOption {
137 type = types.bool;
138 default = false;
139 description = ''
140 Whether to enable a system tray icon, if possible.
141 '';
142 };
143 };
144 };
145 };
146 description = ''
147 Configuration to write to {file}`server.conf`.
148 See <https://github.com/Suwayomi/Suwayomi-Server/wiki/Configuring-Suwayomi-Server> for more information.
149 '';
150 default = { };
151 example = {
152 server.socksProxyEnabled = true;
153 server.socksProxyHost = "yourproxyhost.com";
154 server.socksProxyPort = "8080";
155 };
156 };
157 };
158 };
159
160 config = mkIf cfg.enable {
161
162 assertions = [
163 {
164 assertion =
165 with cfg.settings.server;
166 basicAuthEnabled -> (basicAuthUsername != null && basicAuthPasswordFile != null);
167 message = ''
168 [suwayomi-server]: the username and the password file cannot be null when the basic auth is enabled
169 '';
170 }
171 ];
172
173 networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.server.port ];
174
175 users.groups = mkIf (cfg.group == "suwayomi") {
176 suwayomi = { };
177 };
178
179 users.users = mkIf (cfg.user == "suwayomi") {
180 suwayomi = {
181 group = cfg.group;
182 # Need to set the user home because the package writes to ~/.local/Tachidesk
183 home = cfg.dataDir;
184 description = "Suwayomi Daemon user";
185 isSystemUser = true;
186 };
187 };
188
189 systemd.tmpfiles.settings."10-suwayomi-server" = {
190 "${cfg.dataDir}/.local/share/Tachidesk".d = {
191 mode = "0700";
192 inherit (cfg) user group;
193 };
194 };
195
196 systemd.services.suwayomi-server =
197 let
198 configFile = format.generate "server.conf" (
199 lib.pipe cfg.settings [
200 (
201 settings:
202 lib.recursiveUpdate settings {
203 server.basicAuthPasswordFile = null;
204 server.basicAuthPassword =
205 if settings.server.basicAuthEnabled then "$TACHIDESK_SERVER_BASIC_AUTH_PASSWORD" else null;
206 }
207 )
208 (lib.filterAttrsRecursive (_: x: x != null))
209 ]
210 );
211 in
212 {
213 description = "A free and open source manga reader server that runs extensions built for Tachiyomi.";
214
215 wantedBy = [ "multi-user.target" ];
216 wants = [ "network-online.target" ];
217 after = [ "network-online.target" ];
218
219 script = ''
220 ${lib.optionalString cfg.settings.server.basicAuthEnabled ''
221 export TACHIDESK_SERVER_BASIC_AUTH_PASSWORD="$(<${cfg.settings.server.basicAuthPasswordFile})"
222 ''}
223 ${lib.getExe pkgs.envsubst} -i ${configFile} -o ${cfg.dataDir}/.local/share/Tachidesk/server.conf
224 ${lib.getExe cfg.package} -Dsuwayomi.tachidesk.config.server.rootDir=${cfg.dataDir}
225 '';
226
227 serviceConfig = {
228 User = cfg.user;
229 Group = cfg.group;
230
231 Type = "simple";
232 Restart = "on-failure";
233
234 StateDirectory = mkIf (cfg.dataDir == "/var/lib/suwayomi-server") "suwayomi-server";
235 };
236 };
237 };
238
239 meta = {
240 maintainers = with lib.maintainers; [ ratcornu ];
241 doc = ./suwayomi-server.md;
242 };
243}