#!/bin/bash # # PgBouncer Setup Script for LeoCRM # =================================== # # This script installs and configures PgBouncer for PostgreSQL connection pooling. # It creates the configuration files and starts the service. # # Usage: # ./scripts/setup_pgbouncer.sh [--docker] [--password PASSWORD] # # Options: # --docker Configure for Docker Compose environment # --password PASS Set PostgreSQL password (default: from .env or prompt) # set -euo pipefail # ─── Color Output ─── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } # ─── Default Values ─── PGBOUNCER_VERSION="1.23.1" PGBOUNCER_PORT="6432" POOL_MODE="transaction" DEFAULT_POOL_SIZE="25" MAX_CLIENT_CONN="200" # ─── Parse Arguments ─── DOCKER_MODE=false POSTGRES_PASSWORD="" while [[ $# -gt 0 ]]; do case $1 in --docker) DOCKER_MODE=true shift ;; --password) POSTGRES_PASSWORD="$2" shift 2 ;; --help) echo "Usage: $0 [--docker] [--password PASSWORD]" echo "" echo "Options:" echo " --docker Configure for Docker Compose environment" echo " --password PASS Set PostgreSQL password (default: from .env or prompt)" exit 0 ;; *) log_error "Unknown option: $1" exit 1 ;; esac done # ─── Detect Environment ─── SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" if [ -f "$PROJECT_DIR/.env" ]; then source "$PROJECT_DIR/.env" fi if [ -z "$POSTGRES_PASSWORD" ]; then POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-}" fi if [ -z "$POSTGRES_PASSWORD" ]; then read -s -p "Enter PostgreSQL password: " POSTGRES_PASSWORD echo "" fi # ─── Docker Mode ─── if [ "$DOCKER_MODE" = true ]; then log_info "Configuring PgBouncer for Docker Compose..." # Check if docker-compose.yml exists if [ ! -f "$PROJECT_DIR/docker-compose.yml" ]; then log_error "docker-compose.yml not found in $PROJECT_DIR" exit 1 fi # Check if PgBouncer service already exists if grep -q "pgbouncer" "$PROJECT_DIR/docker-compose.yml" 2>/dev/null; then log_warn "PgBouncer service already exists in docker-compose.yml" else log_info "Adding PgBouncer service to docker-compose.yml..." # Add PgBouncer service before the last line of services cat >> "$PROJECT_DIR/docker-compose.yml" << 'EOF' pgbouncer: image: bitnami/pgbouncer:latest container_name: leocrm-pgbouncer ports: - "6432:6432" environment: - POSTGRESQL_HOST=crm-postgres - POSTGRESQL_PORT=5432 - POSTGRESQL_USERNAME=leocrm - POSTGRESQL_PASSWORD=${POSTGRES_PASSWORD} - POSTGRESQL_DATABASE=crm_db - PGBOUNCER_POOL_MODE=transaction - PGBOUNCER_DEFAULT_POOL_SIZE=25 - PGBOUNCER_MAX_CLIENT_CONN=200 depends_on: - crm-postgres restart: unless-stopped EOF log_info "PgBouncer service added to docker-compose.yml" fi log_info "Docker PgBouncer configuration complete!" log_info "Run 'docker-compose up -d pgbouncer' to start PgBouncer" exit 0 fi # ─── Native Installation ─── log_info "Installing PgBouncer v${PGBOUNCER_VERSION}..." # Check if PgBouncer is already installed if command -v pgbouncer &>/dev/null; then log_info "PgBouncer is already installed: $(pgbouncer --version)" else # Install PgBouncer if command -v apt-get &>/dev/null; then apt-get update apt-get install -y pgbouncer elif command -v yum &>/dev/null; then yum install -y pgbouncer else log_error "Unsupported package manager. Install PgBouncer manually." exit 1 fi log_info "PgBouncer installed successfully" fi # ─── Create Configuration ─── log_info "Creating PgBouncer configuration..." PGBOUNCER_CONF_DIR="/etc/pgbouncer" mkdir -p "$PGBOUNCER_CONF_DIR" # Generate md5 password hash PG_MD5_HASH=$(echo -n "md5$(echo -n "${POSTGRES_PASSWORD}leocrm" | md5sum | cut -d' ' -f1)") # Create pgbouncer.ini cat > "${PGBOUNCER_CONF_DIR}/pgbouncer.ini" << INI [databases] leocrm = host=localhost port=5432 dbname=crm_db leocrm_test = host=localhost port=5432 dbname=leocrm_test [pgbouncer] listen_addr = 0.0.0.0 listen_port = ${PGBOUNCER_PORT} unix_socket_dir = /var/run/pgbouncer auth_type = md5 auth_file = ${PGBOUNCER_CONF_DIR}/userlist.txt pool_mode = ${POOL_MODE} default_pool_size = ${DEFAULT_POOL_SIZE} max_client_conn = ${MAX_CLIENT_CONN} max_db_connections = 50 server_idle_timeout = 600 server_lifetime = 3600 client_idle_timeout = 1800 query_timeout = 30 log_connections = 1 log_disconnections = 1 log_pooler_errors = 1 stats_period = 60 listen_backlog = 128 INI log_info "Created ${PGBOUNCER_CONF_DIR}/pgbouncer.ini" # Create userlist.txt cat > "${PGBOUNCER_CONF_DIR}/userlist.txt" << USERLIST "leocrm" "${PG_MD5_HASH}" "postgres" "${PG_MD5_HASH}" USERLIST log_info "Created ${PGBOUNCER_CONF_DIR}/userlist.txt" # Set proper permissions chmod 640 "${PGBOUNCER_CONF_DIR}/pgbouncer.ini" chmod 640 "${PGBOUNCER_CONF_DIR}/userlist.txt" chown -R pgbouncer:pgbouncer "$PGBOUNCER_CONF_DIR" 2>/dev/null || true # ─── Create Systemd Service ─── log_info "Creating systemd service..." cat > /etc/systemd/system/pgbouncer.service << 'SYSTEMD' [Unit] Description=PgBouncer PostgreSQL Connection Pooler After=network.target [Service] Type=forking User=pgbouncer ExecStart=/usr/sbin/pgbouncer -d /etc/pgbouncer/pgbouncer.ini ExecReload=/bin/kill -HUP $MAINPID ExecStop=/bin/kill -INT $MAINPID PIDFile=/var/run/pgbouncer/pgbouncer.pid Restart=always RestartSec=5 [Install] WantedBy=multi-user.target SYSTEMD log_info "Created systemd service" # ─── Start PgBouncer ─── log_info "Starting PgBouncer..." systemctl daemon-reload systemctl enable pgbouncer systemctl start pgbouncer # Wait for PgBouncer to start sleep 2 # Check status if systemctl is-active --quiet pgbouncer; then log_info "PgBouncer is running on port ${PGBOUNCER_PORT}" else log_error "PgBouncer failed to start. Check logs: journalctl -u pgbouncer" exit 1 fi # ─── Verify Connection ─── log_info "Verifying PgBouncer connection..." if command -v psql &>/dev/null; then if PGPASSWORD="$POSTGRES_PASSWORD" psql -h localhost -p "$PGBOUNCER_PORT" -U leocrm -d leocrm -c "SELECT 1 AS pgbouncer_test;" &>/dev/null; then log_info "PgBouncer connection verified successfully!" else log_warn "Could not verify PgBouncer connection. Check PostgreSQL credentials." fi fi # ─── Summary ─── echo "" echo "═══════════════════════════════════════════════" echo " PgBouncer Setup Complete" echo "═══════════════════════════════════════════════" echo "" echo " Configuration:" echo " Config file: ${PGBOUNCER_CONF_DIR}/pgbouncer.ini" echo " User list: ${PGBOUNCER_CONF_DIR}/userlist.txt" echo " Listen port: ${PGBOUNCER_PORT}" echo " Pool mode: ${POOL_MODE}" echo " Pool size: ${DEFAULT_POOL_SIZE}" echo "" echo " Commands:" echo " Status: systemctl status pgbouncer" echo " Restart: systemctl restart pgbouncer" echo " Reload: systemctl reload pgbouncer" echo " Stop: systemctl stop pgbouncer" echo "" echo " Monitoring:" echo " Pools: echo 'SHOW POOLS;' | psql -h localhost -p ${PGBOUNCER_PORT} -U leocrm -d pgbouncer" echo " Stats: echo 'SHOW STATS;' | psql -h localhost -p ${PGBOUNCER_PORT} -U leocrm -d pgbouncer" echo "" echo "═══════════════════════════════════════════════"