mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-10-30 11:33:51 +00:00
716 lines
27 KiB
Bash
Executable File
716 lines
27 KiB
Bash
Executable File
#!/bin/bash
|
||
# PatchMon Diagnostics Collection Script
|
||
# Collects system information, logs, and configuration for troubleshooting
|
||
# Usage: sudo bash diagnostics.sh [instance-name]
|
||
|
||
# Note: Not using 'set -e' because we want to continue even if some commands fail
|
||
set -o pipefail
|
||
|
||
# Colors for output
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# Print functions
|
||
print_status() {
|
||
echo -e "${GREEN}✅ $1${NC}"
|
||
}
|
||
|
||
print_info() {
|
||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||
}
|
||
|
||
print_error() {
|
||
echo -e "${RED}❌ $1${NC}"
|
||
}
|
||
|
||
print_warning() {
|
||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||
}
|
||
|
||
print_success() {
|
||
echo -e "${GREEN}🎉 $1${NC}"
|
||
}
|
||
|
||
# Check if running as root
|
||
if [[ $EUID -ne 0 ]]; then
|
||
print_error "This script must be run as root"
|
||
print_info "Please run: sudo bash $0"
|
||
exit 1
|
||
fi
|
||
|
||
# Function to sanitize sensitive information
|
||
sanitize_sensitive() {
|
||
local input="$1"
|
||
# Replace passwords, secrets, and tokens with [REDACTED]
|
||
echo "$input" | \
|
||
sed -E 's/(PASSWORD|SECRET|TOKEN|KEY|PASS)=[^"]*$/\1=[REDACTED]/gi' | \
|
||
sed -E 's/(PASSWORD|SECRET|TOKEN|KEY|PASS)="[^"]*"/\1="[REDACTED]"/gi' | \
|
||
sed -E 's/(password|secret|token|key|pass)": *"[^"]*"/\1": "[REDACTED]"/gi' | \
|
||
sed -E 's/(>)[a-zA-Z0-9+\/=]{20,}/\1[REDACTED]/g' | \
|
||
sed -E 's|postgresql://([^:]+):([^@]+)@|postgresql://\1:[REDACTED]@|g' | \
|
||
sed -E 's|mysql://([^:]+):([^@]+)@|mysql://\1:[REDACTED]@|g' | \
|
||
sed -E 's|mongodb://([^:]+):([^@]+)@|mongodb://\1:[REDACTED]@|g'
|
||
}
|
||
|
||
# Function to detect PatchMon installations
|
||
detect_installations() {
|
||
local installations=()
|
||
|
||
if [ ! -d "/opt" ]; then
|
||
print_error "/opt directory does not exist"
|
||
return 1
|
||
fi
|
||
|
||
for dir in /opt/*/; do
|
||
# Skip if no directories found
|
||
[ -d "$dir" ] || continue
|
||
|
||
local dirname=$(basename "$dir")
|
||
|
||
# Skip backup directories
|
||
if [[ "$dirname" =~ \.backup\. ]]; then
|
||
continue
|
||
fi
|
||
|
||
# Check if it's a PatchMon installation
|
||
if [ -f "$dir/backend/package.json" ]; then
|
||
if grep -q "patchmon" "$dir/backend/package.json" 2>/dev/null; then
|
||
installations+=("$dirname")
|
||
fi
|
||
fi
|
||
done
|
||
|
||
echo "${installations[@]}"
|
||
}
|
||
|
||
# Function to select installation
|
||
select_installation() {
|
||
local installations=($(detect_installations))
|
||
|
||
if [ ${#installations[@]} -eq 0 ]; then
|
||
print_error "No PatchMon installations found in /opt" >&2
|
||
exit 1
|
||
fi
|
||
|
||
if [ -n "$1" ]; then
|
||
# Use provided instance name
|
||
if [[ " ${installations[@]} " =~ " $1 " ]]; then
|
||
echo "$1"
|
||
return 0
|
||
else
|
||
print_error "Instance '$1' not found" >&2
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
# Send status messages to stderr so they don't contaminate the return value
|
||
print_info "Found ${#installations[@]} installation(s):" >&2
|
||
echo "" >&2
|
||
|
||
local i=1
|
||
declare -A install_map
|
||
for install in "${installations[@]}"; do
|
||
# Get service status
|
||
local status="unknown"
|
||
if systemctl is-active --quiet "$install" 2>/dev/null; then
|
||
status="${GREEN}running${NC}"
|
||
elif systemctl is-enabled --quiet "$install" 2>/dev/null; then
|
||
status="${RED}stopped${NC}"
|
||
fi
|
||
|
||
printf "%2d. %-30s (%b)\n" "$i" "$install" "$status" >&2
|
||
install_map[$i]="$install"
|
||
i=$((i + 1))
|
||
done
|
||
|
||
echo "" >&2
|
||
|
||
# If only one installation, select it automatically
|
||
if [ ${#installations[@]} -eq 1 ]; then
|
||
print_info "Only one installation found, selecting automatically: ${installations[0]}" >&2
|
||
echo "${installations[0]}"
|
||
return 0
|
||
fi
|
||
|
||
# Multiple installations - prompt user
|
||
printf "${BLUE}Select installation number [1]: ${NC}" >&2
|
||
read -r selection </dev/tty
|
||
|
||
selection=${selection:-1}
|
||
|
||
if [[ "$selection" =~ ^[0-9]+$ ]] && [ -n "${install_map[$selection]}" ]; then
|
||
echo "${install_map[$selection]}"
|
||
return 0
|
||
else
|
||
print_error "Invalid selection" >&2
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# Main script
|
||
main() {
|
||
# Capture the directory where script is run from at the very start
|
||
ORIGINAL_DIR=$(pwd)
|
||
|
||
echo -e "${BLUE}====================================================${NC}"
|
||
echo -e "${BLUE} PatchMon Diagnostics Collection${NC}"
|
||
echo -e "${BLUE}====================================================${NC}"
|
||
echo ""
|
||
|
||
# Select instance
|
||
instance_name=$(select_installation "$1")
|
||
instance_dir="/opt/$instance_name"
|
||
|
||
print_info "Selected instance: $instance_name"
|
||
print_info "Directory: $instance_dir"
|
||
echo ""
|
||
|
||
# Create single diagnostics file in the original directory
|
||
timestamp=$(date +%Y%m%d_%H%M%S)
|
||
diag_file="${ORIGINAL_DIR}/patchmon_diagnostics_${instance_name}_${timestamp}.txt"
|
||
|
||
print_info "Collecting diagnostics to: $diag_file"
|
||
echo ""
|
||
|
||
# Initialize the diagnostics file with header
|
||
cat > "$diag_file" << EOF
|
||
===================================================
|
||
PatchMon Diagnostics Report
|
||
===================================================
|
||
Instance: $instance_name
|
||
Generated: $(date)
|
||
Hostname: $(hostname)
|
||
Generated from: ${ORIGINAL_DIR}
|
||
===================================================
|
||
|
||
EOF
|
||
|
||
# ========================================
|
||
# 1. System Information
|
||
# ========================================
|
||
print_info "Collecting system information..."
|
||
|
||
cat >> "$diag_file" << EOF
|
||
=== System Information ===
|
||
OS: $(cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d'"' -f2 || echo "Unknown")
|
||
Kernel: $(uname -r)
|
||
Uptime: $(uptime)
|
||
|
||
=== CPU Information ===
|
||
$(lscpu | grep -E "Model name|CPU\(s\)|Thread|Core" || echo "Not available")
|
||
|
||
=== Memory Information ===
|
||
$(free -h)
|
||
|
||
=== Disk Usage ===
|
||
$(df -h | grep -E "Filesystem|/dev/|/opt")
|
||
|
||
=== Network Interfaces ===
|
||
$(ip -br addr)
|
||
|
||
===================================================
|
||
EOF
|
||
|
||
# ========================================
|
||
# 2. PatchMon Instance Information
|
||
# ========================================
|
||
print_info "Collecting instance information..."
|
||
|
||
cat >> "$diag_file" << EOF
|
||
|
||
=== PatchMon Instance Information ===
|
||
|
||
=== Directory Structure ===
|
||
$(ls -lah "$instance_dir" 2>/dev/null || echo "Cannot access directory")
|
||
|
||
=== Backend Package Info ===
|
||
$(cat "$instance_dir/backend/package.json" 2>/dev/null | grep -E "name|version" || echo "Not found")
|
||
|
||
=== Frontend Package Info ===
|
||
$(cat "$instance_dir/frontend/package.json" 2>/dev/null | grep -E "name|version" || echo "Not found")
|
||
|
||
=== Deployment Info ===
|
||
$(cat "$instance_dir/deployment-info.txt" 2>/dev/null || echo "No deployment-info.txt found")
|
||
|
||
===================================================
|
||
EOF
|
||
|
||
# ========================================
|
||
# 3. Environment Configuration (Sanitized)
|
||
# ========================================
|
||
print_info "Collecting environment configuration (sanitized)..."
|
||
|
||
echo "" >> "$diag_file"
|
||
echo "=== Backend Environment Configuration (Sanitized) ===" >> "$diag_file"
|
||
if [ -f "$instance_dir/backend/.env" ]; then
|
||
sanitize_sensitive "$(cat "$instance_dir/backend/.env")" >> "$diag_file"
|
||
else
|
||
echo "Backend .env file not found" >> "$diag_file"
|
||
fi
|
||
echo "" >> "$diag_file"
|
||
|
||
# ========================================
|
||
# 4. Service Status and Configuration
|
||
# ========================================
|
||
print_info "Collecting service information..."
|
||
|
||
cat >> "$diag_file" << EOF
|
||
|
||
=== Service Status and Configuration ===
|
||
|
||
=== Service Status ===
|
||
$(systemctl status "$instance_name" 2>/dev/null || echo "Service not found")
|
||
|
||
=== Service File ===
|
||
$(cat "/etc/systemd/system/${instance_name}.service" 2>/dev/null || echo "Service file not found")
|
||
|
||
=== Service is-enabled ===
|
||
$(systemctl is-enabled "$instance_name" 2>/dev/null || echo "unknown")
|
||
|
||
=== Service is-active ===
|
||
$(systemctl is-active "$instance_name" 2>/dev/null || echo "unknown")
|
||
|
||
===================================================
|
||
EOF
|
||
|
||
# ========================================
|
||
# 5. Service Logs
|
||
# ========================================
|
||
print_info "Collecting service logs..."
|
||
|
||
echo "" >> "$diag_file"
|
||
echo "=== Service Logs (last 500 lines) ===" >> "$diag_file"
|
||
journalctl -u "$instance_name" -n 500 --no-pager >> "$diag_file" 2>&1 || \
|
||
echo "Could not retrieve service logs" >> "$diag_file"
|
||
echo "" >> "$diag_file"
|
||
|
||
# ========================================
|
||
# 6. Nginx Configuration
|
||
# ========================================
|
||
print_info "Collecting nginx configuration..."
|
||
|
||
cat >> "$diag_file" << EOF
|
||
|
||
=== Nginx Configuration ===
|
||
|
||
=== Nginx Status ===
|
||
$(systemctl status nginx 2>/dev/null | head -20 || echo "Nginx not found")
|
||
|
||
=== Site Configuration ===
|
||
$(cat "/etc/nginx/sites-available/$instance_name" 2>/dev/null || echo "Nginx config not found")
|
||
|
||
=== Nginx Error Log (last 100 lines) ===
|
||
$(tail -100 /var/log/nginx/error.log 2>/dev/null || echo "Error log not accessible")
|
||
|
||
=== Nginx Access Log (last 50 lines) ===
|
||
$(tail -50 /var/log/nginx/access.log 2>/dev/null || echo "Access log not accessible")
|
||
|
||
=== Nginx Test ===
|
||
$(nginx -t 2>&1 || echo "Nginx test failed")
|
||
|
||
===================================================
|
||
EOF
|
||
|
||
# ========================================
|
||
# 7. Database Connection Test
|
||
# ========================================
|
||
print_info "Testing database connection..."
|
||
|
||
echo "" >> "$diag_file"
|
||
echo "=== Database Information ===" >> "$diag_file"
|
||
echo "" >> "$diag_file"
|
||
|
||
if [ -f "$instance_dir/backend/.env" ]; then
|
||
# Load .env
|
||
set -a
|
||
source "$instance_dir/backend/.env"
|
||
set +a
|
||
|
||
# Parse DATABASE_URL
|
||
if [ -n "$DATABASE_URL" ]; then
|
||
DB_USER=$(echo "$DATABASE_URL" | sed -n 's|postgresql://\([^:]*\):.*|\1|p')
|
||
DB_PASS=$(echo "$DATABASE_URL" | sed -n 's|postgresql://[^:]*:\([^@]*\)@.*|\1|p')
|
||
DB_HOST=$(echo "$DATABASE_URL" | sed -n 's|.*@\([^:]*\):.*|\1|p')
|
||
DB_PORT=$(echo "$DATABASE_URL" | sed -n 's|.*:\([0-9]*\)/.*|\1|p')
|
||
DB_NAME=$(echo "$DATABASE_URL" | sed -n 's|.*/\([^?]*\).*|\1|p')
|
||
|
||
cat >> "$diag_file" << EOF
|
||
=== Database Connection Details ===
|
||
Host: $DB_HOST
|
||
Port: $DB_PORT
|
||
Database: $DB_NAME
|
||
User: $DB_USER
|
||
|
||
=== PostgreSQL Status ===
|
||
$(systemctl status postgresql 2>/dev/null | head -20 || echo "PostgreSQL status not available")
|
||
|
||
=== Connection Test ===
|
||
EOF
|
||
|
||
if PGPASSWORD="$DB_PASS" psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c "SELECT version();" >> "$diag_file" 2>&1; then
|
||
echo "✅ Database connection: SUCCESSFUL" >> "$diag_file"
|
||
else
|
||
echo "❌ Database connection: FAILED" >> "$diag_file"
|
||
fi
|
||
|
||
echo "" >> "$diag_file"
|
||
echo "=== Database Size ===" >> "$diag_file"
|
||
PGPASSWORD="$DB_PASS" psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c "
|
||
SELECT
|
||
pg_size_pretty(pg_database_size('$DB_NAME')) as database_size;
|
||
" >> "$diag_file" 2>&1 || echo "Could not get database size" >> "$diag_file"
|
||
|
||
echo "" >> "$diag_file"
|
||
echo "=== Table Sizes ===" >> "$diag_file"
|
||
PGPASSWORD="$DB_PASS" psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c "
|
||
SELECT
|
||
schemaname,
|
||
tablename,
|
||
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
|
||
FROM pg_tables
|
||
WHERE schemaname = 'public'
|
||
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC
|
||
LIMIT 10;
|
||
" >> "$diag_file" 2>&1 || echo "Could not get table sizes" >> "$diag_file"
|
||
|
||
echo "" >> "$diag_file"
|
||
echo "=== Migration Status ===" >> "$diag_file"
|
||
cd "$instance_dir/backend"
|
||
npx prisma migrate status >> "$diag_file" 2>&1 || echo "Could not get migration status" >> "$diag_file"
|
||
|
||
echo "===================================================" >> "$diag_file"
|
||
else
|
||
echo "DATABASE_URL not found in .env" >> "$diag_file"
|
||
fi
|
||
else
|
||
echo ".env file not found" >> "$diag_file"
|
||
fi
|
||
|
||
# ========================================
|
||
# 8. Redis Connection Test
|
||
# ========================================
|
||
print_info "Testing Redis connection..."
|
||
|
||
if [ -f "$instance_dir/backend/.env" ]; then
|
||
# Load .env
|
||
set -a
|
||
source "$instance_dir/backend/.env"
|
||
set +a
|
||
|
||
cat >> "$diag_file" << EOF
|
||
===================================================
|
||
Redis Information
|
||
===================================================
|
||
|
||
=== Redis Connection Details ===
|
||
Host: ${REDIS_HOST:-localhost}
|
||
Port: ${REDIS_PORT:-6379}
|
||
User: ${REDIS_USER:-(none)}
|
||
Database: ${REDIS_DB:-0}
|
||
|
||
=== Redis Status ===
|
||
$(systemctl status redis-server 2>/dev/null | head -20 || echo "Redis status not available")
|
||
|
||
=== Connection Test ===
|
||
EOF
|
||
|
||
# Test connection
|
||
if [ -n "$REDIS_USER" ] && [ -n "$REDIS_PASSWORD" ]; then
|
||
if redis-cli -h "${REDIS_HOST:-localhost}" -p "${REDIS_PORT:-6379}" --user "$REDIS_USER" --pass "$REDIS_PASSWORD" --no-auth-warning -n "${REDIS_DB:-0}" ping >> "$diag_file" 2>&1; then
|
||
echo "✅ Redis connection (with user): SUCCESSFUL" >> "$diag_file"
|
||
|
||
echo "" >> "$diag_file"
|
||
echo "=== Redis INFO ===" >> "$diag_file"
|
||
redis-cli -h "${REDIS_HOST:-localhost}" -p "${REDIS_PORT:-6379}" --user "$REDIS_USER" --pass "$REDIS_PASSWORD" --no-auth-warning -n "${REDIS_DB:-0}" INFO >> "$diag_file" 2>&1
|
||
|
||
echo "" >> "$diag_file"
|
||
echo "=== Redis Database Size ===" >> "$diag_file"
|
||
redis-cli -h "${REDIS_HOST:-localhost}" -p "${REDIS_PORT:-6379}" --user "$REDIS_USER" --pass "$REDIS_PASSWORD" --no-auth-warning -n "${REDIS_DB:-0}" DBSIZE >> "$diag_file" 2>&1
|
||
else
|
||
echo "❌ Redis connection (with user): FAILED" >> "$diag_file"
|
||
fi
|
||
elif [ -n "$REDIS_PASSWORD" ]; then
|
||
if redis-cli -h "${REDIS_HOST:-localhost}" -p "${REDIS_PORT:-6379}" -a "$REDIS_PASSWORD" --no-auth-warning -n "${REDIS_DB:-0}" ping >> "$diag_file" 2>&1; then
|
||
echo "✅ Redis connection (requirepass): SUCCESSFUL" >> "$diag_file"
|
||
|
||
echo "" >> "$diag_file"
|
||
echo "=== Redis INFO ===" >> "$diag_file"
|
||
redis-cli -h "${REDIS_HOST:-localhost}" -p "${REDIS_PORT:-6379}" -a "$REDIS_PASSWORD" --no-auth-warning -n "${REDIS_DB:-0}" INFO >> "$diag_file" 2>&1
|
||
|
||
echo "" >> "$diag_file"
|
||
echo "=== Redis Database Size ===" >> "$diag_file"
|
||
redis-cli -h "${REDIS_HOST:-localhost}" -p "${REDIS_PORT:-6379}" -a "$REDIS_PASSWORD" --no-auth-warning -n "${REDIS_DB:-0}" DBSIZE >> "$diag_file" 2>&1
|
||
else
|
||
echo "❌ Redis connection (requirepass): FAILED" >> "$diag_file"
|
||
fi
|
||
else
|
||
if redis-cli -h "${REDIS_HOST:-localhost}" -p "${REDIS_PORT:-6379}" -n "${REDIS_DB:-0}" ping >> "$diag_file" 2>&1; then
|
||
echo "✅ Redis connection (no auth): SUCCESSFUL" >> "$diag_file"
|
||
else
|
||
echo "❌ Redis connection: FAILED" >> "$diag_file"
|
||
fi
|
||
fi
|
||
|
||
echo "" >> "$diag_file"
|
||
echo "=== Redis ACL Users ===" >> "$diag_file"
|
||
if [ -n "$REDIS_USER" ] && [ -n "$REDIS_PASSWORD" ]; then
|
||
redis-cli -h "${REDIS_HOST:-localhost}" -p "${REDIS_PORT:-6379}" --user "$REDIS_USER" --pass "$REDIS_PASSWORD" --no-auth-warning ACL LIST >> "$diag_file"
|
||
elif [ -n "$REDIS_PASSWORD" ]; then
|
||
redis-cli -h "${REDIS_HOST:-localhost}" -p "${REDIS_PORT:-6379}" -a "$REDIS_PASSWORD" --no-auth-warning ACL LIST >> "$diag_file"
|
||
fi
|
||
|
||
echo "===================================================" >> "$diag_file"
|
||
else
|
||
echo ".env file not found" >> "$diag_file"
|
||
fi
|
||
|
||
# ========================================
|
||
# 9. Network and Port Information
|
||
# ========================================
|
||
print_info "Collecting network information..."
|
||
|
||
# Get backend port from .env
|
||
local backend_port=$(grep '^PORT=' "$instance_dir/backend/.env" 2>/dev/null | cut -d'=' -f2 | tr -d ' ' || echo "3000")
|
||
|
||
cat >> "$diag_file" << EOF
|
||
===================================================
|
||
Network and Port Information
|
||
===================================================
|
||
|
||
=== Listening Ports ===
|
||
$(ss -tlnp | grep -E "LISTEN|nginx|node|postgres|redis" || netstat -tlnp | grep -E "LISTEN|nginx|node|postgres|redis" || echo "Could not get port information")
|
||
|
||
=== Active Connections ===
|
||
$(ss -tn state established | head -20 || echo "Could not get connection information")
|
||
|
||
=== Backend Port Connections (Port $backend_port) ===
|
||
Total connections to backend: $(ss -tn | grep ":$backend_port" | wc -l || echo "0")
|
||
$(ss -tn | grep ":$backend_port" | head -10 || echo "No connections found")
|
||
|
||
=== PostgreSQL Connections ===
|
||
EOF
|
||
|
||
# Get PostgreSQL connection count
|
||
if [ -n "$DB_PASS" ] && [ -n "$DB_USER" ] && [ -n "$DB_NAME" ]; then
|
||
PGPASSWORD="$DB_PASS" psql -h "${DB_HOST:-localhost}" -U "$DB_USER" -d "$DB_NAME" -c "
|
||
SELECT
|
||
count(*) as total_connections,
|
||
count(*) FILTER (WHERE state = 'active') as active_connections,
|
||
count(*) FILTER (WHERE state = 'idle') as idle_connections
|
||
FROM pg_stat_activity
|
||
WHERE datname = '$DB_NAME';
|
||
" >> "$diag_file" 2>&1 || echo "Could not get PostgreSQL connection stats" >> "$diag_file"
|
||
|
||
echo "" >> "$diag_file"
|
||
echo "=== PostgreSQL Connection Details ===" >> "$diag_file"
|
||
PGPASSWORD="$DB_PASS" psql -h "${DB_HOST:-localhost}" -U "$DB_USER" -d "$DB_NAME" -c "
|
||
SELECT
|
||
pid,
|
||
usename,
|
||
application_name,
|
||
client_addr,
|
||
state,
|
||
query_start,
|
||
state_change
|
||
FROM pg_stat_activity
|
||
WHERE datname = '$DB_NAME'
|
||
ORDER BY query_start DESC
|
||
LIMIT 20;
|
||
" >> "$diag_file" 2>&1 || echo "Could not get connection details" >> "$diag_file"
|
||
else
|
||
echo "Database credentials not available" >> "$diag_file"
|
||
fi
|
||
|
||
echo "" >> "$diag_file"
|
||
echo "=== Redis Connections ===" >> "$diag_file"
|
||
|
||
# Get Redis connection count
|
||
if [ -n "$REDIS_USER" ] && [ -n "$REDIS_PASSWORD" ]; then
|
||
redis-cli -h "${REDIS_HOST:-localhost}" -p "${REDIS_PORT:-6379}" --user "$REDIS_USER" --pass "$REDIS_PASSWORD" --no-auth-warning -n "${REDIS_DB:-0}" INFO clients >> "$diag_file" 2>&1 || echo "Could not get Redis connection info" >> "$diag_file"
|
||
elif [ -n "$REDIS_PASSWORD" ]; then
|
||
redis-cli -h "${REDIS_HOST:-localhost}" -p "${REDIS_PORT:-6379}" -a "$REDIS_PASSWORD" --no-auth-warning -n "${REDIS_DB:-0}" INFO clients >> "$diag_file" 2>&1 || echo "Could not get Redis connection info" >> "$diag_file"
|
||
fi
|
||
|
||
cat >> "$diag_file" << EOF
|
||
|
||
=== Firewall Status (UFW) ===
|
||
$(ufw status 2>/dev/null || echo "UFW not available")
|
||
|
||
=== Firewall Status (iptables) ===
|
||
$(iptables -L -n | head -50 2>/dev/null || echo "iptables not available")
|
||
|
||
===================================================
|
||
EOF
|
||
|
||
# ========================================
|
||
# 10. Process Information
|
||
# ========================================
|
||
print_info "Collecting process information..."
|
||
|
||
cat >> "$diag_file" << EOF
|
||
===================================================
|
||
Process Information
|
||
===================================================
|
||
|
||
=== PatchMon Node Processes ===
|
||
$(ps aux | grep -E "node.*$instance_dir|PID" | grep -v grep || echo "No processes found")
|
||
|
||
=== Top Processes (CPU) ===
|
||
$(ps aux --sort=-%cpu | head -15)
|
||
|
||
=== Top Processes (Memory) ===
|
||
$(ps aux --sort=-%mem | head -15)
|
||
|
||
===================================================
|
||
EOF
|
||
|
||
# ========================================
|
||
# 11. SSL Certificate Information
|
||
# ========================================
|
||
print_info "Collecting SSL certificate information..."
|
||
|
||
cat >> "$diag_file" << EOF
|
||
===================================================
|
||
SSL Certificate Information
|
||
===================================================
|
||
|
||
=== Certbot Certificates ===
|
||
$(certbot certificates 2>/dev/null || echo "Certbot not available or no certificates")
|
||
|
||
=== SSL Certificate Files ===
|
||
$(ls -lh /etc/letsencrypt/live/$instance_name/ 2>/dev/null || echo "No SSL certificates found for $instance_name")
|
||
|
||
===================================================
|
||
EOF
|
||
|
||
# ========================================
|
||
# 12. Recent System Logs
|
||
# ========================================
|
||
print_info "Collecting recent system logs..."
|
||
|
||
journalctl -n 200 --no-pager >> "$diag_file" 2>&1 || \
|
||
echo "Could not retrieve system logs" >> "$diag_file"
|
||
|
||
# ========================================
|
||
# 13. Installation Log (if exists)
|
||
# ========================================
|
||
print_info "Collecting installation log..."
|
||
|
||
echo "" >> "$diag_file"
|
||
echo "=== Installation Log (last 200 lines) ===" >> "$diag_file"
|
||
if [ -f "$instance_dir/patchmon-install.log" ]; then
|
||
tail -200 "$instance_dir/patchmon-install.log" >> "$diag_file" 2>&1
|
||
else
|
||
echo "No installation log found" >> "$diag_file"
|
||
fi
|
||
echo "" >> "$diag_file"
|
||
|
||
# ========================================
|
||
# 14. Node.js and npm Information
|
||
# ========================================
|
||
print_info "Collecting Node.js information..."
|
||
|
||
cat >> "$diag_file" << EOF
|
||
===================================================
|
||
Node.js and npm Information
|
||
===================================================
|
||
|
||
=== Node.js Version ===
|
||
$(node --version 2>/dev/null || echo "Node.js not found")
|
||
|
||
=== npm Version ===
|
||
$(npm --version 2>/dev/null || echo "npm not found")
|
||
|
||
=== Backend Dependencies ===
|
||
$(cd "$instance_dir/backend" && npm list --depth=0 2>/dev/null || echo "Could not list backend dependencies")
|
||
|
||
===================================================
|
||
EOF
|
||
|
||
# ========================================
|
||
# Finalize diagnostics file
|
||
# ========================================
|
||
print_info "Finalizing diagnostics file..."
|
||
|
||
echo "" >> "$diag_file"
|
||
echo "====================================================" >> "$diag_file"
|
||
echo "END OF DIAGNOSTICS REPORT" >> "$diag_file"
|
||
echo "====================================================" >> "$diag_file"
|
||
echo "" >> "$diag_file"
|
||
echo "IMPORTANT: Sensitive Information" >> "$diag_file"
|
||
echo "Passwords, secrets, and tokens have been sanitized" >> "$diag_file"
|
||
echo "and replaced with [REDACTED]. However, please review" >> "$diag_file"
|
||
echo "before sharing to ensure no sensitive data is included." >> "$diag_file"
|
||
echo "====================================================" >> "$diag_file"
|
||
|
||
print_status "Diagnostics file created: $diag_file"
|
||
|
||
# ========================================
|
||
# Display summary
|
||
# ========================================
|
||
echo ""
|
||
echo -e "${GREEN}====================================================${NC}"
|
||
echo -e "${GREEN} Diagnostics Collection Complete!${NC}"
|
||
echo -e "${GREEN}====================================================${NC}"
|
||
echo ""
|
||
|
||
# Get service statuses and file size
|
||
local service_status=$(systemctl is-active "$instance_name" 2>/dev/null || echo "unknown")
|
||
local nginx_status=$(systemctl is-active nginx 2>/dev/null || echo "unknown")
|
||
local postgres_status=$(systemctl is-active postgresql 2>/dev/null || echo "unknown")
|
||
local redis_status=$(systemctl is-active redis-server 2>/dev/null || echo "unknown")
|
||
local file_size=$(du -h "$diag_file" 2>/dev/null | cut -f1 || echo "unknown")
|
||
local line_count=$(wc -l < "$diag_file" 2>/dev/null || echo "unknown")
|
||
|
||
# Get connection counts for summary
|
||
local backend_port=$(grep '^PORT=' "$instance_dir/backend/.env" 2>/dev/null | cut -d'=' -f2 | tr -d ' ' || echo "3000")
|
||
local backend_conn_count=$(ss -tn 2>/dev/null | grep ":$backend_port" | wc -l || echo "0")
|
||
|
||
local db_conn_count="N/A"
|
||
if [ -n "$DB_PASS" ] && [ -n "$DB_USER" ] && [ -n "$DB_NAME" ]; then
|
||
db_conn_count=$(PGPASSWORD="$DB_PASS" psql -h "${DB_HOST:-localhost}" -U "$DB_USER" -d "$DB_NAME" -t -A -c "SELECT count(*) FROM pg_stat_activity WHERE datname = '$DB_NAME';" 2>/dev/null || echo "N/A")
|
||
fi
|
||
|
||
local redis_conn_count="N/A"
|
||
if [ -n "$REDIS_USER" ] && [ -n "$REDIS_PASSWORD" ]; then
|
||
redis_conn_count=$(redis-cli -h "${REDIS_HOST:-localhost}" -p "${REDIS_PORT:-6379}" --user "$REDIS_USER" --pass "$REDIS_PASSWORD" --no-auth-warning INFO clients 2>/dev/null | grep "connected_clients:" | cut -d':' -f2 | tr -d '\r' || echo "N/A")
|
||
elif [ -n "$REDIS_PASSWORD" ]; then
|
||
redis_conn_count=$(redis-cli -h "${REDIS_HOST:-localhost}" -p "${REDIS_PORT:-6379}" -a "$REDIS_PASSWORD" --no-auth-warning INFO clients 2>/dev/null | grep "connected_clients:" | cut -d':' -f2 | tr -d '\r' || echo "N/A")
|
||
fi
|
||
|
||
# Compact, copyable summary
|
||
echo -e "${BLUE}═══════════════════════════════════════════════════${NC}"
|
||
echo -e "${BLUE}DIAGNOSTICS SUMMARY (copy-paste friendly)${NC}"
|
||
echo -e "${BLUE}═══════════════════════════════════════════════════${NC}"
|
||
echo "Instance: $instance_name"
|
||
echo "File: $diag_file"
|
||
echo "Size: $file_size ($line_count lines)"
|
||
echo "Generated: $(date '+%Y-%m-%d %H:%M:%S')"
|
||
echo "---"
|
||
echo "Service Status: $service_status"
|
||
echo "Nginx Status: $nginx_status"
|
||
echo "PostgreSQL: $postgres_status"
|
||
echo "Redis: $redis_status"
|
||
echo "---"
|
||
echo "Backend Port: $backend_port (Active Connections: $backend_conn_count)"
|
||
echo "Database Connections: $db_conn_count"
|
||
echo "Redis Connections: $redis_conn_count"
|
||
echo "---"
|
||
echo "View: cat $(basename "$diag_file")"
|
||
echo "Or: less $(basename "$diag_file")"
|
||
echo "Share: Send $(basename "$diag_file") to support"
|
||
echo -e "${BLUE}═══════════════════════════════════════════════════${NC}"
|
||
echo ""
|
||
print_warning "Review file before sharing - sensitive data has been sanitized"
|
||
echo ""
|
||
|
||
print_success "Done!"
|
||
}
|
||
|
||
# Run main function
|
||
main "$@"
|
||
|