use rand::RngCore;
fn generate_hex_key(bytes: usize) -> String {
let mut buf = vec![0u8; bytes];
rand::thread_rng().fill_bytes(&mut buf);
buf.iter().map(|b| format!("{b:02x}")).collect()
}
fn should_generate(key: &str, value: &str) -> bool {
let upper = key.to_uppercase();
let skip = [
"DATABASE",
"DB_",
"HOST",
"PORT",
"ADDR",
"URL",
"LISTEN",
"APP_NAME",
"APP_DEBUG",
"APP_ENV",
"LOG_",
];
if skip
.iter()
.any(|p| upper.contains(p) || upper.starts_with(p))
{
return false;
}
let v = value.trim();
if v.is_empty()
|| v == "\"\""
|| v == "''"
|| v.eq_ignore_ascii_case("change-me")
|| v.eq_ignore_ascii_case("change-me-in-production")
|| v.eq_ignore_ascii_case("changethis")
|| v.starts_with("your-")
|| v.starts_with('<')
|| v.starts_with('[')
{
return true;
}
upper.contains("SECRET")
|| upper.contains("_KEY")
|| upper.ends_with("KEY")
|| upper.contains("PASSWORD")
|| upper.contains("TOKEN")
|| upper.contains("SALT")
|| upper == "APP_KEY"
}
pub fn run() -> anyhow::Result<()> {
use std::path::Path;
let env_path = Path::new(".env");
if env_path.exists() {
anyhow::bail!(
".env already exists. Delete it first or use `rok key:rotate --write` \
to update individual keys."
);
}
let example_path = Path::new(".env.example");
if !example_path.exists() {
anyhow::bail!(".env.example not found in current directory");
}
let content = std::fs::read_to_string(example_path)?;
let mut output = String::new();
let mut generated: Vec<String> = Vec::new();
for line in content.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
output.push_str(line);
output.push('\n');
continue;
}
if let Some((key, value)) = trimmed.split_once('=') {
let key = key.trim();
let val = value.trim();
if should_generate(key, val) {
let secret = generate_hex_key(32);
output.push_str(&format!("{key}={secret}\n"));
generated.push(key.to_string());
} else {
output.push_str(line);
output.push('\n');
}
} else {
output.push_str(line);
output.push('\n');
}
}
std::fs::write(env_path, output)?;
println!(
"{} Generated .env with secrets:",
console::style("secrets:generate").green().bold()
);
for key in &generated {
println!(" {key}=<generated>");
}
if generated.is_empty() {
println!(" (no keys needed secret generation; .env is a copy of .env.example)");
}
Ok(())
}