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