#![forbid(unsafe_code)]
use std::path::PathBuf;
use std::sync::Arc;
use kevy_config::{CliOverrides, Config};
fn main() -> ! {
for arg in std::env::args().skip(1) {
match arg.as_str() {
"--help" | "-h" => {
print_help();
std::process::exit(0);
}
"--version" | "-V" => {
println!("kevy {}", env!("CARGO_PKG_VERSION"));
std::process::exit(0);
}
_ => {}
}
}
let (config_path, cli) = parse_cli();
let mut cfg = Config::load(config_path.as_deref()).unwrap_or_else(|e| {
eprintln!("{e}");
std::process::exit(1);
});
cfg.merge_env(env_vars()).unwrap_or_else(|e| {
eprintln!("{e}");
std::process::exit(1);
});
cfg.merge_cli(cli).unwrap_or_else(|e| {
eprintln!("{e}");
std::process::exit(1);
});
let threads = if cfg.server.threads == 0 {
std::thread::available_parallelism().map_or(1, |n| n.get())
} else {
cfg.server.threads
};
let [a, b, c, d] = cfg.server.bind;
eprintln!(
"kevy v{} starting: {a}.{b}.{c}.{d}:{}, {threads} shard(s), dir={}, aof={} (thread-per-core)",
env!("CARGO_PKG_VERSION"),
cfg.server.port,
cfg.server.data_dir.display(),
if cfg.persistence.aof { "on" } else { "off" }
);
if !is_loopback(cfg.server.bind) {
warn_unprotected_bind(cfg.server.bind);
}
let bind = cfg.server.bind;
let port = cfg.server.port;
let data_dir = cfg.server.data_dir.clone();
let aof = cfg.persistence.aof;
kevy::config_init(Arc::new(cfg));
kevy::serve(bind, port, threads, data_dir, aof); }
fn print_help() {
let v = env!("CARGO_PKG_VERSION");
println!(
"\
kevy {v} — pure-Rust Redis-compatible KV server.
USAGE:
kevy [OPTIONS]
OPTIONS:
--config <PATH> TOML config file (auto-detected: ./kevy.toml,
/etc/kevy/kevy.toml, $XDG_CONFIG_HOME/kevy/kevy.toml)
--bind <IPv4> Bind address (default: 127.0.0.1)
--port <PORT> Listen port (default: 6004)
--threads <N> Shard count (default: 0 = available_parallelism())
--dir <PATH> Data directory for snapshot + AOF (default: .)
--no-aof Disable the AOF (in-memory only / cache-only mode)
-h, --help Show this help and exit
-V, --version Print version and exit
Precedence (top wins): CLI flags > env vars > TOML file > built-in defaults.
Env vars: KEVY_BIND, KEVY_PORT, KEVY_THREADS, KEVY_DIR, KEVY_AOF.
EXAMPLES:
kevy # 127.0.0.1:6004, all cores, AOF on
kevy --bind 0.0.0.0 --port 6379
kevy --config /etc/kevy/kevy.toml
KEVY_BIND=0.0.0.0 KEVY_AOF=0 kevy
CLI client for healthchecks / one-shot commands: see `kevy-cli --help`.
Docs: https://github.com/goliajp/kevy"
);
}
fn parse_cli() -> (Option<PathBuf>, CliOverrides) {
let config_path = arg_value("--config").map(PathBuf::from);
let aof = if std::env::args().any(|a| a == "--no-aof") {
Some(false)
} else {
None
};
let overrides = CliOverrides {
bind: arg_value("--bind").and_then(|s| parse_ipv4(&s)),
port: arg_value("--port").and_then(|s| s.parse().ok()),
threads: arg_value("--threads")
.and_then(|s| s.parse::<usize>().ok())
.filter(|&n| n > 0),
data_dir: arg_value("--dir").map(PathBuf::from),
aof,
};
(config_path, overrides)
}
fn env_vars() -> impl IntoIterator<Item = (String, String)> {
std::env::vars().collect::<Vec<_>>()
}
fn arg_value(flag: &str) -> Option<String> {
let mut args = std::env::args().skip(1);
let eq_prefix = format!("{flag}=");
while let Some(arg) = args.next() {
if arg == flag {
return args.next();
}
if let Some(v) = arg.strip_prefix(&eq_prefix) {
return Some(v.to_string());
}
}
None
}
fn parse_ipv4(s: &str) -> Option<[u8; 4]> {
let mut octets = [0u8; 4];
let mut parts = s.split('.');
for slot in &mut octets {
*slot = parts.next()?.parse().ok()?;
}
if parts.next().is_some() {
return None;
}
Some(octets)
}
#[inline]
fn is_loopback(bind: [u8; 4]) -> bool {
bind[0] == 127
}
fn warn_unprotected_bind(bind: [u8; 4]) {
let [a, b, c, d] = bind;
eprintln!("kevy WARN: bind={a}.{b}.{c}.{d} is not loopback and kevy has no AUTH/TLS yet.");
eprintln!("kevy WARN: anyone who can reach this socket can read/write every key.");
eprintln!("kevy WARN: safe only on trust-bounded networks (docker-compose internal,");
eprintln!("kevy WARN: kubernetes pod network, VPC private subnet). Do NOT expose to");
eprintln!("kevy WARN: the public internet. Front with stunnel/nginx + IP allowlist");
eprintln!("kevy WARN: until AUTH/TLS lands in v0.3+.");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn loopback_classification() {
assert!(is_loopback([127, 0, 0, 1]));
assert!(is_loopback([127, 255, 255, 254]));
assert!(is_loopback([127, 1, 2, 3]));
assert!(!is_loopback([0, 0, 0, 0])); assert!(!is_loopback([10, 0, 0, 1])); assert!(!is_loopback([192, 168, 1, 1])); assert!(!is_loopback([8, 8, 8, 8])); }
#[test]
fn ipv4_parser_accepts_valid_only() {
assert_eq!(parse_ipv4("127.0.0.1"), Some([127, 0, 0, 1]));
assert_eq!(parse_ipv4("0.0.0.0"), Some([0, 0, 0, 0]));
assert_eq!(parse_ipv4("256.0.0.1"), None);
assert_eq!(parse_ipv4("1.2.3"), None);
}
}