use colored::Colorize;
use lmrc_config_validator::LmrcConfig;
use std::fs;
use std::path::Path;
use crate::error::Result;
pub fn generate_docs(project_path: &Path, config: &LmrcConfig) -> Result<()> {
let docs_path = project_path.join("docs");
generate_readme(&docs_path, config)?;
generate_secrets_doc(&docs_path, config)?;
generate_setup_doc(&docs_path, config)?;
println!(" {} docs/", "Created:".green());
Ok(())
}
fn generate_readme(docs_path: &Path, config: &LmrcConfig) -> Result<()> {
let readme = format!(
r#"# {}
{}
## Project Structure
```
.
├── apps/ # Application code
├── libs/ # Shared libraries
├── infra/ # Infrastructure code
│ └── pipeline/ # Deployment pipeline binary
├── docker/ # Docker configurations
├── docs/ # Documentation
└── lmrc.toml # Project configuration
```
## Applications
{}
## Quick Start
### Prerequisites
- Rust 1.75 or later
- Docker and docker-compose
- Access to configured cloud providers
### Local Development
1. Start local services:
```bash
cd docker
docker-compose up -d
```
2. Build the project:
```bash
cargo build
```
3. Run an application:
```bash
cargo run -p <app-name>
```
### Pipeline Commands
Build the pipeline binary:
```bash
cd infra/pipeline
cargo build --release
```
Run pipeline commands:
```bash
# Show configuration
./target/release/pipeline config
# Provision infrastructure
./target/release/pipeline provision
# Setup services (K8s, databases, etc.)
./target/release/pipeline setup
# Deploy applications
./target/release/pipeline deploy
# Run full pipeline
./target/release/pipeline full
```
## Infrastructure Stack
- **Server Provider**: {}
- **Kubernetes**: {}
- **Database**: {}
- **DNS**: {}
- **CI/CD**: {}
## Configuration
The project configuration is stored in `lmrc.toml`. See the file for all available options.
## GitLab CI/CD
The project includes a `.gitlab-ci.yml` file that:
1. Builds the pipeline binary
2. Builds and tests all applications
3. Provisions infrastructure (manual trigger)
4. Sets up services
5. Deploys applications
### Required GitLab Variables
See [SECRETS.md](./SECRETS.md) for the complete list of required CI/CD variables.
## Documentation
- [SECRETS.md](./SECRETS.md) - Required secrets and GitLab variables
- [SETUP.md](./SETUP.md) - Detailed setup instructions
## License
Apache-2.0
"#,
config.project.name,
config.project.description,
config
.apps
.applications
.iter()
.map(|app| {
let app_type_desc = app
.app_type
.as_ref()
.map(|t| t.display_name())
.unwrap_or("Basic");
format!("- **{}**: {}", app.name, app_type_desc)
})
.collect::<Vec<_>>()
.join("\n"),
config.providers.server,
config.providers.kubernetes,
config.providers.database,
config.providers.dns,
config.providers.git
);
fs::write(docs_path.join("README.md"), readme)?;
Ok(())
}
fn generate_secrets_doc(docs_path: &Path, config: &LmrcConfig) -> Result<()> {
let mut secrets = vec![
"# Required Secrets and Variables\n".to_string(),
"This document lists all required secrets and CI/CD variables that must be configured in GitLab.\n".to_string(),
"## GitLab Configuration\n".to_string(),
format!(
"Navigate to: `{}/{} > Settings > CI/CD > Variables`\n",
config
.infrastructure
.gitlab
.as_ref()
.map(|g| g.url.as_str())
.unwrap_or("https://gitlab.com"),
config.project.name
),
];
if config.providers.server == "hetzner" {
secrets.push("\n## Hetzner Cloud\n".to_string());
secrets.push("| Variable Name | Type | Description | Protected | Masked |\n".to_string());
secrets.push("|---------------|------|-------------|-----------|--------|\n".to_string());
secrets.push(
"| `HETZNER_API_TOKEN` | Variable | Hetzner Cloud API token | Yes | Yes |\n"
.to_string(),
);
secrets.push("\n**How to get:**\n".to_string());
secrets.push("1. Log in to Hetzner Cloud Console\n".to_string());
secrets.push("2. Go to your project\n".to_string());
secrets.push("3. Navigate to Security > API Tokens\n".to_string());
secrets.push("4. Generate a new token with Read & Write permissions\n".to_string());
}
if config.providers.database == "postgres" {
secrets.push("\n## PostgreSQL\n".to_string());
secrets.push("| Variable Name | Type | Description | Protected | Masked |\n".to_string());
secrets.push("|---------------|------|-------------|-----------|--------|\n".to_string());
secrets.push(
"| `POSTGRES_PASSWORD` | Variable | PostgreSQL admin password | Yes | Yes |\n"
.to_string(),
);
secrets.push(
"| `DATABASE_URL` | Variable | Database connection string | Yes | Yes |\n".to_string(),
);
}
if config.providers.dns == "cloudflare" {
secrets.push("\n## Cloudflare\n".to_string());
secrets.push("| Variable Name | Type | Description | Protected | Masked |\n".to_string());
secrets.push("|---------------|------|-------------|-----------|--------|\n".to_string());
secrets.push(
"| `CLOUDFLARE_API_TOKEN` | Variable | Cloudflare API token | Yes | Yes |\n"
.to_string(),
);
secrets.push(
"| `CLOUDFLARE_ZONE_ID` | Variable | Cloudflare Zone ID | No | No |\n".to_string(),
);
secrets.push("\n**How to get:**\n".to_string());
secrets.push("1. Log in to Cloudflare Dashboard\n".to_string());
secrets.push("2. Go to My Profile > API Tokens\n".to_string());
secrets.push("3. Create token with Zone:DNS:Edit permissions\n".to_string());
secrets.push("4. Get Zone ID from your domain's Overview page\n".to_string());
}
if config.providers.kubernetes == "k3s" {
secrets.push(format!("\n## {} Cluster\n", config.providers.kubernetes));
secrets.push("| Variable Name | Type | Description | Protected | Masked |\n".to_string());
secrets.push("|---------------|------|-------------|-----------|--------|\n".to_string());
secrets.push("| `KUBECONFIG` | File | Kubernetes config file | Yes | No |\n".to_string());
secrets
.push("| `K8S_NAMESPACE` | Variable | Kubernetes namespace | No | No |\n".to_string());
}
secrets.push("\n## SSH Access\n".to_string());
secrets.push("| Variable Name | Type | Description | Protected | Masked |\n".to_string());
secrets.push("|---------------|------|-------------|-----------|--------|\n".to_string());
secrets.push(
"| `SSH_PRIVATE_KEY` | File | SSH private key for server access | Yes | Yes |\n"
.to_string(),
);
secrets.push("| `SSH_KNOWN_HOSTS` | File | SSH known hosts file | No | No |\n".to_string());
secrets.push("\n**How to set up:**\n".to_string());
secrets.push("1. Create SSH keys in your project's `.ssh/` directory (see SETUP.md)\n".to_string());
secrets.push("2. Upload the contents of `.ssh/id_rsa` as `SSH_PRIVATE_KEY` (type: File)\n".to_string());
secrets.push("3. The pipeline will use this key to access provisioned servers\n".to_string());
secrets.push("\n**Note:** The `.ssh/` directory is git-ignored and never committed to the repository.\n".to_string());
secrets.push("\n## Container Registry\n".to_string());
secrets.push("| Variable Name | Type | Description | Protected | Masked |\n".to_string());
secrets.push("|---------------|------|-------------|-----------|--------|\n".to_string());
secrets.push(
"| `CI_REGISTRY_USER` | Variable | Container registry username | No | No |\n".to_string(),
);
secrets.push(
"| `CI_REGISTRY_PASSWORD` | Variable | Container registry password | Yes | Yes |\n"
.to_string(),
);
secrets.push("\n**Note:** GitLab provides built-in registry variables. Use them if using GitLab Container Registry.\n".to_string());
secrets.push("\n## Quick Setup Script\n".to_string());
secrets.push("You can use the GitLab API to set variables programmatically:\n\n".to_string());
secrets.push("```bash\n".to_string());
secrets.push("#!/bin/bash\n".to_string());
secrets.push("GITLAB_TOKEN=\"your-gitlab-token\"\n".to_string());
secrets.push(format!("PROJECT_ID=\"{}\"\n", config.project.name));
secrets.push(format!(
"GITLAB_URL=\"{}\"\n\n",
config
.infrastructure
.gitlab
.as_ref()
.map(|g| g.url.as_str())
.unwrap_or("https://gitlab.com")
));
secrets.push("# Example: Set Hetzner API token\n".to_string());
secrets.push("curl --request POST --header \"PRIVATE-TOKEN: $GITLAB_TOKEN\" \\\n".to_string());
secrets.push(" \"$GITLAB_URL/api/v4/projects/$PROJECT_ID/variables\" \\\n".to_string());
secrets.push(" --form \"key=HETZNER_API_TOKEN\" \\\n".to_string());
secrets.push(" --form \"value=your-token-here\" \\\n".to_string());
secrets.push(" --form \"protected=true\" \\\n".to_string());
secrets.push(" --form \"masked=true\"\n".to_string());
secrets.push("```\n".to_string());
fs::write(docs_path.join("SECRETS.md"), secrets.join(""))?;
Ok(())
}
fn generate_setup_doc(docs_path: &Path, config: &LmrcConfig) -> Result<()> {
let setup = format!(
r#"# Setup Guide
This guide walks you through setting up the {} infrastructure project.
## Prerequisites
### Development Machine
- Rust 1.75 or later
- Docker and docker-compose
- Git
- kubectl (for Kubernetes management)
### Cloud Accounts
{}
## Step 1: Clone and Configure
1. Clone the repository:
```bash
git clone <repository-url>
cd {}
```
2. Review and update `lmrc.toml` if needed
3. Verify the configuration:
```bash
cd infra/pipeline
cargo run -- config
```
## Step 2: Set Up SSH Keys
The project uses SSH keys for secure server access. These keys are stored locally in the project directory and are NOT committed to version control.
1. Create the SSH directory in your project:
```bash
mkdir -p .ssh
chmod 700 .ssh
```
2. Generate an SSH key pair (or copy your existing keys):
```bash
# Generate new key
ssh-keygen -t rsa -b 4096 -f .ssh/id_rsa -N ""
# Or copy existing keys
cp ~/.ssh/id_rsa .ssh/id_rsa
cp ~/.ssh/id_rsa.pub .ssh/id_rsa.pub
```
3. Set correct permissions:
```bash
chmod 600 .ssh/id_rsa
chmod 644 .ssh/id_rsa.pub
```
4. Add the public key to your servers:
```bash
# Copy the public key
cat .ssh/id_rsa.pub
# Then add it to ~/.ssh/authorized_keys on each server
```
**Important Notes:**
- The `.ssh/` directory is in `.gitignore` and will never be committed
- You can override the default SSH key path using the `SSH_KEY_PATH` environment variable
- For CI/CD, use GitLab secrets to store the private key (see SECRETS.md)
## Step 3: Configure GitLab CI/CD Variables
Follow the instructions in [SECRETS.md](./SECRETS.md) to configure all required secrets and variables in GitLab.
## Step 4: Local Development Setup
1. Start local services:
```bash
cd docker
docker-compose up -d
```
2. Build all applications:
```bash
cargo build
```
3. Run tests:
```bash
cargo test
```
## Step 5: Infrastructure Provisioning
### Option A: Using GitLab CI/CD (Recommended)
1. Push your code to GitLab
2. Go to CI/CD > Pipelines
3. Manually trigger the `provision` job
4. The pipeline will automatically run setup and deploy after provisioning
### Option B: Manual Provisioning
1. Build the pipeline binary:
```bash
cd infra/pipeline
cargo build --release
```
2. Run provisioning:
```bash
./target/release/pipeline provision
```
3. Run setup:
```bash
./target/release/pipeline setup
```
4. Deploy applications:
```bash
./target/release/pipeline deploy
```
## Step 6: Verify Deployment
{}
## Troubleshooting
### Pipeline fails to build
- Check that all required GitLab variables are set
- Verify Rust version is 1.75 or later
### Provisioning fails
- Verify cloud provider credentials are correct
- Check that your account has sufficient permissions
- Review the logs for specific error messages
### Deployment fails
- Ensure Kubernetes cluster is accessible
- Verify kubeconfig is correctly configured
- Check that Docker images built successfully
## Next Steps
- Set up monitoring and logging
- Configure backups
- Set up alerting
- Review security settings
## Support
For issues and questions, please refer to the project documentation or create an issue in the repository.
"#,
config.project.name,
generate_cloud_accounts_section(config),
config.project.name,
generate_verification_section(config)
);
fs::write(docs_path.join("SETUP.md"), setup)?;
Ok(())
}
fn generate_cloud_accounts_section(config: &LmrcConfig) -> String {
let mut accounts = Vec::new();
if config.providers.server == "hetzner" {
accounts.push("- Hetzner Cloud account with billing enabled");
}
if config.providers.dns == "cloudflare" {
accounts.push("- Cloudflare account with domain registered");
}
if config.providers.git == "gitlab" {
accounts.push("- GitLab account with project created");
}
accounts.join("\n")
}
fn generate_verification_section(config: &LmrcConfig) -> String {
let mut checks = Vec::new();
if config.providers.kubernetes == "k3s" || config.providers.kubernetes == "kubernetes" {
checks.push(
r#"1. Check cluster status:
```bash
kubectl get nodes
kubectl get pods --all-namespaces
```
"#,
);
}
if config.providers.database == "postgres" {
checks.push(
r#"2. Verify database connection:
```bash
psql $DATABASE_URL -c "SELECT version();"
```
"#,
);
}
checks.push(
r#"3. Check application endpoints:
```bash
curl https://your-domain.com/health
```
"#,
);
checks.join("\n")
}