skip to Main Content

Hope y’all are enjoying the holidays. I am attempting an automated installation of wordpress on my Linux VM using ansible. To that end, I have written this ansible piece of code that tries to mimic the official ubuntu guide.

Here is the code:

- name: "Installing wordpress dependencies"
  hosts: all
  become: True
  gather_facts: True
  vars:
    get_installer: 'curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php || /bin/true'
    get_signature: 'curl -sS https://composer.github.io/installer.sig'
  tasks:
        - name: "Update repository"
          apt: 
              update_cache: "yes"
        - name: "Installing requirements"
          apt:
            name:
                 - "curl"
                 - "php"
                 - "php-cli"
                 - "gnupg"
                 - "unzip"
                 - "mysql-server"
                 - "php-fpm"
                 - "php-mysql"
                 - "apache2"
                 - "ghostscript"
                 - "libapache2-mod-php"
                 - "php-bcmath"
                 - "php-curl"
                 - "php-imagick"
                 - "php-intl"
                 - "php-json"
                 - "php-mbstring"
                 - "php-xml"
                 - "php-zip"
            state: present
        - name: Populate service facts
          ansible.builtin.service_facts:
        - name: Print service facts
          ansible.builtin.debug:
            var: ansible_facts.services
        - name: "stopping nginx if running"
          service:
            name: nginx
            state: stopped
          when: "'nginx' in ansible_facts.services"
        - name: "remove nginx if installed"
          apt:
            name:
                - "nginx"
            state: absent
        - name: stop Mysql
          service:
            name: mysql
            state: stopped
          when: "'mysql' in ansible_facts.services"
        - name: stop apache2
          service:
            name: apache2
            state: stopped
          when: "'apache2' in ansible_facts.services"

- name: Installing wordpress through source
  hosts: all
  become: True
  gather_facts: False
  vars:
    wprootdir: "/srv/www/wordpress"
  tasks:
    - name: checking if wp src dir exists
      stat:
        path: "{{ wprootdir }}"
      register: dir_details
    - name: delete existing wordpress source files
      become_user: www-data
      no_log: True
      file:
        #path: "{{ item.path }}"
        #recurse: True
        path: "{{ wprootdir }}"
        state: absent
      #with_items: "{{ path_list.files }}"
    - name: creating /var/www for wordpress source
      file:
        #path: "'{{ wp-root-dir }}' + 'wordpress'" 
        path: "/srv/www/wordpress" 
        recurse: yes
        state: directory
        owner: www-data
        mode: '0755'  
    - name: downloading and extracting wordpress source
      shell:
        cmd: "curl https://wordpress.org/latest.tar.gz | sudo -u www-data tar zx -C /srv/www"
      register: status
    - fail:
        msg: "Unable to download or extract wordpress source"
      when: (status.rc != 0)

- name: Configuring apache for wordpress
  hosts: all
  become: True
  gather_facts: False
  vars:
    wprootdir: "/srv/www/wordpress"
    wpconffile: "/etc/apache2/sites-available/wordpress.conf"
  tasks:
    - name: deleting the file if it exists
      file:
        path: "{{ wpconffile }}"
        state: absent
    - name: creating wordpress conf file
      file:
        path: "{{ wpconffile }}"
        state: touch
        owner: www-data
    - name: populating wordpress conf file
      template:
        src: apache2.j2
        dest: "{{ wpconffile }}"
    - name: enabling the site
      shell:
        cmd: "a2ensite wordpress"
    - name: enable URL rewriting
      shell:
        cmd: "a2enmod rewrite"
    - name: disable default "it works" site
      shell:
        cmd: "a2dissite 000-default"
    - name: restart apache2
      service:
        name: apache2
        state: reloaded

- name: Configuring database
  hosts: all
  become: True
  gather_facts: True
  #gather_facts: yes
  vars:
    mysql_port: 3306
    mysql_socket: /var/run/mysqld/mysqld.sock
    mysql_superuser: root
    mysql_superuser_home: "{% if mysql_superuser == 'root' %}/root{% else %}/home/{{ mysql_superuser }}{% endif %}"
    mysql_superuser_password: SuperUserPwd
    mysql_wordpress_password: WordPressPwd
    http_port: 80  
  tasks:
    - name: Installing PyMySql through pip
      pip:
        name: PyMySql
        state: present
    - name: ensure mysql is running and starts on boot
      service:
        name: mysql
        state: started
        enabled: True
          
    - name: Removes anonymous user account for localhost
      community.mysql.mysql_user:
        name: ''
        state: absent
        login_user: root
        login_password: ""
        login_unix_socket: "{{ mysql_socket }}"
      when: ansible_local.mysqlinfo is undefined      

    - name: adding a password for root user
      mysql_user:
        # Update the superuser to have all grants and a password
        name: "{{ mysql_superuser }}"
        host: localhost
        password: "{{ mysql_superuser_password }}"
        priv: "*.*:ALL,GRANT"
        # Login *as root* to perform this change, even though you might
        # be altering the root user itself
        login_user: root
        login_password: ""
        login_port: "{{ mysql_port }}"
        login_host: localhost
        login_unix_socket: "{{ mysql_socket }}"
        # As a good measure,have ansible check whether an implicit login
        # is possible first
        check_implicit_admin: yes
      when: ansible_local.mysqlinfo is undefined      
    - name: "Create custom fact directory"
      file:
        path: "/etc/ansible/facts.d"
        state: "directory"
        recurse: yes
      when: ansible_local.mysqlinfo is undefined      
    - name: "record mysql info in custom fact"
      template:
        src: mysqlinfo.j2
        dest: /etc/ansible/facts.d/mysqlinfo.fact
        mode: 0644
      when: ansible_local.mysqlinfo is undefined      
    - name: "re-run setup to use custom facts"
      setup:
        filter: ansible_local
      when: ansible_local.mysqlinfo is undefined      
    - debug:
        msg:
          - "mysqlinfo is {{ ansible_local.mysqlinfo }}"
      when: ansible_local.mysqlinfo is defined
        
        #- name: Create system-wide mysql configuration file
        #template:
        #src: mysql_sys.cnf.j2
        #dest: /etc/my.cnf

        #- name: Create mysql configuration file for `{{ mysql_superuser }}`
        #template:
        #src: mysql_superuser.cnf.j2
        #dest: "{{ mysql_superuser_home }}/.my.cnf"

    - name: create database wordpress
      mysql_db: 
        db: wordpress
        state: present
        login_user: "{{ ansible_local.mysqlinfo.mysql_superuser }}"
        login_password: "{{ ansible_local.mysqlinfo.mysql_superuser_password }}"
        login_unix_socket: "{{ mysql_socket }}"
      when: ansible_local.mysqlinfo is defined

    - name: Create database user 'wordpress' with all database privileges
      community.mysql.mysql_user:
        name: wordpress
        password: "{{ mysql_wordpress_password }}"
        login_user: "{{ ansible_local.mysqlinfo.mysql_superuser }}"
        login_password: "{{ ansible_local.mysqlinfo.mysql_superuser_password }}"
        priv: '*.*:ALL'
        state: present
      when: ansible_local.mysqlinfo is defined

    - name: Flush privileges
      mysql_query:
        login_db: wordpress
        login_user: "{{ ansible_local.mysqlinfo.mysql_superuser }}"
        login_password: "{{ ansible_local.mysqlinfo.mysql_superuser_password }}"
        login_unix_socket: "{{ mysql_socket }}"
        query: FLUSH PRIVILEGES 

     # UFW Configuration
    - name: "UFW - Allow HTTP on port {{ http_port }}"
      ufw:
        rule: allow
        port: "{{ http_port }}"
        proto: tcp
      notify:
      - Restart Mysql
      tags: [ system ]

  handlers:
    - name: Restart Mysql
      service:
        name: mysql
        state: restarted
    - name: Restart Apache2
      service:
        name: apache2
        state: restarted

- name: Configuring wordpress to connect to the database
  hosts: all
  gather_facts: False
  become: true
  vars:
    wpconfigfile: "/srv/www/wordpress/wp-config.php"
  tasks:
    - name: copy sample config to wp-config.php
      #become_user: www-data
      copy:
        remote_src: yes
        src: /srv/www/wordpress/wp-config-sample.php
        dest: "{{ wpconfigfile }}"
        owner: www-data

    - name: "re-run setup to use custom facts"
      setup:
        filter: ansible_local
    - name: set database credentials in the config file
      become: false
      #become_user: www-data
      #become_method: "su"
      # multiple commands are run like this whereas with
      # single command one can use a cmd paramater
      # since this is technically *not* a list passed to /bin/sh
      # we do not need a list here. Instead it is a series of 
      # commands being passed to /bin/sh
      #shell: |
      # apparently, passing this list directly doesn't seem to work
      # what works is this loop
      command: "{{ item }}"
      with_items:
        - "sudo -u www-data sed -i s/database_name_here/wordpress/ {{ wpconfigfile }}"
        - "sudo -u www-data sed -i s/username_here/wordpress/ {{ wpconfigfile }}"
        - "sudo -u www-data sed -i s/password_here/{{ ansible_local.mysqlinfo.mysql_wordpress_password }}/ {{ wpconfigfile }}"
    - name: get random secret keys
      uri:
        url: https://api.wordpress.org/secret-key/1.1/salt/
        return_content: yes
        body_format: json
      register: wordpress_keys
    - debug:
        var: wordpress_keys.content
    - name: delete existing bak file
      file:
        path: "{{ wpconfigfile }}.bak"
        state: absent
    - name: run script to remove key placeholders
      become_user: www-data
      script:
        chdir: /srv/www/wordpress/
        cmd: replacelines.py
        executable: /usr/bin/python3
        environment: /srv/www/wordpress/
    - name: update config file
      become_user: www-data
      copy:
        remote_src: yes
        src: "{{ wpconfigfile }}.bak"
        dest: "{{ wpconfigfile }}"
    - blockinfile:
        path: "{{ wpconfigfile }}"
        marker: // {mark} ANSIBLE MANAGED BLOCK  
        # having this separator here was giving me issues   
        #block: |
        block:
          "{{ wordpress_keys.content }}"

  handlers:
    - name: Restart Mysql
      service:
        name: mysql
        state: restarted
    - name: Restart Apache2
      service:
        name: apache2
        state: restarted

Associated jinja2 template files are here:

Apache2 template:

<VirtualHost *:80>
    Servername {{ ansible_hostname }}
    DocumentRoot "{{ wprootdir }}"
    <Directory "{{ wprootdir }}">
        Options FollowSymLinks
        AllowOverride Limit Options FileInfo
        DirectoryIndex index.php
        Require all granted
    </Directory>
    <Directory "{{ wprootdir }}/wp-content">
        Options FollowSymLinks
        Require all granted
    </Directory>
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

mysqlinfo template

{
    "mysql_port": "{{ mysql_port }}",
    "mysql_socket": "{{ mysql_socket }}",
    "mysql_superuser": "{{ mysql_superuser }}",
    "mysql_superuser_password": "{{ mysql_superuser_password }}",
    "mysql_wordpress_password": "{{ mysql_wordpress_password }}"
}

replacelines.py script:

import re

with open("wp-config.php", "r") as wpconfig, open("wp-config.php.bak", "w") as wpconfigbak:
    for line in wpconfig:
        found = re.search(r'AUTH_KEY|SECURE_AUTH_KEY|LOGGED_IN_KEY|NONCE_KEY|AUTH_SALT|SECURE_AUTH_SALT|LOGGED_IN_SALT|NONCE_SALT', line.strip());
        if (not found):
            wpconfigbak.write(line)
        else:
            continue

inventory file:

[local]
localhost ansible_connection=local

With this playbook I am able to see the wordpress landing page when I open ‘localhost:80/’ on my Linux machine. However I am unable to get to the wordpress dashboard. I run the playbook like so: ansible-playbook -i inventory SetupWordpress.yaml

To save time, you may use my github repo:

git clone -b WIP [email protected]:redbilledpanda/DevOpsScripts.git
cd DevOpsScripts && ansible-playbook -i inventory SetupWordpress.yaml

After the playbook completes, I go to http://localhost:80 and I am presented with the installer:
enter image description here

I fill in the details:
enter image description here

Apparently, it succeeds:
enter image description here

When I try logging in, I don’t see the dashboard. Instead, I never go past the login screen (it doesn’t say incorrect credentials or anything though):
enter image description here

I am at a loss as to what am I doing wrong. Keen to hear from you folks.

UPDATE1: If I skip the part where I generate the wordpress ‘salts’/keys it works. I can see the dashboard etc. With these salts however, it just won’t get to the wordpress admin dashboard.

2

Answers


  1. Using a minimal sample config file wpconfig.file

    <?php
    /**
     * The base configuration for WordPress
     * ...
     * Authentication unique keys and salts.
     *
     * Change these to different unique phrases! You can generate these using
     * the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}.
     *
     * You can change these at any point in time to invalidate all existing cookies.
     * This will force all users to have to log in again.
     * ...
     */
    

    and a minimal example playbook

    ---
    - hosts: localhost
      become: false
      gather_facts: false
    
      tasks:
    
      - name: Get random secret keys
        uri:
          url: https://api.wordpress.org/secret-key/1.1/salt/
          return_content: yes
          body_format: json
        register: wordpress_keys
    
      - name: Show keys
        debug:
          var: wordpress_keys.content
    
      - name: Write keys to config
        blockinfile:
          path: wpconfig.file
          marker: // {mark} ANSIBLE MANAGED BLOCK
          block:
            "{{ wordpress_keys.content }}"
    

    it results into the expected and probably correct output.

    TASK [Show keys] ************************************************************************************************
    ok: [localhost] =>
      wordpress_keys.content: |-
        define('AUTH_KEY',         '...');
        define('SECURE_AUTH_KEY',  '...');
        define('LOGGED_IN_KEY',    '...');
        define('NONCE_KEY',        '...');
        define('AUTH_SALT',        '...');
        define('SECURE_AUTH_SALT', '...');
        define('LOGGED_IN_SALT',   '...');
        define('NONCE_SALT',       '...');
    
    <?php
    /**
     * The base configuration for WordPress
     * ...
     * Authentication unique keys and salts.
     *
     * Change these to different unique phrases! You can generate these using
     * the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}.
     *
     * You can change these at any point in time to invalidate all existing cookies.
     * This will force all users to have to log in again.
     * ...
     */
    // BEGIN ANSIBLE MANAGED BLOCK
    define('AUTH_KEY',         '...');
    define('SECURE_AUTH_KEY',  '...');
    define('LOGGED_IN_KEY',    '...');
    define('NONCE_KEY',        '...');
    define('AUTH_SALT',        '...');
    define('SECURE_AUTH_SALT', '...');
    define('LOGGED_IN_SALT',   '...');
    define('NONCE_SALT',       '...');
    // END ANSIBLE MANAGED BLOCK
    

    Summary

    • Your current question and description seems not to be focused on the necessary part but on everything not so related around
    • On Ansible tasks I am not able to (re-)produce an issue
    • The part deals with configuration for a 3rd party web service or PHP only
    • According this it seems not to be related to Ansible at all
    • The problem domain seems to be WordPress and PHP setup and configuration only, namely the config file
    • For further troubleshooting you may try to template module – Template a file out to a target host, the config file including keys generated define('AUTH_KEY', '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}');
    • Check with Browser in Incognito Mode because of invalidated cookies
    • Therefore it is also not about programming at all
    • An other site on Stack like serverfault.com, superuser.com, devops.staexchange.com or wordpress.stackexchange.com might fit better for your question
    Login or Signup to reply.
  2. -regenerate the security keys

    -Make sure the keys are entered correctly in the wp-config file of your WordPress installation.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search