1{
2 config,
3 pkgs,
4 lib,
5 ...
6}:
7
8let
9 cfg = config.services.komga;
10 inherit (lib) mkOption mkEnableOption maintainers;
11 inherit (lib.types)
12 port
13 str
14 bool
15 submodule
16 ;
17
18 settingsFormat = pkgs.formats.yaml { };
19in
20{
21 imports = [
22 (lib.mkRenamedOptionModule
23 [
24 "services"
25 "komga"
26 "port"
27 ]
28 [
29 "services"
30 "komga"
31 "settings"
32 "server"
33 "port"
34 ]
35 )
36 ];
37
38 options = {
39 services.komga = {
40 enable = mkEnableOption "Komga, a free and open source comics/mangas media server";
41
42 user = mkOption {
43 type = str;
44 default = "komga";
45 description = "User account under which Komga runs.";
46 };
47
48 group = mkOption {
49 type = str;
50 default = "komga";
51 description = "Group under which Komga runs.";
52 };
53
54 stateDir = mkOption {
55 type = str;
56 default = "/var/lib/komga";
57 description = "State and configuration directory Komga will use.";
58 };
59
60 settings = lib.mkOption {
61 type = submodule {
62 freeformType = settingsFormat.type;
63 options.server.port = mkOption {
64 type = port;
65 description = "The port that Komga will listen on.";
66 default = 8080;
67 };
68 };
69 description = ''
70 Komga configuration.
71
72 See [documentation](https://komga.org/docs/installation/configuration).
73 '';
74 };
75
76 openFirewall = mkOption {
77 type = bool;
78 default = false;
79 description = "Whether to open the firewall for the port in {option}`services.komga.settings.server.port`.";
80 };
81 };
82 };
83
84 config =
85 let
86 inherit (lib) mkIf getExe;
87 in
88 mkIf cfg.enable {
89 assertions = [
90 {
91 assertion = (cfg.settings.komga.config-dir or cfg.stateDir) == cfg.stateDir;
92 message = "You must use the `services.komga.stateDir` option to properly configure `komga.config-dir`.";
93 }
94 ];
95
96 networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.server.port ];
97
98 users.groups = mkIf (cfg.group == "komga") { komga = { }; };
99
100 users.users = mkIf (cfg.user == "komga") {
101 komga = {
102 group = cfg.group;
103 home = cfg.stateDir;
104 description = "Komga Daemon user";
105 isSystemUser = true;
106 };
107 };
108
109 systemd.tmpfiles.settings."10-komga" = {
110 ${cfg.stateDir}.d = {
111 inherit (cfg) user group;
112 };
113 "${cfg.stateDir}/application.yml"."L+" = {
114 argument = builtins.toString (settingsFormat.generate "application.yml" cfg.settings);
115 };
116 };
117
118 systemd.services.komga = {
119 environment = {
120 KOMGA_CONFIGDIR = cfg.stateDir;
121 };
122
123 description = "Komga is a free and open source comics/mangas media server";
124
125 wantedBy = [ "multi-user.target" ];
126 wants = [ "network-online.target" ];
127 after = [ "network-online.target" ];
128
129 serviceConfig = {
130 User = cfg.user;
131 Group = cfg.group;
132
133 Type = "simple";
134 Restart = "on-failure";
135 ExecStart = getExe pkgs.komga;
136
137 StateDirectory = mkIf (cfg.stateDir == "/var/lib/komga") "komga";
138
139 RemoveIPC = true;
140 NoNewPrivileges = true;
141 CapabilityBoundingSet = "";
142 SystemCallFilter = [ "@system-service" ];
143 ProtectSystem = "full";
144 PrivateTmp = true;
145 ProtectProc = "invisible";
146 ProtectClock = true;
147 ProcSubset = "pid";
148 PrivateUsers = true;
149 PrivateDevices = true;
150 ProtectHostname = true;
151 ProtectKernelTunables = true;
152 RestrictAddressFamilies = [
153 "AF_INET"
154 "AF_INET6"
155 "AF_NETLINK"
156 ];
157 LockPersonality = true;
158 RestrictNamespaces = true;
159 ProtectKernelLogs = true;
160 ProtectControlGroups = true;
161 ProtectKernelModules = true;
162 SystemCallArchitectures = "native";
163 RestrictSUIDSGID = true;
164 RestrictRealtime = true;
165 };
166 };
167 };
168
169 meta.maintainers = with maintainers; [ govanify ];
170}