at master 7.3 kB view raw
1{ 2 lib, 3 pkgs, 4 config, 5 ... 6}: 7let 8 cfg = config.services.wakapi; 9 10 settingsFormat = pkgs.formats.yaml { }; 11 settingsFile = settingsFormat.generate "wakapi-settings" cfg.settings; 12 13 inherit (lib) 14 getExe 15 mkOption 16 mkEnableOption 17 mkPackageOption 18 types 19 mkIf 20 optional 21 mkMerge 22 singleton 23 ; 24in 25{ 26 options.services.wakapi = { 27 enable = mkEnableOption "Wakapi"; 28 package = mkPackageOption pkgs "wakapi" { }; 29 stateDir = mkOption { 30 type = types.path; 31 default = "/var/lib/wakapi"; 32 description = '' 33 The state directory where data is stored. Will also be used as the 34 working directory for the wakapi service. 35 ''; 36 }; 37 38 settings = mkOption { 39 inherit (settingsFormat) type; 40 default = { }; 41 description = '' 42 Settings for Wakapi. 43 44 See [config.default.yml](https://github.com/muety/wakapi/blob/master/config.default.yml) for a list of all possible options. 45 ''; 46 }; 47 48 passwordSalt = mkOption { 49 type = types.nullOr types.str; 50 default = null; 51 description = '' 52 The password salt to use for Wakapi. 53 ''; 54 }; 55 passwordSaltFile = mkOption { 56 type = types.nullOr types.path; 57 default = null; 58 description = '' 59 The path to a file containing the password salt to use for Wakapi. 60 ''; 61 }; 62 63 smtpPassword = mkOption { 64 type = types.nullOr types.str; 65 default = null; 66 description = '' 67 The password used for the smtp mailed to used by Wakapi. 68 ''; 69 }; 70 smtpPasswordFile = mkOption { 71 type = types.nullOr types.path; 72 default = null; 73 description = '' 74 The path to a file containing the password for the smtp mailer used by Wakapi. 75 ''; 76 }; 77 78 database = { 79 createLocally = mkEnableOption '' 80 automatic database configuration. 81 82 ::: {.note} 83 Only PostgreSQL is supported for the time being. 84 ::: 85 ''; 86 87 dialect = mkOption { 88 type = types.nullOr ( 89 types.enum [ 90 "postgres" 91 "sqlite3" 92 "mysql" 93 "cockroach" 94 "mssql" 95 ] 96 ); 97 default = cfg.settings.db.dialect or null; # handle case where dialect is not set 98 defaultText = '' 99 Database dialect from settings if {option}`services.wakatime.settings.db.dialect` 100 is set, or `null` otherwise. 101 ''; 102 description = '' 103 The database type to use for Wakapi. 104 ''; 105 }; 106 107 name = mkOption { 108 type = types.str; 109 default = cfg.settings.db.name or "wakapi"; 110 defaultText = '' 111 Database name from settings if {option}`services.wakatime.settings.db.name` 112 is set, or "wakapi" otherwise. 113 ''; 114 description = '' 115 The name of the database to use for Wakapi. 116 ''; 117 }; 118 119 user = mkOption { 120 type = types.str; 121 default = cfg.settings.db.user or "wakapi"; 122 defaultText = '' 123 User from settings if {option}`services.wakatime.settings.db.user` 124 is set, or "wakapi" otherwise. 125 ''; 126 description = '' 127 The name of the user to use for Wakapi. 128 ''; 129 }; 130 }; 131 }; 132 133 config = mkIf cfg.enable { 134 systemd.services.wakapi = { 135 description = "Wakapi (self-hosted WakaTime-compatible backend)"; 136 wants = [ 137 "network-online.target" 138 ] 139 ++ optional (cfg.database.dialect == "postgres") "postgresql.target"; 140 after = [ 141 "network-online.target" 142 ] 143 ++ optional (cfg.database.dialect == "postgres") "postgresql.target"; 144 wantedBy = [ "multi-user.target" ]; 145 146 script = '' 147 exec ${getExe cfg.package} -config ${settingsFile} 148 ''; 149 150 serviceConfig = { 151 Environment = mkMerge [ 152 (mkIf (cfg.passwordSalt != null) "WAKAPI_PASSWORD_SALT=${cfg.passwordSalt}") 153 (mkIf (cfg.smtpPassword != null) "WAKAPI_MAIL_SMTP_PASS=${cfg.smtpPassword}") 154 ]; 155 156 EnvironmentFile = 157 (lib.optional (cfg.passwordSaltFile != null) cfg.passwordSaltFile) 158 ++ (lib.optional (cfg.smtpPasswordFile != null) cfg.smtpPasswordFile); 159 160 User = config.users.users.wakapi.name; 161 Group = config.users.users.wakapi.group; 162 163 DynamicUser = true; 164 PrivateTmp = true; 165 PrivateUsers = true; 166 PrivateDevices = true; 167 ProtectHome = true; 168 ProtectHostname = true; 169 ProtectClock = true; 170 ProtectKernelLogs = true; 171 ProtectKernelModules = true; 172 ProtectKernelTunables = true; 173 ProtectControlGroups = true; 174 NoNewPrivileges = true; 175 ProtectProc = "invisible"; 176 ProtectSystem = "full"; 177 RestrictAddressFamilies = [ 178 "AF_INET" 179 "AF_INET6" 180 "AF_UNIX" 181 ]; 182 CapabilityBoundingSet = "CAP_NET_BIND_SERVICE"; 183 RestrictNamespaces = true; 184 RestrictRealtime = true; 185 RestrictSUIDSGID = true; 186 WorkingDirectory = cfg.stateDir; 187 RuntimeDirectory = "wakapi"; 188 StateDirectory = "wakapi"; 189 StateDirectoryMode = "0700"; 190 Restart = "always"; 191 }; 192 }; 193 194 services.wakapi.settings = { 195 env = lib.mkDefault "production"; 196 }; 197 198 assertions = [ 199 { 200 assertion = cfg.passwordSalt != null || cfg.passwordSaltFile != null; 201 message = "Either `services.wakapi.passwordSalt` or `services.wakapi.passwordSaltFile` must be set."; 202 } 203 { 204 assertion = !(cfg.passwordSalt != null && cfg.passwordSaltFile != null); 205 message = "Both `services.wakapi.passwordSalt` and `services.wakapi.passwordSaltFile` should not be set at the same time."; 206 } 207 { 208 assertion = !(cfg.smtpPassword != null && cfg.smtpPasswordFile != null); 209 message = "Both `services.wakapi.smtpPassword` and `services.wakapi.smtpPasswordFile` should not be set at the same time."; 210 } 211 { 212 assertion = cfg.database.createLocally -> cfg.settings.db.dialect != null; 213 message = "`services.wakapi.database.createLocally` is true, but a database dialect is not set!"; 214 } 215 ]; 216 217 warnings = [ 218 (lib.optionalString (cfg.database.createLocally && cfg.settings.db.dialect != "postgres") '' 219 You have enabled automatic database configuration, but the database dialect is not set to "posgres". 220 221 The Wakapi module only supports PostgreSQL. Please set `services.wakapi.database.createLocally` 222 to `false`, or switch to "postgres" as your database dialect. 223 '') 224 ]; 225 226 users = { 227 users.wakapi = { 228 group = "wakapi"; 229 createHome = false; 230 isSystemUser = true; 231 }; 232 groups.wakapi = { }; 233 }; 234 235 services.postgresql = mkIf (cfg.database.createLocally && cfg.database.dialect == "postgres") { 236 enable = true; 237 238 ensureDatabases = singleton cfg.database.name; 239 ensureUsers = singleton { 240 name = cfg.settings.db.user; 241 ensureDBOwnership = true; 242 }; 243 244 authentication = '' 245 host ${cfg.settings.db.name} ${cfg.settings.db.user} 127.0.0.1/32 trust 246 ''; 247 }; 248 }; 249 250 meta.maintainers = with lib.maintainers; [ 251 isabelroses 252 NotAShelf 253 ]; 254}