diff --git a/README.md b/README.md
deleted file mode 100644
index 4e9d626f..00000000
--- a/README.md
+++ /dev/null
@@ -1,1513 +0,0 @@
-ansible-openwisp2
-=================
-
-[![Installing OpenWISP2](https://raw.githubusercontent.com/openwisp/ansible-openwisp2/master/docs/install-openwisp2.png)](https://www.youtube.com/watch?v=v_DUeFUGG8Q&index=1&list=PLPueLZei9c8_DEYgC5StOcR5bCAcQVfR8)
-
-[![Build Status](https://github.com/openwisp/ansible-openwisp2/workflows/Ansible%20OpenWISP2%20CI%20Build/badge.svg?branch=master)](https://github.com/openwisp/ansible-openwisp2/actions?query=workflow%3A%22Ansible+OpenWISP2+CI+Build%22)
-[![Galaxy](http://img.shields.io/badge/galaxy-openwisp.openwisp2-blue.svg?style=flat-square)](https://galaxy.ansible.com/ui/standalone/roles/openwisp/openwisp2/)
-[![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=flat-square)](https://gitter.im/openwisp/general)
-
-Ansible role that installs the OpenWISP Server Application.
-
-Tested on **Debian (Bookworm/Bullseye)**, **Ubuntu (24/22/20 LTS)**.
-
-**NOTE**: it is highly suggested to use this procedure on clean virtual machines or linux containers.
-
-**Recommended minimum ansible core version**: 2.13.
-
-Demo
-====
-
-[Try the OpenWISP Demo](https://openwisp.org/demo.html) to get a quick
-overview of what OpenWISP can do for you.
-
-Help OpenWISP
-=============
-
-Like OpenWISP? Find out how to help us!
-
-- [Help us to grow our community](http://openwisp.io/docs/general/help-us.html)
-- [How to contribute to OpenWISP](http://openwisp.io/docs/developer/contributing.html)
-
-Architecture
-============
-
-For more information, see [OpenWISP Architecture](https://openwisp.io/docs/general/architecture.html).
-
-[![openwisp2 modules diagram](https://raw.githubusercontent.com/openwisp/ansible-openwisp2/master/docs/openwisp2-modules-diagram.png)](https://openwisp.io/docs/general/architecture.html)
-
-System Requirements
-===================
-
-The following specifications will run a new, *empty* instance of OpenWISP.
-Please ensure you account for the amount of disk space your use case will require, e.g. allocate
-enough space for users to upload floor plan images.
-
-Hardware requirements (Recommended)
------------------------------------
-
-- 2 CPUs
-- 2 GB Memory
-- Disk space - depends on the projected size of your database and uploaded photo images
-
-Keep in mind that increasing the number of celery workers will require
-more memory and CPU. You will need to increase the amount of celery workers
-as the number of devices you manage grows.
-
-For more information about how to increase concurrency, look for
-the variables which end with `_concurrency` or `_autoscale` in the
-[Role Variables](#role-variables) section.
-
-Software
---------
-
-Generally a fresh installation of one of the supported operating systems is sufficient; no pre-configuration required. The
-Ansible Playbook will install and configure all dependencies and leave you with a running OpenWISP installation.
-
-Tips: Ensure the hostname of your target machine matches what is in your Ansible configuration file. Also, please ensure
-that Ansible can access your target machine by SSH, be it either with a key or password. For more information see the
-[Ansible Getting Started Documentation](https://docs.ansible.com/ansible/latest/user_guide/intro_getting_started.html).
-
-Supported Operating Systems
----------------------------
-
-See the section "OS Platforms" on the
-[ansible-galaxy page of ansible-openwisp2](https://galaxy.ansible.com/openwisp/openwisp2).
-
-
-Usage (tutorial)
-================
-
-If you don't know how to use ansible, don't panic, this procedure will
-guide you towards a fully working basic openwisp2 installation.
-
-If you already know how to use ansible, you can skip this tutorial.
-
-First of all you need to understand two key concepts:
-
-* for **"production server"** we mean a server (**not a laptop or a desktop computer!**) with public
- ipv4 / ipv6 which is used to host openwisp2
-* for **"local machine"** we mean the host from which you launch ansible, eg: your own laptop
-
-Ansible is a configuration management tool that works by entering production servers via SSH,
-**so you need to install it and configure it on the machine where you launch the deployment** and
-this machine must be able to SSH into the production server.
-
-Ansible will be run on your local machine and from there it will connect to the production server
-to install openwisp2.
-
-**If you are trying to install OpenWISP2 on your laptop or desktop pc just for testing purposes**,
-please read [Install OpenWISP2 for testing in a VirtualBox VM](#install-openwisp2-for-testing-in-a-virtualbox-vm).
-
-Install ansible
----------------
-
-Install ansible (minimum recommended version 2.13) **on your local machine**
-(not the production server!) if you haven't done already.
-
-To **install ansible** we suggest you follow the official [ansible installation guide](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html#installing-ansible-in-a-virtual-environment-with-pip). It is recommended to install ansible through a virtual environment to avoid dependency issues.
-
-Please ensure that you have the correct version of Jinja installed in your Python environment:
-```
-pip install Jinja2>=2.11
-```
-
-After having installed ansible, **you need to install git** (example for linux debian/ubuntu systems):
-
- sudo apt-get install git
-
-Install this role
------------------
-
-For the sake of simplicity, the easiest thing is to install this role **on your local machine**
-via `ansible-galaxy` (which was installed when installing ansible), therefore run:
-
- ansible-galaxy install openwisp.openwisp2
-
-Ensure that you have the [`community.general`](https://github.com/ansible-collections/community.general)
-and `ansible.posix` collections installed and up to date:
-
- ansible-galaxy collection install "community.general:>=3.6.0"
- ansible-galaxy collection install "ansible.posix"
-
-Choose a working directory
---------------------------
-
-Choose a working directory **on your local machine** where to put the configuration of openwisp2.
-
-This will be useful when you will need to upgrade openwisp2.
-
-Eg:
-
- mkdir ~/openwisp2-ansible-playbook
- cd ~/openwisp2-ansible-playbook
-
-Putting this working directory under version control is also a very good idea.
-
-Create inventory file
----------------------
-
-The inventory file is where group of servers are defined. In our simple case we can
-get away with defining just one group in which we will put just one server.
-
-Create a new file called `hosts` **in your local machine**'s working directory
-(the directory just created in the previous step), with the following contents:
-
- [openwisp2]
- openwisp2.mydomain.com
-
-Substitute `openwisp2.mydomain.com` with your **production server**'s hostname - **DO NOT REPLACE
-`openwisp2.mydomain.com` WITH AN IP ADDRESS**, otherwise email sending through postfix will break,
-causing 500 internal server errors on some operations.
-
-Create playbook file
---------------------
-
-Create a new playbook file `playbook.yml` **on your local machine** with the following contents:
-
-```yaml
-- hosts: openwisp2
- become: "{{ become | default('yes') }}"
- roles:
- - openwisp.openwisp2
- vars:
- openwisp2_default_from_email: "openwisp2@openwisp2.mydomain.com"
-```
-
-The line `become: "{{ become | default('yes') }}"` means ansible will use the `sudo`
-program to run each command. You may remove this line if you don't need it (eg: if you are
- `root` user on the production server).
-
-You may replace `openwisp2` on the `hosts` field with your production server's hostname if you desire.
-
-Substitute `openwisp2@openwisp2.mydomain.com` with what you deem most appropriate
-as default sender for emails sent by OpenWISP 2.
-
-Run the playbook
-----------------
-
-Now is time to **deploy openwisp2 to the production server**.
-
-Run the playbook **from your local machine** with:
-
- ansible-playbook -i hosts playbook.yml -u -k --become -K
-
-Substitute `` with your **production server**'s username.
-
-The `-k` argument will need the `sshpass` program.
-
-You can remove `-k`, `--become` and `-K` if your public SSH key is installed on the server.
-
-**Tips**:
-
-- If you have an error like `Authentication or permission failure` then try to use *root* user `ansible-playbook -i hosts playbook.yml -u root -k`
-- If you have an error about adding the host's fingerprint to the `known_hosts` file, you can simply connect to the host via SSH and answer yes when prompted; then you can run `ansible-playbook` again.
-
-When the playbook is done running, if you got no errors you can login at:
-
- https://openwisp2.mydomain.com/admin
- username: admin
- password: admin
-
-Substitute `openwisp2.mydomain.com` with your production server's hostname.
-
-Now proceed with the following steps:
-
-1. change the password (and the username if you like) of the superuser as soon as possible
-2. update the `name` field of the default `Site` object to accurately display site name in email notifications
-3. edit the information of the default organization
-4. in the default organization you just updated, note down the automatically generated *shared secret*
- option, you will need it to use the [auto-registration feature of openwisp-config](https://github.com/openwisp/openwisp-config#automatic-registration)
-5. this Ansible role creates a default template to update ``authorized_keys`` on networking devices
- using the default access credentials. The role will either use an existing SSH key pair or create
- a new one if no SSH key pair exists on the host machine.
-
-Now you are ready to start configuring your network! **If you need help** you can ask questions
-on one of the official [OpenWISP Support Channels](http://openwisp.org/support.html).
-
-Install ansible-openwisp2 for development
------------------------------------------
-If you need to modify the logic of this ansible role and you need to test your changes here we explain how to do it.
-
-First of all, create the directory where you want to place the repositories of the ansible roles and create directory roles.
-
-```bash
- mkdir -p ~/openwisp-dev/roles
- cd ~/openwisp-dev/roles
-```
-
-Clone `ansible-openwisp2` and `Stouts.postfix` as follows:
-
-```bash
- git clone https://github.com/openwisp/ansible-openwisp2.git openwisp.openwisp2
- git clone https://github.com/Stouts/Stouts.postfix
- git clone https://github.com/openwisp/ansible-ow-influxdb openwisp.influxdb
-```
-
-Now, go to the parent directory & create hosts file and playbook.yml:
-
-```bash
- cd ../
- touch hosts
- touch playbook.yml
-```
-
-From here on you can follow the instructions available at the following sections:
-
-- [Create inventory file](#create-inventory-file)
-- [Create playbook file](#create-playbook-file)
-- [Run the playbook](#run-the-playbook)
-
-
-**Note:** Please remember to [install ansible](#install-ansible).
-
-All done!
-
-How to run tests
-----------------
-
-If you want to contribute to `ansible-openwisp2` you should run tests
-in your development environment to ensure your changes are not breaking anything.
-
-To do that, proceed with the following steps:
-
-**Step 1**: Clone `ansible-openwisp2`
-
-Clone repository by:
-
- git clone https://github.com//ansible-openwisp2.git openwisp.openwisp2
- cd openwisp.openwisp2
-
-**Step 2**: Install docker
-
-If you haven't installed docker yet, you need to install it (example for linux debian/ubuntu systems):
-
- sudo apt-get install docker.io
-
-**Step 3**: Install molecule and dependences
-
- pip install molecule[docker] molecule-plugins yamllint ansible-lint docker
-
-**Step 4**: Download docker images
-
- docker pull geerlingguy/docker-ubuntu2404-ansible:latest
- docker pull geerlingguy/docker-ubuntu2204-ansible:latest
- docker pull geerlingguy/docker-ubuntu2004-ansible:latest
- docker pull geerlingguy/docker-debian12-ansible:latest
- docker pull geerlingguy/docker-debian11-ansible:latest
-
-**Step 5**: Run molecule test
-
- molecule test -s local
-
-If you don't get any error message it means that the tests ran successfully without errors.
-
-**ProTip:** Use `molecule test --destroy=never` to speed up subsequent test runs.
-
-Install OpenWISP2 for testing in a VirtualBox VM
--------------------------------------------------
-
-If you want to try out **OpenWISP 2** in your own development environment, the safest
-way is to use a VirtualBox Virtual Machine (from here on VM).
-
-### Using Vagrant
-
-**Since August 2018 there's a new fast and easy way to install OpenWISP 2 for testing
-purposes** leveraging [Vagrant](https://www.vagrantup.com), a popular open source
-tool for building and maintaining portable virtual software development environments.
-
-To use this new way, clone the repository [vagrant-openwisp2](https://github.com/openwisp/vagrant-openwisp2),
-it contains the instructions (in the `README.md`) and the vagrant configuration
-to perform the automatic installation.
-
-Alternatively, you can read on to learn how to install *VirtualBox* and run
-*ansible-openwisp2* manually, this is useful if you need to test advanced
-customisations of *OpenWISP*.
-
-### Installing Debian 9 on VirtualBox
-
-Install [VirtualBox](https://virtualbox.org) and create a new Virtual Machine running
-Debian 11. A step-by-step guide is available
-[here](http://www.brianlinkletter.com/installing-debian-linux-in-a-virtualbox-virtual-machine/),
-however we need to change a few things to get ansible working.
-
-#### VM configuration
-
-Proceed with the installation as shown in the guide linked above, and come back
-here when you see this screen:
-
-![Screenshot of the Software Selection screen](https://raw.githubusercontent.com/openwisp/ansible-openwisp2/master/docs/debian-software-selection.png)
-
-We're only running this as a server, so you can uncheck `Debian desktop environment`.
-Make sure `SSH server` and `standard system utilities` are checked.
-
-Next, add a [Host-only Network Adapter](https://www.virtualbox.org/manual/ch06.html#network_hostonly)
-and assign an IP address to the VM.
-
-- On the Main VirtualBox page, Go to `File > Host Network Manager`
-- Click the + icon to create a new adapter
-- Set the IPv4 address to `192.168.56.1` and the IPv4 Network Mask to `255.255.255.0`. You may need to select `Configure Adapter Manually` to do this. The IPv6 settings can be ignored
- ![Screenshot of the Host-only network configuration screen](https://raw.githubusercontent.com/openwisp/ansible-openwisp2/master/docs/host-only-network.png)
-- Shut off your VM
-- In your VM settings, in the Network section, click Adapter 2 and Enable this Adapter
-- Select Host-only adapter and the name of the adapter you created
-- Boot up your VM, run `su`, and type in your superuser password
-- Run `ls /sys/class/net` and take note of the output
-- Run `nano /etc/network/interfaces` and add the following at the end of the file:
-
- auto enp0s8
- iface enp0s8 inet static
- address 192.168.56.2
- netmask 255.255.255.0
- network 192.168.56.0
- broadcast 192.168.56.255
-
- Replace `enp0s8` with the network interface not present in the file but is shown when running `ls /sys/class/net`
-- Save the file with CtrlO then Enter, and exit with CtrlX
-- Restart the machine by running `reboot`
-
-Make sure you can access your VM via ssh:
-
-```bash
-ssh 192.168.56.2
-```
-
-#### Back to your local machine
-
-Proceed with these steps in your **local machine**, not the VM.
-
-**Step 1**: [Install ansible](#install-ansible)
-
-**Step 2**: [Install the OpenWISP2 role for Ansible](#install-this-role)
-
-**Step 3**: [Set up a working directory](#choose-a-working-directory)
-
-**Step 4**: Create the `hosts` file
-
-Create an ansible inventory file named `hosts` **in your working directory**
-(i.e. not in the VM) with the following contents:
-
-```
-[openwisp2]
-192.168.56.2
-```
-
-**Step 5**: Create the ansible playbook
-
-In the same directory where you created the `host` file,
-create an empty file named `playbook.yml` which contains the following:
-
-```yaml
-- hosts: openwisp2
- roles:
- - openwisp.openwisp2
- # the following line is needed only when an IP address is used as the inventory hostname
- vars:
- postfix_myhostname: localhost
-```
-
-**Step 6**: Run the playbook
-
-```bash
-ansible-playbook -i hosts playbook.yml -b -k -K --become-method=su
-```
-
-When the playbook ran successfully, you can log in at:
-
-```
-https://192.168.56.2/admin
-username: admin
-password: admin
-```
-
-Enabling the Monitoring module
-------------------------------
-
-The [Monitoring module](https://openwisp.io/docs/user/monitoring.html)
-is enabled by default, it can be disabled by setting
-``openwisp2_monitoring`` to ``false``.
-
-Enabling the Network Topology module
-------------------------------------
-
-To enable the [Network Topology module](https://openwisp.io/docs/user/network-topology.html)
-you need to set `openwisp2_network_topology` to `true` in
-your `playbook.yml` file. Here's a short summary of how to do this:
-
-**Step 1**: [Install ansible](#install-ansible)
-
-**Step 2**: [Install this role](#install-this-role)
-
-**Step 3**: [Create inventory file](#create-inventory-file)
-
-**Step 4**: Create a playbook file with following contents:
-
-```yaml
-- hosts: openwisp2
- become: "{{ become | default('yes') }}"
- roles:
- - openwisp.openwisp2
- vars:
- openwisp2_network_topology: true
-```
-
-**Step 5**: [Run the playbook](#run-the-playbook)
-
-When the playbook is done running, if you got no errors you can login at:
-
- https://openwisp2.mydomain.com/admin
- username: admin
- password: admin
-
-Enabling the Firmware Upgrader module
--------------------------------------
-
-**Note**: It is encouraged that you read the
-[quick-start guide of openwisp-firmware-upgrader](https://openwisp.io/docs/user/firmware-upgrades.html#quickstart-guide)
-before going ahead.
-
-To enable the [Firmware Upgrader](https://openwisp.io/docs/user/firmware-upgrades.html)
-module you need to set `openwisp2_firmware_upgrader` to `true` in
-your `playbook.yml` file. Here's a short summary of how to do this:
-
-**Step 1**: [Install ansible](#install-ansible)
-
-**Step 2**: [Install this role](#install-this-role)
-
-**Step 3**: [Create inventory file](#create-inventory-file)
-
-**Step 4**: Create a playbook file with following contents:
-
-```yaml
-- hosts: openwisp2
- become: "{{ become | default('yes') }}"
- roles:
- - openwisp.openwisp2
- vars:
- openwisp2_firmware_upgrader: true
-```
-
-**Step 5**: [Run the playbook](#run-the-playbook)
-
-When the playbook is done running, if you got no errors you can login at:
-
- https://openwisp2.mydomain.com/admin
- username: admin
- password: admin
-
-**Note**: You can configure [openwisp-firmware-upgrader specific settings](https://github.com/openwisp/openwisp-firmware-upgrader#settings)
-using the `openwisp2_extra_django_settings` or
-`openwisp2_extra_django_settings_instructions`.
-
-E.g:
-
-```yaml
-- hosts: openwisp2
- become: "{{ become | default('yes') }}"
- roles:
- - openwisp.openwisp2
- vars:
- openwisp2_firmware_upgrader: true
- openwisp2_extra_django_settings_instructions:
- - |
- OPENWISP_CUSTOM_OPENWRT_IMAGES = (
- ('my-custom-image-squashfs-sysupgrade.bin', {
- 'label': 'My Custom Image',
- 'boards': ('MyCustomImage',)
- }),
- )
-```
-
-Enabling the RADIUS module
---------------------------
-
-To enable the [RADIUS module](https://openwisp.io/docs/user/radius.html)
-you need to set `openwisp2_radius` to `true` in
-your `playbook.yml` file. Here's a short summary of how to do this:
-
-**Step 1**: [Install ansible](#install-ansible)
-
-**Step 2**: [Install this role](#install-this-role)
-
-**Step 3**: [Create inventory file](#create-inventory-file)
-
-**Step 4**: Create a playbook file with following contents:
-
-```yaml
-- hosts: openwisp2
- become: "{{ become | default('yes') }}"
- roles:
- - openwisp.openwisp2
- vars:
- openwisp2_radius: true
- openwisp2_freeradius_install: true
- # set to false when you don't want to register openwisp-radius
- # API endpoints.
- openwisp2_radius_urls: true
-```
-
-**Note:** `openwisp2_freeradius_install` option provides a basic
-configuration of freeradius for openwisp, it sets up the
-[radius user token mechanism](https://openwisp-radius.readthedocs.io/en/latest/user/api.html#radius-user-token-recommended)
-if you want to use another mechanism or manage your freeradius separately,
-please disable this option by setting it to `false`.
-
-**Step 5**: [Run the playbook](#run-the-playbook)
-
-When the playbook is done running, if you got no errors you can login at:
-
- https://openwisp2.mydomain.com/admin
- username: admin
- password: admin
-
-**Note:** for more information regarding radius configuration options,
-look for the word "radius" in the
-[Role variables](#role-variables) section of this document.
-
-### Configuring FreeRADIUS for WPA Enterprise (EAP-TTLS-PAP)
-
-You can use OpenWISP RADIUS for setting up WPA Enterprise (EAP-TTLS-PAP)
-authentication. This allows to authenticate on WiFi networks using Django
-user credentials. Prior to proceeding, ensure you've reviewed the tutorial
-on [Setting Up WPA Enterprise (EAP-TTLS-PAP) authentication](https://openwisp.io/docs/tutorials/wpa-enterprise.html).
-This documentation section complements the tutorial and focuses solely on
-demonstrating the ansible role's capabilities to configure FreeRADIUS.
-
-**Note**: The ansible role supports OpenWISP's multi-tenancy by creating
-individual FreeRADIUS sites for each organization. You must include
-configuration details for **each organization** that will use WPA
-Enterprise.
-
-Here's an example playbook which enables OpenWISP RADIUS module,
-installs FreeRADIUS, and configures it for WPA Enterprise (EAP-TTLS-PAP):
-
- ```yaml
- - hosts: openwisp2
- become: "{{ become | default('yes') }}"
- roles:
- - openwisp.openwisp2
- vars:
- openwisp2_radius: true
- openwisp2_freeradius_install: true
- # Define a list of dictionaries detailing each organization's
- # name, UUID, RADIUS token, and ports for authentication,
- # accounting, and the inner tunnel. These details will be used
- # to create FreeRADIUS sites tailored for WPA Enterprise
- # (EAP-TTLS-PAP) authentication per organization.
- freeradius_eap_orgs:
- # A reference name for the organization,
- # used in FreeRADIUS configurations.
- # Don't use spaces or special characters.
- - name: openwisp
- # UUID of the organization.
- # You can retrieve this from the organization admin
- # in the OpenWISP web interface.
- uuid: 00000000-0000-0000-0000-000000000000
- # Radius token of the organization.
- # You can retrieve this from the organization admin
- # in the OpenWISP web interface.
- radius_token: secret-radius-token
- # Port used by the authentication service for
- # this FreeRADIUS site
- auth_port: 1822
- # Port used by the accounting service for this FreeRADIUS site
- acct_port: 1823
- # Port used by the authentication service of inner tunnel
- # for this FreeRADIUS site
- inner_tunnel_auth_port: 18230
- # If you want to use a custom certificate for FreeRADIUS
- # EAP module, you can specify the path to the CA, server
- # certificate, and private key, and DH key as follows.
- # Ensure that these files can be read by the "freerad" user.
- cert: /etc/freeradius/certs/cert.pem
- private_key: /etc/freeradius/certs/key.pem
- ca: /etc/freeradius/certs/ca.crt
- dh: /etc/freeradius/certs/dh
- tls_config_extra: |
- private_key_password = whatever
- ecdh_curve = "prime256v1"
-
- # You can add as many organizations as you want
- - name: demo
- uuid: 00000000-0000-0000-0000-000000000001
- radius_secret: demo-radius-token
- auth_port: 1832
- acct_port: 1833
- inner_tunnel_auth_port: 18330
- # If you omit the certificate fields,
- # the FreeRADIUS site will use the default certificates
- # located in /etc/freeradius/certs.
-```
-
-**Note**: In the example above, custom ports 1822, 1823, and 18230
-are utilized for FreeRADIUS authentication, accounting, and inner tunnel
-authentication, respectively. These custom ports are specified because the
-Ansible role creates a common FreeRADIUS site for all organizations, which
-also supports captive portal functionality. This common site is configured
-to listen on the default FreeRADIUS ports 1812, 1813, and 18120. Therefore,
-when configuring WPA Enterprise authentication for each organization,
-unique ports must be provided to ensure proper isolation and functionality.
-
-#### Using Let's Encrypt Certificate for WPA Enterprise (EAP-TTLS-PAP)
-
-In this section, we demonstrate how to utilize Let's Encrypt certificates
-for WPA Enterprise (EAP-TTLS-PAP) authentication. Similar to the
-[Automatic SSL certificate](#automatic-ssl-certificate), we use
-[geerlingguy.certbot](https://galaxy.ansible.com/geerlingguy/certbot/)
-role to automatically install and renew a valid SSL certificate.
-
-The following example playbook achieves the following goals:
-
-- Provision a separate Let's Encrypt certificate for the
- `freeradius.yourdomain.com` hostname. This certificate will be
- utilized by the FreeRADIUS site for WPA Enterprise authentication.
-- Create a renewal hook to set permissions on the generated certificate
- so the FreeRADIUS server can read it.
-
-**Note**: You can also use the same SSL certificate for both Nginx and
-FreeRADIUS, but it's crucial to understand the security implications.
-Please exercise caution and refer to the example playbook comments for
-guidance.
-
-```yaml
- - hosts: openwisp2
- become: "{{ become | default('yes') }}"
- roles:
- - geerlingguy.certbot
- - openwisp.openwisp2
- vars:
- # certbot configuration
- certbot_auto_renew_minute: "20"
- certbot_auto_renew_hour: "5"
- certbot_create_if_missing: true
- certbot_auto_renew_user: ""
- certbot_certs:
- - email: ""
- domains:
- - "{{ inventory_hostname }}"
- # If you choose to re-use the same certificate for both services,
- # you can omit the following item in your playbook.
- - email: ""
- domains:
- - "freeradius.yourdomain.com"
- # Configuration to use Let's Encrypt certificate for OpenWISP server (Nnginx)
- openwisp2_ssl_cert: "/etc/letsencrypt/live/{{ inventory_hostname }}/fullchain.pem"
- openwisp2_ssl_key: "/etc/letsencrypt/live/{{ inventory_hostname }}/privkey.pem"
- # Configuration for openwisp-radius
- openwisp2_radius: true
- openwisp2_freeradius_install: true
- freeradius_eap_orgs:
- - name: demo
- uuid: 00000000-0000-0000-0000-000000000001
- radius_secret: demo-radius-token
- auth_port: 1832
- acct_port: 1833
- inner_tunnel_auth_port: 18330
- # Update the cert_file and private_key paths to point to the
- # Let's Encrypt certificate.
- cert: /etc/letsencrypt/live/freeradius.yourdomain.com/fullchain.pem
- private_key: /etc/letsencrypt/live/freeradius.yourdomain.com/privkey.pem
- # If you choose to re-use the same certificate for both services,
- # your configuration would look like this
- # cert: /etc/letsencrypt/live/{{ inventory_hostname }}/fullchain.pem
- # private_key: /etc/letsencrypt/live/{{ inventory_hostname }}/privkey.pem
- tasks:
- # Tasks to ensure the Let's Encrypt certificate can be read by the FreeRADIUS server.
- # If you are using the same certificate for both services, you need to
- # replace "freeradius.yourdomain.com" with "{{ inventory_hostname }}"
- # in the following task.
- - name: "Create a renewal hook for setting permissions on /etc/letsencrypt/live/freeradius.yourdomain.com"
- copy:
- content: |
- #!/bin/bash
- chown -R root:freerad /etc/letsencrypt/live/ /etc/letsencrypt/archive/
- chmod 0750 /etc/letsencrypt/live/ /etc/letsencrypt/archive/
- chmod -R 0640 /etc/letsencrypt/archive/freeradius.yourdomain.com/
- chmod 0750 /etc/letsencrypt/archive/freeradius.yourdomain.com/
- dest: /etc/letsencrypt/renewal-hooks/post/chown_freerad
- owner: root
- group: root
- mode: '0700'
- register: chown_freerad_result
- - name: Change the ownership of the certificate files
- when: chown_freerad_result.changed
- command: /etc/letsencrypt/renewal-hooks/post/chown_freerad
-```
-
-Configuring CORS Headers
-------------------------
-
-While integrating OpenWISP with external services, you can
-run into issues related to [CORS (Cross-Origin Resource Sharing)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). This role
-allows users to configure the CORS headers with the help of
-[django-cors-headers](https://github.com/adamchainz/django-cors-headers)
-package. Here's a short summary of how to do this:
-
-**Step 1**: [Install ansible](#install-ansible)
-
-**Step 2**: [Install this role](#install-this-role)
-
-**Step 3**: [Create inventory file](#create-inventory-file)
-
-**Step 4**: Create a playbook file with following contents:
-
-```yaml
-- hosts: openwisp2
- become: "{{ become | default('yes') }}"
- roles:
- - openwisp.openwisp2
- vars:
- # Cross-Origin Resource Sharing (CORS) settings
- openwisp2_django_cors:
- enabled: true
- allowed_origins_list:
- - https://frontend.openwisp.org
- - https://logs.openwisp.org
-```
-
-**Note:** to learn about the supported fields of the `openwisp2_django_cors` variable,
-look for the word "openwisp2_django_cors" in the
-[Role variables](#role-variables) section of this document.
-
-**Step 5**: [Run the playbook](#run-the-playbook)
-
-When the playbook is done running, if you got no errors you can login at:
-
-```
-https://openwisp2.mydomain.com/admin
-username: admin
-password: admin
-```
-
-The ansible-openwisp2 only provides abstraction (variables)
-for handful of settings available in [django-cors-headers](https://github.com/adamchainz/django-cors-headers)
-module. Use the `openwisp2_extra_django_settings_instructions` or
-`openwisp2_extra_django_settings` variable to configure additional
-setting of `django-cors-headers` as shown in the following example:
-
-```yaml
-- hosts: openwisp2
- become: "{{ become | default('yes') }}"
- roles:
- - openwisp.openwisp2
- vars:
- openwisp2_django_cors:
- enabled: true
- allowed_origins_list:
- - https://frontend.openwisp.org
- - https://logs.openwisp.org
- replace_https_referer: true
- # Configuring additional settings for django-cors-headers
- openwisp2_extra_django_settings_instructions:
- - |
- CORS_ALLOW_CREDENTIALS = True
- CORS_ALLOW_ALL_ORIGINS = True
-```
-
-Deploying custom static content
-===============================
-
-For deploying custom static content (HTML files, etc.) add all
-the static content in `files/ow2_static` directory. The files inside
-`files/ow2_static` will be uploaded to a directory named `static_custom`
-in `openwisp2_path`.
-
-This is helpful for [customizing OpenWISP's theme](https://github.com/openwisp/openwisp-utils#openwisp_admin_theme_links).
-
-E.g., if you added a custom CSS file in `files/ow2_static/css/custom.css`, the
-file location to use in [OPENWISP_ADMIN_THEME_LINKS](https://github.com/openwisp/openwisp-utils#openwisp_admin_theme_links) setting will be `css/custom.css`.
-
-Deploying the upcoming release of OpenWISP
-==========================================
-
-The following steps will help you set up and install the new version of OpenWISP
-which is not released yet, but ships new features and fixes.
-
-Create a directory for organizing your playbook, roles and collections. In this example,
-`openwisp-dev` is used. Create `roles` and `collections` directories in `~/openwisp-dev`.
-
-```
-mkdir -p ~/openwisp-dev/roles
-mkdir -p ~/openwisp-dev/collections
-```
-
-Change directory to `~/openwisp-dev/` in terminal and create configuration
-and requirement files for Ansible.
-
-```
-cd ~/openwisp-dev/
-touch ansible.cfg
-touch requirements.yml
-```
-
-Setup `roles_path` and `collections_paths` variables in `ansible.cfg` as follows:
-
-```
-[defaults]
-roles_path=~/openwisp-dev/roles
-collections_paths=~/openwisp-dev/collections
-```
-
-Ensure your `requirements.yml` contains following content:
-
-```yml
----
-roles:
- - src: https://github.com/openwisp/ansible-openwisp2.git
- version: master
- name: openwisp.openwisp2-dev
-collections:
- - name: community.general
- version: ">=3.6.0"
-```
-
-Install requirements from the `requirements.yml` as follows
-
-```
-ansible-galaxy install -r requirements.yml
-```
-
-Now, create hosts file and playbook.yml:
-
-```
-touch hosts
-touch playbook.yml
-```
-
-Follow instructions in ["Create inventory file"](#create-inventory-file) section to
-configure `hosts` file.
-
-You can reference the example playbook below (tested on Debian 11)
-for installing a fully-featured version of OpenWISP.
-
-```yml
-- hosts: openwisp2
- become: "{{ become | default('yes') }}"
- roles:
- - openwisp.openwisp2-dev
- vars:
- openwisp2_network_topology: true
- openwisp2_firmware_upgrader: true
- openwisp2_radius: true
- openwisp2_monitoring: true # monitoring is enabled by default
-```
-
-Read ["Role Variables"](#role-variables) section to learn about
-available configuration variables.
-
-Follow instructions in ["Run the playbook"](#run-the-playbook) section to
-run above playbook.
-
-Troubleshooting
-===============
-
-OpenWISP 2 is deployed using **uWSGI**, it also uses **daphne** fo
- WebSockets and **celery** as task queue.
-
- All this services are run by **supervisor**.
-
-```
-sudo service supervisor start|stop|status
-```
-
-You can view each individual process run by supervisor with the next command.
-
-More info at [Running supervisorctl](http://supervisord.org/running.html#running-supervisorctl)
-
-```
-sudo supervisorctl status
-```
-
-**nginx** is in front of **uWSGI**. You can control it with next command
-
-```
-service nginx status start|stop|status
-```
-
-OpenWISP 2 is installed in `/opt/openwisp2`
-(unless you changed the ``openwisp2_path`` variable in the
-ansible playbook configuration), these are some useful directories to
-look for when experiencing issues.
-
-| Location | Description |
-|---------------------------|-------------------------------|
-| /opt/openwisp2 | The OpenWISP 2 root dir. |
-| /opt/openwisp2/log | Log files |
-| /opt/openwisp2/env | Python virtual env |
-| /opt/openwisp2/db.sqlite3 | OpenWISP 2 sqlite database |
-
-All processes are running as ``www-data`` user.
-
-If you need to copy or edit files, you can switch to ``www-data``
-user with the commands
-
-```
-sudo su www-data -s /bin/bash
-cd /opt/openwisp2
-source env/bin/activate
-```
-
-SSL certificate gotchas
-=======================
-
-When you access the admin website you will get an SSL certificate warning because the
-playbook creates a self-signed (untrusted) SSL certificate. You can get rid of the warning by
-installing your own trusted certificate and set the `openwisp2_ssl_cert` and `openwisp2_ssl_key`
-variables accordingly or by following the instructions explained in the section
-["Automatic SSL certificate"](#automatic-ssl-certificate).
-
-If you keep the untrusted certificate, you will also need to disable SSL verification on devices
-using [openwisp-config](https://github.com/openwisp/openwisp-config) by setting `verify_ssl` to `0`,
-although I advice against using this kind of setup in a production environment.
-
-Automatic SSL certificate
-=========================
-
-This section explains how to **automatically install and renew a valid SSL certificate** signed by
-[letsencrypt](https://letsencrypt.org/).
-
-The first thing you have to do is to setup a valid domain for your openwisp2 instance, this means
-your inventory file (hosts) should look like the following:
-
-```
-[openwisp2]
-openwisp2.yourdomain.com
-```
-
-You must be able to add a DNS record for `openwisp2.yourdomain.com`, you cannot use an ip address
-in place of `openwisp2.yourdomain.com`.
-
-Once your domain is set up and the DNS record is propagated, proceed by installing the ansible role
-[geerlingguy.certbot](https://galaxy.ansible.com/geerlingguy/certbot/):
-
-```
-ansible-galaxy install geerlingguy.certbot
-```
-
-Then proceed to edit your `playbook.yml` so that it will look similar to the following example:
-
-```yaml
-- hosts: openwisp2
- become: "{{ become | default('yes') }}"
- roles:
- - geerlingguy.certbot
- - openwisp.openwisp2
- vars:
- # SSL certificates
- openwisp2_ssl_cert: "/etc/letsencrypt/live/{{ inventory_hostname }}/fullchain.pem"
- openwisp2_ssl_key: "/etc/letsencrypt/live/{{ inventory_hostname }}/privkey.pem"
-
- # certbot configuration
- certbot_auto_renew_minute: "20"
- certbot_auto_renew_hour: "5"
- certbot_create_if_missing: true
- certbot_auto_renew_user: ""
- certbot_certs:
- - email: ""
- domains:
- - "{{ inventory_hostname }}"
- pre_tasks:
- - name: Update APT package cache
- apt:
- update_cache: true
- changed_when: false
- retries: 5
- delay: 10
- register: result
- until: result is success
-```
-
-Read the [documentation of geerlingguy.certbot](https://github.com/geerlingguy/ansible-role-certbot#readme)
-to learn more about configuration of certbot role.
-
-Once you have set up all the variables correctly, run the playbook again.
-
-Upgrading openwisp2
-===================
-
-**It's highly recommended to back up your current instance
-before upgrading**.
-
-Update this ansible-role via `ansible-galaxy`:
-
- ansible-galaxy install --force openwisp.openwisp2
-
-Run `ansible-playbook` again **from your local machine**:
-
- ansible-playbook -i hosts playbook.yml
-
-You may also run the playbook automatically periodically or when a new release of OpenWISP2, for
-example, by setting up a continuous integration system.
-
-Role variables
-==============
-
-This role has many variables values that can be changed to best suit
-your needs.
-
-Below are listed all the variables you can customize (you may also want to take a look at
-[the default values of these variables](https://github.com/openwisp/ansible-openwisp2/blob/master/defaults/main.yml)).
-
-```yaml
-- hosts: yourhost
- roles:
- # you can add other roles here
- - openwisp.openwisp2
- vars:
- # Enable the modules you want to use
- openwisp2_network_topology: false
- openwisp2_firmware_upgrader: false
- openwisp2_monitoring: true
- # you may replace the values of these variables with any value or URL
- # supported by pip (the python package installer)
- # use these to install forks, branches or development versions
- # WARNING: only do this if you know what you are doing; disruption
- # of service is very likely to occur if these variables are changed
- # without careful analysis and testing
- openwisp2_controller_version: "openwisp-controller~=1.0.0"
- openwisp2_network_topology_version: "openwisp-network-topology~=1.0.0"
- openwisp2_firmware_upgrader_version: "openwisp-firmware-upgrader~=1.0.0"
- openwisp2_monitoring_version: "openwisp-monitoring~=1.0.0"
- openwisp2_radius_version: "openwisp-radius~=1.0.0"
- openwisp2_django_version: "django~=3.2.13"
- # Setting this to true will enable subnet division feature of
- # openwisp-controller. Refer openwisp-controller documentation
- # for more information. https://github.com/openwisp/openwisp-controller#subnet-division-app
- # By default, it is set to false.
- openwisp2_controller_subnet_division: true
- # when openwisp2_radius_urls is set to false, the radius module
- # is setup but it's urls are not added, which means API and social
- # views cannot be used, this is helpful if you have an external
- # radius instance.
- openwisp2_radius_urls: "{{ openwisp2_radius }}"
- openwisp2_path: /opt/openwisp2
- # It is recommended that you change the value of this variable if you intend to use
- # OpenWISP2 in production, as a misconfiguration may result in emails not being sent
- openwisp2_default_from_email: "openwisp2@yourhostname.com"
- # Email backend used by Django for sending emails. By default, the role
- # uses "CeleryEmailBackend" from django-celery-email.
- # (https://github.com/pmclanahan/django-celery-email)
- openwisp2_email_backend: "djcelery_email.backends.CeleryEmailBackend"
- # Email timeout in seconds used by Django for blocking operations
- # like connection attempts. For more info read the Django documentation,
- # https://docs.djangoproject.com/en/3.2/ref/settings/#email-timeout.
- # Defaults to 10 seconds.
- openwisp2_email_timeout: 5
- # edit database settings only if you are not using sqlite
- # eg, for deploying with PostgreSQL (recommended for production usage)
- # you will need the PostGIS spatial extension, find more info at:
- # https://docs.djangoproject.com/en/4.1/ref/contrib/gis/tutorial/
- openwisp2_database:
- engine: django.contrib.gis.db.backends.postgis
- name: "{{ DB_NAME }}"
- user: "{{ DB_USER }}"
- host: "{{ DB_HOST }}"
- password: "{{ DB_PASSWORD }}"
- port: 5432
- # SPATIALITE_LIBRARY_PATH django setting
- # The role will attempt determining the right mod-spatialite path automatically
- # But you can use this variable to customize the path or fix future arising issues
- openwisp2_spatialite_path: "mod_spatialite.so"
- # customize other django settings:
- openwisp2_language_code: en-gb
- openwisp2_time_zone: UTC
- # openwisp-controller context
- openwisp2_context: {}
- # additional allowed hosts
- openwisp2_allowed_hosts:
- - myadditionalhost.openwisp.org
- # geographic map settings
- openwisp2_leaflet_config:
- DEFAULT_CENTER: [42.06775, 12.62011]
- DEFAULT_ZOOM: 6
- # enable/disable geocoding check
- openwisp2_geocoding_check: true
- # specify path to a valid SSL certificate and key
- # (a self-signed SSL cert will be generated if omitted)
- openwisp2_ssl_cert: "/etc/nginx/ssl/server.crt"
- openwisp2_ssl_key: "/etc/nginx/ssl/server.key"
- # customize the self-signed SSL certificate info if needed
- openwisp2_ssl_country: "US"
- openwisp2_ssl_state: "California"
- openwisp2_ssl_locality: "San Francisco"
- openwisp2_ssl_organization: "IT dep."
- # the following setting controls which ip address range
- # is allowed to access the controller via unencrypted HTTP
- # (this feature is disabled by default)
- openwisp2_http_allowed_ip: "10.8.0.0/16"
- # additional python packages that will be installed with pip
- openwisp2_extra_python_packages:
- - bpython
- - django-owm-legacy
- # additional django apps that will be added to settings.INSTALLED_APPS
- # (if the app needs to be installed, the name its python package
- # must be also added to the openwisp2_extra_python_packages var)
- openwisp2_extra_django_apps:
- - owm_legacy
- # additional django settings example
- openwisp2_extra_django_settings:
- CSRF_COOKIE_AGE: 2620800.0
- # in case you need to add python instructions to the django settings file
- openwisp2_extra_django_settings_instructions:
- - TEMPLATES[0]['OPTIONS']['loaders'].insert(0, 'apptemplates.Loader')
- # extra URL settings for django
- openwisp2_extra_urls:
- - "path(r'', include('my_custom_app.urls'))"
- # allows to specify imports that are used in the websocket routes, eg:
- openwisp2_websocket_extra_imports:
- - from my_custom_app.websockets.routing import get_routes as get_custom_app_routes
- # allows to specify extra websocket routes, eg:
- openwisp2_websocket_extra_routes:
- # Callable that returns a list of routes
- - get_custom_app_routes()
- # List of routes
- - "[path('ws/custom-app/', consumer.CustomAppConsumer.as_asgi())]"
- # controller URL are enabled by default
- # but can be disabled in multi-VM installations if needed
- openwisp2_controller_urls: true
- # The default retention policy that applies to the timeseries data
- # https://github.com/openwisp/openwisp-monitoring#openwisp-monitoring-default-retention-policy
- openwisp2_monitoring_default_retention_policy: "26280h0m0s" # 3 years
- # whether NGINX should be installed
- openwisp2_nginx_install: true
- # spdy protocol support (disabled by default)
- openwisp2_nginx_spdy: false
- # HTTP2 protocol support (disabled by default)
- openwisp2_nginx_http2: false
- # ipv6 must be enabled explicitly to avoid errors
- openwisp2_nginx_ipv6: false
- # nginx client_max_body_size setting
- openwisp2_nginx_client_max_body_size: 10M
- # list of upstream servers for OpenWISP
- openwisp2_nginx_openwisp_server:
- - "localhost:8000"
- # dictionary containing more nginx settings for
- # the 443 section of the openwisp2 nginx configuration
- # IMPORTANT: 1. you can add more nginx settings in this dictionary
- # 2. here we list the default values used
- openwisp2_nginx_ssl_config:
- gzip: "on"
- gzip_comp_level: "6"
- gzip_proxied: "any"
- gzip_min_length: "1000"
- gzip_types:
- - "text/plain"
- - "text/html"
- - "image/svg+xml"
- - "application/json"
- - "application/javascript"
- - "text/xml"
- - "text/css"
- - "application/xml"
- - "application/x-font-ttf"
- - "font/opentype"
- # nginx error log configuration
- openwisp2_nginx_access_log: "{{ openwisp2_path }}/log/nginx.access.log"
- openwisp2_nginx_error_log: "{{ openwisp2_path }}/log/nginx.error.log error"
- # nginx Content Security Policy header, customize if needed
- openwisp2_nginx_csp: >
- CUSTOM_NGINX_SECURITY_POLICY
- # uwsgi gid, omitted by default
- openwisp2_uwsgi_gid: null
- # number of uWSGI process to spawn. Default value is 1.
- openwisp2_uwsgi_processes: 1
- # number of threads each uWSGI process will have. Default value is 1.
- openwisp2_uwsgi_threads: 2
- # value of the listen queue of uWSGI
- openwisp2_uwsgi_listen: 100
- # socket on which uwsgi should listen. Defaults to UNIX socket
- # at "{{ openwisp2_path }}/uwsgi.sock"
- openwisp2_uwsgi_socket: 127.0.0.1:8000
- # extra uwsgi configuration parameters that cannot be
- # configured using dedicated ansible variables
- openwisp2_uwsgi_extra_conf: |
- single-interpreter=True
- log-4xx=True
- log-5xx=True
- disable-logging=True
- auto-procname=True
- # whether daphne should be installed
- # must be enabled for serving websocket requests
- openwisp2_daphne_install: true
- # number of daphne process to spawn. Default value is 1
- openwisp2_daphne_processes: 2
- # maximum time to allow a websocket to be connected (in seconds)
- openwisp2_daphne_websocket_timeout: 1800
- # the following setting controls which ip address range
- # is allowed to access the openwisp2 admin web interface
- # (by default any IP is allowed)
- openwisp2_admin_allowed_network: null
- # install ntp client (enabled by default)
- openwisp2_install_ntp: true
- # if you have any custom supervisor service, you can
- # configure it to restart along with other supervisor services
- openwisp2_extra_supervisor_restart:
- - name: my_custom_service
- when: my_custom_service_enabled
- # Disable usage metric collection. It is enabled by default.
- # Read more about it at
- # https://openwisp.io/docs/user/usage-metric-collection.html
- openwisp2_usage_metric_collection: false
- # enable sentry example
- openwisp2_sentry:
- dsn: "https://7d2e3cd61acc32eca1fb2a390f7b55e1:bf82aab5ddn4422688e34a486c7426e3@getsentry.com:443/12345"
- openwisp2_default_cert_validity: 1825
- openwisp2_default_ca_validity: 3650
- # the following options for redis allow to configure an external redis instance if needed
- openwisp2_redis_install: true
- openwisp2_redis_host: localhost
- openwisp2_redis_port: 6379
- openwisp2_redis_cache_url: "redis://{{ openwisp2_redis_host }}:{{ openwisp2_redis_port }}/1"
- # the following options are required to configure influxdb which is used in openwisp-monitoring
- openwisp2_influxdb_install: true
- openwisp2_timeseries_database:
- backend: "openwisp_monitoring.db.backends.influxdb"
- user: "openwisp"
- password: "openwisp"
- name: "openwisp2"
- host: "localhost"
- port: 8086
- # celery concurrency for the default queue, by default the number of CPUs is used
- # celery concurrency for the default queue, by default it is set to 1
- # Setting it to "null" will make concurrency equal to number of CPUs if autoscaling is not used
- openwisp2_celery_concurrency: null
- # alternative to the previous option, the celery autoscale option can be set if needed
- # for more info, consult the documentation of celery regarding "autoscaling"
- # by default it is set to "null" (no autoscaling)
- openwisp2_celery_autoscale: 4,1
- # prefetch multiplier for the default queue,
- # the default value is calculated automatically by celery
- openwisp2_celery_prefetch_multiplier: null
- # celery queuing mode for the default queue,
- # leaving the default will work for most cases
- openwisp2_celery_optimization: default
- # whether the dedicated celerybeat worker is enabled which is
- # responsible for triggering periodic tasks
- # must be turned on unless there's another server running celerybeat
- openwisp2_celerybeat: true
- # whether the dedicated worker for the celery "network" queue is enabled
- # must be turned on unless there's another server running a worker for this queue
- openwisp2_celery_network: true
- # concurrency option for the "network" queue (a worker is dedicated solely to network operations)
- # the default is 1. Setting it to "null" will make concurrency equal to number of CPUs if autoscaling is not used.
- openwisp2_celery_network_concurrency: null
- # alternative to the previous option, the celery autoscale option can be set if needed
- # for more info, consult the documentation of celery regarding "autoscaling"
- # by default it is set to "null" (no autoscaling)
- openwisp2_celery_network_autoscale: 8,4
- # prefetch multiplier for the "network" queue,
- # the default is 1, which mean no prefetching,
- # because the network tasks are long running and is better
- # to distribute the tasks to multiple processes
- openwisp2_celery_network_prefetch_multiplier: 1
- # celery queuing mode for the "network" queue,
- # fair mode is used in this case, which means
- # tasks will be equally distributed among workers
- openwisp2_celery_network_optimization: fair
- # whether the dedicated worker for the celery "firmware_upgrader" queue is enabled
- # must be turned on unless there's another server running a worker for this queue
- openwisp2_celery_firmware_upgrader: true
- # concurrency option for the "firmware_upgrader" queue (a worker is dedicated solely to firmware upgrade operations)
- # the default is 1. Setting it to "null" will make concurrency equal to number of CPUs if autoscaling is not used
- openwisp2_celery_firmware_upgrader_concurrency: null
- # alternative to the previous option, the celery autoscale option can be set if needed
- # for more info, consult the documentation of celery regarding "autoscaling"
- # by default it is set to "null" (no autoscaling)
- openwisp2_celery_firmware_upgrader_autoscale: 8,4
- # prefetch multiplier for the "firmware_upgrader" queue,
- # the default is 1, which mean no prefetching,
- # because the firmware upgrade tasks are long running and is better
- # to distribute the tasks to multiple processes
- openwisp2_celery_firmware_upgrader_prefetch_multiplier: 1
- # celery queuing mode for the "firmware_upgrader" queue,
- # fair mode is used in this case, which means
- # tasks will be equally distributed among workers
- openwisp2_celery_firmware_upgrader_optimization: fair
- # whether the dedicated worker for the celery "monitoring" queue is enabled
- # must be turned on unless there's another server running a worker for this queue
- openwisp2_celery_monitoring: true
- # concurrency option for the "monitoring" queue (a worker is dedicated solely to monitoring operations)
- # the default is 2. Setting it to "null" will make concurrency equal to number of CPUs
- # if autoscaling is not used.
- openwisp2_celery_monitoring_concurrency: null
- # alternative to the previous option, the celery autoscale option can be set if needed
- # for more info, consult the documentation of celery regarding "autoscaling"
- # by default it is set to "null" (no autoscaling)
- openwisp2_celery_monitoring_autoscale: 4,8
- # prefetch multiplier for the "monitoring" queue,
- # the default is 1, which mean no prefetching,
- # because the monitoring tasks can be long running and is better
- # to distribute the tasks to multiple processes
- openwisp2_celery_monitoring_prefetch_multiplier: 1
- # celery queuing mode for the "monitoring" queue,
- # fair mode is used in this case, which means
- # tasks will be equally distributed among workers
- openwisp2_celery_monitoring_optimization: fair
- # whether the default celery task routes should be written to the settings.py file
- # turn this off if you're defining custom task routing rules
- openwisp2_celery_task_routes_defaults: true
- # celery settings
- openwisp2_celery_broker_url: redis://{{ openwisp2_redis_host }}:{{ openwisp2_redis_port }}/3
- openwisp2_celery_task_acks_late: true
- # maximum number of retries by celery before giving up when broker is unreachable
- openwisp2_celery_broker_max_tries: 10
- # whether to activate the django logging configuration in celery
- # if set to true, will log all the celery events in the same log stream used by django
- # which will cause log lines to be written to "{{ openwisp2_path }}/log/openwisp2.log"
- # instead of "{{ openwisp2_path }}/log/celery.log" and "{{ openwisp2_path }}/log/celerybeat.log"
- openwisp2_django_celery_logging: false
- # postfix is installed by default, set to false if you don't need it
- openwisp2_postfix_install: true
- # allow overriding default `postfix_smtp_sasl_auth_enable` variable
- postfix_smtp_sasl_auth_enable_override: true
- # allow overriding postfix_smtpd_relay_restrictions
- postfix_smtpd_relay_restrictions_override: permit_mynetworks
- # allows overriding the default duration for keeping notifications
- openwisp2_notifications_delete_old_notifications: 10
- # Expiration time limit (in seconds) of magic sign-in links.
- # Magic sign-in links are used only when OpenWISP RADIUS is enabled.
- openwisp2_django_sesame_max_age: 1800 # 30 minutes
- # Maximum file size(in bytes) allowed to be uploaded as firmware image.
- # It overrides "openwisp2_nginx_client_max_body_size" setting
- # and updates nginx configuration accordingly.
- openwisp2_firmware_upgrader_max_file_size: 41943040 # 40MB
- # to add multi-language support
- openwisp2_internationalization: true
- openwisp2_users_auth_api: true
- # Allows setting OPENWISP_USERS_USER_PASSWORD_EXPIRATION setting.
- # Read https://github.com/openwisp/openwisp-users#openwisp_users_user_password_expiration
- openwisp2_users_user_password_expiration: 30
- # Allows setting OPENWISP_USERS_STAFF_USER_PASSWORD_EXPIRATION setting.
- # Read https://github.com/openwisp/openwisp-users#openwisp_users_staff_user_password_expiration
- openwisp2_users_staff_user_password_expiration: 30
- # used for SMS verification, the default is a dummy SMS backend
- # which prints to standard output and hence does nothing
- # one of the available providers from django-sendsms can be
- # used or alternatively, you can write a backend class for your
- # favorite SMS API gateway
- openwisp2_radius_sms_backend: "sendsms.backends.console.SmsBackend"
- openwisp2_radius_sms_token_max_ip_daily: 25
- openwisp2_radius_delete_old_radiusbatch_users: 365
- openwisp2_radius_cleanup_stale_radacct: 1
- openwisp2_radius_delete_old_postauth: 365
- # days for which the radius accounting sessions (radacct) are retained,
- # 0 means sessions are kept forever.
- # we highly suggest to set this number according
- # to the privacy regulation of your jurisdiction
- openwisp2_radius_delete_old_radacct: 365
- # days after which inactive users will flagged as unverified
- # Read https://openwisp-radius.readthedocs.io/en/latest/user/settings.html#openwisp-radius-unverify-inactive-users
- openwisp2_radius_unverify_inactive_users: 540
- # days after which inactive users will be deleted
- # Read https://openwisp-radius.readthedocs.io/en/latest/user/settings.html#openwisp-radius-delete-inactive-users
- openwisp2_radius_delete_inactive_users: 540
- openwisp2_radius_allowed_hosts: ["127.0.0.1"]
- # allow disabling celery beat tasks if needed
- openwisp2_monitoring_periodic_tasks: true
- openwisp2_radius_periodic_tasks: true
- openwisp2_usage_metric_collection_periodic_tasks: true
- # this role provides a default configuration of freeradius
- # if you manage freeradius on a different machine or you need different configurations
- # you can disable this default behavior
- openwisp2_freeradius_install: true
- # Set an account to expire T seconds after first login.
- # This variable sets the value of T.
- freeradius_expire_attr_after_seconds: 86400
- freeradius_dir: /etc/freeradius/3.0
- freeradius_mods_available_dir: "{{ freeradius_dir }}/mods-available"
- freeradius_mods_enabled_dir: "{{ freeradius_dir }}/mods-enabled"
- freeradius_sites_available_dir: "{{ freeradius_dir }}/sites-available"
- freeradius_sites_enabled_dir: "{{ freeradius_dir }}/sites-enabled"
- freeradius_rest:
- url: "https://{{ inventory_hostname }}/api/v1/freeradius"
- freeradius_safe_characters: "+@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"
- # Sets the source path of the template that contains freeradius site configuration.
- # Defaults to "templates/freeradius/openwisp_site.j2" shipped in the role.
- freeradius_openwisp_site_template_src: custom_freeradius_site.j2
- # Whether to deploy the default openwisp_site for FreeRADIUS.
- # Defaults to true.
- freeradius_deploy_openwisp_site: false
- # FreeRADIUS listen address for the openwisp_site.
- # Defaults to "*", i.e. listen on all interfaces.
- freeradius_openwisp_site_listen_ipaddr: "10.8.0.1"
- # A list of dict that includes organization's name, UUID, RADIUS token,
- # TLS configuration, and ports for authentication, accounting, and inner tunnel.
- # This list of dict is used to generate FreeRADIUS sites that support
- # WPA Enterprise (EAP-TTLS-PAP) authentication.
- # Defaults to an empty list.
- freeradius_eap_orgs:
- # The name should not contain spaces or special characters
- - name: openwisp
- # UUID of the organization can be retrieved from the OpenWISP admin
- uuid: 00000000-0000-0000-0000-000000000000
- # Radius token of the organization can be retrieved from the OpenWISP admin
- radius_token: secret-radius-token
- # Port used by the authentication service for this FreeRADIUS site
- auth_port: 1832
- # Port used by the accounting service for this FreeRADIUS site
- acct_port: 1833
- # Port used by the authentication service of inner tunnel for this FreeRADIUS site
- inner_tunnel_auth_port: 18330
- # CA certificate for the FreeRADIUS site
- ca: /etc/freeradius/certs/ca.crt
- # TLS certificate for the FreeRADIUS site
- cert: /etc/freeradius/certs/cert.pem
- # TLS private key for the FreeRADIUS site
- private_key: /etc/freeradius/certs/key.pem
- # Diffie-Hellman key for the FreeRADIUS site
- dh: /etc/freeradius/certs/dh
- # Extra instructions for the "tls-config" section of the EAP module
- # for the FreeRADIUS site
- tls_config_extra: |
- private_key_password = whatever
- ecdh_curve = "prime256v1"
- # Sets the source path of the template that contains freeradius site configuration
- # for WPA Enterprise (EAP-TTLS-PAP) authentication.
- # Defaults to "templates/freeradius/eap/openwisp_site.j2" shipped in the role.
- freeradius_eap_openwisp_site_template_src: custom_eap_openwisp_site.j2
- # Sets the source path of the template that contains freeradius inner tunnel
- # configuration for WPA Enterprise (EAP-TTLS-PAP) authentication.
- # Defaults to "templates/freeradius/eap/inner_tunnel.j2" shipped in the role.
- freeradius_eap_inner_tunnel_template_src: custom_eap_inner_tunnel.j2
- # Sets the source path of the template that contains freeradius EAP configuration
- # for WPA Enterprise (EAP-TTLS-PAP) authentication.
- # Defaults to "templates/freeradius/eap/eap.j2" shipped in the role.
- freeradius_eap_template_src: custom_eap.j2
- cron_delete_old_notifications: "'hour': 0, 'minute': 0"
- cron_deactivate_expired_users: "'hour': 0, 'minute': 5"
- cron_delete_old_radiusbatch_users: "'hour': 0, 'minute': 10"
- cron_cleanup_stale_radacct: "'hour': 0, 'minute': 20"
- cron_delete_old_postauth: "'hour': 0, 'minute': 30"
- cron_delete_old_radacct: "'hour': 1, 'minute': 30"
- cron_password_expiration_email: "'hour': 1, 'minute': 0"
- cron_unverify_inactive_users: "'hour': 1, 'minute': 45"
- cron_delete_inactive_users: "'hour': 1, 'minute': 55"
- # cross-origin resource sharing (CORS) settings
- # https://pypi.org/project/django-cors-headers/
- openwisp2_django_cors:
- # Setting this to "true" will install the django-cors-headers package
- # and configure the Django middleware setting to support CORS.
- # By default, it is set to false.
- enabled: true
- # Configures "CORS_ALLOWED_ORIGINS" setting of the django-cors-headers
- # package. A list of origins that are authorized to make cross-site
- # HTTP requests. Read https://github.com/adamchainz/django-cors-headers#cors_allowed_origins-sequencestr
- # for detail. By default, it is set to an empty list.
- allowed_origins_list: ["https://log.openwisp.org"]
- # Configures "CORS_REPLACE_HTTPS_REFERER" setting of the django-cors-headers
- # package. Read https://github.com/adamchainz/django-cors-headers#cors_replace_https_referer-bool
- # for detail. Setting this to "true" will also configure the
- # Django middleware setting to add "CorsPostCsrfMiddleware".
- # By default, it is set to false.
- replace_https_referer: true
-```
-
-**Note**: The default values for settings provided to control the number of process and threads
-of uWSGI and Daphne are set conservatively. It is expected from user to update these settings
-to suit scale of their project. The same thing applies for concurrency and autoscale settings
-for celery workers.
-
-Support
-=======
-
-See [OpenWISP Support Channels](http://openwisp.org/support.html).
diff --git a/README.rst b/README.rst
new file mode 100644
index 00000000..ec81a0c3
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,32 @@
+ansible-openwisp2
+=================
+
+.. image:: https://github.com/openwisp/ansible-openwisp2/workflows/Ansible%20OpenWISP2%20CI%20Build/badge.svg?branch=master
+ :target: https://github.com/openwisp/ansible-openwisp2/actions?query=workflow%3A%22Ansible+OpenWISP2+CI+Build%22
+ :alt: Build Status
+
+.. image:: http://img.shields.io/badge/galaxy-openwisp.openwisp2-blue.svg?style=flat-square
+ :target: https://galaxy.ansible.com/ui/standalone/roles/openwisp/openwisp2/
+ :alt: Galaxy
+
+.. image:: https://img.shields.io/gitter/room/nwjs/nw.js.svg
+ :target: https://gitter.im/openwisp/general
+ :alt: Chat
+
+Ansible role that installs the OpenWISP Server Application.
+
+Tested on **Debian (Bookworm/Bullseye)**, **Ubuntu (24/22/20 LTS)**.
+
+**Recommended minimum ansible core version**: 2.13.
+
+Documentation
+-------------
+
+- `Developer documentation
+ `_
+- `User documentation `_
+
+Support
+-------
+
+See `OpenWISP Support Channels `__.
diff --git a/docs/developer/installation.rst b/docs/developer/installation.rst
new file mode 100644
index 00000000..22a57a2a
--- /dev/null
+++ b/docs/developer/installation.rst
@@ -0,0 +1,100 @@
+Developer Installation instructions
+===================================
+
+.. include:: ../partials/developer-docs.rst
+
+.. contents:: **Table of Contents**:
+ :depth: 2
+ :local:
+
+Installing for Development
+--------------------------
+
+First of all, create the directory where you want to place the
+repositories of the ansible roles and create directory roles.
+
+.. code-block:: bash
+
+ mkdir -p ~/openwisp-dev/roles
+ cd ~/openwisp-dev/roles
+
+Clone ``ansible-openwisp2`` and ``Stouts.postfix`` as follows:
+
+.. code-block:: bash
+
+ git clone https://github.com/openwisp/ansible-openwisp2.git openwisp.openwisp2
+ git clone https://github.com/Stouts/Stouts.postfix
+ git clone https://github.com/openwisp/ansible-ow-influxdb openwisp.influxdb
+
+Now, go to the parent directory & create hosts file and playbook.yml:
+
+.. code-block:: bash
+
+ cd ../
+ touch hosts
+ touch playbook.yml
+
+From here on you can follow the instructions available at the following
+sections:
+
+- :ref:`ansible_install`
+- :ref:`ansible_create_inventory_file`
+- :ref:`ansible_create_playbook_file`
+- :ref:`ansible_run_playbook`
+
+All done!
+
+How to Run Tests
+----------------
+
+If you want to contribute to ``ansible-openwisp2`` you should run tests in
+your development environment to ensure your changes are not breaking
+anything.
+
+To do that, proceed with the following steps:
+
+**Step 1**: Clone ``ansible-openwisp2``
+
+Clone repository by:
+
+.. code-block:: shell
+
+ git clone https://github.com//ansible-openwisp2.git openwisp.openwisp2
+ cd openwisp.openwisp2
+
+**Step 2**: Install docker
+
+If you haven't installed docker yet, you need to install it (example for
+linux debian/ubuntu systems):
+
+.. code-block:: shell
+
+ sudo apt install docker.io
+
+**Step 3**: Install molecule and dependencies
+
+.. code-block:: shell
+
+ pip install molecule[docker] molecule-plugins yamllint ansible-lint docker
+
+**Step 4**: Download docker images
+
+.. code-block:: shell
+
+ docker pull geerlingguy/docker-ubuntu2204-ansible:latest
+ docker pull geerlingguy/docker-ubuntu2004-ansible:latest
+ docker pull geerlingguy/docker-debian11-ansible:latest
+
+**Step 5**: Run molecule test
+
+.. code-block:: shell
+
+ molecule test -s local
+
+If you don't get any error message it means that the tests ran
+successfully without errors.
+
+.. important::
+
+ **Pro Tip:** Use ``molecule test --destroy=never`` to speed up
+ subsequent test runs.
diff --git a/docs/images/architecture-v2-ansible-openwisp.png b/docs/images/architecture-v2-ansible-openwisp.png
new file mode 100644
index 00000000..e9fe7f2f
Binary files /dev/null and b/docs/images/architecture-v2-ansible-openwisp.png differ
diff --git a/docs/debian-software-selection.png b/docs/images/debian-software-selection.png
similarity index 100%
rename from docs/debian-software-selection.png
rename to docs/images/debian-software-selection.png
diff --git a/docs/host-only-network.png b/docs/images/host-only-network.png
similarity index 100%
rename from docs/host-only-network.png
rename to docs/images/host-only-network.png
diff --git a/docs/install-openwisp2.png b/docs/images/install-openwisp2.png
similarity index 100%
rename from docs/install-openwisp2.png
rename to docs/images/install-openwisp2.png
diff --git a/docs/openwisp2-modules-diagram.png b/docs/images/openwisp2-modules-diagram.png
similarity index 100%
rename from docs/openwisp2-modules-diagram.png
rename to docs/images/openwisp2-modules-diagram.png
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 00000000..d02f960b
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,51 @@
+Ansible OpenWISP
+================
+
+.. seealso::
+
+ **Source code**: `github.com/openwisp/ansible-openwisp2
+ `_.
+
+This ansible role allows deploying the OpenWISP Server Application.
+
+**Recommended minimum ansible core version**: 2.13.
+
+Tested on **Debian (Bookworm/Bullseye)**, **Ubuntu (24/22/20 LTS)**.
+
+The following diagram illustrates the role of the Ansible OpenWISP role
+within the OpenWISP architecture.
+
+.. figure:: images/architecture-v2-ansible-openwisp.png
+ :target: ../_images/architecture-v2-ansible-openwisp.png
+ :align: center
+ :alt: OpenWISP Architecture: Ansible OpenWISP role
+
+ **OpenWISP Architecture: highlighted Ansible OpenWISP role**
+
+.. important::
+
+ For an enhanced viewing experience, open the image above in a new
+ browser tab.
+
+ Refer to :doc:`/general/architecture` for more information.
+
+.. toctree::
+ :caption: Ansible OpenWISP Usage Docs
+ :maxdepth: 1
+
+ ./user/system-requirements.rst
+ ./user/quickstart.rst
+ ./user/certbot-ssl.rst
+ ./user/enabling-modules.rst
+ ./user/deploying-wpa-eap-ttls-pap.rst
+ ./user/deploying-custom-static-content.rst
+ ./user/configuring-cors-headers.rst
+ ./user/installing-on-vm.rst
+ ./user/troubleshooting.rst
+ ./user/role-variables.rst
+
+.. toctree::
+ :caption: Ansible OpenWISP Developer Docs
+ :maxdepth: 1
+
+ ./developer/installation.rst
diff --git a/docs/partials/developer-docs.rst b/docs/partials/developer-docs.rst
new file mode 100644
index 00000000..76dede26
--- /dev/null
+++ b/docs/partials/developer-docs.rst
@@ -0,0 +1,12 @@
+.. note::
+
+ This documentation page is aimed at developers who want to customize,
+ change or extend the code of Ansible OpenWISP2 in order to modify its
+ behavior (eg: for personal or commercial purposes or to fix a bug,
+ implement a new feature or contribute to the project in general).
+
+ If you aren't a developer and you are looking for information on how
+ to use OpenWISP, please refer to:
+
+ - :doc:`General OpenWISP Quickstart `
+ - :doc:`Ansible OpenWISP2 User Docs `
diff --git a/docs/user/certbot-ssl.rst b/docs/user/certbot-ssl.rst
new file mode 100644
index 00000000..42600a12
--- /dev/null
+++ b/docs/user/certbot-ssl.rst
@@ -0,0 +1,65 @@
+Using Let's Encrypt SSL Certificate
+===================================
+
+This section explains how to **automatically install and renew a valid SSL
+certificate** signed by `Let's Encrypt `__.
+
+The first thing you have to do is to setup a valid domain for your
+OpenWISP instance, this means your inventory file (hosts) should look like
+the following:
+
+.. code-block:: text
+
+ [openwisp2]
+ openwisp2.yourdomain.com
+
+You must be able to add a DNS record for ``openwisp2.yourdomain.com``, you
+cannot use an ip address in place of ``openwisp2.yourdomain.com``.
+
+Once your domain is set up and the DNS record is propagated, proceed by
+installing the ansible role `geerlingguy.certbot
+`__:
+
+.. code-block:: shell
+
+ ansible-galaxy install geerlingguy.certbot
+
+Then proceed to edit your ``playbook.yml`` so that it will look similar to
+the following example:
+
+.. code-block:: yaml
+
+ - hosts: openwisp2
+ become: "{{ become | default('yes') }}"
+ roles:
+ - geerlingguy.certbot
+ - openwisp.openwisp2
+ vars:
+ # SSL certificates
+ openwisp2_ssl_cert: "/etc/letsencrypt/live/{{ inventory_hostname }}/fullchain.pem"
+ openwisp2_ssl_key: "/etc/letsencrypt/live/{{ inventory_hostname }}/privkey.pem"
+
+ # certbot configuration
+ certbot_auto_renew_minute: "20"
+ certbot_auto_renew_hour: "5"
+ certbot_create_if_missing: true
+ certbot_auto_renew_user: ""
+ certbot_certs:
+ - email: ""
+ domains:
+ - "{{ inventory_hostname }}"
+ pre_tasks:
+ - name: Update APT package cache
+ apt:
+ update_cache: true
+ changed_when: false
+ retries: 5
+ delay: 10
+ register: result
+ until: result is success
+
+Read the `documentation of geerlingguy.certbot
+`__ to learn
+more about configuration of certbot role.
+
+Once you have set up all the variables correctly, run the playbook again.
diff --git a/docs/user/configuring-cors-headers.rst b/docs/user/configuring-cors-headers.rst
new file mode 100644
index 00000000..7bf84128
--- /dev/null
+++ b/docs/user/configuring-cors-headers.rst
@@ -0,0 +1,73 @@
+Configuring CORS Headers
+========================
+
+While integrating OpenWISP with external services, you can run into issues
+related to `CORS (Cross-Origin Resource Sharing)
+`__. This role
+allows users to configure the CORS headers with the help of
+`django-cors-headers
+`__ package. Here's a
+short summary of how to do this:
+
+**Step 1**: :ref:`Install ansible `
+
+**Step 2**: :ref:`Install this role `
+
+**Step 3**: :ref:`Create inventory file `
+
+**Step 4**: Create a playbook file with following contents:
+
+.. code-block:: yaml
+
+ - hosts: openwisp2
+ become: "{{ become | default('yes') }}"
+ roles:
+ - openwisp.openwisp2
+ vars:
+ # Cross-Origin Resource Sharing (CORS) settings
+ openwisp2_django_cors:
+ enabled: true
+ allowed_origins_list:
+ - https://frontend.openwisp.org
+ - https://logs.openwisp.org
+
+**Note:** to learn about the supported fields of the
+``openwisp2_django_cors`` variable, look for the word
+“openwisp2_django_cors” in the :doc:`role-variables` section of this
+document.
+
+**Step 5**: :ref:`Run the playbook `
+
+When the playbook is done running, if you got no errors you can login at
+https://openwisp2.mydomain.com/admin, with the following credentials:
+
+.. code-block:: text
+
+ username: admin
+ password: admin
+
+The ansible-openwisp2 only provides abstraction (variables) for handful of
+settings available in `django-cors-headers
+`__ module. Use the
+``openwisp2_extra_django_settings_instructions`` or
+``openwisp2_extra_django_settings`` variable to configure additional
+setting of ``django-cors-headers`` as shown in the following example:
+
+.. code-block:: yaml
+
+ - hosts: openwisp2
+ become: "{{ become | default('yes') }}"
+ roles:
+ - openwisp.openwisp2
+ vars:
+ openwisp2_django_cors:
+ enabled: true
+ allowed_origins_list:
+ - https://frontend.openwisp.org
+ - https://logs.openwisp.org
+ replace_https_referer: true
+ # Configuring additional settings for django-cors-headers
+ openwisp2_extra_django_settings_instructions:
+ - |
+ CORS_ALLOW_CREDENTIALS = True
+ CORS_ALLOW_ALL_ORIGINS = True
diff --git a/docs/user/deploying-custom-static-content.rst b/docs/user/deploying-custom-static-content.rst
new file mode 100644
index 00000000..e5844828
--- /dev/null
+++ b/docs/user/deploying-custom-static-content.rst
@@ -0,0 +1,14 @@
+Deploying Custom Static Content
+===============================
+
+For deploying custom static content (HTML files, etc.) add all the static
+content in ``files/ow2_static`` directory. The files inside
+``files/ow2_static`` will be uploaded to a directory named
+``static_custom`` in ``openwisp2_path``.
+
+This is helpful for :ref:`customizing OpenWISP's theme
+`.
+
+E.g., if you added a custom CSS file in
+``files/ow2_static/css/custom.css``, the file location to use in
+:ref:`openwisp_admin_theme_links` setting will be ``css/custom.css``.
diff --git a/docs/user/deploying-wpa-eap-ttls-pap.rst b/docs/user/deploying-wpa-eap-ttls-pap.rst
new file mode 100644
index 00000000..ea3b548d
--- /dev/null
+++ b/docs/user/deploying-wpa-eap-ttls-pap.rst
@@ -0,0 +1,175 @@
+Configuring FreeRADIUS for WPA Enterprise (EAP-TTLS-PAP)
+========================================================
+
+You can use OpenWISP RADIUS for setting up WPA Enterprise (EAP-TTLS-PAP)
+authentication. This allows to authenticate on WiFi networks using Django
+user credentials. Prior to proceeding, ensure you've reviewed the tutorial
+on :doc:`/tutorials/wpa-enterprise-eap-ttls-pap`. This documentation
+section complements the tutorial and focuses solely on demonstrating the
+ansible role's capabilities to configure FreeRADIUS.
+
+.. important::
+
+ The ansible role supports OpenWISP's multi-tenancy by creating
+ individual FreeRADIUS sites for each organization. You must include
+ configuration details for **each organization** that will use WPA
+ Enterprise.
+
+Here's an example playbook which enables OpenWISP RADIUS module, installs
+FreeRADIUS, and configures it for WPA Enterprise (EAP-TTLS-PAP):
+
+.. code-block:: yaml
+
+ - hosts: openwisp2
+ become: "{{ become | default('yes') }}"
+ roles:
+ - openwisp.openwisp2
+ vars:
+ openwisp2_radius: true
+ openwisp2_freeradius_install: true
+ # Define a list of dictionaries detailing each organization's
+ # name, UUID, RADIUS token, and ports for authentication,
+ # accounting, and the inner tunnel. These details will be used
+ # to create FreeRADIUS sites tailored for WPA Enterprise
+ # (EAP-TTLS-PAP) authentication per organization.
+ freeradius_eap_orgs:
+ # A reference name for the organization,
+ # used in FreeRADIUS configurations.
+ # Don't use spaces or special characters.
+ - name: openwisp
+ # UUID of the organization.
+ # You can retrieve this from the organization admin
+ # in the OpenWISP web interface.
+ uuid: 00000000-0000-0000-0000-000000000000
+ # Radius token of the organization.
+ # You can retrieve this from the organization admin
+ # in the OpenWISP web interface.
+ radius_token: secret-radius-token
+ # Port used by the authentication service for
+ # this FreeRADIUS site
+ auth_port: 1822
+ # Port used by the accounting service for this FreeRADIUS site
+ acct_port: 1823
+ # Port used by the authentication service of inner tunnel
+ # for this FreeRADIUS site
+ inner_tunnel_auth_port: 18230
+ # If you want to use a custom certificate for FreeRADIUS
+ # EAP module, you can specify the path to the CA, server
+ # certificate, and private key, and DH key as follows.
+ # Ensure that these files can be read by the "freerad" user.
+ cert: /etc/freeradius/certs/cert.pem
+ private_key: /etc/freeradius/certs/key.pem
+ ca: /etc/freeradius/certs/ca.crt
+ dh: /etc/freeradius/certs/dh
+ tls_config_extra: |
+ private_key_password = whatever
+ ecdh_curve = "prime256v1"
+ # You can add as many organizations as you want
+ - name: demo
+ uuid: 00000000-0000-0000-0000-000000000001
+ radius_secret: demo-radius-token
+ auth_port: 1832
+ acct_port: 1833
+ inner_tunnel_auth_port: 18330
+ # If you omit the certificate fields,
+ # the FreeRADIUS site will use the default certificates
+ # located in /etc/freeradius/certs.
+
+In the example above, custom ports 1822, 1823, and 18230 are utilized for
+FreeRADIUS authentication, accounting, and inner tunnel authentication,
+respectively. These custom ports are specified because the Ansible role
+creates a common FreeRADIUS site for all organizations, which also
+supports captive portal functionality. This common site is configured to
+listen on the default FreeRADIUS ports 1812, 1813, and 18120. Therefore,
+when configuring WPA Enterprise authentication for each organization,
+unique ports must be provided to ensure proper isolation and
+functionality.
+
+Using Let's Encrypt Certificate for WPA Enterprise (EAP-TTLS-PAP)
+-----------------------------------------------------------------
+
+In this section, we demonstrate how to utilize Let's Encrypt certificates
+for WPA Enterprise (EAP-TTLS-PAP) authentication. Similar to the
+:doc:`./certbot-ssl`, we use `geerlingguy.certbot
+`_ role to automatically
+install and renew a valid SSL certificate.
+
+The following example playbook achieves the following goals:
+
+- Provision a separate Let's Encrypt certificate for the
+ `freeradius.yourdomain.com` hostname. This certificate will be utilized
+ by the FreeRADIUS site for WPA Enterprise authentication.
+- Create a renewal hook to set permissions on the generated certificate so
+ the FreeRADIUS server can read it.
+
+.. note::
+
+ You can also use the same SSL certificate for both Nginx and
+ FreeRADIUS, but it's crucial to understand the security implications.
+ Please exercise caution and refer to the example playbook comments for
+ guidance.
+
+.. code-block:: yaml
+
+ - hosts: openwisp2
+ become: "{{ become | default('yes') }}"
+ roles:
+ - geerlingguy.certbot
+ - openwisp.openwisp2
+ vars:
+ # certbot configuration
+ certbot_auto_renew_minute: "20"
+ certbot_auto_renew_hour: "5"
+ certbot_create_if_missing: true
+ certbot_auto_renew_user: ""
+ certbot_certs:
+ - email: ""
+ domains:
+ - "{{ inventory_hostname }}"
+ # If you choose to re-use the same certificate for both services,
+ # you can omit the following item in your playbook.
+ - email: ""
+ domains:
+ - "freeradius.yourdomain.com"
+ # Configuration to use Let's Encrypt certificate for OpenWISP server (Nnginx)
+ openwisp2_ssl_cert: "/etc/letsencrypt/live/{{ inventory_hostname }}/fullchain.pem"
+ openwisp2_ssl_key: "/etc/letsencrypt/live/{{ inventory_hostname }}/privkey.pem"
+ # Configuration for openwisp-radius
+ openwisp2_radius: true
+ openwisp2_freeradius_install: true
+ freeradius_eap_orgs:
+ - name: demo
+ uuid: 00000000-0000-0000-0000-000000000001
+ radius_secret: demo-radius-token
+ auth_port: 1832
+ acct_port: 1833
+ inner_tunnel_auth_port: 18330
+ # Update the cert_file and private_key paths to point to the
+ # Let's Encrypt certificate.
+ cert: /etc/letsencrypt/live/freeradius.yourdomain.com/fullchain.pem
+ private_key: /etc/letsencrypt/live/freeradius.yourdomain.com/privkey.pem
+ # If you choose to re-use the same certificate for both services,
+ # your configuration would look like this
+ # cert: /etc/letsencrypt/live/{{ inventory_hostname }}/fullchain.pem
+ # private_key: /etc/letsencrypt/live/{{ inventory_hostname }}/privkey.pem
+ tasks:
+ # Tasks to ensure the Let's Encrypt certificate can be read by the FreeRADIUS server.
+ # If you are using the same certificate for both services, you need to
+ # replace "freeradius.yourdomain.com" with "{{ inventory_hostname }}"
+ # in the following task.
+ - name: "Create a renewal hook for setting permissions on /etc/letsencrypt/live/freeradius.yourdomain.com"
+ copy:
+ content: |
+ #!/bin/bash
+ chown -R root:freerad /etc/letsencrypt/live/ /etc/letsencrypt/archive/
+ chmod 0750 /etc/letsencrypt/live/ /etc/letsencrypt/archive/
+ chmod -R 0640 /etc/letsencrypt/archive/freeradius.yourdomain.com/
+ chmod 0750 /etc/letsencrypt/archive/freeradius.yourdomain.com/
+ dest: /etc/letsencrypt/renewal-hooks/post/chown_freerad
+ owner: root
+ group: root
+ mode: '0700'
+ register: chown_freerad_result
+ - name: Change the ownership of the certificate files
+ when: chown_freerad_result.changed
+ command: /etc/letsencrypt/renewal-hooks/post/chown_freerad
diff --git a/docs/user/enabling-modules.rst b/docs/user/enabling-modules.rst
new file mode 100644
index 00000000..dd2cf725
--- /dev/null
+++ b/docs/user/enabling-modules.rst
@@ -0,0 +1,164 @@
+Enabling OpenWISP Modules
+=========================
+
+.. contents:: **Table of Contents**:
+ :depth: 3
+ :local:
+
+Enabling the Monitoring Module
+------------------------------
+
+The :doc:`Monitoring module ` is enabled by default, it
+can be disabled by setting ``openwisp2_monitoring`` to ``false``.
+
+Enabling the Firmware Upgrader Module
+-------------------------------------
+
+It is encouraged that you read the :doc:`quick-start guide of
+openwisp-firmware-upgrader ` before
+going ahead.
+
+To enable the :doc:`Firmware Upgrader ` module
+you need to set ``openwisp2_firmware_upgrader`` to ``true`` in your
+``playbook.yml`` file. Here's a short summary of how to do this:
+
+**Step 1**: :ref:`Install ansible `
+
+**Step 2**: :ref:`Install this role `
+
+**Step 3**: :ref:`Create inventory file `
+
+**Step 4**: Create a playbook file with following contents:
+
+.. code-block:: yaml
+
+ - hosts: openwisp2
+ become: "{{ become | default('yes') }}"
+ roles:
+ - openwisp.openwisp2
+ vars:
+ openwisp2_firmware_upgrader: true
+
+**Step 5**: :ref:`Run the playbook `
+
+When the playbook is done running, if you got no errors you can login at
+https://openwisp2.mydomain.com/admin with the following credentials:
+
+.. code-block:: text
+
+ username: admin
+ password: admin
+
+You can configure :doc:`openwisp-firmware-upgrader specific settings
+` using the
+``openwisp2_extra_django_settings`` or
+``openwisp2_extra_django_settings_instructions``.
+
+E.g:
+
+.. code-block:: yaml
+
+ - hosts: openwisp2
+ become: "{{ become | default('yes') }}"
+ roles:
+ - openwisp.openwisp2
+ vars:
+ openwisp2_firmware_upgrader: true
+ openwisp2_extra_django_settings_instructions:
+ - |
+ OPENWISP_CUSTOM_OPENWRT_IMAGES = (
+ ('my-custom-image-squashfs-sysupgrade.bin', {
+ 'label': 'My Custom Image',
+ 'boards': ('MyCustomImage',)
+ }),
+ )
+
+Refer the :doc:`role-variables` section of the documentation for a
+complete list of available role variables.
+
+Enabling the Network Topology Module
+------------------------------------
+
+To enable the :doc:`Network Topology module ` you
+need to set ``openwisp2_network_topology`` to ``true`` in your
+``playbook.yml`` file. Here's a short summary of how to do this:
+
+**Step 1**: :ref:`Install ansible `
+
+**Step 2**: :ref:`Install this role `
+
+**Step 3**: :ref:`Create inventory file `
+
+**Step 4**: Create a playbook file with following contents:
+
+.. code-block:: yaml
+
+ - hosts: openwisp2
+ become: "{{ become | default('yes') }}"
+ roles:
+ - openwisp.openwisp2
+ vars:
+ openwisp2_network_topology: true
+
+**Step 5**: :ref:`Run the playbook `
+
+When the playbook is done running, if you got no errors you can login at
+https://openwisp2.mydomain.com/admin with the following credentials:
+
+.. code-block:: text
+
+ username: admin
+ password: admin
+
+.. _ansible_enabling_radius_module:
+
+Enabling the RADIUS Module
+--------------------------
+
+To enable the :doc:`RADIUS module ` you need to set
+``openwisp2_radius`` to ``true`` in your ``playbook.yml`` file. Here's a
+short summary of how to do this:
+
+**Step 1**: :ref:`Install ansible `
+
+**Step 2**: :ref:`Install this role `
+
+**Step 3**: :ref:`Create inventory file `
+
+**Step 4**: Create a playbook file with following contents:
+
+.. code-block:: yaml
+
+ - hosts: openwisp2
+ become: "{{ become | default('yes') }}"
+ roles:
+ - openwisp.openwisp2
+ vars:
+ openwisp2_radius: true
+ openwisp2_freeradius_install: true
+ # set to false when you don't want to register openwisp-radius
+ # API endpoints.
+ openwisp2_radius_urls: true
+
+.. note::
+
+ ``openwisp2_freeradius_install`` option provides a basic configuration
+ of freeradius for OpenIWSP, it sets up the `radius user token
+ mechanism
+ `__
+ if you want to use another mechanism or manage your freeradius
+ separately, please disable this option by setting it to ``false``.
+
+**Step 5**: :ref:`Run the playbook `
+
+When the playbook is done running, if you got no errors you can login at:
+
+.. code-block::
+
+ https://openwisp2.mydomain.com/admin
+ username: admin
+ password: admin
+
+**Note:** for more information regarding radius configuration options,
+look for the word “radius” in the :doc:`role-variables` section of this
+document.
diff --git a/docs/user/installing-on-vm.rst b/docs/user/installing-on-vm.rst
new file mode 100644
index 00000000..a43bd724
--- /dev/null
+++ b/docs/user/installing-on-vm.rst
@@ -0,0 +1,143 @@
+Install OpenWISP for Testing in a VirtualBox VM
+===============================================
+
+If you want to try out OpenWISP in your own development environment, the
+safest way is to use a VirtualBox Virtual Machine (from here on VM).
+
+.. contents:: **Table of Contents**:
+ :depth: 2
+ :local:
+
+Using Vagrant
+-------------
+
+**Since August 2018 there's a new fast and easy way to install OpenWISP
+for testing purposes** leveraging `Vagrant `__,
+a popular open source tool for building and maintaining portable virtual
+software development environments.
+
+To use this new way, clone the repository `vagrant-openwisp2
+`__, it contains the
+instructions (in the ``README.md``) and the vagrant configuration to
+perform the automatic installation.
+
+Alternatively, you can read on to learn how to install *VirtualBox* and
+run *ansible-openwisp2* manually, this is useful if you need to test
+advanced customizations of *OpenWISP*.
+
+Installing Debian 11 on VirtualBox
+----------------------------------
+
+Install `VirtualBox `__ and create a new Virtual
+Machine running Debian 11. A step-by-step guide is available `here
+`__,
+however we need to change a few things to get ansible working.
+
+VM Configuration
+----------------
+
+Proceed with the installation as shown in the guide linked above, and come
+back here when you see this screen:
+
+.. figure:: ../images/debian-software-selection.png
+ :target: ../../_images/debian-software-selection.png
+ :alt: Screenshot of the Software Selection screen
+
+We're only running this as a server, so you can uncheck ``Debian desktop
+environment``. Make sure ``SSH server`` and ``standard system utilities``
+are checked.
+
+Next, add a `Host-only Network Adapter
+`__ and
+assign an IP address to the VM.
+
+- On the Main VirtualBox page, Go to ``File > Host Network Manager``
+- Click the + icon to create a new adapter
+- Set the IPv4 address to ``192.168.56.1`` and the IPv4 Network Mask to
+ ``255.255.255.0``. You may need to select ``Configure Adapter Manually``
+ to do this. The IPv6 settings can be ignored
+
+ .. image:: ../images/host-only-network.png
+ :target: ../../_images/host-only-network.png
+ :alt: Screenshot of the Host-only network configuration screen
+
+- Shut off your VM
+- In your VM settings, in the Network section, click Adapter 2 and Enable
+ this Adapter
+- Select Host-only adapter and the name of the adapter you created
+- Boot up your VM, run ``su``, and type in your superuser password
+- Run ``ls /sys/class/net`` and take note of the output
+- Run ``nano /etc/network/interfaces`` and add the following at the end of
+ the file:
+
+ .. code-block:: text
+
+ auto enp0s8
+ iface enp0s8 inet static
+ address 192.168.56.2
+ netmask 255.255.255.0
+ network 192.168.56.0
+ broadcast 192.168.56.255
+
+ Replace ``enp0s8`` with the network interface not present in the file
+ but is shown when running ``ls /sys/class/net``
+
+- Save the file with CtrlO then Enter, and exit with CtrlX
+- Restart the machine by running ``reboot``
+
+Make sure you can access your VM via ssh:
+
+.. code-block:: shell
+
+ ssh 192.168.56.2
+
+Back to your local machine
+--------------------------
+
+Proceed with these steps in your **local machine**, not the VM.
+
+**Step 1**: :ref:`Install ansible `
+
+**Step 2**: :ref:`Install the OpenWISP2 role for Ansible
+`
+
+**Step 3**: :ref:`Set up a working directory
+`
+
+**Step 4**: Create the ``hosts`` file
+
+Create an ansible inventory file named ``hosts`` **in your working
+directory** (i.e. not in the VM) with the following contents:
+
+.. code-block::
+
+ [openwisp2]
+ 192.168.56.2
+
+**Step 5**: Create the ansible playbook
+
+In the same directory where you created the ``host`` file, create a file
+named ``playbook.yml`` which contains the following:
+
+.. code-block:: yaml
+
+ - hosts: openwisp2
+ roles:
+ - openwisp.openwisp2
+ # the following line is needed only when an IP address is used as the inventory hostname
+ vars:
+ postfix_myhostname: localhost
+
+**Step 6**: Run the playbook
+
+.. code-block:: shell
+
+ ansible-playbook -i hosts playbook.yml -b -k -K --become-method=su
+
+When the playbook ran successfully, you can log in at
+``https://192.168.56.2/admin`` with the following credentials:
+
+.. code-block:: text
+
+ username: admin
+ password: admin
diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst
new file mode 100644
index 00000000..e73932a0
--- /dev/null
+++ b/docs/user/quickstart.rst
@@ -0,0 +1,333 @@
+Deploying OpenWISP Using Ansible
+================================
+
+.. contents:: **Table of Contents**:
+ :depth: 3
+ :local:
+
+Introduction & Prerequisites
+----------------------------
+
+.. note::
+
+ If you want to use the latest features of OpenWISP, refer to
+ :ref:`ansible_deploying_development_version`.
+
+.. raw:: html
+
+
+
+
+
+If you don't know how to use ansible, don't panic, this procedure will
+guide you towards a fully working basic OpenWISP installation.
+
+If you already know how to use ansible, you can skip this tutorial.
+
+First of all you need to understand two key concepts:
+
+- for **“production server”** we mean a server (**not a laptop or a
+ desktop computer!**) with public IpV4 / IPv6 which is used to host
+ OpenWISP
+- for **“local machine”** we mean the host from which you launch ansible,
+ eg: your own laptop
+
+Ansible is a configuration management tool that works by entering
+production servers via SSH, **so you need to install it and configure it
+on the machine where you launch the deployment** and this machine must be
+able to SSH into the production server.
+
+Ansible will be run on your local machine and from there it will connect
+to the production server to install OpenWISP.
+
+.. note::
+
+ It is recommended to use this procedure on clean virtual machines or
+ linux containers.
+
+ If you are trying to install OpenWISP on your laptop or desktop pc
+ just for testing purposes, please read :doc:`Install OpenWISP for
+ testing in a VirtualBox VM <./installing-on-vm>`.
+
+.. _ansible_install:
+
+Install Ansible
+---------------
+
+Install ansible (minimum recommended version 2.13) **on your local
+machine** (not the production server!) if you haven't done already.
+
+We suggest following the `ansible installation guide
+`__.
+to install ansible. It is recommended to install ansible through a virtual
+environment to avoid dependency issues.
+
+Please ensure that you have the correct version of Jinja installed in your
+Python environment: ``pip install Jinja2>=2.11``
+
+.. _ansible_install_role:
+
+Install This Role
+-----------------
+
+For the sake of simplicity, the easiest thing is to install this role **on
+your local machine** via ``ansible-galaxy`` (which was installed when
+installing ansible), therefore run:
+
+.. code-block:: shell
+
+ ansible-galaxy install openwisp.openwisp2
+
+Ensure that you have the `community.general
+`_
+and `ansible.posix
+`_
+collections installed and up to date:
+
+.. code-block:: shell
+
+ ansible-galaxy collection install "community.general:>=3.6.0"
+ ansible-galaxy collection install "ansible.posix"
+
+.. _ansible_choose_working_directory:
+
+Choose a Working Directory
+--------------------------
+
+Choose a working directory **on your local machine** where to put the
+configuration of OpenWISP.
+
+This will be useful when you will need to upgrade OpenWISP.
+
+Eg:
+
+.. code-block:: shell
+
+ mkdir ~/openwisp2-ansible-playbook
+ cd ~/openwisp2-ansible-playbook
+
+.. _ansible_create_inventory_file:
+
+Create Inventory File
+---------------------
+
+The inventory file is where group of servers are defined. In our simple
+case we will define just one group in which we will put just one server.
+
+Create a new file called ``hosts`` in the working directory **on your
+local machine** (the directory just created in the previous step), with
+the following contents:
+
+.. code-block:: text
+
+ [openwisp2]
+ openwisp2.mydomain.com
+
+Substitute ``openwisp2.mydomain.com`` with your **production server**'s
+hostname - **DO NOT REPLACE ``openwisp2.mydomain.com`` WITH AN IP
+ADDRESS**, otherwise email sending through postfix will break, causing 500
+internal server errors on some operations.
+
+.. _ansible_create_playbook_file:
+
+Create Playbook File
+--------------------
+
+Create a new playbook file ``playbook.yml`` **on your local machine** with
+the following contents:
+
+.. code-block:: yaml
+
+ - hosts: openwisp2
+ become: "{{ become | default('yes') }}"
+ roles:
+ - openwisp.openwisp2
+ vars:
+ openwisp2_default_from_email: "openwisp2@openwisp2.mydomain.com"
+
+The line ``become: "{{ become | default('yes') }}"`` means ansible will
+use the ``sudo`` program to run each command. You may remove this line if
+you don't need it (eg: if you are ``root`` user on the production server).
+
+You may replace ``openwisp2`` on the ``hosts`` field with your production
+server's hostname if you desire.
+
+Substitute ``openwisp2@openwisp2.mydomain.com`` with what you deem most
+appropriate as default sender for emails sent by OpenWISP 2.
+
+.. _ansible_run_playbook:
+
+Run the Playbook
+----------------
+
+Now is time to **deploy OpenWISP to the production server**.
+
+Run the playbook **from your local machine** with:
+
+.. code-block:: shell
+
+ ansible-playbook -i hosts playbook.yml -u -k --become -K
+
+Substitute ```` with your **production server**'s username.
+
+The ``-k`` argument will need the ``sshpass`` program.
+
+You can remove ``-k``, ``--become`` and ``-K`` if your public SSH key is
+installed on the server.
+
+.. tip::
+
+ - If you have an error like ``Authentication or permission failure``
+ then try to use *root* user ``ansible-playbook -i hosts playbook.yml
+ -u root -k``
+ - If you have an error about adding the host's fingerprint to the
+ ``known_hosts`` file, you can simply connect to the host via SSH and
+ answer yes when prompted; then you can run ``ansible-playbook``
+ again.
+
+When the playbook is done running, if you got no errors you can login at
+``https://openwisp2.mydomain.com/admin`` with the following credentials:
+
+.. code-block:: text
+
+ username: admin
+ password: admin
+
+Substitute ``openwisp2.mydomain.com`` with your production server's
+hostname.
+
+Now proceed with the following steps:
+
+1. change the password (and the username if you like) of the superuser as
+ soon as possible
+2. update the ``name`` field of the default ``Site`` object to accurately
+ display site name in email notifications
+3. edit the information of the default organization
+4. in the default organization you just updated, note down the
+ automatically generated *shared secret* option, you will need it to use
+ the :doc:`auto-registration feature of openwisp-config
+ `
+5. this Ansible role creates a default template to update
+ ``authorized_keys`` on networking devices using the default access
+ credentials. The role will either use an existing SSH key pair or
+ create a new one if no SSH key pair exists on the host machine.
+
+Now you are ready to start configuring your network! **If you need help**
+you can ask questions on one of the official `OpenWISP Support Channels
+`__.
+
+Upgrading OpeNWISP
+------------------
+
+.. important::
+
+ It is strongly recommended to back up your current instance before
+ upgrading.
+
+Update this ansible-role via ``ansible-galaxy``:
+
+.. code-block:: shell
+
+ ansible-galaxy install --force openwisp.openwisp2
+
+Run ``ansible-playbook`` again **from your local machine**:
+
+.. code-block:: shell
+
+ ansible-playbook -i hosts playbook.yml
+
+You may also run the playbook automatically periodically or when a new
+release of OpenWISP2, for example, by setting up a continuous integration
+system.
+
+.. _ansible_deploying_development_version:
+
+Deploying the Development Version of OpenWISP
+---------------------------------------------
+
+The following steps will help you set up and install the development
+version of OpenWISP which is not released yet, but ships new features and
+improvements.
+
+Create a directory for organizing your playbook, roles and collections. In
+this example, ``openwisp-dev`` is used. Create ``roles`` and
+``collections`` directories in ``~/openwisp-dev``.
+
+.. code-block::
+
+ mkdir -p ~/openwisp-dev/roles
+ mkdir -p ~/openwisp-dev/collections
+
+Change directory to ``~/openwisp-dev/`` in terminal and create
+configuration and requirement files for Ansible.
+
+.. code-block::
+
+ cd ~/openwisp-dev/
+ touch ansible.cfg
+ touch requirements.yml
+
+Setup ``roles_path`` and ``collections_paths`` variables in
+``ansible.cfg`` as follows:
+
+.. code-block::
+
+ [defaults]
+ roles_path=~/openwisp-dev/roles
+ collections_paths=~/openwisp-dev/collections
+
+Ensure your ``requirements.yml`` contains following content:
+
+.. code-block:: yaml
+
+ ---
+ roles:
+ - src: https://github.com/openwisp/ansible-openwisp2.git
+ version: master
+ name: openwisp.openwisp2-dev
+ collections:
+ - name: community.general
+ version: ">=3.6.0"
+
+Install requirements from the ``requirements.yml`` as follows
+
+.. code-block::
+
+ ansible-galaxy install -r requirements.yml
+
+Now, create hosts file and playbook.yml:
+
+.. code-block::
+
+ touch hosts
+ touch playbook.yml
+
+Follow instructions in :ref:`ansible_create_inventory_file` section to
+configure ``hosts`` file.
+
+You can reference the example playbook below (tested on Debian 11) for
+installing a fully-featured version of OpenWISP.
+
+.. code-block:: yaml
+
+ - hosts: openwisp2
+ become: "{{ become | default('yes') }}"
+ roles:
+ - openwisp.openwisp2-dev
+ vars:
+ openwisp2_network_topology: true
+ openwisp2_firmware_upgrader: true
+ openwisp2_radius: true
+ openwisp2_monitoring: true # monitoring is enabled by default
+
+Read :doc:`role-variables` section to learn about available configuration
+variables.
+
+Follow instructions in :ref:`ansible_run_playbook` section to run above
+playbook.
diff --git a/docs/user/role-variables.rst b/docs/user/role-variables.rst
new file mode 100644
index 00000000..4a4434df
--- /dev/null
+++ b/docs/user/role-variables.rst
@@ -0,0 +1,469 @@
+Role Variables
+==============
+
+This role has many variables values that can be changed to best suit your
+needs.
+
+Below are listed all the variables you can customize (you may also want to
+take a look at `the default values of these variables
+`__).
+
+.. code-block:: yaml
+
+ - hosts: yourhost
+ roles:
+ # you can add other roles here
+ - openwisp.openwisp2
+ vars:
+ # Enable the modules you want to use
+ openwisp2_network_topology: false
+ openwisp2_firmware_upgrader: false
+ openwisp2_monitoring: true
+ # you may replace the values of these variables with any value or URL
+ # supported by pip (the python package installer)
+ # use these to install forks, branches or development versions
+ # WARNING: only do this if you know what you are doing; disruption
+ # of service is very likely to occur if these variables are changed
+ # without careful analysis and testing
+ openwisp2_controller_version: "openwisp-controller~=1.0.0"
+ openwisp2_network_topology_version: "openwisp-network-topology~=1.0.0"
+ openwisp2_firmware_upgrader_version: "openwisp-firmware-upgrader~=1.0.0"
+ openwisp2_monitoring_version: "openwisp-monitoring~=1.0.0"
+ openwisp2_radius_version: "openwisp-radius~=1.0.0"
+ openwisp2_django_version: "django~=3.2.13"
+ # Setting this to true will enable subnet division feature of
+ # openwisp-controller. Refer openwisp-controller documentation
+ # for more information. https://github.com/openwisp/openwisp-controller#subnet-division-app
+ # By default, it is set to false.
+ openwisp2_controller_subnet_division: true
+ # when openwisp2_radius_urls is set to false, the radius module
+ # is setup but it's urls are not added, which means API and social
+ # views cannot be used, this is helpful if you have an external
+ # radius instance.
+ openwisp2_radius_urls: "{{ openwisp2_radius }}"
+ openwisp2_path: /opt/openwisp2
+ # It is recommended that you change the value of this variable if you intend to use
+ # OpenWISP2 in production, as a misconfiguration may result in emails not being sent
+ openwisp2_default_from_email: "openwisp2@yourhostname.com"
+ # Email backend used by Django for sending emails. By default, the role
+ # uses "CeleryEmailBackend" from django-celery-email.
+ # (https://github.com/pmclanahan/django-celery-email)
+ openwisp2_email_backend: "djcelery_email.backends.CeleryEmailBackend"
+ # Email timeout in seconds used by Django for blocking operations
+ # like connection attempts. For more info read the Django documentation,
+ # https://docs.djangoproject.com/en/3.2/ref/settings/#email-timeout.
+ # Defaults to 10 seconds.
+ openwisp2_email_timeout: 5
+ # edit database settings only if you are not using sqlite
+ # eg, for deploying with PostgreSQL (recommended for production usage)
+ # you will need the PostGIS spatial extension, find more info at:
+ # https://docs.djangoproject.com/en/4.1/ref/contrib/gis/tutorial/
+ openwisp2_database:
+ engine: django.contrib.gis.db.backends.postgis
+ name: "{{ DB_NAME }}"
+ user: "{{ DB_USER }}"
+ host: "{{ DB_HOST }}"
+ password: "{{ DB_PASSWORD }}"
+ port: 5432
+ # SPATIALITE_LIBRARY_PATH django setting
+ # The role will attempt determining the right mod-spatialite path automatically
+ # But you can use this variable to customize the path or fix future arising issues
+ openwisp2_spatialite_path: "mod_spatialite.so"
+ # customize other django settings:
+ openwisp2_language_code: en-gb
+ openwisp2_time_zone: UTC
+ # openwisp-controller context
+ openwisp2_context: {}
+ # additional allowed hosts
+ openwisp2_allowed_hosts:
+ - myadditionalhost.openwisp.org
+ # geographic map settings
+ openwisp2_leaflet_config:
+ DEFAULT_CENTER: [42.06775, 12.62011]
+ DEFAULT_ZOOM: 6
+ # enable/disable geocoding check
+ openwisp2_geocoding_check: true
+ # specify path to a valid SSL certificate and key
+ # (a self-signed SSL cert will be generated if omitted)
+ openwisp2_ssl_cert: "/etc/nginx/ssl/server.crt"
+ openwisp2_ssl_key: "/etc/nginx/ssl/server.key"
+ # customize the self-signed SSL certificate info if needed
+ openwisp2_ssl_country: "US"
+ openwisp2_ssl_state: "California"
+ openwisp2_ssl_locality: "San Francisco"
+ openwisp2_ssl_organization: "IT dep."
+ # the following setting controls which ip address range
+ # is allowed to access the controller via unencrypted HTTP
+ # (this feature is disabled by default)
+ openwisp2_http_allowed_ip: "10.8.0.0/16"
+ # additional python packages that will be installed with pip
+ openwisp2_extra_python_packages:
+ - bpython
+ - django-owm-legacy
+ # additional django apps that will be added to settings.INSTALLED_APPS
+ # (if the app needs to be installed, the name its python package
+ # must be also added to the openwisp2_extra_python_packages var)
+ openwisp2_extra_django_apps:
+ - owm_legacy
+ # additional django settings example
+ openwisp2_extra_django_settings:
+ CSRF_COOKIE_AGE: 2620800.0
+ # in case you need to add python instructions to the django settings file
+ openwisp2_extra_django_settings_instructions:
+ - TEMPLATES[0]['OPTIONS']['loaders'].insert(0, 'apptemplates.Loader')
+ # extra URL settings for django
+ openwisp2_extra_urls:
+ - "path(r'', include('my_custom_app.urls'))"
+ # allows to specify imports that are used in the websocket routes, eg:
+ openwisp2_websocket_extra_imports:
+ - from my_custom_app.websockets.routing import get_routes as get_custom_app_routes
+ # allows to specify extra websocket routes, eg:
+ openwisp2_websocket_extra_routes:
+ # Callable that returns a list of routes
+ - get_custom_app_routes()
+ # List of routes
+ - "[path('ws/custom-app/', consumer.CustomAppConsumer.as_asgi())]"
+ # controller URL are enabled by default
+ # but can be disabled in multi-VM installations if needed
+ openwisp2_controller_urls: true
+ # The default retention policy that applies to the timeseries data
+ # https://github.com/openwisp/openwisp-monitoring#openwisp-monitoring-default-retention-policy
+ openwisp2_monitoring_default_retention_policy: "26280h0m0s" # 3 years
+ # whether NGINX should be installed
+ openwisp2_nginx_install: true
+ # spdy protocol support (disabled by default)
+ openwisp2_nginx_spdy: false
+ # HTTP2 protocol support (disabled by default)
+ openwisp2_nginx_http2: false
+ # ipv6 must be enabled explicitly to avoid errors
+ openwisp2_nginx_ipv6: false
+ # nginx client_max_body_size setting
+ openwisp2_nginx_client_max_body_size: 10M
+ # list of upstream servers for OpenWISP
+ openwisp2_nginx_openwisp_server:
+ - "localhost:8000"
+ # dictionary containing more nginx settings for
+ # the 443 section of the openwisp2 nginx configuration
+ # IMPORTANT: 1. you can add more nginx settings in this dictionary
+ # 2. here we list the default values used
+ openwisp2_nginx_ssl_config:
+ gzip: "on"
+ gzip_comp_level: "6"
+ gzip_proxied: "any"
+ gzip_min_length: "1000"
+ gzip_types:
+ - "text/plain"
+ - "text/html"
+ - "image/svg+xml"
+ - "application/json"
+ - "application/javascript"
+ - "text/xml"
+ - "text/css"
+ - "application/xml"
+ - "application/x-font-ttf"
+ - "font/opentype"
+ # nginx error log configuration
+ openwisp2_nginx_access_log: "{{ openwisp2_path }}/log/nginx.access.log"
+ openwisp2_nginx_error_log: "{{ openwisp2_path }}/log/nginx.error.log error"
+ # nginx Content Security Policy header, customize if needed
+ openwisp2_nginx_csp: >
+ CUSTOM_NGINX_SECURITY_POLICY
+ # uwsgi gid, omitted by default
+ openwisp2_uwsgi_gid: null
+ # number of uWSGI process to spawn. Default value is 1.
+ openwisp2_uwsgi_processes: 1
+ # number of threads each uWSGI process will have. Default value is 1.
+ openwisp2_uwsgi_threads: 2
+ # value of the listen queue of uWSGI
+ openwisp2_uwsgi_listen: 100
+ # socket on which uwsgi should listen. Defaults to UNIX socket
+ # at "{{ openwisp2_path }}/uwsgi.sock"
+ openwisp2_uwsgi_socket: 127.0.0.1:8000
+ # extra uwsgi configuration parameters that cannot be
+ # configured using dedicated ansible variables
+ openwisp2_uwsgi_extra_conf: |
+ single-interpreter=True
+ log-4xx=True
+ log-5xx=True
+ disable-logging=True
+ auto-procname=True
+ # whether daphne should be installed
+ # must be enabled for serving websocket requests
+ openwisp2_daphne_install: true
+ # number of daphne process to spawn. Default value is 1
+ openwisp2_daphne_processes: 2
+ # maximum time to allow a websocket to be connected (in seconds)
+ openwisp2_daphne_websocket_timeout: 1800
+ # the following setting controls which ip address range
+ # is allowed to access the openwisp2 admin web interface
+ # (by default any IP is allowed)
+ openwisp2_admin_allowed_network: null
+ # install ntp client (enabled by default)
+ openwisp2_install_ntp: true
+ # if you have any custom supervisor service, you can
+ # configure it to restart along with other supervisor services
+ openwisp2_extra_supervisor_restart:
+ - name: my_custom_service
+ when: my_custom_service_enabled
+ # Disable usage metric collection. It is enabled by default.
+ # Read more about it at
+ # https://openwisp.io/docs/user/usage-metric-collection.html
+ openwisp2_usage_metric_collection: false
+ # enable sentry example
+ openwisp2_sentry:
+ dsn: "https://7d2e3cd61acc32eca1fb2a390f7b55e1:bf82aab5ddn4422688e34a486c7426e3@getsentry.com:443/12345"
+ openwisp2_default_cert_validity: 1825
+ openwisp2_default_ca_validity: 3650
+ # the following options for redis allow to configure an external redis instance if needed
+ openwisp2_redis_install: true
+ openwisp2_redis_host: localhost
+ openwisp2_redis_port: 6379
+ openwisp2_redis_cache_url: "redis://{{ openwisp2_redis_host }}:{{ openwisp2_redis_port }}/1"
+ # the following options are required to configure influxdb which is used in openwisp-monitoring
+ openwisp2_influxdb_install: true
+ openwisp2_timeseries_database:
+ backend: "openwisp_monitoring.db.backends.influxdb"
+ user: "openwisp"
+ password: "openwisp"
+ name: "openwisp2"
+ host: "localhost"
+ port: 8086
+ # celery concurrency for the default queue, by default the number of CPUs is used
+ # celery concurrency for the default queue, by default it is set to 1
+ # Setting it to "null" will make concurrency equal to number of CPUs if autoscaling is not used
+ openwisp2_celery_concurrency: null
+ # alternative to the previous option, the celery autoscale option can be set if needed
+ # for more info, consult the documentation of celery regarding "autoscaling"
+ # by default it is set to "null" (no autoscaling)
+ openwisp2_celery_autoscale: 4,1
+ # prefetch multiplier for the default queue,
+ # the default value is calculated automatically by celery
+ openwisp2_celery_prefetch_multiplier: null
+ # celery queuing mode for the default queue,
+ # leaving the default will work for most cases
+ openwisp2_celery_optimization: default
+ # whether the dedicated celerybeat worker is enabled which is
+ # responsible for triggering periodic tasks
+ # must be turned on unless there's another server running celerybeat
+ openwisp2_celerybeat: true
+ # whether the dedicated worker for the celery "network" queue is enabled
+ # must be turned on unless there's another server running a worker for this queue
+ openwisp2_celery_network: true
+ # concurrency option for the "network" queue (a worker is dedicated solely to network operations)
+ # the default is 1. Setting it to "null" will make concurrency equal to number of CPUs if autoscaling is not used.
+ openwisp2_celery_network_concurrency: null
+ # alternative to the previous option, the celery autoscale option can be set if needed
+ # for more info, consult the documentation of celery regarding "autoscaling"
+ # by default it is set to "null" (no autoscaling)
+ openwisp2_celery_network_autoscale: 8,4
+ # prefetch multiplier for the "network" queue,
+ # the default is 1, which mean no prefetching,
+ # because the network tasks are long running and is better
+ # to distribute the tasks to multiple processes
+ openwisp2_celery_network_prefetch_multiplier: 1
+ # celery queuing mode for the "network" queue,
+ # fair mode is used in this case, which means
+ # tasks will be equally distributed among workers
+ openwisp2_celery_network_optimization: fair
+ # whether the dedicated worker for the celery "firmware_upgrader" queue is enabled
+ # must be turned on unless there's another server running a worker for this queue
+ openwisp2_celery_firmware_upgrader: true
+ # concurrency option for the "firmware_upgrader" queue (a worker is dedicated solely to firmware upgrade operations)
+ # the default is 1. Setting it to "null" will make concurrency equal to number of CPUs if autoscaling is not used
+ openwisp2_celery_firmware_upgrader_concurrency: null
+ # alternative to the previous option, the celery autoscale option can be set if needed
+ # for more info, consult the documentation of celery regarding "autoscaling"
+ # by default it is set to "null" (no autoscaling)
+ openwisp2_celery_firmware_upgrader_autoscale: 8,4
+ # prefetch multiplier for the "firmware_upgrader" queue,
+ # the default is 1, which mean no prefetching,
+ # because the firmware upgrade tasks are long running and is better
+ # to distribute the tasks to multiple processes
+ openwisp2_celery_firmware_upgrader_prefetch_multiplier: 1
+ # celery queuing mode for the "firmware_upgrader" queue,
+ # fair mode is used in this case, which means
+ # tasks will be equally distributed among workers
+ openwisp2_celery_firmware_upgrader_optimization: fair
+ # whether the dedicated worker for the celery "monitoring" queue is enabled
+ # must be turned on unless there's another server running a worker for this queue
+ openwisp2_celery_monitoring: true
+ # concurrency option for the "monitoring" queue (a worker is dedicated solely to monitoring operations)
+ # the default is 2. Setting it to "null" will make concurrency equal to number of CPUs
+ # if autoscaling is not used.
+ openwisp2_celery_monitoring_concurrency: null
+ # alternative to the previous option, the celery autoscale option can be set if needed
+ # for more info, consult the documentation of celery regarding "autoscaling"
+ # by default it is set to "null" (no autoscaling)
+ openwisp2_celery_monitoring_autoscale: 4,8
+ # prefetch multiplier for the "monitoring" queue,
+ # the default is 1, which mean no prefetching,
+ # because the monitoring tasks can be long running and is better
+ # to distribute the tasks to multiple processes
+ openwisp2_celery_monitoring_prefetch_multiplier: 1
+ # celery queuing mode for the "monitoring" queue,
+ # fair mode is used in this case, which means
+ # tasks will be equally distributed among workers
+ openwisp2_celery_monitoring_optimization: fair
+ # whether the default celery task routes should be written to the settings.py file
+ # turn this off if you're defining custom task routing rules
+ openwisp2_celery_task_routes_defaults: true
+ # celery settings
+ openwisp2_celery_broker_url: redis://{{ openwisp2_redis_host }}:{{ openwisp2_redis_port }}/3
+ openwisp2_celery_task_acks_late: true
+ # maximum number of retries by celery before giving up when broker is unreachable
+ openwisp2_celery_broker_max_tries: 10
+ # whether to activate the django logging configuration in celery
+ # if set to true, will log all the celery events in the same log stream used by django
+ # which will cause log lines to be written to "{{ openwisp2_path }}/log/openwisp2.log"
+ # instead of "{{ openwisp2_path }}/log/celery.log" and "{{ openwisp2_path }}/log/celerybeat.log"
+ openwisp2_django_celery_logging: false
+ # postfix is installed by default, set to false if you don't need it
+ openwisp2_postfix_install: true
+ # allow overriding default `postfix_smtp_sasl_auth_enable` variable
+ postfix_smtp_sasl_auth_enable_override: true
+ # allow overriding postfix_smtpd_relay_restrictions
+ postfix_smtpd_relay_restrictions_override: permit_mynetworks
+ # allows overriding the default duration for keeping notifications
+ openwisp2_notifications_delete_old_notifications: 10
+ # Expiration time limit (in seconds) of magic sign-in links.
+ # Magic sign-in links are used only when OpenWISP RADIUS is enabled.
+ openwisp2_django_sesame_max_age: 1800 # 30 minutes
+ # Maximum file size(in bytes) allowed to be uploaded as firmware image.
+ # It overrides "openwisp2_nginx_client_max_body_size" setting
+ # and updates nginx configuration accordingly.
+ openwisp2_firmware_upgrader_max_file_size: 41943040 # 40MB
+ # to add multi-language support
+ openwisp2_internationalization: true
+ openwisp2_users_auth_api: true
+ # Allows setting OPENWISP_USERS_USER_PASSWORD_EXPIRATION setting.
+ # Read https://github.com/openwisp/openwisp-users#openwisp_users_user_password_expiration
+ openwisp2_users_user_password_expiration: 30
+ # Allows setting OPENWISP_USERS_STAFF_USER_PASSWORD_EXPIRATION setting.
+ # Read https://github.com/openwisp/openwisp-users#openwisp_users_staff_user_password_expiration
+ openwisp2_users_staff_user_password_expiration: 30
+ # used for SMS verification, the default is a dummy SMS backend
+ # which prints to standard output and hence does nothing
+ # one of the available providers from django-sendsms can be
+ # used or alternatively, you can write a backend class for your
+ # favorite SMS API gateway
+ openwisp2_radius_sms_backend: "sendsms.backends.console.SmsBackend"
+ openwisp2_radius_sms_token_max_ip_daily: 25
+ openwisp2_radius_delete_old_radiusbatch_users: 365
+ openwisp2_radius_cleanup_stale_radacct: 1
+ openwisp2_radius_delete_old_postauth: 365
+ # days for which the radius accounting sessions (radacct) are retained,
+ # 0 means sessions are kept forever.
+ # we highly suggest to set this number according
+ # to the privacy regulation of your jurisdiction
+ openwisp2_radius_delete_old_radacct: 365
+ # days after which inactive users will flagged as unverified
+ # Read https://openwisp-radius.readthedocs.io/en/latest/user/settings.html#openwisp-radius-unverify-inactive-users
+ openwisp2_radius_unverify_inactive_users: 540
+ # days after which inactive users will be deleted
+ # Read https://openwisp-radius.readthedocs.io/en/latest/user/settings.html#openwisp-radius-delete-inactive-users
+ openwisp2_radius_delete_inactive_users: 540
+ openwisp2_radius_allowed_hosts: ["127.0.0.1"]
+ # allow disabling celery beat tasks if needed
+ openwisp2_monitoring_periodic_tasks: true
+ openwisp2_radius_periodic_tasks: true
+ openwisp2_usage_metric_collection_periodic_tasks: true
+ # this role provides a default configuration of freeradius
+ # if you manage freeradius on a different machine or you need different configurations
+ # you can disable this default behavior
+ openwisp2_freeradius_install: true
+ # Set an account to expire T seconds after first login.
+ # This variable sets the value of T.
+ freeradius_expire_attr_after_seconds: 86400
+ freeradius_dir: /etc/freeradius/3.0
+ freeradius_mods_available_dir: "{{ freeradius_dir }}/mods-available"
+ freeradius_mods_enabled_dir: "{{ freeradius_dir }}/mods-enabled"
+ freeradius_sites_available_dir: "{{ freeradius_dir }}/sites-available"
+ freeradius_sites_enabled_dir: "{{ freeradius_dir }}/sites-enabled"
+ freeradius_rest:
+ url: "https://{{ inventory_hostname }}/api/v1/freeradius"
+ freeradius_safe_characters: "+@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"
+ # Sets the source path of the template that contains freeradius site configuration.
+ # Defaults to "templates/freeradius/openwisp_site.j2" shipped in the role.
+ freeradius_openwisp_site_template_src: custom_freeradius_site.j2
+ # Whether to deploy the default openwisp_site for FreeRADIUS.
+ # Defaults to true.
+ freeradius_deploy_openwisp_site: false
+ # FreeRADIUS listen address for the openwisp_site.
+ # Defaults to "*", i.e. listen on all interfaces.
+ freeradius_openwisp_site_listen_ipaddr: "10.8.0.1"
+ # A list of dict that includes organization's name, UUID, RADIUS token,
+ # TLS configuration, and ports for authentication, accounting, and inner tunnel.
+ # This list of dict is used to generate FreeRADIUS sites that support
+ # WPA Enterprise (EAP-TTLS-PAP) authentication.
+ # Defaults to an empty list.
+ freeradius_eap_orgs:
+ # The name should not contain spaces or special characters
+ - name: openwisp
+ # UUID of the organization can be retrieved from the OpenWISP admin
+ uuid: 00000000-0000-0000-0000-000000000000
+ # Radius token of the organization can be retrieved from the OpenWISP admin
+ radius_token: secret-radius-token
+ # Port used by the authentication service for this FreeRADIUS site
+ auth_port: 1832
+ # Port used by the accounting service for this FreeRADIUS site
+ acct_port: 1833
+ # Port used by the authentication service of inner tunnel for this FreeRADIUS site
+ inner_tunnel_auth_port: 18330
+ # CA certificate for the FreeRADIUS site
+ ca: /etc/freeradius/certs/ca.crt
+ # TLS certificate for the FreeRADIUS site
+ cert: /etc/freeradius/certs/cert.pem
+ # TLS private key for the FreeRADIUS site
+ private_key: /etc/freeradius/certs/key.pem
+ # Diffie-Hellman key for the FreeRADIUS site
+ dh: /etc/freeradius/certs/dh
+ # Extra instructions for the "tls-config" section of the EAP module
+ # for the FreeRADIUS site
+ tls_config_extra: |
+ private_key_password = whatever
+ ecdh_curve = "prime256v1"
+ # Sets the source path of the template that contains freeradius site configuration
+ # for WPA Enterprise (EAP-TTLS-PAP) authentication.
+ # Defaults to "templates/freeradius/eap/openwisp_site.j2" shipped in the role.
+ freeradius_eap_openwisp_site_template_src: custom_eap_openwisp_site.j2
+ # Sets the source path of the template that contains freeradius inner tunnel
+ # configuration for WPA Enterprise (EAP-TTLS-PAP) authentication.
+ # Defaults to "templates/freeradius/eap/inner_tunnel.j2" shipped in the role.
+ freeradius_eap_inner_tunnel_template_src: custom_eap_inner_tunnel.j2
+ # Sets the source path of the template that contains freeradius EAP configuration
+ # for WPA Enterprise (EAP-TTLS-PAP) authentication.
+ # Defaults to "templates/freeradius/eap/eap.j2" shipped in the role.
+ freeradius_eap_template_src: custom_eap.j2
+ cron_delete_old_notifications: "'hour': 0, 'minute': 0"
+ cron_deactivate_expired_users: "'hour': 0, 'minute': 5"
+ cron_delete_old_radiusbatch_users: "'hour': 0, 'minute': 10"
+ cron_cleanup_stale_radacct: "'hour': 0, 'minute': 20"
+ cron_delete_old_postauth: "'hour': 0, 'minute': 30"
+ cron_delete_old_radacct: "'hour': 1, 'minute': 30"
+ cron_password_expiration_email: "'hour': 1, 'minute': 0"
+ cron_unverify_inactive_users: "'hour': 1, 'minute': 45"
+ cron_delete_inactive_users: "'hour': 1, 'minute': 55"
+ # cross-origin resource sharing (CORS) settings
+ # https://pypi.org/project/django-cors-headers/
+ openwisp2_django_cors:
+ # Setting this to "true" will install the django-cors-headers package
+ # and configure the Django middleware setting to support CORS.
+ # By default, it is set to false.
+ enabled: true
+ # Configures "CORS_ALLOWED_ORIGINS" setting of the django-cors-headers
+ # package. A list of origins that are authorized to make cross-site
+ # HTTP requests. Read https://github.com/adamchainz/django-cors-headers#cors_allowed_origins-sequencestr
+ # for detail. By default, it is set to an empty list.
+ allowed_origins_list: ["https://log.openwisp.org"]
+ # Configures "CORS_REPLACE_HTTPS_REFERER" setting of the django-cors-headers
+ # package. Read https://github.com/adamchainz/django-cors-headers#cors_replace_https_referer-bool
+ # for detail. Setting this to "true" will also configure the
+ # Django middleware setting to add "CorsPostCsrfMiddleware".
+ # By default, it is set to false.
+ replace_https_referer: true
+
+**Note**: The default values for settings provided to control the number
+of process and threads of uWSGI and Daphne are set conservatively. It is
+expected from user to update these settings to suit scale of their
+project. The same thing applies for concurrency and autoscale settings for
+celery workers.
diff --git a/docs/user/system-requirements.rst b/docs/user/system-requirements.rst
new file mode 100644
index 00000000..da20d431
--- /dev/null
+++ b/docs/user/system-requirements.rst
@@ -0,0 +1,48 @@
+System Requirements
+===================
+
+The following specifications will run a new, *empty* instance of OpenWISP.
+Please ensure you account for the amount of disk space your use case will
+require, e.g. allocate enough space for users to upload floor plan images.
+
+Hardware Requirements (Recommended)
+-----------------------------------
+
+- 2 CPUs
+- 2 GB Memory
+- Disk space - depends on the projected size of your database and uploaded
+ photo images
+
+Keep in mind that increasing the number of celery workers will require
+more memory and CPU. You will need to increase the amount of celery
+workers as the number of devices you manage grows.
+
+For more information about how to increase concurrency, look for the
+variables which end with ``_concurrency`` or ``_autoscale`` in the
+:doc:`role-variables` section.
+
+Software
+--------
+
+Generally a fresh installation of one of the supported operating systems
+is sufficient; no pre-configuration required. The Ansible Playbook will
+install and configure all dependencies and leave you with a running
+OpenWISP installation.
+
+.. important::
+
+ Ensure the hostname of your target machine matches what is in your
+ Ansible configuration file. Also, please ensure that Ansible can
+ access your target machine by SSH, be it either with a key or
+ password. For more information see the `Ansible Getting Started
+ Documentation
+ `__.
+
+Supported Operating Systems
+---------------------------
+
+- Debian 12
+- Debian 11
+- Ubuntu 24 LTS
+- Ubuntu 22 LTS
+- Ubuntu 20 LTS
diff --git a/docs/user/troubleshooting.rst b/docs/user/troubleshooting.rst
new file mode 100644
index 00000000..d2f875f9
--- /dev/null
+++ b/docs/user/troubleshooting.rst
@@ -0,0 +1,68 @@
+Troubleshooting
+===============
+
+OpenWISP is deployed using **uWSGI**, it also uses **daphne** fo
+WebSockets and **celery** as task queue.
+
+All this services are run by **supervisor**.
+
+.. code-block:: shell
+
+ sudo service supervisor start|stop|status
+
+You can view each individual process run by supervisor with the following
+command:
+
+.. code-block:: shell
+
+ sudo supervisorctl status
+
+For more info about Supervisord, refer to `Running supervisorctl
+`__.
+
+The **nginx** web server sits in front of the **uWSGI** application
+server. You can control nginx with the following commands:
+
+.. code-block:: shell
+
+ service nginx status start|stop|status
+
+OpenWISP is installed in ``/opt/openwisp2`` (unless you changed the
+``openwisp2_path`` variable in the ansible playbook configuration), these
+are some useful directories to look for when experiencing issues.
+
+========================= ==========================
+Location Description
+========================= ==========================
+/opt/openwisp2 The OpenWISP 2 root dir.
+/opt/openwisp2/log Log files
+/opt/openwisp2/env Python virtual env
+/opt/openwisp2/db.sqlite3 OpenWISP 2 sqlite database
+========================= ==========================
+
+All processes are running as ``www-data`` user.
+
+If you need to copy or edit files, you can switch to ``www-data`` user
+with the following commands:
+
+.. code-block:: shell
+
+ sudo su www-data -s /bin/bash
+ cd /opt/openwisp2
+ source env/bin/activate
+
+SSL Certificate Gotchas
+-----------------------
+
+When you access the admin website you will get an SSL certificate warning
+because the playbook creates a self-signed (untrusted) SSL certificate.
+You can get rid of the warning by installing your own trusted certificate
+and set the ``openwisp2_ssl_cert`` and ``openwisp2_ssl_key`` variables
+accordingly or by following the instructions explained in the section
+:doc:`certbot-ssl`.
+
+If you keep the untrusted certificate, you will also need to disable SSL
+verification on devices using :doc:`openwisp-config
+` by setting ``verify_ssl`` to ``0``,
+although we advice against using this kind of setup in a production
+environment.
diff --git a/files/generate_django_secret_key.py b/files/generate_django_secret_key.py
index d468b937..6faa365e 100755
--- a/files/generate_django_secret_key.py
+++ b/files/generate_django_secret_key.py
@@ -1,14 +1,15 @@
#!/usr/bin/env python
-"""
-Pseudo-random django secret key generator
-"""
+"""Pseudo-random django secret key generator"""
from __future__ import print_function
+
import random
-chars = 'abcdefghijklmnopqrstuvwxyz' \
- 'ABCDEFGHIJKLMNOPQRSTUVXYZ' \
- '0123456789' \
- '#()^[]-_*%&=+/'
+chars = (
+ 'abcdefghijklmnopqrstuvwxyz'
+ 'ABCDEFGHIJKLMNOPQRSTUVXYZ'
+ '0123456789'
+ '#()^[]-_*%&=+/'
+)
SECRET_KEY = ''.join([random.SystemRandom().choice(chars) for i in range(50)])
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..21ea6859
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,2 @@
+[tool.docstrfmt]
+extend_exclude = ["templates/**/*.py"]
diff --git a/templates/load_initial_data.py b/templates/load_initial_data.py
index 4894cbf7..45d41693 100644
--- a/templates/load_initial_data.py
+++ b/templates/load_initial_data.py
@@ -7,7 +7,9 @@
template to use the same
"""
import os
+
import django
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'openwisp2.settings')
django.setup()
@@ -22,13 +24,12 @@
changed = False
if User.objects.filter(is_superuser=True).count() < 1:
- admin = User.objects.create_superuser(username='admin',
- password='admin',
- email='')
+ admin = User.objects.create_superuser(username='admin', password='admin', email='')
print('superuser created')
if 'django.contrib.sites' in settings.INSTALLED_APPS:
from django.contrib.sites.models import Site
+
site = Site.objects.first()
if site and 'example.com' in [site.name, site.domain]:
site.name = '{{ inventory_hostname }}'
diff --git a/templates/openwisp2/__init__.py b/templates/openwisp2/__init__.py
index 67014dab..493e5e2c 100644
--- a/templates/openwisp2/__init__.py
+++ b/templates/openwisp2/__init__.py
@@ -2,4 +2,6 @@
__all__ = ['celery_app']
__openwisp_version__ = '24.0.0a'
-__openwisp_installation_method__ = '{{ openwisp2_installation_method | default("ansible-openwisp2") }}'
+__openwisp_installation_method__ = (
+ '{{ openwisp2_installation_method | default("ansible-openwisp2") }}'
+)
diff --git a/templates/openwisp2/asgi.py b/templates/openwisp2/asgi.py
index e9477f95..cf450b81 100644
--- a/templates/openwisp2/asgi.py
+++ b/templates/openwisp2/asgi.py
@@ -4,6 +4,7 @@
"""
import os
+
import django
from channels.routing import get_default_application
diff --git a/templates/openwisp2/celery.py b/templates/openwisp2/celery.py
index 20e28ef5..a171e1c8 100644
--- a/templates/openwisp2/celery.py
+++ b/templates/openwisp2/celery.py
@@ -10,9 +10,11 @@
app.autodiscover_tasks()
{% if openwisp2_django_celery_logging %}
-from celery.signals import setup_logging
from logging.config import dictConfig
+from celery.signals import setup_logging
+
+
@setup_logging.connect
def config_loggers(*args, **kwargs):
dictConfig(settings.LOGGING)
diff --git a/templates/openwisp2/routing.py b/templates/openwisp2/routing.py
index cc43522e..3cbd22a0 100644
--- a/templates/openwisp2/routing.py
+++ b/templates/openwisp2/routing.py
@@ -10,11 +10,14 @@
{% if openwisp2_controller_urls %}
from openwisp_controller.routing import get_routes as get_controller_routes
+
routes.extend(get_controller_routes())
{% endif %}
{% if openwisp2_network_topology %}
-from openwisp_network_topology.routing import websocket_urlpatterns as network_topology_routes
+from openwisp_network_topology.routing import \
+ websocket_urlpatterns as network_topology_routes
+
routes.extend(network_topology_routes)
{% endif %}
diff --git a/templates/openwisp2/settings.py b/templates/openwisp2/settings.py
index 49f336ed..7f7a314b 100644
--- a/templates/openwisp2/settings.py
+++ b/templates/openwisp2/settings.py
@@ -1,8 +1,9 @@
import os
import sys
-from celery.schedules import crontab
from datetime import timedelta
+from celery.schedules import crontab
+
TESTING = 'test' in sys.argv
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
diff --git a/templates/openwisp2/urls.py b/templates/openwisp2/urls.py
index 1bb7a0f7..c76a57c9 100644
--- a/templates/openwisp2/urls.py
+++ b/templates/openwisp2/urls.py
@@ -1,14 +1,15 @@
-from django.urls import include, path, reverse_lazy
from django.contrib import admin
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
+from django.urls import include, path, reverse_lazy
from django.views.generic import RedirectView
+
{% if openwisp2_firmware_upgrader %}
# When using S3_REVERSE_PROXY feature of django-private-storage,
# the storage backend reverse the "serve_private_file" URL
# pattern in order to proxy the file with the correct URL.
-from openwisp_firmware_upgrader.private_storage.urls import (
- urlpatterns as fw_private_storage_urls,
-)
+from openwisp_firmware_upgrader.private_storage.urls import \
+ urlpatterns as fw_private_storage_urls
+
{% endif %}
redirect_view = RedirectView.as_view(url=reverse_lazy('admin:index'))