#!/bin/bash ################################################################################ # Chatwoot Installation Script for Ubuntu # Supports: Ubuntu 20.04 LTS, 22.04 LTS, 24.04 LTS # Description: Automated installation of Chatwoot on Ubuntu # Usage: sudo bash chatwoot_install.sh ################################################################################ set -eu -o pipefail # Color codes for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Variables CHATWOOT_USER="chatwoot" CHATWOOT_HOME="/home/chatwoot" CHATWOOT_APP_PATH="${CHATWOOT_HOME}/chatwoot" RAILS_ENV="production" RAILS_LOG_DIR="/var/log/chatwoot" LOG_FILE="/var/log/chatwoot-setup.log" # Check if running as root if [ "$EUID" -ne 0 ]; then echo -e "${RED}This script must be run as root (use: sudo bash chatwoot_install.sh)${NC}" exit 1 fi # Functions log_info() { echo -e "${BLUE}[INFO]${NC} $1" | tee -a "$LOG_FILE" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" | tee -a "$LOG_FILE" } log_error() { echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a "$LOG_FILE" } # Initialize log file mkdir -p "$(dirname "$LOG_FILE")" touch "$LOG_FILE" log_info "Starting Chatwoot installation script" # Function to check command existence command_exists() { command -v "$1" >/dev/null 2>&1 } # Function to detect Ubuntu version detect_ubuntu_version() { if [ -f /etc/os-release ]; then . /etc/os-release UBUNTU_VERSION="$VERSION_ID" UBUNTU_CODENAME="$VERSION_CODENAME" log_info "Detected Ubuntu version: $UBUNTU_VERSION ($UBUNTU_CODENAME)" else log_error "Unable to detect Ubuntu version" exit 1 fi } # Function to update system update_system() { log_info "Updating system packages..." apt-get update -qq apt-get upgrade -y -qq log_success "System packages updated" } # Function to install dependencies install_dependencies() { log_info "Installing system dependencies..." local packages=( "curl" "wget" "git" "build-essential" "libssl-dev" "libreadline-dev" "zlib1g-dev" "libsqlite3-dev" "libxml2-dev" "libxslt1-dev" "libcurl4-openssl-dev" "postgresql" "postgresql-contrib" "redis-server" "nginx" "certbot" "python3-certbot-nginx" "nodejs" "npm" ) for package in "${packages[@]}"; do if ! dpkg -l | grep -q "^ii $package"; then log_info "Installing $package..." apt-get install -y -qq "$package" || log_warning "Failed to install $package" fi done log_success "Dependencies installed" } # Function to setup Ruby setup_ruby() { log_info "Setting up Ruby environment..." # Install RVM if ! command_exists rvm; then log_info "Installing RVM..." gpg2 --keyserver hkp://keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB 2>/dev/null || true curl -sSL https://get.rvm.io | bash -s stable >/dev/null 2>&1 source /etc/profile.d/rvm.sh fi # Install Ruby log_info "Installing Ruby 3.2..." source /etc/profile.d/rvm.sh rvm install ruby-3.2.0 >/dev/null 2>&1 || log_warning "Ruby installation may have issues" rvm use ruby-3.2.0 --default >/dev/null 2>&1 # Install Bundler gem install bundler --quiet log_success "Ruby environment setup complete" } # Function to create chatwoot user and directories setup_chatwoot_user() { log_info "Setting up Chatwoot user and directories..." if ! id "$CHATWOOT_USER" &>/dev/null; then useradd -m -s /bin/bash -d "$CHATWOOT_HOME" "$CHATWOOT_USER" log_info "Created chatwoot user" fi # Create necessary directories mkdir -p "$RAILS_LOG_DIR" chown -R "$CHATWOOT_USER:$CHATWOOT_USER" "$RAILS_LOG_DIR" chmod 755 "$RAILS_LOG_DIR" log_success "Chatwoot user and directories created" } # Function to setup PostgreSQL setup_postgresql() { log_info "Setting up PostgreSQL database..." # Generate secure password local db_password=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 20; echo '') # Start PostgreSQL systemctl start postgresql systemctl enable postgresql # Create database and user sudo -u postgres psql <&1 | tee -a "$LOG_FILE" else log_warning "Chatwoot directory already exists" fi cd "$CHATWOOT_APP_PATH" # Checkout latest stable version log_info "Checking out latest release..." sudo -u "$CHATWOOT_USER" git fetch --tags 2>&1 | tee -a "$LOG_FILE" # Setup environment file log_info "Setting up environment configuration..." if [ ! -f "$CHATWOOT_APP_PATH/.env" ]; then sudo -u "$CHATWOOT_USER" cp "$CHATWOOT_APP_PATH/.env.example" "$CHATWOOT_APP_PATH/.env" fi # Update .env file with database credentials sudo -u "$CHATWOOT_USER" sed -i 's|POSTGRES_HOST=localhost|POSTGRES_HOST=localhost|g' "$CHATWOOT_APP_PATH/.env" sudo -u "$CHATWOOT_USER" sed -i 's|POSTGRES_USERNAME=postgres|POSTGRES_USERNAME=chatwoot|g' "$CHATWOOT_APP_PATH/.env" sudo -u "$CHATWOOT_USER" sed -i 's|POSTGRES_PASSWORD=|POSTGRES_PASSWORD=YOUR_DB_PASSWORD|g' "$CHATWOOT_APP_PATH/.env" log_success "Chatwoot repository cloned and configured" } # Function to install Ruby gems install_gems() { log_info "Installing Ruby gems (this may take a while)..." cd "$CHATWOOT_APP_PATH" source /etc/profile.d/rvm.sh sudo -u "$CHATWOOT_USER" bash -c "cd $CHATWOOT_APP_PATH && rvm use ruby-3.2.0 && bundle install --quiet" 2>&1 | tee -a "$LOG_FILE" log_success "Ruby gems installed" } # Function to compile assets compile_assets() { log_info "Compiling assets (this may take a while)..." cd "$CHATWOOT_APP_PATH" source /etc/profile.d/rvm.sh sudo -u "$CHATWOOT_USER" bash -c "cd $CHATWOOT_APP_PATH && rvm use ruby-3.2.0 && rake assets:precompile RAILS_ENV=production NODE_OPTIONS='--max-old-space-size=4096 --openssl-legacy-provider'" 2>&1 | tee -a "$LOG_FILE" log_success "Assets compiled successfully" } # Function to setup systemd services setup_systemd_services() { log_info "Setting up systemd services..." # Copy service files cp "$CHATWOOT_APP_PATH/deployment/chatwoot-web.1.service" /etc/systemd/system/ cp "$CHATWOOT_APP_PATH/deployment/chatwoot-web.target" /etc/systemd/system/ cp "$CHATWOOT_APP_PATH/deployment/chatwoot-worker.1.service" /etc/systemd/system/ cp "$CHATWOOT_APP_PATH/deployment/chatwoot-worker.target" /etc/systemd/system/ # Update service files for correct user and paths sed -i "s|User=chatwoot|User=$CHATWOOT_USER|g" /etc/systemd/system/chatwoot-web.1.service sed -i "s|User=chatwoot|User=$CHATWOOT_USER|g" /etc/systemd/system/chatwoot-worker.1.service sed -i "s|WorkingDirectory=/home/chatwoot/chatwoot|WorkingDirectory=$CHATWOOT_APP_PATH|g" /etc/systemd/system/chatwoot-web.1.service sed -i "s|WorkingDirectory=/home/chatwoot/chatwoot|WorkingDirectory=$CHATWOOT_APP_PATH|g" /etc/systemd/system/chatwoot-worker.1.service systemctl daemon-reload systemctl enable chatwoot-web.target systemctl enable chatwoot-worker.target log_success "Systemd services configured" } # Function to start services start_services() { log_info "Starting Chatwoot services..." systemctl start chatwoot-web.target systemctl start chatwoot-worker.target # Wait for services to start sleep 5 if systemctl is-active --quiet chatwoot-web.target && systemctl is-active --quiet chatwoot-worker.target; then log_success "Chatwoot services started successfully" else log_error "Failed to start Chatwoot services" log_info "Check logs: journalctl -u chatwoot-web.target -f" exit 1 fi } # Function to setup Nginx setup_nginx() { log_info "Configuring Nginx as reverse proxy..." # Create Nginx configuration cat > /etc/nginx/sites-available/chatwoot <<'EOF' server { listen 80; server_name _; # Point upstream to Chatwoot App Server set $upstream 127.0.0.1:3000; # Nginx strips out underscore in headers by default # Chatwoot relies on underscore in headers for API underscores_in_headers on; location / { proxy_pass http://$upstream; 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_read_timeout 90; proxy_buffering off; } } EOF # Enable site ln -sf /etc/nginx/sites-available/chatwoot /etc/nginx/sites-enabled/chatwoot rm -f /etc/nginx/sites-enabled/default # Test and reload Nginx if nginx -t >/dev/null 2>&1; then systemctl restart nginx log_success "Nginx configured and restarted" else log_error "Nginx configuration error" nginx -t exit 1 fi } # Function to setup SSL with Let's Encrypt setup_ssl() { local domain="$1" local email="$2" log_info "Setting up SSL certificate for $domain..." certbot certonly --nginx -d "$domain" -m "$email" --agree-tos --non-interactive 2>&1 | tee -a "$LOG_FILE" # Update Nginx configuration for HTTPS cat > /etc/nginx/sites-available/chatwoot </dev/null 2>&1; then systemctl restart nginx log_success "SSL certificate installed and Nginx configured" else log_error "Nginx configuration error" exit 1 fi } # Main installation flow main() { log_info "====== Chatwoot Installation Started ======" # Detect Ubuntu version detect_ubuntu_version # Check requirements if ! command_exists curl; then log_info "Installing curl first..." apt-get update -qq && apt-get install -y -qq curl fi # Installation steps update_system install_dependencies setup_chatwoot_user setup_postgresql setup_redis setup_ruby setup_chatwoot install_gems compile_assets setup_systemd_services start_services setup_nginx # Summary log_success "====== Chatwoot Installation Complete ======" echo "" echo -e "${GREEN}Chatwoot installation completed successfully!${NC}" echo "" echo "Access your Chatwoot instance at:" echo -e "${BLUE} http://$(hostname -I | awk '{print $1}'):3000${NC}" echo "" echo "To configure domain and SSL, run:" echo -e "${BLUE} sudo bash chatwoot_install.sh --ssl --domain yourdomain.com --email your@email.com${NC}" echo "" echo "Important next steps:" echo "1. Update .env file with correct database password" echo "2. Run database migrations: cd $CHATWOOT_APP_PATH && bundle exec rake db:migrate RAILS_ENV=production" echo "3. Create admin user and complete initial setup via web interface" echo "" echo "For logs, check: journalctl -u chatwoot-web.target -f" echo "" } # Parse command line arguments CONFIGURE_SSL=false DOMAIN="" EMAIL="" while [[ $# -gt 0 ]]; do case $1 in --ssl) CONFIGURE_SSL=true shift ;; --domain) DOMAIN="$2" shift 2 ;; --email) EMAIL="$2" shift 2 ;; *) echo "Unknown option: $1" exit 1 ;; esac done # Run main installation main # Setup SSL if requested if [ "$CONFIGURE_SSL" = true ]; then if [ -z "$DOMAIN" ] || [ -z "$EMAIL" ]; then log_error "Domain and email are required for SSL setup" exit 1 fi setup_ssl "$DOMAIN" "$EMAIL" fi