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