mirror of
				https://github.com/9technologygroup/patchmon.net.git
				synced 2025-10-31 12:03:47 +00:00 
			
		
		
		
	Compare commits
	
		
			10 Commits
		
	
	
		
			c98203a997
			...
			v1.3.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 0189a307ef | ||
|  | 00abbc8c62 | ||
|  | c9aef78912 | ||
|  | fd2df0729e | ||
|  | d7f7b24f8f | ||
|  | 1ef2308d56 | ||
|  | fcd1b52e0e | ||
|  | 5be8e01aa3 | ||
|  | 293733dc0b | ||
|  | c7ab40e4a2 | 
| @@ -1,29 +1,23 @@ | ||||
| # Database Configuration | ||||
| DATABASE_URL="postgresql://patchmon_user:your-password-here@localhost:5432/patchmon_db" | ||||
| DATABASE_URL="postgresql://patchmon_user:p@tchm0n_p@55@localhost:5432/patchmon_db" | ||||
| PM_DB_CONN_MAX_ATTEMPTS=30 | ||||
| PM_DB_CONN_WAIT_INTERVAL=2 | ||||
|  | ||||
| # JWT Configuration | ||||
| JWT_SECRET=your-secure-random-secret-key-change-this-in-production | ||||
| JWT_EXPIRES_IN=1h | ||||
| JWT_REFRESH_EXPIRES_IN=7d | ||||
| # Redis Configuration | ||||
| REDIS_HOST=localhost | ||||
| REDIS_PORT=6379 | ||||
| REDIS_USER=your-redis-username-here | ||||
| REDIS_PASSWORD=your-redis-password-here | ||||
| REDIS_DB=0 | ||||
|  | ||||
| # Server Configuration | ||||
| PORT=3001 | ||||
| NODE_ENV=production | ||||
| NODE_ENV=development | ||||
|  | ||||
| # API Configuration | ||||
| API_VERSION=v1 | ||||
|  | ||||
| # CORS Configuration | ||||
| CORS_ORIGIN=http://localhost:3000 | ||||
|  | ||||
| # Session Configuration | ||||
| SESSION_INACTIVITY_TIMEOUT_MINUTES=30 | ||||
|  | ||||
| # User Configuration | ||||
| DEFAULT_USER_ROLE=user | ||||
|  | ||||
| # Rate Limiting (times in milliseconds) | ||||
| RATE_LIMIT_WINDOW_MS=900000 | ||||
| RATE_LIMIT_MAX=5000 | ||||
| @@ -32,18 +26,20 @@ AUTH_RATE_LIMIT_MAX=500 | ||||
| AGENT_RATE_LIMIT_WINDOW_MS=60000 | ||||
| AGENT_RATE_LIMIT_MAX=1000 | ||||
|  | ||||
| # Redis Configuration | ||||
| REDIS_HOST=localhost | ||||
| REDIS_PORT=6379 | ||||
| REDIS_USER=your-redis-username-here | ||||
| REDIS_PASSWORD=your-redis-password-here | ||||
| REDIS_DB=0 | ||||
|  | ||||
| # Logging | ||||
| LOG_LEVEL=info | ||||
| ENABLE_LOGGING=true | ||||
|  | ||||
| # TFA Configuration (optional - used if TFA is enabled) | ||||
| # User Registration | ||||
| DEFAULT_USER_ROLE=user | ||||
|  | ||||
| # JWT Configuration | ||||
| JWT_SECRET=your-secure-random-secret-key-change-this-in-production | ||||
| JWT_EXPIRES_IN=1h | ||||
| JWT_REFRESH_EXPIRES_IN=7d | ||||
| SESSION_INACTIVITY_TIMEOUT_MINUTES=30 | ||||
|  | ||||
| # TFA Configuration | ||||
| TFA_REMEMBER_ME_EXPIRES_IN=30d | ||||
| TFA_MAX_REMEMBER_SESSIONS=5 | ||||
| TFA_SUSPICIOUS_ACTIVITY_THRESHOLD=3 | ||||
|   | ||||
| @@ -1,10 +0,0 @@ | ||||
| # Frontend Environment Configuration | ||||
| # This file is used by Vite during build and runtime | ||||
|  | ||||
| # API URL - Update this to match your backend server | ||||
| VITE_API_URL=http://localhost:3001/api/v1 | ||||
|  | ||||
| # Application Metadata | ||||
| VITE_APP_NAME=PatchMon | ||||
| VITE_APP_VERSION=1.3.0 | ||||
|  | ||||
							
								
								
									
										729
									
								
								setup.sh
									
									
									
									
									
								
							
							
						
						
									
										729
									
								
								setup.sh
									
									
									
									
									
								
							| @@ -651,11 +651,10 @@ configure_redis() { | ||||
|         return 1 | ||||
|     fi | ||||
|      | ||||
|     # Generate Redis username based on instance (global variable for use in create_env_files) | ||||
|     # Generate Redis username based on instance | ||||
|     REDIS_USER="patchmon_${DB_SAFE_NAME}" | ||||
|      | ||||
|     # Generate separate user password (more secure than reusing admin password) | ||||
|     # This will be stored in the .env file for the application to use | ||||
|     REDIS_USER_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-32) | ||||
|      | ||||
|     print_info "Creating Redis user: $REDIS_USER for database $REDIS_DB" | ||||
| @@ -762,9 +761,12 @@ configure_redis() { | ||||
|     redis-cli -h 127.0.0.1 -p 6379 --user "$REDIS_USER" --pass "$REDIS_USER_PASSWORD" --no-auth-warning -n "$REDIS_DB" SET "patchmon:initialized" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > /dev/null | ||||
|     print_status "Marked Redis database $REDIS_DB as in-use" | ||||
|      | ||||
|     # Note: Redis credentials will be written to .env by create_env_files() function | ||||
|     print_status "Redis user '$REDIS_USER' configured successfully" | ||||
|     print_info "Redis credentials will be saved to backend/.env" | ||||
|     # Update .env with the USER PASSWORD, not admin password | ||||
|     echo "REDIS_USER=$REDIS_USER" >> .env | ||||
|     echo "REDIS_PASSWORD=$REDIS_USER_PASSWORD" >> .env | ||||
|     echo "REDIS_DB=$REDIS_DB" >> .env | ||||
|      | ||||
|     print_status "Redis user password: $REDIS_USER_PASSWORD" | ||||
|      | ||||
|     return 0 | ||||
| } | ||||
| @@ -1061,17 +1063,12 @@ AGENT_RATE_LIMIT_MAX=1000 | ||||
| REDIS_HOST=localhost | ||||
| REDIS_PORT=6379 | ||||
| REDIS_USER=$REDIS_USER | ||||
| REDIS_PASSWORD=$REDIS_USER_PASSWORD | ||||
| REDIS_PASSWORD=$REDIS_PASSWORD | ||||
| REDIS_DB=$REDIS_DB | ||||
|  | ||||
| # Logging | ||||
| LOG_LEVEL=info | ||||
| ENABLE_LOGGING=true | ||||
|  | ||||
| # TFA Configuration | ||||
| TFA_REMEMBER_ME_EXPIRES_IN=30d | ||||
| TFA_MAX_REMEMBER_SESSIONS=5 | ||||
| TFA_SUSPICIOUS_ACTIVITY_THRESHOLD=3 | ||||
| EOF | ||||
|  | ||||
|     # Frontend .env | ||||
| @@ -1133,216 +1130,108 @@ EOF | ||||
|     print_status "Systemd service created: $SERVICE_NAME (running as $INSTANCE_USER)" | ||||
| } | ||||
|  | ||||
| # Unified nginx configuration generator | ||||
| generate_nginx_config() { | ||||
|     local fqdn="$1" | ||||
|     local app_dir="$2" | ||||
|     local backend_port="$3" | ||||
|     local ssl_enabled="$4"  # "true" or "false" | ||||
|     local config_file="/etc/nginx/sites-available/$fqdn" | ||||
|      | ||||
|     print_info "Generating nginx configuration for $fqdn (SSL: $ssl_enabled)" | ||||
|      | ||||
|     if [ "$ssl_enabled" = "true" ]; then | ||||
|         # SSL Configuration | ||||
|         cat > "$config_file" << EOF | ||||
| # HTTP to HTTPS redirect | ||||
| server { | ||||
|     listen 80; | ||||
|     server_name $fqdn; | ||||
|      | ||||
|     # Let's Encrypt challenge location | ||||
|     location /.well-known/acme-challenge/ { | ||||
|         root /var/www/html; | ||||
|     } | ||||
|      | ||||
|     # Redirect all other traffic to HTTPS | ||||
|     location / { | ||||
|         return 301 https://\$server_name\$request_uri; | ||||
|     } | ||||
| } | ||||
|  | ||||
| # HTTPS server block | ||||
| server { | ||||
|     listen 443 ssl http2; | ||||
|     server_name $fqdn; | ||||
|      | ||||
|     # SSL Configuration | ||||
|     ssl_certificate /etc/letsencrypt/live/$fqdn/fullchain.pem; | ||||
|     ssl_certificate_key /etc/letsencrypt/live/$fqdn/privkey.pem; | ||||
|     include /etc/letsencrypt/options-ssl-nginx.conf; | ||||
|     ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; | ||||
|      | ||||
|     # Security headers (applied to all responses) | ||||
|     add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; | ||||
|     add_header X-Frame-Options DENY always; | ||||
|     add_header X-Content-Type-Options nosniff always; | ||||
|     add_header X-XSS-Protection "1; mode=block" always; | ||||
|     add_header Referrer-Policy "strict-origin-when-cross-origin" always; | ||||
|      | ||||
|     # Frontend | ||||
|     location / { | ||||
|         root $app_dir/frontend/dist; | ||||
|         try_files \$uri \$uri/ /index.html; | ||||
|     } | ||||
|      | ||||
|     # Bull Board proxy | ||||
|     location /bullboard { | ||||
|         proxy_pass http://127.0.0.1:$backend_port; | ||||
|         proxy_http_version 1.1; | ||||
|         proxy_set_header Upgrade \$http_upgrade; | ||||
|         proxy_set_header Connection 'upgrade'; | ||||
|         proxy_set_header Host \$host; | ||||
|         proxy_set_header X-Real-IP \$remote_addr; | ||||
|         proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||||
|         proxy_set_header X-Forwarded-Proto \$scheme; | ||||
|         proxy_set_header X-Forwarded-Host \$host; | ||||
|         proxy_set_header Cookie \$http_cookie; | ||||
|         proxy_cache_bypass \$http_upgrade; | ||||
|         proxy_read_timeout 300s; | ||||
|         proxy_connect_timeout 75s; | ||||
|          | ||||
|         # Enable cookie passthrough | ||||
|         proxy_pass_header Set-Cookie; | ||||
|         proxy_cookie_path / /; | ||||
|          | ||||
|         # Preserve original client IP | ||||
|         proxy_set_header X-Original-Forwarded-For \$http_x_forwarded_for; | ||||
|          | ||||
|         if (\$request_method = 'OPTIONS') { | ||||
|             return 204; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     # API proxy | ||||
|     location /api/ { | ||||
|         proxy_pass http://127.0.0.1:$backend_port; | ||||
|         proxy_http_version 1.1; | ||||
|         proxy_set_header Upgrade \$http_upgrade; | ||||
|         proxy_set_header Connection 'upgrade'; | ||||
|         proxy_set_header Host \$host; | ||||
|         proxy_set_header X-Real-IP \$remote_addr; | ||||
|         proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||||
|         proxy_set_header X-Forwarded-Proto \$scheme; | ||||
|         proxy_cache_bypass \$http_upgrade; | ||||
|         proxy_read_timeout 300s; | ||||
|         proxy_connect_timeout 75s; | ||||
|          | ||||
|         # Preserve original client IP | ||||
|         proxy_set_header X-Original-Forwarded-For \$http_x_forwarded_for; | ||||
|          | ||||
|         if (\$request_method = 'OPTIONS') { | ||||
|             return 204; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     # Static assets caching (exclude Bull Board assets) | ||||
|     location ~* ^/(?!bullboard).*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { | ||||
|         expires 1y; | ||||
|         add_header Cache-Control "public, immutable"; | ||||
|     } | ||||
|      | ||||
|     # Health check endpoint | ||||
|     location /health { | ||||
|         proxy_pass http://127.0.0.1:$backend_port/health; | ||||
|         access_log off; | ||||
|     } | ||||
| } | ||||
| EOF | ||||
|     else | ||||
|         # HTTP-only configuration | ||||
|         cat > "$config_file" << EOF | ||||
| server { | ||||
|     listen 80; | ||||
|     server_name $fqdn; | ||||
|      | ||||
|     # Security headers | ||||
|     add_header X-Frame-Options DENY always; | ||||
|     add_header X-Content-Type-Options nosniff always; | ||||
|     add_header X-XSS-Protection "1; mode=block" always; | ||||
|     add_header Referrer-Policy "strict-origin-when-cross-origin" always; | ||||
|      | ||||
|     # Frontend | ||||
|     location / { | ||||
|         root $app_dir/frontend/dist; | ||||
|         try_files \$uri \$uri/ /index.html; | ||||
|     } | ||||
|      | ||||
|     # Bull Board proxy | ||||
|     location /bullboard { | ||||
|         proxy_pass http://127.0.0.1:$backend_port; | ||||
|         proxy_http_version 1.1; | ||||
|         proxy_set_header Upgrade \$http_upgrade; | ||||
|         proxy_set_header Connection 'upgrade'; | ||||
|         proxy_set_header Host \$host; | ||||
|         proxy_set_header X-Real-IP \$remote_addr; | ||||
|         proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||||
|         proxy_set_header X-Forwarded-Proto \$scheme; | ||||
|         proxy_set_header X-Forwarded-Host \$host; | ||||
|         proxy_set_header Cookie \$http_cookie; | ||||
|         proxy_cache_bypass \$http_upgrade; | ||||
|         proxy_read_timeout 300s; | ||||
|         proxy_connect_timeout 75s; | ||||
|          | ||||
|         # Enable cookie passthrough | ||||
|         proxy_pass_header Set-Cookie; | ||||
|         proxy_cookie_path / /; | ||||
|          | ||||
|         # Preserve original client IP | ||||
|         proxy_set_header X-Original-Forwarded-For \$http_x_forwarded_for; | ||||
|          | ||||
|         if (\$request_method = 'OPTIONS') { | ||||
|             return 204; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     # API proxy | ||||
|     location /api/ { | ||||
|         proxy_pass http://127.0.0.1:$backend_port; | ||||
|         proxy_http_version 1.1; | ||||
|         proxy_set_header Upgrade \$http_upgrade; | ||||
|         proxy_set_header Connection 'upgrade'; | ||||
|         proxy_set_header Host \$host; | ||||
|         proxy_set_header X-Real-IP \$remote_addr; | ||||
|         proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||||
|         proxy_set_header X-Forwarded-Proto \$scheme; | ||||
|         proxy_cache_bypass \$http_upgrade; | ||||
|         proxy_read_timeout 300s; | ||||
|         proxy_connect_timeout 75s; | ||||
|          | ||||
|         # Preserve original client IP | ||||
|         proxy_set_header X-Original-Forwarded-For \$http_x_forwarded_for; | ||||
|          | ||||
|         if (\$request_method = 'OPTIONS') { | ||||
|             return 204; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     # Static assets caching (exclude Bull Board assets) | ||||
|     location ~* ^/(?!bullboard).*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { | ||||
|         expires 1y; | ||||
|         add_header Cache-Control "public, immutable"; | ||||
|     } | ||||
|      | ||||
|     # Health check endpoint | ||||
|     location /health { | ||||
|         proxy_pass http://127.0.0.1:$backend_port/health; | ||||
|         access_log off; | ||||
|     } | ||||
| } | ||||
| EOF | ||||
|     fi | ||||
|      | ||||
|     print_status "Nginx configuration generated for $fqdn" | ||||
| } | ||||
|  | ||||
| # Setup nginx configuration | ||||
| setup_nginx() { | ||||
|     print_info "Setting up nginx configuration..." | ||||
|     log_message "Setting up nginx configuration for $FQDN" | ||||
|      | ||||
|     # Generate HTTP-only config first (needed for Let's Encrypt challenge if SSL enabled) | ||||
|     generate_nginx_config "$FQDN" "$APP_DIR" "$BACKEND_PORT" "false" | ||||
|     if [ "$USE_LETSENCRYPT" = "true" ]; then | ||||
|         # HTTP-only config first for Certbot challenge | ||||
|         cat > "/etc/nginx/sites-available/$FQDN" << EOF | ||||
| server { | ||||
|     listen 80; | ||||
|     server_name $FQDN; | ||||
|      | ||||
|     location /.well-known/acme-challenge/ { | ||||
|         root /var/www/html; | ||||
|     } | ||||
|      | ||||
|     location / { | ||||
|         return 301 https://\$server_name\$request_uri; | ||||
|     } | ||||
| } | ||||
| EOF | ||||
|     else | ||||
|         # HTTP-only configuration for local hosting | ||||
|         cat > "/etc/nginx/sites-available/$FQDN" << EOF | ||||
| server { | ||||
|     listen 80; | ||||
|     server_name $FQDN; | ||||
|      | ||||
|     # Frontend | ||||
|     location / { | ||||
|         root $APP_DIR/frontend/dist; | ||||
|         try_files \$uri \$uri/ /index.html; | ||||
|          | ||||
|         # Security headers | ||||
|         add_header X-Frame-Options DENY; | ||||
|         add_header X-Content-Type-Options nosniff; | ||||
|         add_header X-XSS-Protection "1; mode=block"; | ||||
|     } | ||||
|      | ||||
|     # Bull Board proxy | ||||
|     location /bullboard { | ||||
|         proxy_pass http://127.0.0.1:$BACKEND_PORT; | ||||
|         proxy_set_header Host \$host; | ||||
|         proxy_set_header X-Real-IP \$remote_addr; | ||||
|         proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||||
|         proxy_set_header X-Forwarded-Proto \$scheme; | ||||
|         proxy_set_header X-Forwarded-Host \$host; | ||||
|          | ||||
|         # CORS headers for Bull Board | ||||
|         add_header Access-Control-Allow-Origin * always; | ||||
|         add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; | ||||
|         add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always; | ||||
|          | ||||
|         # Handle preflight requests | ||||
|         if (\$request_method = 'OPTIONS') { | ||||
|             return 204; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     # API routes | ||||
|     # Bull Board proxy | ||||
|     location /bullboard { | ||||
|         proxy_pass http://127.0.0.1:$BACKEND_PORT; | ||||
|         proxy_set_header Host \$host; | ||||
|         proxy_set_header X-Real-IP \$remote_addr; | ||||
|         proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||||
|         proxy_set_header X-Forwarded-Proto \$scheme; | ||||
|         proxy_set_header X-Forwarded-Host \$host; | ||||
|          | ||||
|         # CORS headers for Bull Board | ||||
|         add_header Access-Control-Allow-Origin * always; | ||||
|         add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; | ||||
|         add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always; | ||||
|          | ||||
|         # Handle preflight requests | ||||
|         if (\$request_method = 'OPTIONS') { | ||||
|             return 204; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     location /api/ { | ||||
|         proxy_pass http://127.0.0.1:$BACKEND_PORT; | ||||
|         proxy_http_version 1.1; | ||||
|         proxy_set_header Upgrade \$http_upgrade; | ||||
|         proxy_set_header Connection 'upgrade'; | ||||
|         proxy_set_header Host \$host; | ||||
|         proxy_set_header X-Real-IP \$remote_addr; | ||||
|         proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||||
|         proxy_set_header X-Forwarded-Proto \$scheme; | ||||
|         proxy_cache_bypass \$http_upgrade; | ||||
|         proxy_read_timeout 300s; | ||||
|         proxy_connect_timeout 75s; | ||||
|     } | ||||
|      | ||||
|     # Health check | ||||
|     location /health { | ||||
|         proxy_pass http://127.0.0.1:$BACKEND_PORT/health; | ||||
|         access_log off; | ||||
|     } | ||||
| } | ||||
| EOF | ||||
|     fi | ||||
|  | ||||
|     # Enable site | ||||
|     ln -sf "/etc/nginx/sites-available/$FQDN" "/etc/nginx/sites-enabled/" | ||||
| @@ -1365,10 +1254,96 @@ setup_letsencrypt() { | ||||
|      | ||||
|     # Check if a valid certificate already exists | ||||
|     if certbot certificates 2>/dev/null | grep -q "$FQDN" && certbot certificates 2>/dev/null | grep -A 10 "$FQDN" | grep -q "VALID"; then | ||||
|         print_status "Valid SSL certificate already exists for $FQDN" | ||||
|         print_status "Valid SSL certificate already exists for $FQDN, skipping certificate generation" | ||||
|          | ||||
|         # Generate SSL config with existing certificate | ||||
|         generate_nginx_config "$FQDN" "$APP_DIR" "$BACKEND_PORT" "true" | ||||
|         # Update Nginx config with existing HTTPS configuration | ||||
|         cat > "/etc/nginx/sites-available/$FQDN" << EOF | ||||
| server { | ||||
|     listen 80; | ||||
|     server_name $FQDN; | ||||
|     return 301 https://\$server_name\$request_uri; | ||||
| } | ||||
|  | ||||
| server { | ||||
|     listen 443 ssl http2; | ||||
|     server_name $FQDN; | ||||
|      | ||||
|     ssl_certificate /etc/letsencrypt/live/$FQDN/fullchain.pem; | ||||
|     ssl_certificate_key /etc/letsencrypt/live/$FQDN/privkey.pem; | ||||
|     include /etc/letsencrypt/options-ssl-nginx.conf; | ||||
|     ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; | ||||
|      | ||||
|     # Security headers | ||||
|     add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; | ||||
|     add_header X-Frame-Options DENY; | ||||
|     add_header X-Content-Type-Options nosniff; | ||||
|     add_header X-XSS-Protection "1; mode=block"; | ||||
|      | ||||
|     # Frontend | ||||
|     location / { | ||||
|         root $APP_DIR/frontend/dist; | ||||
|         try_files \$uri \$uri/ /index.html; | ||||
|          | ||||
|         # Security headers | ||||
|         add_header X-Frame-Options DENY; | ||||
|         add_header X-Content-Type-Options nosniff; | ||||
|         add_header X-XSS-Protection "1; mode=block"; | ||||
|     } | ||||
|      | ||||
|     # Bull Board proxy | ||||
|     location /bullboard { | ||||
|         proxy_pass http://127.0.0.1:$BACKEND_PORT; | ||||
|         proxy_set_header Host \$host; | ||||
|         proxy_set_header X-Real-IP \$remote_addr; | ||||
|         proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||||
|         proxy_set_header X-Forwarded-Proto \$scheme; | ||||
|         proxy_set_header X-Forwarded-Host \$host; | ||||
|          | ||||
|         # CORS headers for Bull Board | ||||
|         add_header Access-Control-Allow-Origin * always; | ||||
|         add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; | ||||
|         add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always; | ||||
|          | ||||
|         # Handle preflight requests | ||||
|         if (\$request_method = 'OPTIONS') { | ||||
|             return 204; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     # API proxy | ||||
|     # Bull Board proxy | ||||
|     location /bullboard { | ||||
|         proxy_pass http://127.0.0.1:$BACKEND_PORT; | ||||
|         proxy_set_header Host \$host; | ||||
|         proxy_set_header X-Real-IP \$remote_addr; | ||||
|         proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||||
|         proxy_set_header X-Forwarded-Proto \$scheme; | ||||
|         proxy_set_header X-Forwarded-Host \$host; | ||||
|          | ||||
|         # CORS headers for Bull Board | ||||
|         add_header Access-Control-Allow-Origin * always; | ||||
|         add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; | ||||
|         add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always; | ||||
|          | ||||
|         # Handle preflight requests | ||||
|         if (\$request_method = 'OPTIONS') { | ||||
|             return 204; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     location /api/ { | ||||
|         proxy_pass http://127.0.0.1:$BACKEND_PORT; | ||||
|         proxy_http_version 1.1; | ||||
|         proxy_set_header Upgrade \$http_upgrade; | ||||
|         proxy_set_header Connection 'upgrade'; | ||||
|         proxy_set_header Host \$host; | ||||
|         proxy_set_header X-Real-IP \$remote_addr; | ||||
|         proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||||
|         proxy_set_header X-Forwarded-Proto \$scheme; | ||||
|         proxy_cache_bypass \$http_upgrade; | ||||
|     } | ||||
| } | ||||
| EOF | ||||
|          | ||||
|         # Enable the site | ||||
|         ln -sf "/etc/nginx/sites-available/$FQDN" "/etc/nginx/sites-enabled/" | ||||
| @@ -1387,7 +1362,7 @@ setup_letsencrypt() { | ||||
|      | ||||
|     print_info "No valid certificate found, generating new SSL certificate..." | ||||
|      | ||||
|     # Wait for nginx to be ready | ||||
|     # Wait a moment for nginx to be ready | ||||
|     sleep 5 | ||||
|      | ||||
|     # Obtain SSL certificate | ||||
| @@ -1395,17 +1370,100 @@ setup_letsencrypt() { | ||||
|     certbot --nginx -d "$FQDN" --non-interactive --agree-tos --email "$EMAIL" --redirect | ||||
|     log_message "SSL certificate obtained successfully" | ||||
|      | ||||
|     # Generate SSL nginx configuration | ||||
|     generate_nginx_config "$FQDN" "$APP_DIR" "$BACKEND_PORT" "true" | ||||
|     # Update Nginx config with full HTTPS configuration | ||||
|     cat > "/etc/nginx/sites-available/$FQDN" << EOF | ||||
| server { | ||||
|     listen 80; | ||||
|     server_name $FQDN; | ||||
|     return 301 https://\$server_name\$request_uri; | ||||
| } | ||||
|  | ||||
|     # Test and reload nginx | ||||
|     if nginx -t; then | ||||
|         systemctl reload nginx | ||||
|         print_status "Nginx configuration updated successfully" | ||||
|     else | ||||
|         print_error "Nginx configuration test failed" | ||||
|         return 1 | ||||
|     fi | ||||
| server { | ||||
|     listen 443 ssl http2; | ||||
|     server_name $FQDN; | ||||
|      | ||||
|     ssl_certificate /etc/letsencrypt/live/$FQDN/fullchain.pem; | ||||
|     ssl_certificate_key /etc/letsencrypt/live/$FQDN/privkey.pem; | ||||
|     include /etc/letsencrypt/options-ssl-nginx.conf; | ||||
|     ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; | ||||
|      | ||||
|     # Frontend | ||||
|     location / { | ||||
|         root $APP_DIR/frontend/dist; | ||||
|         try_files \$uri \$uri/ /index.html; | ||||
|          | ||||
|         # Security headers | ||||
|         add_header X-Frame-Options DENY; | ||||
|         add_header X-Content-Type-Options nosniff; | ||||
|         add_header X-XSS-Protection "1; mode=block"; | ||||
|         add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; | ||||
|     } | ||||
|      | ||||
|     # Bull Board proxy | ||||
|     location /bullboard { | ||||
|         proxy_pass http://127.0.0.1:$BACKEND_PORT; | ||||
|         proxy_set_header Host \$host; | ||||
|         proxy_set_header X-Real-IP \$remote_addr; | ||||
|         proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||||
|         proxy_set_header X-Forwarded-Proto \$scheme; | ||||
|         proxy_set_header X-Forwarded-Host \$host; | ||||
|          | ||||
|         # CORS headers for Bull Board | ||||
|         add_header Access-Control-Allow-Origin * always; | ||||
|         add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; | ||||
|         add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always; | ||||
|          | ||||
|         # Handle preflight requests | ||||
|         if (\$request_method = 'OPTIONS') { | ||||
|             return 204; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     # API routes | ||||
|     # Bull Board proxy | ||||
|     location /bullboard { | ||||
|         proxy_pass http://127.0.0.1:$BACKEND_PORT; | ||||
|         proxy_set_header Host \$host; | ||||
|         proxy_set_header X-Real-IP \$remote_addr; | ||||
|         proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||||
|         proxy_set_header X-Forwarded-Proto \$scheme; | ||||
|         proxy_set_header X-Forwarded-Host \$host; | ||||
|          | ||||
|         # CORS headers for Bull Board | ||||
|         add_header Access-Control-Allow-Origin * always; | ||||
|         add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; | ||||
|         add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always; | ||||
|          | ||||
|         # Handle preflight requests | ||||
|         if (\$request_method = 'OPTIONS') { | ||||
|             return 204; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     location /api/ { | ||||
|         proxy_pass http://127.0.0.1:$BACKEND_PORT; | ||||
|         proxy_http_version 1.1; | ||||
|         proxy_set_header Upgrade \$http_upgrade; | ||||
|         proxy_set_header Connection 'upgrade'; | ||||
|         proxy_set_header Host \$host; | ||||
|         proxy_set_header X-Real-IP \$remote_addr; | ||||
|         proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||||
|         proxy_set_header X-Forwarded-Proto \$scheme; | ||||
|         proxy_cache_bypass \$http_upgrade; | ||||
|         proxy_read_timeout 300s; | ||||
|         proxy_connect_timeout 75s; | ||||
|     } | ||||
|      | ||||
|     # Health check | ||||
|     location /health { | ||||
|         proxy_pass http://127.0.0.1:$BACKEND_PORT/health; | ||||
|         access_log off; | ||||
|     } | ||||
| } | ||||
| EOF | ||||
|      | ||||
|     nginx -t | ||||
|     nginx -s reload | ||||
|      | ||||
|     # Setup auto-renewal | ||||
|     echo "0 12 * * * /usr/bin/certbot renew --quiet" | crontab - | ||||
| @@ -1964,190 +2022,6 @@ select_installation_to_update() { | ||||
|     done | ||||
| } | ||||
|  | ||||
| # Check and update Redis configuration for existing installation | ||||
| update_redis_configuration() { | ||||
|     print_info "Checking Redis configuration..." | ||||
|      | ||||
|     # Check if Redis configuration exists in .env | ||||
|     if [ -f "$instance_dir/backend/.env" ]; then | ||||
|         if grep -q "^REDIS_HOST=" "$instance_dir/backend/.env" && \ | ||||
|            grep -q "^REDIS_PASSWORD=" "$instance_dir/backend/.env"; then | ||||
|             print_status "Redis configuration already exists in .env" | ||||
|             return 0 | ||||
|         fi | ||||
|     fi | ||||
|      | ||||
|     print_warning "Redis configuration not found in .env - this is a legacy installation" | ||||
|     print_info "Setting up Redis for this instance..." | ||||
|      | ||||
|     # Detect package manager if not already set | ||||
|     if [ -z "$PKG_INSTALL" ]; then | ||||
|         if command -v apt >/dev/null 2>&1; then | ||||
|             PKG_INSTALL="apt install -y" | ||||
|         elif command -v apt-get >/dev/null 2>&1; then | ||||
|             PKG_INSTALL="apt-get install -y" | ||||
|         else | ||||
|             print_error "No supported package manager found" | ||||
|             return 1 | ||||
|         fi | ||||
|     fi | ||||
|      | ||||
|     # Ensure Redis is installed and running | ||||
|     if ! systemctl is-active --quiet redis-server; then | ||||
|         print_info "Installing Redis..." | ||||
|         $PKG_INSTALL redis-server | ||||
|         systemctl start redis-server | ||||
|         systemctl enable redis-server | ||||
|     fi | ||||
|      | ||||
|     # Generate Redis variables for this instance | ||||
|     # Extract DB_SAFE_NAME from existing database name | ||||
|     DB_SAFE_NAME=$(echo "$DB_NAME" | sed 's/[^a-zA-Z0-9]/_/g') | ||||
|     REDIS_USER="patchmon_${DB_SAFE_NAME}" | ||||
|     REDIS_USER_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-32) | ||||
|      | ||||
|     # Find available Redis database | ||||
|     print_info "Finding available Redis database..." | ||||
|     local redis_db=0 | ||||
|     local max_attempts=16 | ||||
|      | ||||
|     while [ $redis_db -lt $max_attempts ]; do | ||||
|         local key_count | ||||
|         key_count=$(redis-cli -h localhost -p 6379 -n "$redis_db" DBSIZE 2>&1 | grep -v "ERR" || echo "1") | ||||
|          | ||||
|         if [ "$key_count" = "0" ] || [ "$key_count" = "(integer) 0" ]; then | ||||
|             print_status "Found available Redis database: $redis_db" | ||||
|             REDIS_DB=$redis_db | ||||
|             break | ||||
|         fi | ||||
|         redis_db=$((redis_db + 1)) | ||||
|     done | ||||
|      | ||||
|     if [ -z "$REDIS_DB" ]; then | ||||
|         print_warning "No empty Redis database found, using database 0" | ||||
|         REDIS_DB=0 | ||||
|     fi | ||||
|      | ||||
|     # Generate admin password if not exists | ||||
|     REDIS_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-32) | ||||
|      | ||||
|     # Configure Redis with ACL if needed | ||||
|     print_info "Configuring Redis ACL..." | ||||
|      | ||||
|     # Create ACL file if it doesn't exist | ||||
|     if [ ! -f /etc/redis/users.acl ]; then | ||||
|         touch /etc/redis/users.acl | ||||
|         chown redis:redis /etc/redis/users.acl | ||||
|         chmod 640 /etc/redis/users.acl | ||||
|     fi | ||||
|      | ||||
|     # Configure ACL file in redis.conf | ||||
|     if ! grep -q "^aclfile" /etc/redis/redis.conf 2>/dev/null; then | ||||
|         echo "aclfile /etc/redis/users.acl" >> /etc/redis/redis.conf | ||||
|     fi | ||||
|      | ||||
|     # Remove requirepass (incompatible with ACL) | ||||
|     if grep -q "^requirepass" /etc/redis/redis.conf 2>/dev/null; then | ||||
|         sed -i 's/^requirepass.*/# &/' /etc/redis/redis.conf | ||||
|     fi | ||||
|      | ||||
|     # Create admin user if it doesn't exist | ||||
|     if ! grep -q "^user admin" /etc/redis/users.acl; then | ||||
|         echo "user admin on sanitize-payload >$REDIS_PASSWORD ~* &* +@all" >> /etc/redis/users.acl | ||||
|         systemctl restart redis-server | ||||
|         sleep 3 | ||||
|     fi | ||||
|      | ||||
|     # Create instance-specific Redis user | ||||
|     print_info "Creating Redis user: $REDIS_USER" | ||||
|      | ||||
|     # Try to authenticate with admin (may already exist from another instance) | ||||
|     local acl_result | ||||
|     acl_result=$(redis-cli -h 127.0.0.1 -p 6379 --user admin --pass "$REDIS_PASSWORD" --no-auth-warning ACL SETUSER "$REDIS_USER" on ">${REDIS_USER_PASSWORD}" ~* +@all 2>&1) | ||||
|      | ||||
|     if [ "$acl_result" = "OK" ] || echo "$acl_result" | grep -q "OK"; then | ||||
|         print_status "Redis user created successfully" | ||||
|         redis-cli -h 127.0.0.1 -p 6379 --user admin --pass "$REDIS_PASSWORD" --no-auth-warning ACL SAVE > /dev/null 2>&1 | ||||
|     else | ||||
|         print_warning "Could not create Redis user with ACL, trying without authentication..." | ||||
|         # Fallback for systems without ACL configured | ||||
|         redis-cli -h 127.0.0.1 -p 6379 CONFIG SET requirepass "$REDIS_USER_PASSWORD" > /dev/null 2>&1 || true | ||||
|     fi | ||||
|      | ||||
|     # Backup existing .env | ||||
|     cp "$instance_dir/backend/.env" "$instance_dir/backend/.env.backup.$(date +%Y%m%d_%H%M%S)" | ||||
|     print_info "Backed up existing .env file" | ||||
|      | ||||
|     # Add Redis configuration to .env | ||||
|     print_info "Adding Redis configuration to .env..." | ||||
|     cat >> "$instance_dir/backend/.env" << EOF | ||||
|  | ||||
| # Redis Configuration (added during update) | ||||
| REDIS_HOST=localhost | ||||
| REDIS_PORT=6379 | ||||
| REDIS_USER=$REDIS_USER | ||||
| REDIS_PASSWORD=$REDIS_USER_PASSWORD | ||||
| REDIS_DB=$REDIS_DB | ||||
| EOF | ||||
|      | ||||
|     print_status "Redis configuration added to .env" | ||||
|     print_info "Redis User: $REDIS_USER" | ||||
|     print_info "Redis Database: $REDIS_DB" | ||||
|      | ||||
|     return 0 | ||||
| } | ||||
|  | ||||
| # Update nginx configuration for existing installation | ||||
| update_nginx_configuration() { | ||||
|     print_info "Updating nginx configuration..." | ||||
|      | ||||
|     # Detect SSL status | ||||
|     local ssl_enabled="false" | ||||
|     if [ -f "/etc/letsencrypt/live/$SELECTED_INSTANCE/fullchain.pem" ]; then | ||||
|         ssl_enabled="true" | ||||
|         print_info "SSL certificate detected, updating HTTPS configuration" | ||||
|     else | ||||
|         print_info "No SSL certificate found, updating HTTP configuration" | ||||
|     fi | ||||
|      | ||||
|     # Backup existing config | ||||
|     local backup_file="/etc/nginx/sites-available/$SELECTED_INSTANCE.backup.$(date +%Y%m%d_%H%M%S)" | ||||
|     if [ -f "/etc/nginx/sites-available/$SELECTED_INSTANCE" ]; then | ||||
|         cp "/etc/nginx/sites-available/$SELECTED_INSTANCE" "$backup_file" | ||||
|         print_info "Backed up existing nginx config to: $backup_file" | ||||
|     fi | ||||
|      | ||||
|     # Extract backend port | ||||
|     local backend_port=$(grep -o 'proxy_pass http://127.0.0.1:[0-9]*' "/etc/nginx/sites-available/$SELECTED_INSTANCE" 2>/dev/null | grep -oP ':\K[0-9]+' | head -1) | ||||
|     if [ -z "$backend_port" ] && [ -f "$instance_dir/backend/.env" ]; then | ||||
|         backend_port=$(grep '^PORT=' "$instance_dir/backend/.env" | cut -d'=' -f2 | tr -d ' ') | ||||
|     fi | ||||
|      | ||||
|     if [ -z "$backend_port" ]; then | ||||
|         print_warning "Could not determine backend port, skipping nginx config update" | ||||
|         return 0 | ||||
|     fi | ||||
|      | ||||
|     print_info "Detected backend port: $backend_port" | ||||
|      | ||||
|     # Generate new configuration using the unified function | ||||
|     generate_nginx_config "$SELECTED_INSTANCE" "$instance_dir" "$backend_port" "$ssl_enabled" | ||||
|      | ||||
|     # Test and reload nginx | ||||
|     if nginx -t; then | ||||
|         systemctl reload nginx | ||||
|         print_status "Nginx configuration updated successfully" | ||||
|     else | ||||
|         print_error "Nginx configuration test failed" | ||||
|         # Restore backup | ||||
|         if [ -f "$backup_file" ]; then | ||||
|             mv "$backup_file" "/etc/nginx/sites-available/$SELECTED_INSTANCE" | ||||
|             print_info "Restored backup nginx configuration" | ||||
|         fi | ||||
|         return 1 | ||||
|     fi | ||||
| } | ||||
|  | ||||
| # Update existing installation | ||||
| update_installation() { | ||||
|     local instance_dir="/opt/$SELECTED_INSTANCE" | ||||
| @@ -2271,12 +2145,6 @@ update_installation() { | ||||
|     npx prisma generate | ||||
|     npx prisma migrate deploy | ||||
|      | ||||
|     # Check and update Redis configuration if needed (for legacy installations) | ||||
|     update_redis_configuration | ||||
|      | ||||
|     # Update nginx configuration with latest improvements | ||||
|     update_nginx_configuration | ||||
|      | ||||
|     # Start the service | ||||
|     print_info "Starting service: $service_name" | ||||
|     systemctl start "$service_name" | ||||
| @@ -2344,27 +2212,6 @@ main() { | ||||
|     fi | ||||
|      | ||||
|     # Normal installation mode | ||||
|     # Check if existing installations are present | ||||
|     local existing_installs=($(detect_installations)) | ||||
|     if [ ${#existing_installs[@]} -gt 0 ]; then | ||||
|         print_warning "⚠️  Found ${#existing_installs[@]} existing PatchMon installation(s):" | ||||
|         for install in "${existing_installs[@]}"; do | ||||
|             print_info "   - $install" | ||||
|         done | ||||
|         echo "" | ||||
|         print_warning "If you want to UPDATE an existing installation, run:" | ||||
|         print_info "   sudo bash $0 --update" | ||||
|         echo "" | ||||
|         print_warning "If you want to create a NEW installation alongside the existing one(s), continue below." | ||||
|         echo "" | ||||
|         read_yes_no "Do you want to continue with NEW installation?" CONTINUE_NEW "n" | ||||
|          | ||||
|         if [ "$CONTINUE_NEW" != "y" ]; then | ||||
|             print_info "Installation cancelled. Run with --update flag to update existing installations." | ||||
|             exit 0 | ||||
|         fi | ||||
|     fi | ||||
|      | ||||
|     # Run interactive setup | ||||
|     interactive_setup | ||||
|      | ||||
|   | ||||
		Reference in New Issue
	
	Block a user