#!/bin/bash ### Copyright 1999-2024. Plesk International GmbH. # Script from Plesk KB article https://support.plesk.com/hc/en-us/articles/12376925289879 # Allows installing varnish webserver along with Plesk environment and configure domains to use it. # Requirements : bash 3.x, GNU coreutils, mysql client # Version : 1.0 # Constants and variables PATH=$PATH:/usr/bin:/usr/sbin:/bin varnishConfigSrc="https://raw.githubusercontent.com/plesk/kb-scripts/master/varnishmng/default.vcl" varnishConfig="/etc/varnish/default.vcl" MYSQL_PASSWORD=`cat /etc/psa/.psa.shadow` # Functions function sqlQuery(){ # Handling PSA queries with this one. MYSQL_PWD=$MYSQL_PASSWORD mysql -Ns -uadmin -D psa -e "$@" } function die(){ echo -e "\\e[31mERROR\\e[m: $*" >&2 exit 1 } function info(){ echo -e "\\e[32mINFO\\e[m: $*" } function isLiteSpeedInUse(){ # Checking the backend webserver unit status in order to see if it's powered with LiteSpeed or not. Exiting if it is. case "$osFamilyType" in "rhel") webServerInfo=$(systemctl status httpd 2>/dev/null | egrep -iE '(lsws|litespeed)') ;; "debian") webServerInfo=$(systemctl status apache2 2>/dev/null | egrep -iE '(lsws|litespeed)') ;; esac if [ ! -z "$webServerInfo" ] ; then die "Litespeed is in use on this server. The script is intended to be used with default Apache2(httpd)+Nginx webservers bundle." fi } function osFamilyDetect(){ # Detects OS family. Returns rather "rhel" or "debian" RH=("rhel" "redhat" "centos" "alma" "rocky" "cloudlinux") DE=("ubuntu" "debian") idLike=$(cat /etc/os-release |egrep -iE '(ID_LIKE|ID=)') # on debian there is no ID_LIKE constant in that file. Therefore, egrepping multiple patterns # Rhel-based checks for val in "${RH[@]}" ; do case "$idLike" in *"$val"*) osFamily="rhel" ;; esac done # Debian-based checks for val in "${DE[@]}" ; do case "$idLike" in *"$val"*) osFamily="debian" ;; esac done echo "$osFamily" } osFamilyType=$(osFamilyDetect) # Fetching this right away function isPoweredByCentOs(){ # CentOS 7 has a deprecated repository which only contains varnish 4, hence we will be using a packagecloud.io, as suggested here: https://varnish-cache.org/docs/6.0/installation/install.html#source-or-packages if [ "$osFamilyType" == "rhel" ] ; then centosRelease=$(yum info centos-release | grep "Version" | awk '{print $3}' 2>/dev/null) fi if [ "$centosRelease" == "7" ] ; then echo "1" # That's it, script is running on CentOS7 fi } function centOsRepoMng(){ # epel repo contains varnish 4.X which we're not going to use repoFile="/etc/yum.repos.d/varnish60lts.repo" if [ "$1" == "add" ] ; then mkdir /etc/yum.repos.d 2>/dev/null echo -ne "[varnish60lts] name=varnishcache_varnish60lts baseurl=https://packagecloud.io/varnishcache/varnish60lts/el/7/x86_64 repo_gpgcheck=1 gpgcheck=0 enabled=1 gpgkey=https://packagecloud.io/varnishcache/varnish60lts/gpgkey sslverify=0 metadata_expire=300" > "$repoFile" fi if [ "$1" == "del" ] ; then rm -rf "$repoFile" 2>/dev/null fi } function centOsSystemdTweak(){ # https proxy we're not going to use wants port 8443. That of course, we cannot afford. sed -i 's/8443/8444/' /lib/systemd/system/varnish.service && systemctl daemon-reload } function refreshPkgCache(){ case "$osFamilyType" in "rhel") yum makecache ;; "debian") apt-get update ;; *) ;; esac } function handleNeedRestart(){ # To avoid prompts on debian-based OS with the "needrestart" package. case $1 in "lock") if [ -d /etc/needrestart ] ; then mkdir /etc/needrestart/conf.d 2>/dev/null printf "\$nrconf{restart} = 'l';\n\$nrconf{kernelhints} = 0;\n" >> /etc/needrestart/conf.d/noPrompt.conf fi ;; "release") rm -rf /etc/needrestart/conf.d/noPrompt.conf 2>/dev/null ;; *) esac } function installPkg(){ # Varnish package installation. case "$osFamilyType" in "rhel") if [ `isPoweredByCentOs` == "1" ] ; then # Centos-specific section start refreshPkgCache centOsRepoMng add yum install -y $1 centOsRepoMng del centOsSystemdTweak systemctl enable varnish systemctl start varnish else # Centos-specific section end yum install -y $1 systemctl enable varnish systemctl start varnish fi ;; "debian") handleNeedRestart lock apt-get install -y $1 systemctl enable varnish systemctl start varnish handleNeedRestart release ;; *) ;; esac } function validateVarnishInstallation(){ # Checking if package's files are in place and systemd unit is running. Exiting otherwise. varnishExists=$(which varnishd) if [ -z "$varnishExists" ] ; then die "varnishd binary is missing. The package was not installed or corrupted. Exiting ..." fi varnishStatusCheck info "Varnish package has been installed and the unit is running." sePolicy } function varnishStatusCheck(){ systemctl is-active --quiet varnish varnishIsUp=$(echo $?) if [ "$varnishIsUp" != '0' ] ; then die "The systemd unit 'varnish' is down. Review it's status manually. 1. Make sure it is installed 2. Check configuration file consistency: /etc/varnish/default.vcl 3. Check the systemd unit status for possible errors with 'systemctl status varnish' " fi } function sePolicy(){ # Selinux tweak if [ ! -z `which getenforce` ] ; then seMode=$(getenforce) fi if [ "$seMode" == "Enforcing" ] ; then info "Applying Selinux policy for varnishd ... " setsebool -P varnishd_connect_any 1 systemctl restart varnish else info "Selinux is not in use, skipping ... " fi } function checkApacheBindings(){ # Returns "0" if apache is listening on localhost. Terminates script otherwise. if [[ `sqlQuery "select val from misc where param='apacheListenLocalhost'"` == 'true' ]] ; then echo "0" else if [[ `sqlQuery "select val from misc where param='apacheListenLocalhost'"` == 'false' ]] ;then die "Apache is not listening on localhost. This can be changed with the following command: plesk bin apache --listen-on-localhost true \nMind that this will cause recreation of apache2 and nginx vhost configuration files and might take a while." else if [[ -z `sqlQuery "select val from misc where param='apacheListenLocalhost'"` ]] ; then die "Failed to detect apache2 bindings. Either you're running unsupported version of Plesk or the request to PSA fails. Exiting ..." fi fi fi } function domainStatusCheck(){ if [[ ! -z `sqlQuery "select * from domains where name='$1'"` ]] ; then # Making sure domain exists true else die "Domain $1 does not exist, exiting ..." fi if [[ `sqlQuery "select status from domains where name='$1'"` != "0" ]] ; then # Domain is not suspended die "Domain is suspended, exiting ..." fi if [[ `sqlQuery "select htype from domains where name='$1'"` != "vrt_hst" ]] ; then # Domain has hosting type enabled die "Domain isn't configured for the website hosting. First switch hosting type to 'Website' ..." fi if [ `domainProxyModeCheck "$1"` != "1" ] ; then # Proxy mode is ON for the domain die "Domain does not have proxy mode enabled. Make sure to enable Proxy Mode in:\nDomains > $1 > Hosting > Apache & Nginx Settings" fi } function domainProxyModeCheck(){ proxyModeStatus=$(sqlQuery "select wssp.value from WebServerSettingsParameters wssp join domains d join dom_param dp where d.id=dp.dom_id and dp.param='webServerSettingsId' and dp.val=wssp.webServerSettingsId and wssp.name='nginxProxyMode' and d.name='$1'") if [ "$proxyModeStatus" == "true" ] ; then echo "1" else echo "0" fi } function deployVarnishConfig(){ cat /dev/null > $varnishConfig curl -sSLo $varnishConfig https://gist.githubusercontent.com/fevangelou/84d2ce05896cab5f730a/raw/79614fe6d417abaebf05abb623cc2e04941967db/for_Varnish_4.x_or_newer_default.vcl if [[ -z `grep "backend default" $varnishConfig` ]] ; then die "Configuration file wasn't downloaded properly. Exiting ..." else echo "Configuration /etc/varnish/default.vcl deployed." fi # Also perhaps it's a good idea to have that default.vcl stored in Plesk or my personal repo? } function sanityChecks(){ if [[ -z `sqlQuery "select * from misc limit 1" 2>/dev/null` ]] ; then die "SQL Connection failed. Make sure the PSA database is accessible and Plesk is operational. Exiting ..." fi if [ "$osFamilyType" != "debian" ] && [ "$osFamilyType" != "rhel" ] ; then # we've failed to detect OS family. Can't proceed. die "Failed to detect OS family type. Exiting..." fi isLiteSpeedInUse } function restartVarnish(){ systemctl restart varnish sleep 2 varnishStatusCheck } function reloadApache(){ if [ "$osFamilyType" == "debian" ] ; then systemctl reload apache2 else if [ "$osFamilyType" == "rhel" ] ; then systemctl reload httpd fi fi } function reloadNginx(){ systemctl reload nginx } function changeVarnishPort(){ sed -i "/.port =/c\ .port = \"$1\";" $varnishConfig } function changeVarnishIp(){ sed -i "/.host =/c\ .host = \"$1\";" $varnishConfig } function mngDomainRedirects(){ # $1 - domain name, $2 - true/false plesk bin domain -u $1 -ssl-redirect $2 1>/dev/null } function availableDomainsList(){ sqlQuery "select name from domains where htype='vrt_hst' and status='0'" } function addDomainNginxConf(){ domNginxConf="$(grep HTTPD_VHOSTS_D /etc/psa/psa.conf | awk {'print $2'})/system/$1/conf/vhost_nginx.conf" if [ -z `grep PLESK-VARNISH-BEGIN $domNginxConf 2>/dev/null` ] ; then echo -ne "\n#PLESK-VARNISH-BEGIN location ~ ^/.* { proxy_pass http://0.0.0.0:6081; 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;} #PLESK-VARNISH-END" >> $domNginxConf fi } function addDomainApacheConf(){ domApacheConf="$(grep HTTPD_VHOSTS_D /etc/psa/psa.conf |awk {'print $2'})/system/$1/conf/vhost.conf" if [ -z `grep PLESK-VARNISH-BEGIN $domApacheConf 2>/dev/null` ] ; then echo -ne "\n#PLESK-VARNISH-BEGIN# SetEnvIf X-Forwarded-Proto "https" HTTPS=on Header append Vary: X-Forwarded-Proto RewriteEngine on RewriteCond %{HTTPS} !=on RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC] RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] #PLESK-VARNISH-END#" >> $domApacheConf fi } function delDomainNginxConf(){ domNginxConf="$(grep HTTPD_VHOSTS_D /etc/psa/psa.conf | awk {'print $2'})/system/$1/conf/vhost_nginx.conf" sed -i '/PLESK-VARNISH-BEGIN/,/PLESK-VARNISH-END/d' $domNginxConf 2>/dev/null } function delDomainApacheConf(){ domApacheConf="$(grep HTTPD_VHOSTS_D /etc/psa/psa.conf |awk {'print $2'})/system/$1/conf/vhost.conf" sed -i '/PLESK-VARNISH-BEGIN/,/PLESK-VARNISH-END/d' $domApacheConf 2>/dev/null } function enableVarnishOnDomain(){ varnishStatusCheck domainStatusCheck $1 mngDomainRedirects $1 false addDomainApacheConf $1 addDomainNginxConf $1 reloadApache && reloadNginx } function disableVarnishOnDomain(){ domainStatusCheck $1 mngDomainRedirects $1 true delDomainNginxConf $1 delDomainApacheConf $1 reloadApache && reloadNginx } function menuDisableVarnishOnDomain(){ if [ `availableDomainsList |wc -l` -lt "35" ] ; then menuDomainSelect else read -p "Disabling varnish... Enter domain name: " domainName fi disableVarnishOnDomain "$selectedDomain" } function menuEnableVarnishOnDomain(){ if [ `availableDomainsList |wc -l` -lt "35" ] ; then menuDomainSelect else read -p "Enabling varnish... Enter domain name: " domainName fi enableVarnishOnDomain "$selectedDomain" } function menuDomainSelect(){ domList=$(availableDomainsList) readarray -t lines < <(echo "$domList") echo -e "List of active domains with enabled hosting, select one: " select selectedDomain in "${lines[@]}"; do [[ -n $selectedDomain ]] || { echo "Wrong input. Select valid domain." >&2; continue; } break done read -r selectedFromArray <<<"$selectedDomain" } function configureVarnish(){ deployVarnishConfig changeVarnishPort "7080" if [[ `checkApacheBindings` == "0" ]] ; then # Setting varnish to proxy content from 127.0.0.1 if apache in Plesk listening on it. changeVarnishIp "127.0.0.1" fi restartVarnish } function installVarnish(){ refreshPkgCache installPkg varnish validateVarnishInstallation configureVarnish } sanityChecks # Making sure the environment is ready function help(){ cat < Enables varnish cache on domain. Domain name is passed as extra argument. Example: ./varnishMng.sh --enable-cache example.com --disable-cache Disables varnish cache on domain. Domain name is passed as extra argument. Example: ./varnishMng.sh --disable-cache example.com HELP startupMenu } function startupMenu(){ info "Currently supported CMS by the script-provided varnish config are: WordPress 6.0+ Joomla 3.6+ with https://github.com/joomlaworks/url-normalizer installed." echo -ne " \n\nPlesk varnish management script 1) Install varnish 2) Enable varnish cache on domain 3) Disable varnish cache on domain 4) Help (information about argument for the script usage without interactive menu) 5) Exit\n" read -p "Select an option: " o case "$o" in 1) installVarnish ;; 2) menuEnableVarnishOnDomain ;; 3) menuDisableVarnishOnDomain ;; 4) help ;; 5) exit 0 ;; *) die "Wrong input. Exiting ..." esac } function nonInteractive(){ if [ "$1" == "--install" ] ; then installVarnish && exit 0 else if [ "$1" == "--enable-cache" ] ; then enableVarnishOnDomain "$2" && exit 0 else if [ "$1" == "--disable-cache" ] ; then disableVarnishOnDomain "$2" && exit 0 fi fi fi } # User-interaction starts here nonInteractive $@ startupMenu