#!/usr/bin/env python3 """ Wesal Social Media App - Installation Script This script sets up all necessary configuration files and secrets for the Wesal app. By Saleh Bubshait """ import os import sys import secrets import string import shutil from pathlib import Path from typing import Optional # ANSI color codes class Colors: BLUE = '\033[94m' GREEN = '\033[92m' YELLOW = '\033[93m' RED = '\033[91m' BOLD = '\033[1m' END = '\033[0m' def print_ascii_logo(): """Print the Wesal logo in ASCII art with blue color""" logo = """ ██╗ ██╗███████╗███████╗ █████╗ ██╗ ██║ ██║██╔════╝██╔════╝██╔══██╗██║ ██║ █╗ ██║█████╗ ███████╗███████║██║ ██║███╗██║██╔══╝ ╚════██║██╔══██║██║ ╚███╔███╔╝███████╗███████║██║ ██║███████╗ ╚══╝╚══╝ ╚══════╝╚══════╝╚═╝ ╚═╝╚══════╝ Installation Script """ print(f"{Colors.BLUE}{Colors.BOLD}{logo}{Colors.END}") def generate_complex_password(length: int = 20) -> str: """Generate a complex password with specified requirements""" # Define character sets lowercase = string.ascii_lowercase uppercase = string.ascii_uppercase digits = string.digits special_chars = "*@!" # Ensure at least one character from each category password_chars = [ secrets.choice(lowercase), secrets.choice(uppercase), secrets.choice(digits), secrets.choice(special_chars) ] # Fill the rest randomly all_chars = lowercase + uppercase + digits + special_chars for _ in range(length - 4): password_chars.append(secrets.choice(all_chars)) # Shuffle the password secrets.SystemRandom().shuffle(password_chars) return ''.join(password_chars) def get_user_input(prompt: str, default: Optional[str] = None) -> str: """Get user input with optional default value""" if default: full_prompt = f"{Colors.YELLOW}{prompt} (default: {default}): {Colors.END}" else: full_prompt = f"{Colors.YELLOW}{prompt}: {Colors.END}" user_input = input(full_prompt).strip() return user_input if user_input else (default or "") def print_success(message: str): """Print success message in green""" print(f"{Colors.GREEN}✓ {message}{Colors.END}") def print_error(message: str): """Print error message in red""" print(f"{Colors.RED}✗ {message}{Colors.END}") def print_info(message: str): """Print info message in blue""" print(f"{Colors.BLUE}ℹ {message}{Colors.END}") def create_env_files(hostname: str, port: str, db_name: str, password: str): """Create .env and env.properties files""" # Create backend resources directory if it doesn't exist resources_dir = Path("backend/src/main/resources") resources_dir.mkdir(parents=True, exist_ok=True) # Prepare the environment variables content env_content = f"""DB_URL=jdbc:postgresql://{hostname}:{port}/{db_name} DB_USER=wesaladmin DB_PASSWORD={password} DB_DB={db_name}""" # Write .env file in TOP LEVEL (root directory) env_file_path = Path(".env") with open(env_file_path, 'w') as f: f.write(env_content) print_success(f"Created {env_file_path} (top level)") # Write env.properties file in resources directory env_properties_path = resources_dir / "env.properties" with open(env_properties_path, 'w') as f: f.write(env_content) print_success(f"Created {env_properties_path}") def setup_database_config(): """Setup database configuration""" print(f"\n{Colors.BOLD}📊 Database Configuration{Colors.END}") print("Setting up PostgreSQL connection parameters...") hostname = get_user_input("Database hostname", "db") port = get_user_input("Database port", "5432") db_name = get_user_input("Database name", "prod") # Ask if user wants to generate password or provide their own generate_pass = get_user_input("Generate complex password automatically? (y/n)", "y").lower() if generate_pass in ['y', 'yes', '']: password = generate_complex_password() print_info(f"Generated password: {password}") else: password = get_user_input("Enter database password") if not password: print_error("Password cannot be empty. Generating one automatically...") password = generate_complex_password() print_info(f"Generated password: {password}") create_env_files(hostname, port, db_name, password) return password def setup_server_config(): """Setup server configuration for API base URL""" print(f"\n{Colors.BOLD}🌐 Server Configuration{Colors.END}") print("Setting up server hostname and port for API access...") server_hostname = get_user_input("Server hostname", "server") server_port = get_user_input("Server port", "8080") # Update the API constants file api_constants_path = Path("frontend/lib/constants/api_constants.dart") if not api_constants_path.exists(): print_error(f"Could not find {api_constants_path}") print_info("You'll need to manually update the API base URL in your constants file") return # Read the existing file try: with open(api_constants_path, 'r') as f: content = f.read() # Find and replace the baseUrl line lines = content.split('\n') updated_lines = [] found_base_url = False for line in lines: if 'static const String baseUrl' in line and '=' in line: # Replace the line with the new hostname and port indent = line[:len(line) - len(line.lstrip())] # Preserve indentation new_line = f"{indent}static const String baseUrl = 'http://{server_hostname}:{server_port}';" updated_lines.append(new_line) found_base_url = True print_info(f"Updated baseUrl to: http://{server_hostname}:{server_port}") else: updated_lines.append(line) if not found_base_url: print_error("Could not find 'static const String baseUrl' line in api_constants.dart") print_info("You'll need to manually update the API base URL") return # Write the updated content back with open(api_constants_path, 'w') as f: f.write('\n'.join(updated_lines)) print_success(f"Updated {api_constants_path}") except Exception as e: print_error(f"Error updating API constants file: {e}") print_info("You'll need to manually update the API base URL") def setup_firebase_credentials(): """Setup Firebase service account credentials""" print(f"\n{Colors.BOLD}🔥 Firebase Configuration{Colors.END}") # Ask if user needs guidance need_help = get_user_input("Do you need help finding the Firebase Service Account JSON file? (y/n)", "n").lower() if need_help in ['y', 'yes']: print(f"\n{Colors.BLUE}📋 Steps to get Firebase Service Account JSON:{Colors.END}") print("1. Go to https://console.firebase.google.com/") print("2. Select your project") print("3. Click on the gear icon (Project Settings)") print("4. Go to the 'Service accounts' tab") print("5. Click 'Generate new private key'") print("6. Download the JSON file") print("7. Save it in the current directory and name it 'firebase-service-account.json'") print() print_info("Please place your Firebase Service Account JSON file in the current directory") print_info("Name the file: 'firebase-service-account.json'") while True: input(f"{Colors.YELLOW}Press Enter when the file is ready...{Colors.END}") firebase_file = Path("firebase-service-account.json") if firebase_file.exists(): print_success("Firebase service account file found!") break else: print_error("File 'firebase-service-account.json' not found in current directory.") retry = get_user_input("Try again? (y/n)", "y").lower() if retry not in ['y', 'yes', '']: print_error("Skipping Firebase setup...") return False # Copy to backend resources backend_resources = Path("backend/src/main/resources") backend_resources.mkdir(parents=True, exist_ok=True) backend_firebase_path = backend_resources / "firebase-service-account.json" shutil.copy2(firebase_file, backend_firebase_path) print_success(f"Copied Firebase credentials to {backend_firebase_path}") # Copy to frontend directory (the specified path) frontend_firebase_path = Path("/Users/bubshait/Desktop/My life/2025/SWA/wesal_app/frontend/firebase-service-account.json") try: # Create parent directories if they don't exist frontend_firebase_path.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(firebase_file, frontend_firebase_path) print_success(f"Copied Firebase credentials to {frontend_firebase_path}") except Exception as e: print_error(f"Could not copy to frontend directory: {e}") print_info("You may need to manually copy the file to the frontend directory") return True def print_summary(): """Print installation summary""" print(f"\n{Colors.BOLD}{Colors.GREEN}🎉 Installation Complete!{Colors.END}") print(f"\n{Colors.BOLD}Files created/updated:{Colors.END}") print("• .env (top level)") print("• backend/src/main/resources/env.properties") print("• backend/src/main/resources/firebase-service-account.json") print("• frontend/lib/constants/api_constants.dart (updated)") print(f"\n{Colors.BOLD}Next steps:{Colors.END}") print("1. Run: docker-compose up --build") print("2. Access your app at: http://localhost:6060") print("3. Access Adminer (DB admin) at: http://localhost:8100") print("4. API will be available at: http://localhost:4044") print(f"\n{Colors.BLUE}Happy coding! 🚀{Colors.END}") def main(): """Main installation function""" try: # Print logo print_ascii_logo() print("Welcome to the Wesal Social Media App installer!") print("This script will help you set up all necessary configuration files.\n") # Setup database db_password = setup_database_config() # Setup server configuration setup_server_config() # Setup Firebase firebase_success = setup_firebase_credentials() # Print summary print_summary() if not firebase_success: print(f"\n{Colors.YELLOW}⚠️ Note: Firebase setup was skipped. You'll need to set it up manually later.{Colors.END}") except KeyboardInterrupt: print(f"\n{Colors.RED}Installation cancelled by user.{Colors.END}") sys.exit(1) except Exception as e: print_error(f"An error occurred: {e}") sys.exit(1) if __name__ == "__main__": main()