Skip to main content

redis_server_wrapper/
server.rs

1//! Type-safe wrapper for `redis-server` with builder pattern.
2
3use std::collections::HashMap;
4use std::fs;
5use std::path::PathBuf;
6use std::time::Duration;
7
8use tokio::process::Command;
9
10use crate::cli::RedisCli;
11use crate::error::{Error, Result};
12
13/// Full configuration snapshot for a single `redis-server` process.
14///
15/// This struct is populated by the [`RedisServer`] builder and passed to
16/// [`RedisServer::start`]. You rarely need to construct it directly; use the
17/// builder instead.
18///
19/// # Example
20///
21/// ```no_run
22/// use redis_server_wrapper::RedisServer;
23///
24/// # async fn example() {
25/// let server = RedisServer::new()
26///     .port(6400)
27///     .bind("127.0.0.1")
28///     .save(false)
29///     .start()
30///     .await
31///     .unwrap();
32///
33/// assert!(server.is_alive().await);
34/// // Stopped automatically on Drop.
35/// # }
36/// ```
37#[derive(Debug, Clone)]
38pub struct RedisServerConfig {
39    // -- network --
40    /// TCP port the server listens on (default: `6379`).
41    pub port: u16,
42    /// IP address to bind (default: `"127.0.0.1"`).
43    pub bind: String,
44    /// Whether protected mode is enabled (default: `false`).
45    pub protected_mode: bool,
46    /// TCP backlog queue length, if set.
47    pub tcp_backlog: Option<u32>,
48    /// Unix domain socket path, if set.
49    pub unixsocket: Option<PathBuf>,
50    /// Unix socket file permissions (e.g. `700`), if set.
51    pub unixsocketperm: Option<u32>,
52    /// Idle client timeout in seconds (`0` = disabled), if set.
53    pub timeout: Option<u32>,
54    /// TCP keepalive interval in seconds, if set.
55    pub tcp_keepalive: Option<u32>,
56
57    // -- tls --
58    /// TLS listening port, if set.
59    pub tls_port: Option<u16>,
60    /// Path to the TLS certificate file, if set.
61    pub tls_cert_file: Option<PathBuf>,
62    /// Path to the TLS private key file, if set.
63    pub tls_key_file: Option<PathBuf>,
64    /// Passphrase for the TLS private key file, if set.
65    pub tls_key_file_pass: Option<String>,
66    /// Path to the TLS CA certificate file, if set.
67    pub tls_ca_cert_file: Option<PathBuf>,
68    /// Path to a directory containing TLS CA certificates, if set.
69    pub tls_ca_cert_dir: Option<PathBuf>,
70    /// Whether TLS client authentication is required, if set.
71    pub tls_auth_clients: Option<bool>,
72    /// Path to the TLS client certificate file (for outgoing connections), if set.
73    pub tls_client_cert_file: Option<PathBuf>,
74    /// Path to the TLS client private key file (for outgoing connections), if set.
75    pub tls_client_key_file: Option<PathBuf>,
76    /// Passphrase for the TLS client private key file, if set.
77    pub tls_client_key_file_pass: Option<String>,
78    /// Path to the DH parameters file for DHE ciphers, if set.
79    pub tls_dh_params_file: Option<PathBuf>,
80    /// Allowed TLS 1.2 ciphers (OpenSSL cipher list format), if set.
81    pub tls_ciphers: Option<String>,
82    /// Allowed TLS 1.3 ciphersuites (colon-separated), if set.
83    pub tls_ciphersuites: Option<String>,
84    /// Allowed TLS protocol versions (e.g. `"TLSv1.2 TLSv1.3"`), if set.
85    pub tls_protocols: Option<String>,
86    /// Whether the server prefers its own cipher order, if set.
87    pub tls_prefer_server_ciphers: Option<bool>,
88    /// Whether TLS session caching is enabled, if set.
89    pub tls_session_caching: Option<bool>,
90    /// Number of entries in the TLS session cache, if set.
91    pub tls_session_cache_size: Option<u32>,
92    /// Timeout in seconds for cached TLS sessions, if set.
93    pub tls_session_cache_timeout: Option<u32>,
94    /// Whether replication traffic uses TLS, if set.
95    pub tls_replication: Option<bool>,
96    /// Whether cluster bus communication uses TLS, if set.
97    pub tls_cluster: Option<bool>,
98
99    // -- general --
100    /// Whether the server daemonizes itself (default: `true`).
101    pub daemonize: bool,
102    /// Working directory for data files (default: a sub-directory of `$TMPDIR`).
103    pub dir: PathBuf,
104    /// Path to the log file, if set. Defaults to `redis.log` inside the node directory.
105    pub logfile: Option<String>,
106    /// Server log verbosity (default: [`LogLevel::Notice`]).
107    pub loglevel: LogLevel,
108    /// Number of databases, if set (Redis default: `16`).
109    pub databases: Option<u32>,
110
111    // -- memory --
112    /// Maximum memory limit (e.g. `"256mb"`), if set.
113    pub maxmemory: Option<String>,
114    /// Eviction policy when `maxmemory` is reached, if set.
115    pub maxmemory_policy: Option<String>,
116    /// Number of keys sampled per eviction round, if set (Redis default: `5`).
117    pub maxmemory_samples: Option<u32>,
118    /// Per-client memory limit (e.g. `"0"` = disabled), if set.
119    pub maxmemory_clients: Option<String>,
120    /// Eviction processing effort (1-100), if set (Redis default: `10`).
121    pub maxmemory_eviction_tenacity: Option<u32>,
122    /// Maximum number of simultaneous client connections, if set.
123    pub maxclients: Option<u32>,
124    /// Logarithmic factor for the LFU frequency counter, if set (Redis default: `10`).
125    pub lfu_log_factor: Option<u32>,
126    /// LFU counter decay time in minutes, if set (Redis default: `1`).
127    pub lfu_decay_time: Option<u32>,
128    /// Effort spent on active key expiration (1-100), if set (Redis default: `10`).
129    pub active_expire_effort: Option<u32>,
130
131    // -- lazyfree --
132    /// Whether eviction uses background deletion, if set.
133    pub lazyfree_lazy_eviction: Option<bool>,
134    /// Whether expired-key deletion uses background threads, if set.
135    pub lazyfree_lazy_expire: Option<bool>,
136    /// Whether implicit `DEL` commands (e.g. `RENAME`) use background deletion, if set.
137    pub lazyfree_lazy_server_del: Option<bool>,
138    /// Whether explicit `DEL` behaves like `UNLINK`, if set.
139    pub lazyfree_lazy_user_del: Option<bool>,
140    /// Whether `FLUSHDB`/`FLUSHALL` default to `ASYNC`, if set.
141    pub lazyfree_lazy_user_flush: Option<bool>,
142
143    // -- persistence --
144    /// RDB save policy (default: [`SavePolicy::Disabled`]).
145    pub save: SavePolicy,
146    /// Whether AOF persistence is enabled (default: `false`).
147    pub appendonly: bool,
148    /// AOF fsync policy, if set.
149    pub appendfsync: Option<AppendFsync>,
150    /// AOF filename, if set (Redis default: `"appendonly.aof"`).
151    pub appendfilename: Option<String>,
152    /// AOF directory name, if set (Redis default: `"appendonlydir"`).
153    pub appenddirname: Option<PathBuf>,
154    /// Whether the AOF file uses an RDB preamble, if set.
155    pub aof_use_rdb_preamble: Option<bool>,
156    /// Whether truncated AOF files are loaded, if set.
157    pub aof_load_truncated: Option<bool>,
158    /// Maximum allowed size of a corrupt AOF tail, if set (e.g. `"32mb"`).
159    pub aof_load_corrupt_tail_max_size: Option<String>,
160    /// Whether AOF rewrite performs incremental fsync, if set.
161    pub aof_rewrite_incremental_fsync: Option<bool>,
162    /// Whether timestamps are recorded in the AOF file, if set.
163    pub aof_timestamp_enabled: Option<bool>,
164    /// Trigger an AOF rewrite when the file grows by this percentage, if set.
165    pub auto_aof_rewrite_percentage: Option<u32>,
166    /// Minimum AOF size before an automatic rewrite is triggered, if set (e.g. `"64mb"`).
167    pub auto_aof_rewrite_min_size: Option<String>,
168    /// Whether fsync is suppressed during AOF rewrites, if set.
169    pub no_appendfsync_on_rewrite: Option<bool>,
170
171    // -- replication --
172    /// Master host and port to replicate from, if set.
173    pub replicaof: Option<(String, u16)>,
174    /// Password for authenticating with a master, if set.
175    pub masterauth: Option<String>,
176    /// Username for authenticating with a master, if set.
177    pub masteruser: Option<String>,
178    /// Replication backlog size (e.g. `"1mb"`), if set.
179    pub repl_backlog_size: Option<String>,
180    /// Seconds before the backlog is freed when no replicas are connected, if set.
181    pub repl_backlog_ttl: Option<u32>,
182    /// Whether TCP_NODELAY is disabled on the replication socket, if set.
183    pub repl_disable_tcp_nodelay: Option<bool>,
184    /// Diskless load policy for replicas, if set.
185    pub repl_diskless_load: Option<ReplDisklessLoad>,
186    /// Whether the master sends RDB to replicas via diskless transfer, if set.
187    pub repl_diskless_sync: Option<bool>,
188    /// Delay in seconds before starting a diskless sync, if set.
189    pub repl_diskless_sync_delay: Option<u32>,
190    /// Maximum number of replicas to wait for before starting a diskless sync, if set.
191    pub repl_diskless_sync_max_replicas: Option<u32>,
192    /// Interval in seconds between PING commands sent to the master, if set.
193    pub repl_ping_replica_period: Option<u32>,
194    /// Replication timeout in seconds, if set.
195    pub repl_timeout: Option<u32>,
196    /// IP address a replica announces to the master, if set.
197    pub replica_announce_ip: Option<String>,
198    /// Port a replica announces to the master, if set.
199    pub replica_announce_port: Option<u16>,
200    /// Whether the replica is announced to clients, if set.
201    pub replica_announced: Option<bool>,
202    /// Buffer limit for full synchronization on replicas (e.g. `"256mb"`), if set.
203    pub replica_full_sync_buffer_limit: Option<String>,
204    /// Whether replicas ignore disk-write errors, if set.
205    pub replica_ignore_disk_write_errors: Option<bool>,
206    /// Whether replicas ignore the maxmemory setting, if set.
207    pub replica_ignore_maxmemory: Option<bool>,
208    /// Whether replicas perform a lazy flush during full sync, if set.
209    pub replica_lazy_flush: Option<bool>,
210    /// Replica priority for Sentinel promotion, if set.
211    pub replica_priority: Option<u32>,
212    /// Whether the replica is read-only, if set.
213    pub replica_read_only: Option<bool>,
214    /// Whether the replica serves stale data while syncing, if set.
215    pub replica_serve_stale_data: Option<bool>,
216    /// Minimum number of replicas that must acknowledge writes, if set.
217    pub min_replicas_to_write: Option<u32>,
218    /// Maximum replication lag (in seconds) for a replica to count toward `min-replicas-to-write`, if set.
219    pub min_replicas_max_lag: Option<u32>,
220
221    // -- security --
222    /// `requirepass` password for client connections, if set.
223    pub password: Option<String>,
224    /// Path to an ACL file, if set.
225    pub acl_file: Option<PathBuf>,
226
227    // -- cluster --
228    /// Whether Redis Cluster mode is enabled (default: `false`).
229    pub cluster_enabled: bool,
230    /// Cluster node timeout in milliseconds, if set.
231    pub cluster_node_timeout: Option<u64>,
232    /// Path to the cluster config file, if set. Overrides the auto-generated default.
233    pub cluster_config_file: Option<PathBuf>,
234    /// Whether full hash slot coverage is required for the cluster to accept writes, if set.
235    pub cluster_require_full_coverage: Option<bool>,
236    /// Whether reads are allowed when the cluster is down, if set.
237    pub cluster_allow_reads_when_down: Option<bool>,
238    /// Whether pubsub shard channels are allowed when the cluster is down, if set.
239    pub cluster_allow_pubsubshard_when_down: Option<bool>,
240    /// Whether automatic replica migration is allowed, if set.
241    pub cluster_allow_replica_migration: Option<bool>,
242    /// Minimum number of replicas a master must have before one can migrate, if set.
243    pub cluster_migration_barrier: Option<u32>,
244    /// Whether this replica will never attempt a failover, if set.
245    pub cluster_replica_no_failover: Option<bool>,
246    /// Factor multiplied by node timeout to determine replica validity, if set.
247    pub cluster_replica_validity_factor: Option<u32>,
248    /// IP address this node announces to the cluster bus, if set.
249    pub cluster_announce_ip: Option<String>,
250    /// Client port this node announces to the cluster, if set.
251    pub cluster_announce_port: Option<u16>,
252    /// Cluster bus port this node announces, if set.
253    pub cluster_announce_bus_port: Option<u16>,
254    /// TLS port this node announces to the cluster, if set.
255    pub cluster_announce_tls_port: Option<u16>,
256    /// Hostname this node announces to the cluster, if set.
257    pub cluster_announce_hostname: Option<String>,
258    /// Human-readable node name announced to the cluster, if set.
259    pub cluster_announce_human_nodename: Option<String>,
260    /// Dedicated cluster bus port, if set (0 = auto, default offset +10000).
261    pub cluster_port: Option<u16>,
262    /// Preferred endpoint type for cluster redirections, if set (e.g. `"ip"`, `"hostname"`).
263    pub cluster_preferred_endpoint_type: Option<String>,
264    /// Send buffer limit in bytes for cluster bus links, if set.
265    pub cluster_link_sendbuf_limit: Option<u64>,
266    /// Compatibility sample ratio percentage, if set.
267    pub cluster_compatibility_sample_ratio: Option<u32>,
268    /// Maximum lag in bytes before slot migration handoff, if set.
269    pub cluster_slot_migration_handoff_max_lag_bytes: Option<u64>,
270    /// Write pause timeout in milliseconds during slot migration, if set.
271    pub cluster_slot_migration_write_pause_timeout: Option<u64>,
272    /// Whether per-slot statistics are enabled, if set.
273    pub cluster_slot_stats_enabled: Option<bool>,
274
275    // -- data structures --
276    /// Maximum number of entries in a hash before converting from listpack to hash table, if set.
277    pub hash_max_listpack_entries: Option<u32>,
278    /// Maximum size of a hash entry value before converting from listpack to hash table, if set.
279    pub hash_max_listpack_value: Option<u32>,
280    /// Maximum listpack size for list entries (positive = element count, negative = byte limit), if set.
281    pub list_max_listpack_size: Option<i32>,
282    /// Number of list quicklist nodes at each end that are not compressed, if set.
283    pub list_compress_depth: Option<u32>,
284    /// Maximum number of integer entries in a set before converting from intset to hash table, if set.
285    pub set_max_intset_entries: Option<u32>,
286    /// Maximum number of entries in a set before converting from listpack to hash table, if set.
287    pub set_max_listpack_entries: Option<u32>,
288    /// Maximum size of a set entry value before converting from listpack to hash table, if set.
289    pub set_max_listpack_value: Option<u32>,
290    /// Maximum number of entries in a sorted set before converting from listpack to skiplist, if set.
291    pub zset_max_listpack_entries: Option<u32>,
292    /// Maximum size of a sorted set entry value before converting from listpack to skiplist, if set.
293    pub zset_max_listpack_value: Option<u32>,
294    /// Maximum number of bytes used by the sparse representation of a HyperLogLog, if set.
295    pub hll_sparse_max_bytes: Option<u32>,
296    /// Maximum number of bytes in a single stream listpack node, if set.
297    pub stream_node_max_bytes: Option<u32>,
298    /// Maximum number of entries in a single stream listpack node, if set.
299    pub stream_node_max_entries: Option<u32>,
300    /// Duration in milliseconds for stream ID de-duplication, if set.
301    pub stream_idmp_duration: Option<u64>,
302    /// Maximum number of entries tracked for stream ID de-duplication, if set.
303    pub stream_idmp_maxsize: Option<u64>,
304
305    // -- modules --
306    /// List of Redis module paths to load at startup.
307    pub loadmodule: Vec<PathBuf>,
308
309    // -- advanced --
310    /// Server tick frequency in Hz, if set (Redis default: `10`).
311    pub hz: Option<u32>,
312    /// Number of I/O threads, if set.
313    pub io_threads: Option<u32>,
314    /// Whether I/O threads also handle reads, if set.
315    pub io_threads_do_reads: Option<bool>,
316    /// Keyspace notification event mask (e.g. `"KEA"`), if set.
317    pub notify_keyspace_events: Option<String>,
318
319    // -- slow log --
320    /// Log queries slower than this many microseconds (`0` = log everything, `-1` = disabled).
321    pub slowlog_log_slower_than: Option<i64>,
322    /// Maximum number of entries in the slow log.
323    pub slowlog_max_len: Option<u32>,
324
325    // -- latency tracking --
326    /// Latency monitor threshold in milliseconds (`0` = disabled).
327    pub latency_monitor_threshold: Option<u64>,
328    /// Enable the extended latency tracking system.
329    pub latency_tracking: Option<bool>,
330    /// Percentiles reported by the latency tracking system (e.g. `"50 99 99.9"`).
331    pub latency_tracking_info_percentiles: Option<String>,
332
333    // -- active defragmentation --
334    /// Enable active defragmentation.
335    pub activedefrag: Option<bool>,
336    /// Minimum amount of fragmentation waste to start defragmentation.
337    pub active_defrag_ignore_bytes: Option<String>,
338    /// Minimum percentage of fragmentation to start defragmentation.
339    pub active_defrag_threshold_lower: Option<u32>,
340    /// Maximum percentage of fragmentation at which we use maximum effort.
341    pub active_defrag_threshold_upper: Option<u32>,
342    /// Minimal effort for defragmentation as a percentage of CPU time.
343    pub active_defrag_cycle_min: Option<u32>,
344    /// Maximum effort for defragmentation as a percentage of CPU time.
345    pub active_defrag_cycle_max: Option<u32>,
346    /// Maximum number of set/hash/zset/list fields processed per defrag scan step.
347    pub active_defrag_max_scan_fields: Option<u32>,
348
349    // -- logging and process --
350    /// Enable logging to syslog.
351    pub syslog_enabled: Option<bool>,
352    /// Syslog identity string.
353    pub syslog_ident: Option<String>,
354    /// Syslog facility (e.g. `"local0"`).
355    pub syslog_facility: Option<String>,
356    /// Supervision mode (`"upstart"`, `"systemd"`, `"auto"`, or `"no"`).
357    pub supervised: Option<String>,
358    /// Show the Redis logo on startup.
359    pub always_show_logo: Option<bool>,
360    /// Set the process title.
361    pub set_proc_title: Option<bool>,
362    /// Template for the process title.
363    pub proc_title_template: Option<String>,
364
365    // -- security and ACL --
366    /// Default pub/sub permissions for ACL users (`"allchannels"` or `"resetchannels"`).
367    pub acl_pubsub_default: Option<String>,
368    /// Maximum length of the ACL log.
369    pub acllog_max_len: Option<u32>,
370    /// Enable the DEBUG command (`"yes"`, `"local"`, or `"no"`).
371    pub enable_debug_command: Option<String>,
372    /// Enable the MODULE command (`"yes"`, `"local"`, or `"no"`).
373    pub enable_module_command: Option<String>,
374    /// Allow CONFIG SET to modify protected configs.
375    pub enable_protected_configs: Option<String>,
376    /// Rename a command (command, new-name). Empty new-name disables the command.
377    pub rename_command: Vec<(String, String)>,
378    /// Sanitize dump payload on restore (`"yes"`, `"no"`, or `"clients"`).
379    pub sanitize_dump_payload: Option<String>,
380    /// Hide user data from log messages.
381    pub hide_user_data_from_log: Option<bool>,
382
383    // -- networking (additional) --
384    /// Source address for outgoing connections.
385    pub bind_source_addr: Option<String>,
386    /// Busy reply threshold in milliseconds.
387    pub busy_reply_threshold: Option<u64>,
388    /// Client output buffer limits (e.g. `"normal 0 0 0"`, `"replica 256mb 64mb 60"`).
389    pub client_output_buffer_limit: Vec<String>,
390    /// Maximum size of a single client query buffer.
391    pub client_query_buffer_limit: Option<String>,
392    /// Maximum size of a single protocol bulk request.
393    pub proto_max_bulk_len: Option<String>,
394    /// Maximum number of new connections per event loop cycle.
395    pub max_new_connections_per_cycle: Option<u32>,
396    /// Maximum number of new TLS connections per event loop cycle.
397    pub max_new_tls_connections_per_cycle: Option<u32>,
398    /// Socket mark ID for outgoing connections.
399    pub socket_mark_id: Option<u32>,
400
401    // -- RDB (additional) --
402    /// RDB dump filename.
403    pub dbfilename: Option<String>,
404    /// Enable RDB compression.
405    pub rdbcompression: Option<bool>,
406    /// Enable RDB checksum.
407    pub rdbchecksum: Option<bool>,
408    /// Incremental fsync during RDB save.
409    pub rdb_save_incremental_fsync: Option<bool>,
410    /// Delete RDB sync files used by diskless replication.
411    pub rdb_del_sync_files: Option<bool>,
412    /// Stop accepting writes when bgsave fails.
413    pub stop_writes_on_bgsave_error: Option<bool>,
414
415    // -- shutdown --
416    /// Shutdown behavior on SIGINT (e.g. `"default"`, `"save"`, `"nosave"`, `"now"`, `"force"`).
417    pub shutdown_on_sigint: Option<String>,
418    /// Shutdown behavior on SIGTERM.
419    pub shutdown_on_sigterm: Option<String>,
420    /// Maximum seconds to wait during shutdown for lagging replicas.
421    pub shutdown_timeout: Option<u32>,
422
423    // -- other --
424    /// Enable active rehashing.
425    pub activerehashing: Option<bool>,
426    /// Enable crash log on crash.
427    pub crash_log_enabled: Option<bool>,
428    /// Enable crash memory check on crash.
429    pub crash_memcheck_enabled: Option<bool>,
430    /// Disable transparent huge pages.
431    pub disable_thp: Option<bool>,
432    /// Enable dynamic Hz adjustment.
433    pub dynamic_hz: Option<bool>,
434    /// Ignore specific warnings (e.g. `"ARM64-COW-BUG"`).
435    pub ignore_warnings: Option<String>,
436    /// Include another config file.
437    pub include: Vec<PathBuf>,
438    /// Enable jemalloc background thread.
439    pub jemalloc_bg_thread: Option<bool>,
440    /// Locale collation setting.
441    pub locale_collate: Option<String>,
442    /// Lua script time limit in milliseconds.
443    pub lua_time_limit: Option<u64>,
444    /// OOM score adjustment mode (`"yes"`, `"no"`, or `"absolute"`).
445    pub oom_score_adj: Option<String>,
446    /// OOM score adjustment values (e.g. `"0 200 800"`).
447    pub oom_score_adj_values: Option<String>,
448    /// Propagation error behavior (`"panic"` or `"ignore"`).
449    pub propagation_error_behavior: Option<String>,
450    /// Maximum number of keys in the tracking table.
451    pub tracking_table_max_keys: Option<u64>,
452
453    // -- catch-all for anything not covered above --
454    /// Arbitrary key/value directives forwarded verbatim to the config file.
455    pub extra: HashMap<String, String>,
456
457    // -- binary paths --
458    /// Path to the `redis-server` binary (default: `"redis-server"`).
459    pub redis_server_bin: String,
460    /// Path to the `redis-cli` binary (default: `"redis-cli"`).
461    pub redis_cli_bin: String,
462}
463
464/// AOF fsync policy.
465#[derive(Debug, Clone, Copy)]
466pub enum AppendFsync {
467    /// Fsync after every write operation.
468    Always,
469    /// Fsync once per second (Redis default).
470    Everysec,
471    /// Let the OS decide when to flush.
472    No,
473}
474
475impl std::fmt::Display for AppendFsync {
476    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
477        match self {
478            AppendFsync::Always => f.write_str("always"),
479            AppendFsync::Everysec => f.write_str("everysec"),
480            AppendFsync::No => f.write_str("no"),
481        }
482    }
483}
484
485/// Diskless load policy for replicas.
486///
487/// Controls how a replica loads the RDB payload received from a master during
488/// diskless replication.
489#[derive(Debug, Clone, Copy)]
490pub enum ReplDisklessLoad {
491    /// Never load the RDB directly from the socket (write to disk first).
492    Disabled,
493    /// Load directly from the socket only when the current dataset is empty.
494    OnEmptyDb,
495    /// Load directly from the socket, swapping the dataset atomically.
496    Swapdb,
497}
498
499impl std::fmt::Display for ReplDisklessLoad {
500    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
501        match self {
502            ReplDisklessLoad::Disabled => f.write_str("disabled"),
503            ReplDisklessLoad::OnEmptyDb => f.write_str("on-empty-db"),
504            ReplDisklessLoad::Swapdb => f.write_str("swapdb"),
505        }
506    }
507}
508
509/// RDB save policy.
510///
511/// Controls whether and how the `save` directive is emitted in the Redis
512/// configuration file.
513#[derive(Debug, Clone, Default)]
514pub enum SavePolicy {
515    /// Emit `save ""` to disable RDB snapshots entirely.
516    #[default]
517    Disabled,
518    /// Omit the `save` directive and let Redis use its built-in defaults.
519    Default,
520    /// Emit one `save <seconds> <changes>` line for each pair.
521    Custom(Vec<(u64, u64)>),
522}
523
524/// Redis log level.
525#[derive(Debug, Clone, Copy)]
526pub enum LogLevel {
527    /// Very verbose output, useful for diagnosing Redis internals.
528    Debug,
529    /// Slightly less verbose than `Debug`.
530    Verbose,
531    /// Informational messages only (default).
532    Notice,
533    /// Only critical events are logged.
534    Warning,
535}
536
537impl std::fmt::Display for LogLevel {
538    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
539        match self {
540            LogLevel::Debug => f.write_str("debug"),
541            LogLevel::Verbose => f.write_str("verbose"),
542            LogLevel::Notice => f.write_str("notice"),
543            LogLevel::Warning => f.write_str("warning"),
544        }
545    }
546}
547
548impl Default for RedisServerConfig {
549    fn default() -> Self {
550        Self {
551            port: 6379,
552            bind: "127.0.0.1".into(),
553            protected_mode: false,
554            tcp_backlog: None,
555            unixsocket: None,
556            unixsocketperm: None,
557            timeout: None,
558            tcp_keepalive: None,
559            tls_port: None,
560            tls_cert_file: None,
561            tls_key_file: None,
562            tls_key_file_pass: None,
563            tls_ca_cert_file: None,
564            tls_ca_cert_dir: None,
565            tls_auth_clients: None,
566            tls_client_cert_file: None,
567            tls_client_key_file: None,
568            tls_client_key_file_pass: None,
569            tls_dh_params_file: None,
570            tls_ciphers: None,
571            tls_ciphersuites: None,
572            tls_protocols: None,
573            tls_prefer_server_ciphers: None,
574            tls_session_caching: None,
575            tls_session_cache_size: None,
576            tls_session_cache_timeout: None,
577            tls_replication: None,
578            tls_cluster: None,
579            daemonize: true,
580            dir: std::env::temp_dir().join("redis-server-wrapper"),
581            logfile: None,
582            loglevel: LogLevel::Notice,
583            databases: None,
584            maxmemory: None,
585            maxmemory_policy: None,
586            maxmemory_samples: None,
587            maxmemory_clients: None,
588            maxmemory_eviction_tenacity: None,
589            maxclients: None,
590            lfu_log_factor: None,
591            lfu_decay_time: None,
592            active_expire_effort: None,
593            lazyfree_lazy_eviction: None,
594            lazyfree_lazy_expire: None,
595            lazyfree_lazy_server_del: None,
596            lazyfree_lazy_user_del: None,
597            lazyfree_lazy_user_flush: None,
598            save: SavePolicy::Disabled,
599            appendonly: false,
600            appendfsync: None,
601            appendfilename: None,
602            appenddirname: None,
603            aof_use_rdb_preamble: None,
604            aof_load_truncated: None,
605            aof_load_corrupt_tail_max_size: None,
606            aof_rewrite_incremental_fsync: None,
607            aof_timestamp_enabled: None,
608            auto_aof_rewrite_percentage: None,
609            auto_aof_rewrite_min_size: None,
610            no_appendfsync_on_rewrite: None,
611            replicaof: None,
612            masterauth: None,
613            masteruser: None,
614            repl_backlog_size: None,
615            repl_backlog_ttl: None,
616            repl_disable_tcp_nodelay: None,
617            repl_diskless_load: None,
618            repl_diskless_sync: None,
619            repl_diskless_sync_delay: None,
620            repl_diskless_sync_max_replicas: None,
621            repl_ping_replica_period: None,
622            repl_timeout: None,
623            replica_announce_ip: None,
624            replica_announce_port: None,
625            replica_announced: None,
626            replica_full_sync_buffer_limit: None,
627            replica_ignore_disk_write_errors: None,
628            replica_ignore_maxmemory: None,
629            replica_lazy_flush: None,
630            replica_priority: None,
631            replica_read_only: None,
632            replica_serve_stale_data: None,
633            min_replicas_to_write: None,
634            min_replicas_max_lag: None,
635            password: None,
636            acl_file: None,
637            cluster_enabled: false,
638            cluster_node_timeout: None,
639            cluster_config_file: None,
640            cluster_require_full_coverage: None,
641            cluster_allow_reads_when_down: None,
642            cluster_allow_pubsubshard_when_down: None,
643            cluster_allow_replica_migration: None,
644            cluster_migration_barrier: None,
645            cluster_replica_no_failover: None,
646            cluster_replica_validity_factor: None,
647            cluster_announce_ip: None,
648            cluster_announce_port: None,
649            cluster_announce_bus_port: None,
650            cluster_announce_tls_port: None,
651            cluster_announce_hostname: None,
652            cluster_announce_human_nodename: None,
653            cluster_port: None,
654            cluster_preferred_endpoint_type: None,
655            cluster_link_sendbuf_limit: None,
656            cluster_compatibility_sample_ratio: None,
657            cluster_slot_migration_handoff_max_lag_bytes: None,
658            cluster_slot_migration_write_pause_timeout: None,
659            cluster_slot_stats_enabled: None,
660            hash_max_listpack_entries: None,
661            hash_max_listpack_value: None,
662            list_max_listpack_size: None,
663            list_compress_depth: None,
664            set_max_intset_entries: None,
665            set_max_listpack_entries: None,
666            set_max_listpack_value: None,
667            zset_max_listpack_entries: None,
668            zset_max_listpack_value: None,
669            hll_sparse_max_bytes: None,
670            stream_node_max_bytes: None,
671            stream_node_max_entries: None,
672            stream_idmp_duration: None,
673            stream_idmp_maxsize: None,
674            loadmodule: Vec::new(),
675            hz: None,
676            io_threads: None,
677            io_threads_do_reads: None,
678            notify_keyspace_events: None,
679            slowlog_log_slower_than: None,
680            slowlog_max_len: None,
681            latency_monitor_threshold: None,
682            latency_tracking: None,
683            latency_tracking_info_percentiles: None,
684            activedefrag: None,
685            active_defrag_ignore_bytes: None,
686            active_defrag_threshold_lower: None,
687            active_defrag_threshold_upper: None,
688            active_defrag_cycle_min: None,
689            active_defrag_cycle_max: None,
690            active_defrag_max_scan_fields: None,
691            syslog_enabled: None,
692            syslog_ident: None,
693            syslog_facility: None,
694            supervised: None,
695            always_show_logo: None,
696            set_proc_title: None,
697            proc_title_template: None,
698            acl_pubsub_default: None,
699            acllog_max_len: None,
700            enable_debug_command: None,
701            enable_module_command: None,
702            enable_protected_configs: None,
703            rename_command: Vec::new(),
704            sanitize_dump_payload: None,
705            hide_user_data_from_log: None,
706            bind_source_addr: None,
707            busy_reply_threshold: None,
708            client_output_buffer_limit: Vec::new(),
709            client_query_buffer_limit: None,
710            proto_max_bulk_len: None,
711            max_new_connections_per_cycle: None,
712            max_new_tls_connections_per_cycle: None,
713            socket_mark_id: None,
714            dbfilename: None,
715            rdbcompression: None,
716            rdbchecksum: None,
717            rdb_save_incremental_fsync: None,
718            rdb_del_sync_files: None,
719            stop_writes_on_bgsave_error: None,
720            shutdown_on_sigint: None,
721            shutdown_on_sigterm: None,
722            shutdown_timeout: None,
723            activerehashing: None,
724            crash_log_enabled: None,
725            crash_memcheck_enabled: None,
726            disable_thp: None,
727            dynamic_hz: None,
728            ignore_warnings: None,
729            include: Vec::new(),
730            jemalloc_bg_thread: None,
731            locale_collate: None,
732            lua_time_limit: None,
733            oom_score_adj: None,
734            oom_score_adj_values: None,
735            propagation_error_behavior: None,
736            tracking_table_max_keys: None,
737            extra: HashMap::new(),
738            redis_server_bin: "redis-server".into(),
739            redis_cli_bin: "redis-cli".into(),
740        }
741    }
742}
743
744/// Builder for a Redis server.
745pub struct RedisServer {
746    config: RedisServerConfig,
747}
748
749impl RedisServer {
750    /// Create a new builder with default settings.
751    pub fn new() -> Self {
752        Self {
753            config: RedisServerConfig::default(),
754        }
755    }
756
757    // -- network --
758
759    /// Set the listening port (default: 6379).
760    pub fn port(mut self, port: u16) -> Self {
761        self.config.port = port;
762        self
763    }
764
765    /// Set the bind address (default: `127.0.0.1`).
766    pub fn bind(mut self, bind: impl Into<String>) -> Self {
767        self.config.bind = bind.into();
768        self
769    }
770
771    /// Enable or disable protected mode (default: off).
772    pub fn protected_mode(mut self, protected: bool) -> Self {
773        self.config.protected_mode = protected;
774        self
775    }
776
777    /// Set the TCP backlog queue length.
778    pub fn tcp_backlog(mut self, backlog: u32) -> Self {
779        self.config.tcp_backlog = Some(backlog);
780        self
781    }
782
783    /// Set a Unix socket path for connections.
784    pub fn unixsocket(mut self, path: impl Into<PathBuf>) -> Self {
785        self.config.unixsocket = Some(path.into());
786        self
787    }
788
789    /// Set Unix socket permissions (e.g. `700`).
790    pub fn unixsocketperm(mut self, perm: u32) -> Self {
791        self.config.unixsocketperm = Some(perm);
792        self
793    }
794
795    /// Close idle client connections after this many seconds (0 = disabled).
796    pub fn timeout(mut self, seconds: u32) -> Self {
797        self.config.timeout = Some(seconds);
798        self
799    }
800
801    /// Set TCP keepalive interval in seconds.
802    pub fn tcp_keepalive(mut self, seconds: u32) -> Self {
803        self.config.tcp_keepalive = Some(seconds);
804        self
805    }
806
807    // -- tls --
808
809    /// Set TLS listening port.
810    pub fn tls_port(mut self, port: u16) -> Self {
811        self.config.tls_port = Some(port);
812        self
813    }
814
815    /// Set the TLS certificate file path.
816    pub fn tls_cert_file(mut self, path: impl Into<PathBuf>) -> Self {
817        self.config.tls_cert_file = Some(path.into());
818        self
819    }
820
821    /// Set the TLS private key file path.
822    pub fn tls_key_file(mut self, path: impl Into<PathBuf>) -> Self {
823        self.config.tls_key_file = Some(path.into());
824        self
825    }
826
827    /// Set the TLS CA certificate file path.
828    pub fn tls_ca_cert_file(mut self, path: impl Into<PathBuf>) -> Self {
829        self.config.tls_ca_cert_file = Some(path.into());
830        self
831    }
832
833    /// Require TLS client authentication.
834    pub fn tls_auth_clients(mut self, require: bool) -> Self {
835        self.config.tls_auth_clients = Some(require);
836        self
837    }
838
839    /// Set the passphrase for the TLS private key file.
840    pub fn tls_key_file_pass(mut self, pass: impl Into<String>) -> Self {
841        self.config.tls_key_file_pass = Some(pass.into());
842        self
843    }
844
845    /// Set the TLS CA certificate directory path.
846    pub fn tls_ca_cert_dir(mut self, path: impl Into<PathBuf>) -> Self {
847        self.config.tls_ca_cert_dir = Some(path.into());
848        self
849    }
850
851    /// Set the TLS client certificate file path (for outgoing connections).
852    pub fn tls_client_cert_file(mut self, path: impl Into<PathBuf>) -> Self {
853        self.config.tls_client_cert_file = Some(path.into());
854        self
855    }
856
857    /// Set the TLS client private key file path (for outgoing connections).
858    pub fn tls_client_key_file(mut self, path: impl Into<PathBuf>) -> Self {
859        self.config.tls_client_key_file = Some(path.into());
860        self
861    }
862
863    /// Set the passphrase for the TLS client private key file.
864    pub fn tls_client_key_file_pass(mut self, pass: impl Into<String>) -> Self {
865        self.config.tls_client_key_file_pass = Some(pass.into());
866        self
867    }
868
869    /// Set the DH parameters file path for DHE ciphers.
870    pub fn tls_dh_params_file(mut self, path: impl Into<PathBuf>) -> Self {
871        self.config.tls_dh_params_file = Some(path.into());
872        self
873    }
874
875    /// Set the allowed TLS 1.2 ciphers (OpenSSL cipher list format).
876    pub fn tls_ciphers(mut self, ciphers: impl Into<String>) -> Self {
877        self.config.tls_ciphers = Some(ciphers.into());
878        self
879    }
880
881    /// Set the allowed TLS 1.3 ciphersuites (colon-separated).
882    pub fn tls_ciphersuites(mut self, suites: impl Into<String>) -> Self {
883        self.config.tls_ciphersuites = Some(suites.into());
884        self
885    }
886
887    /// Set the allowed TLS protocol versions (e.g. `"TLSv1.2 TLSv1.3"`).
888    pub fn tls_protocols(mut self, protocols: impl Into<String>) -> Self {
889        self.config.tls_protocols = Some(protocols.into());
890        self
891    }
892
893    /// Prefer the server's cipher order over the client's.
894    pub fn tls_prefer_server_ciphers(mut self, prefer: bool) -> Self {
895        self.config.tls_prefer_server_ciphers = Some(prefer);
896        self
897    }
898
899    /// Enable or disable TLS session caching.
900    pub fn tls_session_caching(mut self, enable: bool) -> Self {
901        self.config.tls_session_caching = Some(enable);
902        self
903    }
904
905    /// Set the number of entries in the TLS session cache.
906    pub fn tls_session_cache_size(mut self, size: u32) -> Self {
907        self.config.tls_session_cache_size = Some(size);
908        self
909    }
910
911    /// Set the timeout in seconds for cached TLS sessions.
912    pub fn tls_session_cache_timeout(mut self, seconds: u32) -> Self {
913        self.config.tls_session_cache_timeout = Some(seconds);
914        self
915    }
916
917    /// Enable TLS for replication traffic.
918    pub fn tls_replication(mut self, enable: bool) -> Self {
919        self.config.tls_replication = Some(enable);
920        self
921    }
922
923    /// Enable TLS for cluster bus communication.
924    pub fn tls_cluster(mut self, enable: bool) -> Self {
925        self.config.tls_cluster = Some(enable);
926        self
927    }
928
929    // -- general --
930
931    /// Set the working directory for data files.
932    pub fn dir(mut self, dir: impl Into<PathBuf>) -> Self {
933        self.config.dir = dir.into();
934        self
935    }
936
937    /// Set the log level (default: [`LogLevel::Notice`]).
938    pub fn loglevel(mut self, level: LogLevel) -> Self {
939        self.config.loglevel = level;
940        self
941    }
942
943    /// Set the log file path. Defaults to `redis.log` inside the node directory.
944    pub fn logfile(mut self, path: impl Into<String>) -> Self {
945        self.config.logfile = Some(path.into());
946        self
947    }
948
949    /// Set the number of databases (default: 16).
950    pub fn databases(mut self, n: u32) -> Self {
951        self.config.databases = Some(n);
952        self
953    }
954
955    // -- memory --
956
957    /// Set the maximum memory limit (e.g. `"256mb"`, `"1gb"`).
958    pub fn maxmemory(mut self, limit: impl Into<String>) -> Self {
959        self.config.maxmemory = Some(limit.into());
960        self
961    }
962
963    /// Set the eviction policy when maxmemory is reached.
964    pub fn maxmemory_policy(mut self, policy: impl Into<String>) -> Self {
965        self.config.maxmemory_policy = Some(policy.into());
966        self
967    }
968
969    /// Set the number of keys sampled per eviction round (Redis default: 5).
970    pub fn maxmemory_samples(mut self, n: u32) -> Self {
971        self.config.maxmemory_samples = Some(n);
972        self
973    }
974
975    /// Set per-client memory limit (e.g. `"0"` to disable).
976    pub fn maxmemory_clients(mut self, limit: impl Into<String>) -> Self {
977        self.config.maxmemory_clients = Some(limit.into());
978        self
979    }
980
981    /// Set eviction processing effort (1-100, Redis default: 10).
982    pub fn maxmemory_eviction_tenacity(mut self, tenacity: u32) -> Self {
983        self.config.maxmemory_eviction_tenacity = Some(tenacity);
984        self
985    }
986
987    /// Set the maximum number of simultaneous client connections.
988    pub fn maxclients(mut self, n: u32) -> Self {
989        self.config.maxclients = Some(n);
990        self
991    }
992
993    /// Set the logarithmic factor for the LFU frequency counter (Redis default: 10).
994    pub fn lfu_log_factor(mut self, factor: u32) -> Self {
995        self.config.lfu_log_factor = Some(factor);
996        self
997    }
998
999    /// Set the LFU counter decay time in minutes (Redis default: 1).
1000    pub fn lfu_decay_time(mut self, minutes: u32) -> Self {
1001        self.config.lfu_decay_time = Some(minutes);
1002        self
1003    }
1004
1005    /// Set the effort spent on active key expiration (1-100, Redis default: 10).
1006    pub fn active_expire_effort(mut self, effort: u32) -> Self {
1007        self.config.active_expire_effort = Some(effort);
1008        self
1009    }
1010
1011    // -- lazyfree --
1012
1013    /// Enable or disable background deletion during eviction.
1014    pub fn lazyfree_lazy_eviction(mut self, enable: bool) -> Self {
1015        self.config.lazyfree_lazy_eviction = Some(enable);
1016        self
1017    }
1018
1019    /// Enable or disable background deletion of expired keys.
1020    pub fn lazyfree_lazy_expire(mut self, enable: bool) -> Self {
1021        self.config.lazyfree_lazy_expire = Some(enable);
1022        self
1023    }
1024
1025    /// Enable or disable background deletion for implicit `DEL` (e.g. `RENAME`).
1026    pub fn lazyfree_lazy_server_del(mut self, enable: bool) -> Self {
1027        self.config.lazyfree_lazy_server_del = Some(enable);
1028        self
1029    }
1030
1031    /// Make explicit `DEL` behave like `UNLINK` (background deletion).
1032    pub fn lazyfree_lazy_user_del(mut self, enable: bool) -> Self {
1033        self.config.lazyfree_lazy_user_del = Some(enable);
1034        self
1035    }
1036
1037    /// Make `FLUSHDB`/`FLUSHALL` default to `ASYNC`.
1038    pub fn lazyfree_lazy_user_flush(mut self, enable: bool) -> Self {
1039        self.config.lazyfree_lazy_user_flush = Some(enable);
1040        self
1041    }
1042
1043    // -- persistence --
1044
1045    /// Enable or disable RDB snapshots (default: off).
1046    ///
1047    /// `true` omits the `save` directive (Redis built-in defaults apply).
1048    /// `false` emits `save ""` to disable RDB entirely.
1049    pub fn save(mut self, save: bool) -> Self {
1050        self.config.save = if save {
1051            SavePolicy::Default
1052        } else {
1053            SavePolicy::Disabled
1054        };
1055        self
1056    }
1057
1058    /// Set a custom RDB save schedule.
1059    ///
1060    /// Each `(seconds, changes)` pair emits a `save <seconds> <changes>` line.
1061    pub fn save_schedule(mut self, schedule: Vec<(u64, u64)>) -> Self {
1062        self.config.save = SavePolicy::Custom(schedule);
1063        self
1064    }
1065
1066    /// Enable or disable AOF persistence.
1067    pub fn appendonly(mut self, appendonly: bool) -> Self {
1068        self.config.appendonly = appendonly;
1069        self
1070    }
1071
1072    /// Set the AOF fsync policy.
1073    pub fn appendfsync(mut self, policy: AppendFsync) -> Self {
1074        self.config.appendfsync = Some(policy);
1075        self
1076    }
1077
1078    /// Set the AOF filename.
1079    pub fn appendfilename(mut self, name: impl Into<String>) -> Self {
1080        self.config.appendfilename = Some(name.into());
1081        self
1082    }
1083
1084    /// Set the AOF directory name.
1085    pub fn appenddirname(mut self, name: impl Into<PathBuf>) -> Self {
1086        self.config.appenddirname = Some(name.into());
1087        self
1088    }
1089
1090    /// Enable or disable the RDB preamble in AOF files.
1091    pub fn aof_use_rdb_preamble(mut self, enable: bool) -> Self {
1092        self.config.aof_use_rdb_preamble = Some(enable);
1093        self
1094    }
1095
1096    /// Control whether truncated AOF files are loaded.
1097    pub fn aof_load_truncated(mut self, enable: bool) -> Self {
1098        self.config.aof_load_truncated = Some(enable);
1099        self
1100    }
1101
1102    /// Set the maximum allowed size of a corrupt AOF tail (e.g. `"32mb"`).
1103    pub fn aof_load_corrupt_tail_max_size(mut self, size: impl Into<String>) -> Self {
1104        self.config.aof_load_corrupt_tail_max_size = Some(size.into());
1105        self
1106    }
1107
1108    /// Enable or disable incremental fsync during AOF rewrites.
1109    pub fn aof_rewrite_incremental_fsync(mut self, enable: bool) -> Self {
1110        self.config.aof_rewrite_incremental_fsync = Some(enable);
1111        self
1112    }
1113
1114    /// Enable or disable timestamps in the AOF file.
1115    pub fn aof_timestamp_enabled(mut self, enable: bool) -> Self {
1116        self.config.aof_timestamp_enabled = Some(enable);
1117        self
1118    }
1119
1120    /// Set the percentage growth that triggers an automatic AOF rewrite.
1121    pub fn auto_aof_rewrite_percentage(mut self, pct: u32) -> Self {
1122        self.config.auto_aof_rewrite_percentage = Some(pct);
1123        self
1124    }
1125
1126    /// Set the minimum AOF size before an automatic rewrite is triggered (e.g. `"64mb"`).
1127    pub fn auto_aof_rewrite_min_size(mut self, size: impl Into<String>) -> Self {
1128        self.config.auto_aof_rewrite_min_size = Some(size.into());
1129        self
1130    }
1131
1132    /// Control whether fsync is suppressed during AOF rewrites.
1133    pub fn no_appendfsync_on_rewrite(mut self, enable: bool) -> Self {
1134        self.config.no_appendfsync_on_rewrite = Some(enable);
1135        self
1136    }
1137
1138    // -- replication --
1139
1140    /// Configure this server as a replica of the given master.
1141    pub fn replicaof(mut self, host: impl Into<String>, port: u16) -> Self {
1142        self.config.replicaof = Some((host.into(), port));
1143        self
1144    }
1145
1146    /// Set the password for authenticating with a master.
1147    pub fn masterauth(mut self, password: impl Into<String>) -> Self {
1148        self.config.masterauth = Some(password.into());
1149        self
1150    }
1151
1152    /// Set the username for authenticating with a master (ACL-based auth).
1153    pub fn masteruser(mut self, user: impl Into<String>) -> Self {
1154        self.config.masteruser = Some(user.into());
1155        self
1156    }
1157
1158    /// Set the replication backlog size (e.g. `"1mb"`).
1159    pub fn repl_backlog_size(mut self, size: impl Into<String>) -> Self {
1160        self.config.repl_backlog_size = Some(size.into());
1161        self
1162    }
1163
1164    /// Set seconds before the backlog is freed when no replicas are connected.
1165    pub fn repl_backlog_ttl(mut self, seconds: u32) -> Self {
1166        self.config.repl_backlog_ttl = Some(seconds);
1167        self
1168    }
1169
1170    /// Disable TCP_NODELAY on the replication socket.
1171    pub fn repl_disable_tcp_nodelay(mut self, disable: bool) -> Self {
1172        self.config.repl_disable_tcp_nodelay = Some(disable);
1173        self
1174    }
1175
1176    /// Set the diskless load policy for replicas.
1177    pub fn repl_diskless_load(mut self, policy: ReplDisklessLoad) -> Self {
1178        self.config.repl_diskless_load = Some(policy);
1179        self
1180    }
1181
1182    /// Enable or disable diskless sync from master to replicas.
1183    pub fn repl_diskless_sync(mut self, enable: bool) -> Self {
1184        self.config.repl_diskless_sync = Some(enable);
1185        self
1186    }
1187
1188    /// Set the delay in seconds before starting a diskless sync.
1189    pub fn repl_diskless_sync_delay(mut self, seconds: u32) -> Self {
1190        self.config.repl_diskless_sync_delay = Some(seconds);
1191        self
1192    }
1193
1194    /// Set the maximum number of replicas to wait for before starting a diskless sync.
1195    pub fn repl_diskless_sync_max_replicas(mut self, n: u32) -> Self {
1196        self.config.repl_diskless_sync_max_replicas = Some(n);
1197        self
1198    }
1199
1200    /// Set the interval in seconds between PING commands sent to the master.
1201    pub fn repl_ping_replica_period(mut self, seconds: u32) -> Self {
1202        self.config.repl_ping_replica_period = Some(seconds);
1203        self
1204    }
1205
1206    /// Set the replication timeout in seconds.
1207    pub fn repl_timeout(mut self, seconds: u32) -> Self {
1208        self.config.repl_timeout = Some(seconds);
1209        self
1210    }
1211
1212    /// Set the IP address a replica announces to the master.
1213    pub fn replica_announce_ip(mut self, ip: impl Into<String>) -> Self {
1214        self.config.replica_announce_ip = Some(ip.into());
1215        self
1216    }
1217
1218    /// Set the port a replica announces to the master.
1219    pub fn replica_announce_port(mut self, port: u16) -> Self {
1220        self.config.replica_announce_port = Some(port);
1221        self
1222    }
1223
1224    /// Control whether the replica is announced to clients.
1225    pub fn replica_announced(mut self, announced: bool) -> Self {
1226        self.config.replica_announced = Some(announced);
1227        self
1228    }
1229
1230    /// Set the buffer limit for full synchronization on replicas (e.g. `"256mb"`).
1231    pub fn replica_full_sync_buffer_limit(mut self, size: impl Into<String>) -> Self {
1232        self.config.replica_full_sync_buffer_limit = Some(size.into());
1233        self
1234    }
1235
1236    /// Control whether replicas ignore disk-write errors.
1237    pub fn replica_ignore_disk_write_errors(mut self, ignore: bool) -> Self {
1238        self.config.replica_ignore_disk_write_errors = Some(ignore);
1239        self
1240    }
1241
1242    /// Control whether replicas ignore the maxmemory setting.
1243    pub fn replica_ignore_maxmemory(mut self, ignore: bool) -> Self {
1244        self.config.replica_ignore_maxmemory = Some(ignore);
1245        self
1246    }
1247
1248    /// Enable or disable lazy flush on replicas during full sync.
1249    pub fn replica_lazy_flush(mut self, enable: bool) -> Self {
1250        self.config.replica_lazy_flush = Some(enable);
1251        self
1252    }
1253
1254    /// Set the replica priority for Sentinel promotion.
1255    pub fn replica_priority(mut self, priority: u32) -> Self {
1256        self.config.replica_priority = Some(priority);
1257        self
1258    }
1259
1260    /// Control whether the replica is read-only.
1261    pub fn replica_read_only(mut self, read_only: bool) -> Self {
1262        self.config.replica_read_only = Some(read_only);
1263        self
1264    }
1265
1266    /// Control whether the replica serves stale data while syncing.
1267    pub fn replica_serve_stale_data(mut self, serve: bool) -> Self {
1268        self.config.replica_serve_stale_data = Some(serve);
1269        self
1270    }
1271
1272    /// Set the minimum number of replicas that must acknowledge writes.
1273    pub fn min_replicas_to_write(mut self, n: u32) -> Self {
1274        self.config.min_replicas_to_write = Some(n);
1275        self
1276    }
1277
1278    /// Set the maximum replication lag (in seconds) for a replica to count toward `min-replicas-to-write`.
1279    pub fn min_replicas_max_lag(mut self, seconds: u32) -> Self {
1280        self.config.min_replicas_max_lag = Some(seconds);
1281        self
1282    }
1283
1284    // -- security --
1285
1286    /// Set a `requirepass` password for client connections.
1287    pub fn password(mut self, password: impl Into<String>) -> Self {
1288        self.config.password = Some(password.into());
1289        self
1290    }
1291
1292    /// Set the path to an ACL file.
1293    pub fn acl_file(mut self, path: impl Into<PathBuf>) -> Self {
1294        self.config.acl_file = Some(path.into());
1295        self
1296    }
1297
1298    // -- cluster --
1299
1300    /// Enable Redis Cluster mode.
1301    pub fn cluster_enabled(mut self, enabled: bool) -> Self {
1302        self.config.cluster_enabled = enabled;
1303        self
1304    }
1305
1306    /// Set the cluster node timeout in milliseconds.
1307    pub fn cluster_node_timeout(mut self, ms: u64) -> Self {
1308        self.config.cluster_node_timeout = Some(ms);
1309        self
1310    }
1311
1312    /// Set a custom cluster config file path (overrides auto-generated default).
1313    pub fn cluster_config_file(mut self, path: impl Into<PathBuf>) -> Self {
1314        self.config.cluster_config_file = Some(path.into());
1315        self
1316    }
1317
1318    /// Require full hash slot coverage for the cluster to accept writes.
1319    pub fn cluster_require_full_coverage(mut self, require: bool) -> Self {
1320        self.config.cluster_require_full_coverage = Some(require);
1321        self
1322    }
1323
1324    /// Allow reads when the cluster is down.
1325    pub fn cluster_allow_reads_when_down(mut self, allow: bool) -> Self {
1326        self.config.cluster_allow_reads_when_down = Some(allow);
1327        self
1328    }
1329
1330    /// Allow pubsub shard channels when the cluster is down.
1331    pub fn cluster_allow_pubsubshard_when_down(mut self, allow: bool) -> Self {
1332        self.config.cluster_allow_pubsubshard_when_down = Some(allow);
1333        self
1334    }
1335
1336    /// Allow automatic replica migration between masters.
1337    pub fn cluster_allow_replica_migration(mut self, allow: bool) -> Self {
1338        self.config.cluster_allow_replica_migration = Some(allow);
1339        self
1340    }
1341
1342    /// Set the minimum number of replicas a master must retain before one can migrate.
1343    pub fn cluster_migration_barrier(mut self, barrier: u32) -> Self {
1344        self.config.cluster_migration_barrier = Some(barrier);
1345        self
1346    }
1347
1348    /// Prevent this replica from ever attempting a failover.
1349    pub fn cluster_replica_no_failover(mut self, no_failover: bool) -> Self {
1350        self.config.cluster_replica_no_failover = Some(no_failover);
1351        self
1352    }
1353
1354    /// Set the replica validity factor (multiplied by node timeout).
1355    pub fn cluster_replica_validity_factor(mut self, factor: u32) -> Self {
1356        self.config.cluster_replica_validity_factor = Some(factor);
1357        self
1358    }
1359
1360    /// Set the IP address this node announces to the cluster bus.
1361    pub fn cluster_announce_ip(mut self, ip: impl Into<String>) -> Self {
1362        self.config.cluster_announce_ip = Some(ip.into());
1363        self
1364    }
1365
1366    /// Set the client port this node announces to the cluster.
1367    pub fn cluster_announce_port(mut self, port: u16) -> Self {
1368        self.config.cluster_announce_port = Some(port);
1369        self
1370    }
1371
1372    /// Set the cluster bus port this node announces.
1373    pub fn cluster_announce_bus_port(mut self, port: u16) -> Self {
1374        self.config.cluster_announce_bus_port = Some(port);
1375        self
1376    }
1377
1378    /// Set the TLS port this node announces to the cluster.
1379    pub fn cluster_announce_tls_port(mut self, port: u16) -> Self {
1380        self.config.cluster_announce_tls_port = Some(port);
1381        self
1382    }
1383
1384    /// Set the hostname this node announces to the cluster.
1385    pub fn cluster_announce_hostname(mut self, hostname: impl Into<String>) -> Self {
1386        self.config.cluster_announce_hostname = Some(hostname.into());
1387        self
1388    }
1389
1390    /// Set the human-readable node name announced to the cluster.
1391    pub fn cluster_announce_human_nodename(mut self, name: impl Into<String>) -> Self {
1392        self.config.cluster_announce_human_nodename = Some(name.into());
1393        self
1394    }
1395
1396    /// Set the dedicated cluster bus port (0 = auto with +10000 offset).
1397    pub fn cluster_port(mut self, port: u16) -> Self {
1398        self.config.cluster_port = Some(port);
1399        self
1400    }
1401
1402    /// Set the preferred endpoint type for cluster redirections (e.g. `"ip"`, `"hostname"`).
1403    pub fn cluster_preferred_endpoint_type(mut self, endpoint_type: impl Into<String>) -> Self {
1404        self.config.cluster_preferred_endpoint_type = Some(endpoint_type.into());
1405        self
1406    }
1407
1408    /// Set the send buffer limit in bytes for cluster bus links.
1409    pub fn cluster_link_sendbuf_limit(mut self, limit: u64) -> Self {
1410        self.config.cluster_link_sendbuf_limit = Some(limit);
1411        self
1412    }
1413
1414    /// Set the compatibility sample ratio percentage.
1415    pub fn cluster_compatibility_sample_ratio(mut self, ratio: u32) -> Self {
1416        self.config.cluster_compatibility_sample_ratio = Some(ratio);
1417        self
1418    }
1419
1420    /// Set the maximum lag in bytes before slot migration handoff.
1421    pub fn cluster_slot_migration_handoff_max_lag_bytes(mut self, bytes: u64) -> Self {
1422        self.config.cluster_slot_migration_handoff_max_lag_bytes = Some(bytes);
1423        self
1424    }
1425
1426    /// Set the write pause timeout in milliseconds during slot migration.
1427    pub fn cluster_slot_migration_write_pause_timeout(mut self, ms: u64) -> Self {
1428        self.config.cluster_slot_migration_write_pause_timeout = Some(ms);
1429        self
1430    }
1431
1432    /// Enable per-slot statistics collection.
1433    pub fn cluster_slot_stats_enabled(mut self, enable: bool) -> Self {
1434        self.config.cluster_slot_stats_enabled = Some(enable);
1435        self
1436    }
1437
1438    // -- data structures --
1439
1440    /// Set the maximum number of entries in a hash before converting from listpack to hash table.
1441    pub fn hash_max_listpack_entries(mut self, n: u32) -> Self {
1442        self.config.hash_max_listpack_entries = Some(n);
1443        self
1444    }
1445
1446    /// Set the maximum size of a hash entry value before converting from listpack to hash table.
1447    pub fn hash_max_listpack_value(mut self, n: u32) -> Self {
1448        self.config.hash_max_listpack_value = Some(n);
1449        self
1450    }
1451
1452    /// Set the maximum listpack size for list entries.
1453    ///
1454    /// Positive values limit the number of elements per listpack node.
1455    /// Negative values set a byte-size limit: -1 = 4KB, -2 = 8KB, -3 = 16KB, -4 = 32KB, -5 = 64KB.
1456    pub fn list_max_listpack_size(mut self, n: i32) -> Self {
1457        self.config.list_max_listpack_size = Some(n);
1458        self
1459    }
1460
1461    /// Set the number of quicklist nodes at each end of the list that are not compressed.
1462    ///
1463    /// `0` disables compression. `1` means the head and tail are uncompressed, etc.
1464    pub fn list_compress_depth(mut self, n: u32) -> Self {
1465        self.config.list_compress_depth = Some(n);
1466        self
1467    }
1468
1469    /// Set the maximum number of integer entries in a set before converting from intset to hash table.
1470    pub fn set_max_intset_entries(mut self, n: u32) -> Self {
1471        self.config.set_max_intset_entries = Some(n);
1472        self
1473    }
1474
1475    /// Set the maximum number of entries in a set before converting from listpack to hash table.
1476    pub fn set_max_listpack_entries(mut self, n: u32) -> Self {
1477        self.config.set_max_listpack_entries = Some(n);
1478        self
1479    }
1480
1481    /// Set the maximum size of a set entry value before converting from listpack to hash table.
1482    pub fn set_max_listpack_value(mut self, n: u32) -> Self {
1483        self.config.set_max_listpack_value = Some(n);
1484        self
1485    }
1486
1487    /// Set the maximum number of entries in a sorted set before converting from listpack to skiplist.
1488    pub fn zset_max_listpack_entries(mut self, n: u32) -> Self {
1489        self.config.zset_max_listpack_entries = Some(n);
1490        self
1491    }
1492
1493    /// Set the maximum size of a sorted set entry value before converting from listpack to skiplist.
1494    pub fn zset_max_listpack_value(mut self, n: u32) -> Self {
1495        self.config.zset_max_listpack_value = Some(n);
1496        self
1497    }
1498
1499    /// Set the maximum number of bytes for the sparse representation of a HyperLogLog.
1500    pub fn hll_sparse_max_bytes(mut self, n: u32) -> Self {
1501        self.config.hll_sparse_max_bytes = Some(n);
1502        self
1503    }
1504
1505    /// Set the maximum number of bytes in a single stream listpack node.
1506    pub fn stream_node_max_bytes(mut self, n: u32) -> Self {
1507        self.config.stream_node_max_bytes = Some(n);
1508        self
1509    }
1510
1511    /// Set the maximum number of entries in a single stream listpack node.
1512    pub fn stream_node_max_entries(mut self, n: u32) -> Self {
1513        self.config.stream_node_max_entries = Some(n);
1514        self
1515    }
1516
1517    /// Set the duration in milliseconds for stream ID de-duplication.
1518    pub fn stream_idmp_duration(mut self, ms: u64) -> Self {
1519        self.config.stream_idmp_duration = Some(ms);
1520        self
1521    }
1522
1523    /// Set the maximum number of entries tracked for stream ID de-duplication.
1524    pub fn stream_idmp_maxsize(mut self, n: u64) -> Self {
1525        self.config.stream_idmp_maxsize = Some(n);
1526        self
1527    }
1528
1529    // -- modules --
1530
1531    /// Load a Redis module at startup.
1532    pub fn loadmodule(mut self, path: impl Into<PathBuf>) -> Self {
1533        self.config.loadmodule.push(path.into());
1534        self
1535    }
1536
1537    // -- advanced --
1538
1539    /// Set the server tick frequency in Hz (default: 10).
1540    pub fn hz(mut self, hz: u32) -> Self {
1541        self.config.hz = Some(hz);
1542        self
1543    }
1544
1545    /// Set the number of I/O threads.
1546    pub fn io_threads(mut self, n: u32) -> Self {
1547        self.config.io_threads = Some(n);
1548        self
1549    }
1550
1551    /// Enable I/O threads for reads as well as writes.
1552    pub fn io_threads_do_reads(mut self, enable: bool) -> Self {
1553        self.config.io_threads_do_reads = Some(enable);
1554        self
1555    }
1556
1557    /// Set keyspace notification events (e.g. `"KEA"`).
1558    pub fn notify_keyspace_events(mut self, events: impl Into<String>) -> Self {
1559        self.config.notify_keyspace_events = Some(events.into());
1560        self
1561    }
1562
1563    // -- slow log --
1564
1565    /// Set the slow log threshold in microseconds (`0` = log everything, `-1` = disabled).
1566    pub fn slowlog_log_slower_than(mut self, us: i64) -> Self {
1567        self.config.slowlog_log_slower_than = Some(us);
1568        self
1569    }
1570
1571    /// Set the maximum number of slow log entries.
1572    pub fn slowlog_max_len(mut self, n: u32) -> Self {
1573        self.config.slowlog_max_len = Some(n);
1574        self
1575    }
1576
1577    // -- latency tracking --
1578
1579    /// Set the latency monitor threshold in milliseconds (`0` = disabled).
1580    pub fn latency_monitor_threshold(mut self, ms: u64) -> Self {
1581        self.config.latency_monitor_threshold = Some(ms);
1582        self
1583    }
1584
1585    /// Enable or disable the extended latency tracking system.
1586    pub fn latency_tracking(mut self, enable: bool) -> Self {
1587        self.config.latency_tracking = Some(enable);
1588        self
1589    }
1590
1591    /// Set percentiles reported by the latency tracking system (e.g. `"50 99 99.9"`).
1592    pub fn latency_tracking_info_percentiles(mut self, percentiles: impl Into<String>) -> Self {
1593        self.config.latency_tracking_info_percentiles = Some(percentiles.into());
1594        self
1595    }
1596
1597    // -- active defragmentation --
1598
1599    /// Enable or disable active defragmentation.
1600    pub fn activedefrag(mut self, enable: bool) -> Self {
1601        self.config.activedefrag = Some(enable);
1602        self
1603    }
1604
1605    /// Set the minimum fragmentation waste to start defragmentation (e.g. `"100mb"`).
1606    pub fn active_defrag_ignore_bytes(mut self, bytes: impl Into<String>) -> Self {
1607        self.config.active_defrag_ignore_bytes = Some(bytes.into());
1608        self
1609    }
1610
1611    /// Set the minimum fragmentation percentage to start defragmentation.
1612    pub fn active_defrag_threshold_lower(mut self, pct: u32) -> Self {
1613        self.config.active_defrag_threshold_lower = Some(pct);
1614        self
1615    }
1616
1617    /// Set the fragmentation percentage at which maximum effort is used.
1618    pub fn active_defrag_threshold_upper(mut self, pct: u32) -> Self {
1619        self.config.active_defrag_threshold_upper = Some(pct);
1620        self
1621    }
1622
1623    /// Set the minimal CPU effort for defragmentation (percentage).
1624    pub fn active_defrag_cycle_min(mut self, pct: u32) -> Self {
1625        self.config.active_defrag_cycle_min = Some(pct);
1626        self
1627    }
1628
1629    /// Set the maximum CPU effort for defragmentation (percentage).
1630    pub fn active_defrag_cycle_max(mut self, pct: u32) -> Self {
1631        self.config.active_defrag_cycle_max = Some(pct);
1632        self
1633    }
1634
1635    /// Set the maximum fields processed per defrag scan step.
1636    pub fn active_defrag_max_scan_fields(mut self, n: u32) -> Self {
1637        self.config.active_defrag_max_scan_fields = Some(n);
1638        self
1639    }
1640
1641    // -- logging and process --
1642
1643    /// Enable logging to syslog.
1644    pub fn syslog_enabled(mut self, enable: bool) -> Self {
1645        self.config.syslog_enabled = Some(enable);
1646        self
1647    }
1648
1649    /// Set the syslog identity string.
1650    pub fn syslog_ident(mut self, ident: impl Into<String>) -> Self {
1651        self.config.syslog_ident = Some(ident.into());
1652        self
1653    }
1654
1655    /// Set the syslog facility (e.g. `"local0"`).
1656    pub fn syslog_facility(mut self, facility: impl Into<String>) -> Self {
1657        self.config.syslog_facility = Some(facility.into());
1658        self
1659    }
1660
1661    /// Set the supervision mode (`"upstart"`, `"systemd"`, `"auto"`, or `"no"`).
1662    pub fn supervised(mut self, mode: impl Into<String>) -> Self {
1663        self.config.supervised = Some(mode.into());
1664        self
1665    }
1666
1667    /// Show the Redis logo on startup.
1668    pub fn always_show_logo(mut self, enable: bool) -> Self {
1669        self.config.always_show_logo = Some(enable);
1670        self
1671    }
1672
1673    /// Enable setting the process title.
1674    pub fn set_proc_title(mut self, enable: bool) -> Self {
1675        self.config.set_proc_title = Some(enable);
1676        self
1677    }
1678
1679    /// Set the process title template.
1680    pub fn proc_title_template(mut self, template: impl Into<String>) -> Self {
1681        self.config.proc_title_template = Some(template.into());
1682        self
1683    }
1684
1685    // -- security and ACL --
1686
1687    /// Set the default pub/sub ACL permissions (`"allchannels"` or `"resetchannels"`).
1688    pub fn acl_pubsub_default(mut self, default: impl Into<String>) -> Self {
1689        self.config.acl_pubsub_default = Some(default.into());
1690        self
1691    }
1692
1693    /// Set the maximum length of the ACL log.
1694    pub fn acllog_max_len(mut self, n: u32) -> Self {
1695        self.config.acllog_max_len = Some(n);
1696        self
1697    }
1698
1699    /// Enable the DEBUG command (`"yes"`, `"local"`, or `"no"`).
1700    pub fn enable_debug_command(mut self, mode: impl Into<String>) -> Self {
1701        self.config.enable_debug_command = Some(mode.into());
1702        self
1703    }
1704
1705    /// Enable the MODULE command (`"yes"`, `"local"`, or `"no"`).
1706    pub fn enable_module_command(mut self, mode: impl Into<String>) -> Self {
1707        self.config.enable_module_command = Some(mode.into());
1708        self
1709    }
1710
1711    /// Allow CONFIG SET to modify protected configs (`"yes"`, `"local"`, or `"no"`).
1712    pub fn enable_protected_configs(mut self, mode: impl Into<String>) -> Self {
1713        self.config.enable_protected_configs = Some(mode.into());
1714        self
1715    }
1716
1717    /// Rename a command. Pass an empty new name to disable the command entirely.
1718    pub fn rename_command(
1719        mut self,
1720        command: impl Into<String>,
1721        new_name: impl Into<String>,
1722    ) -> Self {
1723        self.config
1724            .rename_command
1725            .push((command.into(), new_name.into()));
1726        self
1727    }
1728
1729    /// Set dump payload sanitization mode (`"yes"`, `"no"`, or `"clients"`).
1730    pub fn sanitize_dump_payload(mut self, mode: impl Into<String>) -> Self {
1731        self.config.sanitize_dump_payload = Some(mode.into());
1732        self
1733    }
1734
1735    /// Hide user data from log messages.
1736    pub fn hide_user_data_from_log(mut self, enable: bool) -> Self {
1737        self.config.hide_user_data_from_log = Some(enable);
1738        self
1739    }
1740
1741    // -- networking (additional) --
1742
1743    /// Set the source address for outgoing connections.
1744    pub fn bind_source_addr(mut self, addr: impl Into<String>) -> Self {
1745        self.config.bind_source_addr = Some(addr.into());
1746        self
1747    }
1748
1749    /// Set the busy reply threshold in milliseconds.
1750    pub fn busy_reply_threshold(mut self, ms: u64) -> Self {
1751        self.config.busy_reply_threshold = Some(ms);
1752        self
1753    }
1754
1755    /// Add a client output buffer limit (e.g. `"normal 0 0 0"` or `"replica 256mb 64mb 60"`).
1756    pub fn client_output_buffer_limit(mut self, limit: impl Into<String>) -> Self {
1757        self.config.client_output_buffer_limit.push(limit.into());
1758        self
1759    }
1760
1761    /// Set the maximum size of a single client query buffer.
1762    pub fn client_query_buffer_limit(mut self, limit: impl Into<String>) -> Self {
1763        self.config.client_query_buffer_limit = Some(limit.into());
1764        self
1765    }
1766
1767    /// Set the maximum size of a single protocol bulk request.
1768    pub fn proto_max_bulk_len(mut self, len: impl Into<String>) -> Self {
1769        self.config.proto_max_bulk_len = Some(len.into());
1770        self
1771    }
1772
1773    /// Set the maximum number of new connections per event loop cycle.
1774    pub fn max_new_connections_per_cycle(mut self, n: u32) -> Self {
1775        self.config.max_new_connections_per_cycle = Some(n);
1776        self
1777    }
1778
1779    /// Set the maximum number of new TLS connections per event loop cycle.
1780    pub fn max_new_tls_connections_per_cycle(mut self, n: u32) -> Self {
1781        self.config.max_new_tls_connections_per_cycle = Some(n);
1782        self
1783    }
1784
1785    /// Set the socket mark ID for outgoing connections.
1786    pub fn socket_mark_id(mut self, id: u32) -> Self {
1787        self.config.socket_mark_id = Some(id);
1788        self
1789    }
1790
1791    // -- RDB (additional) --
1792
1793    /// Set the RDB dump filename.
1794    pub fn dbfilename(mut self, name: impl Into<String>) -> Self {
1795        self.config.dbfilename = Some(name.into());
1796        self
1797    }
1798
1799    /// Enable or disable RDB compression.
1800    pub fn rdbcompression(mut self, enable: bool) -> Self {
1801        self.config.rdbcompression = Some(enable);
1802        self
1803    }
1804
1805    /// Enable or disable RDB checksum.
1806    pub fn rdbchecksum(mut self, enable: bool) -> Self {
1807        self.config.rdbchecksum = Some(enable);
1808        self
1809    }
1810
1811    /// Enable incremental fsync during RDB save.
1812    pub fn rdb_save_incremental_fsync(mut self, enable: bool) -> Self {
1813        self.config.rdb_save_incremental_fsync = Some(enable);
1814        self
1815    }
1816
1817    /// Delete RDB sync files used by diskless replication.
1818    pub fn rdb_del_sync_files(mut self, enable: bool) -> Self {
1819        self.config.rdb_del_sync_files = Some(enable);
1820        self
1821    }
1822
1823    /// Stop accepting writes when bgsave fails.
1824    pub fn stop_writes_on_bgsave_error(mut self, enable: bool) -> Self {
1825        self.config.stop_writes_on_bgsave_error = Some(enable);
1826        self
1827    }
1828
1829    // -- shutdown --
1830
1831    /// Set shutdown behavior on SIGINT (e.g. `"default"`, `"save"`, `"nosave"`).
1832    pub fn shutdown_on_sigint(mut self, behavior: impl Into<String>) -> Self {
1833        self.config.shutdown_on_sigint = Some(behavior.into());
1834        self
1835    }
1836
1837    /// Set shutdown behavior on SIGTERM.
1838    pub fn shutdown_on_sigterm(mut self, behavior: impl Into<String>) -> Self {
1839        self.config.shutdown_on_sigterm = Some(behavior.into());
1840        self
1841    }
1842
1843    /// Set the maximum seconds to wait during shutdown for lagging replicas.
1844    pub fn shutdown_timeout(mut self, seconds: u32) -> Self {
1845        self.config.shutdown_timeout = Some(seconds);
1846        self
1847    }
1848
1849    // -- other --
1850
1851    /// Enable or disable active rehashing.
1852    pub fn activerehashing(mut self, enable: bool) -> Self {
1853        self.config.activerehashing = Some(enable);
1854        self
1855    }
1856
1857    /// Enable crash log on crash.
1858    pub fn crash_log_enabled(mut self, enable: bool) -> Self {
1859        self.config.crash_log_enabled = Some(enable);
1860        self
1861    }
1862
1863    /// Enable crash memory check on crash.
1864    pub fn crash_memcheck_enabled(mut self, enable: bool) -> Self {
1865        self.config.crash_memcheck_enabled = Some(enable);
1866        self
1867    }
1868
1869    /// Disable transparent huge pages.
1870    pub fn disable_thp(mut self, enable: bool) -> Self {
1871        self.config.disable_thp = Some(enable);
1872        self
1873    }
1874
1875    /// Enable dynamic Hz adjustment.
1876    pub fn dynamic_hz(mut self, enable: bool) -> Self {
1877        self.config.dynamic_hz = Some(enable);
1878        self
1879    }
1880
1881    /// Ignore specific warnings (e.g. `"ARM64-COW-BUG"`).
1882    pub fn ignore_warnings(mut self, warning: impl Into<String>) -> Self {
1883        self.config.ignore_warnings = Some(warning.into());
1884        self
1885    }
1886
1887    /// Include another config file.
1888    pub fn include(mut self, path: impl Into<PathBuf>) -> Self {
1889        self.config.include.push(path.into());
1890        self
1891    }
1892
1893    /// Enable or disable jemalloc background thread.
1894    pub fn jemalloc_bg_thread(mut self, enable: bool) -> Self {
1895        self.config.jemalloc_bg_thread = Some(enable);
1896        self
1897    }
1898
1899    /// Set the locale collation setting.
1900    pub fn locale_collate(mut self, locale: impl Into<String>) -> Self {
1901        self.config.locale_collate = Some(locale.into());
1902        self
1903    }
1904
1905    /// Set the Lua script time limit in milliseconds.
1906    pub fn lua_time_limit(mut self, ms: u64) -> Self {
1907        self.config.lua_time_limit = Some(ms);
1908        self
1909    }
1910
1911    /// Set the OOM score adjustment mode (`"yes"`, `"no"`, or `"absolute"`).
1912    pub fn oom_score_adj(mut self, mode: impl Into<String>) -> Self {
1913        self.config.oom_score_adj = Some(mode.into());
1914        self
1915    }
1916
1917    /// Set the OOM score adjustment values (e.g. `"0 200 800"`).
1918    pub fn oom_score_adj_values(mut self, values: impl Into<String>) -> Self {
1919        self.config.oom_score_adj_values = Some(values.into());
1920        self
1921    }
1922
1923    /// Set the propagation error behavior (`"panic"` or `"ignore"`).
1924    pub fn propagation_error_behavior(mut self, behavior: impl Into<String>) -> Self {
1925        self.config.propagation_error_behavior = Some(behavior.into());
1926        self
1927    }
1928
1929    /// Set the maximum number of keys in the tracking table.
1930    pub fn tracking_table_max_keys(mut self, n: u64) -> Self {
1931        self.config.tracking_table_max_keys = Some(n);
1932        self
1933    }
1934
1935    // -- binary paths --
1936
1937    /// Set a custom `redis-server` binary path.
1938    pub fn redis_server_bin(mut self, bin: impl Into<String>) -> Self {
1939        self.config.redis_server_bin = bin.into();
1940        self
1941    }
1942
1943    /// Set a custom `redis-cli` binary path.
1944    pub fn redis_cli_bin(mut self, bin: impl Into<String>) -> Self {
1945        self.config.redis_cli_bin = bin.into();
1946        self
1947    }
1948
1949    /// Set an arbitrary config directive not covered by dedicated methods.
1950    pub fn extra(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1951        self.config.extra.insert(key.into(), value.into());
1952        self
1953    }
1954
1955    /// Start the server. Returns a handle that stops the server on Drop.
1956    ///
1957    /// Verifies that `redis-server` and `redis-cli` binaries are available
1958    /// before attempting to launch anything.
1959    pub async fn start(self) -> Result<RedisServerHandle> {
1960        if which::which(&self.config.redis_server_bin).is_err() {
1961            return Err(Error::BinaryNotFound {
1962                binary: self.config.redis_server_bin.clone(),
1963            });
1964        }
1965        if which::which(&self.config.redis_cli_bin).is_err() {
1966            return Err(Error::BinaryNotFound {
1967                binary: self.config.redis_cli_bin.clone(),
1968            });
1969        }
1970
1971        let node_dir = self.config.dir.join(format!("node-{}", self.config.port));
1972        fs::create_dir_all(&node_dir)?;
1973
1974        let conf_path = node_dir.join("redis.conf");
1975        let conf_content = self.generate_config(&node_dir);
1976        fs::write(&conf_path, conf_content)?;
1977
1978        let status = Command::new(&self.config.redis_server_bin)
1979            .arg(&conf_path)
1980            .stdout(std::process::Stdio::null())
1981            .stderr(std::process::Stdio::null())
1982            .status()
1983            .await?;
1984
1985        if !status.success() {
1986            return Err(Error::ServerStart {
1987                port: self.config.port,
1988            });
1989        }
1990
1991        let mut cli = RedisCli::new()
1992            .bin(&self.config.redis_cli_bin)
1993            .host(&self.config.bind)
1994            .port(self.config.port);
1995        if let Some(ref pw) = self.config.password {
1996            cli = cli.password(pw);
1997        }
1998
1999        cli.wait_for_ready(Duration::from_secs(10)).await?;
2000
2001        let pid_path = node_dir.join("redis.pid");
2002        let pid: u32 = fs::read_to_string(&pid_path)
2003            .map_err(Error::Io)?
2004            .trim()
2005            .parse()
2006            .map_err(|_| Error::ServerStart {
2007                port: self.config.port,
2008            })?;
2009
2010        Ok(RedisServerHandle {
2011            config: self.config,
2012            cli,
2013            pid,
2014            detached: false,
2015        })
2016    }
2017
2018    fn generate_config(&self, node_dir: &std::path::Path) -> String {
2019        let yn = |b: bool| if b { "yes" } else { "no" };
2020
2021        let mut conf = format!(
2022            "port {port}\n\
2023             bind {bind}\n\
2024             daemonize {daemonize}\n\
2025             pidfile {dir}/redis.pid\n\
2026             dir {dir}\n\
2027             loglevel {level}\n\
2028             protected-mode {protected}\n",
2029            port = self.config.port,
2030            bind = self.config.bind,
2031            daemonize = yn(self.config.daemonize),
2032            dir = node_dir.display(),
2033            level = self.config.loglevel,
2034            protected = yn(self.config.protected_mode),
2035        );
2036
2037        let logfile = self
2038            .config
2039            .logfile
2040            .as_deref()
2041            .map(str::to_owned)
2042            .unwrap_or_else(|| format!("{}/redis.log", node_dir.display()));
2043        conf.push_str(&format!("logfile {logfile}\n"));
2044
2045        // -- network --
2046        if let Some(backlog) = self.config.tcp_backlog {
2047            conf.push_str(&format!("tcp-backlog {backlog}\n"));
2048        }
2049        if let Some(ref path) = self.config.unixsocket {
2050            conf.push_str(&format!("unixsocket {}\n", path.display()));
2051        }
2052        if let Some(perm) = self.config.unixsocketperm {
2053            conf.push_str(&format!("unixsocketperm {perm}\n"));
2054        }
2055        if let Some(t) = self.config.timeout {
2056            conf.push_str(&format!("timeout {t}\n"));
2057        }
2058        if let Some(ka) = self.config.tcp_keepalive {
2059            conf.push_str(&format!("tcp-keepalive {ka}\n"));
2060        }
2061
2062        // -- tls --
2063        if let Some(port) = self.config.tls_port {
2064            conf.push_str(&format!("tls-port {port}\n"));
2065        }
2066        if let Some(ref path) = self.config.tls_cert_file {
2067            conf.push_str(&format!("tls-cert-file {}\n", path.display()));
2068        }
2069        if let Some(ref path) = self.config.tls_key_file {
2070            conf.push_str(&format!("tls-key-file {}\n", path.display()));
2071        }
2072        if let Some(ref pass) = self.config.tls_key_file_pass {
2073            conf.push_str(&format!("tls-key-file-pass {pass}\n"));
2074        }
2075        if let Some(ref path) = self.config.tls_ca_cert_file {
2076            conf.push_str(&format!("tls-ca-cert-file {}\n", path.display()));
2077        }
2078        if let Some(ref path) = self.config.tls_ca_cert_dir {
2079            conf.push_str(&format!("tls-ca-cert-dir {}\n", path.display()));
2080        }
2081        if let Some(auth) = self.config.tls_auth_clients {
2082            conf.push_str(&format!("tls-auth-clients {}\n", yn(auth)));
2083        }
2084        if let Some(ref path) = self.config.tls_client_cert_file {
2085            conf.push_str(&format!("tls-client-cert-file {}\n", path.display()));
2086        }
2087        if let Some(ref path) = self.config.tls_client_key_file {
2088            conf.push_str(&format!("tls-client-key-file {}\n", path.display()));
2089        }
2090        if let Some(ref pass) = self.config.tls_client_key_file_pass {
2091            conf.push_str(&format!("tls-client-key-file-pass {pass}\n"));
2092        }
2093        if let Some(ref path) = self.config.tls_dh_params_file {
2094            conf.push_str(&format!("tls-dh-params-file {}\n", path.display()));
2095        }
2096        if let Some(ref ciphers) = self.config.tls_ciphers {
2097            conf.push_str(&format!("tls-ciphers {ciphers}\n"));
2098        }
2099        if let Some(ref suites) = self.config.tls_ciphersuites {
2100            conf.push_str(&format!("tls-ciphersuites {suites}\n"));
2101        }
2102        if let Some(ref protocols) = self.config.tls_protocols {
2103            conf.push_str(&format!("tls-protocols {protocols}\n"));
2104        }
2105        if let Some(v) = self.config.tls_prefer_server_ciphers {
2106            conf.push_str(&format!("tls-prefer-server-ciphers {}\n", yn(v)));
2107        }
2108        if let Some(v) = self.config.tls_session_caching {
2109            conf.push_str(&format!("tls-session-caching {}\n", yn(v)));
2110        }
2111        if let Some(size) = self.config.tls_session_cache_size {
2112            conf.push_str(&format!("tls-session-cache-size {size}\n"));
2113        }
2114        if let Some(timeout) = self.config.tls_session_cache_timeout {
2115            conf.push_str(&format!("tls-session-cache-timeout {timeout}\n"));
2116        }
2117        if let Some(v) = self.config.tls_replication {
2118            conf.push_str(&format!("tls-replication {}\n", yn(v)));
2119        }
2120        if let Some(v) = self.config.tls_cluster {
2121            conf.push_str(&format!("tls-cluster {}\n", yn(v)));
2122        }
2123
2124        // -- general --
2125        if let Some(n) = self.config.databases {
2126            conf.push_str(&format!("databases {n}\n"));
2127        }
2128
2129        // -- memory --
2130        if let Some(ref limit) = self.config.maxmemory {
2131            conf.push_str(&format!("maxmemory {limit}\n"));
2132        }
2133        if let Some(ref policy) = self.config.maxmemory_policy {
2134            conf.push_str(&format!("maxmemory-policy {policy}\n"));
2135        }
2136        if let Some(n) = self.config.maxmemory_samples {
2137            conf.push_str(&format!("maxmemory-samples {n}\n"));
2138        }
2139        if let Some(ref limit) = self.config.maxmemory_clients {
2140            conf.push_str(&format!("maxmemory-clients {limit}\n"));
2141        }
2142        if let Some(n) = self.config.maxmemory_eviction_tenacity {
2143            conf.push_str(&format!("maxmemory-eviction-tenacity {n}\n"));
2144        }
2145        if let Some(n) = self.config.maxclients {
2146            conf.push_str(&format!("maxclients {n}\n"));
2147        }
2148        if let Some(n) = self.config.lfu_log_factor {
2149            conf.push_str(&format!("lfu-log-factor {n}\n"));
2150        }
2151        if let Some(n) = self.config.lfu_decay_time {
2152            conf.push_str(&format!("lfu-decay-time {n}\n"));
2153        }
2154        if let Some(n) = self.config.active_expire_effort {
2155            conf.push_str(&format!("active-expire-effort {n}\n"));
2156        }
2157
2158        // -- lazyfree --
2159        if let Some(v) = self.config.lazyfree_lazy_eviction {
2160            conf.push_str(&format!("lazyfree-lazy-eviction {}\n", yn(v)));
2161        }
2162        if let Some(v) = self.config.lazyfree_lazy_expire {
2163            conf.push_str(&format!("lazyfree-lazy-expire {}\n", yn(v)));
2164        }
2165        if let Some(v) = self.config.lazyfree_lazy_server_del {
2166            conf.push_str(&format!("lazyfree-lazy-server-del {}\n", yn(v)));
2167        }
2168        if let Some(v) = self.config.lazyfree_lazy_user_del {
2169            conf.push_str(&format!("lazyfree-lazy-user-del {}\n", yn(v)));
2170        }
2171        if let Some(v) = self.config.lazyfree_lazy_user_flush {
2172            conf.push_str(&format!("lazyfree-lazy-user-flush {}\n", yn(v)));
2173        }
2174
2175        // -- persistence --
2176        match &self.config.save {
2177            SavePolicy::Disabled => conf.push_str("save \"\"\n"),
2178            SavePolicy::Default => {}
2179            SavePolicy::Custom(pairs) => {
2180                for (secs, changes) in pairs {
2181                    conf.push_str(&format!("save {secs} {changes}\n"));
2182                }
2183            }
2184        }
2185        if self.config.appendonly {
2186            conf.push_str("appendonly yes\n");
2187        }
2188        if let Some(ref policy) = self.config.appendfsync {
2189            conf.push_str(&format!("appendfsync {policy}\n"));
2190        }
2191        if let Some(ref name) = self.config.appendfilename {
2192            conf.push_str(&format!("appendfilename \"{name}\"\n"));
2193        }
2194        if let Some(ref name) = self.config.appenddirname {
2195            conf.push_str(&format!("appenddirname \"{}\"\n", name.display()));
2196        }
2197        if let Some(v) = self.config.aof_use_rdb_preamble {
2198            conf.push_str(&format!("aof-use-rdb-preamble {}\n", yn(v)));
2199        }
2200        if let Some(v) = self.config.aof_load_truncated {
2201            conf.push_str(&format!("aof-load-truncated {}\n", yn(v)));
2202        }
2203        if let Some(ref size) = self.config.aof_load_corrupt_tail_max_size {
2204            conf.push_str(&format!("aof-load-corrupt-tail-max-size {size}\n"));
2205        }
2206        if let Some(v) = self.config.aof_rewrite_incremental_fsync {
2207            conf.push_str(&format!("aof-rewrite-incremental-fsync {}\n", yn(v)));
2208        }
2209        if let Some(v) = self.config.aof_timestamp_enabled {
2210            conf.push_str(&format!("aof-timestamp-enabled {}\n", yn(v)));
2211        }
2212        if let Some(pct) = self.config.auto_aof_rewrite_percentage {
2213            conf.push_str(&format!("auto-aof-rewrite-percentage {pct}\n"));
2214        }
2215        if let Some(ref size) = self.config.auto_aof_rewrite_min_size {
2216            conf.push_str(&format!("auto-aof-rewrite-min-size {size}\n"));
2217        }
2218        if let Some(v) = self.config.no_appendfsync_on_rewrite {
2219            conf.push_str(&format!("no-appendfsync-on-rewrite {}\n", yn(v)));
2220        }
2221
2222        // -- replication --
2223        if let Some((ref host, port)) = self.config.replicaof {
2224            conf.push_str(&format!("replicaof {host} {port}\n"));
2225        }
2226        if let Some(ref pw) = self.config.masterauth {
2227            conf.push_str(&format!("masterauth {pw}\n"));
2228        }
2229        if let Some(ref user) = self.config.masteruser {
2230            conf.push_str(&format!("masteruser {user}\n"));
2231        }
2232        if let Some(ref size) = self.config.repl_backlog_size {
2233            conf.push_str(&format!("repl-backlog-size {size}\n"));
2234        }
2235        if let Some(ttl) = self.config.repl_backlog_ttl {
2236            conf.push_str(&format!("repl-backlog-ttl {ttl}\n"));
2237        }
2238        if let Some(v) = self.config.repl_disable_tcp_nodelay {
2239            conf.push_str(&format!("repl-disable-tcp-nodelay {}\n", yn(v)));
2240        }
2241        if let Some(ref policy) = self.config.repl_diskless_load {
2242            conf.push_str(&format!("repl-diskless-load {policy}\n"));
2243        }
2244        if let Some(v) = self.config.repl_diskless_sync {
2245            conf.push_str(&format!("repl-diskless-sync {}\n", yn(v)));
2246        }
2247        if let Some(delay) = self.config.repl_diskless_sync_delay {
2248            conf.push_str(&format!("repl-diskless-sync-delay {delay}\n"));
2249        }
2250        if let Some(n) = self.config.repl_diskless_sync_max_replicas {
2251            conf.push_str(&format!("repl-diskless-sync-max-replicas {n}\n"));
2252        }
2253        if let Some(period) = self.config.repl_ping_replica_period {
2254            conf.push_str(&format!("repl-ping-replica-period {period}\n"));
2255        }
2256        if let Some(t) = self.config.repl_timeout {
2257            conf.push_str(&format!("repl-timeout {t}\n"));
2258        }
2259        if let Some(ref ip) = self.config.replica_announce_ip {
2260            conf.push_str(&format!("replica-announce-ip {ip}\n"));
2261        }
2262        if let Some(port) = self.config.replica_announce_port {
2263            conf.push_str(&format!("replica-announce-port {port}\n"));
2264        }
2265        if let Some(v) = self.config.replica_announced {
2266            conf.push_str(&format!("replica-announced {}\n", yn(v)));
2267        }
2268        if let Some(ref size) = self.config.replica_full_sync_buffer_limit {
2269            conf.push_str(&format!("replica-full-sync-buffer-limit {size}\n"));
2270        }
2271        if let Some(v) = self.config.replica_ignore_disk_write_errors {
2272            conf.push_str(&format!("replica-ignore-disk-write-errors {}\n", yn(v)));
2273        }
2274        if let Some(v) = self.config.replica_ignore_maxmemory {
2275            conf.push_str(&format!("replica-ignore-maxmemory {}\n", yn(v)));
2276        }
2277        if let Some(v) = self.config.replica_lazy_flush {
2278            conf.push_str(&format!("replica-lazy-flush {}\n", yn(v)));
2279        }
2280        if let Some(priority) = self.config.replica_priority {
2281            conf.push_str(&format!("replica-priority {priority}\n"));
2282        }
2283        if let Some(v) = self.config.replica_read_only {
2284            conf.push_str(&format!("replica-read-only {}\n", yn(v)));
2285        }
2286        if let Some(v) = self.config.replica_serve_stale_data {
2287            conf.push_str(&format!("replica-serve-stale-data {}\n", yn(v)));
2288        }
2289        if let Some(n) = self.config.min_replicas_to_write {
2290            conf.push_str(&format!("min-replicas-to-write {n}\n"));
2291        }
2292        if let Some(lag) = self.config.min_replicas_max_lag {
2293            conf.push_str(&format!("min-replicas-max-lag {lag}\n"));
2294        }
2295
2296        // -- security --
2297        if let Some(ref pw) = self.config.password {
2298            conf.push_str(&format!("requirepass {pw}\n"));
2299        }
2300        if let Some(ref path) = self.config.acl_file {
2301            conf.push_str(&format!("aclfile {}\n", path.display()));
2302        }
2303
2304        // -- cluster --
2305        if self.config.cluster_enabled {
2306            conf.push_str("cluster-enabled yes\n");
2307            if let Some(ref path) = self.config.cluster_config_file {
2308                conf.push_str(&format!("cluster-config-file {}\n", path.display()));
2309            } else {
2310                conf.push_str(&format!(
2311                    "cluster-config-file {}/nodes.conf\n",
2312                    node_dir.display()
2313                ));
2314            }
2315            if let Some(timeout) = self.config.cluster_node_timeout {
2316                conf.push_str(&format!("cluster-node-timeout {timeout}\n"));
2317            }
2318            if let Some(v) = self.config.cluster_require_full_coverage {
2319                conf.push_str(&format!("cluster-require-full-coverage {}\n", yn(v)));
2320            }
2321            if let Some(v) = self.config.cluster_allow_reads_when_down {
2322                conf.push_str(&format!("cluster-allow-reads-when-down {}\n", yn(v)));
2323            }
2324            if let Some(v) = self.config.cluster_allow_pubsubshard_when_down {
2325                conf.push_str(&format!("cluster-allow-pubsubshard-when-down {}\n", yn(v)));
2326            }
2327            if let Some(v) = self.config.cluster_allow_replica_migration {
2328                conf.push_str(&format!("cluster-allow-replica-migration {}\n", yn(v)));
2329            }
2330            if let Some(barrier) = self.config.cluster_migration_barrier {
2331                conf.push_str(&format!("cluster-migration-barrier {barrier}\n"));
2332            }
2333            if let Some(v) = self.config.cluster_replica_no_failover {
2334                conf.push_str(&format!("cluster-replica-no-failover {}\n", yn(v)));
2335            }
2336            if let Some(factor) = self.config.cluster_replica_validity_factor {
2337                conf.push_str(&format!("cluster-replica-validity-factor {factor}\n"));
2338            }
2339            if let Some(ref ip) = self.config.cluster_announce_ip {
2340                conf.push_str(&format!("cluster-announce-ip {ip}\n"));
2341            }
2342            if let Some(port) = self.config.cluster_announce_port {
2343                conf.push_str(&format!("cluster-announce-port {port}\n"));
2344            }
2345            if let Some(port) = self.config.cluster_announce_bus_port {
2346                conf.push_str(&format!("cluster-announce-bus-port {port}\n"));
2347            }
2348            if let Some(port) = self.config.cluster_announce_tls_port {
2349                conf.push_str(&format!("cluster-announce-tls-port {port}\n"));
2350            }
2351            if let Some(ref hostname) = self.config.cluster_announce_hostname {
2352                conf.push_str(&format!("cluster-announce-hostname {hostname}\n"));
2353            }
2354            if let Some(ref name) = self.config.cluster_announce_human_nodename {
2355                conf.push_str(&format!("cluster-announce-human-nodename {name}\n"));
2356            }
2357            if let Some(port) = self.config.cluster_port {
2358                conf.push_str(&format!("cluster-port {port}\n"));
2359            }
2360            if let Some(ref endpoint_type) = self.config.cluster_preferred_endpoint_type {
2361                conf.push_str(&format!(
2362                    "cluster-preferred-endpoint-type {endpoint_type}\n"
2363                ));
2364            }
2365            if let Some(limit) = self.config.cluster_link_sendbuf_limit {
2366                conf.push_str(&format!("cluster-link-sendbuf-limit {limit}\n"));
2367            }
2368            if let Some(ratio) = self.config.cluster_compatibility_sample_ratio {
2369                conf.push_str(&format!("cluster-compatibility-sample-ratio {ratio}\n"));
2370            }
2371            if let Some(bytes) = self.config.cluster_slot_migration_handoff_max_lag_bytes {
2372                conf.push_str(&format!(
2373                    "cluster-slot-migration-handoff-max-lag-bytes {bytes}\n"
2374                ));
2375            }
2376            if let Some(ms) = self.config.cluster_slot_migration_write_pause_timeout {
2377                conf.push_str(&format!(
2378                    "cluster-slot-migration-write-pause-timeout {ms}\n"
2379                ));
2380            }
2381            if let Some(v) = self.config.cluster_slot_stats_enabled {
2382                conf.push_str(&format!("cluster-slot-stats-enabled {}\n", yn(v)));
2383            }
2384        }
2385
2386        // -- data structures --
2387        if let Some(n) = self.config.hash_max_listpack_entries {
2388            conf.push_str(&format!("hash-max-listpack-entries {n}\n"));
2389        }
2390        if let Some(n) = self.config.hash_max_listpack_value {
2391            conf.push_str(&format!("hash-max-listpack-value {n}\n"));
2392        }
2393        if let Some(n) = self.config.list_max_listpack_size {
2394            conf.push_str(&format!("list-max-listpack-size {n}\n"));
2395        }
2396        if let Some(n) = self.config.list_compress_depth {
2397            conf.push_str(&format!("list-compress-depth {n}\n"));
2398        }
2399        if let Some(n) = self.config.set_max_intset_entries {
2400            conf.push_str(&format!("set-max-intset-entries {n}\n"));
2401        }
2402        if let Some(n) = self.config.set_max_listpack_entries {
2403            conf.push_str(&format!("set-max-listpack-entries {n}\n"));
2404        }
2405        if let Some(n) = self.config.set_max_listpack_value {
2406            conf.push_str(&format!("set-max-listpack-value {n}\n"));
2407        }
2408        if let Some(n) = self.config.zset_max_listpack_entries {
2409            conf.push_str(&format!("zset-max-listpack-entries {n}\n"));
2410        }
2411        if let Some(n) = self.config.zset_max_listpack_value {
2412            conf.push_str(&format!("zset-max-listpack-value {n}\n"));
2413        }
2414        if let Some(n) = self.config.hll_sparse_max_bytes {
2415            conf.push_str(&format!("hll-sparse-max-bytes {n}\n"));
2416        }
2417        if let Some(n) = self.config.stream_node_max_bytes {
2418            conf.push_str(&format!("stream-node-max-bytes {n}\n"));
2419        }
2420        if let Some(n) = self.config.stream_node_max_entries {
2421            conf.push_str(&format!("stream-node-max-entries {n}\n"));
2422        }
2423        if let Some(ms) = self.config.stream_idmp_duration {
2424            conf.push_str(&format!("stream-idmp-duration {ms}\n"));
2425        }
2426        if let Some(n) = self.config.stream_idmp_maxsize {
2427            conf.push_str(&format!("stream-idmp-maxsize {n}\n"));
2428        }
2429
2430        // -- modules --
2431        for path in &self.config.loadmodule {
2432            conf.push_str(&format!("loadmodule {}\n", path.display()));
2433        }
2434
2435        // -- advanced --
2436        if let Some(hz) = self.config.hz {
2437            conf.push_str(&format!("hz {hz}\n"));
2438        }
2439        if let Some(n) = self.config.io_threads {
2440            conf.push_str(&format!("io-threads {n}\n"));
2441        }
2442        if let Some(enable) = self.config.io_threads_do_reads {
2443            conf.push_str(&format!("io-threads-do-reads {}\n", yn(enable)));
2444        }
2445        if let Some(ref events) = self.config.notify_keyspace_events {
2446            conf.push_str(&format!("notify-keyspace-events {events}\n"));
2447        }
2448
2449        // -- slow log --
2450        if let Some(us) = self.config.slowlog_log_slower_than {
2451            conf.push_str(&format!("slowlog-log-slower-than {us}\n"));
2452        }
2453        if let Some(n) = self.config.slowlog_max_len {
2454            conf.push_str(&format!("slowlog-max-len {n}\n"));
2455        }
2456
2457        // -- latency tracking --
2458        if let Some(ms) = self.config.latency_monitor_threshold {
2459            conf.push_str(&format!("latency-monitor-threshold {ms}\n"));
2460        }
2461        if let Some(enable) = self.config.latency_tracking {
2462            conf.push_str(&format!("latency-tracking {}\n", yn(enable)));
2463        }
2464        if let Some(ref pcts) = self.config.latency_tracking_info_percentiles {
2465            conf.push_str(&format!("latency-tracking-info-percentiles \"{pcts}\"\n"));
2466        }
2467
2468        // -- active defragmentation --
2469        if let Some(enable) = self.config.activedefrag {
2470            conf.push_str(&format!("activedefrag {}\n", yn(enable)));
2471        }
2472        if let Some(ref bytes) = self.config.active_defrag_ignore_bytes {
2473            conf.push_str(&format!("active-defrag-ignore-bytes {bytes}\n"));
2474        }
2475        if let Some(pct) = self.config.active_defrag_threshold_lower {
2476            conf.push_str(&format!("active-defrag-threshold-lower {pct}\n"));
2477        }
2478        if let Some(pct) = self.config.active_defrag_threshold_upper {
2479            conf.push_str(&format!("active-defrag-threshold-upper {pct}\n"));
2480        }
2481        if let Some(pct) = self.config.active_defrag_cycle_min {
2482            conf.push_str(&format!("active-defrag-cycle-min {pct}\n"));
2483        }
2484        if let Some(pct) = self.config.active_defrag_cycle_max {
2485            conf.push_str(&format!("active-defrag-cycle-max {pct}\n"));
2486        }
2487        if let Some(n) = self.config.active_defrag_max_scan_fields {
2488            conf.push_str(&format!("active-defrag-max-scan-fields {n}\n"));
2489        }
2490
2491        // -- logging and process --
2492        if let Some(enable) = self.config.syslog_enabled {
2493            conf.push_str(&format!("syslog-enabled {}\n", yn(enable)));
2494        }
2495        if let Some(ref ident) = self.config.syslog_ident {
2496            conf.push_str(&format!("syslog-ident {ident}\n"));
2497        }
2498        if let Some(ref facility) = self.config.syslog_facility {
2499            conf.push_str(&format!("syslog-facility {facility}\n"));
2500        }
2501        if let Some(ref mode) = self.config.supervised {
2502            conf.push_str(&format!("supervised {mode}\n"));
2503        }
2504        if let Some(enable) = self.config.always_show_logo {
2505            conf.push_str(&format!("always-show-logo {}\n", yn(enable)));
2506        }
2507        if let Some(enable) = self.config.set_proc_title {
2508            conf.push_str(&format!("set-proc-title {}\n", yn(enable)));
2509        }
2510        if let Some(ref template) = self.config.proc_title_template {
2511            conf.push_str(&format!("proc-title-template \"{template}\"\n"));
2512        }
2513
2514        // -- security and ACL --
2515        if let Some(ref default) = self.config.acl_pubsub_default {
2516            conf.push_str(&format!("acl-pubsub-default {default}\n"));
2517        }
2518        if let Some(n) = self.config.acllog_max_len {
2519            conf.push_str(&format!("acllog-max-len {n}\n"));
2520        }
2521        if let Some(ref mode) = self.config.enable_debug_command {
2522            conf.push_str(&format!("enable-debug-command {mode}\n"));
2523        }
2524        if let Some(ref mode) = self.config.enable_module_command {
2525            conf.push_str(&format!("enable-module-command {mode}\n"));
2526        }
2527        if let Some(ref mode) = self.config.enable_protected_configs {
2528            conf.push_str(&format!("enable-protected-configs {mode}\n"));
2529        }
2530        for (cmd, new_name) in &self.config.rename_command {
2531            conf.push_str(&format!("rename-command {cmd} \"{new_name}\"\n"));
2532        }
2533        if let Some(ref mode) = self.config.sanitize_dump_payload {
2534            conf.push_str(&format!("sanitize-dump-payload {mode}\n"));
2535        }
2536        if let Some(enable) = self.config.hide_user_data_from_log {
2537            conf.push_str(&format!("hide-user-data-from-log {}\n", yn(enable)));
2538        }
2539
2540        // -- networking (additional) --
2541        if let Some(ref addr) = self.config.bind_source_addr {
2542            conf.push_str(&format!("bind-source-addr {addr}\n"));
2543        }
2544        if let Some(ms) = self.config.busy_reply_threshold {
2545            conf.push_str(&format!("busy-reply-threshold {ms}\n"));
2546        }
2547        for limit in &self.config.client_output_buffer_limit {
2548            conf.push_str(&format!("client-output-buffer-limit {limit}\n"));
2549        }
2550        if let Some(ref limit) = self.config.client_query_buffer_limit {
2551            conf.push_str(&format!("client-query-buffer-limit {limit}\n"));
2552        }
2553        if let Some(ref len) = self.config.proto_max_bulk_len {
2554            conf.push_str(&format!("proto-max-bulk-len {len}\n"));
2555        }
2556        if let Some(n) = self.config.max_new_connections_per_cycle {
2557            conf.push_str(&format!("max-new-connections-per-cycle {n}\n"));
2558        }
2559        if let Some(n) = self.config.max_new_tls_connections_per_cycle {
2560            conf.push_str(&format!("max-new-tls-connections-per-cycle {n}\n"));
2561        }
2562        if let Some(id) = self.config.socket_mark_id {
2563            conf.push_str(&format!("socket-mark-id {id}\n"));
2564        }
2565
2566        // -- RDB (additional) --
2567        if let Some(ref name) = self.config.dbfilename {
2568            conf.push_str(&format!("dbfilename {name}\n"));
2569        }
2570        if let Some(enable) = self.config.rdbcompression {
2571            conf.push_str(&format!("rdbcompression {}\n", yn(enable)));
2572        }
2573        if let Some(enable) = self.config.rdbchecksum {
2574            conf.push_str(&format!("rdbchecksum {}\n", yn(enable)));
2575        }
2576        if let Some(enable) = self.config.rdb_save_incremental_fsync {
2577            conf.push_str(&format!("rdb-save-incremental-fsync {}\n", yn(enable)));
2578        }
2579        if let Some(enable) = self.config.rdb_del_sync_files {
2580            conf.push_str(&format!("rdb-del-sync-files {}\n", yn(enable)));
2581        }
2582        if let Some(enable) = self.config.stop_writes_on_bgsave_error {
2583            conf.push_str(&format!("stop-writes-on-bgsave-error {}\n", yn(enable)));
2584        }
2585
2586        // -- shutdown --
2587        if let Some(ref behavior) = self.config.shutdown_on_sigint {
2588            conf.push_str(&format!("shutdown-on-sigint {behavior}\n"));
2589        }
2590        if let Some(ref behavior) = self.config.shutdown_on_sigterm {
2591            conf.push_str(&format!("shutdown-on-sigterm {behavior}\n"));
2592        }
2593        if let Some(seconds) = self.config.shutdown_timeout {
2594            conf.push_str(&format!("shutdown-timeout {seconds}\n"));
2595        }
2596
2597        // -- other --
2598        if let Some(enable) = self.config.activerehashing {
2599            conf.push_str(&format!("activerehashing {}\n", yn(enable)));
2600        }
2601        if let Some(enable) = self.config.crash_log_enabled {
2602            conf.push_str(&format!("crash-log-enabled {}\n", yn(enable)));
2603        }
2604        if let Some(enable) = self.config.crash_memcheck_enabled {
2605            conf.push_str(&format!("crash-memcheck-enabled {}\n", yn(enable)));
2606        }
2607        if let Some(enable) = self.config.disable_thp {
2608            conf.push_str(&format!("disable-thp {}\n", yn(enable)));
2609        }
2610        if let Some(enable) = self.config.dynamic_hz {
2611            conf.push_str(&format!("dynamic-hz {}\n", yn(enable)));
2612        }
2613        if let Some(ref warning) = self.config.ignore_warnings {
2614            conf.push_str(&format!("ignore-warnings {warning}\n"));
2615        }
2616        for path in &self.config.include {
2617            conf.push_str(&format!("include {}\n", path.display()));
2618        }
2619        if let Some(enable) = self.config.jemalloc_bg_thread {
2620            conf.push_str(&format!("jemalloc-bg-thread {}\n", yn(enable)));
2621        }
2622        if let Some(ref locale) = self.config.locale_collate {
2623            conf.push_str(&format!("locale-collate {locale}\n"));
2624        }
2625        if let Some(ms) = self.config.lua_time_limit {
2626            conf.push_str(&format!("lua-time-limit {ms}\n"));
2627        }
2628        if let Some(ref mode) = self.config.oom_score_adj {
2629            conf.push_str(&format!("oom-score-adj {mode}\n"));
2630        }
2631        if let Some(ref values) = self.config.oom_score_adj_values {
2632            conf.push_str(&format!("oom-score-adj-values {values}\n"));
2633        }
2634        if let Some(ref behavior) = self.config.propagation_error_behavior {
2635            conf.push_str(&format!("propagation-error-behavior {behavior}\n"));
2636        }
2637        if let Some(n) = self.config.tracking_table_max_keys {
2638            conf.push_str(&format!("tracking-table-max-keys {n}\n"));
2639        }
2640
2641        // -- catch-all --
2642        for (key, value) in &self.config.extra {
2643            conf.push_str(&format!("{key} {value}\n"));
2644        }
2645
2646        conf
2647    }
2648}
2649
2650impl Default for RedisServer {
2651    fn default() -> Self {
2652        Self::new()
2653    }
2654}
2655
2656/// Handle to a running Redis server. Stops the server on Drop.
2657pub struct RedisServerHandle {
2658    config: RedisServerConfig,
2659    cli: RedisCli,
2660    pid: u32,
2661    detached: bool,
2662}
2663
2664impl RedisServerHandle {
2665    /// The server's address as "host:port".
2666    pub fn addr(&self) -> String {
2667        format!("{}:{}", self.config.bind, self.config.port)
2668    }
2669
2670    /// The server's port.
2671    pub fn port(&self) -> u16 {
2672        self.config.port
2673    }
2674
2675    /// The server's bind address.
2676    pub fn host(&self) -> &str {
2677        &self.config.bind
2678    }
2679
2680    /// The PID of the `redis-server` process.
2681    pub fn pid(&self) -> u32 {
2682        self.pid
2683    }
2684
2685    /// Check if the server is alive via PING.
2686    pub async fn is_alive(&self) -> bool {
2687        self.cli.ping().await
2688    }
2689
2690    /// Get a `RedisCli` configured for this server.
2691    pub fn cli(&self) -> &RedisCli {
2692        &self.cli
2693    }
2694
2695    /// Run a redis-cli command against this server.
2696    pub async fn run(&self, args: &[&str]) -> Result<String> {
2697        self.cli.run(args).await
2698    }
2699
2700    /// Consume the handle without stopping the server.
2701    pub fn detach(mut self) {
2702        self.detached = true;
2703    }
2704
2705    /// Stop the server via SHUTDOWN NOSAVE.
2706    pub fn stop(&self) {
2707        self.cli.shutdown();
2708    }
2709
2710    /// Wait until the server is ready (PING -> PONG).
2711    pub async fn wait_for_ready(&self, timeout: Duration) -> Result<()> {
2712        self.cli.wait_for_ready(timeout).await
2713    }
2714}
2715
2716impl Drop for RedisServerHandle {
2717    fn drop(&mut self) {
2718        if !self.detached {
2719            self.stop();
2720        }
2721    }
2722}
2723
2724#[cfg(test)]
2725mod tests {
2726    use super::*;
2727
2728    #[test]
2729    fn default_config() {
2730        let s = RedisServer::new();
2731        assert_eq!(s.config.port, 6379);
2732        assert_eq!(s.config.bind, "127.0.0.1");
2733        assert!(matches!(s.config.save, SavePolicy::Disabled));
2734    }
2735
2736    #[test]
2737    fn builder_chain() {
2738        let s = RedisServer::new()
2739            .port(6400)
2740            .bind("0.0.0.0")
2741            .save(true)
2742            .appendonly(true)
2743            .password("secret")
2744            .logfile("/tmp/redis.log")
2745            .loglevel(LogLevel::Warning)
2746            .extra("maxmemory", "100mb");
2747
2748        assert_eq!(s.config.port, 6400);
2749        assert_eq!(s.config.bind, "0.0.0.0");
2750        assert!(matches!(s.config.save, SavePolicy::Default));
2751        assert!(s.config.appendonly);
2752        assert_eq!(s.config.password.as_deref(), Some("secret"));
2753        assert_eq!(s.config.logfile.as_deref(), Some("/tmp/redis.log"));
2754        assert_eq!(s.config.extra.get("maxmemory").unwrap(), "100mb");
2755    }
2756
2757    #[test]
2758    fn save_schedule() {
2759        let s = RedisServer::new().save_schedule(vec![(900, 1), (300, 10)]);
2760        match &s.config.save {
2761            SavePolicy::Custom(pairs) => {
2762                assert_eq!(pairs, &[(900, 1), (300, 10)]);
2763            }
2764            _ => panic!("expected SavePolicy::Custom"),
2765        }
2766    }
2767
2768    #[test]
2769    fn aof_tuning() {
2770        let s = RedisServer::new()
2771            .appendonly(true)
2772            .appendfsync(AppendFsync::Always)
2773            .appendfilename("my.aof")
2774            .aof_use_rdb_preamble(true)
2775            .auto_aof_rewrite_percentage(100)
2776            .auto_aof_rewrite_min_size("64mb")
2777            .no_appendfsync_on_rewrite(true);
2778
2779        assert!(s.config.appendonly);
2780        assert!(matches!(s.config.appendfsync, Some(AppendFsync::Always)));
2781        assert_eq!(s.config.appendfilename.as_deref(), Some("my.aof"));
2782        assert_eq!(s.config.aof_use_rdb_preamble, Some(true));
2783        assert_eq!(s.config.auto_aof_rewrite_percentage, Some(100));
2784        assert_eq!(s.config.auto_aof_rewrite_min_size.as_deref(), Some("64mb"));
2785        assert_eq!(s.config.no_appendfsync_on_rewrite, Some(true));
2786    }
2787
2788    #[test]
2789    fn memory_eviction_and_lazyfree() {
2790        let s = RedisServer::new()
2791            .maxmemory("256mb")
2792            .maxmemory_policy("allkeys-lfu")
2793            .maxmemory_samples(10)
2794            .maxmemory_clients("0")
2795            .maxmemory_eviction_tenacity(50)
2796            .lfu_log_factor(10)
2797            .lfu_decay_time(1)
2798            .active_expire_effort(25)
2799            .lazyfree_lazy_eviction(true)
2800            .lazyfree_lazy_expire(true)
2801            .lazyfree_lazy_server_del(true)
2802            .lazyfree_lazy_user_del(false)
2803            .lazyfree_lazy_user_flush(true);
2804
2805        assert_eq!(s.config.maxmemory.as_deref(), Some("256mb"));
2806        assert_eq!(s.config.maxmemory_policy.as_deref(), Some("allkeys-lfu"));
2807        assert_eq!(s.config.maxmemory_samples, Some(10));
2808        assert_eq!(s.config.maxmemory_clients.as_deref(), Some("0"));
2809        assert_eq!(s.config.maxmemory_eviction_tenacity, Some(50));
2810        assert_eq!(s.config.lfu_log_factor, Some(10));
2811        assert_eq!(s.config.lfu_decay_time, Some(1));
2812        assert_eq!(s.config.active_expire_effort, Some(25));
2813        assert_eq!(s.config.lazyfree_lazy_eviction, Some(true));
2814        assert_eq!(s.config.lazyfree_lazy_expire, Some(true));
2815        assert_eq!(s.config.lazyfree_lazy_server_del, Some(true));
2816        assert_eq!(s.config.lazyfree_lazy_user_del, Some(false));
2817        assert_eq!(s.config.lazyfree_lazy_user_flush, Some(true));
2818    }
2819
2820    #[test]
2821    fn replication_tuning() {
2822        let s = RedisServer::new()
2823            .replicaof("127.0.0.1", 6379)
2824            .masterauth("secret")
2825            .masteruser("repl-user")
2826            .repl_backlog_size("1mb")
2827            .repl_backlog_ttl(3600)
2828            .repl_disable_tcp_nodelay(true)
2829            .repl_diskless_load(ReplDisklessLoad::Swapdb)
2830            .repl_diskless_sync(true)
2831            .repl_diskless_sync_delay(5)
2832            .repl_diskless_sync_max_replicas(3)
2833            .repl_ping_replica_period(10)
2834            .repl_timeout(60)
2835            .replica_announce_ip("10.0.0.1")
2836            .replica_announce_port(6380)
2837            .replica_announced(true)
2838            .replica_full_sync_buffer_limit("256mb")
2839            .replica_ignore_disk_write_errors(false)
2840            .replica_ignore_maxmemory(true)
2841            .replica_lazy_flush(true)
2842            .replica_priority(100)
2843            .replica_read_only(true)
2844            .replica_serve_stale_data(false)
2845            .min_replicas_to_write(2)
2846            .min_replicas_max_lag(10);
2847
2848        assert_eq!(s.config.replicaof, Some(("127.0.0.1".into(), 6379)));
2849        assert_eq!(s.config.masterauth.as_deref(), Some("secret"));
2850        assert_eq!(s.config.masteruser.as_deref(), Some("repl-user"));
2851        assert_eq!(s.config.repl_backlog_size.as_deref(), Some("1mb"));
2852        assert_eq!(s.config.repl_backlog_ttl, Some(3600));
2853        assert_eq!(s.config.repl_disable_tcp_nodelay, Some(true));
2854        assert!(matches!(
2855            s.config.repl_diskless_load,
2856            Some(ReplDisklessLoad::Swapdb)
2857        ));
2858        assert_eq!(s.config.repl_diskless_sync, Some(true));
2859        assert_eq!(s.config.repl_diskless_sync_delay, Some(5));
2860        assert_eq!(s.config.repl_diskless_sync_max_replicas, Some(3));
2861        assert_eq!(s.config.repl_ping_replica_period, Some(10));
2862        assert_eq!(s.config.repl_timeout, Some(60));
2863        assert_eq!(s.config.replica_announce_ip.as_deref(), Some("10.0.0.1"));
2864        assert_eq!(s.config.replica_announce_port, Some(6380));
2865        assert_eq!(s.config.replica_announced, Some(true));
2866        assert_eq!(
2867            s.config.replica_full_sync_buffer_limit.as_deref(),
2868            Some("256mb")
2869        );
2870        assert_eq!(s.config.replica_ignore_disk_write_errors, Some(false));
2871        assert_eq!(s.config.replica_ignore_maxmemory, Some(true));
2872        assert_eq!(s.config.replica_lazy_flush, Some(true));
2873        assert_eq!(s.config.replica_priority, Some(100));
2874        assert_eq!(s.config.replica_read_only, Some(true));
2875        assert_eq!(s.config.replica_serve_stale_data, Some(false));
2876        assert_eq!(s.config.min_replicas_to_write, Some(2));
2877        assert_eq!(s.config.min_replicas_max_lag, Some(10));
2878    }
2879
2880    #[test]
2881    fn cluster_config() {
2882        let s = RedisServer::new()
2883            .port(7000)
2884            .cluster_enabled(true)
2885            .cluster_node_timeout(5000)
2886            .cluster_config_file("/tmp/nodes.conf")
2887            .cluster_require_full_coverage(false)
2888            .cluster_allow_reads_when_down(true)
2889            .cluster_allow_pubsubshard_when_down(true)
2890            .cluster_allow_replica_migration(true)
2891            .cluster_migration_barrier(1)
2892            .cluster_replica_no_failover(false)
2893            .cluster_replica_validity_factor(10)
2894            .cluster_announce_ip("10.0.0.1")
2895            .cluster_announce_port(7000)
2896            .cluster_announce_bus_port(17000)
2897            .cluster_announce_tls_port(7100)
2898            .cluster_announce_hostname("node1.example.com")
2899            .cluster_announce_human_nodename("node-1")
2900            .cluster_port(17000)
2901            .cluster_preferred_endpoint_type("ip")
2902            .cluster_link_sendbuf_limit(67108864)
2903            .cluster_compatibility_sample_ratio(50)
2904            .cluster_slot_migration_handoff_max_lag_bytes(1048576)
2905            .cluster_slot_migration_write_pause_timeout(5000)
2906            .cluster_slot_stats_enabled(true);
2907
2908        assert!(s.config.cluster_enabled);
2909        assert_eq!(s.config.cluster_node_timeout, Some(5000));
2910        assert_eq!(
2911            s.config.cluster_config_file,
2912            Some(PathBuf::from("/tmp/nodes.conf"))
2913        );
2914        assert_eq!(s.config.cluster_require_full_coverage, Some(false));
2915        assert_eq!(s.config.cluster_allow_reads_when_down, Some(true));
2916        assert_eq!(s.config.cluster_allow_pubsubshard_when_down, Some(true));
2917        assert_eq!(s.config.cluster_allow_replica_migration, Some(true));
2918        assert_eq!(s.config.cluster_migration_barrier, Some(1));
2919        assert_eq!(s.config.cluster_replica_no_failover, Some(false));
2920        assert_eq!(s.config.cluster_replica_validity_factor, Some(10));
2921        assert_eq!(s.config.cluster_announce_ip.as_deref(), Some("10.0.0.1"));
2922        assert_eq!(s.config.cluster_announce_port, Some(7000));
2923        assert_eq!(s.config.cluster_announce_bus_port, Some(17000));
2924        assert_eq!(s.config.cluster_announce_tls_port, Some(7100));
2925        assert_eq!(
2926            s.config.cluster_announce_hostname.as_deref(),
2927            Some("node1.example.com")
2928        );
2929        assert_eq!(
2930            s.config.cluster_announce_human_nodename.as_deref(),
2931            Some("node-1")
2932        );
2933        assert_eq!(s.config.cluster_port, Some(17000));
2934        assert_eq!(
2935            s.config.cluster_preferred_endpoint_type.as_deref(),
2936            Some("ip")
2937        );
2938        assert_eq!(s.config.cluster_link_sendbuf_limit, Some(67108864));
2939        assert_eq!(s.config.cluster_compatibility_sample_ratio, Some(50));
2940        assert_eq!(
2941            s.config.cluster_slot_migration_handoff_max_lag_bytes,
2942            Some(1048576)
2943        );
2944        assert_eq!(
2945            s.config.cluster_slot_migration_write_pause_timeout,
2946            Some(5000)
2947        );
2948        assert_eq!(s.config.cluster_slot_stats_enabled, Some(true));
2949    }
2950
2951    #[test]
2952    fn data_structure_tuning() {
2953        let s = RedisServer::new()
2954            .hash_max_listpack_entries(128)
2955            .hash_max_listpack_value(64)
2956            .list_max_listpack_size(-2)
2957            .list_compress_depth(1)
2958            .set_max_intset_entries(512)
2959            .set_max_listpack_entries(128)
2960            .set_max_listpack_value(64)
2961            .zset_max_listpack_entries(128)
2962            .zset_max_listpack_value(64)
2963            .hll_sparse_max_bytes(3000)
2964            .stream_node_max_bytes(4096)
2965            .stream_node_max_entries(100)
2966            .stream_idmp_duration(5000)
2967            .stream_idmp_maxsize(1000);
2968
2969        assert_eq!(s.config.hash_max_listpack_entries, Some(128));
2970        assert_eq!(s.config.hash_max_listpack_value, Some(64));
2971        assert_eq!(s.config.list_max_listpack_size, Some(-2));
2972        assert_eq!(s.config.list_compress_depth, Some(1));
2973        assert_eq!(s.config.set_max_intset_entries, Some(512));
2974        assert_eq!(s.config.set_max_listpack_entries, Some(128));
2975        assert_eq!(s.config.set_max_listpack_value, Some(64));
2976        assert_eq!(s.config.zset_max_listpack_entries, Some(128));
2977        assert_eq!(s.config.zset_max_listpack_value, Some(64));
2978        assert_eq!(s.config.hll_sparse_max_bytes, Some(3000));
2979        assert_eq!(s.config.stream_node_max_bytes, Some(4096));
2980        assert_eq!(s.config.stream_node_max_entries, Some(100));
2981        assert_eq!(s.config.stream_idmp_duration, Some(5000));
2982        assert_eq!(s.config.stream_idmp_maxsize, Some(1000));
2983    }
2984
2985    #[test]
2986    fn tls_config() {
2987        let s = RedisServer::new()
2988            .port(6400)
2989            .tls_port(6401)
2990            .tls_cert_file("/etc/tls/redis.crt")
2991            .tls_key_file("/etc/tls/redis.key")
2992            .tls_key_file_pass("keypass")
2993            .tls_ca_cert_file("/etc/tls/ca.crt")
2994            .tls_ca_cert_dir("/etc/tls/certs")
2995            .tls_auth_clients(true)
2996            .tls_client_cert_file("/etc/tls/client.crt")
2997            .tls_client_key_file("/etc/tls/client.key")
2998            .tls_client_key_file_pass("clientpass")
2999            .tls_dh_params_file("/etc/tls/dhparams.pem")
3000            .tls_ciphers("ECDHE-RSA-AES256-GCM-SHA384")
3001            .tls_ciphersuites("TLS_AES_256_GCM_SHA384")
3002            .tls_protocols("TLSv1.2 TLSv1.3")
3003            .tls_prefer_server_ciphers(true)
3004            .tls_session_caching(true)
3005            .tls_session_cache_size(20480)
3006            .tls_session_cache_timeout(300)
3007            .tls_replication(true)
3008            .tls_cluster(true);
3009
3010        assert_eq!(s.config.tls_port, Some(6401));
3011        assert_eq!(
3012            s.config.tls_cert_file.as_deref(),
3013            Some(std::path::Path::new("/etc/tls/redis.crt"))
3014        );
3015        assert_eq!(
3016            s.config.tls_key_file.as_deref(),
3017            Some(std::path::Path::new("/etc/tls/redis.key"))
3018        );
3019        assert_eq!(s.config.tls_key_file_pass.as_deref(), Some("keypass"));
3020        assert_eq!(
3021            s.config.tls_ca_cert_file.as_deref(),
3022            Some(std::path::Path::new("/etc/tls/ca.crt"))
3023        );
3024        assert_eq!(
3025            s.config.tls_ca_cert_dir.as_deref(),
3026            Some(std::path::Path::new("/etc/tls/certs"))
3027        );
3028        assert_eq!(s.config.tls_auth_clients, Some(true));
3029        assert_eq!(
3030            s.config.tls_client_cert_file.as_deref(),
3031            Some(std::path::Path::new("/etc/tls/client.crt"))
3032        );
3033        assert_eq!(
3034            s.config.tls_client_key_file.as_deref(),
3035            Some(std::path::Path::new("/etc/tls/client.key"))
3036        );
3037        assert_eq!(
3038            s.config.tls_client_key_file_pass.as_deref(),
3039            Some("clientpass")
3040        );
3041        assert_eq!(
3042            s.config.tls_dh_params_file.as_deref(),
3043            Some(std::path::Path::new("/etc/tls/dhparams.pem"))
3044        );
3045        assert_eq!(
3046            s.config.tls_ciphers.as_deref(),
3047            Some("ECDHE-RSA-AES256-GCM-SHA384")
3048        );
3049        assert_eq!(
3050            s.config.tls_ciphersuites.as_deref(),
3051            Some("TLS_AES_256_GCM_SHA384")
3052        );
3053        assert_eq!(s.config.tls_protocols.as_deref(), Some("TLSv1.2 TLSv1.3"));
3054        assert_eq!(s.config.tls_prefer_server_ciphers, Some(true));
3055        assert_eq!(s.config.tls_session_caching, Some(true));
3056        assert_eq!(s.config.tls_session_cache_size, Some(20480));
3057        assert_eq!(s.config.tls_session_cache_timeout, Some(300));
3058        assert_eq!(s.config.tls_replication, Some(true));
3059        assert_eq!(s.config.tls_cluster, Some(true));
3060    }
3061}