KevCaz's Website

I recently got a new SD card for my Raspberry Pi 3B, so I had to reinstall Nextcloud and I took the opportunity to create an Ansible playbook for it. I basically went through the steps described in my previous note ‘Nextcloud server on a Raspberry Pi 3B’ and I turned them into a playbook. If you want to get an idea of what is Ansible, check out the Wikipedia article and for what follows, let’s keep in mind the following:

Ansible helps to manage multiple machines by selecting portions of Ansible’s inventory stored in simple plain text files. The inventory is configurable, and target machine inventory can be sourced dynamically or from cloud-based sources in different formats (YAML, INI).

Pre-requisites

Set-up the SD card with Raspberry Pi OS

  1. Install Raspberry Pi OS which is straightforward with Imager, you will follow several steps in one of then you can require a ssh-server to be set up.
1
sudo apt install rpi-imager

Use a static ID

This is done by editing /etc/dhcpcd.conf see ‘How to Setup a Raspberry Pi Static IP Address’.

Install Ansible

On my Ubuntu 22.04 machine, I did the following

1
$ apt install ansible 

to install Ansible and then

1
$ ansible-galaxy collection install community.mysql

to install the Ansible modules for mySQL used to install the MariaDB database for Nextcloud.

My playbook

Below are the two YAML files I used to reinstall Nextcloud. In both files I added links to documentation pages and Stack Overflow answers that helped develop my playbook. There is also a section ‘Notes’ below that provides further details on specific topics.

Inventory file

In my case the inventory is pretty simple, my Ubuntu machine was the controller node (in Ansible jargon) and my Raspberry Pi 3B was the managed node named ‘my_raspberry’ below. Note that the content of the inventory file can be directly integrated in the playbook (I personally prefer having two files).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
all:
  hosts:
    my_raspberry:
      remote_user: pi_user
      ansible_host: 192.168.XXX.XXX
  vars:
    ansible_connection: ssh
    ansible_ssh_user: pi_user
    ansible_ssh_private_key_file: /home/user/.ssh/id_rsa
    ansible_python_interpreter: /usr/bin/python3
    # encrypted password for the nextcloud database
    # https://docs.ansible.com/ansible/latest/vault_guide/vault_encrypting_content.html
    # see section 'note below'
    db_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          38623364643532613461383939663739356439396261623465626136646365336338633463636562
          3363343831383335623832643032323036653162346536330a373636333133376466653561346333
          30383661663833396433313465353238343532363763663861643637373836363233653631346432
          3533393164353365340a623438626462356437633334653136346236316531633039626331373833

Playbook

This playbook reproduces the steps describes in https://pimylifeup.com/raspberry-pi-nextcloud-server/. Note that I added tags to identify different parts of the installation.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
- name: Set up basic
  become: yes
  hosts: all
  tasks:
  # update+ upgrde apt 
  # use tags never to not do it, use always when upgrade is desired
  # https://docs.ansible.com/ansible/latest/collections/ansible/builtin/apt_module.html
  - name: Update and upgrade
    apt:
      update_cache: true
      cache_valid_time: 7200
      upgrade: dist
      tags: never
  #--------------------- PHP
  # https://www.tal.org/tutorials/setup-php-7-or-8-apache-raspberry-pi
  # https://pimylifeup.com/raspberry-pi-latest-php/
  - name: Install lsb-release
    apt:
        pkg:
          - lsb-release
        state: latest
        cache_valid_time: 7200
    tags: php
  # https://docs.ansible.com/ansible/latest/collections/ansible/builtin/shell_module.html
  - name: Get distribution name
    shell:
      cmd: lsb_release -cs
    register: command_output
    tags: php
  # create variable host_release_name
  # https://docs.ansible.com/ansible/latest/collections/ansible/builtin/set_fact_module.html
  - name: Create host_release_name variable
    set_fact:
      host_release_name: "{{ command_output.stdout }}"
    tags: php
  # https://docs.ansible.com/ansible/latest/collections/ansible/builtin/get_url_module.html
  - name: Get GPG key for PHP repository 
    get_url:
      url: https://packages.sury.org/php/apt.gpg
      dest: /usr/share/keyrings/suryphp-archive-keyring.gpg
    tags: php
  - name: Add PHP repository
    apt_repository:
      repo: "deb [signed-by=/usr/share/keyrings/suryphp-archive-keyring.gpg] https://packages.sury.org/php/ {{ host_release_name }} main" 
      state: present
    tags: php
  - name: Install php8.1
    apt:
      pkg:
        - php8.1
        - php8.1-gd 
        - php8.1-sqlite3 
        - php8.1-curl 
        - php8.1-zip 
        - php8.1-xml 
        - php8.1-mbstring 
        - php8.1-mysql
        - php8.1-bz2 
        - php8.1-intl 
        - php8.1-smbclient 
        - php8.1-imap
        - php8.1-gmp 
        - php8.1-bcmath 
      state: latest
      cache_valid_time: 7200
    tags: php
  # https://docs.ansible.com/ansible/latest/collections/ansible/builtin/lineinfile_module.html
  - name: Change PHP post_max_size
    lineinfile:
      path: /etc/php/8.1/apache2/php.ini
      regexp: '^post_max_size ='
      line: 'post_max_size = 2048M'
    tags: php
  - name: Change PHP upload_max_filesize
    lineinfile:
      path: /etc/php/8.1/apache2/php.ini
      regexp: '^upload_max_filesize ='
      line: 'upload_max_filesize = 2048M'
    tags: php
  #--------------------- APACHE 2
  - name: Install apache 2
    apt:
      pkg:
        - apache2
        - libapache2-mod-php8.1
      state: latest
      cache_valid_time: 7200
    tags: apache
  # https://docs.ansible.com/ansible/latest/collections/ansible/builtin/service_module.html
  - name: restart apache2
    service: 
      name: apache2
      state: restarted
    tags: apache
  #--------------------- MYSQL
  # https://pimylifeup.com/raspberry-pi-mysql/
  - name: Install MySQL
    apt:
      pkg:
        - mariadb-server
        - python3-pymysql 
        - default-libmysqlclient-dev
      state: latest
    tags: sql
  # https://stackoverflow.com/questions/25136498/ansible-answers-to-mysql-secure-installation
  # https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_db_module.html
  # /run/mysqld/mysqld.sock is very important on Linux otherwise errors are thrown
  - name: Removes test database
    community.mysql.mysql_db:
      name: test 
      state: absent
      login_unix_socket: /run/mysqld/mysqld.sock
    tags: sql
  # https://docs.ansible.com/ansible/latest/collections/community/mysql/mysql_user_module.html
  - name: Removes all anonymous accounts
    community.mysql.mysql_user:
      name: ''
      host_all: true
      login_unix_socket: /run/mysqld/mysqld.sock
      state: absent
    tags: sql
  - name: Create nextclouddb
    community.mysql.mysql_db:
      name: nextclouddb
      login_unix_socket: /run/mysqld/mysqld.sock
      state: present
    tags: sql
  - name: Create nexcloudadmin user
    community.mysql.mysql_user:
      name: nextcloudadmin
      password: "{{ db_password }}"
      priv:
        'nextclouddb.*': 'ALL,GRANT'
      login_unix_socket: /run/mysqld/mysqld.sock
      state: present
    tags: sql
  #--------------------- NEXTCLOUD
  - name: Download nextcloudadmin
    get_url:
      url: https://download.nextcloud.com/server/releases/latest.tar.bz2
      dest: /tmp/latest_nextcloud.tar.bz2
    tags: nextcloud
  # https://docs.ansible.com/ansible/latest/collections/ansible/builtin/unarchive_module.html
  # NB: this takes some time cause of all work done to ensure idempotency, 
  # see https://github.com/ansible/ansible/issues/38267
  - name: Extract nextcloud archive
    unarchive:
      src: /tmp/latest_nextcloud.tar.bz2
      creates: /var/www/nextcloud/
      dest: /var/www
      copy: no
      owner: www-data 
      group: www-data
      mode: "750"
    tags: nextcloud
  # https://docs.ansible.com/ansible/latest/collections/ansible/builtin/copy_module.html
  - name: Copy local nextcloud.conf 
    copy:
      src: nextcloud.conf
      dest: /etc/apache2/sites-available/
      owner: root
      group: root
      mode: '0644'
      backup: yes
    tags: nextcloud
  #--------------------- NEXTCLOUD PART 2
  # https://serverfault.com/questions/1064669/mv-command-did-not-rename-but-move-into-existing-directory-from-ansible
  # https://docs.ansible.com/ansible/latest/collections/ansible/builtin/stat_module.html
  - name: Check if new data folder
    stat:
      path: /var/nextcloud/data
    register: dest_folder
    tags: nextcloudp2
  - name: Move data directory
    shell: 
      cmd: |
        mkdir -p /var/nextcloud
        mv /var/www/nextcloud/data /var/nextcloud/data        
      warn: false
    when: not dest_folder.stat.exists
    tags: nextcloudp2
  # https://docs.ansible.com/ansible/latest/collections/ansible/builtin/file_module.html
  - name: Ensure that data has the right ownership
    file: 
      path: /var/nextcloud/data
      state: directory
      recurse: yes
      owner: www-data
      group: www-data 
    when: not dest_folder.stat.exists
    tags: nextcloudp2
  # https://docs.ansible.com/ansible/latest/collections/ansible/builtin/lineinfile_module.html
  - name: Redirect data folder properly
    lineinfile:
      path: /var/www/nextcloud/config/config.php
      regexp: "datadirectory"
      line: "'datadirectory' => '/var/nextcloud/data',"
      backup: yes
    tags: nextcloudp2
  - name: Enable nextcloud
    command: a2ensite nextcloud.conf
    tags: nextcloudp2
  #--------------------- UFW
  - name: Install the Uncomplicated Firewall (ufw)
    apt:
      pkg:
        - ufw
      state: latest
      update_cache: true
    tags: ufw
  - name: Allow port 80 and 443
    command: ufw allow 80,443/tcp
    tags: ufw
  #--------------------- CERTBOT
  - name: Install certbot
    apt: 
      pkg:
        - python3-certbot-apache
      state: latest
      update_cache: true
    tags: certbot

Play it

To run the entire playbook, I run the following command:

1
$ ansible-playbook --vault-password-file psw_tmp --inventory-file inventory.yaml tasks.yaml

Since I added tags, I can easily target a specific part of the installation, e.g.

1
$ ansible-playbook --vault-password-file psw_tmp --inventory-file inventory.yaml tasks.yaml --tags "apache"

or a combination of parts:

1
$ ansible-playbook --vault-password-file psw_tmp --inventory-file inventory.yaml tasks.yaml --tags "apache,php"

Notes

Encryption

I run the following command to encrypt db_password:

1
$ cat db_password_file | ansible-vault encrypt_string --vault-password-file psw_tmp --stdin-name 'db_password'

where db_password_file is a file containing the password for the database and psw_tmp is a file including the password used for encryption (see Ansible vault for further details).

Local files

There are 3 files used in this playbook that I am not sharing:

Issues with MariaDB installation

As I was working on my playbook I did something wrong with the root user and as a result I was not able to install the database properly, so I had to remove mariad-sb server:

1
2
$ apt purge mariadb-server 
$ apt autoremove

But I also had to remove other packages which were not purged, I identified them with the following:

1
$ sudo dpkg -l | grep mariadb 

One important line with community.mysql modules on Linux is:

1
login_unix_socket: /run/mysqld/mysqld.sock

Not entirely sure I understand why, but see issue #358 on GitHub.

Post actions

This playbook does not complete the installation, there are several steps to carry out afterward, in a nutshell:

  1. adding your domain in trusted_domains in /var/www/nextcloud/config/config.php;
  2. grabbing a SSL certificate, basically running sudo certbot --apache;
  3. setting Nextcloud (admin account, applications, etc.).

Ansible

Ansible is great and fairly easy to use, which amazes me given all the work it does.

References

There are tons of good posts about Ansible online and the documentation is thorough, below are listed the references I used as I was developing my playbook.

Get started

Three posts that were very helpful to get me started:

Ansible documentation pages

This is a table that includes all pages listed in the playbook above as comments and additional pages in the documentation that I found very useful to develop this playbook.

TypePage
GeneralCreate your First playbook
GeneralLearn about privilege escalation
GeneralLearn about Ansible vault
GeneralLearn how to use tags
GeneralLearn about Ansible vault
Built-inModule apt
Built-inModule shell
Built-inModule set_fact
Built-inModule get_url
Built-inModule service
Built-inModule unarchive
Built-inModule stat
Built-inModule copy
Built-inModule file
Built-inModule lineinfile
Community MySQLModule mysql_db
Community MySQLModule mysql_user