#!/bin/bash # keygen.sh - Production Environment Generator # Generates cryptographically secure secrets and configures .env files # # Usage: # ./keygen.sh # Interactive mode # ./keygen.sh --dry-run # Show what would be generated # ./keygen.sh --show-secrets # Show generated secrets (debug only) set -e # Exit on error set -u # Exit on undefined variable set -o pipefail # Exit on pipe failure # Cleanup on exit cleanup() { if [ -f .env.tmp ]; then rm -f .env.tmp fi } trap cleanup EXIT # Script version readonly VERSION="1.0.0" # Parse command line arguments DRY_RUN=false SHOW_SECRETS=false for arg in "$@"; do case $arg in --dry-run) DRY_RUN=true ;; --show-secrets) SHOW_SECRETS=true ;; --help|-h) echo "keygen.sh v${VERSION} - Production Environment Generator" echo "" echo "Usage:" echo " ./keygen.sh Interactive mode" echo " ./keygen.sh --dry-run Show what would be generated" echo " ./keygen.sh --show-secrets Show secrets (debug only)" echo "" exit 0 ;; *) echo "Unknown option: $arg" echo "Use --help for usage information" exit 1 ;; esac done # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Preflight checks check_dependencies() { echo "Prüfe Abhängigkeiten..." # Check for openssl if ! command -v openssl &> /dev/null; then echo -e "${RED}❌ Fehler: openssl ist nicht installiert${NC}" echo " Debian/Ubuntu: apt-get install openssl" echo " Alpine: apk add openssl" exit 1 fi echo -e "${GREEN}✓${NC} openssl verfügbar" } check_env_example() { if [ ! -f .env.example ]; then echo -e "${RED}❌ Fehler: .env.example nicht gefunden${NC}" echo " Script muss im Projekt-Root ausgeführt werden" echo " Aktuelles Verzeichnis: $(pwd)" exit 1 fi echo -e "${GREEN}✓${NC} .env.example gefunden" } check_write_permissions() { if [ ! -w . ]; then echo -e "${RED}❌ Fehler: Keine Schreibrechte im aktuellen Verzeichnis${NC}" echo " Verzeichnis: $(pwd)" exit 1 fi echo -e "${GREEN}✓${NC} Schreibrechte vorhanden" } # Run all preflight checks run_preflight_checks() { echo "=== Preflight Checks ===" check_dependencies check_env_example check_write_permissions echo "" } # Secret generation functions generate_password() { # Generate 32-character password from base64, removing confusing characters openssl rand -base64 48 | tr -d "=+/" | cut -c1-32 } generate_jwt_secret() { # Generate JWT secret with maximum entropy openssl rand -base64 48 } generate_encryption_key() { # Generate exactly 32 characters for AES-256-GCM local key=$(openssl rand -base64 32 | cut -c1-32) # Critical validation: MUST be exactly 32 characters if [ ${#key} -ne 32 ]; then echo -e "${RED}❌ KRITISCH: Encryption Key hat ${#key} Zeichen statt 32${NC}" echo " Generierter Key: '$key'" exit 1 fi echo "$key" } # Generate all secrets generate_secrets() { echo "Generiere Secrets..." POSTGRES_PASSWORD=$(generate_password) REDIS_PASSWORD=$(generate_password) JWT_ACCESS_SECRET=$(generate_jwt_secret) JWT_REFRESH_SECRET=$(generate_jwt_secret) ENCRYPTION_KEY=$(generate_encryption_key) # Validate all secrets were generated if [ -z "$POSTGRES_PASSWORD" ] || [ -z "$REDIS_PASSWORD" ] || \ [ -z "$JWT_ACCESS_SECRET" ] || [ -z "$JWT_REFRESH_SECRET" ] || \ [ -z "$ENCRYPTION_KEY" ]; then echo -e "${RED}❌ Fehler: Secret-Generierung fehlgeschlagen${NC}" exit 1 fi echo -e "${GREEN}✓${NC} Alle Secrets generiert" # Show secrets only if --show-secrets flag is set if [ "$SHOW_SECRETS" = true ]; then echo "" echo "=== Generierte Secrets (DEBUG MODE) ===" echo "POSTGRES_PASSWORD: $POSTGRES_PASSWORD (${#POSTGRES_PASSWORD} Zeichen)" echo "REDIS_PASSWORD: $REDIS_PASSWORD (${#REDIS_PASSWORD} Zeichen)" echo "JWT_ACCESS_SECRET: $JWT_ACCESS_SECRET (${#JWT_ACCESS_SECRET} Zeichen)" echo "JWT_REFRESH_SECRET: $JWT_REFRESH_SECRET (${#JWT_REFRESH_SECRET} Zeichen)" echo "ENCRYPTION_KEY: $ENCRYPTION_KEY (${#ENCRYPTION_KEY} Zeichen)" echo "" fi } # Domain configuration get_domain_configuration() { echo "=== Secure Portal - Production Environment Setup ===" echo "" # Get domain from user read -p "Domain (z.B. portal.example.com): " DOMAIN # Validate domain is not empty if [ -z "$DOMAIN" ]; then echo -e "${RED}❌ Fehler: Domain ist erforderlich${NC}" exit 1 fi # Generate URLs from domain FRONTEND_URL="https://$DOMAIN" VITE_API_URL="https://$DOMAIN/api" PASSWORD_RESET_URL="https://$DOMAIN/password-reset" CORS_ORIGIN="https://$DOMAIN" # Show configuration echo "" echo "=== Konfiguration ===" echo "Domain: $DOMAIN" echo "Frontend URL: $FRONTEND_URL" echo "API URL: $VITE_API_URL" echo "" # Confirm configuration read -p "Konfiguration korrekt? (ja/nein): " confirm if [ "$confirm" != "ja" ]; then echo "Abgebrochen." exit 0 fi echo "" } # Backup management cleanup_old_backups() { # Keep only last 5 backups local backups=$(ls -t .env.backup-* 2>/dev/null | tail -n +6) if [ -n "$backups" ]; then echo "$backups" | xargs rm -f 2>/dev/null local count=$(echo "$backups" | wc -l) echo -e "${GREEN}✓${NC} $count alte Backups gelöscht" fi } check_existing_env() { if [ ! -f .env ]; then return 0 # No .env exists, safe to proceed fi echo -e "${YELLOW}⚠️ WARNUNG: .env existiert bereits!${NC}" # Show last modification time if command -v stat &> /dev/null; then if stat -c %y .env &> /dev/null 2>&1; then # GNU stat echo " Letzte Änderung: $(stat -c %y .env)" else # BSD stat (macOS) echo " Letzte Änderung: $(stat -f %Sm .env)" fi fi # Check if it contains production data if grep -qv "change_this\|your_" .env 2>/dev/null; then echo -e " ${YELLOW}⚠️ PRODUKTIV-DATEN GEFUNDEN!${NC}" fi # Ask for confirmation read -p " Überschreiben? Backup wird erstellt (ja/nein): " confirm if [ "$confirm" != "ja" ]; then echo "Abgebrochen." exit 0 fi # Create backup with timestamp backup=".env.backup-$(date +%Y%m%d-%H%M%S)" cp .env "$backup" echo -e " ${GREEN}✓${NC} Backup erstellt: $backup" echo "" # Cleanup old backups cleanup_old_backups } # .env file generation generate_env_file() { echo "Generiere .env Datei..." if [ "$DRY_RUN" = true ]; then echo "" echo "=== DRY RUN MODE - Änderungen werden NICHT gespeichert ===" echo "" fi # Create temporary file local tmpfile=".env.tmp" # Copy template cp .env.example "$tmpfile" # Replace secrets (using | as delimiter for URLs) sed -i "s|POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$POSTGRES_PASSWORD|" "$tmpfile" || { echo -e "${RED}❌ Fehler beim Ersetzen von POSTGRES_PASSWORD${NC}" rm "$tmpfile" exit 1 } sed -i "s|REDIS_PASSWORD=.*|REDIS_PASSWORD=$REDIS_PASSWORD|" "$tmpfile" sed -i "s|JWT_ACCESS_SECRET=.*|JWT_ACCESS_SECRET=$JWT_ACCESS_SECRET|" "$tmpfile" sed -i "s|JWT_REFRESH_SECRET=.*|JWT_REFRESH_SECRET=$JWT_REFRESH_SECRET|" "$tmpfile" sed -i "s|ENCRYPTION_KEY=.*|ENCRYPTION_KEY=$ENCRYPTION_KEY|" "$tmpfile" # Replace URLs sed -i "s|FRONTEND_URL=.*|FRONTEND_URL=$FRONTEND_URL|" "$tmpfile" sed -i "s|VITE_API_URL=.*|VITE_API_URL=$VITE_API_URL|" "$tmpfile" sed -i "s|PASSWORD_RESET_URL=.*|PASSWORD_RESET_URL=$PASSWORD_RESET_URL|" "$tmpfile" sed -i "s|CORS_ORIGIN=.*|CORS_ORIGIN=$CORS_ORIGIN|" "$tmpfile" # Verify all replacements succeeded if grep -q "change_this" "$tmpfile"; then echo -e "${YELLOW}⚠️ Warnung: Einige Platzhalter wurden nicht ersetzt${NC}" echo " Dies ist normal für Werte die manuell konfiguriert werden müssen" fi if [ "$DRY_RUN" = true ]; then # Show what would be generated echo "=== Generated .env content (preview) ===" grep -E "POSTGRES_PASSWORD|REDIS_PASSWORD|JWT_ACCESS_SECRET|JWT_REFRESH_SECRET|ENCRYPTION_KEY|FRONTEND_URL|VITE_API_URL" "$tmpfile" | head -20 echo "..." echo "" rm "$tmpfile" echo "Dry run complete. No files were modified." return 0 fi # Move to final location with secure permissions mv "$tmpfile" .env chmod 600 .env echo -e "${GREEN}✓${NC} .env erstellt mit sicheren Permissions (600)" } # Success output show_success() { echo "" echo -e "${GREEN}✅ .env erfolgreich erstellt!${NC}" echo "" echo "=== Generierte Secrets ===" echo "✓ PostgreSQL Passwort (32 Zeichen)" echo "✓ Redis Passwort (32 Zeichen)" echo "✓ JWT Access Secret (~64 Zeichen)" echo "✓ JWT Refresh Secret (~64 Zeichen)" echo "✓ Encryption Key (exakt 32 Zeichen)" echo "" echo "=== Konfigurierte URLs ===" echo "✓ Frontend: $FRONTEND_URL" echo "✓ API: $VITE_API_URL" echo "" echo "=== Nächste Schritte ===" echo "1. Email-Konfiguration anpassen (optional):" echo " nano .env" echo "" echo "2. Container starten:" echo " docker compose up -d" echo "" if [ -n "${backup:-}" ]; then echo "3. Backup gespeichert: $backup" echo "" fi } # Main workflow main() { run_preflight_checks check_existing_env get_domain_configuration generate_secrets generate_env_file if [ "$DRY_RUN" = false ]; then show_success fi } # Main execution starts here echo "keygen.sh v${VERSION}" echo "" main