Automatización de la instalación de Wordpress

Éste será el primero de una serie de posts sobre automatización en varios lenguajes de scripting. En este post expongo una automatización para instalación de WordPress, con un backend de MariaDB y PHP 7.0, en una distribución Debian Jessie. En próximas entregas desgranaré scripts de desinstalación, backup y restauración de la instalación realizada con este script.

Automatización

Las funciones del script son llamadas una vez durante la ejecución del script, excepto la función auxiliar iferror, que es llamada cuando un proceso devuelve un código de error y termina la ejecución del script devolviendo un mensaje a la consola.

 

Parámetros

El script utiliza variables que han de definirse en un archivo de texto plano. Si ejecutamos el script en una instalación limpia, la contraseña del administrador de la base de datos debemos escribirla en el archivo de configuración antes de ejecutar el script. Más adelante, el instalador de MariaDB nos pedirá una contraseña para el nuevo administrador y deberemos escribir la misma que hayamos puesto en el archivo. En caso de que ya exista la base de datos, en principio hay que usar la contraseña de root. El archivo tiene este formato:

#Domain for WordPress
#domain=
# Title for WordPress
#title=
# Mysql root password
#mysql_root_pass=
# Install path (without final slash)
#wp_path=
# Charset that will use WordPress
#wp_charset=
# WordPress locales
#wp_locale=
# WordPress user with administration rights
#wp_admin_user=
# Password for WordPress administrator
#wp_admin_password=
# WordPress administrator email
#wp_admin_email=
# Host of Mariadb
#wp_db_host=
# Database user for WordPress
#wp_db_user=
# Database user for WordPress password
#wp_db_password=
# Name of database for WordPress
#wp_db_name=
# User for wp cli. It's needed to execute wp cli.
#wpcli_user=

La variables las cargamos en el script con el comando source:

if [[ -e ./conf/config ]];then
 	source ./conf/config;
else
        iferror "First you need configure parameters"
fi

En caso de que el script no exista, se llama a la función iferror con un mensaje como parámetro. La función iferror es como sigue:

# function: iferror
# produces an exit code 1 with message

function iferror {
  if [[ $? -eq 1 ]]; then
    echo $1; exit 1;
  fi
}

Y creamos un usuario:

useradd $wpcli_user;

 

Fuentes

Añadimos los paquetes de PHP7.0, ya que en los repositorios de Debian Jessie sólo existen versiones anteriores.

# function  set_php7_sources
# add php7 repository to sources.list

set_php7_sources() {
    if ! (grep -qs "dotdeb" /etc/apt/sources.list); then
        echo "deb http://packages.dotdeb.org jessie all" \
				 >> /etc/apt/sources.list;
    fi
    if ! [[ -e /tmp/dotdeb.gpg ]]; then
        wget https://www.dotdeb.org/dotdeb.gpg && apt-key add dotdeb.gpg;
   fi
   install_dependencies
}

Comprobamos que el repositorio no esté en el sources.list y en ese caso el script añade la fuente. La segunda condición comprueba si ya ha sido descargada la clave gpg del repositorio. En caso, contrario la descarga y la añade al anillo de claves. A continuación, llama a la siguiente función.

# function: install_dependencies
# Install needed dependencies

function install_dependencies {
    apt-get -y install sudo mariadb-server mariadb-client \
    php7.0-mysql php7.0-fpm nginx nginx-extras php7.0 \
    && install_WP_cli \
    && if ! ( grep -qs $sudoers_root /etc/sudoers ); then
           echo $sudoers_root >> /etc/sudoers;
       fi
}

Instala todas las dependencias necesarias y añade root como usuario sudo. De entrada, parece absurdo añadir al administrador del sistema como un nuevo administrador, pero no lo es tanto cuando el programa WP-CLI, que llamamos a partir de la función install_WP_cli para instalar Worpress, no permite ser lanzado como root. En cambio, root puede otorgarle permisos a otro usuario, para lo root debe estar en la lista de sudoers

La función install_dependecies llamó a la función install_WP_cli:

# function: install_WP_cli
# Install command line interface for wordpress

function install_WP_cli {
  wget https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \
    || iferror "Failed. WP command line interface not installed.";
  chmod +x wp-cli.phar;
  mv wp-cli.phar /usr/local/bin/wp \
    || iferror "Failed. /usr/local/bin/wp not created";
  
  if ! (grep -qs $sudoers_wpcli /etc/sudoers); then
       	echo $sudoers_wpcli >> /etc/sudoers
  fi
  install_WP
}

Descargamos las fuentes de WP-CLI, le asignamos permisos de ejecución y la renombramos a uno de los directorios de variable de entorno PATH para que pueda WP-CLI sea ejecutado llamando al comando wp. Añade, además, al usuario wpcli a sudoers con estos permisos:

sudoers_wpcli="wpcli ALL=(www-data) NOPASSWD: /usr/local/bin/wp"

De este modo, wpcli puede ejecutar /usr/local/bin/wp sin introducir password con los permisos del usuario www-data, el del servidor web.  El útlimo paso es la llamada a la función install_WP.

 

MariaDB

La siguiente función crea la base de datos para WordPress:

# function: create_database
# create wordpress data base in mardiadb

function create_database {
     mysql -uroot -p$mysql_root_pass -e "drop database if exists $wp_db_name; create \
     database $wp_db_name" \
     || iferror "Failed. Database not created";
}


Aquí accede a la base de datos con el usuario root de MariaDB y crea una nueva base de datos para ser usada por WordPress. Si la base de datos no es creada, sale del script.

Creamos un usuario para la base de datos y le otorgamos privilegios:

# function: create_and_grant_user
# create user for wordpress and grant all privileges

function create_and_grant_user {
  mysql -uroot -p$mysql_root_pass -e " grant all privileges on $wp_db_name.* to \
  '$wp_db_user'@'localhost' identified by '$wp_db_password';"
}

Instalación de WordPress

En este punto es donde está la «chicha» del script. Expongo la función y a continuación explico qué hace:

# function: install_WP version path()
# install wordpress version at a given path

function install_WP {
  prefix=wp$RANDOM;
  if ! $(sudo -u $wpcli_user -- wp core is-installed); then
     create_database;
     create_and_grant_user;

    mkdir -p $wp_path && chmod -R 777 $wp_path && chown -R \
    www-data:www-data $wp_path;

    sudo -u $wpcli_user -- wp core download --locale=$wp_locale \
    --path=$wp_path \
    || iferror "Wordpress not downloaded";

    if [ -e $wp_path/wp-config.php ];  then
      rm $wp_path/wp-config.php;
    fi

    sudo -u $wpcli_user --  wp core config --dbname=$wp_db_name \
    --dbuser=$wp_db_user --dbpass=$wp_db_password --dbhost=$wp_host  \
    --dbprefix=$prefix --locale=$wp_locale --path=$wp_path \
    || iferror "Wordpress not configured" ;

    sudo -u $wpcli_user -- wp core install \
    --url=$domain --title="$title" --path=$wp_path --admin_user=$wp_admin_user \
    --admin_password=$wp_admin_password --admin_email=$wp_admin_email \
    --path=$wp_path \
    || iferror "Wordpress not installed";

    chmod -R 775 $wp_path;
    nginx_create_site
  fi
}

En la línea 5 se define el prefijo de las tablas de WordPress. Por motivos de seguridad, no utilizo el prefijo por defecto, sino que a la cadena de texto «wp» le añado un numero entero aleatorio.

En la línea 6, el condicional comprueba que WP-cli esté instalado.

Antes de instalar WordPress, llama a las funciones create_database y create_and_grant_user mostradas anteriormente.

Las líneas 10 y 11, crean el directorio raíz para la instancia de WordPress y le cambia permisos y propietarios. El permiso 777 se será cambiado de nuevo al teminar la ejecución de está función.

Línea 13, descarga la última versión de WordPress

Línea 17, comprueba que exista el archivo wp-config.php. Si es el caso (y suele serlo) lo borra para crear uno nuevo en el siguiente paso.

De las líneas 21 a 24, crea un archivo wp-config.php con parámetros definidos en el archivo de configuración.

De las líneas 26 a 30, instala Worpdress con los parámetros que siempre requiere en toda instalación.

Línea 32, modifica los permisos del directorio a 755

Y la línea 33, llama a una función para parsear un archivo de configuración para un host virtual en Nginx.

Nginx

He tomado un archivo de configuracion de Nginx de ejemplo de una publicación para usarlo como plantilla. En todo caso, podéis (y deberíais) utilizar vuestra propia configuración en un servidor en producción. El modo de parsear el archivo será siempre el mismo.

# nginx.available

server {
  listen 80;
  root ROOTPATH;
  index index.php index.html index.htm;

  server_name DOMAIN;

        location / {
        	try_files $uri $uri/ /index.php?q=$uri&$args;
  }

  error_page 404 /404.html;
        location ~* wp-config.php {
                deny all;
        }
  location = /favicon.ico {
    log_not_found off;
    access_log off;
  }

  location = /robots.txt {
    allow all;
    log_not_found off;
    access_log off;
  }

  error_page 500 502 503 504 /50x.html;
  location = /50x.html {
    root /usr/share/nginx/html;
  }

  location ~ \.php$ {
#NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
    fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
    fastcgi_pass_header Set-Cookie;
    fastcgi_pass_header Cookie;
    fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_split_path_info ^(.+.php)(/.+)$;
    fastcgi_param  PATH_INFO $fastcgi_path_info;
    fastcgi_param  PATH_TRANSLATED    $document_root$fastcgi_path_info;
    fastcgi_intercept_errors on;
    fastcgi_cache_valid 404 60m;
    fastcgi_cache_valid 200 60m;
    fastcgi_cache_valid 304 60m;
    fastcgi_max_temp_file_size 60m;
    fastcgi_cache_use_stale updating;
    fastcgi_index index.php;
    include fastcgi_params;

  }

    set $cache_uri $request_uri;
            # POST requests and URLs with a query string should always go to PHP
    if ($request_method = POST) {
        set $cache_uri 'null cache';
    }
    if ($query_string != "") {
        set $cache_uri 'null cache';
    }

    # Don't cache URIs containing the following segments
    if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php |sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)") {
        set $cache_uri 'null cache';
    }
    # Don't use the cache for logged-in users or recent commenters
    if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") {
        set $cache_uri 'null cache';
    }
    location ~*\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
        expires max;
        log_not_found off;
        access_log off;
    }


}

Esta plantilla será parseada por el script y atención a las líneas 5 y 8: contienen respectivamente las cadenas ROOT y DOMAIN. Ambas serán reemplazadas por los valores adecuados a la instalación. ROOT será el directorio raíz de WordPress y DOMAIN el nombre de dominio.

# function: nginx_create_site
# Configure site for nginx

function nginx_create_site {
  if [ -e ./nginx/nginx.available ]; then
    sed -e "s|ROOTPATH|$wp_path|g" \
    -e "s|DOMAIN|$domain|g" ./nginx/nginx.available > \
    /etc/nginx/sites-available/$domain.conf \
    || iferror "Site not available";
  else
    iferror "nginx.available does not exists";
  fi
  nginx_enable_site
}

Comprobamos que exista la plantilla llamada nginx.available, entonces reemplaza los patrones ROOT y DOMAIN con la ayuda del comando sed y escribe el archivo en sites-available. Sigue la ejecución llamando a la función nginx-enable-site.

# function: nginx_enable_site
# Enable site in Nginx

function nginx_enable_site {
  if [[ -e /etc/nginx/sites-enabled/default ]]; then
    rm -f /etc/nginx/sites-enabled/default;
  fi
  if [[ -e /etc/nginx/sites-enabled/$domain ]]; then
        rm -f /etc/nginx/sites-enabled/$domain
  fi
  ln -s /etc/nginx/sites-available/$domain.conf /etc/nginx/sites-enabled/. \
  || iferror "sites-enabled/$domain not created";
  systemctl reload nginx;
}

Desahibilitamos el servidor por defecto de Nginx y también, en caso de existir, una versión antigua del archivo del dominio que estamos instalando. Podría ser que exista si anteriormente hemos ejecutado una instalación fallida. Por último, habilitar el servidor virtual y Nginx recarga los servidores.

Verificación de root

El script debe ejecutare como root. Para comprobar que, efectivamente, sea así, al comienzo de la ejecución del script:

if [ "$(id -u)" != "0" ]; then
  echo "This script must be run as root" 1>&2
  exit 1
fi

Inicio

Esta función actualiza los repositorios, instala sudo y lanza todo el script.

function start {
  apt-get update && apt-get -y install sudo && set_php7_sources
}

Y se llama a start al final de script.

Código fuente

El script no ha sido explicado exactamente en el mismo orden en que se encuentran las funciones escritas en el código fuente. Creo que así se facilita la compresión de las partes para entender el script al completo. El script está en mi github y podéis utilizarlo tal cual está en una Debian Jessie. Es posible que, tras la explicación, se vea más claro visto en conjunto:

https://github.com/gustavomrfz/automate_wordpress

Conclusión

Sin duda, este script es unos de los muchos posibles. Muchos otros modos de automatizar la instalación serán mejores; otros solo serán diferentes. Creo que sólo automatizando procesos es como puedo asegurar que he entendido lo que ocurre cuando realizo, por ejemplo, una instalación de WordPress. Es un gozo, y un ahorro de tiempo, instalar prácticamente editando un archivo de parámetros y poco más. En breve publicaré un script explicando la desintalación automatizada de WordPress, aunque ya podéis ver el script en el github.

Cualquier comentario sensato o sugerencia serán bienvenidos.

Saludos 🙂

Este post forma parte de la serie

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *