editing deploy

This commit is contained in:
Flatlogic Bot 2026-01-30 16:40:00 +00:00
parent 74aa3572c9
commit ac3f000214
3 changed files with 128 additions and 33 deletions

View File

@ -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"]

View 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 ===")

View File

@ -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)