at 24.11-pre 22 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4let 5 cfg = config.services.tt-rss; 6 7 inherit (cfg) phpPackage; 8 9 configVersion = 26; 10 11 dbPort = if cfg.database.port == null 12 then (if cfg.database.type == "pgsql" then 5432 else 3306) 13 else cfg.database.port; 14 15 poolName = "tt-rss"; 16 17 mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql"; 18 pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql"; 19 20 tt-rss-config = let 21 password = 22 if (cfg.database.password != null) then 23 "'${(escape ["'" "\\"] cfg.database.password)}'" 24 else if (cfg.database.passwordFile != null) then 25 "file_get_contents('${cfg.database.passwordFile}')" 26 else 27 null 28 ; 29 in pkgs.writeText "config.php" '' 30 <?php 31 putenv('TTRSS_PHP_EXECUTABLE=${phpPackage}/bin/php'); 32 33 putenv('TTRSS_LOCK_DIRECTORY=${cfg.root}/lock'); 34 putenv('TTRSS_CACHE_DIR=${cfg.root}/cache'); 35 putenv('TTRSS_ICONS_DIR=${cfg.root}/feed-icons'); 36 putenv('TTRSS_ICONS_URL=feed-icons'); 37 putenv('TTRSS_SELF_URL_PATH=${cfg.selfUrlPath}'); 38 39 putenv('TTRSS_MYSQL_CHARSET=UTF8'); 40 41 putenv('TTRSS_DB_TYPE=${cfg.database.type}'); 42 putenv('TTRSS_DB_HOST=${optionalString (cfg.database.host != null) cfg.database.host}'); 43 putenv('TTRSS_DB_USER=${cfg.database.user}'); 44 putenv('TTRSS_DB_NAME=${cfg.database.name}'); 45 putenv('TTRSS_DB_PASS=' ${optionalString (password != null) ". ${password}"}); 46 putenv('TTRSS_DB_PORT=${toString dbPort}'); 47 48 putenv('TTRSS_AUTH_AUTO_CREATE=${boolToString cfg.auth.autoCreate}'); 49 putenv('TTRSS_AUTH_AUTO_LOGIN=${boolToString cfg.auth.autoLogin}'); 50 51 putenv('TTRSS_FEED_CRYPT_KEY=${escape ["'" "\\"] cfg.feedCryptKey}'); 52 53 54 putenv('TTRSS_SINGLE_USER_MODE=${boolToString cfg.singleUserMode}'); 55 56 putenv('TTRSS_SIMPLE_UPDATE_MODE=${boolToString cfg.simpleUpdateMode}'); 57 58 # Never check for updates - the running version of the code should 59 # be controlled entirely by the version of TT-RSS active in the 60 # current Nix profile. If TT-RSS updates itself to a version 61 # requiring a database schema upgrade, and then the SystemD 62 # tt-rss.service is restarted, the old code copied from the Nix 63 # store will overwrite the updated version, causing the code to 64 # detect the need for a schema "upgrade" (since the schema version 65 # in the database is different than in the code), but the update 66 # schema operation in TT-RSS will do nothing because the schema 67 # version in the database is newer than that in the code. 68 putenv('TTRSS_CHECK_FOR_UPDATES=false'); 69 70 putenv('TTRSS_FORCE_ARTICLE_PURGE=${toString cfg.forceArticlePurge}'); 71 putenv('TTRSS_SESSION_COOKIE_LIFETIME=${toString cfg.sessionCookieLifetime}'); 72 putenv('TTRSS_ENABLE_GZIP_OUTPUT=${boolToString cfg.enableGZipOutput}'); 73 74 putenv('TTRSS_PLUGINS=${builtins.concatStringsSep "," cfg.plugins}'); 75 76 putenv('TTRSS_LOG_DESTINATION=${cfg.logDestination}'); 77 putenv('TTRSS_CONFIG_VERSION=${toString configVersion}'); 78 79 80 putenv('TTRSS_PUBSUBHUBBUB_ENABLED=${boolToString cfg.pubSubHubbub.enable}'); 81 putenv('TTRSS_PUBSUBHUBBUB_HUB=${cfg.pubSubHubbub.hub}'); 82 83 putenv('TTRSS_SPHINX_SERVER=${cfg.sphinx.server}'); 84 putenv('TTRSS_SPHINX_INDEX=${builtins.concatStringsSep "," cfg.sphinx.index}'); 85 86 putenv('TTRSS_ENABLE_REGISTRATION=${boolToString cfg.registration.enable}'); 87 putenv('TTRSS_REG_NOTIFY_ADDRESS=${cfg.registration.notifyAddress}'); 88 putenv('TTRSS_REG_MAX_USERS=${toString cfg.registration.maxUsers}'); 89 90 putenv('TTRSS_SMTP_SERVER=${cfg.email.server}'); 91 putenv('TTRSS_SMTP_LOGIN=${cfg.email.login}'); 92 putenv('TTRSS_SMTP_PASSWORD=${escape ["'" "\\"] cfg.email.password}'); 93 putenv('TTRSS_SMTP_SECURE=${cfg.email.security}'); 94 95 putenv('TTRSS_SMTP_FROM_NAME=${escape ["'" "\\"] cfg.email.fromName}'); 96 putenv('TTRSS_SMTP_FROM_ADDRESS=${escape ["'" "\\"] cfg.email.fromAddress}'); 97 putenv('TTRSS_DIGEST_SUBJECT=${escape ["'" "\\"] cfg.email.digestSubject}'); 98 99 ${cfg.extraConfig} 100 ''; 101 102 # tt-rss and plugins and themes and config.php 103 servedRoot = pkgs.runCommand "tt-rss-served-root" {} '' 104 cp --no-preserve=mode -r ${pkgs.tt-rss} $out 105 cp ${tt-rss-config} $out/config.php 106 ${optionalString (cfg.pluginPackages != []) '' 107 for plugin in ${concatStringsSep " " cfg.pluginPackages}; do 108 cp -r "$plugin"/* "$out/plugins.local/" 109 done 110 ''} 111 ${optionalString (cfg.themePackages != []) '' 112 for theme in ${concatStringsSep " " cfg.themePackages}; do 113 cp -r "$theme"/* "$out/themes.local/" 114 done 115 ''} 116 ''; 117 118 in { 119 120 ###### interface 121 122 options = { 123 124 services.tt-rss = { 125 126 enable = mkEnableOption "tt-rss"; 127 128 root = mkOption { 129 type = types.path; 130 default = "/var/lib/tt-rss"; 131 description = '' 132 Root of the application. 133 ''; 134 }; 135 136 user = mkOption { 137 type = types.str; 138 default = "tt_rss"; 139 description = '' 140 User account under which both the update daemon and the web-application run. 141 ''; 142 }; 143 144 pool = mkOption { 145 type = types.str; 146 default = "${poolName}"; 147 description = '' 148 Name of existing phpfpm pool that is used to run web-application. 149 If not specified a pool will be created automatically with 150 default values. 151 ''; 152 }; 153 154 virtualHost = mkOption { 155 type = types.nullOr types.str; 156 default = "tt-rss"; 157 description = '' 158 Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost. 159 ''; 160 }; 161 162 database = { 163 type = mkOption { 164 type = types.enum ["pgsql" "mysql"]; 165 default = "pgsql"; 166 description = '' 167 Database to store feeds. Supported are pgsql and mysql. 168 ''; 169 }; 170 171 host = mkOption { 172 type = types.nullOr types.str; 173 default = null; 174 description = '' 175 Host of the database. Leave null to use Unix domain socket. 176 ''; 177 }; 178 179 name = mkOption { 180 type = types.str; 181 default = "tt_rss"; 182 description = '' 183 Name of the existing database. 184 ''; 185 }; 186 187 user = mkOption { 188 type = types.str; 189 default = "tt_rss"; 190 description = '' 191 The database user. The user must exist and has access to 192 the specified database. 193 ''; 194 }; 195 196 password = mkOption { 197 type = types.nullOr types.str; 198 default = null; 199 description = '' 200 The database user's password. 201 ''; 202 }; 203 204 passwordFile = mkOption { 205 type = types.nullOr types.str; 206 default = null; 207 description = '' 208 The database user's password. 209 ''; 210 }; 211 212 port = mkOption { 213 type = types.nullOr types.port; 214 default = null; 215 description = '' 216 The database's port. If not set, the default ports will be provided (5432 217 and 3306 for pgsql and mysql respectively). 218 ''; 219 }; 220 221 createLocally = mkOption { 222 type = types.bool; 223 default = true; 224 description = "Create the database and database user locally."; 225 }; 226 }; 227 228 auth = { 229 autoCreate = mkOption { 230 type = types.bool; 231 default = true; 232 description = '' 233 Allow authentication modules to auto-create users in tt-rss internal 234 database when authenticated successfully. 235 ''; 236 }; 237 238 autoLogin = mkOption { 239 type = types.bool; 240 default = true; 241 description = '' 242 Automatically login user on remote or other kind of externally supplied 243 authentication, otherwise redirect to login form as normal. 244 If set to true, users won't be able to set application language 245 and settings profile. 246 ''; 247 }; 248 }; 249 250 pubSubHubbub = { 251 hub = mkOption { 252 type = types.str; 253 default = ""; 254 description = '' 255 URL to a PubSubHubbub-compatible hub server. If defined, "Published 256 articles" generated feed would automatically become PUSH-enabled. 257 ''; 258 }; 259 260 enable = mkOption { 261 type = types.bool; 262 default = false; 263 description = '' 264 Enable client PubSubHubbub support in tt-rss. When disabled, tt-rss 265 won't try to subscribe to PUSH feed updates. 266 ''; 267 }; 268 }; 269 270 sphinx = { 271 server = mkOption { 272 type = types.str; 273 default = "localhost:9312"; 274 description = '' 275 Hostname:port combination for the Sphinx server. 276 ''; 277 }; 278 279 index = mkOption { 280 type = types.listOf types.str; 281 default = ["ttrss" "delta"]; 282 description = '' 283 Index names in Sphinx configuration. Example configuration 284 files are available on tt-rss wiki. 285 ''; 286 }; 287 }; 288 289 registration = { 290 enable = mkOption { 291 type = types.bool; 292 default = false; 293 description = '' 294 Allow users to register themselves. Please be aware that allowing 295 random people to access your tt-rss installation is a security risk 296 and potentially might lead to data loss or server exploit. Disabled 297 by default. 298 ''; 299 }; 300 301 notifyAddress = mkOption { 302 type = types.str; 303 default = ""; 304 description = '' 305 Email address to send new user notifications to. 306 ''; 307 }; 308 309 maxUsers = mkOption { 310 type = types.int; 311 default = 0; 312 description = '' 313 Maximum amount of users which will be allowed to register on this 314 system. 0 - no limit. 315 ''; 316 }; 317 }; 318 319 email = { 320 server = mkOption { 321 type = types.str; 322 default = ""; 323 example = "localhost:25"; 324 description = '' 325 Hostname:port combination to send outgoing mail. Blank - use system 326 MTA. 327 ''; 328 }; 329 330 login = mkOption { 331 type = types.str; 332 default = ""; 333 description = '' 334 SMTP authentication login used when sending outgoing mail. 335 ''; 336 }; 337 338 password = mkOption { 339 type = types.str; 340 default = ""; 341 description = '' 342 SMTP authentication password used when sending outgoing mail. 343 ''; 344 }; 345 346 security = mkOption { 347 type = types.enum ["" "ssl" "tls"]; 348 default = ""; 349 description = '' 350 Used to select a secure SMTP connection. Allowed values: ssl, tls, 351 or empty. 352 ''; 353 }; 354 355 fromName = mkOption { 356 type = types.str; 357 default = "Tiny Tiny RSS"; 358 description = '' 359 Name for sending outgoing mail. This applies to password reset 360 notifications, digest emails and any other mail. 361 ''; 362 }; 363 364 fromAddress = mkOption { 365 type = types.str; 366 default = ""; 367 description = '' 368 Address for sending outgoing mail. This applies to password reset 369 notifications, digest emails and any other mail. 370 ''; 371 }; 372 373 digestSubject = mkOption { 374 type = types.str; 375 default = "[tt-rss] New headlines for last 24 hours"; 376 description = '' 377 Subject line for email digests. 378 ''; 379 }; 380 }; 381 382 sessionCookieLifetime = mkOption { 383 type = types.int; 384 default = 86400; 385 description = '' 386 Default lifetime of a session (e.g. login) cookie. In seconds, 387 0 means cookie will be deleted when browser closes. 388 ''; 389 }; 390 391 selfUrlPath = mkOption { 392 type = types.str; 393 description = '' 394 Full URL of your tt-rss installation. This should be set to the 395 location of tt-rss directory, e.g. http://example.org/tt-rss/ 396 You need to set this option correctly otherwise several features 397 including PUSH, bookmarklets and browser integration will not work properly. 398 ''; 399 example = "http://localhost"; 400 }; 401 402 feedCryptKey = mkOption { 403 type = types.str; 404 default = ""; 405 description = '' 406 Key used for encryption of passwords for password-protected feeds 407 in the database. A string of 24 random characters. If left blank, encryption 408 is not used. Requires mcrypt functions. 409 Warning: changing this key will make your stored feed passwords impossible 410 to decrypt. 411 ''; 412 }; 413 414 singleUserMode = mkOption { 415 type = types.bool; 416 default = false; 417 418 description = '' 419 Operate in single user mode, disables all functionality related to 420 multiple users and authentication. Enabling this assumes you have 421 your tt-rss directory protected by other means (e.g. http auth). 422 ''; 423 }; 424 425 simpleUpdateMode = mkOption { 426 type = types.bool; 427 default = false; 428 description = '' 429 Enables fallback update mode where tt-rss tries to update feeds in 430 background while tt-rss is open in your browser. 431 If you don't have a lot of feeds and don't want to or can't run 432 background processes while not running tt-rss, this method is generally 433 viable to keep your feeds up to date. 434 Still, there are more robust (and recommended) updating methods 435 available, you can read about them here: <https://tt-rss.org/wiki/UpdatingFeeds> 436 ''; 437 }; 438 439 forceArticlePurge = mkOption { 440 type = types.int; 441 default = 0; 442 description = '' 443 When this option is not 0, users ability to control feed purging 444 intervals is disabled and all articles (which are not starred) 445 older than this amount of days are purged. 446 ''; 447 }; 448 449 enableGZipOutput = mkOption { 450 type = types.bool; 451 default = true; 452 description = '' 453 Selectively gzip output to improve wire performance. This requires 454 PHP Zlib extension on the server. 455 Enabling this can break tt-rss in several httpd/php configurations, 456 if you experience weird errors and tt-rss failing to start, blank pages 457 after login, or content encoding errors, disable it. 458 ''; 459 }; 460 461 phpPackage = lib.mkOption { 462 type = lib.types.package; 463 default = pkgs.php; 464 defaultText = "pkgs.php"; 465 description = '' 466 php package to use for php fpm and update daemon. 467 ''; 468 }; 469 470 plugins = mkOption { 471 type = types.listOf types.str; 472 default = ["auth_internal" "note"]; 473 description = '' 474 List of plugins to load automatically for all users. 475 System plugins have to be specified here. Please enable at least one 476 authentication plugin here (auth_*). 477 Users may enable other user plugins from Preferences/Plugins but may not 478 disable plugins specified in this list. 479 Disabling auth_internal in this list would automatically disable 480 reset password link on the login form. 481 ''; 482 }; 483 484 pluginPackages = mkOption { 485 type = types.listOf types.package; 486 default = []; 487 description = '' 488 List of plugins to install. The list elements are expected to 489 be derivations. All elements in this derivation are automatically 490 copied to the `plugins.local` directory. 491 ''; 492 }; 493 494 themePackages = mkOption { 495 type = types.listOf types.package; 496 default = []; 497 description = '' 498 List of themes to install. The list elements are expected to 499 be derivations. All elements in this derivation are automatically 500 copied to the `themes.local` directory. 501 ''; 502 }; 503 504 logDestination = mkOption { 505 type = types.enum ["" "sql" "syslog"]; 506 default = "sql"; 507 description = '' 508 Log destination to use. Possible values: sql (uses internal logging 509 you can read in Preferences -> System), syslog - logs to system log. 510 Setting this to blank uses PHP logging (usually to http server 511 error.log). 512 ''; 513 }; 514 515 extraConfig = mkOption { 516 type = types.lines; 517 default = ""; 518 description = '' 519 Additional lines to append to `config.php`. 520 ''; 521 }; 522 }; 523 }; 524 525 imports = [ 526 (mkRemovedOptionModule ["services" "tt-rss" "checkForUpdates"] '' 527 This option was removed because setting this to true will cause TT-RSS 528 to be unable to start if an automatic update of the code in 529 services.tt-rss.root leads to a database schema upgrade that is not 530 supported by the code active in the Nix store. 531 '') 532 ]; 533 534 ###### implementation 535 536 config = mkIf cfg.enable { 537 538 assertions = [ 539 { 540 assertion = cfg.database.password != null -> cfg.database.passwordFile == null; 541 message = "Cannot set both password and passwordFile"; 542 } 543 { 544 assertion = cfg.database.createLocally -> cfg.database.name == cfg.user && cfg.database.user == cfg.user; 545 message = '' 546 When creating a database via NixOS, the db user and db name must be equal! 547 If you already have an existing DB+user and this assertion is new, you can safely set 548 `services.tt-rss.database.createLocally` to `false` because removal of `ensureUsers` 549 and `ensureDatabases` doesn't have any effect. 550 ''; 551 } 552 ]; 553 554 services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") { 555 ${poolName} = { 556 inherit (cfg) user; 557 inherit phpPackage; 558 settings = mapAttrs (name: mkDefault) { 559 "listen.owner" = "nginx"; 560 "listen.group" = "nginx"; 561 "listen.mode" = "0600"; 562 "pm" = "dynamic"; 563 "pm.max_children" = 75; 564 "pm.start_servers" = 10; 565 "pm.min_spare_servers" = 5; 566 "pm.max_spare_servers" = 20; 567 "pm.max_requests" = 500; 568 "catch_workers_output" = 1; 569 }; 570 }; 571 }; 572 573 # NOTE: No configuration is done if not using virtual host 574 services.nginx = mkIf (cfg.virtualHost != null) { 575 enable = true; 576 virtualHosts = { 577 ${cfg.virtualHost} = { 578 root = "${cfg.root}/www"; 579 580 locations."/" = { 581 index = "index.php"; 582 }; 583 584 locations."^~ /feed-icons" = { 585 root = "${cfg.root}"; 586 }; 587 588 locations."~ \\.php$" = { 589 extraConfig = '' 590 fastcgi_split_path_info ^(.+\.php)(/.+)$; 591 fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket}; 592 fastcgi_index index.php; 593 ''; 594 }; 595 }; 596 }; 597 }; 598 599 systemd.tmpfiles.rules = [ 600 "d '${cfg.root}' 0555 ${cfg.user} tt_rss - -" 601 "d '${cfg.root}/lock' 0755 ${cfg.user} tt_rss - -" 602 "d '${cfg.root}/cache' 0755 ${cfg.user} tt_rss - -" 603 "d '${cfg.root}/cache/upload' 0755 ${cfg.user} tt_rss - -" 604 "d '${cfg.root}/cache/images' 0755 ${cfg.user} tt_rss - -" 605 "d '${cfg.root}/cache/export' 0755 ${cfg.user} tt_rss - -" 606 "d '${cfg.root}/feed-icons' 0755 ${cfg.user} tt_rss - -" 607 "L+ '${cfg.root}/www' - - - - ${servedRoot}" 608 ]; 609 610 systemd.services = { 611 phpfpm-tt-rss = mkIf (cfg.pool == "${poolName}") { 612 restartTriggers = [ servedRoot ]; 613 }; 614 615 tt-rss = { 616 description = "Tiny Tiny RSS feeds update daemon"; 617 618 preStart = '' 619 ${phpPackage}/bin/php ${cfg.root}/www/update.php --update-schema --force-yes 620 ''; 621 622 serviceConfig = { 623 User = "${cfg.user}"; 624 Group = "tt_rss"; 625 ExecStart = "${phpPackage}/bin/php ${cfg.root}/www/update.php --daemon --quiet"; 626 Restart = "on-failure"; 627 RestartSec = "60"; 628 SyslogIdentifier = "tt-rss"; 629 }; 630 631 wantedBy = [ "multi-user.target" ]; 632 requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; 633 after = [ "network.target" ] ++ optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; 634 }; 635 }; 636 637 services.mysql = mkIf mysqlLocal { 638 enable = true; 639 package = mkDefault pkgs.mariadb; 640 ensureDatabases = [ cfg.database.name ]; 641 ensureUsers = [ 642 { 643 name = cfg.user; 644 ensurePermissions = { 645 "${cfg.database.name}.*" = "ALL PRIVILEGES"; 646 }; 647 } 648 ]; 649 }; 650 651 services.postgresql = mkIf pgsqlLocal { 652 enable = mkDefault true; 653 ensureDatabases = [ cfg.database.name ]; 654 ensureUsers = [ 655 { name = cfg.database.user; 656 ensureDBOwnership = true; 657 } 658 ]; 659 }; 660 661 users.users.tt_rss = optionalAttrs (cfg.user == "tt_rss") { 662 description = "tt-rss service user"; 663 isSystemUser = true; 664 group = "tt_rss"; 665 }; 666 667 users.groups.tt_rss = {}; 668 }; 669}