pub const EDGEGUARD_TOML: &str = include_str!("../edgeguard.toml");
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Runtime {
Node,
Python,
Go,
Rust,
Unknown,
}
impl Runtime {
pub fn detect(entries: &[String]) -> Runtime {
let has = |name: &str| entries.iter().any(|e| e == name);
if has("package.json") {
Runtime::Node
} else if has("requirements.txt") || has("pyproject.toml") || has("Pipfile") {
Runtime::Python
} else if has("go.mod") {
Runtime::Go
} else if has("Cargo.toml") {
Runtime::Rust
} else {
Runtime::Unknown
}
}
pub fn label(self) -> &'static str {
match self {
Runtime::Node => "Node.js",
Runtime::Python => "Python",
Runtime::Go => "Go",
Runtime::Rust => "Rust",
Runtime::Unknown => "unknown",
}
}
pub fn default_app_port(self) -> u16 {
match self {
Runtime::Python => 8000,
_ => 3000,
}
}
pub fn default_start_cmd(self) -> &'static str {
match self {
Runtime::Node => "npm start",
Runtime::Python => "python app.py",
Runtime::Go => "./app",
Runtime::Rust => "./app",
Runtime::Unknown => "<your app start command>",
}
}
}
pub fn dockerfile(runtime: Runtime) -> String {
let app_port = runtime.default_app_port();
let start = runtime.default_start_cmd();
let (base, build_notes) = match runtime {
Runtime::Node => (
"node:22-slim",
"# Install deps and copy your app source:\n\
# COPY package*.json ./\n\
# RUN npm ci --omit=dev\n\
# COPY . .",
),
Runtime::Python => (
"python:3.12-slim",
"# Install deps and copy your app source:\n\
# COPY requirements.txt ./\n\
# RUN pip install --no-cache-dir -r requirements.txt\n\
# COPY . .",
),
Runtime::Go => (
"debian:bookworm-slim",
"# Copy your prebuilt binary (or add a Go build stage):\n\
# COPY ./app ./app",
),
Runtime::Rust => (
"debian:bookworm-slim",
"# Copy your prebuilt binary (or add a Rust build stage):\n\
# COPY ./app ./app",
),
Runtime::Unknown => (
"debian:bookworm-slim",
"# Install deps and copy your app source here.",
),
};
format!(
"# Generated by `edgeguard init` ({label} app).\n\
# EdgeGuard is the entrypoint: it binds the platform's $PORT and runs your app on\n\
# APP_PORT (localhost only), adding auth + rate-limit + hardened headers with no app\n\
# code changes. Review the TODOs below and wire in your real build steps.\n\
FROM {base}\n\
WORKDIR /app\n\
\n\
# --- the EdgeGuard binary (static musl, distroless-built) ---\n\
COPY --from=mancube/eggrd:latest /usr/local/bin/edgeguard /usr/local/bin/edgeguard\n\
COPY edgeguard.toml /app/edgeguard.toml\n\
\n\
{build_notes}\n\
\n\
# EdgeGuard listens on $PORT (default 8080) and runs your app on APP_PORT.\n\
ENV PORT=8080 APP_PORT={app_port}\n\
EXPOSE 8080\n\
ENTRYPOINT [\"/usr/local/bin/edgeguard\", \"--config\", \"/app/edgeguard.toml\", \
\"--wrap\", \"{start}\"]\n",
label = runtime.label(),
base = base,
build_notes = build_notes,
app_port = app_port,
start = start,
)
}
pub fn next_steps(runtime: Runtime) -> String {
let app_port = runtime.default_app_port();
format!(
"Next steps:\n\
1. Set a real credential — the shipped edgeguard.toml ships a non-working placeholder:\n\
\x20 echo -n 'your-password' | edgeguard --hash\n\
\x20 # paste the $argon2id$… string as the user's value in edgeguard.toml\n\
2. Review edgeguard.toml (auth mode, rate limits, CORS if your frontend is on another origin).\n\
3. Validate it: edgeguard doctor --config edgeguard.toml\n\
4. Run locally: PORT=8080 APP_PORT={app_port} edgeguard --config edgeguard.toml --wrap \"{start}\"\n\
5. Or build the generated Dockerfile and deploy it as your container entrypoint.\n",
app_port = app_port,
start = runtime.default_start_cmd(),
)
}
#[cfg(test)]
mod tests {
use super::*;
fn names(v: &[&str]) -> Vec<String> {
v.iter().map(|s| s.to_string()).collect()
}
#[test]
fn detects_each_runtime() {
assert_eq!(Runtime::detect(&names(&["package.json"])), Runtime::Node);
assert_eq!(
Runtime::detect(&names(&["requirements.txt"])),
Runtime::Python
);
assert_eq!(
Runtime::detect(&names(&["pyproject.toml"])),
Runtime::Python
);
assert_eq!(Runtime::detect(&names(&["go.mod"])), Runtime::Go);
assert_eq!(Runtime::detect(&names(&["Cargo.toml"])), Runtime::Rust);
assert_eq!(Runtime::detect(&names(&["README.md"])), Runtime::Unknown);
}
#[test]
fn node_wins_over_other_markers() {
assert_eq!(
Runtime::detect(&names(&["Cargo.toml", "package.json"])),
Runtime::Node
);
}
#[test]
fn embedded_config_is_the_real_reference() {
assert!(EDGEGUARD_TOML.contains("[server]"));
assert!(EDGEGUARD_TOML.contains("EdgeGuard configuration"));
}
#[test]
fn dockerfile_wires_entrypoint_and_app_port() {
let df = dockerfile(Runtime::Python);
assert!(df.contains("APP_PORT=8000"), "{df}");
assert!(df.contains("mancube/eggrd:latest"), "{df}");
assert!(df.contains("--wrap"), "{df}");
assert!(df.contains("python app.py"), "{df}");
}
}