Files
2025-07-07 01:44:12 +02:00

688 lines
17 KiB
Bash
Executable File

#!/bin/bash
# TankStopp Docker Deployment Script
# This script deploys the TankStopp application using Docker Compose
set -e # Exit on any error
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
NC='\033[0m' # No Color
# Default values
ENVIRONMENT="production"
ACTION="deploy"
SERVICE_NAME="tankstopp"
COMPOSE_FILE="docker-compose.yml"
COMPOSE_OVERRIDE=""
PROJECT_NAME="tankstopp"
BACKUP_BEFORE_DEPLOY=true
WAIT_TIMEOUT=300
HEALTH_CHECK_RETRIES=10
ROLLBACK_ON_FAILURE=true
SCALE_REPLICAS=1
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
DATA_DIR="/var/lib/tankstopp"
BACKUP_DIR="/var/lib/tankstopp/backups"
LOG_DIR="/var/log/tankstopp"
# Functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"
}
log_debug() {
if [ "$DEBUG" = true ]; then
echo -e "${PURPLE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"
fi
}
show_help() {
cat << EOF
TankStopp Docker Deployment Script
Usage: $0 [ACTION] [OPTIONS]
ACTIONS:
deploy Deploy the application (default)
start Start existing containers
stop Stop running containers
restart Restart containers
update Update to latest images
rollback Rollback to previous version
status Show deployment status
logs Show application logs
backup Create database backup
restore Restore from backup
scale Scale service replicas
cleanup Clean up unused resources
OPTIONS:
-h, --help Show this help message
-e, --env ENV Environment: development|production|staging (default: production)
-f, --file FILE Docker compose file (default: docker-compose.yml)
-o, --override FILE Docker compose override file
-p, --project NAME Project name (default: tankstopp)
-s, --service NAME Service name (default: tankstopp)
-r, --replicas COUNT Number of replicas for scaling (default: 1)
-t, --timeout SECONDS Wait timeout in seconds (default: 300)
--no-backup Skip backup before deployment
--no-rollback Don't rollback on failure
--force Force action without confirmation
--debug Enable debug logging
EXAMPLES:
# Deploy to production
$0 deploy --env production
# Deploy with custom compose file
$0 deploy --file docker-compose.prod.yml
# Start development environment
$0 start --env development
# Scale production service
$0 scale --replicas 3
# View logs
$0 logs --service tankstopp
# Backup database
$0 backup
# Rollback deployment
$0 rollback
ENVIRONMENT CONFIGURATIONS:
development - Local development with debug enabled
staging - Staging environment for testing
production - Production deployment with optimizations
EOF
}
check_requirements() {
log_info "Checking deployment requirements..."
# Check Docker
if ! command -v docker &> /dev/null; then
log_error "Docker is not installed or not in PATH"
exit 1
fi
# Check Docker Compose
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
log_error "Docker Compose is not installed or not in PATH"
exit 1
fi
# Set Docker Compose command
if docker compose version &> /dev/null; then
DOCKER_COMPOSE_CMD="docker compose"
else
DOCKER_COMPOSE_CMD="docker-compose"
fi
# Check Docker daemon
if ! docker info &> /dev/null; then
log_error "Docker daemon is not running"
exit 1
fi
# Check compose file
if [ ! -f "$PROJECT_ROOT/$COMPOSE_FILE" ]; then
log_error "Docker Compose file not found: $PROJECT_ROOT/$COMPOSE_FILE"
exit 1
fi
log_success "Requirements check passed"
}
setup_environment() {
log_info "Setting up environment: $ENVIRONMENT"
# Create necessary directories
create_directories
# Set compose files based on environment
case "$ENVIRONMENT" in
"development")
COMPOSE_OVERRIDE="docker-compose.override.yml"
;;
"staging")
COMPOSE_OVERRIDE="docker-compose.staging.yml"
;;
"production")
COMPOSE_OVERRIDE="docker-compose.prod.yml"
;;
esac
# Build compose command
COMPOSE_CMD="$DOCKER_COMPOSE_CMD -p $PROJECT_NAME -f $COMPOSE_FILE"
if [ -n "$COMPOSE_OVERRIDE" ] && [ -f "$PROJECT_ROOT/$COMPOSE_OVERRIDE" ]; then
COMPOSE_CMD="$COMPOSE_CMD -f $COMPOSE_OVERRIDE"
log_info "Using override file: $COMPOSE_OVERRIDE"
fi
log_success "Environment setup completed"
}
create_directories() {
log_info "Creating necessary directories..."
# Create directories with proper permissions
sudo mkdir -p "$DATA_DIR/data" "$BACKUP_DIR" "$LOG_DIR"
sudo chown -R 1001:1001 "$DATA_DIR" 2>/dev/null || true
sudo chmod -R 755 "$DATA_DIR" 2>/dev/null || true
log_success "Directories created"
}
backup_database() {
if [ "$BACKUP_BEFORE_DEPLOY" = false ]; then
log_info "Skipping backup (disabled)"
return 0
fi
log_info "Creating database backup..."
local backup_file="$BACKUP_DIR/fuel_stops_$(date +%Y%m%d_%H%M%S).db"
# Check if database exists
if [ ! -f "$DATA_DIR/data/fuel_stops.db" ]; then
log_warning "Database file not found, skipping backup"
return 0
fi
# Create backup
if cp "$DATA_DIR/data/fuel_stops.db" "$backup_file"; then
log_success "Database backup created: $backup_file"
# Keep only last 10 backups
find "$BACKUP_DIR" -name "fuel_stops_*.db" -type f | sort -r | tail -n +11 | xargs rm -f 2>/dev/null || true
else
log_error "Failed to create database backup"
return 1
fi
}
pull_images() {
log_info "Pulling latest images..."
cd "$PROJECT_ROOT"
if $COMPOSE_CMD pull; then
log_success "Images pulled successfully"
else
log_error "Failed to pull images"
return 1
fi
}
deploy_application() {
log_info "Deploying TankStopp application..."
cd "$PROJECT_ROOT"
# Backup database
backup_database
# Pull latest images
pull_images
# Deploy with docker-compose
log_info "Starting containers..."
if $COMPOSE_CMD up -d --remove-orphans; then
log_success "Containers started successfully"
else
log_error "Failed to start containers"
if [ "$ROLLBACK_ON_FAILURE" = true ]; then
log_info "Attempting rollback..."
rollback_deployment
fi
return 1
fi
# Wait for services to be healthy
wait_for_health
# Run post-deployment checks
post_deployment_checks
log_success "Application deployed successfully"
}
wait_for_health() {
log_info "Waiting for services to become healthy..."
local retries=0
local max_retries=$HEALTH_CHECK_RETRIES
while [ $retries -lt $max_retries ]; do
if check_service_health; then
log_success "All services are healthy"
return 0
fi
retries=$((retries + 1))
log_info "Health check attempt $retries/$max_retries..."
sleep 10
done
log_error "Services failed to become healthy within timeout"
return 1
}
check_service_health() {
cd "$PROJECT_ROOT"
# Check container status
local unhealthy_containers=$($COMPOSE_CMD ps --filter "status=unhealthy" --format "{{.Service}}" 2>/dev/null | wc -l)
local running_containers=$($COMPOSE_CMD ps --filter "status=running" --format "{{.Service}}" 2>/dev/null | wc -l)
log_debug "Running containers: $running_containers, Unhealthy containers: $unhealthy_containers"
if [ "$unhealthy_containers" -gt 0 ]; then
log_warning "Found $unhealthy_containers unhealthy containers"
return 1
fi
if [ "$running_containers" -eq 0 ]; then
log_warning "No running containers found"
return 1
fi
# Test HTTP endpoint
if command -v curl &> /dev/null; then
local port=$(get_service_port)
if curl -sf "http://localhost:$port/" > /dev/null 2>&1; then
return 0
else
log_debug "HTTP health check failed"
return 1
fi
fi
return 0
}
get_service_port() {
case "$ENVIRONMENT" in
"development")
echo "8081"
;;
*)
echo "8080"
;;
esac
}
post_deployment_checks() {
log_info "Running post-deployment checks..."
cd "$PROJECT_ROOT"
# Check container logs for errors
local error_count=$($COMPOSE_CMD logs --tail=50 "$SERVICE_NAME" 2>/dev/null | grep -i "error\|panic\|fatal" | wc -l)
if [ "$error_count" -gt 0 ]; then
log_warning "Found $error_count error messages in logs"
log_info "Recent errors:"
$COMPOSE_CMD logs --tail=10 "$SERVICE_NAME" | grep -i "error\|panic\|fatal" || true
fi
# Check resource usage
check_resource_usage
log_success "Post-deployment checks completed"
}
check_resource_usage() {
log_info "Checking resource usage..."
cd "$PROJECT_ROOT"
# Get container stats
local stats=$($COMPOSE_CMD exec -T "$SERVICE_NAME" sh -c "
echo 'Memory usage:'
free -h | grep Mem
echo 'Disk usage:'
df -h /app/data
echo 'CPU info:'
uptime
" 2>/dev/null || echo "Could not retrieve stats")
log_debug "Resource stats: $stats"
}
start_services() {
log_info "Starting services..."
cd "$PROJECT_ROOT"
if $COMPOSE_CMD start; then
log_success "Services started successfully"
wait_for_health
else
log_error "Failed to start services"
return 1
fi
}
stop_services() {
log_info "Stopping services..."
cd "$PROJECT_ROOT"
if $COMPOSE_CMD stop; then
log_success "Services stopped successfully"
else
log_error "Failed to stop services"
return 1
fi
}
restart_services() {
log_info "Restarting services..."
cd "$PROJECT_ROOT"
if $COMPOSE_CMD restart; then
log_success "Services restarted successfully"
wait_for_health
else
log_error "Failed to restart services"
return 1
fi
}
update_application() {
log_info "Updating application..."
backup_database
pull_images
cd "$PROJECT_ROOT"
if $COMPOSE_CMD up -d --remove-orphans; then
log_success "Application updated successfully"
wait_for_health
post_deployment_checks
else
log_error "Failed to update application"
return 1
fi
}
rollback_deployment() {
log_info "Rolling back deployment..."
cd "$PROJECT_ROOT"
# Stop current containers
$COMPOSE_CMD down
# Restore from latest backup
local latest_backup=$(find "$BACKUP_DIR" -name "fuel_stops_*.db" -type f | sort -r | head -n 1)
if [ -n "$latest_backup" ] && [ -f "$latest_backup" ]; then
log_info "Restoring database from: $latest_backup"
cp "$latest_backup" "$DATA_DIR/data/fuel_stops.db"
log_success "Database restored"
else
log_warning "No backup found for rollback"
fi
# Start with previous configuration
if $COMPOSE_CMD up -d; then
log_success "Rollback completed successfully"
else
log_error "Rollback failed"
return 1
fi
}
show_status() {
log_info "Deployment status:"
cd "$PROJECT_ROOT"
echo ""
echo "=== Container Status ==="
$COMPOSE_CMD ps
echo ""
echo "=== Service Health ==="
if check_service_health; then
echo -e "${GREEN}✓ Services are healthy${NC}"
else
echo -e "${RED}✗ Services are unhealthy${NC}"
fi
echo ""
echo "=== Resource Usage ==="
check_resource_usage
echo ""
echo "=== Recent Logs ==="
$COMPOSE_CMD logs --tail=5 "$SERVICE_NAME"
}
show_logs() {
log_info "Showing application logs..."
cd "$PROJECT_ROOT"
$COMPOSE_CMD logs -f "$SERVICE_NAME"
}
scale_service() {
log_info "Scaling service to $SCALE_REPLICAS replicas..."
cd "$PROJECT_ROOT"
if $COMPOSE_CMD up -d --scale "$SERVICE_NAME=$SCALE_REPLICAS"; then
log_success "Service scaled to $SCALE_REPLICAS replicas"
wait_for_health
else
log_error "Failed to scale service"
return 1
fi
}
cleanup_resources() {
log_info "Cleaning up unused resources..."
# Remove unused containers
docker container prune -f
# Remove unused images
docker image prune -f
# Remove unused volumes (be careful!)
if [ "$FORCE" = true ]; then
docker volume prune -f
fi
# Remove unused networks
docker network prune -f
log_success "Cleanup completed"
}
restore_backup() {
log_info "Available backups:"
ls -la "$BACKUP_DIR"/fuel_stops_*.db 2>/dev/null || {
log_error "No backups found in $BACKUP_DIR"
return 1
}
echo ""
read -p "Enter backup filename to restore (or 'latest' for most recent): " backup_choice
local backup_file
if [ "$backup_choice" = "latest" ]; then
backup_file=$(find "$BACKUP_DIR" -name "fuel_stops_*.db" -type f | sort -r | head -n 1)
else
backup_file="$BACKUP_DIR/$backup_choice"
fi
if [ ! -f "$backup_file" ]; then
log_error "Backup file not found: $backup_file"
return 1
fi
log_info "Restoring from: $backup_file"
# Stop services
stop_services
# Restore database
cp "$backup_file" "$DATA_DIR/data/fuel_stops.db"
# Start services
start_services
log_success "Database restored successfully"
}
main() {
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
deploy|start|stop|restart|update|rollback|status|logs|backup|restore|scale|cleanup)
ACTION="$1"
shift
;;
-h|--help)
show_help
exit 0
;;
-e|--env)
ENVIRONMENT="$2"
shift 2
;;
-f|--file)
COMPOSE_FILE="$2"
shift 2
;;
-o|--override)
COMPOSE_OVERRIDE="$2"
shift 2
;;
-p|--project)
PROJECT_NAME="$2"
shift 2
;;
-s|--service)
SERVICE_NAME="$2"
shift 2
;;
-r|--replicas)
SCALE_REPLICAS="$2"
shift 2
;;
-t|--timeout)
WAIT_TIMEOUT="$2"
shift 2
;;
--no-backup)
BACKUP_BEFORE_DEPLOY=false
shift
;;
--no-rollback)
ROLLBACK_ON_FAILURE=false
shift
;;
--force)
FORCE=true
shift
;;
--debug)
DEBUG=true
shift
;;
*)
log_error "Unknown option: $1"
show_help
exit 1
;;
esac
done
# Validate environment
if [[ "$ENVIRONMENT" != "development" && "$ENVIRONMENT" != "staging" && "$ENVIRONMENT" != "production" ]]; then
log_error "Invalid environment: $ENVIRONMENT"
exit 1
fi
# Start deployment process
log_info "Starting TankStopp deployment process..."
log_info "Action: $ACTION"
log_info "Environment: $ENVIRONMENT"
log_info "Project: $PROJECT_NAME"
check_requirements
setup_environment
# Execute action
case "$ACTION" in
"deploy")
deploy_application
;;
"start")
start_services
;;
"stop")
stop_services
;;
"restart")
restart_services
;;
"update")
update_application
;;
"rollback")
rollback_deployment
;;
"status")
show_status
;;
"logs")
show_logs
;;
"backup")
backup_database
;;
"restore")
restore_backup
;;
"scale")
scale_service
;;
"cleanup")
cleanup_resources
;;
*)
log_error "Unknown action: $ACTION"
show_help
exit 1
;;
esac
log_success "Deployment process completed successfully!"
}
# Execute main function
main "$@"