From ac3f00021479c0892fc5b80a6120a45a866b0d63 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 30 Jan 2026 16:40:00 +0000 Subject: [PATCH] editing deploy --- Dockerfile | 7 +- .../management/commands/test_db_connection.py | 90 +++++++++++++++++++ core/management/commands/wait_for_db.py | 64 ++++++------- 3 files changed, 128 insertions(+), 33 deletions(-) create mode 100644 core/management/commands/test_db_connection.py diff --git a/Dockerfile b/Dockerfile index 7a27f03..81da2e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,15 +10,20 @@ WORKDIR /app # Install system dependencies # WeasyPrint needs: libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0 libjpeg-dev libopenjp2-7-dev libxcb1 # PyMySQL does not need libmysqlclient-dev, but we keep basic build tools +# Added libgobject-2.0-0, libcairo2, libgdk-pixbuf2.0-0, shared-mime-info for full WeasyPrint support RUN apt-get update && apt-get install -y \ gcc \ pkg-config \ + libgobject-2.0-0 \ + libcairo2 \ libpango-1.0-0 \ libpangoft2-1.0-0 \ libharfbuzz-subset0 \ libjpeg-dev \ libopenjp2-7-dev \ libxcb1 \ + libgdk-pixbuf2.0-0 \ + shared-mime-info \ curl \ && rm -rf /var/lib/apt/lists/* @@ -37,4 +42,4 @@ RUN chmod +x /app/entrypoint.sh EXPOSE 8000 # Entrypoint -ENTRYPOINT ["/app/entrypoint.sh"] +ENTRYPOINT ["/app/entrypoint.sh"] \ No newline at end of file diff --git a/core/management/commands/test_db_connection.py b/core/management/commands/test_db_connection.py new file mode 100644 index 0000000..d1dbb96 --- /dev/null +++ b/core/management/commands/test_db_connection.py @@ -0,0 +1,90 @@ +import socket +import sys +import os +from django.core.management.base import BaseCommand +from django.conf import settings +from django.db import connections +from django.db.utils import OperationalError + +class Command(BaseCommand): + help = 'Tests database connectivity step-by-step (DNS, TCP, Auth)' + + def handle(self, *args, **options): + self.stdout.write(self.style.SUCCESS("=== Starting Database Connection Diagnostics ===")) + + # 1. Inspect Configuration + db_conf = settings.DATABASES['default'] + host = db_conf.get('HOST') + port = db_conf.get('PORT') + user = db_conf.get('USER') + name = db_conf.get('NAME') + + # Mask password + password = db_conf.get('PASSWORD') + masked_password = "*****" if password else "None" + + self.stdout.write(f"Configuration:") + self.stdout.write(f" HOST: {host}") + self.stdout.write(f" PORT: {port}") + self.stdout.write(f" USER: {user}") + self.stdout.write(f" NAME: {name}") + self.stdout.write(f" PASS: {masked_password}") + + if not host: + self.stdout.write(self.style.ERROR("ERROR: DB_HOST is not set or empty.")) + return + + # 2. DNS Resolution + self.stdout.write("\n--- Step 1: DNS Resolution ---") + ip_address = None + try: + ip_address = socket.gethostbyname(host) + self.stdout.write(self.style.SUCCESS(f"✔ Success: '{host}' resolved to {ip_address}")) + except socket.gaierror as e: + self.stdout.write(self.style.ERROR(f"✘ Failed: Could not resolve hostname '{host}'. Error: {e}")) + self.stdout.write(self.style.WARNING("Tip: Check for typos in DB_HOST. If this is a Docker container, ensure it is in the same network.")) + return + + # 3. TCP Connection Test + self.stdout.write("\n--- Step 2: TCP Connection Check ---") + try: + port_int = int(port) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(5) # 5 second timeout + result = sock.connect_ex((ip_address, port_int)) + if result == 0: + self.stdout.write(self.style.SUCCESS(f"✔ Success: Connected to {ip_address}:{port_int} via TCP.")) + else: + self.stdout.write(self.style.ERROR(f"✘ Failed: Could not connect to {ip_address}:{port_int}.")) + self.stdout.write(self.style.ERROR(f" Error Code: {result} (Check OS specific socket error codes)")) + self.stdout.write(self.style.WARNING("Possible causes:")) + self.stdout.write(" 1. Firewall blocking the port (Check 'Remote MySQL' in Hostinger/cPanel).") + self.stdout.write(" 2. Database server is down.") + self.stdout.write(" 3. Database is listening on localhost only (Bind Address issue).") + sock.close() + except Exception as e: + self.stdout.write(self.style.ERROR(f"✘ Error during TCP check: {e}")) + + # 4. Django/Driver Connection Test + self.stdout.write("\n--- Step 3: Database Authentication ---") + try: + conn = connections['default'] + conn.cursor() # Forces connection + self.stdout.write(self.style.SUCCESS(f"✔ Success: Authenticated and connected to database '{name}'.")) + except OperationalError as e: + self.stdout.write(self.style.ERROR("✘ Failed: Database Driver Error.")) + self.stdout.write(f" Error: {e}") + + error_str = str(e) + if "2003" in error_str: + self.stdout.write(self.style.WARNING("\nAnalysis for Error 2003 (Can't connect to MySQL server):")) + self.stdout.write(" - If TCP check (Step 2) failed: The issue is Network/Firewall.") + self.stdout.write(" - If TCP check passed: The issue might be SSL/TLS requirements or packet filtering.") + elif "1045" in error_str: + self.stdout.write(self.style.WARNING("\nAnalysis for Error 1045 (Access Denied):")) + self.stdout.write(" - User/Password is incorrect.") + self.stdout.write(" - User is not allowed to connect from this specific IP (Hostinger 'Remote MySQL' whitelist).") + except Exception as e: + self.stdout.write(self.style.ERROR(f"✘ Unexpected Error: {e}")) + + self.stdout.write("\n=== Diagnostics Complete ===") diff --git a/core/management/commands/wait_for_db.py b/core/management/commands/wait_for_db.py index 5f62a55..7cfad7d 100644 --- a/core/management/commands/wait_for_db.py +++ b/core/management/commands/wait_for_db.py @@ -1,4 +1,5 @@ import time +import socket from django.db import connections from django.db.utils import OperationalError from django.core.management.base import BaseCommand @@ -6,53 +7,52 @@ from django.conf import settings class Command(BaseCommand): """Django command to pause execution until database is available""" - - # Critical: Disable system checks to prevent the command from crashing - # if the database is not yet available. requires_system_checks = [] def handle(self, *args, **options): - # Print the connection details (masked password) for debugging db_conf = settings.DATABASES['default'] - host_val = db_conf.get('HOST') - self.stdout.write(f"Debug Info - Host: {host_val}, Port: {db_conf.get('PORT')}, Name: {db_conf.get('NAME')}, User: {db_conf.get('USER')}") + host = db_conf.get('HOST') + port = db_conf.get('PORT') + + self.stdout.write(f"Debug Info - Host: {host}, Port: {port}, User: {db_conf.get('USER')}") - # DEBUG: Check which MySQLdb is loaded + # Try to resolve host immediately to catch DNS issues try: - import MySQLdb - self.stdout.write(f"DEBUG: MySQLdb module is: {MySQLdb}") - if hasattr(MySQLdb, '__file__'): - self.stdout.write(f"DEBUG: MySQLdb location: {MySQLdb.__file__}") - except ImportError: - self.stdout.write("DEBUG: MySQLdb module could NOT be imported.") + ip = socket.gethostbyname(host) + self.stdout.write(f"DEBUG: Host '{host}' resolves to IP: {ip}") + except Exception as e: + self.stdout.write(self.style.ERROR(f"DEBUG ERROR: Could not resolve hostname '{host}': {e}")) self.stdout.write('Waiting for database...') - db_conn = None - for i in range(30): # Retry for 30 seconds + for i in range(30): try: - db_conn = connections['default'] - # Try to actually connect - db_conn.cursor() + connections['default'].cursor() self.stdout.write(self.style.SUCCESS('Database available!')) return except OperationalError as e: - # Check for specific "Unknown database" error (MySQL code 1049) error_str = str(e) - if "1049" in error_str or "Unknown database" in error_str: - self.stdout.write(self.style.ERROR(f"CRITICAL ERROR: The database '{db_conf.get('NAME')}' does not exist on the server.")) - self.stdout.write(self.style.ERROR("Solution: Check your DB_NAME environment variable. It usually defaults to 'default' or the service name.")) - # Fail immediately for this specific error as it won't resolve itself + if "2003" in error_str: + self.stdout.write(self.style.WARNING(f"Connection Failed (Attempt {i+1}/30): Error 2003 - Can't connect to MySQL server.")) + # Perform a quick TCP check + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(2) + result = sock.connect_ex((host, int(port))) + if result == 0: + self.stdout.write(f" > TCP Check: SUCCESS. Server is reachable at {host}:{port}. Issue is likely Auth/SSL or strict User Host limits.") + else: + self.stdout.write(f" > TCP Check: FAILED (Code {result}). Server is NOT reachable at {host}:{port}. Check Firewall/IP.") + sock.close() + except Exception as tcp_e: + self.stdout.write(f" > TCP Check Error: {tcp_e}") + + elif "1049" in error_str: # Unknown database + self.stdout.write(self.style.ERROR(f"CRITICAL: Database '{db_conf.get('NAME')}' does not exist.")) return - - # Print the full error message - self.stdout.write(f'Database unavailable (Error: {e}), waiting 1 second...') + else: + self.stdout.write(f'Database unavailable (Error: {e}), waiting 1 second...') + time.sleep(1) - except UnicodeError as e: - self.stdout.write(self.style.ERROR(f"CONFIGURATION ERROR: The DB_HOST '{host_val}' is invalid.")) - self.stdout.write(self.style.ERROR(f"Details: {e}")) - self.stdout.write(self.style.ERROR("Hint: Check your 'DB_HOST' variable in Coolify. It seems to be too long or contains invalid characters.")) - self.stdout.write(self.style.ERROR("Common mistakes: pasting the full connection string (mysql://...) instead of just the hostname, or trailing spaces.")) - return except Exception as e: self.stdout.write(self.style.WARNING(f'Database error: {e}, waiting 1 second...')) time.sleep(1)