#!/bin/bash # # Production Deployment Script # Deploys pre-built container images to production environment # # Usage: ./deploy-production.sh [OPTIONS] # # Options: # --cdn-update Update CDN configuration after deployment # --no-backup Skip backup creation # --retention-days N Set backup retention days (default: 30) # --domain DOMAIN Override domain name (default: michaelschiemer.de) # --vault-password-file FILE Specify vault password file # --help Show this help message set -euo pipefail # Script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" INFRA_DIR="${SCRIPT_DIR}/infrastructure" # Default values DEFAULT_DOMAIN="michaelschiemer.de" DEFAULT_RETENTION_DAYS="30" ENVIRONMENT="production" # Initialize variables IMAGE_TAG="" DOMAIN_NAME="$DEFAULT_DOMAIN" CDN_UPDATE="false" BACKUP_ENABLED="true" BACKUP_RETENTION_DAYS="$DEFAULT_RETENTION_DAYS" VAULT_PASSWORD_FILE="" EXTRA_VARS="" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Logging functions log_info() { echo -e "${BLUE}[INFO]${NC} $1" >&2 } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" >&2 } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" >&2 } log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2 } # Help function show_help() { cat << EOF Production Deployment Script for Custom PHP Framework USAGE: $0 [OPTIONS] ARGUMENTS: IMAGE_TAG Container image tag to deploy (required) Must NOT be 'latest' for production deployments OPTIONS: --cdn-update Update CDN configuration after deployment --no-backup Skip backup creation before deployment --retention-days N Set backup retention days (default: $DEFAULT_RETENTION_DAYS) --domain DOMAIN Override domain name (default: $DEFAULT_DOMAIN) --vault-password-file FILE Specify vault password file path --help Show this help message EXAMPLES: # Deploy version 1.2.3 to production $0 1.2.3 # Deploy with CDN update $0 1.2.3 --cdn-update # Deploy without backup $0 1.2.3 --no-backup # Deploy with custom retention period $0 1.2.3 --retention-days 60 ENVIRONMENT VARIABLES: ANSIBLE_VAULT_PASSWORD_FILE Vault password file (overrides --vault-password-file) IMAGE_TAG Image tag to deploy (overrides first argument) DOMAIN_NAME Domain name (overrides --domain) CDN_UPDATE Enable CDN update (overrides --cdn-update) BACKUP_ENABLED Enable/disable backup (overrides --no-backup) BACKUP_RETENTION_DAYS Backup retention days (overrides --retention-days) REQUIREMENTS: - Ansible 2.9+ - community.docker collection - SSH access to production server - Vault password file or ANSIBLE_VAULT_PASSWORD_FILE environment variable EOF } # Parse command line arguments parse_args() { if [[ $# -eq 0 ]]; then log_error "No arguments provided" show_help exit 1 fi while [[ $# -gt 0 ]]; do case $1 in --help|-h) show_help exit 0 ;; --cdn-update) CDN_UPDATE="true" shift ;; --no-backup) BACKUP_ENABLED="false" shift ;; --retention-days) if [[ -z "${2:-}" ]] || [[ "$2" =~ ^-- ]]; then log_error "--retention-days requires a number" exit 1 fi BACKUP_RETENTION_DAYS="$2" shift 2 ;; --domain) if [[ -z "${2:-}" ]] || [[ "$2" =~ ^-- ]]; then log_error "--domain requires a domain name" exit 1 fi DOMAIN_NAME="$2" shift 2 ;; --vault-password-file) if [[ -z "${2:-}" ]] || [[ "$2" =~ ^-- ]]; then log_error "--vault-password-file requires a file path" exit 1 fi VAULT_PASSWORD_FILE="$2" shift 2 ;; -*) log_error "Unknown option: $1" show_help exit 1 ;; *) if [[ -z "$IMAGE_TAG" ]]; then IMAGE_TAG="$1" else log_error "Multiple positional arguments provided. Only IMAGE_TAG is expected." show_help exit 1 fi shift ;; esac done } # Validate environment and requirements validate_environment() { log_info "Validating deployment environment..." # Check for required IMAGE_TAG if [[ -z "$IMAGE_TAG" ]]; then if [[ -n "${IMAGE_TAG:-}" ]]; then IMAGE_TAG="$IMAGE_TAG" else log_error "IMAGE_TAG is required" show_help exit 1 fi fi # Validate image tag for production if [[ "$IMAGE_TAG" == "latest" ]]; then log_error "Production deployments cannot use 'latest' tag" exit 1 fi # Override with environment variables if set DOMAIN_NAME="${DOMAIN_NAME:-$DEFAULT_DOMAIN}" CDN_UPDATE="${CDN_UPDATE:-false}" BACKUP_ENABLED="${BACKUP_ENABLED:-true}" BACKUP_RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-$DEFAULT_RETENTION_DAYS}" # Check if ansible is available if ! command -v ansible-playbook &> /dev/null; then log_error "ansible-playbook not found. Please install Ansible." exit 1 fi # Check vault password file if [[ -n "${ANSIBLE_VAULT_PASSWORD_FILE:-}" ]]; then VAULT_PASSWORD_FILE="$ANSIBLE_VAULT_PASSWORD_FILE" fi if [[ -z "$VAULT_PASSWORD_FILE" ]]; then log_warning "No vault password file specified. Ansible will prompt for vault password." elif [[ ! -f "$VAULT_PASSWORD_FILE" ]]; then log_error "Vault password file not found: $VAULT_PASSWORD_FILE" exit 1 fi # Check infrastructure directory if [[ ! -d "$INFRA_DIR" ]]; then log_error "Infrastructure directory not found: $INFRA_DIR" exit 1 fi # Check inventory file local inventory_file="${INFRA_DIR}/inventories/production/hosts.yml" if [[ ! -f "$inventory_file" ]]; then log_error "Production inventory not found: $inventory_file" exit 1 fi # Check playbook file local playbook_file="${INFRA_DIR}/playbooks/deploy-application.yml" if [[ ! -f "$playbook_file" ]]; then log_error "Deployment playbook not found: $playbook_file" exit 1 fi log_success "Environment validation complete" } # Build extra variables for ansible build_extra_vars() { EXTRA_VARS="-e IMAGE_TAG=$IMAGE_TAG" EXTRA_VARS+=" -e DOMAIN_NAME=$DOMAIN_NAME" EXTRA_VARS+=" -e CDN_UPDATE=$CDN_UPDATE" EXTRA_VARS+=" -e BACKUP_ENABLED=$BACKUP_ENABLED" EXTRA_VARS+=" -e BACKUP_RETENTION_DAYS=$BACKUP_RETENTION_DAYS" EXTRA_VARS+=" -e deploy_environment=$ENVIRONMENT" log_info "Deployment configuration:" log_info " Image Tag: $IMAGE_TAG" log_info " Domain: $DOMAIN_NAME" log_info " CDN Update: $CDN_UPDATE" log_info " Backup Enabled: $BACKUP_ENABLED" log_info " Backup Retention: $BACKUP_RETENTION_DAYS days" } # Execute deployment run_deployment() { log_info "Starting production deployment..." local ansible_cmd="ansible-playbook" local inventory="${INFRA_DIR}/inventories/production/hosts.yml" local playbook="${INFRA_DIR}/playbooks/deploy-application.yml" # Build ansible command local cmd="$ansible_cmd -i $inventory $playbook $EXTRA_VARS" # Add vault password file if specified if [[ -n "$VAULT_PASSWORD_FILE" ]]; then cmd+=" --vault-password-file $VAULT_PASSWORD_FILE" fi # Change to infrastructure directory cd "$INFRA_DIR" log_info "Executing: $cmd" # Run deployment if eval "$cmd"; then log_success "Deployment completed successfully!" log_success "Application is available at: https://$DOMAIN_NAME" return 0 else log_error "Deployment failed!" return 1 fi } # Cleanup function cleanup() { local exit_code=$? if [[ $exit_code -ne 0 ]]; then log_error "Deployment failed with exit code: $exit_code" log_info "Check the logs above for details" log_info "You may need to run rollback if the deployment was partially successful" fi exit $exit_code } # Main execution main() { # Set trap for cleanup trap cleanup EXIT # Parse command line arguments parse_args "$@" # Validate environment validate_environment # Build extra variables build_extra_vars # Run deployment run_deployment log_success "Production deployment completed successfully!" } # Execute main function if script is run directly if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi