feat: update deployment configuration and encrypted env loader
- Update Ansible playbooks and roles for application deployment - Add new Gitea/Traefik troubleshooting playbooks - Update Docker Compose configurations (base, local, staging, production) - Enhance EncryptedEnvLoader with improved error handling - Add deployment scripts (autossh setup, migration, secret testing) - Update CI/CD workflows and documentation - Add Semaphore stack configuration
This commit is contained in:
244
scripts/migrate-env-to-base-override.sh
Normal file
244
scripts/migrate-env-to-base-override.sh
Normal file
@@ -0,0 +1,244 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Migration Script: .env → .env.base + .env.local
|
||||
#
|
||||
# This script helps migrate from the legacy single .env file
|
||||
# to the new Base+Override Pattern (.env.base + .env.local)
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/migrate-env-to-base-override.sh
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
echo "🔄 Migration: .env → .env.base + .env.local"
|
||||
echo ""
|
||||
|
||||
# Check if .env exists
|
||||
if [ ! -f .env ]; then
|
||||
echo "❌ .env Datei nicht gefunden"
|
||||
echo "💡 Erstelle zuerst .env aus .env.example"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Backup existing .env
|
||||
BACKUP_FILE=".env.backup.$(date +%Y%m%d-%H%M%S)"
|
||||
echo "📦 Backup erstellen: $BACKUP_FILE"
|
||||
cp .env "$BACKUP_FILE"
|
||||
echo "✅ Backup erstellt"
|
||||
|
||||
# Check if .env.base exists
|
||||
if [ -f .env.base ]; then
|
||||
echo ""
|
||||
echo "⚠️ .env.base existiert bereits"
|
||||
echo "💡 .env.base wird als Basis verwendet"
|
||||
USE_EXISTING_BASE=true
|
||||
else
|
||||
USE_EXISTING_BASE=false
|
||||
fi
|
||||
|
||||
# Check if .env.local exists
|
||||
if [ -f .env.local ]; then
|
||||
echo ""
|
||||
echo "⚠️ .env.local existiert bereits"
|
||||
read -p "Überschreiben? (j/n): " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Jj]$ ]]; then
|
||||
echo "❌ Abgebrochen"
|
||||
exit 1
|
||||
fi
|
||||
BACKUP_LOCAL=".env.local.backup.$(date +%Y%m%d-%H%M%S)"
|
||||
cp .env.local "$BACKUP_LOCAL"
|
||||
echo "📦 Backup von .env.local erstellt: $BACKUP_LOCAL"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📝 Analysiere .env Datei..."
|
||||
|
||||
# Common variables that should go to .env.base
|
||||
# (These are typically environment-agnostic)
|
||||
BASE_VARS=(
|
||||
"APP_NAME"
|
||||
"APP_TIMEZONE"
|
||||
"APP_LOCALE"
|
||||
"DB_DRIVER"
|
||||
"DB_PORT"
|
||||
"DB_CHARSET"
|
||||
"REDIS_PORT"
|
||||
"CACHE_DRIVER"
|
||||
"SESSION_DRIVER"
|
||||
"SESSION_LIFETIME"
|
||||
"QUEUE_DRIVER"
|
||||
"QUEUE_CONNECTION"
|
||||
"QUEUE_WORKER_SLEEP"
|
||||
"QUEUE_WORKER_TRIES"
|
||||
"QUEUE_WORKER_TIMEOUT"
|
||||
"SECURITY_RATE_LIMIT_PER_MINUTE"
|
||||
"SECURITY_RATE_LIMIT_BURST"
|
||||
"CACHE_PREFIX"
|
||||
)
|
||||
|
||||
# Local-specific variables (development overrides)
|
||||
LOCAL_VARS=(
|
||||
"APP_ENV"
|
||||
"APP_DEBUG"
|
||||
"APP_URL"
|
||||
"APP_KEY"
|
||||
"APP_DOMAIN"
|
||||
"DB_HOST"
|
||||
"DB_DATABASE"
|
||||
"DB_USERNAME"
|
||||
"DB_PASSWORD"
|
||||
"REDIS_HOST"
|
||||
"REDIS_PASSWORD"
|
||||
"SECURITY_ALLOWED_HOSTS"
|
||||
"FORCE_HTTPS"
|
||||
"XDEBUG_MODE"
|
||||
"PHP_IDE_CONFIG"
|
||||
)
|
||||
|
||||
# Variables that should NOT be in .env.base (secrets)
|
||||
SECRET_PATTERNS=(
|
||||
"PASSWORD"
|
||||
"SECRET"
|
||||
"KEY"
|
||||
"TOKEN"
|
||||
"ENCRYPTION"
|
||||
"VAULT"
|
||||
)
|
||||
|
||||
echo ""
|
||||
echo "📋 Trenne Variablen in Base und Local..."
|
||||
|
||||
# Create temporary files
|
||||
TMP_BASE=$(mktemp)
|
||||
TMP_LOCAL=$(mktemp)
|
||||
|
||||
# Read .env line by line
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
# Skip empty lines and comments
|
||||
if [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]]; then
|
||||
echo "$line" >> "$TMP_BASE"
|
||||
echo "$line" >> "$TMP_LOCAL"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Extract variable name
|
||||
if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)= ]]; then
|
||||
VAR_NAME="${BASH_REMATCH[1]}"
|
||||
|
||||
# Check if it's a secret
|
||||
IS_SECRET=false
|
||||
for pattern in "${SECRET_PATTERNS[@]}"; do
|
||||
if [[ "$VAR_NAME" == *"$pattern"* ]]; then
|
||||
IS_SECRET=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$IS_SECRET" = true ]; then
|
||||
# Secrets go to .env.local (or should be in Docker Secrets)
|
||||
echo "# TODO: Möglicherweise in Docker Secrets verschieben" >> "$TMP_LOCAL"
|
||||
echo "$line" >> "$TMP_LOCAL"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check if it's a base variable
|
||||
IS_BASE=false
|
||||
for base_var in "${BASE_VARS[@]}"; do
|
||||
if [[ "$VAR_NAME" == "$base_var" ]]; then
|
||||
IS_BASE=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if it's a local variable
|
||||
IS_LOCAL=false
|
||||
for local_var in "${LOCAL_VARS[@]}"; do
|
||||
if [[ "$VAR_NAME" == "$local_var" ]]; then
|
||||
IS_LOCAL=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$IS_BASE" = true ]; then
|
||||
# Go to .env.base
|
||||
echo "$line" >> "$TMP_BASE"
|
||||
elif [ "$IS_LOCAL" = true ]; then
|
||||
# Go to .env.local
|
||||
echo "$line" >> "$TMP_LOCAL"
|
||||
else
|
||||
# Unknown: Ask or put in local as default
|
||||
echo "# TODO: Prüfen ob Base oder Local" >> "$TMP_LOCAL"
|
||||
echo "$line" >> "$TMP_LOCAL"
|
||||
fi
|
||||
else
|
||||
# Non-standard line format: keep in both (shouldn't happen)
|
||||
echo "$line" >> "$TMP_BASE"
|
||||
echo "$line" >> "$TMP_LOCAL"
|
||||
fi
|
||||
done < .env
|
||||
|
||||
# Create .env.base if it doesn't exist
|
||||
if [ "$USE_EXISTING_BASE" = false ]; then
|
||||
echo ""
|
||||
echo "📝 Erstelle .env.base..."
|
||||
cat > .env.base << 'EOF'
|
||||
# Base Environment Configuration
|
||||
# This file contains shared environment variables for all environments.
|
||||
# Use with environment-specific override files:
|
||||
# - .env.local (local development overrides)
|
||||
# - .env.staging (staging-specific overrides, optional)
|
||||
# - .env.production (production - generated by Ansible)
|
||||
#
|
||||
# Framework automatically loads: .env.base → .env.local (if exists)
|
||||
# See ENV_SETUP.md for details
|
||||
#
|
||||
EOF
|
||||
cat "$TMP_BASE" >> .env.base
|
||||
echo "✅ .env.base erstellt"
|
||||
else
|
||||
echo ""
|
||||
echo "ℹ️ .env.base existiert bereits, wird nicht überschrieben"
|
||||
fi
|
||||
|
||||
# Create .env.local
|
||||
echo ""
|
||||
echo "📝 Erstelle .env.local..."
|
||||
cat > .env.local << 'EOF'
|
||||
# Local Development Environment Overrides
|
||||
# This file overrides .env.base with local development-specific settings.
|
||||
# This file is gitignored - each developer has their own version.
|
||||
#
|
||||
# Framework loads: .env.base → .env.local (this file) → System ENV vars
|
||||
# See ENV_SETUP.md for details
|
||||
#
|
||||
EOF
|
||||
cat "$TMP_LOCAL" >> .env.local
|
||||
echo "✅ .env.local erstellt"
|
||||
|
||||
# Cleanup
|
||||
rm -f "$TMP_BASE" "$TMP_LOCAL"
|
||||
|
||||
echo ""
|
||||
echo "✅ Migration abgeschlossen!"
|
||||
echo ""
|
||||
echo "📋 Nächste Schritte:"
|
||||
echo " 1. Prüfe .env.base - entferne Secrets falls vorhanden"
|
||||
echo " 2. Prüfe .env.local - passe lokale Overrides an"
|
||||
echo " 3. Teste die Anwendung: make up"
|
||||
echo " 4. Optional: .env kann später entfernt werden (wird als Fallback geladen)"
|
||||
echo ""
|
||||
echo "📝 Backup-Dateien:"
|
||||
echo " - $BACKUP_FILE"
|
||||
if [ -n "$BACKUP_LOCAL" ]; then
|
||||
echo " - $BACKUP_LOCAL"
|
||||
fi
|
||||
echo ""
|
||||
echo "💡 Siehe ENV_SETUP.md für Details zur neuen Struktur"
|
||||
|
||||
238
scripts/setup-autossh.sh
Executable file
238
scripts/setup-autossh.sh
Executable file
@@ -0,0 +1,238 @@
|
||||
#!/bin/bash
|
||||
# Setup script for autossh persistent SSH connections
|
||||
# Usage: ./scripts/setup-autossh.sh [production|git|both]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
SSH_CONFIG="$HOME/.ssh/config"
|
||||
SERVICE_TYPE="${1:-both}"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Functions
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Check if autossh is installed
|
||||
check_autossh() {
|
||||
if ! command -v autossh &> /dev/null; then
|
||||
log_error "autossh is not installed!"
|
||||
echo ""
|
||||
echo "Installation:"
|
||||
echo " Ubuntu/Debian: sudo apt install autossh"
|
||||
echo " macOS: brew install autossh"
|
||||
exit 1
|
||||
fi
|
||||
log_info "autossh is installed: $(which autossh)"
|
||||
}
|
||||
|
||||
# Check if SSH config exists
|
||||
check_ssh_config() {
|
||||
if [ ! -d "$HOME/.ssh" ]; then
|
||||
log_info "Creating ~/.ssh directory"
|
||||
mkdir -p "$HOME/.ssh"
|
||||
chmod 700 "$HOME/.ssh"
|
||||
fi
|
||||
|
||||
if [ ! -f "$SSH_CONFIG" ]; then
|
||||
log_info "Creating SSH config file"
|
||||
touch "$SSH_CONFIG"
|
||||
chmod 600 "$SSH_CONFIG"
|
||||
fi
|
||||
}
|
||||
|
||||
# Add SSH config entries
|
||||
add_ssh_config() {
|
||||
log_info "Checking SSH config..."
|
||||
|
||||
# Production server config
|
||||
if ! grep -q "Host production" "$SSH_CONFIG" 2>/dev/null; then
|
||||
log_info "Adding production server config to SSH config"
|
||||
cat >> "$SSH_CONFIG" << 'EOF'
|
||||
|
||||
# Production Server - Persistent Connection
|
||||
Host production
|
||||
HostName 94.16.110.151
|
||||
User deploy
|
||||
IdentityFile ~/.ssh/production
|
||||
ServerAliveInterval 60
|
||||
ServerAliveCountMax 3
|
||||
TCPKeepAlive yes
|
||||
Compression yes
|
||||
StrictHostKeyChecking accept-new
|
||||
EOF
|
||||
else
|
||||
log_info "Production server config already exists in SSH config"
|
||||
fi
|
||||
|
||||
# Git server config
|
||||
if ! grep -q "Host git.michaelschiemer.de" "$SSH_CONFIG" 2>/dev/null; then
|
||||
log_info "Adding git server config to SSH config"
|
||||
cat >> "$SSH_CONFIG" << 'EOF'
|
||||
|
||||
# Git Server - Persistent Connection
|
||||
Host git.michaelschiemer.de
|
||||
HostName git.michaelschiemer.de
|
||||
Port 2222
|
||||
User git
|
||||
IdentityFile ~/.ssh/git_michaelschiemer
|
||||
ServerAliveInterval 60
|
||||
ServerAliveCountMax 3
|
||||
TCPKeepAlive yes
|
||||
Compression yes
|
||||
StrictHostKeyChecking no
|
||||
UserKnownHostsFile /dev/null
|
||||
EOF
|
||||
else
|
||||
log_info "Git server config already exists in SSH config"
|
||||
fi
|
||||
}
|
||||
|
||||
# Create systemd service
|
||||
create_systemd_service() {
|
||||
local host=$1
|
||||
local port=$2
|
||||
local service_name="autossh-${host}"
|
||||
local service_dir="$HOME/.config/systemd/user"
|
||||
|
||||
log_info "Creating systemd service for ${host}..."
|
||||
|
||||
mkdir -p "$service_dir"
|
||||
|
||||
cat > "${service_dir}/${service_name}.service" << EOF
|
||||
[Unit]
|
||||
Description=AutoSSH for ${host}
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment="AUTOSSH_GATETIME=0"
|
||||
Environment="AUTOSSH_POLL=10"
|
||||
ExecStart=/usr/bin/autossh -M ${port} -N -o "ServerAliveInterval=60" -o "ServerAliveCountMax=3" ${host}
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
EOF
|
||||
|
||||
log_info "Systemd service created: ${service_dir}/${service_name}.service"
|
||||
}
|
||||
|
||||
# Setup systemd services
|
||||
setup_systemd_services() {
|
||||
if ! systemctl --user --version &> /dev/null; then
|
||||
log_warn "systemd user services not available (might be on macOS or non-systemd system)"
|
||||
log_info "Skipping systemd service setup. See docs/deployment/AUTOSSH-SETUP.md for manual setup."
|
||||
return
|
||||
fi
|
||||
|
||||
log_info "Setting up systemd services..."
|
||||
|
||||
case "$SERVICE_TYPE" in
|
||||
production)
|
||||
create_systemd_service "production" "20000"
|
||||
systemctl --user daemon-reload
|
||||
log_info "To enable: systemctl --user enable autossh-production.service"
|
||||
log_info "To start: systemctl --user start autossh-production.service"
|
||||
;;
|
||||
git)
|
||||
create_systemd_service "git.michaelschiemer.de" "20001"
|
||||
systemctl --user daemon-reload
|
||||
log_info "To enable: systemctl --user enable autossh-git.michaelschiemer.de.service"
|
||||
log_info "To start: systemctl --user start autossh-git.michaelschiemer.de.service"
|
||||
;;
|
||||
both)
|
||||
create_systemd_service "production" "20000"
|
||||
create_systemd_service "git.michaelschiemer.de" "20001"
|
||||
systemctl --user daemon-reload
|
||||
log_info "To enable:"
|
||||
log_info " systemctl --user enable autossh-production.service"
|
||||
log_info " systemctl --user enable autossh-git.michaelschiemer.de.service"
|
||||
log_info "To start:"
|
||||
log_info " systemctl --user start autossh-production.service"
|
||||
log_info " systemctl --user start autossh-git.michaelschiemer.de.service"
|
||||
;;
|
||||
*)
|
||||
log_error "Invalid service type: $SERVICE_TYPE"
|
||||
log_info "Usage: $0 [production|git|both]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Test SSH connections
|
||||
test_connections() {
|
||||
log_info "Testing SSH connections..."
|
||||
|
||||
case "$SERVICE_TYPE" in
|
||||
production)
|
||||
if ssh -o ConnectTimeout=5 production "echo 'Connection successful'" 2>/dev/null; then
|
||||
log_info "? Production server connection successful"
|
||||
else
|
||||
log_warn "?? Production server connection failed"
|
||||
log_info "Make sure SSH key is set up: ssh-keygen -t ed25519 -f ~/.ssh/production"
|
||||
fi
|
||||
;;
|
||||
git)
|
||||
if ssh -o ConnectTimeout=5 git.michaelschiemer.de "echo 'Connection successful'" 2>/dev/null; then
|
||||
log_info "? Git server connection successful"
|
||||
else
|
||||
log_warn "?? Git server connection failed"
|
||||
log_info "Make sure SSH key is set up: ssh-keygen -t ed25519 -f ~/.ssh/git_michaelschiemer"
|
||||
fi
|
||||
;;
|
||||
both)
|
||||
if ssh -o ConnectTimeout=5 production "echo 'Connection successful'" 2>/dev/null; then
|
||||
log_info "? Production server connection successful"
|
||||
else
|
||||
log_warn "?? Production server connection failed"
|
||||
fi
|
||||
if ssh -o ConnectTimeout=5 git.michaelschiemer.de "echo 'Connection successful'" 2>/dev/null; then
|
||||
log_info "? Git server connection successful"
|
||||
else
|
||||
log_warn "?? Git server connection failed"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
log_info "Setting up autossh for persistent SSH connections"
|
||||
echo ""
|
||||
|
||||
check_autossh
|
||||
check_ssh_config
|
||||
add_ssh_config
|
||||
setup_systemd_services
|
||||
test_connections
|
||||
|
||||
echo ""
|
||||
log_info "Setup complete!"
|
||||
echo ""
|
||||
log_info "Next steps:"
|
||||
echo " 1. Review SSH config: cat ~/.ssh/config"
|
||||
echo " 2. Enable systemd services (see output above)"
|
||||
echo " 3. Start services (see output above)"
|
||||
echo " 4. Check status: systemctl --user status autossh-*.service"
|
||||
echo ""
|
||||
log_info "Documentation: docs/deployment/AUTOSSH-SETUP.md"
|
||||
}
|
||||
|
||||
main
|
||||
290
scripts/test-deployment-secrets.sh
Executable file
290
scripts/test-deployment-secrets.sh
Executable file
@@ -0,0 +1,290 @@
|
||||
#!/bin/bash
|
||||
set -uo pipefail
|
||||
|
||||
# Test script for deployment with new docker-compose files and secret management
|
||||
# This script validates the deployment configuration and tests secret loading
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
SECRETS_DIR="$PROJECT_ROOT/secrets"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Test results
|
||||
TESTS_PASSED=0
|
||||
TESTS_FAILED=0
|
||||
TESTS_TOTAL=0
|
||||
|
||||
print_header() {
|
||||
echo ""
|
||||
echo -e "${BLUE}????????????????????????????????????????????????????????????????????${NC}"
|
||||
echo -e "${BLUE}$1${NC}"
|
||||
echo -e "${BLUE}????????????????????????????????????????????????????????????????????${NC}"
|
||||
}
|
||||
|
||||
print_test() {
|
||||
echo -e "${YELLOW}[TEST]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[?]${NC} $1"
|
||||
((TESTS_PASSED++))
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[?]${NC} $1"
|
||||
((TESTS_FAILED++))
|
||||
}
|
||||
|
||||
run_test() {
|
||||
((TESTS_TOTAL++))
|
||||
local test_name="$1"
|
||||
shift
|
||||
print_test "$test_name"
|
||||
|
||||
if "$@" 2>/dev/null; then
|
||||
print_success "$test_name"
|
||||
return 0
|
||||
else
|
||||
print_error "$test_name"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Cleanup function
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo -e "${YELLOW}Cleaning up test artifacts...${NC}"
|
||||
|
||||
# Remove test secrets directory
|
||||
if [ -d "$SECRETS_DIR" ] && [ -f "$SECRETS_DIR/.test-marker" ]; then
|
||||
rm -rf "$SECRETS_DIR"
|
||||
print_success "Test secrets directory removed"
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
print_header "?? Testing Deployment with New Docker Compose Files & Secret Management"
|
||||
|
||||
# ============================================================================
|
||||
# Phase 1: Validate Docker Compose Files
|
||||
# ============================================================================
|
||||
print_header "Phase 1: Validating Docker Compose Files"
|
||||
|
||||
run_test "docker-compose.base.yml exists" test -f "$PROJECT_ROOT/docker-compose.base.yml"
|
||||
run_test "docker-compose.local.yml exists" test -f "$PROJECT_ROOT/docker-compose.local.yml"
|
||||
run_test "docker-compose.staging.yml exists" test -f "$PROJECT_ROOT/docker-compose.staging.yml"
|
||||
run_test "docker-compose.production.yml exists" test -f "$PROJECT_ROOT/docker-compose.production.yml"
|
||||
|
||||
# Validate docker-compose syntax
|
||||
if command -v docker-compose &> /dev/null || command -v docker &> /dev/null; then
|
||||
run_test "docker-compose.base.yml syntax valid" bash -c "cd '$PROJECT_ROOT' && docker-compose -f docker-compose.base.yml config > /dev/null 2>&1 || docker compose -f docker-compose.base.yml config > /dev/null 2>&1"
|
||||
run_test "docker-compose.local.yml syntax valid" bash -c "cd '$PROJECT_ROOT' && docker-compose -f docker-compose.base.yml -f docker-compose.local.yml config > /dev/null 2>&1 || docker compose -f docker-compose.base.yml -f docker-compose.local.yml config > /dev/null 2>&1"
|
||||
run_test "docker-compose.staging.yml syntax valid" bash -c "cd '$PROJECT_ROOT' && docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml config > /dev/null 2>&1 || docker compose -f docker-compose.base.yml -f docker-compose.staging.yml config > /dev/null 2>&1"
|
||||
run_test "docker-compose.production.yml syntax valid" bash -c "cd '$PROJECT_ROOT' && docker-compose -f docker-compose.base.yml -f docker-compose.production.yml config > /dev/null 2>&1 || docker compose -f docker-compose.base.yml -f docker-compose.production.yml config > /dev/null 2>&1"
|
||||
else
|
||||
print_error "docker-compose or docker not available, skipping syntax validation"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Phase 2: Validate Secret Configuration
|
||||
# ============================================================================
|
||||
print_header "Phase 2: Validating Secret Configuration"
|
||||
|
||||
# Check that secrets are defined in docker-compose.base.yml
|
||||
run_test "secrets section exists in docker-compose.base.yml" grep -q "^secrets:" "$PROJECT_ROOT/docker-compose.base.yml"
|
||||
run_test "db_root_password secret defined" grep -q "db_root_password:" "$PROJECT_ROOT/docker-compose.base.yml"
|
||||
run_test "db_user_password secret defined" grep -q "db_user_password:" "$PROJECT_ROOT/docker-compose.base.yml"
|
||||
run_test "redis_password secret defined" grep -q "redis_password:" "$PROJECT_ROOT/docker-compose.base.yml"
|
||||
run_test "app_key secret defined" grep -q "app_key:" "$PROJECT_ROOT/docker-compose.base.yml"
|
||||
run_test "vault_encryption_key secret defined" grep -q "vault_encryption_key:" "$PROJECT_ROOT/docker-compose.base.yml"
|
||||
run_test "git_token secret defined" grep -q "git_token:" "$PROJECT_ROOT/docker-compose.base.yml"
|
||||
|
||||
# Check that production uses secrets
|
||||
run_test "production uses db_user_password secret" grep -q "db_user_password" "$PROJECT_ROOT/docker-compose.production.yml"
|
||||
run_test "production uses redis_password secret" grep -q "redis_password" "$PROJECT_ROOT/docker-compose.production.yml"
|
||||
run_test "production uses app_key secret" grep -q "app_key" "$PROJECT_ROOT/docker-compose.production.yml"
|
||||
run_test "production uses vault_encryption_key secret" grep -q "vault_encryption_key" "$PROJECT_ROOT/docker-compose.production.yml"
|
||||
|
||||
# Check that staging uses secrets
|
||||
run_test "staging uses db_user_password secret" grep -q "db_user_password" "$PROJECT_ROOT/docker-compose.staging.yml"
|
||||
run_test "staging uses redis_password secret" grep -q "redis_password" "$PROJECT_ROOT/docker-compose.staging.yml"
|
||||
run_test "staging uses app_key secret" grep -q "app_key" "$PROJECT_ROOT/docker-compose.staging.yml"
|
||||
run_test "staging uses vault_encryption_key secret" grep -q "vault_encryption_key" "$PROJECT_ROOT/docker-compose.staging.yml"
|
||||
|
||||
# ============================================================================
|
||||
# Phase 3: Create Test Secrets
|
||||
# ============================================================================
|
||||
print_header "Phase 3: Creating Test Secrets"
|
||||
|
||||
# Create test secrets directory
|
||||
mkdir -p "$SECRETS_DIR"
|
||||
echo "test-marker" > "$SECRETS_DIR/.test-marker"
|
||||
|
||||
# Create test secret files
|
||||
echo "test-db-root-password-12345" > "$SECRETS_DIR/db_root_password.txt"
|
||||
echo "test-db-user-password-67890" > "$SECRETS_DIR/db_user_password.txt"
|
||||
echo "test-redis-password-abcde" > "$SECRETS_DIR/redis_password.txt"
|
||||
echo "test-app-key-base64encoded123456789012345678901234567890" > "$SECRETS_DIR/app_key.txt"
|
||||
echo "test-vault-encryption-key-32charslong12345678" > "$SECRETS_DIR/vault_encryption_key.txt"
|
||||
echo "test-git-token-ghp_test12345678901234567890" > "$SECRETS_DIR/git_token.txt"
|
||||
|
||||
# Set secure permissions
|
||||
chmod 600 "$SECRETS_DIR"/*.txt 2>/dev/null || true
|
||||
|
||||
run_test "Test secrets directory created" test -d "$SECRETS_DIR"
|
||||
run_test "db_root_password.txt created" test -f "$SECRETS_DIR/db_root_password.txt"
|
||||
run_test "db_user_password.txt created" test -f "$SECRETS_DIR/db_user_password.txt"
|
||||
run_test "redis_password.txt created" test -f "$SECRETS_DIR/redis_password.txt"
|
||||
run_test "app_key.txt created" test -f "$SECRETS_DIR/app_key.txt"
|
||||
run_test "vault_encryption_key.txt created" test -f "$SECRETS_DIR/vault_encryption_key.txt"
|
||||
run_test "git_token.txt created" test -f "$SECRETS_DIR/git_token.txt"
|
||||
|
||||
# ============================================================================
|
||||
# Phase 4: Test Secret File References
|
||||
# ============================================================================
|
||||
print_header "Phase 4: Validating Secret File References"
|
||||
|
||||
# Check that docker-compose files reference correct secret file paths
|
||||
run_test "base.yml references ./secrets/db_root_password.txt" grep -q "./secrets/db_root_password.txt" "$PROJECT_ROOT/docker-compose.base.yml"
|
||||
run_test "base.yml references ./secrets/db_user_password.txt" grep -q "./secrets/db_user_password.txt" "$PROJECT_ROOT/docker-compose.base.yml"
|
||||
run_test "base.yml references ./secrets/redis_password.txt" grep -q "./secrets/redis_password.txt" "$PROJECT_ROOT/docker-compose.base.yml"
|
||||
run_test "base.yml references ./secrets/app_key.txt" grep -q "./secrets/app_key.txt" "$PROJECT_ROOT/docker-compose.base.yml"
|
||||
run_test "base.yml references ./secrets/vault_encryption_key.txt" grep -q "./secrets/vault_encryption_key.txt" "$PROJECT_ROOT/docker-compose.base.yml"
|
||||
run_test "base.yml references ./secrets/git_token.txt" grep -q "./secrets/git_token.txt" "$PROJECT_ROOT/docker-compose.base.yml"
|
||||
|
||||
# ============================================================================
|
||||
# Phase 5: Test *_FILE Pattern Support
|
||||
# ============================================================================
|
||||
print_header "Phase 5: Testing *_FILE Pattern Support"
|
||||
|
||||
# Check that docker-compose files use *_FILE pattern
|
||||
run_test "production uses DB_PASSWORD_FILE pattern" grep -q "DB_PASSWORD_FILE" "$PROJECT_ROOT/docker-compose.production.yml" || grep -q "db_user_password" "$PROJECT_ROOT/docker-compose.production.yml"
|
||||
run_test "production uses REDIS_PASSWORD_FILE pattern" grep -q "REDIS_PASSWORD_FILE" "$PROJECT_ROOT/docker-compose.production.yml" || grep -q "redis_password" "$PROJECT_ROOT/docker-compose.production.yml"
|
||||
run_test "production uses APP_KEY_FILE pattern" grep -q "APP_KEY_FILE" "$PROJECT_ROOT/docker-compose.production.yml" || grep -q "app_key" "$PROJECT_ROOT/docker-compose.production.yml"
|
||||
run_test "production uses VAULT_ENCRYPTION_KEY_FILE pattern" grep -q "VAULT_ENCRYPTION_KEY_FILE" "$PROJECT_ROOT/docker-compose.production.yml" || grep -q "vault_encryption_key" "$PROJECT_ROOT/docker-compose.production.yml"
|
||||
|
||||
run_test "staging uses DB_PASSWORD_FILE pattern" grep -q "DB_PASSWORD_FILE" "$PROJECT_ROOT/docker-compose.staging.yml" || grep -q "db_user_password" "$PROJECT_ROOT/docker-compose.staging.yml"
|
||||
run_test "staging uses APP_KEY_FILE pattern" grep -q "APP_KEY_FILE" "$PROJECT_ROOT/docker-compose.staging.yml" || grep -q "app_key" "$PROJECT_ROOT/docker-compose.staging.yml"
|
||||
run_test "staging uses VAULT_ENCRYPTION_KEY_FILE pattern" grep -q "VAULT_ENCRYPTION_KEY_FILE" "$PROJECT_ROOT/docker-compose.staging.yml" || grep -q "vault_encryption_key" "$PROJECT_ROOT/docker-compose.staging.yml"
|
||||
run_test "staging uses GIT_TOKEN_FILE pattern" grep -q "GIT_TOKEN_FILE" "$PROJECT_ROOT/docker-compose.staging.yml" || grep -q "git_token" "$PROJECT_ROOT/docker-compose.staging.yml"
|
||||
|
||||
# ============================================================================
|
||||
# Phase 6: Test DockerSecretsResolver Integration
|
||||
# ============================================================================
|
||||
print_header "Phase 6: Testing DockerSecretsResolver Integration"
|
||||
|
||||
# Check that DockerSecretsResolver exists
|
||||
run_test "DockerSecretsResolver.php exists" test -f "$PROJECT_ROOT/src/Framework/Config/DockerSecretsResolver.php"
|
||||
|
||||
# Check that Environment.php uses DockerSecretsResolver
|
||||
run_test "Environment.php imports DockerSecretsResolver" grep -q "DockerSecretsResolver" "$PROJECT_ROOT/src/Framework/Config/Environment.php"
|
||||
run_test "Environment.php resolves secrets via *_FILE pattern" grep -q "secretsResolver->resolve" "$PROJECT_ROOT/src/Framework/Config/Environment.php"
|
||||
|
||||
# Test secret resolution logic
|
||||
if command -v php &> /dev/null; then
|
||||
PHP_TEST_SCRIPT=$(cat <<'PHPEOF'
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use App\Framework\Config\DockerSecretsResolver;
|
||||
|
||||
$resolver = new DockerSecretsResolver();
|
||||
|
||||
// Test 1: Resolve secret from file
|
||||
$variables = [
|
||||
'DB_PASSWORD_FILE' => __DIR__ . '/secrets/db_user_password.txt',
|
||||
];
|
||||
|
||||
$result = $resolver->resolve('DB_PASSWORD', $variables);
|
||||
if ($result === 'test-db-user-password-67890') {
|
||||
echo "? Secret resolution works\n";
|
||||
exit(0);
|
||||
} else {
|
||||
echo "? Secret resolution failed: got '$result'\n";
|
||||
exit(1);
|
||||
}
|
||||
PHPEOF
|
||||
)
|
||||
|
||||
echo "$PHP_TEST_SCRIPT" > "$PROJECT_ROOT/test_secret_resolver.php"
|
||||
|
||||
run_test "DockerSecretsResolver resolves secrets correctly" bash -c "cd '$PROJECT_ROOT' && php test_secret_resolver.php > /dev/null 2>&1"
|
||||
|
||||
# Cleanup
|
||||
rm -f "$PROJECT_ROOT/test_secret_resolver.php"
|
||||
else
|
||||
print_error "PHP not available, skipping DockerSecretsResolver test"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Phase 7: Test EncryptedEnvLoader Integration
|
||||
# ============================================================================
|
||||
print_header "Phase 7: Testing EncryptedEnvLoader Integration"
|
||||
|
||||
run_test "EncryptedEnvLoader.php exists" test -f "$PROJECT_ROOT/src/Framework/Config/EncryptedEnvLoader.php"
|
||||
run_test "EncryptedEnvLoader loads system environment" grep -q "loadSystemEnvironment" "$PROJECT_ROOT/src/Framework/Config/EncryptedEnvLoader.php"
|
||||
run_test "EncryptedEnvLoader supports encryption key" grep -q "ENCRYPTION_KEY" "$PROJECT_ROOT/src/Framework/Config/EncryptedEnvLoader.php"
|
||||
|
||||
# ============================================================================
|
||||
# Phase 8: Test Entrypoint Script
|
||||
# ============================================================================
|
||||
print_header "Phase 8: Testing Entrypoint Script"
|
||||
|
||||
run_test "entrypoint.sh exists" test -f "$PROJECT_ROOT/docker/entrypoint.sh"
|
||||
run_test "entrypoint.sh loads secrets from *_FILE pattern" grep -q "_FILE" "$PROJECT_ROOT/docker/entrypoint.sh" || grep -q "DockerSecretsResolver" "$PROJECT_ROOT/docker/entrypoint.sh"
|
||||
run_test "entrypoint.sh is executable" test -x "$PROJECT_ROOT/docker/entrypoint.sh" || [ -f "$PROJECT_ROOT/docker/entrypoint.sh" ]
|
||||
|
||||
# ============================================================================
|
||||
# Phase 9: Validate Service Configuration
|
||||
# ============================================================================
|
||||
print_header "Phase 9: Validating Service Configuration"
|
||||
|
||||
# Check that services reference secrets correctly
|
||||
run_test "production php service uses secrets" grep -A 5 "php:" "$PROJECT_ROOT/docker-compose.production.yml" | grep -q "secrets:" || grep -q "APP_KEY_FILE" "$PROJECT_ROOT/docker-compose.production.yml"
|
||||
run_test "production queue-worker uses secrets" grep -A 10 "queue-worker:" "$PROJECT_ROOT/docker-compose.production.yml" | grep -q "secrets:" || grep -q "DB_PASSWORD_FILE" "$PROJECT_ROOT/docker-compose.production.yml"
|
||||
run_test "staging-app uses secrets" grep -A 10 "staging-app:" "$PROJECT_ROOT/docker-compose.staging.yml" | grep -q "secrets:" || grep -q "DB_PASSWORD_FILE" "$PROJECT_ROOT/docker-compose.staging.yml"
|
||||
|
||||
# ============================================================================
|
||||
# Phase 10: Test Docker Compose Override Chain
|
||||
# ============================================================================
|
||||
print_header "Phase 10: Testing Docker Compose Override Chain"
|
||||
|
||||
# Test that override chain works correctly
|
||||
if command -v docker-compose &> /dev/null || command -v docker &> /dev/null; then
|
||||
run_test "local override combines with base" bash -c "cd '$PROJECT_ROOT' && (docker-compose -f docker-compose.base.yml -f docker-compose.local.yml config > /dev/null 2>&1 || docker compose -f docker-compose.base.yml -f docker-compose.local.yml config > /dev/null 2>&1)"
|
||||
run_test "staging override combines with base" bash -c "cd '$PROJECT_ROOT' && (docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml config > /dev/null 2>&1 || docker compose -f docker-compose.base.yml -f docker-compose.staging.yml config > /dev/null 2>&1)"
|
||||
run_test "production override combines with base" bash -c "cd '$PROJECT_ROOT' && (docker-compose -f docker-compose.base.yml -f docker-compose.production.yml config > /dev/null 2>&1 || docker compose -f docker-compose.base.yml -f docker-compose.production.yml config > /dev/null 2>&1)"
|
||||
else
|
||||
print_error "docker-compose not available, skipping override chain test"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Summary
|
||||
# ============================================================================
|
||||
print_header "Test Summary"
|
||||
|
||||
echo ""
|
||||
echo -e "Total Tests: ${TESTS_TOTAL}"
|
||||
echo -e "${GREEN}Passed: ${TESTS_PASSED}${NC}"
|
||||
echo -e "${RED}Failed: ${TESTS_FAILED}${NC}"
|
||||
echo ""
|
||||
|
||||
if [ $TESTS_FAILED -eq 0 ]; then
|
||||
echo -e "${GREEN}? All tests passed!${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}Next steps:${NC}"
|
||||
echo " 1. Deploy secrets to your server using Ansible Vault"
|
||||
echo " 2. Run: docker-compose -f docker-compose.base.yml -f docker-compose.production.yml up -d"
|
||||
echo " 3. Verify secrets are loaded correctly in containers"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}? Some tests failed. Please review the errors above.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user