added keygen script
This commit is contained in:
353
keygen.sh
Executable file
353
keygen.sh
Executable file
@@ -0,0 +1,353 @@
|
|||||||
|
#!/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
|
||||||
Reference in New Issue
Block a user