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