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