mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-18 11:59:26 +00:00
Implement comprehensive deployment isolation fixes and verification tools
This commit is contained in:
parent
0a9d0df15d
commit
e94fcded2a
7 changed files with 534 additions and 48 deletions
155
DATABASE_ISOLATION_FIX.md
Normal file
155
DATABASE_ISOLATION_FIX.md
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
# Database Isolation Fix for LearnHouse Deployments
|
||||
|
||||
## Issues Identified: Multiple Sources of Cross-Deployment Contamination
|
||||
|
||||
We have identified several issues causing cross-deployment contamination between DEV (adr-lms.whitex.cloud) and LIVE (edu.adradviser.ro) environments:
|
||||
|
||||
### 1. Shared Database Connection
|
||||
|
||||
Both deployments are connecting to the same database server, causing data sharing:
|
||||
|
||||
- DEV: `postgresql://learnhouse_dev:YOUR_DEV_DB_PASSWORD@db:5432/learnhouse_dev`
|
||||
- LIVE: `postgresql://learnhouse:YOUR_LIVE_DB_PASSWORD@db:5432/learnhouse`
|
||||
|
||||
The container networking is not isolating these properly, causing both deployments to resolve `db` to the same physical database server.
|
||||
|
||||
### 2. Hardcoded URLs in Frontend Bundle
|
||||
|
||||
The DEV deployment contains hardcoded URLs pointing to the LIVE site:
|
||||
- `http://edu.adradviser.ro/collections/new`
|
||||
- `http://edu.adradviser.ro/courses?new=true`
|
||||
|
||||
These URLs are embedded in the JavaScript bundles at build time and aren't being properly updated at runtime.
|
||||
|
||||
### 3. Container API Access Inconsistencies
|
||||
|
||||
The API is accessed differently from inside containers versus from outside:
|
||||
- Inside container: via `http://localhost:9000`
|
||||
- External access: via domain's `/api/v1` path
|
||||
|
||||
This inconsistency complicates URL patching and can cause references to the wrong domain.
|
||||
|
||||
## How to Fix Deployment Isolation
|
||||
|
||||
### 1. Database Connection Isolation
|
||||
|
||||
Each deployment must use its own fully-qualified database hostname:
|
||||
|
||||
1. For DEV deployment:
|
||||
```
|
||||
LEARNHOUSE_SQL_CONNECTION_STRING=postgresql://learnhouse_dev:YOUR_DEV_DB_PASSWORD@db-dev.${DEPLOYMENT_NAME}-network:5432/learnhouse_dev
|
||||
```
|
||||
|
||||
2. For LIVE deployment:
|
||||
```
|
||||
LEARNHOUSE_SQL_CONNECTION_STRING=postgresql://learnhouse:YOUR_LIVE_DB_PASSWORD@db-live.${DEPLOYMENT_NAME}-network:5432/learnhouse
|
||||
```
|
||||
|
||||
This ensures:
|
||||
- Each deployment uses a uniquely named database server
|
||||
- The server hostname includes the deployment name for clarity
|
||||
- The network name is deployment-specific
|
||||
|
||||
### 2. Enhanced URL Patching
|
||||
|
||||
We've improved the URL patching in Dockerfile_coolify to handle multiple URL formats:
|
||||
|
||||
```bash
|
||||
# Replace all occurrences of edu.adradviser.ro with the current domain
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s|http://edu.adradviser.ro|$NEXT_PUBLIC_LEARNHOUSE_DOMAIN|g" {} \;
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s|https://edu.adradviser.ro|$NEXT_PUBLIC_LEARNHOUSE_DOMAIN|g" {} \;
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s|//edu.adradviser.ro|//$DOMAIN_ONLY|g" {} \;
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s|\"href\":\"http://edu.adradviser.ro|\"href\":\"$NEXT_PUBLIC_LEARNHOUSE_DOMAIN|g" {} \;
|
||||
```
|
||||
|
||||
### 3. Cookie Domain Isolation
|
||||
|
||||
Ensure each deployment uses its own cookie domain:
|
||||
|
||||
```
|
||||
LEARNHOUSE_COOKIE_DOMAIN=adr-lms.whitex.cloud # For DEV
|
||||
LEARNHOUSE_COOKIE_DOMAIN=edu.adradviser.ro # For LIVE
|
||||
```
|
||||
|
||||
### 4. API URL Inside vs Outside Container
|
||||
|
||||
Address the inconsistency in how the API is accessed:
|
||||
|
||||
1. Inside container: `http://localhost:9000`
|
||||
2. External access: via domain's `/api/v1` path
|
||||
|
||||
Solution:
|
||||
- Added the debug endpoint at `/api/v1/debug` to ensure it's accessible externally
|
||||
- Enhanced the URL patching to handle both internal and external URL formats
|
||||
|
||||
### 5. API Debug Endpoints
|
||||
|
||||
Added comprehensive debug endpoints:
|
||||
|
||||
1. `/api/v1/debug/deployment` - Shows deployment configuration details
|
||||
2. `/api/v1/debug/urls` - Scans for any remaining hardcoded URLs in the frontend bundle
|
||||
|
||||
## Verification Process
|
||||
|
||||
After implementing these fixes, use this verification process:
|
||||
|
||||
1. **Deploy the API changes** to add the debug endpoints:
|
||||
```bash
|
||||
git add apps/api/src/routers/debug.py apps/api/src/router.py apps/api/app.py
|
||||
git commit -m "Add isolation debug endpoints for deployment verification"
|
||||
git push
|
||||
# Deploy through your CI/CD process
|
||||
```
|
||||
|
||||
2. **Verify database isolation** by accessing the debug endpoints:
|
||||
```bash
|
||||
# Check DEV deployment
|
||||
curl -k https://adr-lms.whitex.cloud/api/v1/debug/deployment
|
||||
|
||||
# Check LIVE deployment
|
||||
curl -k https://edu.adradviser.ro/api/v1/debug/deployment
|
||||
```
|
||||
|
||||
3. **Check for hardcoded URLs** in the frontend bundle:
|
||||
```bash
|
||||
# Check DEV deployment
|
||||
curl -k https://adr-lms.whitex.cloud/api/v1/debug/urls
|
||||
|
||||
# Check LIVE deployment
|
||||
curl -k https://edu.adradviser.ro/api/v1/debug/urls
|
||||
```
|
||||
|
||||
4. **Test with incognito browsers** to ensure sessions don't cross-contaminate
|
||||
|
||||
5. **Run the verification script** to perform all checks automatically:
|
||||
```bash
|
||||
./verify-isolation.sh
|
||||
```
|
||||
|
||||
Solution:
|
||||
- Added the debug endpoint at `/api/v1/debug` to ensure it's accessible externally
|
||||
- Enhanced the URL patching to handle both internal and external URL formats
|
||||
|
||||
### 5. API Debug Endpoints
|
||||
|
||||
Added comprehensive debug endpoints:
|
||||
|
||||
1. `/api/v1/debug/deployment` - Shows deployment configuration details
|
||||
2. `/api/v1/debug/urls` - Scans for any remaining hardcoded URLs in the frontend bundle
|
||||
|
||||
4. **Network Isolation**: Updated docker-compose-coolify.yml to use deployment-specific networks.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
After implementing these changes:
|
||||
|
||||
1. Run `./verify-isolation.sh` to check deployment isolation
|
||||
2. Verify each deployment has its own database connection using `/api/v1/debug/deployment` endpoint
|
||||
3. Check that no cross-domain references appear in the frontend
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
1. Update database connection strings in Coolify environment variables for both deployments
|
||||
2. Rebuild and redeploy both environments to apply the changes
|
||||
3. Run the verification script to confirm isolation
|
||||
4. Clear browser cookies and caches to test with clean state
|
||||
|
|
@ -62,19 +62,46 @@ COPY ./extra/start.sh /app/start.sh
|
|||
COPY ./debug-services.sh /app/debug-services.sh
|
||||
RUN chmod +x /app/start.sh /app/debug-services.sh
|
||||
|
||||
# Add near the end of your Dockerfile_coolify
|
||||
# Add enhanced URL and domain patching
|
||||
RUN echo '#!/bin/bash\n\
|
||||
echo "Enhanced patching of NextAuth cookies..."\n\
|
||||
echo "Enhanced patching of NextAuth cookies and domains..."\n\
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s/domain:[^,}]*,/domain: undefined,/g" {} \\;\n\
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s/domain: *process.env.LEARNHOUSE_COOKIE_DOMAIN/domain: undefined/g" {} \\;\n\
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s/\.domain\s*=\s*[^;]*;/\.domain = undefined;/g" {} \\;\n\
|
||||
echo "Patch complete."\n\
|
||||
echo "Cookie domain patches complete."\n\
|
||||
\n\
|
||||
echo "Patching API URLs for deployment isolation..."\n\
|
||||
if [ ! -z "$NEXT_PUBLIC_LEARNHOUSE_API_URL" ]; then\n\
|
||||
if [ ! -z "$NEXT_PUBLIC_LEARNHOUSE_DOMAIN" ]; then\n\
|
||||
echo "Domain value found: $NEXT_PUBLIC_LEARNHOUSE_DOMAIN"\n\
|
||||
# Extract the domain without protocol\n\
|
||||
DOMAIN_ONLY=$(echo "$NEXT_PUBLIC_LEARNHOUSE_DOMAIN" | sed -E "s/^https?:\\/\\///g")\n\
|
||||
echo "Extracted domain: $DOMAIN_ONLY"\n\
|
||||
\n\
|
||||
# Replace all occurrences of edu.adradviser.ro with the current domain\n\
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s|http://edu.adradviser.ro|$NEXT_PUBLIC_LEARNHOUSE_DOMAIN|g" {} \\;\n\
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s|https://edu.adradviser.ro|$NEXT_PUBLIC_LEARNHOUSE_DOMAIN|g" {} \\;\n\
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s|//edu.adradviser.ro|//$DOMAIN_ONLY|g" {} \\;\n\
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s|\"href\":\"http://edu.adradviser.ro|\"href\":\"$NEXT_PUBLIC_LEARNHOUSE_DOMAIN|g" {} \\;\n\
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s|https://edu.adradviser.ro|$NEXT_PUBLIC_LEARNHOUSE_DOMAIN|g" {} \\;\n\
|
||||
find /app/web/.next -type f -name "*.html" -exec sed -i "s|http://edu.adradviser.ro|$NEXT_PUBLIC_LEARNHOUSE_DOMAIN|g" {} \\;\n\
|
||||
find /app/web/.next -type f -name "*.html" -exec sed -i "s|https://edu.adradviser.ro|$NEXT_PUBLIC_LEARNHOUSE_DOMAIN|g" {} \\;\n\
|
||||
\n\
|
||||
# Replace absolute URL paths\n\
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s|https://[^/\"]*\\(/api/v1/\\)|${NEXT_PUBLIC_LEARNHOUSE_API_URL}|g" {} \\;\n\
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s|https://[^/\"]*\\(/api/auth\\)|${NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL}api/auth|g" {} \\;\n\
|
||||
echo "API URLs patched to: $NEXT_PUBLIC_LEARNHOUSE_API_URL"\n\
|
||||
\n\
|
||||
# Replace hardcoded href values\n\
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s|href:\\\"http://edu.adradviser.ro|href:\\\"$NEXT_PUBLIC_LEARNHOUSE_DOMAIN|g" {} \\;\n\
|
||||
find /app/web/.next -type f -name "*.js" -exec sed -i "s|href:\\\"https://edu.adradviser.ro|href:\\\"$NEXT_PUBLIC_LEARNHOUSE_DOMAIN|g" {} \\;\n\
|
||||
\n\
|
||||
echo "URL and domain patching completed using: $NEXT_PUBLIC_LEARNHOUSE_DOMAIN"\n\
|
||||
\n\
|
||||
# Verify the changes\n\
|
||||
echo "Verification of patched files:"\n\
|
||||
grep -r "edu.adradviser.ro" /app/web/.next --include="*.js" | head -5 || echo "No references to edu.adradviser.ro found (good)"\n\
|
||||
fi\n\
|
||||
\n\
|
||||
echo "Starting application..."\n\
|
||||
sh /app/start.sh' > /app/patched-start.sh && chmod +x /app/patched-start.sh
|
||||
|
||||
# Use the patched start script
|
||||
|
|
|
|||
0
ISOLATION_IMPLEMENTATION_CHECKLIST.md
Normal file
0
ISOLATION_IMPLEMENTATION_CHECKLIST.md
Normal file
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
from fastapi import APIRouter
|
||||
import subprocess
|
||||
from fastapi import APIRouter, Request
|
||||
from config.config import get_learnhouse_config
|
||||
|
||||
router = APIRouter()
|
||||
|
|
@ -9,10 +10,67 @@ async def debug_deployment():
|
|||
"""Debug endpoint for deployment verification and isolation testing"""
|
||||
learnhouse_config = get_learnhouse_config()
|
||||
|
||||
# Parse database host safely
|
||||
db_host = "unknown"
|
||||
if '@' in learnhouse_config.database_config.sql_connection_string:
|
||||
try:
|
||||
parts = learnhouse_config.database_config.sql_connection_string.split('@')
|
||||
if len(parts) > 1:
|
||||
host_parts = parts[1].split('/')
|
||||
if len(host_parts) > 0:
|
||||
db_host = host_parts[0]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Parse redis host safely
|
||||
redis_host = "unknown"
|
||||
if '@' in learnhouse_config.redis_config.redis_connection_string:
|
||||
try:
|
||||
parts = learnhouse_config.redis_config.redis_connection_string.split('@')
|
||||
if len(parts) > 1:
|
||||
host_parts = parts[1].split(':')
|
||||
if len(host_parts) > 0:
|
||||
redis_host = host_parts[0]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {
|
||||
"deployment_name": os.environ.get('DEPLOYMENT_NAME', 'NOT_SET'),
|
||||
"cookie_domain": learnhouse_config.hosting_config.cookie_config.domain,
|
||||
"api_domain": learnhouse_config.hosting_config.domain,
|
||||
"database_host": learnhouse_config.database_config.sql_connection_string.split('@')[1].split('/')[0] if '@' in learnhouse_config.database_config.sql_connection_string else "unknown",
|
||||
"redis_host": learnhouse_config.redis_config.redis_connection_string.split('@')[1].split(':')[0] if '@' in learnhouse_config.redis_config.redis_connection_string else "unknown"
|
||||
"database_host": db_host,
|
||||
"redis_host": redis_host,
|
||||
"database_name": learnhouse_config.database_config.sql_connection_string.split('/')[-1] if '/' in learnhouse_config.database_config.sql_connection_string else "unknown",
|
||||
"env_vars": {
|
||||
"NEXT_PUBLIC_LEARNHOUSE_DOMAIN": os.environ.get('NEXT_PUBLIC_LEARNHOUSE_DOMAIN', 'NOT_SET'),
|
||||
"NEXT_PUBLIC_LEARNHOUSE_API_URL": os.environ.get('NEXT_PUBLIC_LEARNHOUSE_API_URL', 'NOT_SET')
|
||||
}
|
||||
}
|
||||
|
||||
@router.get("/urls")
|
||||
async def debug_urls(request: Request):
|
||||
"""Debug endpoint to detect hardcoded URLs in NextJS bundle"""
|
||||
try:
|
||||
# This only works if Next.js files are accessible from the API container
|
||||
result = subprocess.run(
|
||||
["find", "/app/web/.next", "-type", "f", "-name", "*.js", "-exec", "grep", "-o", "http://edu.adradviser.ro[^\"']*", "{}", ";"],
|
||||
capture_output=True, text=True, timeout=5
|
||||
)
|
||||
hardcoded_urls = list(set(result.stdout.strip().split('\n'))) if result.stdout.strip() else []
|
||||
|
||||
# Get the current domain for comparison
|
||||
current_domain = os.environ.get('NEXT_PUBLIC_LEARNHOUSE_DOMAIN', 'unknown')
|
||||
|
||||
return {
|
||||
"detected_hardcoded_urls": hardcoded_urls,
|
||||
"current_domain": current_domain,
|
||||
"client_host": request.client.host,
|
||||
"headers": dict(request.headers),
|
||||
"deployment_name": os.environ.get('DEPLOYMENT_NAME', 'NOT_SET')
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"error": str(e),
|
||||
"message": "Could not scan for hardcoded URLs",
|
||||
"deployment_name": os.environ.get('DEPLOYMENT_NAME', 'NOT_SET')
|
||||
}
|
||||
|
|
|
|||
0
deploy-isolation-fix.sh
Executable file
0
deploy-isolation-fix.sh
Executable file
171
verify-db-isolation.sh
Executable file
171
verify-db-isolation.sh
Executable file
|
|
@ -0,0 +1,171 @@
|
|||
#!/bin/bash
|
||||
# Database Isolation Verification Script
|
||||
# This script will verify database isolation between DEV and LIVE deployments
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${BLUE}=== Database Isolation Verification Script ===${NC}"
|
||||
echo -e "${YELLOW}This script will verify database isolation between DEV and LIVE deployments${NC}"
|
||||
echo ""
|
||||
|
||||
# First check API debug endpoints for database information
|
||||
echo -e "${BLUE}Checking API debug endpoints for database information...${NC}"
|
||||
DEV_URL="https://adr-lms.whitex.cloud"
|
||||
LIVE_URL="https://edu.adradviser.ro"
|
||||
|
||||
DEV_DEBUG=$(curl -s -m 10 -k "$DEV_URL/api/v1/debug/deployment" || echo '{"error":"Failed to connect"}')
|
||||
LIVE_DEBUG=$(curl -s -m 10 -k "$LIVE_URL/api/v1/debug/deployment" || echo '{"error":"Failed to connect"}')
|
||||
|
||||
# Extract values using Python if available
|
||||
if command -v python3 &> /dev/null; then
|
||||
echo -e "${GREEN}✓${NC} Python3 available for JSON parsing"
|
||||
DEV_DB_HOST=$(echo "$DEV_DEBUG" | python3 -c "import sys, json; print(json.load(sys.stdin).get('database_host', 'unknown'))" 2>/dev/null)
|
||||
LIVE_DB_HOST=$(echo "$LIVE_DEBUG" | python3 -c "import sys, json; print(json.load(sys.stdin).get('database_host', 'unknown'))" 2>/dev/null)
|
||||
DEV_DB_NAME=$(echo "$DEV_DEBUG" | python3 -c "import sys, json; print(json.load(sys.stdin).get('database_name', 'unknown'))" 2>/dev/null)
|
||||
LIVE_DB_NAME=$(echo "$LIVE_DEBUG" | python3 -c "import sys, json; print(json.load(sys.stdin).get('database_name', 'unknown'))" 2>/dev/null)
|
||||
|
||||
echo -e "${YELLOW}From API Debug:${NC}"
|
||||
echo -e "DEV DB: Host=${DEV_DB_HOST}, Name=${DEV_DB_NAME}"
|
||||
echo -e "LIVE DB: Host=${LIVE_DB_HOST}, Name=${LIVE_DB_NAME}"
|
||||
|
||||
if [ "$DEV_DB_HOST" == "$LIVE_DB_HOST" ]; then
|
||||
echo -e "${RED}⚠️ WARNING: Both deployments using same database host: $DEV_DB_HOST${NC}"
|
||||
else
|
||||
if [ "$DEV_DB_HOST" != "unknown" ] && [ "$LIVE_DB_HOST" != "unknown" ]; then
|
||||
echo -e "${GREEN}✓ Database hosts are properly isolated between deployments${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Could not verify database hosts from API - falling back to manual checking${NC}"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}Python3 not available for JSON parsing - falling back to manual checking${NC}"
|
||||
fi
|
||||
|
||||
echo -e "\n${BLUE}Continuing with direct database verification...${NC}"
|
||||
|
||||
# Function to extract database connection details from environment variables
|
||||
extract_db_details() {
|
||||
# Get connection string from environment
|
||||
local conn_string="$1"
|
||||
|
||||
# Extract username, password, host, port, and database name
|
||||
local username=$(echo "$conn_string" | sed -E 's/^postgresql:\/\/([^:]+):.*/\1/')
|
||||
local password=$(echo "$conn_string" | sed -E 's/^postgresql:\/\/[^:]+:([^@]+)@.*/\1/')
|
||||
local host=$(echo "$conn_string" | sed -E 's/^postgresql:\/\/[^@]+@([^:]+):.*/\1/')
|
||||
local port=$(echo "$conn_string" | sed -E 's/^postgresql:\/\/[^@]+@[^:]+:([^\/]+)\/.*/\1/')
|
||||
local dbname=$(echo "$conn_string" | sed -E 's/^postgresql:\/\/[^@]+@[^\/]+\/([^?]+).*/\1/')
|
||||
|
||||
echo "Username: $username"
|
||||
echo "Password: [HIDDEN]"
|
||||
echo "Host: $host"
|
||||
echo "Port: $port"
|
||||
echo "Database: $dbname"
|
||||
|
||||
# Return values in a specific format for later use
|
||||
echo "$host|$port|$dbname|$username|$password"
|
||||
}
|
||||
|
||||
# Function to test database connection
|
||||
test_db_connection() {
|
||||
local details="$1"
|
||||
local host=$(echo "$details" | cut -d'|' -f1)
|
||||
local port=$(echo "$details" | cut -d'|' -f2)
|
||||
local dbname=$(echo "$details" | cut -d'|' -f3)
|
||||
local username=$(echo "$details" | cut -d'|' -f4)
|
||||
local password=$(echo "$details" | cut -d'|' -f5)
|
||||
|
||||
echo -e "${BLUE}Testing connection to $dbname on $host:$port...${NC}"
|
||||
|
||||
# Try to connect and run a simple query
|
||||
if PGPASSWORD="$password" psql -h "$host" -p "$port" -U "$username" -d "$dbname" -c "SELECT 1;" > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓ Successfully connected to database $dbname on $host${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}✗ Failed to connect to database $dbname on $host${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to test if two databases share the same server
|
||||
test_db_isolation() {
|
||||
local dev_details="$1"
|
||||
local live_details="$2"
|
||||
|
||||
local dev_host=$(echo "$dev_details" | cut -d'|' -f1)
|
||||
local live_host=$(echo "$live_details" | cut -d'|' -f1)
|
||||
|
||||
echo -e "${BLUE}Checking database isolation...${NC}"
|
||||
|
||||
if [ "$dev_host" == "$live_host" ]; then
|
||||
echo -e "${RED}✗ ISOLATION FAILURE: DEV and LIVE environments are using the same database host: $dev_host${NC}"
|
||||
echo -e "${RED} This will cause cross-deployment contamination!${NC}"
|
||||
return 1
|
||||
else
|
||||
echo -e "${GREEN}✓ Database isolation confirmed: DEV($dev_host) ≠ LIVE($live_host)${NC}"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
|
||||
# Get connection strings from environment or prompt user
|
||||
if [ -z "$DEV_DB_URL" ]; then
|
||||
echo -e "${YELLOW}DEV database connection string not found in environment.${NC}"
|
||||
echo -e "Enter DEV database connection string (postgresql://user:pass@host:port/dbname):"
|
||||
read -p "> " DEV_DB_URL
|
||||
fi
|
||||
|
||||
if [ -z "$LIVE_DB_URL" ]; then
|
||||
echo -e "${YELLOW}LIVE database connection string not found in environment.${NC}"
|
||||
echo -e "Enter LIVE database connection string (postgresql://user:pass@host:port/dbname):"
|
||||
read -p "> " LIVE_DB_URL
|
||||
fi
|
||||
|
||||
# Extract connection details
|
||||
echo -e "\n${BLUE}DEV Database Details:${NC}"
|
||||
DEV_DETAILS=$(extract_db_details "$DEV_DB_URL")
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}LIVE Database Details:${NC}"
|
||||
LIVE_DETAILS=$(extract_db_details "$LIVE_DB_URL")
|
||||
echo ""
|
||||
|
||||
# Test connections
|
||||
DEV_CONNECTION_OK=false
|
||||
LIVE_CONNECTION_OK=false
|
||||
|
||||
if test_db_connection "$DEV_DETAILS"; then
|
||||
DEV_CONNECTION_OK=true
|
||||
fi
|
||||
|
||||
if test_db_connection "$LIVE_DETAILS"; then
|
||||
LIVE_CONNECTION_OK=true
|
||||
fi
|
||||
|
||||
# If both connections work, test isolation
|
||||
if $DEV_CONNECTION_OK && $LIVE_CONNECTION_OK; then
|
||||
test_db_isolation "$DEV_DETAILS" "$LIVE_DETAILS"
|
||||
ISOLATION_RESULT=$?
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Could not verify isolation because one or both database connections failed.${NC}"
|
||||
ISOLATION_RESULT=2
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}=== Verification Results ===${NC}"
|
||||
if [ $ISOLATION_RESULT -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ SUCCESS: Databases are properly isolated${NC}"
|
||||
elif [ $ISOLATION_RESULT -eq 1 ]; then
|
||||
echo -e "${RED}✗ FAILURE: Databases are not isolated!${NC}"
|
||||
echo -e "${YELLOW}Action required: Update your database connection strings to use different hosts.${NC}"
|
||||
echo -e "See DATABASE_ISOLATION_FIX.md for details."
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ INCONCLUSIVE: Could not verify isolation${NC}"
|
||||
echo -e "Fix connection issues and try again."
|
||||
fi
|
||||
|
||||
exit $ISOLATION_RESULT
|
||||
|
|
@ -1,102 +1,177 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "🔍 LearnHouse Deployment Isolation Verification"
|
||||
echo "==============================================="
|
||||
# Colors for better output
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${BLUE}🔍 LearnHouse Deployment Isolation Verification${NC}"
|
||||
echo -e "${BLUE}===============================================${NC}"
|
||||
|
||||
# Check DEV deployment
|
||||
echo ""
|
||||
echo "📋 DEV Deployment (adr-lms.whitex.cloud):"
|
||||
echo -e "${YELLOW}📋 DEV Deployment (adr-lms.whitex.cloud):${NC}"
|
||||
echo "----------------------------------------"
|
||||
echo "Testing API connection..."
|
||||
DEV_RESPONSE=$(curl -s https://adr-lms.whitex.cloud/api/v1/debug/deployment 2>/dev/null)
|
||||
DEV_ROOT=$(curl -s https://adr-lms.whitex.cloud/ 2>/dev/null | head -200)
|
||||
DEV_RESPONSE=$(curl -s -k https://adr-lms.whitex.cloud/api/v1/debug/deployment 2>/dev/null)
|
||||
DEV_HEALTH=$(curl -s -k https://adr-lms.whitex.cloud/api/health 2>/dev/null)
|
||||
DEV_ROOT=$(curl -s -k https://adr-lms.whitex.cloud/ 2>/dev/null | head -200)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ DEV API accessible"
|
||||
echo -e "${GREEN}✅ DEV API accessible${NC}"
|
||||
echo " Health: $DEV_HEALTH"
|
||||
echo " Debug Response: $DEV_RESPONSE"
|
||||
if [[ "$DEV_ROOT" == *"LearnHouse"* ]] || [[ "$DEV_ROOT" == *"React"* ]] || [[ "$DEV_ROOT" == *"Next"* ]]; then
|
||||
echo "✅ DEV Frontend serving properly"
|
||||
echo -e "${GREEN}✅ DEV Frontend serving properly${NC}"
|
||||
else
|
||||
echo "⚠️ DEV Frontend response unclear"
|
||||
echo -e "${YELLOW}⚠️ DEV Frontend response unclear${NC}"
|
||||
echo " Root response (first 100 chars): ${DEV_ROOT:0:100}"
|
||||
fi
|
||||
else
|
||||
echo "❌ DEV API not accessible"
|
||||
echo -e "${RED}❌ DEV API not accessible${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Testing frontend..."
|
||||
DEV_FRONTEND=$(curl -s -o /dev/null -w "%{http_code}" https://adr-lms.whitex.cloud/ 2>/dev/null)
|
||||
DEV_FRONTEND=$(curl -s -k -o /dev/null -w "%{http_code}" https://adr-lms.whitex.cloud/ 2>/dev/null)
|
||||
if [ "$DEV_FRONTEND" = "200" ]; then
|
||||
echo "✅ DEV Frontend accessible (HTTP $DEV_FRONTEND)"
|
||||
echo -e "${GREEN}✅ DEV Frontend accessible (HTTP $DEV_FRONTEND)${NC}"
|
||||
else
|
||||
echo "❌ DEV Frontend issue (HTTP $DEV_FRONTEND)"
|
||||
echo -e "${RED}❌ DEV Frontend issue (HTTP $DEV_FRONTEND)${NC}"
|
||||
fi
|
||||
|
||||
# Check LIVE deployment
|
||||
echo ""
|
||||
echo "📋 LIVE Deployment (edu.adradviser.ro):"
|
||||
echo -e "${YELLOW}📋 LIVE Deployment (edu.adradviser.ro):${NC}"
|
||||
echo "---------------------------------------"
|
||||
echo "Testing API connection..."
|
||||
LIVE_RESPONSE=$(curl -s https://edu.adradviser.ro/api/v1/debug/deployment 2>/dev/null)
|
||||
LIVE_ROOT=$(curl -s https://edu.adradviser.ro/ 2>/dev/null | head -200)
|
||||
LIVE_RESPONSE=$(curl -s -k https://edu.adradviser.ro/api/v1/debug/deployment 2>/dev/null)
|
||||
LIVE_HEALTH=$(curl -s -k https://edu.adradviser.ro/api/health 2>/dev/null)
|
||||
LIVE_ROOT=$(curl -s -k https://edu.adradviser.ro/ 2>/dev/null | head -200)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ LIVE API accessible"
|
||||
echo -e "${GREEN}✅ LIVE API accessible${NC}"
|
||||
echo " Health: $LIVE_HEALTH"
|
||||
echo " Debug Response: $LIVE_RESPONSE"
|
||||
if [[ "$LIVE_ROOT" == *"LearnHouse"* ]] || [[ "$LIVE_ROOT" == *"React"* ]] || [[ "$LIVE_ROOT" == *"Next"* ]]; then
|
||||
echo "✅ LIVE Frontend serving properly"
|
||||
echo -e "${GREEN}✅ LIVE Frontend serving properly${NC}"
|
||||
else
|
||||
echo "⚠️ LIVE Frontend response unclear"
|
||||
echo -e "${YELLOW}⚠️ LIVE Frontend response unclear${NC}"
|
||||
echo " Root response (first 100 chars): ${LIVE_ROOT:0:100}"
|
||||
fi
|
||||
else
|
||||
echo "❌ LIVE API not accessible"
|
||||
echo -e "${RED}❌ LIVE API not accessible${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Testing frontend..."
|
||||
LIVE_FRONTEND=$(curl -s -o /dev/null -w "%{http_code}" https://edu.adradviser.ro/ 2>/dev/null)
|
||||
LIVE_FRONTEND=$(curl -s -k -o /dev/null -w "%{http_code}" https://edu.adradviser.ro/ 2>/dev/null)
|
||||
if [ "$LIVE_FRONTEND" = "200" ]; then
|
||||
echo "✅ LIVE Frontend accessible (HTTP $LIVE_FRONTEND)"
|
||||
echo -e "${GREEN}✅ LIVE Frontend accessible (HTTP $LIVE_FRONTEND)${NC}"
|
||||
else
|
||||
echo "❌ LIVE Frontend issue (HTTP $LIVE_FRONTEND)"
|
||||
echo -e "${RED}❌ LIVE Frontend issue (HTTP $LIVE_FRONTEND)${NC}"
|
||||
fi
|
||||
|
||||
# Analysis
|
||||
echo ""
|
||||
echo "🔍 Cross-Deployment Isolation Analysis:"
|
||||
echo -e "${BLUE}🔍 Cross-Deployment Isolation Analysis:${NC}"
|
||||
echo "========================================"
|
||||
|
||||
# Check for hardcoded URLs
|
||||
echo -e "\n${BLUE}Checking for hardcoded LIVE URLs in DEV frontend...${NC}"
|
||||
LIVE_URLS_IN_DEV=$(echo "$DEV_ROOT" | grep -o "http://edu.adradviser.ro[^\"']*\|https://edu.adradviser.ro[^\"']*" | sort -u)
|
||||
|
||||
if [[ -n "$LIVE_URLS_IN_DEV" ]]; then
|
||||
echo -e "${RED}⚠️ WARNING: Found hardcoded LIVE URLs in DEV frontend:${NC}"
|
||||
echo "$LIVE_URLS_IN_DEV"
|
||||
else
|
||||
echo -e "${GREEN}✅ No hardcoded LIVE URLs found in DEV frontend${NC}"
|
||||
fi
|
||||
|
||||
# Check for courses UUIDs
|
||||
DEV_COURSES=$(echo "$DEV_ROOT" | grep -o "course_[a-zA-Z0-9-]*" | sort -u)
|
||||
LIVE_COURSES=$(echo "$LIVE_ROOT" | grep -o "course_[a-zA-Z0-9-]*" | sort -u)
|
||||
|
||||
echo -e "\n${BLUE}Course UUIDs found in deployments:${NC}"
|
||||
echo -e "${YELLOW}DEV courses:${NC} $(echo $DEV_COURSES | tr '\n' ' ')"
|
||||
echo -e "${YELLOW}LIVE courses:${NC} $(echo $LIVE_COURSES | tr '\n' ' ')"
|
||||
|
||||
# Find common courses
|
||||
if [[ -n "$DEV_COURSES" && -n "$LIVE_COURSES" ]]; then
|
||||
# Using grep to find common entries
|
||||
COMMON_COURSES=""
|
||||
for course in $DEV_COURSES; do
|
||||
if echo "$LIVE_COURSES" | grep -q "$course"; then
|
||||
COMMON_COURSES="$COMMON_COURSES $course"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$COMMON_COURSES" ]]; then
|
||||
echo -e "${RED}⚠️ WARNING: Found shared courses between deployments (contamination):${NC}"
|
||||
echo "$COMMON_COURSES"
|
||||
else
|
||||
echo -e "${GREEN}✅ No shared courses found between deployments${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Extract database hosts if responses are valid JSON
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
DEV_DB=$(echo "$DEV_RESPONSE" | jq -r '.database_host // "unknown"' 2>/dev/null)
|
||||
LIVE_DB=$(echo "$LIVE_RESPONSE" | jq -r '.database_host // "unknown"' 2>/dev/null)
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
echo -e "\n${BLUE}Database connection analysis:${NC}"
|
||||
if [[ "$DEV_RESPONSE" == *"database_host"* ]]; then
|
||||
DEV_DB=$(echo "$DEV_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin).get('database_host', 'unknown'))" 2>/dev/null)
|
||||
echo -e "DEV database: ${YELLOW}$DEV_DB${NC}"
|
||||
else
|
||||
echo -e "${RED}⚠️ Cannot analyze DEV database - debug endpoint not working${NC}"
|
||||
fi
|
||||
|
||||
if [ "$DEV_DB" != "unknown" ] && [ "$LIVE_DB" != "unknown" ]; then
|
||||
if [[ "$LIVE_RESPONSE" == *"database_host"* ]]; then
|
||||
LIVE_DB=$(echo "$LIVE_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin).get('database_host', 'unknown'))" 2>/dev/null)
|
||||
echo -e "LIVE database: ${YELLOW}$LIVE_DB${NC}"
|
||||
else
|
||||
echo -e "${RED}⚠️ Cannot analyze LIVE database - debug endpoint not working${NC}"
|
||||
fi
|
||||
|
||||
if [[ -n "$DEV_DB" && -n "$LIVE_DB" && "$DEV_DB" != "unknown" && "$LIVE_DB" != "unknown" ]]; then
|
||||
if [ "$DEV_DB" = "$LIVE_DB" ]; then
|
||||
echo "⚠️ WARNING: Both deployments using same database host: $DEV_DB"
|
||||
echo -e "${RED}⚠️ WARNING: Both deployments using same database host: $DEV_DB${NC}"
|
||||
echo -e "${RED} This is likely the cause of cross-deployment contamination!${NC}"
|
||||
else
|
||||
echo "✅ Database isolation: DEV($DEV_DB) ≠ LIVE($LIVE_DB)"
|
||||
echo -e "${GREEN}✅ Database isolation confirmed: DEV($DEV_DB) ≠ LIVE($LIVE_DB)${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
DEV_COOKIE=$(echo "$DEV_RESPONSE" | jq -r '.cookie_domain // "unknown"' 2>/dev/null)
|
||||
LIVE_COOKIE=$(echo "$LIVE_RESPONSE" | jq -r '.cookie_domain // "unknown"' 2>/dev/null)
|
||||
echo -e "\n${BLUE}Cookie domain analysis:${NC}"
|
||||
if [[ "$DEV_RESPONSE" == *"cookie_domain"* ]]; then
|
||||
DEV_COOKIE=$(echo "$DEV_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin).get('cookie_domain', 'unknown'))" 2>/dev/null)
|
||||
echo -e "DEV cookie domain: ${YELLOW}$DEV_COOKIE${NC}"
|
||||
else
|
||||
echo -e "${RED}⚠️ Cannot analyze DEV cookie domain - debug endpoint not working${NC}"
|
||||
fi
|
||||
|
||||
if [ "$DEV_COOKIE" != "unknown" ] && [ "$LIVE_COOKIE" != "unknown" ]; then
|
||||
if [[ "$LIVE_RESPONSE" == *"cookie_domain"* ]]; then
|
||||
LIVE_COOKIE=$(echo "$LIVE_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin).get('cookie_domain', 'unknown'))" 2>/dev/null)
|
||||
echo -e "LIVE cookie domain: ${YELLOW}$LIVE_COOKIE${NC}"
|
||||
else
|
||||
echo -e "${RED}⚠️ Cannot analyze LIVE cookie domain - debug endpoint not working${NC}"
|
||||
fi
|
||||
|
||||
if [[ -n "$DEV_COOKIE" && -n "$LIVE_COOKIE" && "$DEV_COOKIE" != "unknown" && "$LIVE_COOKIE" != "unknown" ]]; then
|
||||
if [ "$DEV_COOKIE" = "$LIVE_COOKIE" ]; then
|
||||
echo "⚠️ WARNING: Both deployments using same cookie domain: $DEV_COOKIE"
|
||||
echo -e "${RED}⚠️ WARNING: Both deployments using same cookie domain: $DEV_COOKIE${NC}"
|
||||
echo -e "${RED} This could cause session contamination between deployments!${NC}"
|
||||
else
|
||||
echo "✅ Cookie isolation: DEV($DEV_COOKIE) ≠ LIVE($LIVE_COOKIE)"
|
||||
echo -e "${GREEN}✅ Cookie domain isolation confirmed: DEV($DEV_COOKIE) ≠ LIVE($LIVE_COOKIE)${NC}"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "ℹ️ Install 'jq' for detailed analysis"
|
||||
echo -e "${YELLOW}ℹ️ Python3 not available for JSON analysis${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🚀 Next Steps:"
|
||||
echo -e "\n${BLUE}🚀 Next Steps:${NC}"
|
||||
echo "=============="
|
||||
echo "1. If APIs are accessible, check browser Network tab for cross-deployment calls"
|
||||
echo "2. Clear browser cache/cookies for both domains"
|
||||
echo "3. Test in incognito mode to verify isolation"
|
||||
echo "1. If debug endpoints are not accessible, deploy the API changes first"
|
||||
echo "2. Verify database connection strings are different between deployments"
|
||||
echo "3. Check the Dockerfile_coolify for proper API URL replacement"
|
||||
echo "4. Clear browser cache/cookies for both domains"
|
||||
echo "5. Test in incognito mode to verify isolation"
|
||||
echo "4. Check container logs: docker logs <container_name>"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue