#!/bin/bash # # Install Odoo 16 on Debian 12 with LetsEncrypt SSL certificate # Copyright 2023 Robert Fauls. Licensed under the GNU GPLv3 License. # Created by Rob Fauls 10/27/2022 # # Description: # This script automates the installation of Odoo 16 on a fresh Debian 12 system. # It configures Nginx as a reverse proxy and sets up Let's Encrypt SSL certificate. # # Usage: # - Run the script as a user with sudo privileges. # - Make sure you have a fresh install of Debian 12 with a domain name and accessible ports 80 and 443. # - Follow the prompts to enter the domain name and email address associated with the SSL certificate. # - The script will update the system, install prerequisites, install Odoo 16, configure Let's Encrypt, and set up Nginx. # # Dependencies: # - wget, gnupg2, certbot, python3-certbot-nginx # # Note: Please review the script and modify it to fit your specific requirements. # # Feel free to use and modify this script, ensuring that you contribute any improvements back to the source. # clear if [ "$EUID" -ne 0 ] then echo "This script must be run with sudo privileges. Exiting." exit 1 fi #If anything fails, exit the script. set -e # Set the log file path LOG_FILE="$(dirname "$0")/OdooInstall.log" # Redirect stdout to a log file exec >"$LOG_FILE" # Append both stderr and stdout to the log file and display stderr on the console exec 2> >(tee -a "$LOG_FILE" >&2) # Function to print messages to the terminal and log them to the file print_to_terminal() { # Print to the terminal echo "$@" > /dev/tty # Log to the file echo "$@" >> "$LOG_FILE" } # Get the current date and time current_date_time=$(date) echo "Beginning of the Odoo installation script log file. It is currently: "$current_date_time > $LOG_FILE print_to_terminal "SYSTEM REQUIREMENTS" print_to_terminal "Fresh install of Debian 12" print_to_terminal "Domain name" print_to_terminal "Public IP with ports 80 and 443 accessible (LetsEncrypt)" print_to_terminal "" print_to_terminal "Please be sure everything is properly configured." print_to_terminal "If you are missing the domain name or ports, LetsEncrypt and Nginx will not be properly configured" #Install prerequisites print_to_terminal "Making sure we have the basics installed before beginning..." DEBIAN_FRONTEND=noninteractive apt-get install -qq -y wget gnupg2 curl jq # Check the installation status if [ $? -ne 0 ]; then print_to_terminal "Error: Failed to install prerequisites. Exiting." exit 1 fi # Create an empty crontab for root if it doesn't exist if ! crontab -l -u root >/dev/null 2>&1; then echo | crontab -u root - fi ###START Check if user would like to update the script### read -rp "Would you like to check for an updated script? [y/n]: " response # Convert the response to lowercase for case-insensitive comparison response=${response,,} # Check if the user wants to check for an updated script if [[ $response == "y" || $response == "yes" ]]; then # Retrieve the latest version of the script curl -s -o odoo_install_update.sh https://code.flatironnetworks.com/RobFauls-Com/website-scripts/raw/branch/main/Odoo/Install-Odoo16-Debian12.sh # Calculate the hash values current_hash=$(md5sum "$0" | awk '{print $1}') updated_hash=$(md5sum odoo_install_update.sh | awk '{print $1}') # Compare the hash values if [[ $current_hash != $updated_hash ]]; then print_to_terminal "An update is available. Performing self-update..." # Replace the existing script with the updated script mv odoo_install_update.sh "$0" chmod +x "$0" # Run the updated script exec "$0" "$@" else print_to_terminal "The script is already up to date." fi else print_to_terminal "Skipping script update." fi ###START Unattended Upgrades### read -rp "Would you like to enable unattended upgrades for automatic security updates? [y/n]: " response # Convert the response to lowercase for case-insensitive comparison response=${response,,} if [[ $response == "y" || $response == "yes" ]]; then # Install unattended-upgrades package apt-get update >/dev/null 2>&1 apt-get install unattended-upgrades -y >/dev/null 2>&1 # Configure unattended-upgrades cat < /etc/apt/apt.conf.d/20auto-upgrades APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1"; EOT # Customize the configuration as needed cat < /etc/apt/apt.conf.d/50unattended-upgrades Unattended-Upgrade::Allowed-Origins { "\${distro_id}:\${distro_codename}-security"; "\${distro_id}:\${distro_codename}-updates"; "\${distro_id}:\${distro_codename}-proposed"; "\${distro_id}:\${distro_codename}-backports"; }; Unattended-Upgrade::Package-Blacklist { // Uncomment the following line to exclude specific packages from automatic upgrades // "package-name"; }; Unattended-Upgrade::AutoFixInterruptedDpkg "true"; Unattended-Upgrade::MinimalSteps "true"; Unattended-Upgrade::Mail "false"; EOT # Enable and start the unattended-upgrades service systemctl enable unattended-upgrades systemctl start unattended-upgrades print_to_terminal "Unattended upgrades have been enabled." else print_to_terminal "Unattended upgrades have not been enabled." fi ###FINISH Unattended Upgrades### ###BEGIN Gather user input### print_to_terminal "Let's collect some information about your environment" # Validate domain name while true; do print_to_terminal "Please enter your domain name:" read -r DOMAIN # Validate domain name using regular expression if [[ ! $DOMAIN =~ ^[a-zA-Z0-9]+([-.][a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$ ]]; then print_to_terminal "Invalid domain name. Please enter a valid domain name." else break fi done # Validate email address while true; do print_to_terminal "Please enter the email address you'd like to associate with the SSL certificate:" read -r EMAIL # Validate email address using regular expression if [[ ! $EMAIL =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then print_to_terminal "Invalid email address. Please enter a valid email address." else break fi done # Get the number of CPU cores minus one DEFAULT_CORES=$(( $(nproc) - 1 )) #Gather CPU cores information while true; do print_to_terminal "Please enter the number of CPU cores you'd like to make available to Odoo (default: $DEFAULT_CORES)." print_to_terminal "Default is # of system cores -1:" read -r CPU_CORES # If user input is empty, set it to default value if [[ -z $CPU_CORES ]]; then CPU_CORES=$DEFAULT_CORES fi # Validate the input to ensure it's a positive integer if ! [[ $CPU_CORES =~ ^[1-9][0-9]*$ ]]; then print_to_terminal "Invalid input. Please enter a positive integer for the number of CPU cores." else break fi done #Define max cron threads as # of workers, minus one. Minimum of 1. CRON_THREADS=$(( CPU_CORES - 1 )) if (( CRON_THREADS < 1 )); then CRON_THREADS=1 fi ###END Gather user input### #Make sure system is up to date and remove any unnecessary packages print_to_terminal "Checking for updates." apt-get update -qq >> "$LOG_FILE" 2>&1 if [ $? -ne 0 ]; then print_to_terminal "Error: Failed to update packages. Exiting." exit 1 fi print_to_terminal "Applying system updates." apt-get upgrade -qq -y >> "$LOG_FILE" 2>&1 if [ $? -ne 0 ]; then print_to_terminal "Error: Failed to install packages. Exiting." exit 1 fi print_to_terminal "Autoremoving unnecessary packages." apt-get autoremove -qq -y >> "$LOG_FILE" 2>&1 if [ $? -ne 0 ]; then print_to_terminal "Error: Failed to auto-remove packages. Exiting." exit 1 fi #Install wkhtmltopdf print_to_terminal "Installing wkhtmltopdf" print_to_terminal "Latest version as of 30OCT23: 0.12.6.1-3" wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.bookworm_amd64.deb >> "$LOG_FILE" 2>&1 apt-get install -qq ./wkhtmltox*bookworm_amd64.deb -y >> "$LOG_FILE" 2>&1 if [ $? -ne 0 ]; then print_to_terminal "Error: Failed to install wkhtmltopdf. Exiting." exit 1 fi #Install Odoo 16 print_to_terminal "Downloading Odoo 16." print_to_terminal "This may take a few minutes..." wget https://nightly.odoo.com/odoo.key >> "$LOG_FILE" 2>&1 cat odoo.key | gpg --dearmor | tee /etc/apt/trusted.gpg.d/odoo.gpg >/dev/null 2>&1 echo "deb http://nightly.odoo.com/16.0/nightly/deb/ ./" | tee -a /etc/apt/sources.list.d/odoo.list >/dev/null print_to_terminal "Installing Odoo 16." print_to_terminal "This also takes about a minute (depending on system speed)." apt-get update -qq >> "$LOG_FILE" apt-get install -qq odoo -y >> "$LOG_FILE" if [ $? -ne 0 ]; then print_to_terminal "Error: Failed to install Odoo. Exiting." exit 1 fi #Install LetsEncrypt and configure SSL certificates print_to_terminal "Installing LetsEncrypt and configuring SSL certificates." apt-get install certbot python3-certbot-nginx -y >> "$LOG_FILE" 2>&1 if [ $? -ne 0 ]; then print_to_terminal "Error: Failed to install Let's Encrypt and Certbot. Exiting." exit 1 fi systemctl stop nginx /usr/bin/certbot certonly --standalone -d ${DOMAIN} --preferred-challenges http --agree-tos -n -m ${EMAIL} --keep-until-expiring if [ $? -ne 0 ]; then print_to_terminal "Error: Failed to configure SSL certificates with Let's Encrypt. Exiting." exit 1 fi #Insert cron job for LetsEncypt refresh certs into crontab print_to_terminal "Configuring cron job to renew LetsEncrypt Certificates" crontab -l; echo "15 3 * * * /usr/bin/certbot renew --pre-hook 'systemctl stop nginx' --post-hook 'systemctl start nginx'" | sort -u | crontab - >/dev/null 2>&1 #Create Nginx configuration file: cat </etc/nginx/sites-available/odoo.conf #odoo server upstream odoo { server 127.0.0.1:8069; } upstream odoochat { server 127.0.0.1:8072; } map \$http_upgrade \$connection_upgrade { default upgrade; '' close; } # http -> https server { listen 80; server_name $DOMAIN; rewrite ^(.*) https://\$host$1 permanent; } server { listen 443 ssl; server_name $DOMAIN; proxy_read_timeout 720s; proxy_connect_timeout 720s; proxy_send_timeout 720s; # Add Headers for odoo proxy mode proxy_set_header X-Forwarded-Host \$host; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; proxy_set_header X-Real-IP \$remote_addr; # SSL parameters ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/$DOMAIN/chain.pem; ssl_session_timeout 30m; ssl_protocols TLSv1.2; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; # log access_log /var/log/nginx/odoo.access.log; error_log /var/log/nginx/odoo.error.log; # Redirect websocket requests to odoo gevent port location /websocket { proxy_pass http://odoochat; proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection \$connection_upgrade; proxy_set_header X-Forwarded-Host \$host; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; proxy_set_header X-Real-IP \$remote_addr; } # Redirect requests to odoo backend server location / { proxy_redirect off; proxy_pass http://odoo; } # common gzip gzip_types text/css text/scss text/plain text/xml application/xml application/json application/javascript; gzip on; } EOF #Create symlink to enable the site ln -s /etc/nginx/sites-available/odoo.conf /etc/nginx/sites-enabled/ if [ $? -ne 0 ]; then print_to_terminal "Error: Failed to create symlink for Nginx configuration. Exiting." exit 1 fi # Check health of Nginx configuration print_to_terminal "Checking health of NGINX configuration." if ! nginx -t; then print_to_terminal "Error: Nginx configuration test failed. Exiting." exit 1 fi #Modify odoo.conf to properly use Nginx, allow chat to work, and define the default addons path cat <>/etc/odoo/odoo.conf ;Certain values copied from: https://www.odoo.com/documentation/16.0/administration/install/deploy.html proxy_mode = True max_cron_threads = $CRON_THREADS workers = $CPU_CORES longpolling_port = 8072 addons_path = /var/lib/odoo/.local/share/Odoo/addons/16.0 ;Memory and CPU limits are commented out. ;Moft limit for virtual memory, after which new requests will spawn new workers. ;limit_memory_soft = 629145600 ;Maximum allowed virtual memory per Odoo worker, beyond which the worker is killed and recycled. ;limit_memory_hard = 1677721600 ;Maximum number of requests a worker can handle before it is recycled. ;limit_request = 8192 ;Maximum allowed CPU time per request. ;limit_time_cpu = 600 ;Maximum allowed real time per request. ;limit_time_real = 1200 ;regular XML-RPC xmlrpc = True xmlrpc_interface = xmlrpc_port = 8069 ;XML-RPC over SSL xmlrpcs = True xmlrpcs_interface = xmlrpcs_port = 8071 EOF #Restart Odoo and Nginx systemctl restart odoo systemctl restart nginx #Fix logged IP addresses in CloudFlare print_to_terminal "If you are using CloudFlare proxy (do not have DNS only selected), you will see CloudFlare IP Addresses" print_to_terminal "in your logs. If you are using the feature and would like to see the users' real IP, select yes below." print_to_terminal "NOTE: this function will download and execute new script from:" print_to_terminal "https://code.flatironnetworks.com/RobFauls-Com/website-scripts/raw/branch/main/Cloudflare/Fix_IP_Logging.sh" print_to_terminal "" print_to_terminal "You'll see some messages repeated. This is intentional, as I want to ensure this script stays updated." print_to_terminal "The other script is designed to be used on it's own, so please be patient with the extra prompts" print_to_terminal "" read -p "Would you like to download and execute the Cloudflare IP Logging fix script? (yes/no): " response if [[ $response == "y" || $response == "yes" ]]; then # Specify the URL of the script script_url="https://code.flatironnetworks.com/RobFauls-Com/website-scripts/raw/branch/main/Cloudflare/Fix_IP_Logging.sh" # Specify the local filename local_file="/tmp/Fix_IP_Logging.sh" #Download the script if command -v curl > /dev/null 2>&1; then curl -o $local_file $script_url elif command -v wget > /dev/null 2>&1; then wget -O $local_file $script_url else print_to_terminal "Error: Neither curl nor wget are available on this system. Please install one of them to continue." exit 1 fi # Make the script executable chmod +x $local_file # Execute the downloaded script $local_file print_to_terminal "Script executed successfully." else print_to_terminal "Continuing without downloading and executing the script." fi print_to_terminal print_to_terminal print_to_terminal "Odoo16 installation has completed!" print_to_terminal "If you're using CloudFlare, you may need to modify the SSL/TLS encryption mode." print_to_terminal "Without modifying the encryption mode, you may be unable to access the site" print_to_terminal print_to_terminal "Please go to https://"${DOMAIN}"/ to access and secure your installation."