lmrc_cli/generator/
documentation.rs

1use 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.md
12    generate_readme(&docs_path, config)?;
13
14    // Generate SECRETS.md with required GitLab variables
15    generate_secrets_doc(&docs_path, config)?;
16
17    // Generate SETUP.md with setup instructions
18    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    // Server provider secrets
180    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    // Database secrets
196    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    // DNS provider secrets
210    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    // Kubernetes secrets
229    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    // SSH access
239    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    // Container registry
254    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    // Summary
267    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}