|
335 | 335 | If the master is password protected (using the requirePass configuration) |
336 | 336 | it is possible to tell the slave to authenticate before starting the replication synchronization |
337 | 337 | process, otherwise the master will refuse the slave request. |
338 | | - (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)''; |
| 338 | + (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE) |
| 339 | + ''; |
| 340 | + }; |
| 341 | + |
| 342 | + masterAuthFile = lib.mkOption { |
| 343 | + type = with types; nullOr path; |
| 344 | + default = null; |
| 345 | + description = "File with password for the master user."; |
| 346 | + example = "/run/keys/redis-master-password"; |
| 347 | + }; |
| 348 | + |
| 349 | + masterUser = lib.mkOption { |
| 350 | + type = with types; nullOr str; |
| 351 | + default = null; |
| 352 | + description = '' |
| 353 | + If the master is password protected via ACLs this option can be used to specify |
| 354 | + the Redis user that is used by replicas.''; |
339 | 355 | }; |
340 | 356 |
|
341 | 357 | requirePass = lib.mkOption { |
|
355 | 371 | example = "/run/keys/redis-password"; |
356 | 372 | }; |
357 | 373 |
|
| 374 | + sentinelAuthPassFile = lib.mkOption { |
| 375 | + type = with types; nullOr path; |
| 376 | + default = null; |
| 377 | + description = "File with password for connecting to other Sentinel instances."; |
| 378 | + example = "/run/keys/sentinel-password"; |
| 379 | + }; |
| 380 | + |
| 381 | + sentinelAuthUser = lib.mkOption { |
| 382 | + type = with types; nullOr str; |
| 383 | + default = null; |
| 384 | + description = "The username to use to monitor a master from Sentinel."; |
| 385 | + }; |
| 386 | + |
| 387 | + sentinelMasterHost = lib.mkOption { |
| 388 | + type = with types; nullOr str; |
| 389 | + default = null; |
| 390 | + description = "The IP address (recommended) or hostname of the Redis master that Sentinel will monitor."; |
| 391 | + }; |
| 392 | + |
| 393 | + sentinelMasterName = lib.mkOption { |
| 394 | + type = with types; nullOr str; |
| 395 | + default = null; |
| 396 | + description = "The master name of the Redis master that Sentinel will monitor."; |
| 397 | + }; |
| 398 | + |
| 399 | + sentinelMasterPort = lib.mkOption { |
| 400 | + type = with types; nullOr int; |
| 401 | + default = null; |
| 402 | + description = "The TCP port of the Redis master that Sentinel will monitor."; |
| 403 | + }; |
| 404 | + |
| 405 | + sentinelMasterQuorum = lib.mkOption { |
| 406 | + type = with types; nullOr int; |
| 407 | + default = null; |
| 408 | + description = "The Sentinel quorum (minimum number of Sentinel nodes online for failover)"; |
| 409 | + }; |
| 410 | + |
358 | 411 | appendOnly = lib.mkOption { |
359 | 412 | type = types.bool; |
360 | 413 | default = false; |
|
452 | 505 |
|
453 | 506 | config = lib.mkIf (enabledServers != { }) { |
454 | 507 |
|
455 | | - assertions = lib.attrValues ( |
456 | | - lib.mapAttrs (name: conf: { |
457 | | - assertion = conf.requirePass != null -> conf.requirePassFile == null; |
458 | | - message = '' |
459 | | - You can only set one services.redis.servers.${name}.requirePass |
460 | | - or services.redis.servers.${name}.requirePassFile |
461 | | - ''; |
462 | | - }) enabledServers |
| 508 | + assertions = lib.concatLists ( |
| 509 | + lib.mapAttrsToList (name: conf: [ |
| 510 | + { |
| 511 | + assertion = conf.requirePass != null -> conf.requirePassFile == null; |
| 512 | + message = '' |
| 513 | + You can only set one of services.redis.servers.${name}.requirePass |
| 514 | + or services.redis.servers.${name}.requirePassFile |
| 515 | + ''; |
| 516 | + } |
| 517 | + { |
| 518 | + assertion = conf.masterAuth != null -> conf.masterAuthFile == null; |
| 519 | + message = '' |
| 520 | + You can only set one of services.redis.servers.${name}.masterAuth |
| 521 | + or services.redis.servers.${name}.masterAuthFile |
| 522 | + ''; |
| 523 | + } |
| 524 | + { |
| 525 | + assertion = conf.masterUser != null -> (conf.masterAuth != null || conf.masterAuthFile != null); |
| 526 | + message = '' |
| 527 | + If using services.redis.servers.${name}.masterUser, either |
| 528 | + services.redis.servers.${name}.masterAuthFile or |
| 529 | + services.redis.servers.${name}.masterAuth must be provided |
| 530 | + ''; |
| 531 | + } |
| 532 | + { |
| 533 | + assertion = |
| 534 | + conf.sentinelMasterName != null |
| 535 | + -> ( |
| 536 | + conf.sentinelMasterHost != null |
| 537 | + && conf.sentinelMasterPort != null |
| 538 | + && conf.sentinelMasterQuorum != null |
| 539 | + ); |
| 540 | + message = '' |
| 541 | + For Sentinel, |
| 542 | + services.redis.servers.${name}.sentinelMasterName, |
| 543 | + services.redis.servers.${name}.sentinelMasterHost, |
| 544 | + services.redis.servers.${name}.sentinelMasterPort, |
| 545 | + and services.redis.servers.${name}.sentinelMasterQuorum |
| 546 | + must all be provided |
| 547 | + ''; |
| 548 | + } |
| 549 | + { |
| 550 | + assertion = conf.sentinelAuthPassFile != null -> conf.sentinelMasterName != null; |
| 551 | + message = '' |
| 552 | + For Sentinel authentication, services.redis.servers.${name}.sentinelMasterName, |
| 553 | + must be provided |
| 554 | + ''; |
| 555 | + } |
| 556 | + ]) enabledServers |
463 | 557 | ); |
464 | 558 |
|
465 | 559 | boot.kernel.sysctl = lib.mkIf cfg.vmOverCommit { |
|
498 | 592 | ExecStart = "${cfg.package}/bin/${ |
499 | 593 | cfg.package.serverBin or "redis-server" |
500 | 594 | } /var/lib/${redisName name}/redis.conf ${lib.escapeShellArgs conf.extraParams}"; |
| 595 | + |
| 596 | + # NOTE: Redis/Valkey Sentinel persists dynamic cluster state by rewriting its |
| 597 | + # configuration file at runtime (redis.conf). This includes monitors, |
| 598 | + # authentication credentials, and failover metadata, and this behaviour |
| 599 | + # cannot be disabled. |
| 600 | + # As a result, a fully declarative configuration is not possible for |
| 601 | + # Sentinel-managed options. The preStart logic below appends sentinel |
| 602 | + # configuration only if it is not already present, in order to avoid |
| 603 | + # overwriting state that is owned and maintained by Sentinel itself. |
| 604 | + # This is an intentional deviation from strict declarative semantics and |
| 605 | + # is required for correct Sentinel operation. |
501 | 606 | ExecStartPre = |
502 | 607 | "+" |
503 | 608 | + pkgs.writeShellScript "${redisName name}-prep-conf" ( |
|
515 | 620 | fi |
516 | 621 | echo 'include "${redisConfStore}"' > "${redisConfRun}" |
517 | 622 | ${lib.optionalString (conf.requirePassFile != null) '' |
518 | | - { |
519 | | - echo -n "requirepass " |
520 | | - cat ${lib.escapeShellArg conf.requirePassFile} |
521 | | - } >> "${redisConfRun}" |
| 623 | + echo "requirepass $(cat ${lib.escapeShellArg conf.requirePassFile})" >> "${redisConfRun}" |
| 624 | + ''} |
| 625 | + ${lib.optionalString (conf.masterUser != null) '' |
| 626 | + echo "masteruser ${conf.masterUser}" >> "${redisConfRun}" |
| 627 | + ''} |
| 628 | + ${lib.optionalString (conf.masterAuthFile != null) '' |
| 629 | + echo "masterauth $(cat ${lib.escapeShellArg conf.masterAuthFile})" >> "${redisConfRun}" |
| 630 | + ''} |
| 631 | + ${lib.optionalString (conf.sentinelMasterHost != null) '' |
| 632 | + sentinel_monitor_line="sentinel monitor ${conf.sentinelMasterName} ${conf.sentinelMasterHost} ${toString conf.sentinelMasterPort} ${toString conf.sentinelMasterQuorum}" |
| 633 | + if grep -qE "^sentinel monitor ${conf.sentinelMasterName}\b" "${redisConfVar}"; then |
| 634 | + sed -i \ |
| 635 | + "s|^sentinel monitor ${conf.sentinelMasterName}\b.*|$sentinel_monitor_line|" "${redisConfVar}" |
| 636 | + else |
| 637 | + echo "$sentinel_monitor_line" >> "${redisConfVar}" |
| 638 | + fi |
| 639 | + ''} |
| 640 | + ${lib.optionalString (conf.sentinelAuthUser != null) '' |
| 641 | + sentinel_auth_user_line="sentinel auth-user ${conf.sentinelMasterName} ${conf.sentinelAuthUser}" |
| 642 | + if grep -qE "^sentinel auth-user ${conf.sentinelMasterName}\b" "${redisConfVar}"; then |
| 643 | + sed -i \ |
| 644 | + "s|^sentinel auth-user ${conf.sentinelMasterName}\b.*|$sentinel_auth_user_line|" "${redisConfVar}" |
| 645 | + else |
| 646 | + echo "$sentinel_auth_user_line" >> "${redisConfVar}" |
| 647 | + fi |
| 648 | + ''} |
| 649 | + ${lib.optionalString (conf.sentinelAuthPassFile != null) '' |
| 650 | + sentinel_auth_pass_line="sentinel auth-pass ${conf.sentinelMasterName} $(cat ${lib.escapeShellArg conf.sentinelAuthPassFile})" |
| 651 | + if grep -qE "^sentinel auth-pass ${conf.sentinelMasterName}\b" "${redisConfVar}"; then |
| 652 | + sed -i \ |
| 653 | + "s|^sentinel auth-pass ${conf.sentinelMasterName}\b.*|$sentinel_auth_pass_line|" "${redisConfVar}" |
| 654 | + else |
| 655 | + echo "$sentinel_auth_pass_line" >> "${redisConfVar}" |
| 656 | + fi |
522 | 657 | ''} |
523 | 658 | '' |
524 | 659 | ); |
|
0 commit comments