diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b1dbc2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# Terraform / OpenTofu +**/.terraform/ +**/.terraform.lock.hcl +**/*.tfstate +**/*.tfstate.backup +**/*.tfstate.*.backup +**/crash.log +**/crash.*.log +**/.terraformrc +**/terraform.rc +**/override.tf +**/override.tf.json +**/*_override.tf +**/*_override.tf.json +**/.terraform.tfstate.lock.info + +# Keep encrypted state files +!**/*.tfstate.enc + +# SOPS +.sops/key.txt +**/.decrypted~* + +# Environment files with secrets +**/.env +!**/.env.example + +# Sensitive data directories +**/data/ +**/config/ +!**/config/*.example +!**/config/.gitkeep + +# Logs +**/logs/ +**/*.log + +# OS files +.DS_Store +Thumbs.db +*.swp +*.swo +*~ + +# Editor files +.vscode/ +.idea/ +*.sublime-* + +# Temporary files +*.tmp +*.temp diff --git a/terraform/proxmox-examples/docker-host/.gitignore b/terraform/proxmox-examples/docker-host/.gitignore new file mode 100644 index 0000000..f444af8 --- /dev/null +++ b/terraform/proxmox-examples/docker-host/.gitignore @@ -0,0 +1,34 @@ +# Terraform state files (unencrypted) +*.tfstate +*.tfstate.backup +*.tfstate.*.backup + +# Keep encrypted state files +!*.tfstate.enc + +# Terraform directory +.terraform/ +.terraform.lock.hcl + +# SOPS configuration with your private key +.sops.yaml + +# Actual terraform.tfvars (may contain secrets) +terraform.tfvars + +# Keep encrypted version +!terraform.tfvars.enc + +# Crash logs +crash.log +crash.*.log + +# Override files +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Terraform RC files +.terraformrc +terraform.rc diff --git a/terraform/proxmox-examples/docker-host/.sops.yaml.example b/terraform/proxmox-examples/docker-host/.sops.yaml.example new file mode 100644 index 0000000..56e84e5 --- /dev/null +++ b/terraform/proxmox-examples/docker-host/.sops.yaml.example @@ -0,0 +1,34 @@ +# SOPS Configuration for Terraform State Encryption +# +# Setup Instructions: +# 1. Install age and sops: +# - Debian/Ubuntu: sudo apt install age +# - macOS: brew install age sops +# - Manual: https://github.com/FiloSottile/age/releases +# https://github.com/getsops/sops/releases +# +# 2. Generate an age key: +# mkdir -p ~/.sops +# age-keygen -o ~/.sops/homelab-terraform.txt +# +# 3. Copy this file: +# cp .sops.yaml.example .sops.yaml +# +# 4. Replace YOUR_AGE_PUBLIC_KEY_HERE with the public key from step 2 +# (the line starting with "age1...") +# +# 5. DO NOT commit .sops.yaml to git (it's in .gitignore) +# Keep your private key (~/.sops/homelab-terraform.txt) secure! + +creation_rules: + # Encrypt all .tfstate files with age + - path_regex: \.tfstate$ + age: YOUR_AGE_PUBLIC_KEY_HERE + + # Encrypt any .secret files + - path_regex: \.secret$ + age: YOUR_AGE_PUBLIC_KEY_HERE + + # Encrypt terraform.tfvars (contains API tokens) + - path_regex: terraform\.tfvars$ + age: YOUR_AGE_PUBLIC_KEY_HERE diff --git a/terraform/proxmox-examples/docker-host/README.md b/terraform/proxmox-examples/docker-host/README.md index d2e90ac..0cb40d5 100644 --- a/terraform/proxmox-examples/docker-host/README.md +++ b/terraform/proxmox-examples/docker-host/README.md @@ -474,6 +474,80 @@ Type `yes` to confirm deletion. ## Troubleshooting +### Datastore Does Not Support Snippets + +Error: `the datastore "local" does not support content type "snippets"` + +**Cause:** The storage you specified doesn't have snippets enabled + +**Solution 1 - Enable snippets on existing storage:** +```bash +# On Proxmox host, check current content types +pvesm status + +# Enable snippets on local storage +pvesm set local --content backup,iso,vztmpl,snippets + +# Verify +pvesm status | grep local +``` + +**Solution 2 - Create dedicated directory storage:** +```bash +# On Proxmox host +# Create directory for snippets +mkdir -p /var/lib/vz/snippets + +# Add directory storage via Proxmox UI: +# Datacenter → Storage → Add → Directory +# ID: local-snippets +# Directory: /var/lib/vz/snippets +# Content: Snippets + +# Or via CLI: +pvesm add dir local-snippets --path /var/lib/vz/snippets --content snippets + +# Update terraform.tfvars: +# snippets_storage = "local-snippets" +``` + +### SSH Authentication Failed + +Error: `failed to open SSH client: unable to authenticate` + +**Cause:** The Proxmox provider needs SSH access to upload cloud-init files + +**Solution 1 - Add SSH key to Proxmox (Recommended):** +```bash +# On your workstation, generate SSH key if you don't have one +ssh-keygen -t ed25519 -C "terraform@homelab" + +# Copy to Proxmox host +ssh-copy-id root@proxmox.local + +# Add key to ssh-agent +eval "$(ssh-agent -s)" +ssh-add ~/.ssh/id_ed25519 + +# Verify +ssh-add -L +ssh root@proxmox.local "echo 'SSH works!'" +``` + +**Solution 2 - Use API token only (workaround):** + +If SSH is problematic, you can create the cloud-init snippet manually: + +```bash +# On Proxmox host, create the snippet +nano /var/lib/vz/snippets/cloud-init-docker-host.yaml +# Paste the cloud-init content from main.tf + +# Then remove the proxmox_virtual_environment_file resource from main.tf +# and reference the file directly in the VM resource: +# user_data_file_id = "local:snippets/cloud-init-docker-host.yaml" +``` + ### Template Not Found Error: `template with ID 9000 not found` diff --git a/terraform/proxmox-examples/docker-host/STATE_MANAGEMENT.md b/terraform/proxmox-examples/docker-host/STATE_MANAGEMENT.md new file mode 100644 index 0000000..a1ff79f --- /dev/null +++ b/terraform/proxmox-examples/docker-host/STATE_MANAGEMENT.md @@ -0,0 +1,378 @@ +# Terraform State Management with SOPS + +This project uses [SOPS](https://github.com/getsops/sops) (Secrets OPerationS) with [age](https://github.com/FiloSottile/age) encryption to securely store Terraform state files in Git. + +## Why SOPS + age? + +✅ **Encrypted at rest** - State files contain sensitive data (IPs, tokens) +✅ **Version controlled** - Track infrastructure changes over time +✅ **No infrastructure required** - No need for S3, PostgreSQL, or other backends +✅ **Perfect for homelabs** - Simple, secure, self-contained +✅ **FOSS** - Fully open source tools + +## Prerequisites + +### 1. Install age + +**Debian/Ubuntu:** +```bash +sudo apt update +sudo apt install age +``` + +**macOS:** +```bash +brew install age +``` + +**Manual installation:** +```bash +# Download from https://github.com/FiloSottile/age/releases +wget https://github.com/FiloSottile/age/releases/download/v1.1.1/age-v1.1.1-linux-amd64.tar.gz +tar xzf age-v1.1.1-linux-amd64.tar.gz +sudo mv age/age age/age-keygen /usr/local/bin/ +``` + +### 2. Install SOPS + +**Debian/Ubuntu:** +```bash +# Download from https://github.com/getsops/sops/releases +wget https://github.com/getsops/sops/releases/download/v3.8.1/sops-v3.8.1.linux.amd64 +sudo mv sops-v3.8.1.linux.amd64 /usr/local/bin/sops +sudo chmod +x /usr/local/bin/sops +``` + +**macOS:** +```bash +brew install sops +``` + +Verify installation: +```bash +age --version +sops --version +``` + +## Initial Setup + +### 1. Generate Age Encryption Key + +```bash +# Create SOPS directory +mkdir -p ~/.sops + +# Generate a new age key pair +age-keygen -o ~/.sops/homelab-terraform.txt + +# View the key (you'll need the public key) +cat ~/.sops/homelab-terraform.txt +``` + +Output will look like: +``` +# created: 2025-11-11T12:34:56Z +# public key: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +AGE-SECRET-KEY-1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx +``` + +**⚠️ IMPORTANT:** +- The line starting with `AGE-SECRET-KEY-1` is your **private key** - keep it secret! +- The line starting with `age1` is your **public key** - you'll use this in .sops.yaml +- **Backup this file** to a secure location (password manager, encrypted backup, etc.) +- If you lose this key, you **cannot decrypt** your state files! + +### 2. Configure SOPS + +```bash +cd terraform/proxmox-examples/docker-host + +# Copy the example config +cp .sops.yaml.example .sops.yaml + +# Edit and replace YOUR_AGE_PUBLIC_KEY_HERE with your public key from step 1 +nano .sops.yaml +``` + +Your `.sops.yaml` should look like: +```yaml +creation_rules: + - path_regex: \.tfstate$ + age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + - path_regex: \.secret$ + age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + - path_regex: terraform\.tfvars$ + age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +``` + +### 3. Set Environment Variable (Optional but Recommended) + +```bash +# Add to your ~/.bashrc or ~/.zshrc +echo 'export SOPS_AGE_KEY_FILE=~/.sops/homelab-terraform.txt' >> ~/.bashrc +source ~/.bashrc +``` + +This tells SOPS where to find your private key for decryption. + +## Usage + +### Option A: Automatic Wrapper Script (Recommended) + +Use the `./scripts/tf` wrapper that handles encryption/decryption automatically: + +```bash +# Initialize (first time) +./scripts/tf init + +# Plan changes +./scripts/tf plan + +# Apply changes (automatically encrypts after) +./scripts/tf apply + +# Destroy infrastructure (automatically encrypts after) +./scripts/tf destroy + +# View state +./scripts/tf show +``` + +The wrapper script: +1. Decrypts state files before running +2. Runs your terraform/tofu command +3. Encrypts state files after (if state was modified) + +### Option B: Manual Encryption/Decryption + +If you prefer manual control: + +```bash +# 1. Decrypt state files +./scripts/tf-decrypt + +# 2. Run terraform commands +tofu init +tofu plan +tofu apply + +# 3. Encrypt state files +./scripts/tf-encrypt + +# 4. Commit encrypted files to Git +git add *.enc +git commit -m "Update infrastructure" +git push +``` + +## Workflow Examples + +### First Time Setup + +```bash +cd terraform/proxmox-examples/docker-host + +# 1. Configure your variables +cp terraform.tfvars.example terraform.tfvars +nano terraform.tfvars # Add your API tokens, SSH keys, etc. + +# 2. Initialize Terraform +./scripts/tf init + +# 3. Plan infrastructure +./scripts/tf plan + +# 4. Apply infrastructure +./scripts/tf apply + +# 5. Encrypted state files are automatically created +# terraform.tfstate.enc now exists + +# 6. Commit encrypted state to Git +git add terraform.tfstate.enc .sops.yaml.example +git commit -m "Add encrypted Terraform state" +git push +``` + +### Making Infrastructure Changes + +```bash +# 1. Decrypt, apply changes, re-encrypt (all automatic) +./scripts/tf apply + +# 2. Commit updated encrypted state +git add terraform.tfstate.enc +git commit -m "Update VM configuration" +git push +``` + +### Cloning on a New Machine + +```bash +# 1. Clone the repository +git clone https://github.com/efigueroa/homelab.git +cd homelab/terraform/proxmox-examples/docker-host + +# 2. Copy your age private key to the new machine +# (Securely transfer ~/.sops/homelab-terraform.txt) +mkdir -p ~/.sops +# Copy the key file here + +# 3. Set up SOPS config +cp .sops.yaml.example .sops.yaml +# Edit with your public key + +# 4. Decrypt state +./scripts/tf-decrypt + +# 5. Now you can run terraform commands +./scripts/tf plan +``` + +## Security Best Practices + +### DO ✅ + +- **Backup your age private key** to multiple secure locations +- **Use different keys** for different projects/environments +- **Commit `.sops.yaml.example`** to Git (without your actual key) +- **Commit encrypted `*.enc` files** to Git +- **Use the wrapper script** to avoid forgetting to encrypt + +### DON'T ❌ + +- **Never commit `.sops.yaml`** with your actual key (it's in .gitignore) +- **Never commit unencrypted `.tfstate`** files (they're in .gitignore) +- **Never commit unencrypted `terraform.tfvars`** with secrets +- **Never share your private age key** publicly +- **Don't lose your private key** - you can't decrypt without it! + +## File Structure + +``` +terraform/proxmox-examples/docker-host/ +├── .gitignore # Ignores unencrypted files +├── .sops.yaml # Your SOPS config (NOT in Git) +├── .sops.yaml.example # Template (in Git) +├── terraform.tfstate # Unencrypted state (NOT in Git) +├── terraform.tfstate.enc # Encrypted state (in Git) ✅ +├── terraform.tfvars # Your config with secrets (NOT in Git) +├── terraform.tfvars.enc # Encrypted config (in Git) ✅ +├── terraform.tfvars.example # Template without secrets (in Git) +├── scripts/ +│ ├── tf # Wrapper script +│ ├── tf-encrypt # Manual encrypt +│ └── tf-decrypt # Manual decrypt +└── STATE_MANAGEMENT.md # This file +``` + +## Troubleshooting + +### Error: "no key could decrypt the data" + +**Cause:** SOPS can't find your private key + +**Solution:** +```bash +# Set the key file location +export SOPS_AGE_KEY_FILE=~/.sops/homelab-terraform.txt + +# Or add to ~/.bashrc permanently +echo 'export SOPS_AGE_KEY_FILE=~/.sops/homelab-terraform.txt' >> ~/.bashrc +``` + +### Error: "YOUR_AGE_PUBLIC_KEY_HERE" + +**Cause:** You didn't replace the placeholder in `.sops.yaml` + +**Solution:** +```bash +# Edit .sops.yaml and replace with your actual public key +nano .sops.yaml +``` + +### Error: "failed to get the data key" + +**Cause:** The file was encrypted with a different key + +**Solution:** +- Ensure you're using the same age key that encrypted the file +- If you lost the original key, you'll need to re-create the state by running `tofu import` + +### Accidentally Committed Unencrypted State + +**Solution:** +```bash +# Remove from Git history (DANGEROUS - coordinate with team if not solo) +git filter-branch --force --index-filter \ + 'git rm --cached --ignore-unmatch terraform.tfstate' \ + --prune-empty --tag-name-filter cat -- --all + +# Force push (only if solo or coordinated) +git push origin --force --all +``` + +### Lost Private Key + +**Solution:** +- Restore from your backup (you made a backup, right?) +- If truly lost, you'll need to: + 1. Manually recreate infrastructure or import existing resources + 2. Generate a new age key + 3. Re-encrypt everything with the new key + +## Advanced: Multiple Keys (Team Access) + +If multiple people need access: + +```yaml +# .sops.yaml +creation_rules: + - path_regex: \.tfstate$ + age: >- + age1person1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + age1person2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + age1person3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +``` + +Each person's private key can decrypt the files. + +## Backup Strategy + +### Recommended Backup Locations: + +1. **Password Manager** (1Password, Bitwarden, etc.) + ```bash + # Copy the contents + cat ~/.sops/homelab-terraform.txt + # Store as a secure note in your password manager + ``` + +2. **Encrypted USB Drive** + ```bash + # Copy to encrypted drive + cp ~/.sops/homelab-terraform.txt /media/encrypted-usb/ + ``` + +3. **Encrypted Cloud Storage** + ```bash + # Encrypt with gpg before uploading + gpg -c ~/.sops/homelab-terraform.txt + # Upload homelab-terraform.txt.gpg to cloud + ``` + +## Resources + +- [SOPS Documentation](https://github.com/getsops/sops) +- [age Documentation](https://github.com/FiloSottile/age) +- [Terraform State Security](https://developer.hashicorp.com/terraform/language/state/sensitive-data) +- [OpenTofu Documentation](https://opentofu.org/docs/) + +## Questions? + +Common questions answered in this document: +- ✅ How do I set up SOPS? → See [Initial Setup](#initial-setup) +- ✅ How do I use it daily? → See [Option A: Automatic Wrapper](#option-a-automatic-wrapper-script-recommended) +- ✅ What if I lose my key? → See [Lost Private Key](#lost-private-key) +- ✅ How do I backup my key? → See [Backup Strategy](#backup-strategy) +- ✅ Can multiple people access? → See [Advanced: Multiple Keys](#advanced-multiple-keys-team-access) diff --git a/terraform/proxmox-examples/docker-host/main.tf b/terraform/proxmox-examples/docker-host/main.tf index 6fa0fe3..8232977 100644 --- a/terraform/proxmox-examples/docker-host/main.tf +++ b/terraform/proxmox-examples/docker-host/main.tf @@ -114,7 +114,7 @@ resource "proxmox_virtual_environment_vm" "docker_host" { # Cloud-init user data for Docker installation resource "proxmox_virtual_environment_file" "cloud_init_user_data" { content_type = "snippets" - datastore_id = "local" + datastore_id = var.snippets_storage node_name = var.proxmox_node source_raw { diff --git a/terraform/proxmox-examples/docker-host/scripts/tf b/terraform/proxmox-examples/docker-host/scripts/tf new file mode 100755 index 0000000..0c8c131 --- /dev/null +++ b/terraform/proxmox-examples/docker-host/scripts/tf @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# +# tf - Wrapper for OpenTofu/Terraform with automatic SOPS encryption/decryption +# +# Usage: +# ./scripts/tf init +# ./scripts/tf plan +# ./scripts/tf apply +# ./scripts/tf destroy +# +# This script automatically: +# 1. Decrypts state before running tofu commands +# 2. Runs your tofu command +# 3. Encrypts state after running tofu commands +# + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TF_DIR="$(dirname "$SCRIPT_DIR")" + +cd "$TF_DIR" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +error() { + echo -e "${RED}ERROR: $1${NC}" >&2 + exit 1 +} + +success() { + echo -e "${GREEN}✓ $1${NC}" +} + +info() { + echo -e "${BLUE}ℹ $1${NC}" +} + +# Check if tofu or terraform is installed +if command -v tofu &> /dev/null; then + TF_CMD="tofu" +elif command -v terraform &> /dev/null; then + TF_CMD="terraform" +else + error "Neither tofu nor terraform is installed" +fi + +# Decrypt state if encrypted files exist +if [[ -f terraform.tfstate.enc || -f terraform.tfvars.enc ]]; then + info "Decrypting state files..." + "$SCRIPT_DIR/tf-decrypt" + echo +fi + +# Run the terraform/tofu command +echo -e "${BLUE}Running: $TF_CMD $*${NC}" +echo +$TF_CMD "$@" +TF_EXIT_CODE=$? + +# If the command succeeded and modified state, encrypt it +if [[ $TF_EXIT_CODE -eq 0 ]]; then + # Commands that modify state + if [[ "$1" =~ ^(apply|destroy|import|refresh|state)$ ]]; then + echo + info "Encrypting state files..." + "$SCRIPT_DIR/tf-encrypt" + fi +fi + +exit $TF_EXIT_CODE diff --git a/terraform/proxmox-examples/docker-host/scripts/tf-decrypt b/terraform/proxmox-examples/docker-host/scripts/tf-decrypt new file mode 100755 index 0000000..48da7b1 --- /dev/null +++ b/terraform/proxmox-examples/docker-host/scripts/tf-decrypt @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# +# tf-decrypt - Decrypt Terraform state and tfvars files with SOPS +# +# Usage: ./scripts/tf-decrypt +# + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TF_DIR="$(dirname "$SCRIPT_DIR")" + +cd "$TF_DIR" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +error() { + echo -e "${RED}ERROR: $1${NC}" >&2 + exit 1 +} + +success() { + echo -e "${GREEN}✓ $1${NC}" +} + +warn() { + echo -e "${YELLOW}⚠ $1${NC}" +} + +# Check if SOPS is installed +if ! command -v sops &> /dev/null; then + error "sops is not installed. Install it from: https://github.com/getsops/sops/releases" +fi + +# Check if .sops.yaml exists +if [[ ! -f .sops.yaml ]]; then + error ".sops.yaml not found. Copy .sops.yaml.example and configure your age key." +fi + +# Check if SOPS_AGE_KEY_FILE is set or exists in default location +if [[ -z "${SOPS_AGE_KEY_FILE:-}" ]]; then + if [[ -f ~/.sops/homelab-terraform.txt ]]; then + export SOPS_AGE_KEY_FILE=~/.sops/homelab-terraform.txt + else + warn "SOPS_AGE_KEY_FILE not set. Trying default age identities..." + fi +fi + +echo "🔓 Decrypting Terraform files..." +echo + +# Decrypt terraform.tfstate.enc if it exists +if [[ -f terraform.tfstate.enc ]]; then + echo "Decrypting terraform.tfstate.enc..." + sops -d terraform.tfstate.enc > terraform.tfstate + success "terraform.tfstate.enc → terraform.tfstate" +else + warn "terraform.tfstate.enc not found (this is normal for first-time setup)" +fi + +# Decrypt terraform.tfvars.enc if it exists +if [[ -f terraform.tfvars.enc ]]; then + echo "Decrypting terraform.tfvars.enc..." + sops -d terraform.tfvars.enc > terraform.tfvars + success "terraform.tfvars.enc → terraform.tfvars" +else + warn "terraform.tfvars.enc not found" +fi + +# Decrypt backup state files if they exist +for backup_enc in terraform.tfstate.backup.enc terraform.tfstate.*.backup.enc; do + if [[ -f "$backup_enc" ]]; then + backup="${backup_enc%.enc}" + echo "Decrypting $backup_enc..." + sops -d "$backup_enc" > "$backup" + success "$backup_enc → $backup" + fi +done + +echo +success "All Terraform files decrypted successfully!" +echo +warn "Remember to encrypt files after making changes: ./scripts/tf-encrypt" diff --git a/terraform/proxmox-examples/docker-host/scripts/tf-encrypt b/terraform/proxmox-examples/docker-host/scripts/tf-encrypt new file mode 100755 index 0000000..0d5855a --- /dev/null +++ b/terraform/proxmox-examples/docker-host/scripts/tf-encrypt @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# +# tf-encrypt - Encrypt Terraform state and tfvars files with SOPS +# +# Usage: ./scripts/tf-encrypt +# + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TF_DIR="$(dirname "$SCRIPT_DIR")" + +cd "$TF_DIR" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +error() { + echo -e "${RED}ERROR: $1${NC}" >&2 + exit 1 +} + +success() { + echo -e "${GREEN}✓ $1${NC}" +} + +warn() { + echo -e "${YELLOW}⚠ $1${NC}" +} + +# Check if SOPS is installed +if ! command -v sops &> /dev/null; then + error "sops is not installed. Install it from: https://github.com/getsops/sops/releases" +fi + +# Check if .sops.yaml exists +if [[ ! -f .sops.yaml ]]; then + error ".sops.yaml not found. Copy .sops.yaml.example and configure your age key." +fi + +# Check if age key placeholder is still present +if grep -q "YOUR_AGE_PUBLIC_KEY_HERE" .sops.yaml; then + error ".sops.yaml contains placeholder. Replace YOUR_AGE_PUBLIC_KEY_HERE with your actual age public key." +fi + +echo "🔐 Encrypting Terraform files..." +echo + +# Encrypt terraform.tfstate if it exists +if [[ -f terraform.tfstate ]]; then + echo "Encrypting terraform.tfstate..." + sops -e terraform.tfstate > terraform.tfstate.enc + success "terraform.tfstate → terraform.tfstate.enc" + + # Securely delete unencrypted state + shred -u terraform.tfstate 2>/dev/null || rm -f terraform.tfstate + success "Deleted unencrypted terraform.tfstate" +else + warn "terraform.tfstate not found (this is normal if you haven't run 'tofu apply' yet)" +fi + +# Encrypt terraform.tfvars if it exists +if [[ -f terraform.tfvars ]]; then + echo "Encrypting terraform.tfvars..." + sops -e terraform.tfvars > terraform.tfvars.enc + success "terraform.tfvars → terraform.tfvars.enc" + + # Keep original tfvars (don't delete, just warn) + warn "Remember to not commit unencrypted terraform.tfvars to Git" +else + warn "terraform.tfvars not found" +fi + +# Encrypt backup state files if they exist +for backup in terraform.tfstate.backup terraform.tfstate.*.backup; do + if [[ -f "$backup" ]]; then + echo "Encrypting $backup..." + sops -e "$backup" > "${backup}.enc" + success "$backup → ${backup}.enc" + shred -u "$backup" 2>/dev/null || rm -f "$backup" + success "Deleted unencrypted $backup" + fi +done + +echo +success "All Terraform files encrypted successfully!" +echo +echo "Next steps:" +echo " 1. git add *.enc" +echo " 2. git commit -m 'Update encrypted Terraform state'" +echo " 3. git push" diff --git a/terraform/proxmox-examples/docker-host/terraform.tfvars.example b/terraform/proxmox-examples/docker-host/terraform.tfvars.example index 533cdb3..879a934 100644 --- a/terraform/proxmox-examples/docker-host/terraform.tfvars.example +++ b/terraform/proxmox-examples/docker-host/terraform.tfvars.example @@ -8,6 +8,12 @@ pm_tls_insecure = true proxmox_node = "pve" template_vm_id = 9000 storage = "local-lvm" + +# Storage for cloud-init snippets (must support 'snippets' content type) +# Common options: "local" (directory storage), or create a directory storage +# See README.md for setup instructions if you get datastore errors +snippets_storage = "local" + network_bridge = "vmbr0" # VM Configuration diff --git a/terraform/proxmox-examples/docker-host/variables.tf b/terraform/proxmox-examples/docker-host/variables.tf index 35e91d0..ce6c0f7 100644 --- a/terraform/proxmox-examples/docker-host/variables.tf +++ b/terraform/proxmox-examples/docker-host/variables.tf @@ -59,11 +59,17 @@ variable "disk_size" { } variable "storage" { - description = "Storage pool name" + description = "Storage pool name for VM disks" type = string default = "local-lvm" } +variable "snippets_storage" { + description = "Storage pool name for cloud-init snippets (must support 'snippets' content type)" + type = string + default = "local" +} + variable "network_bridge" { description = "Network bridge" type = string