Production deployment documentation: Add deployment guides, environment template, verification scripts
This commit is contained in:
342
verify-deployment.sh
Executable file
342
verify-deployment.sh
Executable file
@@ -0,0 +1,342 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Production Deployment Verification Script
|
||||
# Run this before and after production deployment
|
||||
|
||||
set -e
|
||||
|
||||
echo "╔════════════════════════════════════════════════════════════════╗"
|
||||
echo "║ DigiServer v2 Production Deployment Verification ║"
|
||||
echo "╚════════════════════════════════════════════════════════════════╝"
|
||||
|
||||
TIMESTAMP=$(date +%Y-%m-%d\ %H:%M:%S)
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Color codes
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Counters
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
WARNINGS=0
|
||||
|
||||
# Helper functions
|
||||
pass() {
|
||||
echo -e "${GREEN}✓${NC} $1"
|
||||
((PASSED++))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo -e "${RED}✗${NC} $1"
|
||||
((FAILED++))
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "${YELLOW}⚠${NC} $1"
|
||||
((WARNINGS++))
|
||||
}
|
||||
|
||||
info() {
|
||||
echo -e "${BLUE}ℹ${NC} $1"
|
||||
}
|
||||
|
||||
section() {
|
||||
echo -e "\n${BLUE}═══════════════════════════════════════${NC}"
|
||||
echo -e "${BLUE} $1${NC}"
|
||||
echo -e "${BLUE}═══════════════════════════════════════${NC}"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
section "1. Git Status"
|
||||
# ============================================================================
|
||||
|
||||
if git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
pass "Git repository initialized"
|
||||
|
||||
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
COMMIT=$(git rev-parse --short HEAD)
|
||||
info "Current branch: $BRANCH, Commit: $COMMIT"
|
||||
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
warn "Uncommitted changes detected"
|
||||
git status --short
|
||||
else
|
||||
pass "All changes committed"
|
||||
fi
|
||||
else
|
||||
fail "Not a git repository"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "2. Environment Configuration"
|
||||
# ============================================================================
|
||||
|
||||
if [ -f .env ]; then
|
||||
pass ".env file exists"
|
||||
else
|
||||
warn ".env file not found (using defaults or docker-compose environment)"
|
||||
fi
|
||||
|
||||
if [ -f .env.example ]; then
|
||||
pass ".env.example template exists"
|
||||
else
|
||||
warn ".env.example template missing"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "3. Docker Configuration"
|
||||
# ============================================================================
|
||||
|
||||
if command -v docker &> /dev/null; then
|
||||
pass "Docker installed"
|
||||
DOCKER_VERSION=$(docker --version | cut -d' ' -f3 | tr -d ',')
|
||||
info "Docker version: $DOCKER_VERSION"
|
||||
else
|
||||
fail "Docker not installed"
|
||||
fi
|
||||
|
||||
if command -v docker-compose &> /dev/null; then
|
||||
pass "Docker Compose installed"
|
||||
DC_VERSION=$(docker-compose --version | cut -d' ' -f3 | tr -d ',')
|
||||
info "Docker Compose version: $DC_VERSION"
|
||||
else
|
||||
fail "Docker Compose not installed"
|
||||
fi
|
||||
|
||||
if [ -f docker-compose.yml ]; then
|
||||
pass "docker-compose.yml exists"
|
||||
|
||||
# Validate syntax
|
||||
if docker-compose config > /dev/null 2>&1; then
|
||||
pass "docker-compose.yml syntax valid"
|
||||
else
|
||||
fail "docker-compose.yml syntax error"
|
||||
fi
|
||||
else
|
||||
fail "docker-compose.yml not found"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "4. Dockerfile & Images"
|
||||
# ============================================================================
|
||||
|
||||
if [ -f Dockerfile ]; then
|
||||
pass "Dockerfile exists"
|
||||
|
||||
# Check for security best practices
|
||||
if grep -q "HEALTHCHECK" Dockerfile; then
|
||||
pass "Health check configured"
|
||||
else
|
||||
warn "No health check in Dockerfile"
|
||||
fi
|
||||
|
||||
if grep -q "USER appuser" Dockerfile || grep -q "USER.*:1000" Dockerfile; then
|
||||
pass "Non-root user configured"
|
||||
else
|
||||
warn "Root user may be used in container"
|
||||
fi
|
||||
|
||||
if grep -q "FROM.*alpine\|FROM.*slim\|FROM.*distroless" Dockerfile; then
|
||||
pass "Minimal base image used"
|
||||
else
|
||||
warn "Large base image detected"
|
||||
fi
|
||||
else
|
||||
fail "Dockerfile not found"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "5. Python Dependencies"
|
||||
# ============================================================================
|
||||
|
||||
if [ -f requirements.txt ]; then
|
||||
pass "requirements.txt exists"
|
||||
|
||||
PACKAGE_COUNT=$(wc -l < requirements.txt)
|
||||
info "Total packages: $PACKAGE_COUNT"
|
||||
|
||||
# Check for critical packages
|
||||
for pkg in Flask SQLAlchemy gunicorn flask-cors cryptography; do
|
||||
if grep -q "$pkg" requirements.txt; then
|
||||
pass "$pkg installed"
|
||||
else
|
||||
warn "$pkg not found in requirements.txt"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for specific versions
|
||||
FLASK_VERSION=$(grep "^Flask==" requirements.txt | cut -d'=' -f3)
|
||||
SQLALCHEMY_VERSION=$(grep "^SQLAlchemy==" requirements.txt | cut -d'=' -f3)
|
||||
|
||||
if [ -n "$FLASK_VERSION" ]; then
|
||||
info "Flask version: $FLASK_VERSION"
|
||||
fi
|
||||
if [ -n "$SQLALCHEMY_VERSION" ]; then
|
||||
info "SQLAlchemy version: $SQLALCHEMY_VERSION"
|
||||
fi
|
||||
else
|
||||
fail "requirements.txt not found"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "6. Database Configuration"
|
||||
# ============================================================================
|
||||
|
||||
if [ -d migrations ]; then
|
||||
pass "migrations directory exists"
|
||||
|
||||
MIGRATION_COUNT=$(find migrations -name "*.py" | wc -l)
|
||||
info "Migration files: $MIGRATION_COUNT"
|
||||
|
||||
if [ "$MIGRATION_COUNT" -gt 0 ]; then
|
||||
pass "Database migrations configured"
|
||||
else
|
||||
warn "No migration files found"
|
||||
fi
|
||||
else
|
||||
warn "migrations directory not found"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "7. SSL/TLS Certificate"
|
||||
# ============================================================================
|
||||
|
||||
if [ -f data/nginx-ssl/cert.pem ]; then
|
||||
pass "SSL certificate found"
|
||||
|
||||
CERT_EXPIRY=$(openssl x509 -enddate -noout -in data/nginx-ssl/cert.pem 2>/dev/null | cut -d= -f2)
|
||||
EXPIRY_EPOCH=$(date -d "$CERT_EXPIRY" +%s 2>/dev/null || echo 0)
|
||||
NOW_EPOCH=$(date +%s)
|
||||
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
|
||||
|
||||
info "Certificate expires: $CERT_EXPIRY"
|
||||
info "Days remaining: $DAYS_LEFT days"
|
||||
|
||||
if [ "$DAYS_LEFT" -lt 0 ]; then
|
||||
fail "Certificate has expired!"
|
||||
elif [ "$DAYS_LEFT" -lt 30 ]; then
|
||||
warn "Certificate expires in less than 30 days"
|
||||
else
|
||||
pass "Certificate is valid"
|
||||
fi
|
||||
|
||||
if [ -f data/nginx-ssl/key.pem ]; then
|
||||
pass "SSL private key found"
|
||||
else
|
||||
warn "SSL private key not found"
|
||||
fi
|
||||
else
|
||||
warn "SSL certificate not found (self-signed required)"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "8. Configuration Files"
|
||||
# ============================================================================
|
||||
|
||||
if [ -f app/config.py ]; then
|
||||
pass "Flask config.py exists"
|
||||
|
||||
if grep -q "class ProductionConfig" app/config.py; then
|
||||
pass "ProductionConfig class defined"
|
||||
else
|
||||
warn "ProductionConfig class missing"
|
||||
fi
|
||||
|
||||
if grep -q "SESSION_COOKIE_SECURE" app/config.py; then
|
||||
pass "SESSION_COOKIE_SECURE configured"
|
||||
else
|
||||
warn "SESSION_COOKIE_SECURE not configured"
|
||||
fi
|
||||
else
|
||||
fail "app/config.py not found"
|
||||
fi
|
||||
|
||||
if [ -f nginx.conf ]; then
|
||||
pass "nginx.conf exists"
|
||||
|
||||
if grep -q "ssl_protocols" nginx.conf; then
|
||||
pass "SSL protocols configured"
|
||||
else
|
||||
warn "SSL protocols not configured"
|
||||
fi
|
||||
|
||||
if grep -q "access-control-allow" nginx.conf; then
|
||||
pass "CORS headers in nginx"
|
||||
else
|
||||
info "CORS headers may be handled by Flask only"
|
||||
fi
|
||||
else
|
||||
warn "nginx.conf not found"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "9. Runtime Verification"
|
||||
# ============================================================================
|
||||
|
||||
if docker-compose ps 2>/dev/null | grep -q "Up"; then
|
||||
pass "Docker containers are running"
|
||||
|
||||
# Check if app is healthy
|
||||
if docker-compose ps 2>/dev/null | grep -q "digiserver-app.*healthy"; then
|
||||
pass "DigiServer app container is healthy"
|
||||
else
|
||||
warn "DigiServer app container health status unknown"
|
||||
fi
|
||||
|
||||
if docker-compose ps 2>/dev/null | grep -q "digiserver-nginx.*healthy"; then
|
||||
pass "Nginx container is healthy"
|
||||
else
|
||||
warn "Nginx container health status unknown"
|
||||
fi
|
||||
else
|
||||
info "Docker containers not running (will start on deployment)"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "10. Security Best Practices"
|
||||
# ============================================================================
|
||||
|
||||
# Check for hardcoded secrets
|
||||
if grep -r "SECRET_KEY\|PASSWORD\|API_KEY" app/ 2>/dev/null | grep -v "os.getenv\|config.py\|#" | wc -l | grep -q "^0$"; then
|
||||
pass "No hardcoded secrets found"
|
||||
else
|
||||
warn "Possible hardcoded secrets detected (verify they use os.getenv)"
|
||||
fi
|
||||
|
||||
# Check for debug mode
|
||||
if grep -q "DEBUG.*=.*True" app/config.py 2>/dev/null; then
|
||||
fail "DEBUG mode is enabled"
|
||||
else
|
||||
pass "DEBUG mode is disabled"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "Summary"
|
||||
# ============================================================================
|
||||
|
||||
echo ""
|
||||
echo -e "Test Results:"
|
||||
echo -e " ${GREEN}Passed: $PASSED${NC}"
|
||||
echo -e " ${YELLOW}Warnings: $WARNINGS${NC}"
|
||||
echo -e " ${RED}Failed: $FAILED${NC}"
|
||||
|
||||
TOTAL=$((PASSED + FAILED + WARNINGS))
|
||||
PERCENTAGE=$((PASSED * 100 / (PASSED + FAILED)))
|
||||
|
||||
if [ "$FAILED" -eq 0 ]; then
|
||||
echo -e "\n${GREEN}✓ Production Deployment Ready!${NC}"
|
||||
echo "Recommendation: Safe to deploy to production"
|
||||
exit 0
|
||||
elif [ "$FAILED" -le 2 ] && [ "$WARNINGS" -gt 0 ]; then
|
||||
echo -e "\n${YELLOW}⚠ Deployment Possible with Caution${NC}"
|
||||
echo "Recommendation: Address warnings before deployment"
|
||||
exit 0
|
||||
else
|
||||
echo -e "\n${RED}✗ Deployment Not Recommended${NC}"
|
||||
echo "Recommendation: Fix critical failures before deployment"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user