editing deploy
This commit is contained in:
parent
74aa3572c9
commit
ac3f000214
@ -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"]
|
||||
90
core/management/commands/test_db_connection.py
Normal file
90
core/management/commands/test_db_connection.py
Normal file
@ -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 ===")
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user