1pub const EDGEGUARD_TOML: &str = include_str!("../edgeguard.toml");
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Runtime {
20 Node,
21 Python,
22 Go,
23 Rust,
24 Unknown,
26}
27
28impl Runtime {
29 pub fn detect(entries: &[String]) -> Runtime {
32 let has = |name: &str| entries.iter().any(|e| e == name);
33 if has("package.json") {
34 Runtime::Node
35 } else if has("requirements.txt") || has("pyproject.toml") || has("Pipfile") {
36 Runtime::Python
37 } else if has("go.mod") {
38 Runtime::Go
39 } else if has("Cargo.toml") {
40 Runtime::Rust
41 } else {
42 Runtime::Unknown
43 }
44 }
45
46 pub fn label(self) -> &'static str {
47 match self {
48 Runtime::Node => "Node.js",
49 Runtime::Python => "Python",
50 Runtime::Go => "Go",
51 Runtime::Rust => "Rust",
52 Runtime::Unknown => "unknown",
53 }
54 }
55
56 pub fn default_app_port(self) -> u16 {
59 match self {
60 Runtime::Python => 8000,
63 _ => 3000,
64 }
65 }
66
67 pub fn default_start_cmd(self) -> &'static str {
70 match self {
71 Runtime::Node => "npm start",
72 Runtime::Python => "python app.py",
73 Runtime::Go => "./app",
74 Runtime::Rust => "./app",
75 Runtime::Unknown => "<your app start command>",
76 }
77 }
78}
79
80pub fn dockerfile(runtime: Runtime) -> String {
84 let app_port = runtime.default_app_port();
85 let start = runtime.default_start_cmd();
86 let (base, build_notes) = match runtime {
89 Runtime::Node => (
90 "node:22-slim",
91 "# Install deps and copy your app source:\n\
92 # COPY package*.json ./\n\
93 # RUN npm ci --omit=dev\n\
94 # COPY . .",
95 ),
96 Runtime::Python => (
97 "python:3.12-slim",
98 "# Install deps and copy your app source:\n\
99 # COPY requirements.txt ./\n\
100 # RUN pip install --no-cache-dir -r requirements.txt\n\
101 # COPY . .",
102 ),
103 Runtime::Go => (
104 "debian:bookworm-slim",
105 "# Copy your prebuilt binary (or add a Go build stage):\n\
106 # COPY ./app ./app",
107 ),
108 Runtime::Rust => (
109 "debian:bookworm-slim",
110 "# Copy your prebuilt binary (or add a Rust build stage):\n\
111 # COPY ./app ./app",
112 ),
113 Runtime::Unknown => (
114 "debian:bookworm-slim",
115 "# Install deps and copy your app source here.",
116 ),
117 };
118
119 format!(
120 "# Generated by `edgeguard init` ({label} app).\n\
121 # EdgeGuard is the entrypoint: it binds the platform's $PORT and runs your app on\n\
122 # APP_PORT (localhost only), adding auth + rate-limit + hardened headers with no app\n\
123 # code changes. Review the TODOs below and wire in your real build steps.\n\
124 FROM {base}\n\
125 WORKDIR /app\n\
126 \n\
127 # --- the EdgeGuard binary (static musl, distroless-built) ---\n\
128 COPY --from=mancube/eggrd:latest /usr/local/bin/edgeguard /usr/local/bin/edgeguard\n\
129 COPY edgeguard.toml /app/edgeguard.toml\n\
130 \n\
131 {build_notes}\n\
132 \n\
133 # EdgeGuard listens on $PORT (default 8080) and runs your app on APP_PORT.\n\
134 ENV PORT=8080 APP_PORT={app_port}\n\
135 EXPOSE 8080\n\
136 ENTRYPOINT [\"/usr/local/bin/edgeguard\", \"--config\", \"/app/edgeguard.toml\", \
137 \"--wrap\", \"{start}\"]\n",
138 label = runtime.label(),
139 base = base,
140 build_notes = build_notes,
141 app_port = app_port,
142 start = start,
143 )
144}
145
146pub fn next_steps(runtime: Runtime) -> String {
148 let app_port = runtime.default_app_port();
149 format!(
150 "Next steps:\n\
151 1. Set a real credential — the shipped edgeguard.toml ships a non-working placeholder:\n\
152 \x20 echo -n 'your-password' | edgeguard --hash\n\
153 \x20 # paste the $argon2id$… string as the user's value in edgeguard.toml\n\
154 2. Review edgeguard.toml (auth mode, rate limits, CORS if your frontend is on another origin).\n\
155 3. Validate it: edgeguard doctor --config edgeguard.toml\n\
156 4. Run locally: PORT=8080 APP_PORT={app_port} edgeguard --config edgeguard.toml --wrap \"{start}\"\n\
157 5. Or build the generated Dockerfile and deploy it as your container entrypoint.\n",
158 app_port = app_port,
159 start = runtime.default_start_cmd(),
160 )
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 fn names(v: &[&str]) -> Vec<String> {
168 v.iter().map(|s| s.to_string()).collect()
169 }
170
171 #[test]
172 fn detects_each_runtime() {
173 assert_eq!(Runtime::detect(&names(&["package.json"])), Runtime::Node);
174 assert_eq!(
175 Runtime::detect(&names(&["requirements.txt"])),
176 Runtime::Python
177 );
178 assert_eq!(
179 Runtime::detect(&names(&["pyproject.toml"])),
180 Runtime::Python
181 );
182 assert_eq!(Runtime::detect(&names(&["go.mod"])), Runtime::Go);
183 assert_eq!(Runtime::detect(&names(&["Cargo.toml"])), Runtime::Rust);
184 assert_eq!(Runtime::detect(&names(&["README.md"])), Runtime::Unknown);
185 }
186
187 #[test]
188 fn node_wins_over_other_markers() {
189 assert_eq!(
192 Runtime::detect(&names(&["Cargo.toml", "package.json"])),
193 Runtime::Node
194 );
195 }
196
197 #[test]
198 fn embedded_config_is_the_real_reference() {
199 assert!(EDGEGUARD_TOML.contains("[server]"));
201 assert!(EDGEGUARD_TOML.contains("EdgeGuard configuration"));
202 }
203
204 #[test]
205 fn dockerfile_wires_entrypoint_and_app_port() {
206 let df = dockerfile(Runtime::Python);
207 assert!(df.contains("APP_PORT=8000"), "{df}");
208 assert!(df.contains("mancube/eggrd:latest"), "{df}");
209 assert!(df.contains("--wrap"), "{df}");
210 assert!(df.contains("python app.py"), "{df}");
211 }
212}