at 25.11-pre 15 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 ... 6}: 7 8let 9 cfg = config.services.misskey; 10 settingsFormat = pkgs.formats.yaml { }; 11 redisType = lib.types.submodule { 12 freeformType = lib.types.attrsOf settingsFormat.type; 13 options = { 14 host = lib.mkOption { 15 type = lib.types.str; 16 default = "localhost"; 17 description = "The Redis host."; 18 }; 19 port = lib.mkOption { 20 type = lib.types.port; 21 default = 6379; 22 description = "The Redis port."; 23 }; 24 }; 25 }; 26 settings = lib.mkOption { 27 description = '' 28 Configuration for Misskey, see 29 [`example.yml`](https://github.com/misskey-dev/misskey/blob/develop/.config/example.yml) 30 for all supported options. 31 ''; 32 type = lib.types.submodule { 33 freeformType = lib.types.attrsOf settingsFormat.type; 34 options = { 35 url = lib.mkOption { 36 type = lib.types.str; 37 example = "https://example.tld/"; 38 description = '' 39 The final user-facing URL. Do not change after running Misskey for the first time. 40 41 This needs to match up with the configured reverse proxy and is automatically configured when using `services.misskey.reverseProxy`. 42 ''; 43 }; 44 port = lib.mkOption { 45 type = lib.types.port; 46 default = 3000; 47 description = "The port your Misskey server should listen on."; 48 }; 49 socket = lib.mkOption { 50 type = lib.types.nullOr lib.types.path; 51 default = null; 52 example = "/path/to/misskey.sock"; 53 description = "The UNIX socket your Misskey server should listen on."; 54 }; 55 chmodSocket = lib.mkOption { 56 type = lib.types.nullOr lib.types.str; 57 default = null; 58 example = "777"; 59 description = "The file access mode of the UNIX socket."; 60 }; 61 db = lib.mkOption { 62 description = "Database settings."; 63 type = lib.types.submodule { 64 options = { 65 host = lib.mkOption { 66 type = lib.types.str; 67 default = "/var/run/postgresql"; 68 example = "localhost"; 69 description = "The PostgreSQL host."; 70 }; 71 port = lib.mkOption { 72 type = lib.types.port; 73 default = 5432; 74 description = "The PostgreSQL port."; 75 }; 76 db = lib.mkOption { 77 type = lib.types.str; 78 default = "misskey"; 79 description = "The database name."; 80 }; 81 user = lib.mkOption { 82 type = lib.types.str; 83 default = "misskey"; 84 description = "The user used for database authentication."; 85 }; 86 pass = lib.mkOption { 87 type = lib.types.nullOr lib.types.str; 88 default = null; 89 description = "The password used for database authentication."; 90 }; 91 disableCache = lib.mkOption { 92 type = lib.types.bool; 93 default = false; 94 description = "Whether to disable caching queries."; 95 }; 96 extra = lib.mkOption { 97 type = lib.types.nullOr (lib.types.attrsOf settingsFormat.type); 98 default = null; 99 example = { 100 ssl = true; 101 }; 102 description = "Extra connection options."; 103 }; 104 }; 105 }; 106 default = { }; 107 }; 108 redis = lib.mkOption { 109 type = redisType; 110 default = { }; 111 description = "`ioredis` options. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference."; 112 }; 113 redisForPubsub = lib.mkOption { 114 type = lib.types.nullOr redisType; 115 default = null; 116 description = "`ioredis` options for pubsub. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference."; 117 }; 118 redisForJobQueue = lib.mkOption { 119 type = lib.types.nullOr redisType; 120 default = null; 121 description = "`ioredis` options for the job queue. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference."; 122 }; 123 redisForTimelines = lib.mkOption { 124 type = lib.types.nullOr redisType; 125 default = null; 126 description = "`ioredis` options for timelines. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference."; 127 }; 128 meilisearch = lib.mkOption { 129 description = "Meilisearch connection options."; 130 type = lib.types.nullOr ( 131 lib.types.submodule { 132 options = { 133 host = lib.mkOption { 134 type = lib.types.str; 135 default = "localhost"; 136 description = "The Meilisearch host."; 137 }; 138 port = lib.mkOption { 139 type = lib.types.port; 140 default = 7700; 141 description = "The Meilisearch port."; 142 }; 143 apiKey = lib.mkOption { 144 type = lib.types.nullOr lib.types.str; 145 default = null; 146 description = "The Meilisearch API key."; 147 }; 148 ssl = lib.mkOption { 149 type = lib.types.bool; 150 default = false; 151 description = "Whether to connect via SSL."; 152 }; 153 index = lib.mkOption { 154 type = lib.types.nullOr lib.types.str; 155 default = null; 156 description = "Meilisearch index to use."; 157 }; 158 scope = lib.mkOption { 159 type = lib.types.enum [ 160 "local" 161 "global" 162 ]; 163 default = "local"; 164 description = "The search scope."; 165 }; 166 }; 167 } 168 ); 169 default = null; 170 }; 171 id = lib.mkOption { 172 type = lib.types.enum [ 173 "aid" 174 "aidx" 175 "meid" 176 "ulid" 177 "objectid" 178 ]; 179 default = "aidx"; 180 description = "The ID generation method to use. Do not change after starting Misskey for the first time."; 181 }; 182 }; 183 }; 184 }; 185in 186 187{ 188 options = { 189 services.misskey = { 190 enable = lib.mkEnableOption "misskey"; 191 package = lib.mkPackageOption pkgs "misskey" { }; 192 inherit settings; 193 database = { 194 createLocally = lib.mkOption { 195 type = lib.types.bool; 196 default = false; 197 description = "Create the PostgreSQL database locally. Sets `services.misskey.settings.db.{db,host,port,user,pass}`."; 198 }; 199 passwordFile = lib.mkOption { 200 type = lib.types.nullOr lib.types.path; 201 default = null; 202 description = "The path to a file containing the database password. Sets `services.misskey.settings.db.pass`."; 203 }; 204 }; 205 redis = { 206 createLocally = lib.mkOption { 207 type = lib.types.bool; 208 default = false; 209 description = "Create and use a local Redis instance. Sets `services.misskey.settings.redis.host`."; 210 }; 211 passwordFile = lib.mkOption { 212 type = lib.types.nullOr lib.types.path; 213 default = null; 214 description = "The path to a file containing the Redis password. Sets `services.misskey.settings.redis.pass`."; 215 }; 216 }; 217 meilisearch = { 218 createLocally = lib.mkOption { 219 type = lib.types.bool; 220 default = false; 221 description = "Create and use a local Meilisearch instance. Sets `services.misskey.settings.meilisearch.{host,port,ssl}`."; 222 }; 223 keyFile = lib.mkOption { 224 type = lib.types.nullOr lib.types.path; 225 default = null; 226 description = "The path to a file containing the Meilisearch API key. Sets `services.misskey.settings.meilisearch.apiKey`."; 227 }; 228 }; 229 reverseProxy = { 230 enable = lib.mkEnableOption "a HTTP reverse proxy for Misskey"; 231 webserver = lib.mkOption { 232 type = lib.types.attrTag { 233 nginx = lib.mkOption { 234 type = lib.types.submodule (import ../web-servers/nginx/vhost-options.nix); 235 default = { }; 236 description = '' 237 Extra configuration for the nginx virtual host of Misskey. 238 Set to `{ }` to use the default configuration. 239 ''; 240 }; 241 caddy = lib.mkOption { 242 type = lib.types.submodule ( 243 import ../web-servers/caddy/vhost-options.nix { cfg = config.services.caddy; } 244 ); 245 default = { }; 246 description = '' 247 Extra configuration for the caddy virtual host of Misskey. 248 Set to `{ }` to use the default configuration. 249 ''; 250 }; 251 }; 252 description = "The webserver to use as the reverse proxy."; 253 }; 254 host = lib.mkOption { 255 type = lib.types.nullOr lib.types.str; 256 description = '' 257 The fully qualified domain name to bind to. Sets `services.misskey.settings.url`. 258 259 This is required when using `services.misskey.reverseProxy.enable = true`. 260 ''; 261 example = "misskey.example.com"; 262 default = null; 263 }; 264 ssl = lib.mkOption { 265 type = lib.types.nullOr lib.types.bool; 266 description = '' 267 Whether to enable SSL for the reverse proxy. Sets `services.misskey.settings.url`. 268 269 This is required when using `services.misskey.reverseProxy.enable = true`. 270 ''; 271 example = true; 272 default = null; 273 }; 274 }; 275 }; 276 }; 277 278 config = lib.mkIf cfg.enable { 279 assertions = [ 280 { 281 assertion = 282 cfg.reverseProxy.enable -> ((cfg.reverseProxy.host != null) && (cfg.reverseProxy.ssl != null)); 283 message = "`services.misskey.reverseProxy.enable` requires `services.misskey.reverseProxy.host` and `services.misskey.reverseProxy.ssl` to be set."; 284 } 285 ]; 286 287 services.misskey.settings = lib.mkMerge [ 288 (lib.mkIf cfg.database.createLocally { 289 db = { 290 db = lib.mkDefault "misskey"; 291 # Use unix socket instead of localhost to allow PostgreSQL peer authentication, 292 # required for `services.postgresql.ensureUsers` 293 host = lib.mkDefault "/var/run/postgresql"; 294 port = lib.mkDefault config.services.postgresql.settings.port; 295 user = lib.mkDefault "misskey"; 296 pass = lib.mkDefault null; 297 }; 298 }) 299 (lib.mkIf (cfg.database.passwordFile != null) { db.pass = lib.mkDefault "@DATABASE_PASSWORD@"; }) 300 (lib.mkIf cfg.redis.createLocally { redis.host = lib.mkDefault "localhost"; }) 301 (lib.mkIf (cfg.redis.passwordFile != null) { redis.pass = lib.mkDefault "@REDIS_PASSWORD@"; }) 302 (lib.mkIf cfg.meilisearch.createLocally { 303 meilisearch = { 304 host = lib.mkDefault "localhost"; 305 port = lib.mkDefault config.services.meilisearch.listenPort; 306 ssl = lib.mkDefault false; 307 }; 308 }) 309 (lib.mkIf (cfg.meilisearch.keyFile != null) { 310 meilisearch.apiKey = lib.mkDefault "@MEILISEARCH_KEY@"; 311 }) 312 (lib.mkIf cfg.reverseProxy.enable { 313 url = lib.mkDefault "${ 314 if cfg.reverseProxy.ssl then "https" else "http" 315 }://${cfg.reverseProxy.host}"; 316 }) 317 ]; 318 319 systemd.services.misskey = { 320 after = [ 321 "network-online.target" 322 "postgresql.service" 323 ]; 324 wants = [ "network-online.target" ]; 325 wantedBy = [ "multi-user.target" ]; 326 environment = { 327 MISSKEY_CONFIG_YML = "/run/misskey/default.yml"; 328 }; 329 preStart = 330 '' 331 install -m 700 ${settingsFormat.generate "misskey-config.yml" cfg.settings} /run/misskey/default.yml 332 '' 333 + (lib.optionalString (cfg.database.passwordFile != null) '' 334 ${pkgs.replace-secret}/bin/replace-secret '@DATABASE_PASSWORD@' "${cfg.database.passwordFile}" /run/misskey/default.yml 335 '') 336 + (lib.optionalString (cfg.redis.passwordFile != null) '' 337 ${pkgs.replace-secret}/bin/replace-secret '@REDIS_PASSWORD@' "${cfg.redis.passwordFile}" /run/misskey/default.yml 338 '') 339 + (lib.optionalString (cfg.meilisearch.keyFile != null) '' 340 ${pkgs.replace-secret}/bin/replace-secret '@MEILISEARCH_KEY@' "${cfg.meilisearch.keyFile}" /run/misskey/default.yml 341 ''); 342 serviceConfig = { 343 ExecStart = "${cfg.package}/bin/misskey migrateandstart"; 344 RuntimeDirectory = "misskey"; 345 RuntimeDirectoryMode = "700"; 346 StateDirectory = "misskey"; 347 StateDirectoryMode = "700"; 348 TimeoutSec = 60; 349 DynamicUser = true; 350 User = "misskey"; 351 LockPersonality = true; 352 PrivateDevices = true; 353 PrivateUsers = true; 354 ProtectClock = true; 355 ProtectControlGroups = true; 356 ProtectHome = true; 357 ProtectHostname = true; 358 ProtectKernelLogs = true; 359 ProtectProc = "invisible"; 360 ProtectKernelModules = true; 361 ProtectKernelTunables = true; 362 RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK"; 363 }; 364 }; 365 366 services.postgresql = lib.mkIf cfg.database.createLocally { 367 enable = true; 368 ensureDatabases = [ "misskey" ]; 369 ensureUsers = [ 370 { 371 name = "misskey"; 372 ensureDBOwnership = true; 373 } 374 ]; 375 }; 376 377 services.redis.servers = lib.mkIf cfg.redis.createLocally { 378 misskey = { 379 enable = true; 380 port = cfg.settings.redis.port; 381 }; 382 }; 383 384 services.meilisearch = lib.mkIf cfg.meilisearch.createLocally { enable = true; }; 385 386 services.caddy = lib.mkIf (cfg.reverseProxy.enable && cfg.reverseProxy.webserver ? caddy) { 387 enable = true; 388 virtualHosts.${cfg.settings.url} = lib.mkMerge [ 389 cfg.reverseProxy.webserver.caddy 390 { 391 hostName = lib.mkDefault cfg.settings.url; 392 extraConfig = '' 393 reverse_proxy localhost:${toString cfg.settings.port} 394 ''; 395 } 396 ]; 397 }; 398 399 services.nginx = lib.mkIf (cfg.reverseProxy.enable && cfg.reverseProxy.webserver ? nginx) { 400 enable = true; 401 virtualHosts.${cfg.reverseProxy.host} = lib.mkMerge [ 402 cfg.reverseProxy.webserver.nginx 403 { 404 locations."/" = { 405 proxyPass = lib.mkDefault "http://localhost:${toString cfg.settings.port}"; 406 proxyWebsockets = lib.mkDefault true; 407 recommendedProxySettings = lib.mkDefault true; 408 }; 409 } 410 (lib.mkIf (cfg.reverseProxy.ssl != null) { forceSSL = lib.mkDefault cfg.reverseProxy.ssl; }) 411 ]; 412 }; 413 }; 414 415 meta = { 416 maintainers = [ lib.maintainers.feathecutie ]; 417 }; 418}