lmrc_cli/generator/
documentation.rs1use colored::Colorize;
2use lmrc_config_validator::LmrcConfig;
3use std::fs;
4use std::path::Path;
5
6use crate::error::Result;
7
8pub fn generate_docs(project_path: &Path, config: &LmrcConfig) -> Result<()> {
9 let docs_path = project_path.join("docs");
10
11 generate_readme(&docs_path, config)?;
13
14 generate_secrets_doc(&docs_path, config)?;
16
17 generate_setup_doc(&docs_path, config)?;
19
20 println!(" {} docs/", "Created:".green());
21
22 Ok(())
23}
24
25fn generate_readme(docs_path: &Path, config: &LmrcConfig) -> Result<()> {
26 let readme = format!(
27 r#"# {}
28
29{}
30
31## Project Structure
32
33```
34.
35├── apps/ # Application code
36├── libs/ # Shared libraries
37├── infra/ # Infrastructure code
38│ └── pipeline/ # Deployment pipeline binary
39├── docker/ # Docker configurations
40├── docs/ # Documentation
41└── lmrc.toml # Project configuration
42```
43
44## Applications
45
46{}
47
48## Quick Start
49
50### Prerequisites
51
52- Rust 1.75 or later
53- Docker and docker-compose
54- Access to configured cloud providers
55
56### Local Development
57
581. Start local services:
59 ```bash
60 cd docker
61 docker-compose up -d
62 ```
63
642. Build the project:
65 ```bash
66 cargo build
67 ```
68
693. Run an application:
70 ```bash
71 cargo run -p <app-name>
72 ```
73
74### Pipeline Commands
75
76Build the pipeline binary:
77```bash
78cd infra/pipeline
79cargo build --release
80```
81
82Run pipeline commands:
83```bash
84# Show configuration
85./target/release/pipeline config
86
87# Provision infrastructure
88./target/release/pipeline provision
89
90# Setup services (K8s, databases, etc.)
91./target/release/pipeline setup
92
93# Deploy applications
94./target/release/pipeline deploy
95
96# Run full pipeline
97./target/release/pipeline full
98```
99
100## Infrastructure Stack
101
102- **Server Provider**: {}
103- **Kubernetes**: {}
104- **Database**: {}
105- **DNS**: {}
106- **CI/CD**: {}
107
108## Configuration
109
110The project configuration is stored in `lmrc.toml`. See the file for all available options.
111
112## GitLab CI/CD
113
114The project includes a `.gitlab-ci.yml` file that:
1151. Builds the pipeline binary
1162. Builds and tests all applications
1173. Provisions infrastructure (manual trigger)
1184. Sets up services
1195. Deploys applications
120
121### Required GitLab Variables
122
123See [SECRETS.md](./SECRETS.md) for the complete list of required CI/CD variables.
124
125## Documentation
126
127- [SECRETS.md](./SECRETS.md) - Required secrets and GitLab variables
128- [SETUP.md](./SETUP.md) - Detailed setup instructions
129
130## License
131
132Apache-2.0
133"#,
134 config.project.name,
135 config.project.description,
136 config
137 .apps
138 .applications
139 .iter()
140 .map(|app| {
141 let app_type_desc = app
142 .app_type
143 .as_ref()
144 .map(|t| t.display_name())
145 .unwrap_or("Basic");
146 format!("- **{}**: {}", app.name, app_type_desc)
147 })
148 .collect::<Vec<_>>()
149 .join("\n"),
150 config.providers.server,
151 config.providers.kubernetes,
152 config.providers.database,
153 config.providers.dns,
154 config.providers.git
155 );
156
157 fs::write(docs_path.join("README.md"), readme)?;
158
159 Ok(())
160}
161
162fn generate_secrets_doc(docs_path: &Path, config: &LmrcConfig) -> Result<()> {
163 let mut secrets = vec![
164 "# Required Secrets and Variables\n".to_string(),
165 "This document lists all required secrets and CI/CD variables that must be configured in GitLab.\n".to_string(),
166 "## GitLab Configuration\n".to_string(),
167 format!(
168 "Navigate to: `{}/{} > Settings > CI/CD > Variables`\n",
169 config
170 .infrastructure
171 .gitlab
172 .as_ref()
173 .map(|g| g.url.as_str())
174 .unwrap_or("https://gitlab.com"),
175 config.project.name
176 ),
177 ];
178
179 if config.providers.server == "hetzner" {
181 secrets.push("\n## Hetzner Cloud\n".to_string());
182 secrets.push("| Variable Name | Type | Description | Protected | Masked |\n".to_string());
183 secrets.push("|---------------|------|-------------|-----------|--------|\n".to_string());
184 secrets.push(
185 "| `HETZNER_API_TOKEN` | Variable | Hetzner Cloud API token | Yes | Yes |\n"
186 .to_string(),
187 );
188 secrets.push("\n**How to get:**\n".to_string());
189 secrets.push("1. Log in to Hetzner Cloud Console\n".to_string());
190 secrets.push("2. Go to your project\n".to_string());
191 secrets.push("3. Navigate to Security > API Tokens\n".to_string());
192 secrets.push("4. Generate a new token with Read & Write permissions\n".to_string());
193 }
194
195 if config.providers.database == "postgres" {
197 secrets.push("\n## PostgreSQL\n".to_string());
198 secrets.push("| Variable Name | Type | Description | Protected | Masked |\n".to_string());
199 secrets.push("|---------------|------|-------------|-----------|--------|\n".to_string());
200 secrets.push(
201 "| `POSTGRES_PASSWORD` | Variable | PostgreSQL admin password | Yes | Yes |\n"
202 .to_string(),
203 );
204 secrets.push(
205 "| `DATABASE_URL` | Variable | Database connection string | Yes | Yes |\n".to_string(),
206 );
207 }
208
209 if config.providers.dns == "cloudflare" {
211 secrets.push("\n## Cloudflare\n".to_string());
212 secrets.push("| Variable Name | Type | Description | Protected | Masked |\n".to_string());
213 secrets.push("|---------------|------|-------------|-----------|--------|\n".to_string());
214 secrets.push(
215 "| `CLOUDFLARE_API_TOKEN` | Variable | Cloudflare API token | Yes | Yes |\n"
216 .to_string(),
217 );
218 secrets.push(
219 "| `CLOUDFLARE_ZONE_ID` | Variable | Cloudflare Zone ID | No | No |\n".to_string(),
220 );
221 secrets.push("\n**How to get:**\n".to_string());
222 secrets.push("1. Log in to Cloudflare Dashboard\n".to_string());
223 secrets.push("2. Go to My Profile > API Tokens\n".to_string());
224 secrets.push("3. Create token with Zone:DNS:Edit permissions\n".to_string());
225 secrets.push("4. Get Zone ID from your domain's Overview page\n".to_string());
226 }
227
228 if config.providers.kubernetes == "k3s" {
230 secrets.push(format!("\n## {} Cluster\n", config.providers.kubernetes));
231 secrets.push("| Variable Name | Type | Description | Protected | Masked |\n".to_string());
232 secrets.push("|---------------|------|-------------|-----------|--------|\n".to_string());
233 secrets.push("| `KUBECONFIG` | File | Kubernetes config file | Yes | No |\n".to_string());
234 secrets
235 .push("| `K8S_NAMESPACE` | Variable | Kubernetes namespace | No | No |\n".to_string());
236 }
237
238 secrets.push("\n## SSH Access\n".to_string());
240 secrets.push("| Variable Name | Type | Description | Protected | Masked |\n".to_string());
241 secrets.push("|---------------|------|-------------|-----------|--------|\n".to_string());
242 secrets.push(
243 "| `SSH_PRIVATE_KEY` | File | SSH private key for server access | Yes | Yes |\n"
244 .to_string(),
245 );
246 secrets.push("| `SSH_KNOWN_HOSTS` | File | SSH known hosts file | No | No |\n".to_string());
247 secrets.push("\n**How to set up:**\n".to_string());
248 secrets.push("1. Create SSH keys in your project's `.ssh/` directory (see SETUP.md)\n".to_string());
249 secrets.push("2. Upload the contents of `.ssh/id_rsa` as `SSH_PRIVATE_KEY` (type: File)\n".to_string());
250 secrets.push("3. The pipeline will use this key to access provisioned servers\n".to_string());
251 secrets.push("\n**Note:** The `.ssh/` directory is git-ignored and never committed to the repository.\n".to_string());
252
253 secrets.push("\n## Container Registry\n".to_string());
255 secrets.push("| Variable Name | Type | Description | Protected | Masked |\n".to_string());
256 secrets.push("|---------------|------|-------------|-----------|--------|\n".to_string());
257 secrets.push(
258 "| `CI_REGISTRY_USER` | Variable | Container registry username | No | No |\n".to_string(),
259 );
260 secrets.push(
261 "| `CI_REGISTRY_PASSWORD` | Variable | Container registry password | Yes | Yes |\n"
262 .to_string(),
263 );
264 secrets.push("\n**Note:** GitLab provides built-in registry variables. Use them if using GitLab Container Registry.\n".to_string());
265
266 secrets.push("\n## Quick Setup Script\n".to_string());
268 secrets.push("You can use the GitLab API to set variables programmatically:\n\n".to_string());
269 secrets.push("```bash\n".to_string());
270 secrets.push("#!/bin/bash\n".to_string());
271 secrets.push("GITLAB_TOKEN=\"your-gitlab-token\"\n".to_string());
272 secrets.push(format!("PROJECT_ID=\"{}\"\n", config.project.name));
273 secrets.push(format!(
274 "GITLAB_URL=\"{}\"\n\n",
275 config
276 .infrastructure
277 .gitlab
278 .as_ref()
279 .map(|g| g.url.as_str())
280 .unwrap_or("https://gitlab.com")
281 ));
282 secrets.push("# Example: Set Hetzner API token\n".to_string());
283 secrets.push("curl --request POST --header \"PRIVATE-TOKEN: $GITLAB_TOKEN\" \\\n".to_string());
284 secrets.push(" \"$GITLAB_URL/api/v4/projects/$PROJECT_ID/variables\" \\\n".to_string());
285 secrets.push(" --form \"key=HETZNER_API_TOKEN\" \\\n".to_string());
286 secrets.push(" --form \"value=your-token-here\" \\\n".to_string());
287 secrets.push(" --form \"protected=true\" \\\n".to_string());
288 secrets.push(" --form \"masked=true\"\n".to_string());
289 secrets.push("```\n".to_string());
290
291 fs::write(docs_path.join("SECRETS.md"), secrets.join(""))?;
292
293 Ok(())
294}
295
296fn generate_setup_doc(docs_path: &Path, config: &LmrcConfig) -> Result<()> {
297 let setup = format!(
298 r#"# Setup Guide
299
300This guide walks you through setting up the {} infrastructure project.
301
302## Prerequisites
303
304### Development Machine
305
306- Rust 1.75 or later
307- Docker and docker-compose
308- Git
309- kubectl (for Kubernetes management)
310
311### Cloud Accounts
312
313{}
314
315## Step 1: Clone and Configure
316
3171. Clone the repository:
318 ```bash
319 git clone <repository-url>
320 cd {}
321 ```
322
3232. Review and update `lmrc.toml` if needed
324
3253. Verify the configuration:
326 ```bash
327 cd infra/pipeline
328 cargo run -- config
329 ```
330
331## Step 2: Set Up SSH Keys
332
333The project uses SSH keys for secure server access. These keys are stored locally in the project directory and are NOT committed to version control.
334
3351. Create the SSH directory in your project:
336 ```bash
337 mkdir -p .ssh
338 chmod 700 .ssh
339 ```
340
3412. Generate an SSH key pair (or copy your existing keys):
342 ```bash
343 # Generate new key
344 ssh-keygen -t rsa -b 4096 -f .ssh/id_rsa -N ""
345
346 # Or copy existing keys
347 cp ~/.ssh/id_rsa .ssh/id_rsa
348 cp ~/.ssh/id_rsa.pub .ssh/id_rsa.pub
349 ```
350
3513. Set correct permissions:
352 ```bash
353 chmod 600 .ssh/id_rsa
354 chmod 644 .ssh/id_rsa.pub
355 ```
356
3574. Add the public key to your servers:
358 ```bash
359 # Copy the public key
360 cat .ssh/id_rsa.pub
361
362 # Then add it to ~/.ssh/authorized_keys on each server
363 ```
364
365**Important Notes:**
366- The `.ssh/` directory is in `.gitignore` and will never be committed
367- You can override the default SSH key path using the `SSH_KEY_PATH` environment variable
368- For CI/CD, use GitLab secrets to store the private key (see SECRETS.md)
369
370## Step 3: Configure GitLab CI/CD Variables
371
372Follow the instructions in [SECRETS.md](./SECRETS.md) to configure all required secrets and variables in GitLab.
373
374## Step 4: Local Development Setup
375
3761. Start local services:
377 ```bash
378 cd docker
379 docker-compose up -d
380 ```
381
3822. Build all applications:
383 ```bash
384 cargo build
385 ```
386
3873. Run tests:
388 ```bash
389 cargo test
390 ```
391
392## Step 5: Infrastructure Provisioning
393
394### Option A: Using GitLab CI/CD (Recommended)
395
3961. Push your code to GitLab
3972. Go to CI/CD > Pipelines
3983. Manually trigger the `provision` job
3994. The pipeline will automatically run setup and deploy after provisioning
400
401### Option B: Manual Provisioning
402
4031. Build the pipeline binary:
404 ```bash
405 cd infra/pipeline
406 cargo build --release
407 ```
408
4092. Run provisioning:
410 ```bash
411 ./target/release/pipeline provision
412 ```
413
4143. Run setup:
415 ```bash
416 ./target/release/pipeline setup
417 ```
418
4194. Deploy applications:
420 ```bash
421 ./target/release/pipeline deploy
422 ```
423
424## Step 6: Verify Deployment
425
426{}
427
428## Troubleshooting
429
430### Pipeline fails to build
431- Check that all required GitLab variables are set
432- Verify Rust version is 1.75 or later
433
434### Provisioning fails
435- Verify cloud provider credentials are correct
436- Check that your account has sufficient permissions
437- Review the logs for specific error messages
438
439### Deployment fails
440- Ensure Kubernetes cluster is accessible
441- Verify kubeconfig is correctly configured
442- Check that Docker images built successfully
443
444## Next Steps
445
446- Set up monitoring and logging
447- Configure backups
448- Set up alerting
449- Review security settings
450
451## Support
452
453For issues and questions, please refer to the project documentation or create an issue in the repository.
454"#,
455 config.project.name,
456 generate_cloud_accounts_section(config),
457 config.project.name,
458 generate_verification_section(config)
459 );
460
461 fs::write(docs_path.join("SETUP.md"), setup)?;
462
463 Ok(())
464}
465
466fn generate_cloud_accounts_section(config: &LmrcConfig) -> String {
467 let mut accounts = Vec::new();
468
469 if config.providers.server == "hetzner" {
470 accounts.push("- Hetzner Cloud account with billing enabled");
471 }
472
473 if config.providers.dns == "cloudflare" {
474 accounts.push("- Cloudflare account with domain registered");
475 }
476
477 if config.providers.git == "gitlab" {
478 accounts.push("- GitLab account with project created");
479 }
480
481 accounts.join("\n")
482}
483
484fn generate_verification_section(config: &LmrcConfig) -> String {
485 let mut checks = Vec::new();
486
487 if config.providers.kubernetes == "k3s" || config.providers.kubernetes == "kubernetes" {
488 checks.push(
489 r#"1. Check cluster status:
490 ```bash
491 kubectl get nodes
492 kubectl get pods --all-namespaces
493 ```
494"#,
495 );
496 }
497
498 if config.providers.database == "postgres" {
499 checks.push(
500 r#"2. Verify database connection:
501 ```bash
502 psql $DATABASE_URL -c "SELECT version();"
503 ```
504"#,
505 );
506 }
507
508 checks.push(
509 r#"3. Check application endpoints:
510 ```bash
511 curl https://your-domain.com/health
512 ```
513"#,
514 );
515
516 checks.join("\n")
517}