diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a0eba1a..da7c9b8 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -40,6 +40,48 @@ We are migrating the tracker to a new infrastructure on Hetzner, involving: - Migrating the database from SQLite to MySQL - Implementing Infrastructure as Code for reproducible deployments +## ๐Ÿ—๏ธ Twelve-Factor Architecture + +This project implements a complete twelve-factor app architecture with clear separation between infrastructure provisioning and application deployment: + +### Current Working Architecture (Twelve-Factor Compliant) + +```text +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Configuration Management โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ€ข Environment Templates (local.env.tpl, production.env.tpl) โ”‚ +โ”‚ โ€ข Configuration Processing (configure-env.sh) โ”‚ +โ”‚ โ€ข Template Rendering (.tpl โ†’ actual configs) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Infrastructure Layer โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ€ข VM Provisioning (provision-infrastructure.sh) โ”‚ +โ”‚ โ€ข Environment-specific Setup (templated cloud-init) โ”‚ +โ”‚ โ€ข Provider Abstraction (local implemented, cloud planned) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Application Layer โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ€ข Environment-aware Deployment (templated configs) โ”‚ +โ”‚ โ€ข Dynamic Service Configuration โ”‚ +โ”‚ โ€ข Comprehensive Health Validation โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Key Implementation Features + +- **Complete Twelve-Factor Compliance**: All 12 factors implemented and operational +- **Infrastructure/Application Separation**: Clean separation with `make infra-apply` and `make app-deploy` +- **Environment-based Configuration**: Template system with environment switching +- **Build/Release/Run Stages**: Proper separation of configuration processing, deployment, and execution +- **Multi-environment Support**: Local development with production parity + ## ๐Ÿ“ Repository Structure ```text diff --git a/Makefile.backup b/Makefile.backup deleted file mode 100644 index 33a3396..0000000 --- a/Makefile.backup +++ /dev/null @@ -1,536 +0,0 @@ -# Makefile for Torrust Tracker Demo - Twelve-Factor App Deployment -.PHONY: help install-deps lint test clean -.PHONY: infra-init infra-plan infra-apply infra-destroy infra-status infra-refresh-state -.PHONY: app-deploy app-redeploy health-check -.PHONY: ssh console vm-console -.PHONY: configure-local configure-production validate-config - -# Default variables -VM_NAME ?= torrust-tracker-demo -ENVIRONMENT ?= local -TERRAFORM_DIR = infrastructure/terraform -TESTS_DIR = infrastructure/tests -SCRIPTS_DIR = infrastructure/scripts - -# Help target -help: ## Show this help message - @echo "Torrust Tracker Demo - Twelve-Factor App Deployment" - @echo "" - @echo "=== TWELVE-FACTOR DEPLOYMENT WORKFLOW ===" - @echo " 1. infra-apply - Provision infrastructure (Build stage)" - @echo " 2. app-deploy - Deploy application (Release + Run stages)" - @echo " 3. health-check - Validate deployment" - @echo "" - @echo "Available targets:" - @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST) - @echo "" - @echo "Examples:" - @echo " make infra-apply ENVIRONMENT=local" - @echo " make app-deploy ENVIRONMENT=local" - @echo " make health-check ENVIRONMENT=local" - -install-deps: ## Install required dependencies (Ubuntu/Debian) - @echo "Installing dependencies..." - sudo apt update - sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager virt-viewer genisoimage - sudo usermod -aG libvirt $$USER - sudo usermod -aG kvm $$USER - @echo "Dependencies installed. Please log out and log back in for group changes to take effect." - -# ============================================================================= -# TWELVE-FACTOR INFRASTRUCTURE TARGETS (BUILD STAGE) -# ============================================================================= - -infra-init: ## Initialize infrastructure (Terraform init) - @echo "Initializing infrastructure for $(ENVIRONMENT)..." - $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) init - -infra-plan: ## Plan infrastructure changes - @echo "Planning infrastructure for $(ENVIRONMENT)..." - $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) plan - -infra-apply: ## Provision infrastructure (Twelve-Factor Build stage) - @echo "Provisioning infrastructure for $(ENVIRONMENT)..." - $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) apply - -infra-destroy: ## Destroy infrastructure - @echo "Destroying infrastructure for $(ENVIRONMENT)..." - $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) destroy - -infra-status: ## Show infrastructure status - @echo "Infrastructure status for $(ENVIRONMENT):" - @cd $(TERRAFORM_DIR) && tofu show -no-color | grep -E "(vm_ip|vm_status)" || echo "No infrastructure found" - -infra-refresh-state: ## Refresh Terraform state to detect IP changes - @echo "Refreshing Terraform state..." - @cd $(TERRAFORM_DIR) && tofu refresh -auto-approve - -# ============================================================================= -# TWELVE-FACTOR APPLICATION TARGETS (RELEASE + RUN STAGES) -# ============================================================================= - -app-deploy: ## Deploy application (Twelve-Factor Release + Run stages) - @echo "Deploying application for $(ENVIRONMENT)..." - $(SCRIPTS_DIR)/deploy-app.sh $(ENVIRONMENT) - -app-redeploy: ## Redeploy application without infrastructure changes - @echo "Redeploying application for $(ENVIRONMENT)..." - $(SCRIPTS_DIR)/deploy-app.sh $(ENVIRONMENT) - -health-check: ## Validate deployment health - @echo "Running health check for $(ENVIRONMENT)..." - $(SCRIPTS_DIR)/health-check.sh $(ENVIRONMENT) - -# ============================================================================= -# VM ACCESS AND DEBUGGING -# ============================================================================= - -ssh: ## SSH into the VM - @VM_IP=$$(cd $(TERRAFORM_DIR) && tofu output -raw vm_ip 2>/dev/null) && \ - if [ -n "$$VM_IP" ] && [ "$$VM_IP" != "No IP assigned yet" ]; then \ - echo "Connecting to VM: $$VM_IP"; \ - ssh -o StrictHostKeyChecking=no torrust@$$VM_IP; \ - else \ - echo "Error: VM IP not available. Run 'make infra-status' to check infrastructure."; \ - exit 1; \ - fi - -console: ## Access VM console (text-based) - @echo "Accessing VM console..." - @virsh console $(VM_NAME) || echo "VM console not accessible. Try 'make vm-console' for graphical console." - -vm-console: ## Access VM graphical console (requires GUI) - @echo "Opening graphical VM console..." - @virt-viewer --connect qemu:///system $(VM_NAME) & - -# ============================================================================= -# CONFIGURATION MANAGEMENT -# ============================================================================= - -configure-local: ## Generate local environment configuration - @echo "Configuring local environment..." - $(SCRIPTS_DIR)/configure-env.sh local - -configure-production: ## Generate production environment configuration - @echo "Configuring production environment..." - $(SCRIPTS_DIR)/configure-env.sh production - -validate-config: ## Validate configuration for all environments - @echo "Validating configuration..." - $(SCRIPTS_DIR)/validate-config.sh - -# ============================================================================= -# TESTING AND QUALITY ASSURANCE -# ============================================================================= - -test: ## Run comprehensive test suite - @echo "Running comprehensive test suite..." - $(TESTS_DIR)/test-local-setup.sh - -test-syntax: ## Run syntax validation only - @echo "Running syntax validation..." - ./scripts/lint.sh - -lint: test-syntax ## Run all linting (alias for test-syntax) - -clean: ## Clean up temporary files and caches - @echo "Cleaning up..." - @rm -rf $(TERRAFORM_DIR)/.terraform - @rm -f $(TERRAFORM_DIR)/terraform.tfstate.backup - @echo "Clean completed" - -# ============================================================================= -# LEGACY COMPATIBILITY (DEPRECATED) -# ============================================================================= - -# These targets are maintained for backward compatibility but are deprecated -# Use the twelve-factor targets above instead - -init: infra-init ## [DEPRECATED] Use infra-init instead - @echo "โš ๏ธ DEPRECATED: Use 'make infra-init' instead" - -plan: infra-plan ## [DEPRECATED] Use infra-plan instead - @echo "โš ๏ธ DEPRECATED: Use 'make infra-plan' instead" - -apply: ## [DEPRECATED] Use infra-apply + app-deploy instead - @echo "โš ๏ธ DEPRECATED: This target combines infrastructure and application deployment" - @echo " For twelve-factor compliance, use:" - @echo " 1. make infra-apply ENVIRONMENT=$(ENVIRONMENT)" - @echo " 2. make app-deploy ENVIRONMENT=$(ENVIRONMENT)" - @echo "" - @echo "Proceeding with legacy deployment..." - @make infra-apply ENVIRONMENT=$(ENVIRONMENT) - @make app-deploy ENVIRONMENT=$(ENVIRONMENT) - -destroy: infra-destroy ## [DEPRECATED] Use infra-destroy instead - @echo "โš ๏ธ DEPRECATED: Use 'make infra-destroy' instead" - -status: infra-status ## [DEPRECATED] Use infra-status instead - @echo "โš ๏ธ DEPRECATED: Use 'make infra-status' instead" - -refresh-state: infra-refresh-state ## [DEPRECATED] Use infra-refresh-state instead - @echo "โš ๏ธ DEPRECATED: Use 'make infra-refresh-state' instead" - @echo "Fixing permissions after deployment..." - @$(MAKE) fix-libvirt - -apply: ## Deploy the VM - @echo "Ensuring libvirt permissions are correct..." - @$(MAKE) fix-libvirt - @echo "Deploying VM..." - @if [ -f $(TERRAFORM_DIR)/local.tfvars ]; then \ - echo "Using local SSH key configuration..."; \ - cd $(TERRAFORM_DIR) && tofu apply -var-file="local.tfvars" -parallelism=1 -auto-approve; \ - else \ - echo "WARNING: No local.tfvars found. Creating with placeholder..."; \ - echo 'ssh_public_key = "REPLACE_WITH_YOUR_SSH_PUBLIC_KEY"' > $(TERRAFORM_DIR)/local.tfvars; \ - echo "Please edit $(TERRAFORM_DIR)/local.tfvars with your SSH public key and run 'make apply' again"; \ - exit 1; \ - fi - @echo "Fixing permissions after deployment..." - @$(MAKE) fix-libvirt - -destroy: ## Destroy the VM - @echo "Destroying VM..." - cd $(TERRAFORM_DIR) && tofu destroy -auto-approve - -status: ## Show current infrastructure status - @echo "Infrastructure status:" - cd $(TERRAFORM_DIR) && tofu show - -refresh-state: ## Refresh Terraform state to detect IP changes - @echo "Refreshing Terraform state..." - cd $(TERRAFORM_DIR) && tofu refresh - @echo "Updated outputs:" - cd $(TERRAFORM_DIR) && tofu output - -ssh: ## SSH into the VM - @echo "Connecting to VM..." - @VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ - if [ -n "$$VM_IP" ]; then \ - echo "Connecting to $$VM_IP..."; \ - ssh torrust@$$VM_IP; \ - else \ - echo "Could not get VM IP. Is the VM deployed?"; \ - exit 1; \ - fi - -test: ## Run all tests - @echo "Running infrastructure tests..." - $(TESTS_DIR)/test-local-setup.sh full-test - -test-prereq: ## Test prerequisites only - @echo "Testing prerequisites..." - $(TESTS_DIR)/test-local-setup.sh prerequisites - -check-libvirt: ## Check libvirt installation and permissions - @echo "Checking libvirt setup..." - @echo "1. Checking if libvirt service is running:" - @sudo systemctl status libvirtd --no-pager -l || echo "libvirtd not running" - @echo "" - @echo "2. Checking user groups:" - @groups | grep -q libvirt && echo "โœ“ User is in libvirt group" || echo "โœ— User is NOT in libvirt group" - @groups | grep -q kvm && echo "โœ“ User is in kvm group" || echo "โœ— User is NOT in kvm group" - @echo "" - @echo "3. Testing libvirt access:" - @virsh list --all >/dev/null 2>&1 && echo "โœ“ User can access libvirt" || echo "โœ— User cannot access libvirt (try 'sudo virsh list')" - @echo "" - @echo "4. Checking default network:" - @virsh net-list --all 2>/dev/null | grep -q default && echo "โœ“ Default network exists" || echo "โœ— Default network missing" - @echo "" - @echo "5. Checking KVM support:" - @test -r /dev/kvm && echo "โœ“ KVM device accessible" || echo "โœ— KVM device not accessible" - @echo "" - @echo "If you see any โœ— marks, run 'make fix-libvirt' to attempt fixes" - -fix-libvirt: ## Fix common libvirt permission issues - @echo "Setting up user-friendly libvirt configuration..." - @infrastructure/scripts/setup-user-libvirt.sh - @echo "Attempting to fix libvirt permissions..." - @echo "Adding user to required groups..." - sudo usermod -aG libvirt $$USER - sudo usermod -aG kvm $$USER - @echo "Starting libvirt service..." - sudo systemctl enable libvirtd - sudo systemctl start libvirtd - @echo "Checking if default network needs to be started..." - @sudo virsh net-list --all | grep -q "default.*inactive" && sudo virsh net-start default || true - @sudo virsh net-autostart default 2>/dev/null || true - @echo "" - @echo "โœ“ Fix attempt completed!" - @echo "IMPORTANT: You need to log out and log back in (or run 'newgrp libvirt') for group changes to take effect" - @echo "Then run 'make check-libvirt' to verify the fixes worked" - -test-syntax: ## Test configuration syntax only - @echo "Testing configuration syntax..." - $(TESTS_DIR)/test-local-setup.sh syntax - -lint: ## Run all linting checks (yamllint, shellcheck, markdownlint) - @echo "Running linting checks..." - ./scripts/lint.sh - -lint-yaml: ## Run only yamllint - @echo "Running yamllint..." - ./scripts/lint.sh --yaml - -lint-shell: ## Run only shellcheck - @echo "Running shellcheck..." - ./scripts/lint.sh --shell - -lint-markdown: ## Run only markdownlint - @echo "Running markdownlint..." - ./scripts/lint.sh --markdown - -test-integration: ## Run integration tests (requires deployed VM) - @echo "Running integration tests..." - $(TESTS_DIR)/test-integration.sh full-test - -deploy-test: ## Deploy VM for testing (without cleanup) - @echo "Deploying test VM..." - $(TESTS_DIR)/test-local-setup.sh deploy - -clean: ## Clean up temporary files - @echo "Cleaning up..." - rm -f $(TERRAFORM_DIR)/.terraform.lock.hcl - rm -f $(TERRAFORM_DIR)/terraform.tfstate.backup - rm -f install-opentofu.sh - rm -f /tmp/torrust-infrastructure-test.log - -clean-and-fix: ## Clean up all VMs and fix libvirt permissions - @echo "Cleaning up VMs and fixing permissions..." - @echo "1. Stopping and undefining any existing VMs:" - @for vm in $$(virsh list --all --name 2>/dev/null | grep -v '^$$'); do \ - echo " Cleaning up VM: $$vm"; \ - virsh destroy $$vm 2>/dev/null || true; \ - virsh undefine $$vm 2>/dev/null || true; \ - done - @echo "2. Removing OpenTofu state:" - @cd $(TERRAFORM_DIR) && rm -f terraform.tfstate terraform.tfstate.backup .terraform.lock.hcl 2>/dev/null || true - @echo "3. Cleaning libvirt images:" - @sudo rm -f /var/lib/libvirt/images/torrust-tracker-demo* /var/lib/libvirt/images/ubuntu-24.04-base.qcow2 2>/dev/null || true - @echo "4. Cleaning application storage (generated configuration files):" - @if [ -d "application/storage" ]; then \ - echo " WARNING: This will delete all generated configuration files in application/storage/"; \ - echo " This includes nginx configs, tracker configs, and any cached data."; \ - echo " These files will be regenerated when you run 'make configure-local'."; \ - read -p " Do you want to delete application/storage? (y/N): " confirm; \ - if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \ - echo " Removing application/storage..."; \ - rm -rf application/storage; \ - echo " โœ“ Application storage cleaned"; \ - else \ - echo " Skipping application/storage cleanup"; \ - fi; \ - else \ - echo " No application/storage directory found"; \ - fi - @echo "5. Fixing libvirt setup:" - @$(MAKE) fix-libvirt - @echo "โœ“ Clean up complete. You can now run 'make apply' safely." - -# New target for setting up SSH key -setup-ssh-key: ## Setup local SSH key configuration - @if [ -f $(TERRAFORM_DIR)/local.tfvars ]; then \ - echo "Local SSH configuration already exists at $(TERRAFORM_DIR)/local.tfvars"; \ - echo "Current configuration:"; \ - cat $(TERRAFORM_DIR)/local.tfvars; \ - else \ - echo "Creating local SSH key configuration..."; \ - echo 'ssh_public_key = "REPLACE_WITH_YOUR_SSH_PUBLIC_KEY"' > $(TERRAFORM_DIR)/local.tfvars; \ - echo ""; \ - echo "โœ“ Created $(TERRAFORM_DIR)/local.tfvars"; \ - echo ""; \ - echo "Next steps:"; \ - echo "1. Get your SSH public key:"; \ - echo " cat ~/.ssh/id_rsa.pub"; \ - echo " # or cat ~/.ssh/id_ed25519.pub"; \ - echo ""; \ - echo "2. Edit the file and replace the placeholder:"; \ - echo " vim $(TERRAFORM_DIR)/local.tfvars"; \ - echo ""; \ - echo "3. Deploy the VM:"; \ - echo " make apply"; \ - fi - -restart-and-monitor: ## Destroy, deploy fresh, and monitor cloud-init - @echo "๐Ÿ”„ Complete restart: destroying existing VM..." - @$(MAKE) destroy || true - @echo "๐Ÿš€ Deploying fresh VM..." - @$(MAKE) apply & - @echo "โณ Waiting 10 seconds for VM to start..." - @sleep 10 - @echo "๐Ÿ“ก Starting cloud-init monitoring..." - @$(MAKE) monitor-cloud-init - -fresh-start: restart-and-monitor ## Alias for restart-and-monitor - -# Development targets -dev-setup: install-deps init fix-libvirt setup-ssh-key ## Complete development setup - @echo "Development environment setup complete!" - @echo "Next steps:" - @echo "1. Log out and log back in for group changes" - @echo "2. Edit $(TERRAFORM_DIR)/local.tfvars with your SSH public key" - @echo "3. Run 'make test-prereq' to verify setup" - @echo "4. Run 'make apply' to deploy a VM" - -quick-test: test-prereq test-syntax ## Quick test without VM deployment - @echo "Quick tests completed!" - -# Help for specific workflows -workflow-help: ## Show common workflows - @echo "Common workflows:" - @echo "" - @echo "1. First-time setup:" - @echo " make dev-setup" - @echo " # Log out and log back in" - @echo " # Edit infrastructure/cloud-init/user-data.yaml to add your SSH key" - @echo " make test-prereq" - @echo "" - @echo "2. Deploy and test:" - @echo " make apply" - @echo " make ssh" - @echo " make destroy" - @echo "" - @echo "3. Run full test suite:" - @echo " make test" - @echo "" - @echo "4. Run integration tests:" - @echo " make apply" - @echo " make test-integration" - @echo " make destroy" - @echo "" - @echo "5. Development cycle:" - @echo " make plan # Review changes" - @echo " make apply # Deploy" - @echo " make ssh # Test manually" - @echo " make destroy # Clean up" - -monitor-cloud-init: ## Monitor cloud-init progress in real-time - @echo "Monitoring cloud-init progress..." - @./infrastructure/scripts/monitor-cloud-init.sh - -vm-restart: ## Restart the VM - @echo "Restarting VM..." - virsh shutdown $(VM_NAME) - @echo "Waiting for shutdown..." - @sleep 5 - virsh start $(VM_NAME) - @echo "VM restarted" - -# CI/CD specific targets -ci-test-syntax: ## Test syntax for CI (with dummy values) - @echo "Testing syntax for CI environment..." - @echo "Creating temporary config with dummy values..." - @cd $(TERRAFORM_DIR) && \ - echo 'ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC dummy-key-for-ci-testing"' > ci-test.tfvars && \ - tofu init && \ - tofu validate && \ - rm ci-test.tfvars - @echo "Testing cloud-init templates..." - @CI=true $(TESTS_DIR)/test-local-setup.sh syntax - @echo "Testing cloud-init YAML syntax with yamllint..." - @if command -v yamllint >/dev/null 2>&1; then \ - yamllint -c .yamllint-ci.yml infrastructure/cloud-init/network-config.yaml && \ - yamllint -c .yamllint-ci.yml infrastructure/cloud-init/meta-data.yaml && \ - cd infrastructure/cloud-init && \ - sed 's/$${ssh_public_key}/ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC/' user-data.yaml.tpl > /tmp/user-data-test.yaml && \ - sed 's/$${ssh_public_key}/ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC/' user-data-minimal.yaml.tpl > /tmp/user-data-minimal-test.yaml && \ - yamllint -c ../../.yamllint-ci.yml /tmp/user-data-test.yaml && \ - yamllint -c ../../.yamllint-ci.yml /tmp/user-data-minimal-test.yaml && \ - rm -f /tmp/user-data-test.yaml /tmp/user-data-minimal-test.yaml; \ - else \ - echo "yamllint not available, skipping additional YAML validation"; \ - fi - -vm-ip: ## Show VM IP address - @echo "Getting VM IP address..." - @VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ - if [ -n "$$VM_IP" ]; then \ - echo "VM IP: $$VM_IP"; \ - else \ - echo "VM IP not assigned yet or VM not running"; \ - echo "VM status:"; \ - virsh list --all | grep $(VM_NAME) || echo "VM not found"; \ - fi - -vm-info: ## Show detailed VM network information - @echo "VM Network Information:" - @echo "======================" - @virsh list --all | grep $(VM_NAME) | head -1 || echo "VM not found" - @echo "" - @echo "Network interfaces:" - @virsh domifaddr $(VM_NAME) 2>/dev/null || echo "No network information available" - @echo "" - @echo "DHCP leases:" - @virsh net-dhcp-leases default 2>/dev/null | grep $(VM_NAME) || echo "No DHCP lease found" - -console: ## Access VM console (text-based) - @echo "Connecting to VM console..." - @echo "Use Ctrl+] to exit console" - @virsh console $(VM_NAME) - -vm-console: ## Access VM graphical console (GUI) - @echo "Opening VM graphical console..." - @if command -v virt-viewer >/dev/null 2>&1; then \ - virt-viewer $(VM_NAME) || virt-viewer spice://127.0.0.1:5900; \ - else \ - echo "virt-viewer not found. Please install it:"; \ - echo " sudo apt install virt-viewer"; \ - fi - -# Configuration Management Targets -configure-local: ## Generate local environment configuration - @echo "Generating local environment configuration..." - @infrastructure/scripts/configure-env.sh local - -configure-production: ## Generate production environment configuration (requires secrets) - @echo "Generating production environment configuration..." - @infrastructure/scripts/configure-env.sh production - -validate-config: ## Validate generated configuration files - @echo "Validating configuration files..." - @infrastructure/scripts/validate-config.sh local - -validate-config-production: ## Validate production configuration files - @echo "Validating production configuration files..." - @infrastructure/scripts/validate-config.sh production - -# Deployment workflow targets -deploy-local: configure-local ## Deploy VM and configure for local environment - @echo "Deploying local environment..." - @$(MAKE) apply - @echo "Waiting for VM to be ready..." - @sleep 30 - @echo "Starting application services..." - @$(MAKE) start-services - -deploy-production: configure-production ## Deploy and configure for production environment (requires secrets) - @echo "Deploying production environment..." - @$(MAKE) apply - @echo "Waiting for VM to be ready..." - @sleep 30 - @echo "Starting application services..." - @$(MAKE) start-services - -start-services: ## Start Docker Compose services in the VM - @echo "Starting Docker Compose services..." - @VM_IP=$$(cd $(TERRAFORM_DIR) && tofu output -raw vm_ip 2>/dev/null) || \ - VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ - if [ -n "$$VM_IP" ]; then \ - echo "Starting services on $$VM_IP..."; \ - ssh -o StrictHostKeyChecking=no torrust@$$VM_IP 'cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose up -d'; \ - else \ - echo "Could not get VM IP. Is the VM deployed?"; \ - exit 1; \ - fi - -stop-services: ## Stop Docker Compose services in the VM - @echo "Stopping Docker Compose services..." - @VM_IP=$$(cd $(TERRAFORM_DIR) && tofu output -raw vm_ip 2>/dev/null) || \ - VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ - if [ -n "$$VM_IP" ]; then \ - echo "Stopping services on $$VM_IP..."; \ - ssh -o StrictHostKeyChecking=no torrust@$$VM_IP 'cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose down'; \ - else \ - echo "Could not get VM IP. Is the VM deployed?"; \ - exit 1; \ - fi diff --git a/Makefile.old b/Makefile.old deleted file mode 100644 index 33a3396..0000000 --- a/Makefile.old +++ /dev/null @@ -1,536 +0,0 @@ -# Makefile for Torrust Tracker Demo - Twelve-Factor App Deployment -.PHONY: help install-deps lint test clean -.PHONY: infra-init infra-plan infra-apply infra-destroy infra-status infra-refresh-state -.PHONY: app-deploy app-redeploy health-check -.PHONY: ssh console vm-console -.PHONY: configure-local configure-production validate-config - -# Default variables -VM_NAME ?= torrust-tracker-demo -ENVIRONMENT ?= local -TERRAFORM_DIR = infrastructure/terraform -TESTS_DIR = infrastructure/tests -SCRIPTS_DIR = infrastructure/scripts - -# Help target -help: ## Show this help message - @echo "Torrust Tracker Demo - Twelve-Factor App Deployment" - @echo "" - @echo "=== TWELVE-FACTOR DEPLOYMENT WORKFLOW ===" - @echo " 1. infra-apply - Provision infrastructure (Build stage)" - @echo " 2. app-deploy - Deploy application (Release + Run stages)" - @echo " 3. health-check - Validate deployment" - @echo "" - @echo "Available targets:" - @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST) - @echo "" - @echo "Examples:" - @echo " make infra-apply ENVIRONMENT=local" - @echo " make app-deploy ENVIRONMENT=local" - @echo " make health-check ENVIRONMENT=local" - -install-deps: ## Install required dependencies (Ubuntu/Debian) - @echo "Installing dependencies..." - sudo apt update - sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager virt-viewer genisoimage - sudo usermod -aG libvirt $$USER - sudo usermod -aG kvm $$USER - @echo "Dependencies installed. Please log out and log back in for group changes to take effect." - -# ============================================================================= -# TWELVE-FACTOR INFRASTRUCTURE TARGETS (BUILD STAGE) -# ============================================================================= - -infra-init: ## Initialize infrastructure (Terraform init) - @echo "Initializing infrastructure for $(ENVIRONMENT)..." - $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) init - -infra-plan: ## Plan infrastructure changes - @echo "Planning infrastructure for $(ENVIRONMENT)..." - $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) plan - -infra-apply: ## Provision infrastructure (Twelve-Factor Build stage) - @echo "Provisioning infrastructure for $(ENVIRONMENT)..." - $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) apply - -infra-destroy: ## Destroy infrastructure - @echo "Destroying infrastructure for $(ENVIRONMENT)..." - $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) destroy - -infra-status: ## Show infrastructure status - @echo "Infrastructure status for $(ENVIRONMENT):" - @cd $(TERRAFORM_DIR) && tofu show -no-color | grep -E "(vm_ip|vm_status)" || echo "No infrastructure found" - -infra-refresh-state: ## Refresh Terraform state to detect IP changes - @echo "Refreshing Terraform state..." - @cd $(TERRAFORM_DIR) && tofu refresh -auto-approve - -# ============================================================================= -# TWELVE-FACTOR APPLICATION TARGETS (RELEASE + RUN STAGES) -# ============================================================================= - -app-deploy: ## Deploy application (Twelve-Factor Release + Run stages) - @echo "Deploying application for $(ENVIRONMENT)..." - $(SCRIPTS_DIR)/deploy-app.sh $(ENVIRONMENT) - -app-redeploy: ## Redeploy application without infrastructure changes - @echo "Redeploying application for $(ENVIRONMENT)..." - $(SCRIPTS_DIR)/deploy-app.sh $(ENVIRONMENT) - -health-check: ## Validate deployment health - @echo "Running health check for $(ENVIRONMENT)..." - $(SCRIPTS_DIR)/health-check.sh $(ENVIRONMENT) - -# ============================================================================= -# VM ACCESS AND DEBUGGING -# ============================================================================= - -ssh: ## SSH into the VM - @VM_IP=$$(cd $(TERRAFORM_DIR) && tofu output -raw vm_ip 2>/dev/null) && \ - if [ -n "$$VM_IP" ] && [ "$$VM_IP" != "No IP assigned yet" ]; then \ - echo "Connecting to VM: $$VM_IP"; \ - ssh -o StrictHostKeyChecking=no torrust@$$VM_IP; \ - else \ - echo "Error: VM IP not available. Run 'make infra-status' to check infrastructure."; \ - exit 1; \ - fi - -console: ## Access VM console (text-based) - @echo "Accessing VM console..." - @virsh console $(VM_NAME) || echo "VM console not accessible. Try 'make vm-console' for graphical console." - -vm-console: ## Access VM graphical console (requires GUI) - @echo "Opening graphical VM console..." - @virt-viewer --connect qemu:///system $(VM_NAME) & - -# ============================================================================= -# CONFIGURATION MANAGEMENT -# ============================================================================= - -configure-local: ## Generate local environment configuration - @echo "Configuring local environment..." - $(SCRIPTS_DIR)/configure-env.sh local - -configure-production: ## Generate production environment configuration - @echo "Configuring production environment..." - $(SCRIPTS_DIR)/configure-env.sh production - -validate-config: ## Validate configuration for all environments - @echo "Validating configuration..." - $(SCRIPTS_DIR)/validate-config.sh - -# ============================================================================= -# TESTING AND QUALITY ASSURANCE -# ============================================================================= - -test: ## Run comprehensive test suite - @echo "Running comprehensive test suite..." - $(TESTS_DIR)/test-local-setup.sh - -test-syntax: ## Run syntax validation only - @echo "Running syntax validation..." - ./scripts/lint.sh - -lint: test-syntax ## Run all linting (alias for test-syntax) - -clean: ## Clean up temporary files and caches - @echo "Cleaning up..." - @rm -rf $(TERRAFORM_DIR)/.terraform - @rm -f $(TERRAFORM_DIR)/terraform.tfstate.backup - @echo "Clean completed" - -# ============================================================================= -# LEGACY COMPATIBILITY (DEPRECATED) -# ============================================================================= - -# These targets are maintained for backward compatibility but are deprecated -# Use the twelve-factor targets above instead - -init: infra-init ## [DEPRECATED] Use infra-init instead - @echo "โš ๏ธ DEPRECATED: Use 'make infra-init' instead" - -plan: infra-plan ## [DEPRECATED] Use infra-plan instead - @echo "โš ๏ธ DEPRECATED: Use 'make infra-plan' instead" - -apply: ## [DEPRECATED] Use infra-apply + app-deploy instead - @echo "โš ๏ธ DEPRECATED: This target combines infrastructure and application deployment" - @echo " For twelve-factor compliance, use:" - @echo " 1. make infra-apply ENVIRONMENT=$(ENVIRONMENT)" - @echo " 2. make app-deploy ENVIRONMENT=$(ENVIRONMENT)" - @echo "" - @echo "Proceeding with legacy deployment..." - @make infra-apply ENVIRONMENT=$(ENVIRONMENT) - @make app-deploy ENVIRONMENT=$(ENVIRONMENT) - -destroy: infra-destroy ## [DEPRECATED] Use infra-destroy instead - @echo "โš ๏ธ DEPRECATED: Use 'make infra-destroy' instead" - -status: infra-status ## [DEPRECATED] Use infra-status instead - @echo "โš ๏ธ DEPRECATED: Use 'make infra-status' instead" - -refresh-state: infra-refresh-state ## [DEPRECATED] Use infra-refresh-state instead - @echo "โš ๏ธ DEPRECATED: Use 'make infra-refresh-state' instead" - @echo "Fixing permissions after deployment..." - @$(MAKE) fix-libvirt - -apply: ## Deploy the VM - @echo "Ensuring libvirt permissions are correct..." - @$(MAKE) fix-libvirt - @echo "Deploying VM..." - @if [ -f $(TERRAFORM_DIR)/local.tfvars ]; then \ - echo "Using local SSH key configuration..."; \ - cd $(TERRAFORM_DIR) && tofu apply -var-file="local.tfvars" -parallelism=1 -auto-approve; \ - else \ - echo "WARNING: No local.tfvars found. Creating with placeholder..."; \ - echo 'ssh_public_key = "REPLACE_WITH_YOUR_SSH_PUBLIC_KEY"' > $(TERRAFORM_DIR)/local.tfvars; \ - echo "Please edit $(TERRAFORM_DIR)/local.tfvars with your SSH public key and run 'make apply' again"; \ - exit 1; \ - fi - @echo "Fixing permissions after deployment..." - @$(MAKE) fix-libvirt - -destroy: ## Destroy the VM - @echo "Destroying VM..." - cd $(TERRAFORM_DIR) && tofu destroy -auto-approve - -status: ## Show current infrastructure status - @echo "Infrastructure status:" - cd $(TERRAFORM_DIR) && tofu show - -refresh-state: ## Refresh Terraform state to detect IP changes - @echo "Refreshing Terraform state..." - cd $(TERRAFORM_DIR) && tofu refresh - @echo "Updated outputs:" - cd $(TERRAFORM_DIR) && tofu output - -ssh: ## SSH into the VM - @echo "Connecting to VM..." - @VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ - if [ -n "$$VM_IP" ]; then \ - echo "Connecting to $$VM_IP..."; \ - ssh torrust@$$VM_IP; \ - else \ - echo "Could not get VM IP. Is the VM deployed?"; \ - exit 1; \ - fi - -test: ## Run all tests - @echo "Running infrastructure tests..." - $(TESTS_DIR)/test-local-setup.sh full-test - -test-prereq: ## Test prerequisites only - @echo "Testing prerequisites..." - $(TESTS_DIR)/test-local-setup.sh prerequisites - -check-libvirt: ## Check libvirt installation and permissions - @echo "Checking libvirt setup..." - @echo "1. Checking if libvirt service is running:" - @sudo systemctl status libvirtd --no-pager -l || echo "libvirtd not running" - @echo "" - @echo "2. Checking user groups:" - @groups | grep -q libvirt && echo "โœ“ User is in libvirt group" || echo "โœ— User is NOT in libvirt group" - @groups | grep -q kvm && echo "โœ“ User is in kvm group" || echo "โœ— User is NOT in kvm group" - @echo "" - @echo "3. Testing libvirt access:" - @virsh list --all >/dev/null 2>&1 && echo "โœ“ User can access libvirt" || echo "โœ— User cannot access libvirt (try 'sudo virsh list')" - @echo "" - @echo "4. Checking default network:" - @virsh net-list --all 2>/dev/null | grep -q default && echo "โœ“ Default network exists" || echo "โœ— Default network missing" - @echo "" - @echo "5. Checking KVM support:" - @test -r /dev/kvm && echo "โœ“ KVM device accessible" || echo "โœ— KVM device not accessible" - @echo "" - @echo "If you see any โœ— marks, run 'make fix-libvirt' to attempt fixes" - -fix-libvirt: ## Fix common libvirt permission issues - @echo "Setting up user-friendly libvirt configuration..." - @infrastructure/scripts/setup-user-libvirt.sh - @echo "Attempting to fix libvirt permissions..." - @echo "Adding user to required groups..." - sudo usermod -aG libvirt $$USER - sudo usermod -aG kvm $$USER - @echo "Starting libvirt service..." - sudo systemctl enable libvirtd - sudo systemctl start libvirtd - @echo "Checking if default network needs to be started..." - @sudo virsh net-list --all | grep -q "default.*inactive" && sudo virsh net-start default || true - @sudo virsh net-autostart default 2>/dev/null || true - @echo "" - @echo "โœ“ Fix attempt completed!" - @echo "IMPORTANT: You need to log out and log back in (or run 'newgrp libvirt') for group changes to take effect" - @echo "Then run 'make check-libvirt' to verify the fixes worked" - -test-syntax: ## Test configuration syntax only - @echo "Testing configuration syntax..." - $(TESTS_DIR)/test-local-setup.sh syntax - -lint: ## Run all linting checks (yamllint, shellcheck, markdownlint) - @echo "Running linting checks..." - ./scripts/lint.sh - -lint-yaml: ## Run only yamllint - @echo "Running yamllint..." - ./scripts/lint.sh --yaml - -lint-shell: ## Run only shellcheck - @echo "Running shellcheck..." - ./scripts/lint.sh --shell - -lint-markdown: ## Run only markdownlint - @echo "Running markdownlint..." - ./scripts/lint.sh --markdown - -test-integration: ## Run integration tests (requires deployed VM) - @echo "Running integration tests..." - $(TESTS_DIR)/test-integration.sh full-test - -deploy-test: ## Deploy VM for testing (without cleanup) - @echo "Deploying test VM..." - $(TESTS_DIR)/test-local-setup.sh deploy - -clean: ## Clean up temporary files - @echo "Cleaning up..." - rm -f $(TERRAFORM_DIR)/.terraform.lock.hcl - rm -f $(TERRAFORM_DIR)/terraform.tfstate.backup - rm -f install-opentofu.sh - rm -f /tmp/torrust-infrastructure-test.log - -clean-and-fix: ## Clean up all VMs and fix libvirt permissions - @echo "Cleaning up VMs and fixing permissions..." - @echo "1. Stopping and undefining any existing VMs:" - @for vm in $$(virsh list --all --name 2>/dev/null | grep -v '^$$'); do \ - echo " Cleaning up VM: $$vm"; \ - virsh destroy $$vm 2>/dev/null || true; \ - virsh undefine $$vm 2>/dev/null || true; \ - done - @echo "2. Removing OpenTofu state:" - @cd $(TERRAFORM_DIR) && rm -f terraform.tfstate terraform.tfstate.backup .terraform.lock.hcl 2>/dev/null || true - @echo "3. Cleaning libvirt images:" - @sudo rm -f /var/lib/libvirt/images/torrust-tracker-demo* /var/lib/libvirt/images/ubuntu-24.04-base.qcow2 2>/dev/null || true - @echo "4. Cleaning application storage (generated configuration files):" - @if [ -d "application/storage" ]; then \ - echo " WARNING: This will delete all generated configuration files in application/storage/"; \ - echo " This includes nginx configs, tracker configs, and any cached data."; \ - echo " These files will be regenerated when you run 'make configure-local'."; \ - read -p " Do you want to delete application/storage? (y/N): " confirm; \ - if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \ - echo " Removing application/storage..."; \ - rm -rf application/storage; \ - echo " โœ“ Application storage cleaned"; \ - else \ - echo " Skipping application/storage cleanup"; \ - fi; \ - else \ - echo " No application/storage directory found"; \ - fi - @echo "5. Fixing libvirt setup:" - @$(MAKE) fix-libvirt - @echo "โœ“ Clean up complete. You can now run 'make apply' safely." - -# New target for setting up SSH key -setup-ssh-key: ## Setup local SSH key configuration - @if [ -f $(TERRAFORM_DIR)/local.tfvars ]; then \ - echo "Local SSH configuration already exists at $(TERRAFORM_DIR)/local.tfvars"; \ - echo "Current configuration:"; \ - cat $(TERRAFORM_DIR)/local.tfvars; \ - else \ - echo "Creating local SSH key configuration..."; \ - echo 'ssh_public_key = "REPLACE_WITH_YOUR_SSH_PUBLIC_KEY"' > $(TERRAFORM_DIR)/local.tfvars; \ - echo ""; \ - echo "โœ“ Created $(TERRAFORM_DIR)/local.tfvars"; \ - echo ""; \ - echo "Next steps:"; \ - echo "1. Get your SSH public key:"; \ - echo " cat ~/.ssh/id_rsa.pub"; \ - echo " # or cat ~/.ssh/id_ed25519.pub"; \ - echo ""; \ - echo "2. Edit the file and replace the placeholder:"; \ - echo " vim $(TERRAFORM_DIR)/local.tfvars"; \ - echo ""; \ - echo "3. Deploy the VM:"; \ - echo " make apply"; \ - fi - -restart-and-monitor: ## Destroy, deploy fresh, and monitor cloud-init - @echo "๐Ÿ”„ Complete restart: destroying existing VM..." - @$(MAKE) destroy || true - @echo "๐Ÿš€ Deploying fresh VM..." - @$(MAKE) apply & - @echo "โณ Waiting 10 seconds for VM to start..." - @sleep 10 - @echo "๐Ÿ“ก Starting cloud-init monitoring..." - @$(MAKE) monitor-cloud-init - -fresh-start: restart-and-monitor ## Alias for restart-and-monitor - -# Development targets -dev-setup: install-deps init fix-libvirt setup-ssh-key ## Complete development setup - @echo "Development environment setup complete!" - @echo "Next steps:" - @echo "1. Log out and log back in for group changes" - @echo "2. Edit $(TERRAFORM_DIR)/local.tfvars with your SSH public key" - @echo "3. Run 'make test-prereq' to verify setup" - @echo "4. Run 'make apply' to deploy a VM" - -quick-test: test-prereq test-syntax ## Quick test without VM deployment - @echo "Quick tests completed!" - -# Help for specific workflows -workflow-help: ## Show common workflows - @echo "Common workflows:" - @echo "" - @echo "1. First-time setup:" - @echo " make dev-setup" - @echo " # Log out and log back in" - @echo " # Edit infrastructure/cloud-init/user-data.yaml to add your SSH key" - @echo " make test-prereq" - @echo "" - @echo "2. Deploy and test:" - @echo " make apply" - @echo " make ssh" - @echo " make destroy" - @echo "" - @echo "3. Run full test suite:" - @echo " make test" - @echo "" - @echo "4. Run integration tests:" - @echo " make apply" - @echo " make test-integration" - @echo " make destroy" - @echo "" - @echo "5. Development cycle:" - @echo " make plan # Review changes" - @echo " make apply # Deploy" - @echo " make ssh # Test manually" - @echo " make destroy # Clean up" - -monitor-cloud-init: ## Monitor cloud-init progress in real-time - @echo "Monitoring cloud-init progress..." - @./infrastructure/scripts/monitor-cloud-init.sh - -vm-restart: ## Restart the VM - @echo "Restarting VM..." - virsh shutdown $(VM_NAME) - @echo "Waiting for shutdown..." - @sleep 5 - virsh start $(VM_NAME) - @echo "VM restarted" - -# CI/CD specific targets -ci-test-syntax: ## Test syntax for CI (with dummy values) - @echo "Testing syntax for CI environment..." - @echo "Creating temporary config with dummy values..." - @cd $(TERRAFORM_DIR) && \ - echo 'ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC dummy-key-for-ci-testing"' > ci-test.tfvars && \ - tofu init && \ - tofu validate && \ - rm ci-test.tfvars - @echo "Testing cloud-init templates..." - @CI=true $(TESTS_DIR)/test-local-setup.sh syntax - @echo "Testing cloud-init YAML syntax with yamllint..." - @if command -v yamllint >/dev/null 2>&1; then \ - yamllint -c .yamllint-ci.yml infrastructure/cloud-init/network-config.yaml && \ - yamllint -c .yamllint-ci.yml infrastructure/cloud-init/meta-data.yaml && \ - cd infrastructure/cloud-init && \ - sed 's/$${ssh_public_key}/ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC/' user-data.yaml.tpl > /tmp/user-data-test.yaml && \ - sed 's/$${ssh_public_key}/ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC/' user-data-minimal.yaml.tpl > /tmp/user-data-minimal-test.yaml && \ - yamllint -c ../../.yamllint-ci.yml /tmp/user-data-test.yaml && \ - yamllint -c ../../.yamllint-ci.yml /tmp/user-data-minimal-test.yaml && \ - rm -f /tmp/user-data-test.yaml /tmp/user-data-minimal-test.yaml; \ - else \ - echo "yamllint not available, skipping additional YAML validation"; \ - fi - -vm-ip: ## Show VM IP address - @echo "Getting VM IP address..." - @VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ - if [ -n "$$VM_IP" ]; then \ - echo "VM IP: $$VM_IP"; \ - else \ - echo "VM IP not assigned yet or VM not running"; \ - echo "VM status:"; \ - virsh list --all | grep $(VM_NAME) || echo "VM not found"; \ - fi - -vm-info: ## Show detailed VM network information - @echo "VM Network Information:" - @echo "======================" - @virsh list --all | grep $(VM_NAME) | head -1 || echo "VM not found" - @echo "" - @echo "Network interfaces:" - @virsh domifaddr $(VM_NAME) 2>/dev/null || echo "No network information available" - @echo "" - @echo "DHCP leases:" - @virsh net-dhcp-leases default 2>/dev/null | grep $(VM_NAME) || echo "No DHCP lease found" - -console: ## Access VM console (text-based) - @echo "Connecting to VM console..." - @echo "Use Ctrl+] to exit console" - @virsh console $(VM_NAME) - -vm-console: ## Access VM graphical console (GUI) - @echo "Opening VM graphical console..." - @if command -v virt-viewer >/dev/null 2>&1; then \ - virt-viewer $(VM_NAME) || virt-viewer spice://127.0.0.1:5900; \ - else \ - echo "virt-viewer not found. Please install it:"; \ - echo " sudo apt install virt-viewer"; \ - fi - -# Configuration Management Targets -configure-local: ## Generate local environment configuration - @echo "Generating local environment configuration..." - @infrastructure/scripts/configure-env.sh local - -configure-production: ## Generate production environment configuration (requires secrets) - @echo "Generating production environment configuration..." - @infrastructure/scripts/configure-env.sh production - -validate-config: ## Validate generated configuration files - @echo "Validating configuration files..." - @infrastructure/scripts/validate-config.sh local - -validate-config-production: ## Validate production configuration files - @echo "Validating production configuration files..." - @infrastructure/scripts/validate-config.sh production - -# Deployment workflow targets -deploy-local: configure-local ## Deploy VM and configure for local environment - @echo "Deploying local environment..." - @$(MAKE) apply - @echo "Waiting for VM to be ready..." - @sleep 30 - @echo "Starting application services..." - @$(MAKE) start-services - -deploy-production: configure-production ## Deploy and configure for production environment (requires secrets) - @echo "Deploying production environment..." - @$(MAKE) apply - @echo "Waiting for VM to be ready..." - @sleep 30 - @echo "Starting application services..." - @$(MAKE) start-services - -start-services: ## Start Docker Compose services in the VM - @echo "Starting Docker Compose services..." - @VM_IP=$$(cd $(TERRAFORM_DIR) && tofu output -raw vm_ip 2>/dev/null) || \ - VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ - if [ -n "$$VM_IP" ]; then \ - echo "Starting services on $$VM_IP..."; \ - ssh -o StrictHostKeyChecking=no torrust@$$VM_IP 'cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose up -d'; \ - else \ - echo "Could not get VM IP. Is the VM deployed?"; \ - exit 1; \ - fi - -stop-services: ## Stop Docker Compose services in the VM - @echo "Stopping Docker Compose services..." - @VM_IP=$$(cd $(TERRAFORM_DIR) && tofu output -raw vm_ip 2>/dev/null) || \ - VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ - if [ -n "$$VM_IP" ]; then \ - echo "Stopping services on $$VM_IP..."; \ - ssh -o StrictHostKeyChecking=no torrust@$$VM_IP 'cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose down'; \ - else \ - echo "Could not get VM IP. Is the VM deployed?"; \ - exit 1; \ - fi diff --git a/README.md b/README.md index b6a2f3a..9b41e1b 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,46 @@ This repository is organized into distinct concerns: - Security and auditing information - Cross-cutting concerns +## ๐Ÿ—๏ธ Twelve-Factor Architecture + +This project implements a complete [twelve-factor app](https://12factor.net/) architecture with +clear separation between infrastructure provisioning and application deployment: + +```text +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Configuration Management โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ€ข Environment Templates (local.env.tpl, production.env.tpl) โ”‚ +โ”‚ โ€ข Configuration Processing (configure-env.sh) โ”‚ +โ”‚ โ€ข Template Rendering (.tpl โ†’ actual configs) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Infrastructure Layer โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ€ข VM Provisioning (provision-infrastructure.sh) โ”‚ +โ”‚ โ€ข Environment-specific Setup (templated cloud-init) โ”‚ +โ”‚ โ€ข Provider Abstraction (local implemented, cloud planned) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Application Layer โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ€ข Environment-aware Deployment (templated configs) โ”‚ +โ”‚ โ€ข Dynamic Service Configuration โ”‚ +โ”‚ โ€ข Comprehensive Health Validation โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Key Features + +- **Complete Twelve-Factor Compliance**: All 12 factors implemented +- **Infrastructure/Application Separation**: Clean separation with `make infra-apply` and `make app-deploy` +- **Environment-based Configuration**: Template system with `local.env.tpl` and `production.env.tpl` +- **Build/Release/Run Stages**: Proper separation of configuration processing, deployment, and execution + ## Demo Tracker - **HTTP Tracker**: @@ -70,22 +110,28 @@ For detailed setup instructions, see the specific documentation: - **Infrastructure**: [Infrastructure Quick Start](infrastructure/docs/quick-start.md) - **Application**: [Application README](application/README.md) -### Complete Development Setup +### Complete Development Setup (Twelve-Factor Workflow) ```bash # 1. Setup infrastructure dependencies make dev-setup # Log out and log back in for group permissions -# 2. Configure SSH key +# 2. Configure local environment make infra-config-local # Edit infrastructure/terraform/local.tfvars with your SSH public key -# 3. Deploy VM and application -make infra-apply # Deploy VM -make vm-ssh # Access VM -docker compose -f application/compose.yaml up -d # Deploy application -make infra-destroy # Clean up +# 3. Deploy infrastructure and application (twelve-factor workflow) +make infra-apply ENVIRONMENT=local # Deploy VM (infrastructure only) +make app-deploy ENVIRONMENT=local # Deploy application (Build + Release + Run) +make app-health-check ENVIRONMENT=local # Validate deployment + +# 4. Access VM and check status +make vm-ssh # Access VM +make infra-destroy ENVIRONMENT=local # Clean up + +# Alternative: Complete workflow in one command +make dev-deploy ENVIRONMENT=local # Does all steps 3-4 ``` ## ๐Ÿ“š Documentation diff --git a/application/README.md b/application/README.md index 071e7d6..a8fca02 100644 --- a/application/README.md +++ b/application/README.md @@ -19,9 +19,7 @@ application/ โ”‚ โ”œโ”€โ”€ torrust-tracker-grafana-dashboard.png โ”‚ โ””โ”€โ”€ do-firewall-configuration.png โ”œโ”€โ”€ share/ # Application resources -โ”‚ โ”œโ”€โ”€ bin/ # Deployment and utility scripts -โ”‚ โ”‚ โ”œโ”€โ”€ deploy-torrust-tracker-demo.com.sh -โ”‚ โ”‚ โ”œโ”€โ”€ install.sh +โ”‚ โ”œโ”€โ”€ bin/ # Utility scripts โ”‚ โ”‚ โ”œโ”€โ”€ ssl_renew.sh โ”‚ โ”‚ โ”œโ”€โ”€ time-running.sh โ”‚ โ”‚ โ”œโ”€โ”€ tracker-db-backup.sh diff --git a/application/compose.yaml b/application/compose.yaml index 688ab57..2961634 100644 --- a/application/compose.yaml +++ b/application/compose.yaml @@ -5,9 +5,9 @@ services: image: certbot/certbot container_name: certbot volumes: - - ./storage/proxy/webroot:/var/www/html - - ./storage/certbot/etc:/etc/letsencrypt - - ./storage/certbot/lib:/var/lib/letsencrypt + - /var/lib/torrust/proxy/webroot:/var/www/html + - /var/lib/torrust/certbot/etc:/etc/letsencrypt + - /var/lib/torrust/certbot/lib:/var/lib/letsencrypt logging: options: max-size: "10m" @@ -25,11 +25,11 @@ services: - "80:80" - "443:443" volumes: - - ./storage/proxy/webroot:/var/www/html - - ./storage/proxy/etc/nginx-conf:/etc/nginx/conf.d - - ./storage/certbot/etc:/etc/letsencrypt - - ./storage/certbot/lib:/var/lib/letsencrypt - - ./storage/dhparam:/etc/ssl/certs + - /var/lib/torrust/proxy/webroot:/var/www/html + - /var/lib/torrust/proxy/etc/nginx-conf:/etc/nginx/conf.d + - /var/lib/torrust/certbot/etc:/etc/letsencrypt + - /var/lib/torrust/certbot/lib:/var/lib/letsencrypt + - /var/lib/torrust/dhparam:/etc/ssl/certs logging: options: max-size: "10m" @@ -64,7 +64,7 @@ services: ports: - "9090:9090" # This port should not be exposed to the internet volumes: - - ./storage/prometheus/etc:/etc/prometheus:Z + - /var/lib/torrust/prometheus/etc:/etc/prometheus:Z logging: options: max-size: "10m" @@ -87,12 +87,13 @@ services: - "3306:3306" # Only for debugging, remove in production volumes: - mysql_data:/var/lib/mysql - - ./storage/mysql/init:/docker-entrypoint-initdb.d:ro + - /var/lib/torrust/mysql/init:/docker-entrypoint-initdb.d:ro command: > --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p${MYSQL_PASSWORD}"] + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", + "-p${MYSQL_PASSWORD}"] interval: 10s timeout: 5s retries: 5 @@ -121,9 +122,9 @@ services: - 7070:7070 - 1212:1212 volumes: - - ./storage/tracker/lib:/var/lib/torrust/tracker:Z - - ./storage/tracker/log:/var/log/torrust/tracker:Z - - ./storage/tracker/etc:/etc/torrust/tracker:Z + - /var/lib/torrust/tracker/lib:/var/lib/torrust/tracker:Z + - /var/lib/torrust/tracker/log:/var/log/torrust/tracker:Z + - /var/lib/torrust/tracker/etc:/etc/torrust/tracker:Z logging: options: max-size: "10m" diff --git a/application/docs/deployment.md b/application/docs/deployment.md index f39127b..a663840 100644 --- a/application/docs/deployment.md +++ b/application/docs/deployment.md @@ -10,12 +10,32 @@ This document outlines the deployment process for the Torrust Tracker demo appli ## 2. Deployment Steps +The Torrust Tracker Demo now uses a **twelve-factor app deployment workflow** +that separates infrastructure provisioning from application deployment. + +### From Local Machine (Recommended) + +Use the automated deployment workflow from your local development machine: + +```bash +# Deploy infrastructure and application (complete workflow) +make infra-apply ENVIRONMENT=production +make app-deploy ENVIRONMENT=production + +# Validate deployment +make app-health-check ENVIRONMENT=production +``` + +### Manual Deployment on Server (Legacy) + +If you need to manually deploy on the server: + 1. **SSH into the server**. 2. **Navigate to the application directory**: ```bash - cd /home/torrust/github/torrust/torrust-tracker-demo + cd /home/torrust/github/torrust/torrust-tracker-demo/application ``` 3. **Pull the latest changes** from the repository: @@ -24,18 +44,15 @@ This document outlines the deployment process for the Torrust Tracker demo appli git pull ``` -4. **Run the deployment script**: +4. **Deploy using Docker Compose**: ```bash - ./share/bin/deploy-torrust-tracker-demo.com.sh + # Use the persistent volume environment file + docker compose --env-file /var/lib/torrust/compose/.env pull + docker compose --env-file /var/lib/torrust/compose/.env down + docker compose --env-file /var/lib/torrust/compose/.env up -d ``` - This script handles: - - - Stopping services - - Rebuilding containers - - Starting services - ## 3. Verification and Smoke Testing After deployment, verify that all services are running correctly. @@ -45,7 +62,12 @@ After deployment, verify that all services are running correctly. Check the status of all Docker containers: ```bash -docker compose ps +# From local machine +make app-health-check ENVIRONMENT=production + +# Or manually on server +cd /home/torrust/github/torrust/torrust-tracker-demo/application +docker compose --env-file /var/lib/torrust/compose/.env ps ``` ### Application Logs diff --git a/application/share/bin/deploy-torrust-tracker-demo.com.sh b/application/share/bin/deploy-torrust-tracker-demo.com.sh deleted file mode 100755 index 9a40b8f..0000000 --- a/application/share/bin/deploy-torrust-tracker-demo.com.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -# Update the demo by updating the containers - -cd /home/torrust/github/torrust/torrust-tracker-demo || exit -docker compose pull -docker compose down -docker compose up --build --detach -cd ~ || exit diff --git a/application/share/bin/install.sh b/application/share/bin/install.sh deleted file mode 100755 index 80e2413..0000000 --- a/application/share/bin/install.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/bin/bash - -# Torrust Tracker Demo Installation Script -# This script creates the required directory structure for the application. -# Following 12-factor principles, it expects .env to be provided by the infrastructure layer. - -if ! [ -f "./.env" ]; then - echo "ERROR: Environment file './.env' not found!" - echo "The .env file must be provided by the infrastructure configuration system." - echo "Expected location: $(pwd)/.env" - echo "" - echo "To generate the .env file, run:" - echo " make configure-local # For local development" - echo " make configure-production # For production deployment" - exit 1 -fi - -echo "Found environment file: ./.env" - -## Proxy - -mkdir -p ./storage/proxy/etc/nginx-conf -mkdir -p ./storage/proxy/webroot -mkdir -p ./storage/dhparam - -# Verify nginx configuration exists (should be provided by infrastructure) -if ! [ -f "./storage/proxy/etc/nginx-conf/nginx.conf" ]; then - echo "ERROR: Nginx configuration file './storage/proxy/etc/nginx-conf/nginx.conf' not found!" - echo "This file should be generated by the infrastructure configuration system." - echo "Expected location: $(pwd)/storage/proxy/etc/nginx-conf/nginx.conf" - echo "" - echo "To generate the configuration file, run:" - echo " make configure-local # For local development" - echo " make configure-production # For production deployment" - exit 1 -fi - -echo "Found nginx configuration: ./storage/proxy/etc/nginx-conf/nginx.conf" - -## Certbot - -mkdir -p ./storage/certbot/etc -mkdir -p ./storage/certbot/lib - -## Tracker - -# Generate the Tracker sqlite database directory and file if it does not exist -mkdir -p ./storage/tracker/lib/database - -if ! [ -f "./storage/tracker/lib/database/sqlite3.db" ]; then - echo "Creating tracker database: './storage/tracker/lib/database/sqlite3.db'" - sqlite3 "./storage/tracker/lib/database/sqlite3.db" "VACUUM;" -fi - -mkdir -p ./storage/tracker/etc - -# Verify tracker configuration exists (should be provided by infrastructure) -if ! [ -f "./storage/tracker/etc/tracker.toml" ]; then - echo "ERROR: Tracker configuration file './storage/tracker/etc/tracker.toml' not found!" - echo "This file should be generated by the infrastructure configuration system." - echo "Expected location: $(pwd)/storage/tracker/etc/tracker.toml" - echo "" - echo "To generate the configuration file, run:" - echo " make configure-local # For local development" - echo " make configure-production # For production deployment" - exit 1 -fi - -echo "Found tracker configuration: ./storage/tracker/etc/tracker.toml" - -## Prometheus - -mkdir -p ./storage/prometheus/etc - -# Verify prometheus configuration exists (should be provided by infrastructure) -if ! [ -f "./storage/prometheus/etc/prometheus.yml" ]; then - echo "ERROR: Prometheus configuration file './storage/prometheus/etc/prometheus.yml' not found!" - echo "This file should be generated by the infrastructure configuration system." - echo "Expected location: $(pwd)/storage/prometheus/etc/prometheus.yml" - echo "" - echo "To generate the configuration file, run:" - echo " make configure-local # For local development" - echo " make configure-production # For production deployment" - exit 1 -fi - -echo "Found prometheus configuration: ./storage/prometheus/etc/prometheus.yml" - -echo "Installation completed successfully!" -echo "All required directories created and configuration files verified." diff --git a/application/storage/compose/.gitignore b/application/storage/compose/.gitignore new file mode 100644 index 0000000..9b3b006 --- /dev/null +++ b/application/storage/compose/.gitignore @@ -0,0 +1,5 @@ +# Ignore environment files (may contain sensitive data) +.env + +# Keep directory structure +!.gitignore diff --git a/application/tests/test-unit-application.sh b/application/tests/test-unit-application.sh index 9c9355e..d86442f 100755 --- a/application/tests/test-unit-application.sh +++ b/application/tests/test-unit-application.sh @@ -118,7 +118,6 @@ test_application_structure() { "compose.yaml" "config" "share" - "storage" "docs" ) @@ -150,25 +149,27 @@ test_deployment_scripts() { return 0 fi - # Check for key deployment scripts + # Check for key utility scripts local key_scripts=( - "deploy-torrust-tracker-demo.com.sh" + "ssl_renew.sh" + "tracker-db-backup.sh" + "tracker-filtered-logs.sh" ) for script in "${key_scripts[@]}"; do local script_path="${scripts_dir}/${script}" if [[ -f "${script_path}" ]]; then if [[ -x "${script_path}" ]]; then - log_info "Found executable deployment script: ${script}" + log_info "Found executable utility script: ${script}" else - log_warning "Deployment script exists but is not executable: ${script}" + log_warning "Utility script exists but is not executable: ${script}" fi else - log_warning "Deployment script not found: ${script}" + log_warning "Utility script not found: ${script}" fi done - log_success "Deployment scripts validation completed" + log_success "Utility scripts validation completed" return ${failed} } diff --git a/docs/guides/integration-testing-guide.md b/docs/guides/integration-testing-guide.md index 2eeccef..d832129 100644 --- a/docs/guides/integration-testing-guide.md +++ b/docs/guides/integration-testing-guide.md @@ -653,7 +653,7 @@ time make configure-local - `infrastructure/cloud-init/` - VM provisioning files These files are generated from templates in `infrastructure/config/templates/` using -values from `infrastructure/config/environments/local.env`. +values from `infrastructure/config/environments/local.env.tpl`. ### 1.7.2 Validate Generated Configuration @@ -1199,7 +1199,7 @@ curl -s http://$VM_IP/api/health_check | jq . # [PROJECT_ROOT] Test stats API (requires authentication token) # Note: Get the token from the .env file in the VM -TOKEN="local-dev-admin-token-12345" +TOKEN="MyAccessToken" curl -s "http://$VM_IP/api/v1/stats?token=$TOKEN" | jq . ``` @@ -1289,7 +1289,7 @@ curl http://$VM_IP/prometheus/ # Prometheus UI ssh torrust@$VM_IP \ "grep TRACKER_ADMIN_TOKEN /home/torrust/github/torrust/torrust-tracker-demo/application/.env" -# Should output: TRACKER_ADMIN_TOKEN=local-dev-admin-token-12345 +# Should output: TRACKER_ADMIN_TOKEN=MyAccessToken ``` #### 5.2.5 Advanced Testing with jq @@ -1700,10 +1700,10 @@ VM_IP=$(cd infrastructure/terraform && tofu output -raw vm_ip) curl -s http://$VM_IP/api/health_check | jq . # Test stats (auth required) -curl -s "http://$VM_IP/api/v1/stats?token=local-dev-admin-token-12345" | jq . +curl -s "http://$VM_IP/api/v1/stats?token=MyAccessToken" | jq . # Test specific metrics with jq filtering -curl -s "http://$VM_IP/api/v1/stats?token=local-dev-admin-token-12345" | jq '.torrents, .seeders, .leechers' +curl -s "http://$VM_IP/api/v1/stats?token=MyAccessToken" | jq '.torrents, .seeders, .leechers' ``` #### โœ… Monitoring Service Testing @@ -1751,7 +1751,7 @@ curl http://$VM_IP/api/v1/stats **Correct**: Including authentication token: ```bash -curl "http://$VM_IP/api/v1/stats?token=local-dev-admin-token-12345" +curl "http://$VM_IP/api/v1/stats?token=MyAccessToken" ``` ### 9.4 Health Check Limitations @@ -2054,7 +2054,7 @@ During the most recent testing cycle, the following components were validated su - **Health Check API**: `/api/health_check` - No authentication required - **Stats API**: `/api/v1/stats` - **Requires authentication token** -- **Admin Token**: `local-dev-admin-token-12345` (from `.env` file) +- **Admin Token**: `MyAccessToken` (from `.env` file) #### Correct API Testing Examples @@ -2063,7 +2063,7 @@ During the most recent testing cycle, the following components were validated su curl -s http://$VM_IP/api/health_check | jq . # Stats API (auth required) -curl -s "http://$VM_IP/api/v1/stats?token=local-dev-admin-token-12345" | jq . +curl -s "http://$VM_IP/api/v1/stats?token=MyAccessToken" | jq . ``` #### Network Architecture diff --git a/docs/guides/smoke-testing-guide.md b/docs/guides/smoke-testing-guide.md index 6624843..e32b448 100644 --- a/docs/guides/smoke-testing-guide.md +++ b/docs/guides/smoke-testing-guide.md @@ -267,7 +267,7 @@ The statistics API is available through the nginx proxy on port 80: ```bash # Test statistics API through nginx proxy (requires admin token) echo "=== Testing Statistics API ===" -curl -s "http://$TARGET_SERVER:80/api/v1/stats?token=local-dev-admin-token-12345" | jq +curl -s "http://$TARGET_SERVER:80/api/v1/stats?token=MyAccessToken" | jq ``` **Expected Output:** diff --git a/infrastructure/.gitignore b/infrastructure/.gitignore index f5919f2..f9339f5 100644 --- a/infrastructure/.gitignore +++ b/infrastructure/.gitignore @@ -14,7 +14,6 @@ terraform.tfplan.* config/environments/production.env config/environments/*.env !config/environments/*.env.tpl -!config/environments/local.env # Cloud-init generated files user-data.yaml diff --git a/infrastructure/cloud-init/user-data.yaml.tpl b/infrastructure/cloud-init/user-data.yaml.tpl index 8d812d7..c23f2f2 100644 --- a/infrastructure/cloud-init/user-data.yaml.tpl +++ b/infrastructure/cloud-init/user-data.yaml.tpl @@ -33,6 +33,30 @@ users: # Disable SSH password authentication for security ssh_pwauth: false +# Persistent data volume configuration +# This configures the second disk (/dev/vdb) attached to the VM for persistent storage. +# All application data that needs to survive VM destruction is stored here, including: +# - Database data (MySQL) +# - Application configuration files (.env, tracker.toml) +# - SSL certificates and keys +# - Logs and application state +# - Prometheus metrics data +# The volume is mounted at /var/lib/torrust and survives infrastructure recreation. +disk_setup: + /dev/vdb: + table_type: gpt + layout: true + overwrite: false + +fs_setup: + - label: torrust-data + filesystem: ext4 + device: /dev/vdb1 + overwrite: false + +mounts: + - ["/dev/vdb1", "/var/lib/torrust", "ext4", "defaults,noatime", "0", "2"] + # Package updates and installations package_update: true package_upgrade: true @@ -109,6 +133,10 @@ write_files: # Commands to run after package installation runcmd: + # Set up persistent data volume and directory structure + - mkdir -p /var/lib/torrust + - chown -R torrust:torrust /var/lib/torrust + # Create torrust user directories - mkdir -p /home/torrust/github/torrust - chown -R torrust:torrust /home/torrust/github diff --git a/infrastructure/config/environments/README.md b/infrastructure/config/environments/README.md new file mode 100644 index 0000000..1fbcb64 --- /dev/null +++ b/infrastructure/config/environments/README.md @@ -0,0 +1,208 @@ +# Environment Configuration Templates + +This directory contains environment-specific configuration templates that are processed +during deployment to generate the final configuration files. + +## Files + +- `local.env.tpl` - Local development environment template +- `production.env.tpl` - Production environment template (requires manual setup) + +## Template Processing + +Templates use environment variable substitution (`envsubst`) to generate final +configuration files: + +```bash +# Templates are processed like this: +envsubst < local.env.tpl > local.env +envsubst < production.env.tpl > production.env # (after manual setup) +``` + +## Critical Deployment Behavior + +### The Git Archive Issue + +**IMPORTANT:** When you modify templates in this folder and run E2E tests, the tests +might fail if they depend on the new values. This happens due to how the application +deployment process works: + +1. **Infrastructure Provisioning**: New VM is created +2. **Code Deployment**: Git archive is copied to VM (`git archive HEAD`) +3. **Configuration Generation**: Templates are processed on the VM + +### The Problem + +**`git archive` only includes committed changes, not your working tree changes.** + +This means: + +- โœ… If you modify templates and **commit** them, E2E tests will use the new values +- โŒ If you modify templates but **don't commit** them, E2E tests will use the old + committed values + +### Example Scenario + +```bash +# 1. You modify local.env.tpl to change TRACKER_ADMIN_TOKEN +vim infrastructure/config/environments/local.env.tpl + +# 2. You run E2E tests without committing +make test-e2e # โŒ FAILS - Uses old token from git archive + +# 3. You commit your changes +git add infrastructure/config/environments/local.env.tpl +git commit -m "update token" + +# 4. You run E2E tests again +make test-e2e # โœ… PASSES - Uses new token from git archive +``` + +## Why Git Archive? + +The deployment process uses `git archive` for several important reasons: + +### Development Benefits + +- **Clean Deployment**: Only committed, tested changes are deployed +- **Excludes Local Files**: Doesn't copy `.env` files, build artifacts, or local storage +- **Reproducible**: Same git commit always produces the same deployment +- **Fast**: Compressed archive transfer is faster than full directory sync + +### Production Safety + +- **Version Control**: Only committed code reaches production +- **No Accidental Deployments**: Prevents deploying uncommitted debug code or secrets +- **Audit Trail**: Clear link between deployments and git commits +- **Rollback Capability**: Easy to redeploy any previous commit + +## Best Practices + +### For Development (E2E Testing) + +1. **Always commit template changes before running E2E tests**: + + ```bash + git add infrastructure/config/environments/ + git commit -m "update configuration templates" + make test-e2e + ``` + +2. **Check git status before testing**: + + ```bash + git status # Should show "working tree clean" + make test-e2e + ``` + +### For Production Deployment + +1. **Never modify templates directly in production** +2. **Always test changes in development first** +3. **Use proper git workflow** (feature branches, reviews, etc.) +4. **Verify configuration after deployment** + +## Alternative Approaches Considered + +### Option 1: Copy Working Tree + +```bash +# Instead of: git archive HEAD | tar -xz +rsync -av --exclude='.git' . vm:/path/ +``` + +**Pros**: Includes uncommitted changes + +**Cons**: + +- Copies local secrets and build artifacts +- No version control guarantee +- Inconsistent between development and production +- Larger transfer size + +### Option 2: Separate Config Management + +```bash +# Keep templates separate from code deployment +scp infrastructure/config/environments/*.tpl vm:/path/ +``` + +**Pros**: Templates can be updated independently + +**Cons**: + +- More complex deployment process +- Configuration and code can get out of sync +- Additional deployment step to fail + +## Current Choice: Git Archive + +We chose to keep `git archive` because: + +1. **Production Safety**: Ensures only committed code is deployed +2. **Consistency**: Same process for development and production +3. **Simplicity**: Single deployment artifact +4. **Version Control**: Clear audit trail of what was deployed + +The trade-off is that **developers must commit template changes before E2E testing**, +but this is actually a good practice that ensures: + +- Template changes are reviewed and tested +- No accidental deployment of uncommitted changes +- Clear history of configuration changes + +## Troubleshooting + +### E2E Tests Fail After Template Changes + +1. **Check if changes are committed**: + + ```bash + git status infrastructure/config/environments/ + ``` + +2. **If uncommitted, commit them**: + + ```bash + git add infrastructure/config/environments/ + git commit -m "update: configuration templates for testing" + ``` + +3. **Re-run tests**: + + ```bash + make test-e2e + ``` + +### Configuration Not Updated After Deployment + +1. **Verify the git archive contains your changes**: + + ```bash + git archive HEAD -- infrastructure/config/environments/ | tar -tz + ``` + +2. **Check template processing on VM**: + + ```bash + ssh torrust@$VM_IP 'cd torrust-tracker-demo && cat infrastructure/config/environments/local.env' + ``` + +3. **Verify generated configuration**: + + ```bash + ssh torrust@$VM_IP 'cd torrust-tracker-demo && cat application/.env' + ``` + +## Security Notes + +- **Never commit production secrets** - Use placeholder values in templates +- **Review template changes** - Configuration changes can affect security +- **Test thoroughly** - Configuration errors can break the entire application +- **Backup production configs** - Before deploying configuration changes + +## Related Documentation + +- [Deployment Guide](../../../docs/guides/integration-testing-guide.md) +- [Twelve-Factor App Methodology](../../../docs/guides/integration-testing-guide.md#twelve-factor-compliance) +- [Configuration Management ADR](../../../docs/adr/004-configuration-approach-files-vs-environment-variables.md) diff --git a/infrastructure/config/environments/local.env b/infrastructure/config/environments/local.env.tpl similarity index 92% rename from infrastructure/config/environments/local.env rename to infrastructure/config/environments/local.env.tpl index 62ae263..895090a 100644 --- a/infrastructure/config/environments/local.env +++ b/infrastructure/config/environments/local.env.tpl @@ -14,7 +14,7 @@ MYSQL_USER=torrust MYSQL_PASSWORD=tracker_secret_local # Tracker API Token -TRACKER_ADMIN_TOKEN=local-dev-admin-token-12345 +TRACKER_ADMIN_TOKEN=MyAccessToken # Grafana Admin Credentials GF_SECURITY_ADMIN_USER=admin diff --git a/infrastructure/docs/refactoring/multi-provider-abstraction/README.md b/infrastructure/docs/refactoring/multi-provider-abstraction/README.md new file mode 100644 index 0000000..d0973a9 --- /dev/null +++ b/infrastructure/docs/refactoring/multi-provider-abstraction/README.md @@ -0,0 +1,846 @@ +# Multi-Provider Abstraction Plan + +## ๐Ÿ“‹ Overview + +This document outlines the architectural changes needed to support multiple cloud providers +in the Torrust Tracker Demo repository while maintaining twelve-factor methodology compliance. +Currently, the infrastructure is tightly coupled to the local libvirt provider, making it +difficult to add new providers like Hetzner. + +## ๐ŸŽฏ Objectives + +- **Easy Provider Addition**: New cloud providers can be added with minimal changes +- **Twelve-Factor Compliance**: Maintain clear separation of configuration and infrastructure +- **Backward Compatibility**: Existing local workflow remains unchanged +- **Standardized Interface**: Same commands work across all providers +- **Provider Isolation**: Provider-specific logic contained in dedicated modules + +## ๐Ÿ“Š Current State Analysis + +### Current Limitations + +1. **Hardcoded Provider**: Terraform configuration in `main.tf` is libvirt-specific +2. **Mixed Validation Logic**: Provider-specific prerequisites mixed with generic logic +3. **Single Configuration Path**: No provider-specific configuration structure +4. **Shared Resources**: No provider-specific resource definitions or modules + +### Current Architecture Issues + +```text +infrastructure/terraform/main.tf +โ”œโ”€โ”€ Hardcoded libvirt provider configuration +โ”œโ”€โ”€ Libvirt-specific resources (volumes, domains, networks) +โ”œโ”€โ”€ Mixed provider-specific and generic variables +โ””โ”€โ”€ No abstraction for other cloud providers +``` + +### Supported Providers + +- โœ… **Local**: KVM/libvirt for local testing (current implementation) +- ๐Ÿšง **Hetzner**: Hetzner Cloud for production (planned) +- ๐Ÿ”ฎ **Future**: AWS, GCP, Azure, DigitalOcean (potential) + +## ๐Ÿ—๏ธ Proposed Architecture + +### Target Directory Structure + +```text +infrastructure/ +โ”œโ”€โ”€ terraform/ +โ”‚ โ”œโ”€โ”€ providers/ # NEW: Provider-specific modules +โ”‚ โ”‚ โ”œโ”€โ”€ local/ # Libvirt provider (existing logic) +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ main.tf # Libvirt resources +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ variables.tf # Provider-specific variables +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ outputs.tf # Standardized outputs +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ versions.tf # Provider requirements +โ”‚ โ”‚ โ”œโ”€โ”€ hetzner/ # NEW: Hetzner provider +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ main.tf # Hetzner Cloud resources +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ variables.tf # Provider-specific variables +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ outputs.tf # Standardized outputs +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ versions.tf # Provider requirements +โ”‚ โ”‚ โ””โ”€โ”€ [future providers]/ +โ”‚ โ”œโ”€โ”€ main.tf # NEW: Provider-agnostic orchestration +โ”‚ โ”œโ”€โ”€ variables.tf # NEW: Common variables +โ”‚ โ”œโ”€โ”€ outputs.tf # NEW: Standardized outputs +โ”‚ โ””โ”€โ”€ local.tfvars # Existing local config +โ”œโ”€โ”€ config/ +โ”‚ โ”œโ”€โ”€ environments/ +โ”‚ โ”‚ โ”œโ”€โ”€ local.env # Enhanced with infrastructure provider +โ”‚ โ”‚ โ”œโ”€โ”€ production.env.tpl # Enhanced with infrastructure provider +โ”‚ โ”‚ โ””โ”€โ”€ hetzner.env.tpl # NEW: Hetzner-specific environment +โ”‚ โ””โ”€โ”€ templates/ +โ”‚ โ”œโ”€โ”€ terraform/ # NEW: Provider-specific templates +โ”‚ โ”‚ โ”œโ”€โ”€ local.tf.tpl +โ”‚ โ”‚ โ””โ”€โ”€ hetzner.tf.tpl +โ”‚ โ””โ”€โ”€ cloud-init/ # Provider-specific cloud-init +โ”‚ โ”œโ”€โ”€ local/ +โ”‚ โ””โ”€โ”€ hetzner/ +``` + +### Provider Abstraction Layer + +The new architecture introduces a clear separation between: + +1. **Provider-Agnostic Orchestration**: Main Terraform configuration that selects the + appropriate provider module +2. **Provider-Specific Modules**: Self-contained modules for each cloud provider +3. **Standardized Interfaces**: Common variables and outputs across all providers +4. **Enhanced Configuration**: Environment-based provider selection and settings + +## ๐Ÿ“‹ Detailed Implementation Plan + +### Phase 1: Provider Module Structure + +#### 1.1 Create Provider Directory Structure + +```bash +# Create provider module directories +mkdir -p infrastructure/terraform/providers/local +mkdir -p infrastructure/terraform/providers/hetzner + +# Create provider-specific files +touch infrastructure/terraform/providers/local/{main.tf,variables.tf,outputs.tf,versions.tf} +touch infrastructure/terraform/providers/hetzner/{main.tf,variables.tf,outputs.tf,versions.tf} +``` + +#### 1.2 Move Local Provider Logic + +**Current `main.tf` โ†’ `providers/local/main.tf`**: + +```hcl +# Move all existing libvirt resources to local provider module +terraform { + required_providers { + libvirt = { + source = "dmacvicar/libvirt" + version = "~> 0.7" + } + } +} + +provider "libvirt" { + uri = var.libvirt_uri +} + +# All existing libvirt resources (volumes, domains, cloud-init)... +``` + +#### 1.3 Create Provider-Agnostic Orchestration + +**New `main.tf`**: + +```hcl +terraform { + required_version = ">= 1.0" +} + +# Conditional module inclusion based on provider +module "local_infrastructure" { + count = var.infrastructure_provider == "local" ? 1 : 0 + source = "./providers/local" + + # Standardized variables + vm_name = var.vm_name + vm_memory = var.vm_memory + vm_vcpus = var.vm_vcpus + vm_disk_size = var.vm_disk_size + ssh_public_key = var.ssh_public_key + use_minimal_config = var.use_minimal_config + + # Provider-specific variables + libvirt_uri = var.libvirt_uri + libvirt_pool = var.libvirt_pool + libvirt_network = var.libvirt_network +} + +module "hetzner_infrastructure" { + count = var.infrastructure_provider == "hetzner" ? 1 : 0 + source = "./providers/hetzner" + + # Standardized variables + vm_name = var.vm_name + vm_memory = var.vm_memory + vm_vcpus = var.vm_vcpus + vm_disk_size = var.vm_disk_size + ssh_public_key = var.ssh_public_key + + # Provider-specific variables + hetzner_token = var.hetzner_token + hetzner_server_type = var.hetzner_server_type + hetzner_location = var.hetzner_location + hetzner_image = var.hetzner_image +} + +# Standardized outputs (regardless of provider) +output "vm_ip" { + value = var.infrastructure_provider == "local" ? + (length(module.local_infrastructure) > 0 ? + module.local_infrastructure[0].vm_ip : "No IP assigned yet") : + (length(module.hetzner_infrastructure) > 0 ? + module.hetzner_infrastructure[0].vm_ip : "No IP assigned yet") + description = "IP address of the created VM" +} + +output "vm_name" { + value = var.infrastructure_provider == "local" ? + (length(module.local_infrastructure) > 0 ? module.local_infrastructure[0].vm_name : "") : + (length(module.hetzner_infrastructure) > 0 ? module.hetzner_infrastructure[0].vm_name : "") + description = "Name of the created VM" +} + +output "connection_info" { + value = var.infrastructure_provider == "local" ? + (length(module.local_infrastructure) > 0 ? + module.local_infrastructure[0].connection_info : "VM not created") : + (length(module.hetzner_infrastructure) > 0 ? + module.hetzner_infrastructure[0].connection_info : "VM not created") + description = "SSH connection command" +} +``` + +### Phase 2: Enhanced Environment Configuration + +#### 2.1 Add Infrastructure Provider Settings + +**Enhanced `local.env`**: + +```bash +# Infrastructure Provider Configuration +ENVIRONMENT=local +INFRASTRUCTURE_PROVIDER=local + +# Provider-specific settings +PROVIDER_LOCAL_LIBVIRT_URI=qemu:///system +PROVIDER_LOCAL_POOL=user-default +PROVIDER_LOCAL_NETWORK=default + +# VM Configuration (provider-agnostic) +VM_NAME=torrust-tracker-demo +VM_MEMORY=2048 +VM_VCPUS=2 +VM_DISK_SIZE=20 + +# Existing application configuration... +MYSQL_ROOT_PASSWORD=root_secret_local +MYSQL_DATABASE=torrust_tracker +MYSQL_USER=torrust +MYSQL_PASSWORD=tracker_secret_local +TRACKER_ADMIN_TOKEN=MyAccessToken +GF_SECURITY_ADMIN_USER=admin +GF_SECURITY_ADMIN_PASSWORD=admin_secret_local +USER_ID=1000 +``` + +#### 2.2 Create Hetzner Environment Template + +**New `hetzner.env.tpl`**: + +```bash +# Hetzner Cloud Environment Configuration Template +# Copy this file to hetzner.env and replace placeholder values + +ENVIRONMENT=production +INFRASTRUCTURE_PROVIDER=hetzner + +# Hetzner-specific settings +PROVIDER_HETZNER_TOKEN=REPLACE_WITH_HETZNER_API_TOKEN +PROVIDER_HETZNER_SERVER_TYPE=cx31 +PROVIDER_HETZNER_LOCATION=nbg1 +PROVIDER_HETZNER_IMAGE=ubuntu-24.04 +PROVIDER_HETZNER_SSH_KEY_NAME=torrust-demo-key + +# VM Configuration (provider-agnostic) +VM_NAME=torrust-tracker-prod +VM_MEMORY=8192 # Larger for production +VM_VCPUS=4 +VM_DISK_SIZE=40 + +# Application configuration (same structure as other environments) +MYSQL_ROOT_PASSWORD=REPLACE_WITH_SECURE_ROOT_PASSWORD +MYSQL_DATABASE=torrust_tracker +MYSQL_USER=torrust +MYSQL_PASSWORD=REPLACE_WITH_SECURE_PASSWORD +TRACKER_ADMIN_TOKEN=REPLACE_WITH_SECURE_ADMIN_TOKEN +GF_SECURITY_ADMIN_USER=admin +GF_SECURITY_ADMIN_PASSWORD=REPLACE_WITH_SECURE_GRAFANA_PASSWORD +USER_ID=1000 +``` + +### Phase 3: Enhanced Scripts with Provider Logic + +#### 3.1 Provider-Aware Infrastructure Provisioning + +**Enhanced `provision-infrastructure.sh`**: + +```bash +# Extract infrastructure provider from environment +get_infrastructure_provider() { + if [[ -f "${CONFIG_DIR}/environments/${ENVIRONMENT}.env" ]]; then + INFRASTRUCTURE_PROVIDER=$(grep "INFRASTRUCTURE_PROVIDER=" \ + "${CONFIG_DIR}/environments/${ENVIRONMENT}.env" | cut -d'=' -f2) + fi + + if [[ -z "${INFRASTRUCTURE_PROVIDER}" ]]; then + log_error "INFRASTRUCTURE_PROVIDER not specified in environment: ${ENVIRONMENT}" + exit 1 + fi + + log_info "Infrastructure provider: ${INFRASTRUCTURE_PROVIDER}" +} + +# Provider-specific validation +validate_provider_prerequisites() { + case "${INFRASTRUCTURE_PROVIDER}" in + "local") + validate_libvirt_prerequisites + ;; + "hetzner") + validate_hetzner_prerequisites + ;; + *) + log_error "Unknown infrastructure provider: ${INFRASTRUCTURE_PROVIDER}" + log_error "Supported providers: local, hetzner" + exit 1 + ;; + esac +} + +validate_libvirt_prerequisites() { + log_info "Validating libvirt prerequisites" + + if ! command -v virsh >/dev/null 2>&1; then + log_error "virsh not found. Please install libvirt-clients." + exit 1 + fi + + if ! virsh list >/dev/null 2>&1; then + log_error "No libvirt access. Please add user to libvirt group and restart session." + exit 1 + fi + + log_success "Libvirt prerequisites validated" +} + +validate_hetzner_prerequisites() { + log_info "Validating Hetzner Cloud prerequisites" + + if [[ -z "${PROVIDER_HETZNER_TOKEN:-}" ]]; then + log_error "Hetzner API token not configured (PROVIDER_HETZNER_TOKEN)" + exit 1 + fi + + # Optionally check hcloud CLI + if command -v hcloud >/dev/null 2>&1; then + log_info "Hetzner CLI (hcloud) found - validating token" + if ! hcloud server list >/dev/null 2>&1; then + log_warning "Hetzner API token validation failed" + fi + fi + + log_success "Hetzner Cloud prerequisites validated" +} + +# Provider-specific Terraform variable file generation +generate_terraform_vars() { + local vars_file="${TERRAFORM_DIR}/${INFRASTRUCTURE_PROVIDER}.auto.tfvars" + + log_info "Generating Terraform variables for provider: ${INFRASTRUCTURE_PROVIDER}" + + case "${INFRASTRUCTURE_PROVIDER}" in + "local") + generate_local_terraform_vars "${vars_file}" + ;; + "hetzner") + generate_hetzner_terraform_vars "${vars_file}" + ;; + esac + + TERRAFORM_VARS_FILE="${vars_file}" + log_success "Terraform variables generated: ${TERRAFORM_VARS_FILE}" +} + +generate_local_terraform_vars() { + local vars_file="$1" + + cat > "${vars_file}" < "${vars_file}" < "${output_file}" + log_success "Generated provider configuration: ${output_file}" + fi +} +``` + +### Phase 4: Hetzner Provider Implementation + +#### 4.1 Hetzner Provider Module + +**`providers/hetzner/main.tf`**: + +```hcl +terraform { + required_providers { + hcloud = { + source = "hetznercloud/hcloud" + version = "~> 1.45" + } + } +} + +provider "hcloud" { + token = var.hetzner_token +} + +# SSH Key resource +resource "hcloud_ssh_key" "torrust_key" { + name = "${var.vm_name}-key" + public_key = var.ssh_public_key +} + +# Server instance +resource "hcloud_server" "vm" { + name = var.vm_name + server_type = var.hetzner_server_type + location = var.hetzner_location + image = var.hetzner_image + + ssh_keys = [hcloud_ssh_key.torrust_key.id] + + user_data = templatefile( + "${path.module}/../../cloud-init/${var.use_minimal_config ? + "user-data-minimal.yaml.tpl" : "user-data.yaml.tpl"}", { + ssh_public_key = var.ssh_public_key + }) + + # Basic firewall rules + firewall_ids = [hcloud_firewall.torrust_firewall.id] +} + +# Firewall configuration +resource "hcloud_firewall" "torrust_firewall" { + name = "${var.vm_name}-firewall" + + rule { + direction = "in" + port = "22" + protocol = "tcp" + source_ips = ["0.0.0.0/0", "::/0"] + } + + rule { + direction = "in" + port = "80" + protocol = "tcp" + source_ips = ["0.0.0.0/0", "::/0"] + } + + rule { + direction = "in" + port = "443" + protocol = "tcp" + source_ips = ["0.0.0.0/0", "::/0"] + } + + # Torrust Tracker UDP ports + rule { + direction = "in" + port = "6868" + protocol = "udp" + source_ips = ["0.0.0.0/0", "::/0"] + } + + rule { + direction = "in" + port = "6969" + protocol = "udp" + source_ips = ["0.0.0.0/0", "::/0"] + } +} +``` + +**`providers/hetzner/variables.tf`**: + +```hcl +# Standardized variables (common across all providers) +variable "vm_name" { + description = "Name of the virtual machine" + type = string +} + +variable "vm_memory" { + description = "Memory allocation for VM in MB" + type = number +} + +variable "vm_vcpus" { + description = "Number of vCPUs for the VM" + type = number +} + +variable "vm_disk_size" { + description = "Disk size in GB" + type = number +} + +variable "ssh_public_key" { + description = "SSH public key for VM access" + type = string +} + +variable "use_minimal_config" { + description = "Use minimal cloud-init configuration for debugging" + type = bool + default = false +} + +# Hetzner-specific variables +variable "hetzner_token" { + description = "Hetzner Cloud API token" + type = string + sensitive = true +} + +variable "hetzner_server_type" { + description = "Hetzner server type (e.g., cx31, cx41)" + type = string + default = "cx31" +} + +variable "hetzner_location" { + description = "Hetzner datacenter location" + type = string + default = "nbg1" +} + +variable "hetzner_image" { + description = "Hetzner server image" + type = string + default = "ubuntu-24.04" +} +``` + +**`providers/hetzner/outputs.tf`**: + +```hcl +# Standardized outputs (must match local provider interface) +output "vm_ip" { + value = hcloud_server.vm.ipv4_address + description = "IP address of the created VM" +} + +output "vm_name" { + value = hcloud_server.vm.name + description = "Name of the created VM" +} + +output "connection_info" { + value = "SSH to VM: ssh torrust@${hcloud_server.vm.ipv4_address}" + description = "SSH connection command" +} + +# Provider-specific outputs +output "hetzner_server_id" { + value = hcloud_server.vm.id + description = "Hetzner server ID" +} + +output "hetzner_datacenter" { + value = hcloud_server.vm.datacenter + description = "Hetzner datacenter information" +} +``` + +### Phase 5: Enhanced Makefile Commands + +#### 5.1 New Provider-Aware Commands + +```makefile +# Enhanced commands that work with any provider +infra-apply: ## Provision infrastructure (works with any provider) + @echo "Provisioning infrastructure for $(ENVIRONMENT)..." + @echo "โš ๏ธ This command may prompt for your password for provider-specific operations" + $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) apply + +# Provider-specific configuration commands +infra-config-hetzner: ## Generate Hetzner environment configuration + @echo "Configuring Hetzner environment..." + $(SCRIPTS_DIR)/configure-env.sh hetzner + +# Provider validation +infra-test-prereq: ## Test system prerequisites for environment + @echo "Testing prerequisites for $(ENVIRONMENT)..." + $(INFRA_TESTS_DIR)/test-unit-infrastructure.sh prerequisites $(ENVIRONMENT) + +# Provider-specific help +infra-providers: ## Show supported infrastructure providers + @echo "Supported Infrastructure Providers:" + @echo " local - KVM/libvirt for local testing" + @echo " hetzner - Hetzner Cloud for production" + @echo "" + @echo "Usage:" + @echo " make infra-apply ENVIRONMENT=local # Local testing" + @echo " make infra-apply ENVIRONMENT=hetzner # Hetzner production" +``` + +## ๐Ÿš€ Benefits After Implementation + +### For Developers + +1. **๐Ÿ”ง Easy Provider Addition**: New providers require only: + + - Adding a provider module in `providers/[name]/` + - Creating environment template `[name].env.tpl` + - Adding validation logic to scripts + +2. **๐Ÿ—๏ธ Twelve-Factor Compliance**: + + - Configuration externalized via environment variables + - Infrastructure and application concerns clearly separated + - Provider selection via environment configuration + +3. **๐Ÿ”„ Backward Compatibility**: + - Existing `make infra-apply ENVIRONMENT=local` commands unchanged + - Local testing workflow unaffected + - Gradual migration path for users + +### For Operations + +1. **๐Ÿ“‹ Standardized Interface**: Same commands work across all providers +2. **๐ŸŽฏ Provider Isolation**: Provider failures don't affect other providers +3. **โšก Flexible Configuration**: Easy switching between providers +4. **๐Ÿ” Clear Validation**: Provider-specific prerequisite checking + +### Example Usage After Implementation + +```bash +# Local development (unchanged) +make infra-apply ENVIRONMENT=local + +# Hetzner production (new) +make infra-config-hetzner # Generate hetzner.env from template +# Edit hetzner.env with actual secrets +make infra-apply ENVIRONMENT=hetzner + +# Future AWS support (potential) +make infra-config-aws +make infra-apply ENVIRONMENT=aws +``` + +## ๐Ÿ“… Implementation Timeline + +### Week 1: Foundation + +- [ ] Create provider module directory structure +- [ ] Move local provider logic to module +- [ ] Create provider-agnostic orchestration layer +- [ ] Test backward compatibility with local environment + +### Week 2: Configuration Enhancement + +- [ ] Enhance environment files with provider settings +- [ ] Create Hetzner environment template +- [ ] Update configuration processing scripts +- [ ] Test configuration generation + +### Week 3: Script Enhancement + +- [ ] Add provider-aware validation logic +- [ ] Implement Terraform variable generation +- [ ] Update infrastructure provisioning script +- [ ] Test provider switching + +### Week 4: Hetzner Implementation + +- [ ] Implement Hetzner provider module +- [ ] Add Hetzner-specific validation +- [ ] Test Hetzner deployment workflow +- [ ] Validate provider abstraction + +### Week 5: Testing & Documentation + +- [ ] Comprehensive testing of both providers +- [ ] Update documentation and guides +- [ ] Create migration guide for existing users +- [ ] Performance and security validation + +## ๐Ÿ”’ Security Considerations + +### Secret Management + +1. **Provider Tokens**: API tokens stored in environment files (gitignored) +2. **SSH Keys**: Managed per provider (local files vs cloud key management) +3. **Terraform State**: Provider-specific state isolation +4. **Access Control**: Provider-specific access validation + +### Network Security + +1. **Firewall Rules**: Provider-specific firewall configuration +2. **Network Isolation**: Provider-specific network setup +3. **Access Patterns**: Provider-appropriate security groups/rules + +## ๐Ÿงช Testing Strategy + +### Unit Testing + +- [ ] Provider module validation +- [ ] Configuration template processing +- [ ] Script provider detection logic + +### Integration Testing + +- [ ] Local provider deployment (existing) +- [ ] Hetzner provider deployment (new) +- [ ] Provider switching workflow +- [ ] Cross-provider compatibility + +### End-to-End Testing + +- [ ] Complete deployment workflow per provider +- [ ] Application deployment on each provider +- [ ] Health validation across providers + +## ๐Ÿ“š Related Documentation + +- [Twelve-Factor App Refactoring](../twelve-factor-refactor/README.md) +- [Hetzner Migration Plan](../../../docs/plans/hetzner-migration-plan.md) +- [Infrastructure Overview](../infrastructure-overview.md) +- [Local Testing Setup](../local-testing-setup.md) + +## ๐ŸŽฏ Success Criteria + +- [ ] Multiple providers supported with same command interface +- [ ] Existing local workflow unchanged and unaffected +- [ ] Hetzner provider fully functional with production deployment +- [ ] Provider abstraction allows easy addition of future providers +- [ ] Twelve-factor methodology compliance maintained +- [ ] Comprehensive documentation and testing coverage +- [ ] Migration path clearly documented for existing users + +--- + +**Status**: ๐Ÿ“‹ **Planning Phase** - Ready for implementation when development capacity available + +**Priority**: ๐Ÿš€ **High** - Critical for Hetzner migration and future cloud provider support + +**Effort**: ๐Ÿ“Š **Medium** - ~3-4 weeks development + 1 week testing + +**Dependencies**: Twelve-factor refactoring completion (โœ… Complete) diff --git a/infrastructure/docs/refactoring/twelve-factor-refactor/README.md b/infrastructure/docs/refactoring/twelve-factor-refactor/README.md index 82295ee..70ccaf6 100644 --- a/infrastructure/docs/refactoring/twelve-factor-refactor/README.md +++ b/infrastructure/docs/refactoring/twelve-factor-refactor/README.md @@ -2,30 +2,38 @@ ## ๐Ÿ“‹ Implementation Status -๐Ÿšง **IN PROGRESS**: Twelve-factor refactoring is partially implemented with solid foundation completed +โœ… **PHASE 1 COMPLETE**: Twelve-factor configuration management system is fully implemented and operational ### โœ… Recently Completed (July 2025) -#### Infrastructure/Application Separation +#### Infrastructure/Application Separation โœ… COMPLETE - โœ… **Infrastructure provisioning**: `provision-infrastructure.sh` handles VM setup only - โœ… **Application deployment**: `deploy-app.sh` handles application configuration - โœ… **Local repository deployment**: Uses git archive instead of GitHub clone - โœ… **Integration testing workflow**: 100% reliable end-to-end deployment -#### Quality Improvements +#### Configuration Management System โœ… COMPLETE + +- โœ… **Environment-based templates**: Fully implemented with local.env and production.env.tpl +- โœ… **Automated configuration generation**: `configure-env.sh` script working +- โœ… **Template processing**: All configuration templates (.tpl files) implemented +- โœ… **Secret externalization**: Environment-based secret management working +- โœ… **Multi-environment support**: Local environment fully operational, production ready + +#### Quality Improvements โœ… COMPLETE - โœ… **Database migration**: Successfully migrated from SQLite to MySQL in local environment - โœ… **Endpoint validation**: Updated health checks for nginx proxy architecture - โœ… **SSH authentication**: Proper key-based authentication throughout - โœ… **Linting compliance**: All YAML, Shell, and Markdown files pass validation +- โœ… **Three-layer testing**: Complete CI/CD test architecture implemented -### ๐Ÿšง **IN PROGRESS**: Core Configuration Management +### ๐Ÿšง **NEXT PHASE**: Multi-Cloud Provider Support -- โŒ **Environment-based templates**: Not yet implemented -- โŒ **Automated configuration generation**: Pending -- โŒ **Secret externalization**: Still needed -- โŒ **Multi-environment support**: Partially complete +- ๐Ÿšง **Hetzner cloud integration**: Planning stage +- ๐Ÿšง **Production deployment automation**: Design phase +- ๐Ÿšง **Advanced operational features**: Future enhancement ## Executive Summary @@ -72,24 +80,24 @@ From the official Torrust Tracker documentation, we need to account for: consistency, and frequent updates over peak performance (see [ADR-002](../../../docs/adr/002-docker-for-all-services.md)) -### Twelve-Factor Violations Identified +### Twelve-Factor Violations Assessment (Updated July 2025) -| Factor | Current Issue | Impact | -| ------------------------ | --------------------------------------------------------------- | ------ | -| **I. Codebase** | โœ… Good - Single repo with multiple environments | None | -| **II. Dependencies** | โš ๏ธ Partial - Dependencies in cloud-init, not isolated | Medium | -| **III. Config** | โŒ Config mixed in files and env vars, not environment-specific | High | -| **IV. Backing Services** | โœ… Good - Services are attachable resources | None | -| **V. Build/Release/Run** | โŒ No clear separation, deployment mixed with infrastructure | High | -| **VI. Processes** | โœ… Good - Stateless application processes | None | -| **VII. Port Binding** | โœ… Good - Services export via port binding | None | -| **VIII. Concurrency** | โœ… Good - Can scale via process model | None | -| **IX. Disposability** | โš ๏ธ Partial - VMs not quickly disposable due to app coupling | Medium | -| **X. Dev/Prod Parity** | โŒ Local and production have different deployment paths | High | -| **XI. Logs** | โœ… Good - Docker logging configured | None | -| **XII. Admin Processes** | โš ๏ธ Partial - No clear admin process separation | Low | +| Factor | Current State | Status | +| ------------------------ | ------------------------------------------------------------ | ------------ | +| **I. Codebase** | โœ… Single repo with multiple environments | โœ… Compliant | +| **II. Dependencies** | โœ… Dependencies properly declared via Docker/cloud-init | โœ… Compliant | +| **III. Config** | โœ… Configuration via environment variables and templates | โœ… Compliant | +| **IV. Backing Services** | โœ… MySQL, Prometheus, Grafana as attachable resources | โœ… Compliant | +| **V. Build/Release/Run** | โœ… Clear separation: infra-apply โ†’ app-deploy โ†’ health-check | โœ… Compliant | +| **VI. Processes** | โœ… Stateless application processes | โœ… Compliant | +| **VII. Port Binding** | โœ… Services export via port binding | โœ… Compliant | +| **VIII. Concurrency** | โœ… Process model allows scaling | โœ… Compliant | +| **IX. Disposability** | โœ… VMs quickly disposable via infra-destroy | โœ… Compliant | +| **X. Dev/Prod Parity** | โœ… Same deployment process for local/production environments | โœ… Compliant | +| **XI. Logs** | โœ… Docker logging configured with retention | โœ… Compliant | +| **XII. Admin Processes** | โœ… Health checks and admin processes properly separated | โœ… Compliant | @@ -109,9 +117,9 @@ The refactored architecture will separate infrastructure provisioning from application deployment, ensuring twelve-factor compliance while maintaining the flexibility to deploy to multiple cloud providers. -## ๐Ÿ“‹ Detailed Implementation Plan +## ๐Ÿ“‹ Detailed Implementation Status -### Phase 1: Foundation & Configuration โœ…๐Ÿšง (PARTIALLY COMPLETE) +### Phase 1: Foundation & Configuration โœ… COMPLETE **Objective**: Establish twelve-factor configuration and deployment foundation @@ -122,23 +130,30 @@ the flexibility to deploy to multiple cloud providers. - โœ… **Clean separation**: Infrastructure and application concerns clearly separated - โœ… **Local repository deployment**: Uses git archive for testing local changes -#### ๐Ÿšง 1.2 Configuration Management (IN PROGRESS) +#### โœ… 1.2 Configuration Management (COMPLETED) -- โŒ **Environment structure**: Create `infrastructure/config/environments/` directory -- โŒ **Configuration templates**: Implement `.tpl` files for all configurations -- โŒ **Environment variables**: Replace hardcoded values with environment-based config -- โŒ **Configuration script**: Create `configure-env.sh` for template processing +- โœ… **Environment structure**: `infrastructure/config/environments/` directory implemented +- โœ… **Configuration templates**: `.tpl` files for all configurations implemented + - `tracker.toml.tpl` - Torrust Tracker configuration + - `docker-compose.env.tpl` - Docker environment variables + - `nginx.conf.tpl` - Nginx proxy configuration + - `prometheus.yml.tpl` - Prometheus monitoring configuration +- โœ… **Environment variables**: All hardcoded values replaced with environment-based config +- โœ… **Configuration script**: `configure-env.sh` for template processing implemented and working +- โœ… **Environment files**: Local environment (`local.env`) operational, production template + (`production.env.tpl`) ready #### โœ… 1.3 Integration Testing (COMPLETED) - โœ… **End-to-end workflow**: Complete deployment and validation working -- โœ… **Health checks**: 14/14 validation tests passing consistently +- โœ… **Health checks**: Comprehensive validation tests passing consistently - โœ… **Database migration**: Local environment using MySQL (production parity) - โœ… **Quality assurance**: All linting and syntax validation passing +- โœ… **Three-layer testing**: Project-wide, infrastructure, and application layer tests implemented -**Status**: Infrastructure separation complete, configuration management pending +**Status**: Phase 1 is fully complete and operational -### Phase 2: Build/Release/Run Separation โœ…๐Ÿšง (PARTIALLY COMPLETE) +### Phase 2: Build/Release/Run Separation โœ… COMPLETE **Objective**: Implement clear separation of build, release, and run stages for **application deployment** @@ -151,17 +166,17 @@ specifically to application deployment. - โœ… **Network setup**: UFW firewall, SSH configuration via cloud-init - โœ… **Base system preparation**: Docker, base tools installed during provisioning -#### ๐Ÿšง 2.2 Application Build Stage (PARTIALLY COMPLETE) +#### โœ… 2.2 Application Build Stage (COMPLETED) -- โœ… **Code compilation**: Application deployment from local repository -- โŒ **Container building**: Not yet building application containers +- โœ… **Code compilation**: Application deployment from local repository working +- โœ… **Configuration generation**: Template processing fully implemented - โœ… **Dependency resolution**: Runtime dependencies handled via Docker services -#### ๐Ÿšง 2.3 Application Release Stage (PARTIALLY COMPLETE) +#### โœ… 2.3 Application Release Stage (COMPLETED) -- โœ… **Application deployment**: Working deployment mechanism -- โŒ **Configuration injection**: Still using hardcoded configuration files -- โœ… **Service orchestration**: Docker Compose working for all services +- โœ… **Configuration injection**: Environment-based configuration templates working +- โœ… **Application deployment**: Working deployment mechanism with proper config generation +- โœ… **Service orchestration**: Docker Compose working with generated configurations #### โœ… 2.4 Application Run Stage (COMPLETED) @@ -169,133 +184,108 @@ specifically to application deployment. - โœ… **Health monitoring**: Comprehensive health checks implemented - โœ… **Logging**: Docker logging configured and operational -**Status**: Build and Run stages complete, Release stage needs configuration templates +**Status**: All Build, Release, and Run stages are fully implemented and operational -### Phase 3: Multi-Environment Support ๐ŸšงโŒ (NOT STARTED) +### Phase 3: Multi-Environment Support โœ…๐Ÿšง (FOUNDATION COMPLETE) **Objective**: Enable deployment to multiple environments and cloud providers -#### โŒ 3.1 Environment Abstraction (NOT STARTED) +#### โœ… 3.1 Environment Abstraction (COMPLETED) -- โŒ **Local environment**: Template-based configuration for local development -- โŒ **Production environment**: Template-based configuration for Hetzner -- โŒ **Environment switching**: Single command to deploy to different environments -- โŒ **Provider abstraction**: Support for multiple cloud providers +- โœ… **Local environment**: Template-based configuration for local development implemented +- โœ… **Production environment**: Template-based configuration for production ready +- โœ… **Environment switching**: Commands implemented (`make infra-config-local`, `make infra-config-production`) +- ๐Ÿšง **Provider abstraction**: Local provider complete, cloud providers planned -#### โŒ 3.2 Cloud Provider Support (NOT STARTED) +#### ๐Ÿšง 3.2 Cloud Provider Support (PLANNED) -- โŒ **Hetzner integration**: Terraform/OpenTofu configurations for Hetzner -- โŒ **Multi-cloud capability**: Abstract provider interface -- โŒ **Network configuration**: Provider-specific networking setup +- ๐Ÿšง **Hetzner integration**: Terraform/OpenTofu configurations planned +- ๐Ÿšง **Multi-cloud capability**: Abstract provider interface in design +- ๐Ÿšง **Network configuration**: Provider-specific networking setup planned -**Status**: Planned but not yet implemented +**Status**: Local environment fully implemented, cloud provider support is next major milestone -### Phase 4: Operational Excellence ๐ŸšงโŒ (NOT STARTED) +### Phase 4: Operational Excellence ๐Ÿšง (PLANNED) **Objective**: Implement production-ready operational practices -#### โŒ 4.1 Monitoring & Observability (NOT STARTED) +#### ๐Ÿšง 4.1 Monitoring & Observability (FOUNDATION IMPLEMENTED) -- โŒ **Centralized logging**: Log aggregation and analysis -- โŒ **Advanced metrics**: Performance and business metrics -- โŒ **Alerting**: Automated alerts for critical issues +- โœ… **Basic monitoring**: Prometheus and Grafana operational +- ๐Ÿšง **Centralized logging**: Log aggregation and analysis planned +- ๐Ÿšง **Advanced metrics**: Performance and business metrics planned +- ๐Ÿšง **Alerting**: Automated alerts for critical issues planned -#### โŒ 4.2 Maintenance & Updates (NOT STARTED) +#### ๐Ÿšง 4.2 Maintenance & Updates (PLANNED) -- โŒ **Rolling deployments**: Zero-downtime deployments -- โŒ **Backup automation**: Automated backup procedures -- โŒ **Disaster recovery**: Comprehensive recovery procedures +- ๐Ÿšง **Rolling deployments**: Zero-downtime deployments planned +- ๐Ÿšง **Backup automation**: Automated backup procedures planned +- ๐Ÿšง **Disaster recovery**: Comprehensive recovery procedures planned -**Status**: Future enhancement +**Status**: Basic monitoring implemented, advanced operational features planned -## ๐Ÿš€ Next Steps: Complete Configuration Management +## ๐Ÿš€ Current State: Production-Ready Twelve-Factor Implementation -### Immediate Priority (Phase 1.2) +### What's Working Now (July 2025) -The next major milestone is completing the configuration management system: +โœ… **Complete Twelve-Factor Compliance**: All 12 factors fully implemented +โœ… **Infrastructure/Application Separation**: Clean separation with dedicated scripts +โœ… **Configuration Management**: Template-based system with environment switching +โœ… **Integration Testing**: Comprehensive three-layer test architecture +โœ… **Local Development**: Complete local testing environment with MySQL +โœ… **Health Validation**: Comprehensive validation and monitoring +โœ… **Quality Assurance**: All linting, syntax, and compliance standards met -#### 1. Create Environment Structure +### Available Commands -```bash -# Create directory structure -mkdir -p infrastructure/config/environments -mkdir -p infrastructure/config/templates -mkdir -p application/config/templates +#### Environment Configuration -# Create environment files -infrastructure/config/environments/local.env -infrastructure/config/environments/production.env +```bash +make infra-config-local # Generate local environment configuration +make infra-config-production # Generate production environment configuration +make infra-validate-config # Validate all environment configurations ``` -#### 2. Implement Configuration Templates - -- **Tracker configuration**: `infrastructure/config/templates/tracker.toml.tpl` -- **Docker Compose**: `application/config/templates/compose.yaml.tpl` -- **Environment variables**: Template-based `.env` generation - -#### 3. Build Configuration Processing +#### Twelve-Factor Deployment Workflow -- **Configuration script**: `infrastructure/scripts/configure-env.sh` -- **Template processing**: Replace variables with environment-specific values -- **Validation**: Ensure all required variables are set - -#### 4. Update Deployment Scripts +```bash +# Full workflow +make dev-deploy ENVIRONMENT=local # Complete infrastructure + application deployment -- **Integration**: Use configuration templates in deployment -- **Validation**: Test multi-environment configuration -- **Documentation**: Update guides for new workflow +# Individual stages +make infra-apply ENVIRONMENT=local # Infrastructure provisioning (platform setup) +make app-deploy ENVIRONMENT=local # Application deployment (Build + Release + Run) +make app-health-check ENVIRONMENT=local # Validation and health checks +``` -### Implementation Checklist +#### Testing and Validation -- [ ] **Environment structure**: Create config directories and files -- [ ] **Template system**: Implement `.tpl` files for all configurations -- [ ] **Configuration script**: Build template processing system -- [ ] **Environment variables**: Replace hardcoded values -- [ ] **Validation system**: Ensure configuration correctness -- [ ] **Integration testing**: Test new configuration system -- [ ] **Documentation update**: Reflect new workflow +```bash +make test-ci # Complete project validation (all layers) +make infra-test-ci # Infrastructure-only tests +make app-test-ci # Application-only tests +make lint # Syntax validation +``` -### Success Criteria +### What's Next: Cloud Provider Expansion -Configuration management will be considered complete when: +๐Ÿšง **Hetzner Cloud Integration**: Production cloud deployment +๐Ÿšง **Advanced Monitoring**: Enhanced observability features +๐Ÿšง **Operational Excellence**: Advanced deployment and recovery procedures -1. **Environment switching**: Single command deploys to different environments -2. **No hardcoded values**: All configuration via environment variables -3. **Template validation**: All templates process correctly -4. **Documentation**: Clear guide for adding new environments +The twelve-factor foundation is complete and ready for cloud provider expansion! ## ๐Ÿ—๏ธ Technical Architecture -### Current Working Architecture - -```text -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Infrastructure Layer โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ โ€ข VM Provisioning (provision-infrastructure.sh) โ”‚ -โ”‚ โ€ข Base System Setup (cloud-init) โ”‚ -โ”‚ โ€ข Network Configuration (UFW, networking) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Application Layer โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ โ€ข Application Deployment (deploy-app.sh) โ”‚ -โ”‚ โ€ข Service Configuration (Docker Compose) โ”‚ -โ”‚ โ€ข Health Validation (health-check.sh) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### Target Architecture (After Configuration Management) +### Current Working Architecture (Twelve-Factor Compliant) ```text โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Configuration Management โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ โ€ข Environment Templates (local.env, production.env) โ”‚ +โ”‚ โ€ข Environment Templates (local.env, production.env.tpl) โ”‚ โ”‚ โ€ข Configuration Processing (configure-env.sh) โ”‚ -โ”‚ โ€ข Template Rendering (.tpl โ†’ actual configs) โ”‚ +โ”‚ โ€ข Template Rendering (.tpl โ†’ actual configs) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ–ผ @@ -304,7 +294,7 @@ Configuration management will be considered complete when: โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ€ข VM Provisioning (provision-infrastructure.sh) โ”‚ โ”‚ โ€ข Environment-specific Setup (templated cloud-init) โ”‚ -โ”‚ โ€ข Provider Abstraction (local/hetzner/aws) โ”‚ +โ”‚ โ€ข Provider Abstraction (local implemented, cloud planned) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ–ผ @@ -317,6 +307,31 @@ Configuration management will be considered complete when: โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` +### Twelve-Factor Implementation Details + +#### Build, Release, Run Separation + +- **Infrastructure Provisioning**: Platform setup (separate from twelve-factor) +- **Build Stage**: Configuration template processing, code preparation +- **Release Stage**: Environment-specific configuration injection +- **Run Stage**: Service orchestration and health monitoring + +#### Configuration Management + +- **Environment Templates**: `local.env`, `production.env.tpl` +- **Configuration Templates**: + - `tracker.toml.tpl` - Core tracker configuration + - `docker-compose.env.tpl` - Docker environment variables + - `nginx.conf.tpl` - Reverse proxy configuration + - `prometheus.yml.tpl` - Monitoring configuration +- **Template Processing**: Automated via `configure-env.sh` + +#### Testing Architecture + +- **Project-wide tests**: Global concerns and orchestration +- **Infrastructure tests**: Platform-specific validation +- **Application tests**: Service-specific validation + ## ๐Ÿ“š Integration Testing Improvements (Completed) This section documents the integration testing workflow improvements that were @@ -370,21 +385,38 @@ completed as part of the foundation work. ### What's Working Now (July 2025) -โœ… **Infrastructure/Application Separation**: Clean separation implemented -โœ… **Integration Testing**: 100% reliable deployment workflow -โœ… **Local Development**: Test local changes without pushing to GitHub -โœ… **Database Parity**: MySQL working in local environment +โœ… **Complete Twelve-Factor Compliance**: All 12 factors fully implemented and operational +โœ… **Infrastructure/Application Separation**: Clean separation with dedicated scripts +โœ… **Configuration Management**: Template-based configuration system fully implemented +โœ… **Local Development**: Complete local testing environment with MySQL parity +โœ… **Environment Switching**: Commands for local and production configuration โœ… **Health Validation**: Comprehensive 14-test validation suite -โœ… **Quality Assurance**: All linting and standards compliance +โœ… **Quality Assurance**: Complete three-layer testing architecture +โœ… **Integration Testing**: 100% reliable deployment workflow + +### Available Now + +๐Ÿš€ **Production-Ready Commands**: + +- `make dev-deploy ENVIRONMENT=local` - Complete deployment workflow +- `make infra-config-local` - Generate local environment configuration +- `make app-deploy ENVIRONMENT=local` - Deploy application with templates +- `make test-ci` - Complete project validation + +๐Ÿš€ **Configuration System**: + +- Environment-based templates for all configurations +- Automated secret management +- Template processing with validation +- Multi-environment support foundation ### What's Next -๐Ÿšง **Configuration Management**: Template-based configuration system -๐Ÿšง **Multi-Environment**: Support for local/production/staging environments -๐Ÿšง **Production Deployment**: Hetzner cloud provider integration -๐Ÿšง **Operational Excellence**: Advanced monitoring and deployment features +๐Ÿšง **Cloud Provider Integration**: Hetzner cloud deployment (next major milestone) +๐Ÿšง **Advanced Monitoring**: Enhanced observability and alerting +๐Ÿšง **Operational Excellence**: Advanced deployment and recovery procedures -The foundation is solid and the next phase is ready to begin! +The twelve-factor foundation is complete - the project is ready for cloud expansion! ## ๐Ÿ› ๏ธ Detailed Migration Guide diff --git a/infrastructure/scripts/configure-env.sh b/infrastructure/scripts/configure-env.sh index d43e205..d7249e6 100755 --- a/infrastructure/scripts/configure-env.sh +++ b/infrastructure/scripts/configure-env.sh @@ -17,6 +17,22 @@ VERBOSE="${VERBOSE:-false}" # shellcheck source=../../scripts/shell-utils.sh source "${PROJECT_ROOT}/scripts/shell-utils.sh" +# Setup local environment from template +setup_local_environment() { + local env_file="${CONFIG_DIR}/environments/local.env" + local template_file="${CONFIG_DIR}/environments/local.env.tpl" + + # Always regenerate local.env from template for consistency + if [[ ! -f "${template_file}" ]]; then + log_error "Local template not found: ${template_file}" + exit 1 + fi + + log_info "Creating local.env from template..." + cp "${template_file}" "${env_file}" + log_success "Local environment file created from template: ${env_file}" +} + # Setup production environment from template setup_production_environment() { local env_file="${CONFIG_DIR}/environments/production.env" @@ -55,9 +71,11 @@ setup_production_environment() { load_environment() { local env_file="${CONFIG_DIR}/environments/${ENVIRONMENT}.env" - # Special handling for production environment + # Special handling for template-based environments if [[ "${ENVIRONMENT}" == "production" ]]; then setup_production_environment + elif [[ "${ENVIRONMENT}" == "local" ]]; then + setup_local_environment fi if [[ ! -f "${env_file}" ]]; then @@ -132,10 +150,13 @@ process_templates() { # Generate .env file for Docker Compose generate_docker_env() { local templates_dir="${CONFIG_DIR}/templates" - local env_output="${PROJECT_ROOT}/application/.env" + local env_output="${PROJECT_ROOT}/application/storage/compose/.env" log_info "Generating Docker Compose environment file" + # Ensure the storage/compose directory exists + mkdir -p "$(dirname "${env_output}")" + # Set generation date for template GENERATION_DATE="$(date)" export GENERATION_DATE diff --git a/infrastructure/scripts/deploy-app.sh b/infrastructure/scripts/deploy-app.sh index 2c63658..b7ec2f1 100755 --- a/infrastructure/scripts/deploy-app.sh +++ b/infrastructure/scripts/deploy-app.sh @@ -46,6 +46,66 @@ get_vm_ip() { echo "${vm_ip}" } +# Check git repository status and warn about uncommitted changes +check_git_status() { + log_info "Checking git repository status..." + + cd "${PROJECT_ROOT}" + + # Check if we're in a git repository + if ! git rev-parse --git-dir >/dev/null 2>&1; then + log_warning "Not in a git repository - deployment will use current directory state" + return 0 + fi + + # Check for uncommitted changes in configuration templates + local config_changes + config_changes=$(git status --porcelain infrastructure/config/environments/ 2>/dev/null || echo "") + + if [[ -n "${config_changes}" ]]; then + log_warning "===============================================" + log_warning "โš ๏ธ UNCOMMITTED CONFIGURATION CHANGES DETECTED" + log_warning "===============================================" + log_warning "The following configuration template files have uncommitted changes:" + echo "${config_changes}" | while IFS= read -r line; do + log_warning " ${line}" + done + log_warning "" + log_warning "IMPORTANT: Deployment uses 'git archive' which only includes committed files." + log_warning "Your uncommitted changes will NOT be deployed to the VM." + log_warning "" + log_warning "To include these changes in deployment:" + log_warning " 1. git add infrastructure/config/environments/" + log_warning " 2. git commit -m 'update: configuration templates'" + log_warning " 3. Re-run deployment" + log_warning "" + log_warning "To continue without committing (deployment will use last committed version):" + log_warning " Press ENTER to continue or Ctrl+C to abort" + log_warning "===============================================" + read -r + fi + + # Check for any other uncommitted changes (informational) + local all_changes + all_changes=$(git status --porcelain 2>/dev/null | wc -l) + + if [[ "${all_changes}" -gt 0 ]]; then + local git_status + git_status=$(git status --short 2>/dev/null || echo "") + log_info "Repository has ${all_changes} uncommitted changes (git archive will use committed version)" + if [[ "${all_changes}" -le 10 ]]; then + log_info "Uncommitted files:" + echo "${git_status}" | while IFS= read -r line; do + log_info " ${line}" + done + else + log_info "Run 'git status' to see all uncommitted changes" + fi + else + log_success "Repository working tree is clean - deployment will match current state" + fi +} + # Test SSH connectivity and wait for system readiness test_ssh_connection() { local vm_ip="$1" @@ -181,6 +241,29 @@ vm_exec_with_timeout() { fi } +# Generate configuration locally (Build/Release stage) +generate_configuration_locally() { + log_info "Generating configuration locally (Build/Release stage)" + + cd "${PROJECT_ROOT}" + + if [[ -f "infrastructure/scripts/configure-env.sh" ]]; then + log_info "Running configure-env.sh for environment: ${ENVIRONMENT}" + ./infrastructure/scripts/configure-env.sh "${ENVIRONMENT}" + + # Verify that the .env file was generated + if [[ -f "application/storage/compose/.env" ]]; then + log_success "Configuration files generated successfully" + else + log_error "Failed to generate .env file at application/storage/compose/.env" + exit 1 + fi + else + log_warning "Configuration script not found at infrastructure/scripts/configure-env.sh" + log_warning "Using existing configuration files" + fi +} + # RELEASE STAGE: Deploy application code and configuration release_stage() { local vm_ip="$1" @@ -204,31 +287,13 @@ release_stage() { # Create target directory structure vm_exec "${vm_ip}" "mkdir -p /home/torrust/github/torrust" "Creating directory structure" - # Check if we need to preserve storage before removing repository - storage_exists=$(ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 "torrust@${vm_ip}" " - if [ -d /home/torrust/github/torrust/torrust-tracker-demo/application/storage ]; then - echo 'true' - else - echo 'false' - fi - " 2>/dev/null || echo "false") - - if [[ "${storage_exists}" == "true" ]]; then - log_warning "Preserving existing storage folder with persistent data" - fi - - # Handle existing repository - preserve storage folder if it exists + # Handle existing repository vm_exec "${vm_ip}" " if [ -d /home/torrust/github/torrust/torrust-tracker-demo ]; then - if [ -d /home/torrust/github/torrust/torrust-tracker-demo/application/storage ]; then - # Move storage folder to temporary location - mv /home/torrust/github/torrust/torrust-tracker-demo/application/storage /tmp/torrust-storage-backup-\$(date +%s) || true - fi - - # Remove the repository directory (excluding storage) + # Remove the repository directory rm -rf /home/torrust/github/torrust/torrust-tracker-demo fi - " "Removing existing repository (preserving storage)" + " "Removing existing repository" # Copy archive to VM if ! scp -o StrictHostKeyChecking=no "${temp_archive}" "torrust@${vm_ip}:/tmp/"; then @@ -240,30 +305,9 @@ release_stage() { # Extract archive on VM vm_exec "${vm_ip}" "cd /home/torrust/github/torrust && mkdir -p torrust-tracker-demo" "Creating repository directory" vm_exec "${vm_ip}" "cd /home/torrust/github/torrust/torrust-tracker-demo && tar -xzf /tmp/$(basename "${temp_archive}")" "Extracting repository" + vm_exec "${vm_ip}" "rm -f /tmp/$(basename "${temp_archive}")" "Cleaning up temp files" - # Restore storage folder if it was backed up - vm_exec "${vm_ip}" " - storage_backup=\$(ls /tmp/torrust-storage-backup-* 2>/dev/null | head -1 || echo '') - if [ -n \"\$storage_backup\" ] && [ -d \"\$storage_backup\" ]; then - rm -rf /home/torrust/github/torrust/torrust-tracker-demo/application/storage - mv \"\$storage_backup\" /home/torrust/github/torrust/torrust-tracker-demo/application/storage - fi - " "Restoring preserved storage folder" - - # Check if storage was restored and log appropriately - storage_restored=$(ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 "torrust@${vm_ip}" " - if [ -d /home/torrust/github/torrust/torrust-tracker-demo/application/storage/mysql ] || [ -d /home/torrust/github/torrust/torrust-tracker-demo/application/storage/tracker ]; then - echo 'true' - else - echo 'false' - fi - " 2>/dev/null || echo "false") - - if [[ "${storage_restored}" == "true" ]]; then - log_info "Storage folder restored with existing persistent data" - fi - # Clean up local temp file rm -f "${temp_archive}" @@ -272,18 +316,7 @@ release_stage() { log_success "Local repository deployed successfully" - # Process configuration (Release stage - combining code with config) - vm_exec "${vm_ip}" " - cd /home/torrust/github/torrust/torrust-tracker-demo - - if [ -f infrastructure/scripts/configure-env.sh ]; then - ./infrastructure/scripts/configure-env.sh ${ENVIRONMENT} - else - echo 'Configuration script not found, using defaults' - fi - " "Processing configuration for environment: ${ENVIRONMENT}" - - # Ensure proper permissions + # Set up persistent data volume and copy locally generated configuration files directly vm_exec "${vm_ip}" " cd /home/torrust/github/torrust/torrust-tracker-demo @@ -292,9 +325,47 @@ release_stage() { sudo ./infrastructure/scripts/fix-volume-permissions.sh fi - # Ensure storage directories exist - mkdir -p application/storage/{tracker/lib/database,prometheus/data} - " "Setting up application storage" + # Ensure persistent storage directories exist + sudo mkdir -p /var/lib/torrust/{tracker/{lib/database,log,etc},prometheus/{data,etc},proxy/{webroot,etc/nginx-conf},certbot/{etc,lib},dhparam,mysql/init,compose} + + # Ensure torrust user owns all persistent data directories + sudo chown -R torrust:torrust /var/lib/torrust + " "Setting up persistent data volume directory structure" + + # Copy locally generated configuration files directly to persistent volume + log_info "Copying locally generated configuration files to persistent volume..." + + # Copy tracker configuration + if [[ -f "${PROJECT_ROOT}/application/storage/tracker/etc/tracker.toml" ]]; then + log_info "Copying tracker configuration..." + scp -o StrictHostKeyChecking=no "${PROJECT_ROOT}/application/storage/tracker/etc/tracker.toml" "torrust@${vm_ip}:/tmp/tracker.toml" + vm_exec "${vm_ip}" "sudo mv /tmp/tracker.toml /var/lib/torrust/tracker/etc/tracker.toml && sudo chown torrust:torrust /var/lib/torrust/tracker/etc/tracker.toml" + fi + + # Copy prometheus configuration + if [[ -f "${PROJECT_ROOT}/application/storage/prometheus/etc/prometheus.yml" ]]; then + log_info "Copying prometheus configuration..." + scp -o StrictHostKeyChecking=no "${PROJECT_ROOT}/application/storage/prometheus/etc/prometheus.yml" "torrust@${vm_ip}:/tmp/prometheus.yml" + vm_exec "${vm_ip}" "sudo mv /tmp/prometheus.yml /var/lib/torrust/prometheus/etc/prometheus.yml && sudo chown torrust:torrust /var/lib/torrust/prometheus/etc/prometheus.yml" + fi + + # Copy nginx configuration + if [[ -f "${PROJECT_ROOT}/application/storage/proxy/etc/nginx-conf/nginx.conf" ]]; then + log_info "Copying nginx configuration..." + scp -o StrictHostKeyChecking=no "${PROJECT_ROOT}/application/storage/proxy/etc/nginx-conf/nginx.conf" "torrust@${vm_ip}:/tmp/nginx.conf" + vm_exec "${vm_ip}" "sudo mv /tmp/nginx.conf /var/lib/torrust/proxy/etc/nginx-conf/nginx.conf && sudo chown torrust:torrust /var/lib/torrust/proxy/etc/nginx-conf/nginx.conf" + fi + + # Copy Docker Compose .env file + if [[ -f "${PROJECT_ROOT}/application/storage/compose/.env" ]]; then + log_info "Copying Docker Compose environment file..." + scp -o StrictHostKeyChecking=no "${PROJECT_ROOT}/application/storage/compose/.env" "torrust@${vm_ip}:/tmp/compose.env" + vm_exec "${vm_ip}" "sudo mv /tmp/compose.env /var/lib/torrust/compose/.env && sudo chown torrust:torrust /var/lib/torrust/compose/.env" + else + log_error "No .env file found at ${PROJECT_ROOT}/application/storage/compose/.env" + log_error "Configuration should have been generated locally before deployment" + exit 1 + fi log_success "Release stage completed" } @@ -311,7 +382,7 @@ wait_for_services() { log_info "Checking container status (attempt ${attempt}/${max_attempts})..." # Get container status with service names only - services=$(ssh -n -o StrictHostKeyChecking=no -o ConnectTimeout=10 "torrust@${vm_ip}" "cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose ps --services" 2>/dev/null || echo "SSH_FAILED") + services=$(ssh -n -o StrictHostKeyChecking=no -o ConnectTimeout=10 "torrust@${vm_ip}" "cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose --env-file /var/lib/torrust/compose/.env ps --services" 2>/dev/null || echo "SSH_FAILED") if [[ "${services}" == "SSH_FAILED" ]]; then log_warning "SSH connection failed while checking container status. Retrying in 10 seconds..." @@ -337,7 +408,7 @@ wait_for_services() { container_count=$((container_count + 1)) # Get the container state and health for this service - container_info=$(ssh -n -o StrictHostKeyChecking=no -o ConnectTimeout=10 "torrust@${vm_ip}" "cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose ps ${service_name} --format '{{.State}}'" 2>/dev/null) + container_info=$(ssh -n -o StrictHostKeyChecking=no -o ConnectTimeout=10 "torrust@${vm_ip}" "cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose --env-file /var/lib/torrust/compose/.env ps ${service_name} --format '{{.State}}'" 2>/dev/null) health_status=$(ssh -n -o StrictHostKeyChecking=no -o ConnectTimeout=10 "torrust@${vm_ip}" "cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker inspect ${service_name} --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}no-healthcheck{{end}}' 2>/dev/null" || echo "no-healthcheck") # Clean up output @@ -387,7 +458,7 @@ wait_for_services() { done log_error "Timeout waiting for services to become healthy after ${max_attempts} attempts." - vm_exec "${vm_ip}" "cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose ps && docker compose logs" "Dumping logs on failure" + vm_exec "${vm_ip}" "cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose --env-file /var/lib/torrust/compose/.env ps && docker compose --env-file /var/lib/torrust/compose/.env logs" "Dumping logs on failure" exit 1 } @@ -403,7 +474,7 @@ run_stage() { cd /home/torrust/github/torrust/torrust-tracker-demo/application if [ -f compose.yaml ]; then - docker compose down --remove-orphans || true + docker compose --env-file /var/lib/torrust/compose/.env down --remove-orphans || true fi " "Stopping existing services" @@ -414,7 +485,7 @@ run_stage() { # Pull images with progress output echo 'Starting Docker image pull...' - docker compose pull + docker compose --env-file /var/lib/torrust/compose/.env pull echo 'Docker image pull completed' " 600 "Pulling Docker images with 10-minute timeout" @@ -423,7 +494,7 @@ run_stage() { cd /home/torrust/github/torrust/torrust-tracker-demo/application # Start services - docker compose up -d + docker compose --env-file /var/lib/torrust/compose/.env up -d " "Starting application services" # Wait for services to initialize @@ -442,16 +513,16 @@ validate_deployment() { vm_exec "${vm_ip}" " cd /home/torrust/github/torrust/torrust-tracker-demo/application echo '=== Docker Compose Services (Detailed Status) ===' - docker compose ps --format 'table {{.Service}}\t{{.State}}\t{{.Status}}\t{{.Ports}}' + docker compose --env-file /var/lib/torrust/compose/.env ps --format 'table {{.Service}}\t{{.State}}\t{{.Status}}\t{{.Ports}}' echo '' echo '=== Docker Compose Services (Default Format) ===' - docker compose ps + docker compose --env-file /var/lib/torrust/compose/.env ps echo '' echo '=== Container Health Check Details ===' # Show health status for each container - for container in \$(docker compose ps --format '{{.Name}}'); do + for container in \$(docker compose --env-file /var/lib/torrust/compose/.env ps --format '{{.Name}}'); do echo \"Container: \$container\" state=\$(docker inspect \$container --format '{{.State.Status}}') health=\$(docker inspect \$container --format '{{.State.Health.Status}}' 2>/dev/null || echo 'no-healthcheck') @@ -467,7 +538,7 @@ validate_deployment() { done echo '=== Service Logs (last 10 lines each) ===' - docker compose logs --tail=10 + docker compose --env-file /var/lib/torrust/compose/.env logs --tail=10 " "Checking detailed service status" # Test application endpoints @@ -483,12 +554,21 @@ validate_deployment() { fi # Test API stats endpoint (through nginx proxy, requires auth) - if curl -f -s "http://localhost/api/v1/stats?token=local-dev-admin-token-12345" >/dev/null 2>&1; then + # Save response to temp file and get HTTP status code + api_http_code=\$(curl -s -o /tmp/api_response.json -w '%{http_code}' \"http://localhost/api/v1/stats?token=MyAccessToken\" 2>&1 || echo \"000\") + api_response_body=\$(cat /tmp/api_response.json 2>/dev/null || echo \"No response\") + + # Check if HTTP status is 200 (success) + if [ \"\$api_http_code\" -eq 200 ] 2>/dev/null; then echo 'โœ… API stats endpoint: OK' else echo 'โŒ API stats endpoint: FAILED' + echo \" HTTP Code: \$api_http_code\" + echo \" Response: \$api_response_body\" + rm -f /tmp/api_response.json exit 1 fi + rm -f /tmp/api_response.json # Test HTTP tracker endpoint (through nginx proxy - expects 404 for root) if curl -s -w '%{http_code}' http://localhost/ -o /dev/null | grep -q '404'; then @@ -516,15 +596,15 @@ show_connection_info() { echo echo "=== APPLICATION ENDPOINTS ===" echo "Health Check: http://${vm_ip}/health_check" # DevSkim: ignore DS137138 - echo "API Stats: http://${vm_ip}/api/v1/stats?token=local-dev-admin-token-12345" # DevSkim: ignore DS137138 + echo "API Stats: http://${vm_ip}/api/v1/stats?token=MyAccessToken" # DevSkim: ignore DS137138 echo "HTTP Tracker: http://${vm_ip}/ (for BitTorrent clients)" # DevSkim: ignore DS137138 echo "UDP Tracker: udp://${vm_ip}:6868, udp://${vm_ip}:6969" echo "Grafana: http://${vm_ip}:3100 (admin/admin)" # DevSkim: ignore DS137138 echo echo "=== NEXT STEPS ===" echo "Health Check: make app-health-check ENVIRONMENT=${ENVIRONMENT}" - echo "View Logs: ssh torrust@${vm_ip} 'cd torrust-tracker-demo/application && docker compose logs'" - echo "Stop Services: ssh torrust@${vm_ip} 'cd torrust-tracker-demo/application && docker compose down'" + echo "View Logs: ssh torrust@${vm_ip} 'cd torrust-tracker-demo/application && docker compose --env-file /var/lib/torrust/compose/.env logs'" + echo "Stop Services: ssh torrust@${vm_ip} 'cd torrust-tracker-demo/application && docker compose --env-file /var/lib/torrust/compose/.env down'" echo } @@ -533,6 +613,12 @@ main() { log_info "Starting application deployment (Twelve-Factor Release + Run Stages)" log_info "Environment: ${ENVIRONMENT}" + # Check git status and warn about uncommitted changes + check_git_status + + # LOCAL: Generate configuration (Build/Release stage) + generate_configuration_locally + local vm_ip vm_ip=$(get_vm_ip) diff --git a/infrastructure/scripts/health-check.sh b/infrastructure/scripts/health-check.sh index 99dae0d..0dadaf4 100755 --- a/infrastructure/scripts/health-check.sh +++ b/infrastructure/scripts/health-check.sh @@ -142,19 +142,23 @@ test_application_endpoints() { # Test API stats endpoint (via nginx proxy with auth) ((TOTAL_TESTS++)) - if vm_exec "${vm_ip}" "curl -f -s 'http://localhost/api/v1/stats?token=local-dev-admin-token-12345' >/dev/null 2>&1"; then + local api_response + local api_http_code + api_response=$(vm_exec "${vm_ip}" "curl -s -w '\\n%{http_code}' 'http://localhost/api/v1/stats?token=MyAccessToken'" || echo "") + api_http_code=$(echo "${api_response}" | tail -n1) + api_response=$(echo "${api_response}" | head -n -1) + + if [[ "${api_http_code}" == "200" ]]; then log_test_pass "API stats endpoint (nginx proxy)" # Get stats if verbose if [[ "${VERBOSE}" == "true" ]]; then - local stats - stats=$(vm_exec "${vm_ip}" "curl -s 'http://localhost/api/v1/stats?token=local-dev-admin-token-12345'" || echo "") - if [[ -n "${stats}" ]]; then - echo " Stats: ${stats}" - fi + echo " Stats: ${api_response}" fi else log_test_fail "API stats endpoint (nginx proxy)" + echo " HTTP Code: ${api_http_code}" + echo " Response: ${api_response}" fi # Test HTTP tracker endpoint (via nginx proxy - expects 404 for root) diff --git a/infrastructure/terraform/main.tf b/infrastructure/terraform/main.tf index dbb083a..e9ada35 100644 --- a/infrastructure/terraform/main.tf +++ b/infrastructure/terraform/main.tf @@ -53,6 +53,12 @@ variable "vm_disk_size" { default = 20 } +variable "persistent_data_size" { + description = "Persistent data volume size in GB" + type = number + default = 20 +} + variable "base_image_url" { description = "URL for the base Ubuntu cloud image" type = string @@ -85,6 +91,19 @@ resource "libvirt_volume" "vm_disk" { } } +# Create persistent data volume for application storage +resource "libvirt_volume" "persistent_data" { + name = "${var.vm_name}-data.qcow2" + format = "qcow2" + size = var.persistent_data_size * 1024 * 1024 * 1024 # Convert GB to bytes + pool = "user-default" + + # Fix permissions after creation + provisioner "local-exec" { + command = "${path.module}/../scripts/fix-volume-permissions.sh" + } +} + # Create cloud-init disk resource "libvirt_cloudinit_disk" "commoninit" { name = "${var.vm_name}-cloudinit.iso" @@ -117,6 +136,11 @@ resource "libvirt_domain" "vm" { volume_id = libvirt_volume.vm_disk.id } + # Attach persistent data volume as second disk + disk { + volume_id = libvirt_volume.persistent_data.id + } + network_interface { network_name = "default" wait_for_lease = false diff --git a/project-words.txt b/project-words.txt index c63d6dc..a4115e4 100644 --- a/project-words.txt +++ b/project-words.txt @@ -54,6 +54,7 @@ netdev newgrp newtrackon nmap +noatime NOPASSWD nosniff nullglob diff --git a/tests/test-e2e.sh b/tests/test-e2e.sh index a9021ac..4ed22c7 100755 --- a/tests/test-e2e.sh +++ b/tests/test-e2e.sh @@ -234,7 +234,7 @@ test_smoke_testing() { # Test 2: Statistics API (through nginx proxy on port 80) log_info "Testing statistics API through nginx proxy..." - if [[ $(test_http_endpoint "http://${vm_ip}:80/api/v1/stats?token=local-dev-admin-token-12345" '"torrents"') == "success" ]]; then # DevSkim: ignore DS137138 + if [[ $(test_http_endpoint "http://${vm_ip}:80/api/v1/stats?token=MyAccessToken" '"torrents"') == "success" ]]; then # DevSkim: ignore DS137138 log_success "โœ“ Statistics API working" else log_error "โœ— Statistics API failed" @@ -521,7 +521,7 @@ run_e2e_test() { return 0 else log_section "TEST RESULT: FAILURE" - log_error "End-to-end twelve-factor deployment test failed!" + log_error "End-to-end test failed!" log_error "Total test time: ${minutes}m ${seconds}s" log_error "Check test log for details: ${TEST_LOG_FILE}" return 1