├── .ansible-lint ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ └── ci.yml ├── .gitignore ├── .yamllint.yml ├── CHANGES.md ├── CONTRIBUTING.rst ├── LICENSE ├── README.md ├── defaults └── main.yml ├── docs ├── developer │ └── installation.rst ├── images │ ├── architecture-v2-ansible-openwisp.png │ ├── debian-software-selection.png │ ├── host-only-network.png │ ├── install-openwisp2.png │ └── openwisp2-modules-diagram.png ├── index.rst ├── partials │ └── developer-docs.rst └── user │ ├── certbot-ssl.rst │ ├── configuring-cors-headers.rst │ ├── deploying-custom-static-content.rst │ ├── deploying-wpa-eap-ttls-pap.rst │ ├── enabling-modules.rst │ ├── installing-on-vm.rst │ ├── quickstart.rst │ ├── role-variables.rst │ ├── system-requirements.rst │ └── troubleshooting.rst ├── files └── generate_django_secret_key.py ├── handlers └── main.yml ├── meta └── main.yml ├── molecule ├── default │ └── molecule.yml ├── local │ └── molecule.yml └── resources │ ├── converge.yml │ ├── requirements.yml │ └── verify.yml ├── pyproject.toml ├── run-qa-checks ├── tasks ├── apt.yml ├── complete.yml ├── consent.yml ├── cron.yml ├── django.yml ├── django_secret_key.yml ├── freeradius.yml ├── freeradius_eap.yml ├── main.yml ├── nginx.yml ├── pip.yml ├── ssh.yml ├── supervisor.yml └── system.yml ├── templates ├── freeradius │ ├── eap │ │ ├── eap.j2 │ │ ├── inner_tunnel.j2 │ │ └── openwisp_site.j2 │ ├── inner_tunnel.j2 │ ├── openwisp_site.j2 │ ├── rest.j2 │ └── sql.j2 ├── load_initial_data.py ├── logrotate.d │ └── openwisp-nginx.j2 ├── manage.py ├── nginx │ ├── security-conf.j2 │ ├── site-conf.j2 │ └── ssl-conf.j2 ├── openwisp2 │ ├── __init__.py │ ├── asgi.py │ ├── celery.py │ ├── routing.py │ ├── settings.py │ ├── urls.py │ ├── version.py │ └── wsgi.py ├── supervisor │ ├── celery.j2 │ ├── celery_firmware_upgrader.j2 │ ├── celery_monitoring.j2 │ ├── celery_network.j2 │ ├── celerybeat.j2 │ ├── daphne.j2 │ └── openwisp2.j2 └── uwsgi.ini.j2 └── vars └── main.yml /.ansible-lint: -------------------------------------------------------------------------------- 1 | --- 2 | skip_list: 3 | - '204' # Lines should be no longer than 160 chars 4 | - '301' # Commands should not change things if nothing needs doing 5 | - '303' # Using command rather than module 6 | - '306' # Shells that use pipes should set the pipefail option 7 | - '403' # Package installs should not use latest 8 | - '106' # Role name {} does not match ``^[a-z][a-z0-9_]+$`` pattern' 9 | - 'fqcn-builtins' # This rule is implicit by default(ansible-lint 6), We need to skip this. 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: ["https://openwisp.org/sponsorship/"] 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Open a bug report 4 | title: "[bug] " 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of the bug or unexpected behavior. 12 | 13 | **Steps To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **System Informatioon:** 27 | - Ansible Version: Paste the output of `ansible --version` 28 | - OS: [e.g. Ubuntu 24.04 LTS] 29 | - Python Version: [e.g. Python 3.11.2] 30 | - Django Version: [e.g. Django 4.2.5] 31 | - Browser and Browser Version (if applicable): [e.g. Chromium v126.0.6478.126] 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[feature] " 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Please use the Discussion Forum to ask questions 4 | title: "[question] " 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please use the [Discussion Forum](https://github.com/orgs/openwisp/discussions) to ask questions. 11 | 12 | We will take care of moving the discussion to a more relevant repository if needed. 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" # Check for Python package updates 4 | directory: "/" # The root directory where the Ansible role is located 5 | schedule: 6 | interval: "monthly" # Check for updates weekly 7 | commit-message: 8 | prefix: "[deps] " 9 | - package-ecosystem: "github-actions" # Check for GitHub Actions updates 10 | directory: "/" # The root directory where the Ansible role is located 11 | schedule: 12 | interval: "monthly" # Check for updates weekly 13 | commit-message: 14 | prefix: "[ci] " 15 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Checklist 2 | 3 | - [ ] I have read the [OpenWISP Contributing Guidelines](http://openwisp.io/docs/developer/contributing.html). 4 | - [ ] I have manually tested the changes proposed in this pull request. 5 | - [ ] I have written new test cases for new code and/or updated existing tests for changes to existing code. 6 | - [ ] I have updated the documentation. 7 | 8 | ## Reference to Existing Issue 9 | 10 | Closes #. 11 | 12 | Please [open a new issue](https://github.com/openwisp/ansible-openwisp2/issues/new/choose) if there isn't an existing issue yet. 13 | 14 | ## Description of Changes 15 | 16 | Please describe these changes. 17 | 18 | ## Screenshot 19 | 20 | Please include any relevant screenshots. 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Ansible-OpenWISP2 CI Build 4 | 5 | on: # yamllint disable-line rule:truthy 6 | push: 7 | branches: 8 | - master 9 | - dev 10 | pull_request: 11 | branches: 12 | - master 13 | - dev 14 | 15 | jobs: 16 | build: 17 | name: Build ${{ matrix.distro }} 18 | runs-on: ubuntu-24.04 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | distro: 24 | - ubuntu2204 25 | - ubuntu2404 26 | - debian11 27 | - debian12 28 | 29 | steps: 30 | - uses: actions/checkout@v4 31 | with: 32 | ref: ${{ github.event.pull_request.head.sha }} 33 | 34 | - name: Set up Python ${{ matrix.python-version }} 35 | uses: actions/setup-python@v5 36 | with: 37 | python-version: "3.x" 38 | 39 | - name: Install python dependencies 40 | run: | 41 | pip install molecule molecule-plugins[docker] yamllint ansible-lint docker 42 | pip install openwisp-utils[qa] 43 | # https://github.com/ansible-community/molecule-plugins/issues/256 44 | python -m pip install 'requests<2.32' 45 | 46 | - name: Install Ansible Galaxy dependencies 47 | run: ansible-galaxy collection install "community.general:>=3.6.0" 48 | 49 | - name: QA checks 50 | continue-on-error: true 51 | run: | 52 | ./run-qa-checks 53 | 54 | - name: Tests 55 | run: | 56 | mkdir -p ~/.ansible/roles 57 | ln -s $GITHUB_WORKSPACE ~/.ansible/roles/openwisp.openwisp2 58 | molecule test 59 | env: 60 | ROLE_NAME: openwisp2 61 | PY_COLORS: '1' 62 | ANSIBLE_FORCE_COLOR: '1' 63 | MOLECULE_DISTRO: ${{ matrix.distro }} 64 | ANSIBLE_ROLES_PATH: /home/runner/.cache/molecule/ansible-openwisp2/default/roles:/home/runner/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles 65 | 66 | import: 67 | name: Update imports on Ansible Galaxy 68 | runs-on: ubuntu-22.04 69 | container: python:3-slim 70 | needs: 71 | - build 72 | 73 | if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} 74 | steps: 75 | - name: Install ansible 76 | run: pip install ansible 77 | 78 | - name: Update imports on Ansible Galaxy 79 | run: | 80 | ansible-galaxy role import --api-key ${{ secrets.GALAXY_API_KEY }} openwisp ansible-openwisp2 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Ipython Notebook 62 | .ipynb_checkpoints 63 | 64 | # Terraform 65 | .terraform/* 66 | creds/* 67 | keys/* 68 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | extends: default 4 | rules: 5 | line-length: 6 | max: 162 7 | level: warning 8 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## Version 24.11.1 [2024-11-27] 4 | 5 | ### Bugfixes 6 | 7 | - Updated ``__openwisp_version__`` to ``24.11.1``. 8 | 9 | ## Version 24.11.0 [2024-11-26] 10 | 11 | ### Features 12 | 13 | - Added support for Django CORS 14 | - Allowed deploying [WPA Enterprise 2 EAP-TTLS-PAP](https://openwisp.io/docs/stable/ansible/user/deploying-wpa-eap-ttls-pap.html) 15 | - Added a task to add `inventory_hostname` to `/etc/hosts` 16 | - Added websocket routes for network topology 17 | - Added `openwisp2_websocket_extra_routes` variable to configure websocket 18 | routes 19 | - Added `openwisp2_daphne_install` variable to allow disabling Daphne 20 | - Added `freeradius_openwisp_site_listen_ipaddr` variable to configure 21 | FreeRADIUS listen address 22 | - Added `openwisp2_monitoring_default_retention_policy` variable to configure 23 | the default retention policy for monitoring metrics 24 | - Added `openwisp2_uwsgi_extra_conf` variable to configure extra uWSGI 25 | parameters 26 | - Added `openwisp2_email_timeout` variable to set the [default email 27 | timeout](https://docs.djangoproject.com/en/4.2/ref/settings/#email-timeout) 28 | - Added variables `openwisp2_users_user_password_expiration` and 29 | `openwisp2_users_staff_user_password_expiration` to configure password 30 | expiration settings 31 | - Added the `openwisp2_celerybeat` variable to allow disabling the CeleryBeat 32 | worker 33 | - Introduced a consent mechanism for the [collection of usage 34 | metrics](https://openwisp.io/docs/stable/utils/user/metric-collection.html) 35 | 36 | ### Changes 37 | 38 | - Upgraded to OpenWISP Users 1.1.x (see [change log](https://github.com/openwisp/openwisp-users/releases/tag/1.1.0)) 39 | - Upgraded to OpenWISP Controller 1.1.x (see [change log](https://github.com/openwisp/openwisp-controller/releases/tag/1.1.0)) 40 | - Upgraded to OpenWISP Monitoring 1.1.x (see [change log](https://github.com/openwisp/openwisp-monitoring/releases/tag/1.1.0)) 41 | - Upgraded to OpenWISP Network Topology 1.1.x (see [change log](https://github.com/openwisp/openwisp-network-topology/releases/tag/1.1.0)) 42 | - Upgraded to OpenWISP Firmware Upgrader 1.1.x (see [change log](https://github.com/openwisp/openwisp-firmware-upgrader/releases/tag/1.1.0)) 43 | - Upgraded to OpenWISP RADIUS 1.1.x (see [change log](https://github.com/openwisp/openwisp-radius/releases/tag/1.1.0)) 44 | - Upgraded to FreeRADIUS 3.2 45 | - **Backward incompatible change**: 46 | `openwisp2_radius_delete_old_radiusbatch_users` variable now expects days 47 | instead of months 48 | - Increased the *prefetch multiplier* for `network` and `monitoring` Celery 49 | workers to `10` 50 | - Updated URLs to support cloud backends for private storage 51 | - Removed the SQL module from the default FreeRADIUS site 52 | - Changed the default value of the `openwisp2_radius_cleanup_stale_radacct` 53 | variable to `1` 54 | - Added support for Debian 12 55 | - Added support for Ubuntu 24.04 56 | - Dropped support for Debian 10 57 | - Dropped support for Ubuntu 18.04 58 | - Dropped support for Python 3.7 59 | 60 | ### Bugfixes 61 | 62 | - Implemented efficient reloading of supervisor services 63 | - Included `allowed_hostnames` in the NGINX Content-Security-Policy. 64 | 65 | ## Version 22.05.3 [2023-02-21] 66 | 67 | - Updated source for Stouts.postfix role dependency 68 | - Fix: updated openssl command syntax 69 | 70 | ## Version 22.05.2 [2022-10-18] 71 | 72 | - Removed sql module from default freeradius site which was generating errors 73 | - Fixed ``openwisp2_should_install_python_37`` false test 74 | - Fixed installation of Python 3.7 on old systems 75 | - Fixed installation of freeradius on Ubuntu 22.04.1 76 | 77 | ## Version 22.05.1 [2022-05-30] 78 | 79 | - Fixed redis installation issue on some Ubuntu versions 80 | 81 | ## Version 22.05 [2022-05-12] 82 | 83 | ### Changes 84 | 85 | - Upgraded to OpenWISP Users 1.0.x (see [change log](https://github.com/openwisp/openwisp-users/releases/tag/1.0.0)) 86 | - Upgraded to OpenWISP Controller 1.0.x (see [change log](https://github.com/openwisp/openwisp-controller/releases/tag/1.0.0)) 87 | - Upgraded to OpenWISP Network Topology 1.0.x (see [change log](https://github.com/openwisp/openwisp-network-topology/releases/tag/1.0.0)) 88 | - Upgraded to OpenWISP Firmware Upgrader 1.0.x (see [change log](https://github.com/openwisp/openwisp-firmware-upgrader/releases/tag/1.0.0)) 89 | - **Backward incompatible change**: simplified installation of 90 | custom modules, the variables with `_pip` suffix have been abandoned 91 | in favour of supplying the full version in the variables having 92 | `_version` suffix, for more information please see [[change!] Simplify installation of custom modules #193](https://github.com/openwisp/ansible-openwisp2/commit/3c651a0179ecd7881cd6f388ee4a7d0a8c5a7689) 93 | - `openwisp2_firmware_upgrader_max_file_size` now sets 94 | `OPENWISP_FIRMWARE_UPGRADER_MAX_FILE_SIZE` in `settings.py` and 95 | updates `client_max_body_size` in nginx config. 96 | - Added variable to configure daphne websocket timeout; 97 | this timeout value is also used for configuring the "group_expiry" 98 | of `CHANNEL_LAYERS`. 99 | - Updated nginx SSL configuration: 100 | - Dropped TLSv1.0 and TLSv1.1 protocol 101 | - Updated cipher list 102 | - Updated NGINX security headers 103 | - Disabled nginx `server_tokens` 104 | - Added django-celery-email as default email backend 105 | - Added `django.contrib.humanize` to `INSTALLED_APPS` 106 | - Moved geocoding check from django-loci to explicit task 107 | 108 | ### Features 109 | 110 | - Added support for [OpenWISP Monitoring](https://openwisp.io/docs/user/monitoring.html) 111 | - Added optional support for [OpenWISP RADIUS](https://openwisp.io/docs/user/radius.html) 112 | - Added support for Ubuntu 22.04 113 | - Added support for internationalization 114 | - Added option to [deploy custom static files](https://github.com/openwisp/ansible-openwisp2#deploying-custom-static-content) 115 | - Added support for [subnet division rule feature](https://openwisp.io/docs/user/subnet-division-rules.html) 116 | - Added the [OpenWISP Users authentication backend](https://github.com/openwisp/openwisp-users#authentication-backend) (enabled by default) 117 | - Added sesame default configuration 118 | - Allow specifying Django version 119 | - Added uWSGI listen option 120 | 121 | ### Bugfixes 122 | 123 | - Added handler for removing celerybeat-schedule.db whenever 124 | there's a change to the python code 125 | - Updated celery supervisor config to support Celery 5 126 | - Fixed support for Ubuntu 18.04 127 | - the role will install Python 3.7 if Python version < 3.7 is found 128 | - pinned setuptools~=59.6.0 129 | - Fixed uWSGI OSError 130 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Please refer to the `Contribution Guidelines 2 | `_. 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Federico Capoano 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of ansible-openwisp2 nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ansible-openwisp2 2 | 3 | [![Ansible-OpenWISP2 CI Build](https://github.com/openwisp/ansible-openwisp2/actions/workflows/ci.yml/badge.svg)](https://github.com/openwisp/ansible-openwisp2/actions/workflows/ci.yml) 4 | [![Galaxy](http://img.shields.io/badge/galaxy-openwisp.openwisp2-blue.svg?style=flat-square)](https://galaxy.ansible.com/ui/standalone/roles/openwisp/openwisp2/) 5 | [![Chat](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/openwisp/general) 6 | 7 | Ansible role that installs the OpenWISP Server Application. 8 | 9 | Tested on **Debian (Bookworm/Bullseye)**, **Ubuntu (24/22 LTS)**. 10 | 11 | **Recommended minimum ansible core version**: 2.13. 12 | 13 | ## Documentation 14 | 15 | - [Usage documentation](https://openwisp.io/docs/stable/ansible/) 16 | - [Developer documentation](https://openwisp.io/docs/stable/ansible/developer/installation.html) 17 | 18 | ## Support 19 | 20 | See [OpenWISP Support Channels](http://openwisp.org/support.html). 21 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | openwisp2_python: python3 4 | ansible_python_interpreter: /usr/bin/python3 5 | openwisp2_network_topology: false 6 | openwisp2_firmware_upgrader: false 7 | openwisp2_monitoring: true 8 | openwisp2_radius: false 9 | openwisp2_controller_subnet_division: false 10 | openwisp2_radius_urls: "{{ openwisp2_radius }}" 11 | openwisp2_controller_version: "openwisp-controller @ https://github.com/openwisp/openwisp-controller/tarball/1.2" 12 | openwisp2_network_topology_version: "openwisp-network-topology @ https://github.com/openwisp/openwisp-network-topology/tarball/1.2" 13 | openwisp2_firmware_upgrader_version: "openwisp-firmware-upgrader @ https://github.com/openwisp/openwisp-firmware-upgrader/tarball/1.2" 14 | openwisp2_monitoring_version: "openwisp-monitoring @ https://github.com/openwisp/openwisp-monitoring/tarball/1.2" 15 | openwisp2_radius_version: "openwisp-radius @ https://github.com/openwisp/openwisp-radius/tarball/1.2" 16 | openwisp2_django_version: "django~=5.2.0" 17 | openwisp2_extra_python_packages: 18 | - bpython 19 | openwisp2_extra_django_apps: [] 20 | openwisp2_extra_django_settings: {} 21 | openwisp2_extra_django_settings_instructions: [] 22 | openwisp2_controller_urls: true 23 | openwisp2_extra_urls: [] 24 | openwisp2_websocket_extra_imports: [] 25 | openwisp2_websocket_extra_routes: [] 26 | openwisp2_path: "/opt/openwisp2" 27 | openwisp2_default_from_email: "openwisp2@{{ inventory_hostname }}" 28 | openwisp2_email_backend: "djcelery_email.backends.CeleryEmailBackend" 29 | openwisp2_email_timeout: 10 30 | openwisp2_database: 31 | engine: "openwisp_utils.db.backends.spatialite" 32 | name: "{{ openwisp2_path }}/db.sqlite3" 33 | user: "" 34 | password: "" 35 | host: "" 36 | port: "" 37 | options: {} 38 | openwisp2_spatialite_path: "mod_spatialite.so" 39 | openwisp2_language_code: "en-gb" 40 | openwisp2_time_zone: "UTC" 41 | openwisp2_context: {} 42 | openwisp2_allowed_hosts: [] 43 | openwisp2_leaflet_config: {} 44 | openwisp2_geocoding_check: true 45 | openwisp2_ssl_cert: "{{ openwisp2_path }}/ssl/server.crt" 46 | openwisp2_ssl_key: "{{ openwisp2_path }}/ssl/server.key" 47 | openwisp2_ssl_country: "US" 48 | openwisp2_ssl_state: "California" 49 | openwisp2_ssl_locality: "San Francisco" 50 | openwisp2_ssl_organization: "IT dep." 51 | openwisp2_ssl_common_name: "{{ inventory_hostname }}" 52 | openwisp2_http_allowed_ip: false 53 | openwisp2_nginx_install: true 54 | openwisp2_nginx_openwisp_server: 55 | - "unix://{{ openwisp2_path }}/uwsgi.sock" 56 | openwisp2_nginx_spdy: false 57 | openwisp2_nginx_http2: false 58 | openwisp2_nginx_ipv6: false 59 | openwisp2_nginx_client_max_body_size: 20M 60 | openwisp2_nginx_csp: > 61 | "default-src http: https: data: blob: 'unsafe-inline'; 62 | script-src 'unsafe-eval' https: 'unsafe-inline' 'self'; 63 | frame-ancestors 'self'; connect-src https://{{ inventory_hostname }}{% for host in openwisp2_allowed_hosts %} https://{{ host }}{% endfor %} wss: 'self'; 64 | worker-src https://{{ inventory_hostname }}{% for host in openwisp2_allowed_hosts %} https://{{ host }}{% endfor %} blob: 'self';" always; 65 | openwisp2_uwsgi_gid: null 66 | openwisp2_admin_allowed_network: null 67 | openwisp2_install_ntp: true 68 | openwisp2_sentry: 69 | dsn: false 70 | openwisp2_default_cert_validity: 1825 71 | openwisp2_default_ca_validity: 3650 72 | openwisp2_default_organization_id: 78f74fdd-67c6-4064-97e1-ce761da30745 73 | openwisp2_notifications_delete_old_notifications: 90 74 | openwisp2_firmware_upgrader_max_file_size: 31457280 # 30MB 75 | openwisp2_topology_update_frequency: 76 | day: "*" 77 | hour: "*" 78 | minute: "*/5" 79 | openwisp2_topology_save_snapshot_frequency: 80 | day: "*" 81 | hour: "23" 82 | minute: "30" 83 | openwisp2_django_sesame_max_age: 1800 # 30 minutes 84 | openwisp2_nginx_ssl_config: 85 | gzip: "on" 86 | gzip_comp_level: "6" 87 | gzip_proxied: "any" 88 | gzip_min_length: "1000" 89 | gzip_types: 90 | - "text/plain" 91 | - "image/svg+xml" 92 | - "application/json" 93 | - "application/javascript" 94 | - "text/xml" 95 | - "text/css" 96 | - "application/xml" 97 | - "application/x-font-ttf" 98 | - "font/opentype" 99 | openwisp2_redis_install: true 100 | openwisp2_redis_host: localhost 101 | openwisp2_redis_port: 6379 102 | openwisp2_redis_cache_url: "redis://{{ openwisp2_redis_host }}:{{ openwisp2_redis_port }}/1" 103 | openwisp2_influxdb_install: true 104 | openwisp2_timeseries_database: 105 | backend: "openwisp_monitoring.db.backends.influxdb" 106 | user: "openwisp" 107 | password: "openwisp" 108 | name: "openwisp2" 109 | host: "localhost" 110 | port: 8086 111 | openwisp2_monitoring_default_retention_policy: "26280h0m0s" # 3 years 112 | openwisp2_nginx_access_log: "{{ openwisp2_path }}/log/nginx.access.log" 113 | openwisp2_nginx_error_log: "{{ openwisp2_path }}/log/nginx.error.log error" 114 | openwisp2_celerybeat: true 115 | # keep backward compatibility with old variable openwisp2_eventlet_concurrency 116 | openwisp2_celery_concurrency: "{{ openwisp2_eventlet_concurrency | default(1) }}" 117 | openwisp2_celery_autoscale: null 118 | openwisp2_celery_prefetch_multiplier: null 119 | openwisp2_celery_optimization: default 120 | openwisp2_celery_network: true 121 | openwisp2_celery_network_concurrency: 1 122 | openwisp2_celery_network_autoscale: null 123 | openwisp2_celery_network_prefetch_multiplier: 10 124 | openwisp2_celery_network_optimization: fair 125 | openwisp2_celery_firmware_upgrader: true 126 | openwisp2_celery_firmware_upgrader_concurrency: 1 127 | openwisp2_celery_firmware_upgrader_autoscale: null 128 | openwisp2_celery_firmware_upgrader_prefetch_multiplier: 1 129 | openwisp2_celery_firmware_upgrader_optimization: fair 130 | openwisp2_celery_monitoring: true 131 | openwisp2_celery_monitoring_concurrency: 2 132 | openwisp2_celery_monitoring_autoscale: null 133 | openwisp2_celery_monitoring_prefetch_multiplier: 10 134 | openwisp2_celery_monitoring_optimization: fair 135 | openwisp2_celery_task_routes_defaults: true 136 | openwisp2_celery_broker_url: redis://{{ openwisp2_redis_host }}:{{ openwisp2_redis_port }}/3 137 | openwisp2_celery_task_acks_late: true 138 | openwisp2_celery_broker_max_tries: 10 139 | openwisp2_django_celery_logging: false 140 | openwisp2_postfix_install: true 141 | postfix_smtpd_relay_restrictions_override: "permit_sasl_authenticated, permit_mynetworks, check_relay_domains, reject_unauth_destination, reject" 142 | openwisp2_uwsgi_processes: 1 143 | openwisp2_uwsgi_threads: 1 144 | openwisp2_uwsgi_listen: 100 145 | openwisp2_uwsgi_socket: "{{ openwisp2_path }}/uwsgi.sock" 146 | openwisp2_uwsgi_extra_conf: | 147 | single-interpreter=True 148 | log-4xx=True 149 | log-5xx=True 150 | disable-logging=True 151 | auto-procname=True 152 | openwisp2_daphne_install: true 153 | openwisp2_daphne_processes: 1 154 | openwisp2_daphne_websocket_timeout: 3600 # in seconds 155 | # internationalization 156 | openwisp2_internationalization: false 157 | openwisp2_users_auth_api: true 158 | openwisp2_users_user_password_expiration: 0 159 | openwisp2_users_staff_user_password_expiration: 0 160 | openwisp2_radius_sms_backend: "sendsms.backends.console.SmsBackend" 161 | openwisp2_radius_sms_token_max_ip_daily: 25 162 | openwisp2_radius_delete_old_radiusbatch_users: 365 163 | openwisp2_radius_cleanup_stale_radacct: 1 164 | openwisp2_radius_delete_old_postauth: 365 165 | openwisp2_radius_delete_old_radacct: 365 166 | openwisp2_radius_unverify_inactive_users: 0 167 | openwisp2_radius_delete_inactive_users: 0 168 | openwisp2_radius_allowed_hosts: ["127.0.0.1"] 169 | openwisp2_freeradius_install: true 170 | freeradius_dir: /etc/freeradius 171 | freeradius_mods_available_dir: "{{ freeradius_dir }}/mods-available" 172 | freeradius_mods_enabled_dir: "{{ freeradius_dir }}/mods-enabled" 173 | freeradius_mods_config_dir: "{{ freeradius_dir }}/mods-config" 174 | freeradius_sites_available_dir: "{{ freeradius_dir }}/sites-available" 175 | freeradius_sites_enabled_dir: "{{ freeradius_dir }}/sites-enabled" 176 | freeradius_openwisp_site_template_src: freeradius/openwisp_site.j2 177 | freeradius_deploy_openwisp_site: true 178 | freeradius_db_map: 179 | django.contrib.gis.db.backends.spatialite: 180 | driver: rlm_sql_sqlite 181 | dialect: sqlite 182 | openwisp_utils.db.backends.spatialite: 183 | driver: rlm_sql_sqlite 184 | dialect: sqlite 185 | django.contrib.gis.db.backends.postgis: 186 | driver: rlm_sql_postgresql 187 | dialect: postgresql 188 | django.contrib.gis.db.backends.mysql: 189 | driver: rlm_sql_mysql 190 | dialect: mysql 191 | freeradius_sql: 192 | driver: "{{ freeradius_db_map[openwisp2_database.engine].driver }}" 193 | dialect: "{{ freeradius_db_map[openwisp2_database.engine].dialect }}" 194 | freeradius_rest: 195 | url: "https://{{ inventory_hostname }}/api/v1/freeradius" 196 | freeradius_expire_attr_after_seconds: 86400 197 | freeradius_safe_characters: "+@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" 198 | freeradius_openwisp_site_listen_ipaddr: "*" 199 | freeradius_eap_orgs: [] 200 | freeradius_eap_openwisp_site_template_src: freeradius/eap/openwisp_site.j2 201 | freeradius_eap_inner_tunnel_template_src: freeradius/eap/inner_tunnel.j2 202 | freeradius_eap_template_src: freeradius/eap/eap.j2 203 | cron_delete_old_notifications: "'hour': 0, 'minute': 0" 204 | cron_deactivate_expired_users: "'hour': 0, 'minute': 5" 205 | cron_delete_old_radiusbatch_users: "'hour': 0, 'minute': 10" 206 | cron_cleanup_stale_radacct: "'hour': 0, 'minute': 20" 207 | cron_delete_old_postauth: "'hour': 0, 'minute': 30" 208 | cron_password_expiration_email: "'hour': 1, 'minute': 0" 209 | cron_delete_old_radacct: "'hour': 1, 'minute': 30" 210 | cron_unverify_inactive_users: "'hour': 1, 'minute': 45" 211 | cron_delete_inactive_users: "'hour': 1, 'minute': 55" 212 | # Cross-Origin Resource Sharing (CORS) settings 213 | openwisp2_django_cors: 214 | enabled: false 215 | allowed_origins_list: [] 216 | openwisp2_extra_supervisor_restart: [] 217 | openwisp2_usage_metric_collection: null 218 | # allow disabling celery beat tasks if needed 219 | openwisp2_monitoring_periodic_tasks: true 220 | openwisp2_radius_periodic_tasks: true 221 | openwisp2_usage_metric_collection_periodic_tasks: true 222 | -------------------------------------------------------------------------------- /docs/developer/installation.rst: -------------------------------------------------------------------------------- 1 | Developer Installation instructions 2 | =================================== 3 | 4 | .. include:: ../partials/developer-docs.rst 5 | 6 | .. contents:: **Table of Contents**: 7 | :depth: 2 8 | :local: 9 | 10 | Installing for Development 11 | -------------------------- 12 | 13 | First of all, create the directory where you want to place the 14 | repositories of the ansible roles and create directory roles. 15 | 16 | .. code-block:: bash 17 | 18 | mkdir -p ~/openwisp-dev/roles 19 | cd ~/openwisp-dev/roles 20 | 21 | Clone ``ansible-openwisp2`` and ``Stouts.postfix`` as follows: 22 | 23 | .. code-block:: bash 24 | 25 | git clone https://github.com/openwisp/ansible-openwisp2.git openwisp.openwisp2 26 | git clone https://github.com/Stouts/Stouts.postfix 27 | git clone https://github.com/openwisp/ansible-ow-influxdb openwisp.influxdb 28 | 29 | Now, go to the parent directory & create hosts file and ``playbook.yml``: 30 | 31 | .. code-block:: bash 32 | 33 | cd ../ 34 | touch hosts 35 | touch playbook.yml 36 | 37 | From here on you can follow the instructions available at the following 38 | sections: 39 | 40 | - :ref:`ansible_install` 41 | - :ref:`ansible_create_inventory_file` 42 | - :ref:`ansible_create_playbook_file` 43 | - :ref:`ansible_run_playbook` 44 | 45 | All done! 46 | 47 | How to Run Tests 48 | ---------------- 49 | 50 | If you want to contribute to ``ansible-openwisp2`` you should run tests in 51 | your development environment to ensure your changes are not breaking 52 | anything. 53 | 54 | To do that, proceed with the following steps: 55 | 56 | **Step 1**: Clone ``ansible-openwisp2`` 57 | 58 | Clone repository by: 59 | 60 | .. code-block:: shell 61 | 62 | git clone https://github.com//ansible-openwisp2.git openwisp.openwisp2 63 | cd openwisp.openwisp2 64 | 65 | **Step 2**: Install docker 66 | 67 | If you haven't installed docker yet, you need to install it (example for 68 | linux debian/ubuntu systems): 69 | 70 | .. code-block:: shell 71 | 72 | sudo apt install docker.io 73 | 74 | **Step 3**: Install molecule and dependencies 75 | 76 | .. code-block:: shell 77 | 78 | pip install molecule[docker] molecule-plugins yamllint ansible-lint docker 79 | 80 | **Step 4**: Download docker images 81 | 82 | .. code-block:: shell 83 | 84 | docker pull geerlingguy/docker-ubuntu2404-ansible:latest 85 | docker pull geerlingguy/docker-ubuntu2204-ansible:latest 86 | docker pull geerlingguy/docker-debian12-ansible:latest 87 | docker pull geerlingguy/docker-debian11-ansible:latest 88 | 89 | **Step 5**: Run molecule test 90 | 91 | .. code-block:: shell 92 | 93 | molecule test -s local 94 | 95 | If you don't get any error message it means that the tests ran 96 | successfully without errors. 97 | 98 | .. tip:: 99 | 100 | Use ``molecule test --destroy=never`` to speed up subsequent test 101 | runs. 102 | -------------------------------------------------------------------------------- /docs/images/architecture-v2-ansible-openwisp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openwisp/ansible-openwisp2/0962383039f582c953f7bd4a9e2e53883f214aae/docs/images/architecture-v2-ansible-openwisp.png -------------------------------------------------------------------------------- /docs/images/debian-software-selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openwisp/ansible-openwisp2/0962383039f582c953f7bd4a9e2e53883f214aae/docs/images/debian-software-selection.png -------------------------------------------------------------------------------- /docs/images/host-only-network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openwisp/ansible-openwisp2/0962383039f582c953f7bd4a9e2e53883f214aae/docs/images/host-only-network.png -------------------------------------------------------------------------------- /docs/images/install-openwisp2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openwisp/ansible-openwisp2/0962383039f582c953f7bd4a9e2e53883f214aae/docs/images/install-openwisp2.png -------------------------------------------------------------------------------- /docs/images/openwisp2-modules-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openwisp/ansible-openwisp2/0962383039f582c953f7bd4a9e2e53883f214aae/docs/images/openwisp2-modules-diagram.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Ansible OpenWISP 2 | ================ 3 | 4 | .. seealso:: 5 | 6 | **Source code**: `github.com/openwisp/ansible-openwisp2 7 | `_. 8 | 9 | This ansible role allows deploying the OpenWISP Server Application. 10 | 11 | **Recommended minimum ansible core version**: 2.13. 12 | 13 | Tested on **Debian (Bookworm/Bullseye)**, **Ubuntu (24/22 LTS)**. 14 | 15 | The following diagram illustrates the role of the Ansible OpenWISP role 16 | within the OpenWISP architecture. 17 | 18 | .. figure:: images/architecture-v2-ansible-openwisp.png 19 | :target: ../_images/architecture-v2-ansible-openwisp.png 20 | :align: center 21 | :alt: OpenWISP Architecture: Ansible OpenWISP role 22 | 23 | **OpenWISP Architecture: highlighted Ansible OpenWISP role** 24 | 25 | .. important:: 26 | 27 | For an enhanced viewing experience, open the image above in a new 28 | browser tab. 29 | 30 | Refer to :doc:`/general/architecture` for more information. 31 | 32 | .. toctree:: 33 | :caption: Ansible OpenWISP Usage Docs 34 | :maxdepth: 1 35 | 36 | ./user/system-requirements.rst 37 | ./user/quickstart.rst 38 | ./user/certbot-ssl.rst 39 | ./user/enabling-modules.rst 40 | ./user/deploying-wpa-eap-ttls-pap.rst 41 | ./user/deploying-custom-static-content.rst 42 | ./user/configuring-cors-headers.rst 43 | ./user/installing-on-vm.rst 44 | ./user/troubleshooting.rst 45 | ./user/role-variables.rst 46 | 47 | .. toctree:: 48 | :caption: Ansible OpenWISP Developer Docs 49 | :maxdepth: 1 50 | 51 | ./developer/installation.rst 52 | -------------------------------------------------------------------------------- /docs/partials/developer-docs.rst: -------------------------------------------------------------------------------- 1 | .. note:: 2 | 3 | This page is for developers who want to customize or extend the 4 | Ansible role of OpenWISP, whether for bug fixes, new features, or 5 | contributions. 6 | 7 | For user guides and general information, please see: 8 | 9 | - :doc:`General OpenWISP Quickstart ` 10 | - :doc:`Ansible OpenWISP2 User Docs ` 11 | -------------------------------------------------------------------------------- /docs/user/certbot-ssl.rst: -------------------------------------------------------------------------------- 1 | Using Let's Encrypt SSL Certificate 2 | =================================== 3 | 4 | This section explains how to **automatically install and renew a valid SSL 5 | certificate** signed by `Let's Encrypt `__. 6 | 7 | The first thing you have to do is to setup a valid domain for your 8 | OpenWISP instance, this means your inventory file (hosts) should look like 9 | the following: 10 | 11 | .. code-block:: text 12 | 13 | [openwisp2] 14 | openwisp2.yourdomain.com 15 | 16 | You must be able to add a DNS record for ``openwisp2.yourdomain.com``, you 17 | cannot use an ip address in place of ``openwisp2.yourdomain.com``. 18 | 19 | Once your domain is set up and the DNS record is propagated, proceed by 20 | installing the ansible role `geerlingguy.certbot 21 | `__: 22 | 23 | .. code-block:: shell 24 | 25 | ansible-galaxy install geerlingguy.certbot 26 | 27 | Then proceed to edit your ``playbook.yml`` so that it will look similar to 28 | the following example: 29 | 30 | .. code-block:: yaml 31 | 32 | - hosts: openwisp2 33 | become: "{{ become | default('yes') }}" 34 | roles: 35 | - geerlingguy.certbot 36 | - openwisp.openwisp2 37 | vars: 38 | # SSL certificates 39 | openwisp2_ssl_cert: "/etc/letsencrypt/live/{{ inventory_hostname }}/fullchain.pem" 40 | openwisp2_ssl_key: "/etc/letsencrypt/live/{{ inventory_hostname }}/privkey.pem" 41 | 42 | # certbot configuration 43 | certbot_auto_renew_minute: "20" 44 | certbot_auto_renew_hour: "5" 45 | certbot_create_if_missing: true 46 | certbot_auto_renew_user: "" 47 | certbot_certs: 48 | - email: "" 49 | domains: 50 | - "{{ inventory_hostname }}" 51 | pre_tasks: 52 | - name: Update APT package cache 53 | apt: 54 | update_cache: true 55 | changed_when: false 56 | retries: 5 57 | delay: 10 58 | register: result 59 | until: result is success 60 | 61 | Read the `documentation of geerlingguy.certbot 62 | `__ to learn 63 | more about configuration of certbot role. 64 | 65 | Once you have set up all the variables correctly, run the playbook again. 66 | -------------------------------------------------------------------------------- /docs/user/configuring-cors-headers.rst: -------------------------------------------------------------------------------- 1 | Configuring CORS Headers 2 | ======================== 3 | 4 | While integrating OpenWISP with external services, you can run into issues 5 | related to `CORS (Cross-Origin Resource Sharing) 6 | `__. This role 7 | allows users to configure the CORS headers with the help of 8 | `django-cors-headers 9 | `__ package. Here's a 10 | short summary of how to do this: 11 | 12 | **Step 1**: :ref:`Install ansible ` 13 | 14 | **Step 2**: :ref:`Install this role ` 15 | 16 | **Step 3**: :ref:`Create inventory file ` 17 | 18 | **Step 4**: Create a playbook file with following contents: 19 | 20 | .. code-block:: yaml 21 | 22 | - hosts: openwisp2 23 | become: "{{ become | default('yes') }}" 24 | roles: 25 | - openwisp.openwisp2 26 | vars: 27 | # Cross-Origin Resource Sharing (CORS) settings 28 | openwisp2_django_cors: 29 | enabled: true 30 | allowed_origins_list: 31 | - https://frontend.openwisp.org 32 | - https://logs.openwisp.org 33 | 34 | **Note:** to learn about the supported fields of the 35 | ``openwisp2_django_cors`` variable, look for the word 36 | *"openwisp2_django_cors"* in the :doc:`role-variables` section of this 37 | document. 38 | 39 | **Step 5**: :ref:`Run the playbook ` 40 | 41 | When the playbook is done running, if you got no errors you can login at 42 | https://openwisp2.mydomain.com/admin, with the following credentials: 43 | 44 | .. code-block:: text 45 | 46 | username: admin 47 | password: admin 48 | 49 | The ansible-openwisp2 only provides abstraction (variables) for handful of 50 | settings available in `django-cors-headers 51 | `__ module. Use the 52 | ``openwisp2_extra_django_settings_instructions`` or 53 | ``openwisp2_extra_django_settings`` variable to configure additional 54 | setting of ``django-cors-headers`` as shown in the following example: 55 | 56 | .. code-block:: yaml 57 | 58 | - hosts: openwisp2 59 | become: "{{ become | default('yes') }}" 60 | roles: 61 | - openwisp.openwisp2 62 | vars: 63 | openwisp2_django_cors: 64 | enabled: true 65 | allowed_origins_list: 66 | - https://frontend.openwisp.org 67 | - https://logs.openwisp.org 68 | # Configuring additional settings for django-cors-headers 69 | openwisp2_extra_django_settings_instructions: 70 | - | 71 | CORS_ALLOW_CREDENTIALS = True 72 | CORS_ALLOW_ALL_ORIGINS = True 73 | -------------------------------------------------------------------------------- /docs/user/deploying-custom-static-content.rst: -------------------------------------------------------------------------------- 1 | Deploying Custom Static Content 2 | =============================== 3 | 4 | For deploying custom static content (HTML files, etc.) add all the static 5 | content in ``files/ow2_static`` directory. The files inside 6 | ``files/ow2_static`` will be uploaded to a directory named 7 | ``static_custom`` in ``openwisp2_path``. 8 | 9 | This is helpful for :ref:`customizing OpenWISP's theme 10 | `. 11 | 12 | E.g., if you added a custom CSS file in 13 | ``files/ow2_static/css/custom.css``, the file location to use in 14 | :ref:`openwisp_admin_theme_links` setting will be ``css/custom.css``. 15 | -------------------------------------------------------------------------------- /docs/user/deploying-wpa-eap-ttls-pap.rst: -------------------------------------------------------------------------------- 1 | Configuring FreeRADIUS for WPA Enterprise (EAP-TTLS-PAP) 2 | ======================================================== 3 | 4 | You can use OpenWISP RADIUS for setting up WPA Enterprise (EAP-TTLS-PAP) 5 | authentication. This allows to authenticate on WiFi networks using Django 6 | user credentials. Prior to proceeding, ensure you've reviewed the tutorial 7 | on :doc:`/tutorials/wpa-enterprise-eap-ttls-pap`. This documentation 8 | section complements the tutorial and focuses solely on demonstrating the 9 | ansible role's capabilities to configure FreeRADIUS. 10 | 11 | .. important:: 12 | 13 | The ansible role supports OpenWISP's multi-tenancy by creating 14 | individual FreeRADIUS sites for each organization. You must include 15 | configuration details for **each organization** that will use WPA 16 | Enterprise. 17 | 18 | Here's an example playbook which enables OpenWISP RADIUS module, installs 19 | FreeRADIUS, and configures it for WPA Enterprise (EAP-TTLS-PAP): 20 | 21 | .. code-block:: yaml 22 | 23 | - hosts: openwisp2 24 | become: "{{ become | default('yes') }}" 25 | roles: 26 | - openwisp.openwisp2 27 | vars: 28 | openwisp2_radius: true 29 | openwisp2_freeradius_install: true 30 | # Define a list of dictionaries detailing each organization's 31 | # name, UUID, RADIUS token, and ports for authentication, 32 | # accounting, and the inner tunnel. These details will be used 33 | # to create FreeRADIUS sites tailored for WPA Enterprise 34 | # (EAP-TTLS-PAP) authentication per organization. 35 | freeradius_eap_orgs: 36 | # A reference name for the organization, 37 | # used in FreeRADIUS configurations. 38 | # Don't use spaces or special characters. 39 | - name: openwisp 40 | # UUID of the organization. 41 | # You can retrieve this from the organization admin 42 | # in the OpenWISP web interface. 43 | uuid: 00000000-0000-0000-0000-000000000000 44 | # Radius token of the organization. 45 | # You can retrieve this from the organization admin 46 | # in the OpenWISP web interface. 47 | radius_token: secret-radius-token 48 | # Port used by the authentication service for 49 | # this FreeRADIUS site 50 | auth_port: 1822 51 | # Port used by the accounting service for this FreeRADIUS site 52 | acct_port: 1823 53 | # Port used by the authentication service of inner tunnel 54 | # for this FreeRADIUS site 55 | inner_tunnel_auth_port: 18230 56 | # If you want to use a custom certificate for FreeRADIUS 57 | # EAP module, you can specify the path to the CA, server 58 | # certificate, and private key, and DH key as follows. 59 | # Ensure that these files can be read by the "freerad" user. 60 | cert: /etc/freeradius/certs/cert.pem 61 | private_key: /etc/freeradius/certs/key.pem 62 | ca: /etc/freeradius/certs/ca.crt 63 | dh: /etc/freeradius/certs/dh 64 | tls_config_extra: | 65 | private_key_password = whatever 66 | ecdh_curve = "prime256v1" 67 | # You can add as many organizations as you want 68 | - name: demo 69 | uuid: 00000000-0000-0000-0000-000000000001 70 | radius_secret: demo-radius-token 71 | auth_port: 1832 72 | acct_port: 1833 73 | inner_tunnel_auth_port: 18330 74 | # If you omit the certificate fields, 75 | # the FreeRADIUS site will use the default certificates 76 | # located in /etc/freeradius/certs. 77 | 78 | In the example above, custom ports 1822, 1823, and 18230 are utilized for 79 | FreeRADIUS authentication, accounting, and inner tunnel authentication, 80 | respectively. These custom ports are specified because the Ansible role 81 | creates a common FreeRADIUS site for all organizations, which also 82 | supports captive portal functionality. This common site is configured to 83 | listen on the default FreeRADIUS ports 1812, 1813, and 18120. Therefore, 84 | when configuring WPA Enterprise authentication for each organization, 85 | unique ports must be provided to ensure proper isolation and 86 | functionality. 87 | 88 | Using Let's Encrypt Certificate for WPA Enterprise (EAP-TTLS-PAP) 89 | ----------------------------------------------------------------- 90 | 91 | In this section, we demonstrate how to utilize Let's Encrypt certificates 92 | for WPA Enterprise (EAP-TTLS-PAP) authentication. Similar to the 93 | :doc:`./certbot-ssl`, we use `geerlingguy.certbot 94 | `_ role to automatically 95 | install and renew a valid SSL certificate. 96 | 97 | The following example playbook achieves the following goals: 98 | 99 | - Provision a separate Let's Encrypt certificate for the 100 | `freeradius.yourdomain.com` hostname. This certificate will be utilized 101 | by the FreeRADIUS site for WPA Enterprise authentication. 102 | - Create a renewal hook to set permissions on the generated certificate so 103 | the FreeRADIUS server can read it. 104 | 105 | .. note:: 106 | 107 | You can also use the same SSL certificate for both Nginx and 108 | FreeRADIUS, but it's crucial to understand the security implications. 109 | Please exercise caution and refer to the example playbook comments for 110 | guidance. 111 | 112 | .. code-block:: yaml 113 | 114 | - hosts: openwisp2 115 | become: "{{ become | default('yes') }}" 116 | roles: 117 | - geerlingguy.certbot 118 | - openwisp.openwisp2 119 | vars: 120 | # certbot configuration 121 | certbot_auto_renew_minute: "20" 122 | certbot_auto_renew_hour: "5" 123 | certbot_create_if_missing: true 124 | certbot_auto_renew_user: "" 125 | certbot_certs: 126 | - email: "" 127 | domains: 128 | - "{{ inventory_hostname }}" 129 | # If you choose to re-use the same certificate for both services, 130 | # you can omit the following item in your playbook. 131 | - email: "" 132 | domains: 133 | - "freeradius.yourdomain.com" 134 | # Configuration to use Let's Encrypt certificate for OpenWISP server (Nnginx) 135 | openwisp2_ssl_cert: "/etc/letsencrypt/live/{{ inventory_hostname }}/fullchain.pem" 136 | openwisp2_ssl_key: "/etc/letsencrypt/live/{{ inventory_hostname }}/privkey.pem" 137 | # Configuration for openwisp-radius 138 | openwisp2_radius: true 139 | openwisp2_freeradius_install: true 140 | freeradius_eap_orgs: 141 | - name: demo 142 | uuid: 00000000-0000-0000-0000-000000000001 143 | radius_secret: demo-radius-token 144 | auth_port: 1832 145 | acct_port: 1833 146 | inner_tunnel_auth_port: 18330 147 | # Update the cert_file and private_key paths to point to the 148 | # Let's Encrypt certificate. 149 | cert: /etc/letsencrypt/live/freeradius.yourdomain.com/fullchain.pem 150 | private_key: /etc/letsencrypt/live/freeradius.yourdomain.com/privkey.pem 151 | # If you choose to re-use the same certificate for both services, 152 | # your configuration would look like this 153 | # cert: /etc/letsencrypt/live/{{ inventory_hostname }}/fullchain.pem 154 | # private_key: /etc/letsencrypt/live/{{ inventory_hostname }}/privkey.pem 155 | tasks: 156 | # Tasks to ensure the Let's Encrypt certificate can be read by the FreeRADIUS server. 157 | # If you are using the same certificate for both services, you need to 158 | # replace "freeradius.yourdomain.com" with "{{ inventory_hostname }}" 159 | # in the following task. 160 | - name: "Create a renewal hook for setting permissions on /etc/letsencrypt/live/freeradius.yourdomain.com" 161 | copy: 162 | content: | 163 | #!/bin/bash 164 | chown -R root:freerad /etc/letsencrypt/live/ /etc/letsencrypt/archive/ 165 | chmod 0750 /etc/letsencrypt/live/ /etc/letsencrypt/archive/ 166 | chmod -R 0640 /etc/letsencrypt/archive/freeradius.yourdomain.com/ 167 | chmod 0750 /etc/letsencrypt/archive/freeradius.yourdomain.com/ 168 | dest: /etc/letsencrypt/renewal-hooks/post/chown_freerad 169 | owner: root 170 | group: root 171 | mode: '0700' 172 | register: chown_freerad_result 173 | - name: Change the ownership of the certificate files 174 | when: chown_freerad_result.changed 175 | command: /etc/letsencrypt/renewal-hooks/post/chown_freerad 176 | -------------------------------------------------------------------------------- /docs/user/enabling-modules.rst: -------------------------------------------------------------------------------- 1 | Enabling OpenWISP Modules 2 | ========================= 3 | 4 | .. contents:: **Table of Contents**: 5 | :depth: 3 6 | :local: 7 | 8 | Enabling the Monitoring Module 9 | ------------------------------ 10 | 11 | The :doc:`Monitoring module ` is enabled by default, it 12 | can be disabled by setting ``openwisp2_monitoring`` to ``false``. 13 | 14 | Enabling the Firmware Upgrader Module 15 | ------------------------------------- 16 | 17 | It is encouraged that you read the :doc:`quick-start guide of 18 | openwisp-firmware-upgrader ` before 19 | going ahead. 20 | 21 | To enable the :doc:`Firmware Upgrader ` module 22 | you need to set ``openwisp2_firmware_upgrader`` to ``true`` in your 23 | ``playbook.yml`` file. Here's a short summary of how to do this: 24 | 25 | **Step 1**: :ref:`Install ansible ` 26 | 27 | **Step 2**: :ref:`Install this role ` 28 | 29 | **Step 3**: :ref:`Create inventory file ` 30 | 31 | **Step 4**: Create a playbook file with following contents: 32 | 33 | .. code-block:: yaml 34 | 35 | - hosts: openwisp2 36 | become: "{{ become | default('yes') }}" 37 | roles: 38 | - openwisp.openwisp2 39 | vars: 40 | openwisp2_firmware_upgrader: true 41 | 42 | **Step 5**: :ref:`Run the playbook ` 43 | 44 | When the playbook is done running, if you got no errors you can login at 45 | https://openwisp2.mydomain.com/admin with the following credentials: 46 | 47 | .. code-block:: text 48 | 49 | username: admin 50 | password: admin 51 | 52 | You can configure :doc:`openwisp-firmware-upgrader specific settings 53 | ` using the 54 | ``openwisp2_extra_django_settings`` or 55 | ``openwisp2_extra_django_settings_instructions``. 56 | 57 | E.g: 58 | 59 | .. code-block:: yaml 60 | 61 | - hosts: openwisp2 62 | become: "{{ become | default('yes') }}" 63 | roles: 64 | - openwisp.openwisp2 65 | vars: 66 | openwisp2_firmware_upgrader: true 67 | openwisp2_extra_django_settings_instructions: 68 | - | 69 | OPENWISP_CUSTOM_OPENWRT_IMAGES = ( 70 | ('my-custom-image-squashfs-sysupgrade.bin', { 71 | 'label': 'My Custom Image', 72 | 'boards': ('MyCustomImage',) 73 | }), 74 | ) 75 | 76 | Refer the :doc:`role-variables` section of the documentation for a 77 | complete list of available role variables. 78 | 79 | Enabling the Network Topology Module 80 | ------------------------------------ 81 | 82 | To enable the :doc:`Network Topology module ` you 83 | need to set ``openwisp2_network_topology`` to ``true`` in your 84 | ``playbook.yml`` file. Here's a short summary of how to do this: 85 | 86 | **Step 1**: :ref:`Install ansible ` 87 | 88 | **Step 2**: :ref:`Install this role ` 89 | 90 | **Step 3**: :ref:`Create inventory file ` 91 | 92 | **Step 4**: Create a playbook file with following contents: 93 | 94 | .. code-block:: yaml 95 | 96 | - hosts: openwisp2 97 | become: "{{ become | default('yes') }}" 98 | roles: 99 | - openwisp.openwisp2 100 | vars: 101 | openwisp2_network_topology: true 102 | 103 | **Step 5**: :ref:`Run the playbook ` 104 | 105 | When the playbook is done running, if you got no errors you can login at 106 | https://openwisp2.mydomain.com/admin with the following credentials: 107 | 108 | .. code-block:: text 109 | 110 | username: admin 111 | password: admin 112 | 113 | .. _ansible_enabling_radius_module: 114 | 115 | Enabling the RADIUS Module 116 | -------------------------- 117 | 118 | To enable the :doc:`RADIUS module ` you need to set 119 | ``openwisp2_radius`` to ``true`` in your ``playbook.yml`` file. Here's a 120 | short summary of how to do this: 121 | 122 | **Step 1**: :ref:`Install ansible ` 123 | 124 | **Step 2**: :ref:`Install this role ` 125 | 126 | **Step 3**: :ref:`Create inventory file ` 127 | 128 | **Step 4**: Create a playbook file with following contents: 129 | 130 | .. code-block:: yaml 131 | 132 | - hosts: openwisp2 133 | become: "{{ become | default('yes') }}" 134 | roles: 135 | - openwisp.openwisp2 136 | vars: 137 | openwisp2_radius: true 138 | openwisp2_freeradius_install: true 139 | # set to false when you don't want to register openwisp-radius 140 | # API endpoints. 141 | openwisp2_radius_urls: true 142 | 143 | .. note:: 144 | 145 | ``openwisp2_freeradius_install`` option provides a basic configuration 146 | of freeradius for OpenIWSP, it sets up the :ref:`radius user token 147 | mechanism ` if you want to use another mechanism or 148 | manage your freeradius separately, please disable this option by 149 | setting it to ``false``. 150 | 151 | **Step 5**: :ref:`Run the playbook ` 152 | 153 | When the playbook is done running, if you got no errors you can login at: 154 | 155 | .. code-block:: 156 | 157 | https://openwisp2.mydomain.com/admin 158 | username: admin 159 | password: admin 160 | 161 | **Note:** for more information regarding radius configuration options, 162 | look for the word “radius” in the :doc:`role-variables` section of this 163 | document. 164 | -------------------------------------------------------------------------------- /docs/user/installing-on-vm.rst: -------------------------------------------------------------------------------- 1 | Install OpenWISP for Testing in a VirtualBox VM 2 | =============================================== 3 | 4 | If you want to try out OpenWISP in your own development environment, the 5 | safest way is to use a VirtualBox Virtual Machine (from here on VM). 6 | 7 | .. contents:: **Table of Contents**: 8 | :depth: 2 9 | :local: 10 | 11 | Using Vagrant 12 | ------------- 13 | 14 | **Since August 2018 there's a new fast and easy way to install OpenWISP 15 | for testing purposes** leveraging `Vagrant `__, 16 | a popular open source tool for building and maintaining portable virtual 17 | software development environments. 18 | 19 | To use this new way, clone the repository `vagrant-openwisp2 20 | `__, it contains the 21 | instructions (in the ``README.md``) and the vagrant configuration to 22 | perform the automatic installation. 23 | 24 | Alternatively, you can read on to learn how to install *VirtualBox* and 25 | run *ansible-openwisp2* manually, this is useful if you need to test 26 | advanced customizations of *OpenWISP*. 27 | 28 | Installing Debian 11 on VirtualBox 29 | ---------------------------------- 30 | 31 | Install `VirtualBox `__ and create a new Virtual 32 | Machine running Debian 11. A step-by-step guide is available `here 33 | `__, 34 | however we need to change a few things to get ansible working. 35 | 36 | VM Configuration 37 | ---------------- 38 | 39 | Proceed with the installation as shown in the guide linked above, and come 40 | back here when you see this screen: 41 | 42 | .. figure:: ../images/debian-software-selection.png 43 | :target: ../../_images/debian-software-selection.png 44 | :alt: Screenshot of the Software Selection screen 45 | 46 | We're only running this as a server, so you can uncheck ``Debian desktop 47 | environment``. Make sure ``SSH server`` and ``standard system utilities`` 48 | are checked. 49 | 50 | Next, add a `Host-only Network Adapter 51 | `__ and 52 | assign an IP address to the VM. 53 | 54 | - On the Main VirtualBox page, Go to ``File > Host Network Manager`` 55 | - Click the + icon to create a new adapter 56 | - Set the IPv4 address to ``192.168.56.1`` and the IPv4 Network Mask to 57 | ``255.255.255.0``. You may need to select ``Configure Adapter Manually`` 58 | to do this. The IPv6 settings can be ignored 59 | 60 | .. image:: ../images/host-only-network.png 61 | :target: ../../_images/host-only-network.png 62 | :alt: Screenshot of the Host-only network configuration screen 63 | 64 | - Shut off your VM 65 | - In your VM settings, in the Network section, click Adapter 2 and Enable 66 | this Adapter 67 | - Select Host-only adapter and the name of the adapter you created 68 | - Boot up your VM, run ``su``, and type in your superuser password 69 | - Run ``ls /sys/class/net`` and take note of the output 70 | - Run ``nano /etc/network/interfaces`` and add the following at the end of 71 | the file: 72 | 73 | .. code-block:: text 74 | 75 | auto enp0s8 76 | iface enp0s8 inet static 77 | address 192.168.56.2 78 | netmask 255.255.255.0 79 | network 192.168.56.0 80 | broadcast 192.168.56.255 81 | 82 | Replace ``enp0s8`` with the network interface not present in the file 83 | but is shown when running ``ls /sys/class/net``. 84 | 85 | - Save the file with *CTRL+O* then Enter, and exit with *CTRL+X*. 86 | - Restart the machine by running ``reboot``. 87 | 88 | Make sure you can access your VM via ssh: 89 | 90 | .. code-block:: shell 91 | 92 | ssh 192.168.56.2 93 | 94 | Back to your local machine 95 | -------------------------- 96 | 97 | Proceed with these steps in your **local machine**, not the VM. 98 | 99 | **Step 1**: :ref:`Install ansible ` 100 | 101 | **Step 2**: :ref:`Install the OpenWISP2 role for Ansible 102 | ` 103 | 104 | **Step 3**: :ref:`Set up a working directory 105 | ` 106 | 107 | **Step 4**: Create the ``hosts`` file 108 | 109 | Create an ansible inventory file named ``hosts`` **in your working 110 | directory** (i.e. not in the VM) with the following contents: 111 | 112 | .. code-block:: 113 | 114 | [openwisp2] 115 | 192.168.56.2 116 | 117 | **Step 5**: Create the ansible playbook 118 | 119 | In the same directory where you created the ``host`` file, create a file 120 | named ``playbook.yml`` which contains the following: 121 | 122 | .. code-block:: yaml 123 | 124 | - hosts: openwisp2 125 | roles: 126 | - openwisp.openwisp2 127 | # the following line is needed only when an IP address is used as the inventory hostname 128 | vars: 129 | postfix_myhostname: localhost 130 | 131 | **Step 6**: Run the playbook 132 | 133 | .. code-block:: shell 134 | 135 | ansible-playbook -i hosts playbook.yml -b -k -K --become-method=su 136 | 137 | When the playbook ran successfully, you can log in at 138 | ``https://192.168.56.2/admin`` with the following credentials: 139 | 140 | .. code-block:: text 141 | 142 | username: admin 143 | password: admin 144 | -------------------------------------------------------------------------------- /docs/user/quickstart.rst: -------------------------------------------------------------------------------- 1 | Deploying OpenWISP Using Ansible 2 | ================================ 3 | 4 | .. contents:: **Table of Contents**: 5 | :depth: 3 6 | :local: 7 | 8 | Introduction & Prerequisites 9 | ---------------------------- 10 | 11 | .. note:: 12 | 13 | If you want to use the latest features of OpenWISP, refer to 14 | :ref:`ansible_deploying_development_version`. 15 | 16 | If you don't know how to use ansible, don't panic, this procedure will 17 | guide you towards a fully working basic OpenWISP installation. 18 | 19 | If you already know how to use ansible, you can skip this tutorial. 20 | 21 | First of all you need to understand two key concepts: 22 | 23 | - for **“production server”** we mean a server (**not a laptop or a 24 | desktop computer!**) with public IpV4 / IPv6 which is used to host 25 | OpenWISP 26 | - for **“local machine”** we mean the host from which you launch ansible, 27 | e.g.: your own laptop 28 | 29 | Ansible is a configuration management tool that works by entering 30 | production servers via SSH, **so you need to install it and configure it 31 | on the machine where you launch the deployment** and this machine must be 32 | able to SSH into the production server. 33 | 34 | Ansible will be run on your local machine and from there it will connect 35 | to the production server to install OpenWISP. 36 | 37 | .. note:: 38 | 39 | It is recommended to use this procedure on clean virtual machines or 40 | linux containers. 41 | 42 | If you are trying to install OpenWISP on your laptop or desktop PC 43 | just for testing purposes, please read :doc:`Install OpenWISP for 44 | testing in a VirtualBox VM <./installing-on-vm>`. 45 | 46 | .. _ansible_install: 47 | 48 | Install Ansible 49 | --------------- 50 | 51 | Install ansible (minimum recommended version 2.13) **on your local 52 | machine** (not the production server!) if you haven't done already. 53 | 54 | We suggest following the `ansible installation guide 55 | `__. 56 | to install ansible. It is recommended to install ansible through a virtual 57 | environment to avoid dependency issues. 58 | 59 | Please ensure that you have the correct version of Jinja installed in your 60 | Python environment: ``pip install Jinja2>=2.11`` 61 | 62 | .. _ansible_install_role: 63 | 64 | Install This Role 65 | ----------------- 66 | 67 | For the sake of simplicity, the easiest thing is to install this role **on 68 | your local machine** via ``ansible-galaxy`` (which was installed when 69 | installing ansible), therefore run: 70 | 71 | .. code-block:: shell 72 | 73 | ansible-galaxy install openwisp.openwisp2 74 | 75 | Ensure that you have the `community.general 76 | `_ 77 | and `ansible.posix 78 | `_ 79 | collections installed and up to date: 80 | 81 | .. code-block:: shell 82 | 83 | ansible-galaxy collection install "community.general:>=3.6.0" 84 | ansible-galaxy collection install "ansible.posix" 85 | 86 | .. _ansible_choose_working_directory: 87 | 88 | Choose a Working Directory 89 | -------------------------- 90 | 91 | Choose a working directory **on your local machine** where to put the 92 | configuration of OpenWISP. 93 | 94 | This will be useful when you will need to upgrade OpenWISP. 95 | 96 | E.g.: 97 | 98 | .. code-block:: shell 99 | 100 | mkdir ~/openwisp2-ansible-playbook 101 | cd ~/openwisp2-ansible-playbook 102 | 103 | .. _ansible_create_inventory_file: 104 | 105 | Create Inventory File 106 | --------------------- 107 | 108 | The inventory file is where group of servers are defined. In our simple 109 | case we will define just one group in which we will put just one server. 110 | 111 | Create a new file called ``hosts`` in the working directory **on your 112 | local machine** (the directory just created in the previous step), with 113 | the following contents: 114 | 115 | .. code-block:: text 116 | 117 | [openwisp2] 118 | openwisp2.mydomain.com 119 | 120 | Substitute ``openwisp2.mydomain.com`` with your **production server**'s 121 | hostname - **DO NOT REPLACE openwisp2.mydomain.com WITH AN IP ADDRESS**, 122 | otherwise email sending through postfix will break, causing 500 internal 123 | server errors on some operations. 124 | 125 | .. _ansible_create_playbook_file: 126 | 127 | Create Playbook File 128 | -------------------- 129 | 130 | Create a new playbook file ``playbook.yml`` **on your local machine** with 131 | the following contents: 132 | 133 | .. code-block:: yaml 134 | 135 | - hosts: openwisp2 136 | become: "{{ become | default('yes') }}" 137 | roles: 138 | - openwisp.openwisp2 139 | vars: 140 | openwisp2_default_from_email: "openwisp2@openwisp2.mydomain.com" 141 | 142 | The line ``become: "{{ become | default('yes') }}"`` means ansible will 143 | use the ``sudo`` program to run each command. You may remove this line if 144 | you don't need it (e.g.: if you are ``root`` user on the production 145 | server). 146 | 147 | You may replace ``openwisp2`` on the ``hosts`` field with your production 148 | server's hostname if you desire. 149 | 150 | Substitute ``openwisp2@openwisp2.mydomain.com`` with what you deem most 151 | appropriate as default sender for emails sent by OpenWISP 2. 152 | 153 | .. _ansible_run_playbook: 154 | 155 | Run the Playbook 156 | ---------------- 157 | 158 | Now is time to **deploy OpenWISP to the production server**. 159 | 160 | Run the playbook **from your local machine** with: 161 | 162 | .. code-block:: shell 163 | 164 | ansible-playbook -i hosts playbook.yml -u -k --become -K 165 | 166 | Substitute ```` with your **production server**'s username. 167 | 168 | The ``-k`` argument will need the ``sshpass`` program. 169 | 170 | You can remove ``-k``, ``--become`` and ``-K`` if your public SSH key is 171 | installed on the server. 172 | 173 | .. tip:: 174 | 175 | - If you have an error like ``Authentication or permission failure`` 176 | then try to use *root* user ``ansible-playbook -i hosts playbook.yml 177 | -u root -k`` 178 | - If you have an error about adding the host's fingerprint to the 179 | ``known_hosts`` file, you can simply connect to the host via SSH and 180 | answer yes when prompted; then you can run ``ansible-playbook`` 181 | again. 182 | 183 | When the playbook is done running, if you got no errors you can login at 184 | ``https://openwisp2.mydomain.com/admin`` with the following credentials: 185 | 186 | .. code-block:: text 187 | 188 | username: admin 189 | password: admin 190 | 191 | Substitute ``openwisp2.mydomain.com`` with your production server's 192 | hostname. 193 | 194 | Now proceed with the following steps: 195 | 196 | 1. change the password (and the username if you like) of the superuser as 197 | soon as possible 198 | 2. update the ``name`` field of the default ``Site`` object to accurately 199 | display site name in email notifications 200 | 3. edit the information of the default organization 201 | 4. in the default organization you just updated, note down the 202 | automatically generated *shared secret* option, you will need it to use 203 | the :doc:`auto-registration feature of openwisp-config 204 | ` 205 | 5. this Ansible role creates a default template to update 206 | ``authorized_keys`` on networking devices using the default access 207 | credentials. The role will either use an existing SSH key pair or 208 | create a new one if no SSH key pair exists on the host machine. 209 | 210 | Now you are ready to start configuring your network! **If you need help** 211 | you can ask questions on one of the official `OpenWISP Support Channels 212 | `__. 213 | 214 | Upgrading OpenWISP 215 | ------------------ 216 | 217 | .. important:: 218 | 219 | It is strongly recommended to back up your current instance before 220 | upgrading. 221 | 222 | Update this ansible-role via ``ansible-galaxy``: 223 | 224 | .. code-block:: shell 225 | 226 | ansible-galaxy install --force openwisp.openwisp2 227 | 228 | Run ``ansible-playbook`` again **from your local machine**: 229 | 230 | .. code-block:: shell 231 | 232 | ansible-playbook -i hosts playbook.yml 233 | 234 | You may also run the playbook automatically periodically or when a new 235 | release of OpenWISP2, for example, by setting up a continuous integration 236 | system. 237 | 238 | .. _ansible_deploying_development_version: 239 | 240 | Deploying the Development Version of OpenWISP 241 | --------------------------------------------- 242 | 243 | The following steps will help you set up and install the development 244 | version of OpenWISP which is not released yet, but ships new features and 245 | improvements. 246 | 247 | Create a directory for organizing your playbook, roles and collections. In 248 | this example, ``openwisp-dev`` is used. Create ``roles`` and 249 | ``collections`` directories in ``~/openwisp-dev``. 250 | 251 | .. code-block:: 252 | 253 | mkdir -p ~/openwisp-dev/roles 254 | mkdir -p ~/openwisp-dev/collections 255 | 256 | Change directory to ``~/openwisp-dev/`` in terminal and create 257 | configuration and requirement files for Ansible. 258 | 259 | .. code-block:: 260 | 261 | cd ~/openwisp-dev/ 262 | touch ansible.cfg 263 | touch requirements.yml 264 | 265 | Setup ``roles_path`` and ``collections_paths`` variables in 266 | ``ansible.cfg`` as follows: 267 | 268 | .. code-block:: 269 | 270 | [defaults] 271 | roles_path=~/openwisp-dev/roles 272 | collections_paths=~/openwisp-dev/collections 273 | 274 | Ensure your ``requirements.yml`` contains following content: 275 | 276 | .. code-block:: yaml 277 | 278 | --- 279 | roles: 280 | - src: https://github.com/openwisp/ansible-openwisp2.git 281 | version: master 282 | name: openwisp.openwisp2-dev 283 | collections: 284 | - name: community.general 285 | version: ">=3.6.0" 286 | 287 | Install requirements from the ``requirements.yml`` as follows 288 | 289 | .. code-block:: 290 | 291 | ansible-galaxy install -r requirements.yml 292 | 293 | Now, create hosts file and ``playbook.yml``: 294 | 295 | .. code-block:: 296 | 297 | touch hosts 298 | touch playbook.yml 299 | 300 | Follow instructions in :ref:`ansible_create_inventory_file` section to 301 | configure ``hosts`` file. 302 | 303 | You can reference the example playbook below (tested on Debian 11) for 304 | installing a fully-featured version of OpenWISP. 305 | 306 | .. code-block:: yaml 307 | 308 | - hosts: openwisp2 309 | become: "{{ become | default('yes') }}" 310 | roles: 311 | - openwisp.openwisp2-dev 312 | vars: 313 | openwisp2_network_topology: true 314 | openwisp2_firmware_upgrader: true 315 | openwisp2_radius: true 316 | openwisp2_monitoring: true # monitoring is enabled by default 317 | 318 | Read :doc:`role-variables` section to learn about available configuration 319 | variables. 320 | 321 | Follow instructions in :ref:`ansible_run_playbook` section to run above 322 | playbook. 323 | -------------------------------------------------------------------------------- /docs/user/role-variables.rst: -------------------------------------------------------------------------------- 1 | Role Variables 2 | ============== 3 | 4 | This role has many variables values that can be changed to best suit your 5 | needs. 6 | 7 | Below are listed all the variables you can customize (you may also want to 8 | take a look at `the default values of these variables 9 | `__). 10 | 11 | .. code-block:: yaml 12 | 13 | - hosts: yourhost 14 | roles: 15 | # you can add other roles here 16 | - openwisp.openwisp2 17 | vars: 18 | # Enable the modules you want to use 19 | openwisp2_network_topology: false 20 | openwisp2_firmware_upgrader: false 21 | openwisp2_monitoring: true 22 | # you may replace the values of these variables with any value or URL 23 | # supported by pip (the python package installer) 24 | # use these to install forks, branches or development versions 25 | # WARNING: only do this if you know what you are doing; disruption 26 | # of service is very likely to occur if these variables are changed 27 | # without careful analysis and testing 28 | openwisp2_controller_version: "openwisp-controller~=1.0.0" 29 | openwisp2_network_topology_version: "openwisp-network-topology~=1.0.0" 30 | openwisp2_firmware_upgrader_version: "openwisp-firmware-upgrader~=1.0.0" 31 | openwisp2_monitoring_version: "openwisp-monitoring~=1.0.0" 32 | openwisp2_radius_version: "openwisp-radius~=1.0.0" 33 | openwisp2_django_version: "django~=3.2.13" 34 | # Setting this to true will enable subnet division feature of 35 | # openwisp-controller. Refer openwisp-controller documentation 36 | # for more information. https://github.com/openwisp/openwisp-controller#subnet-division-app 37 | # By default, it is set to false. 38 | openwisp2_controller_subnet_division: true 39 | # when openwisp2_radius_urls is set to false, the radius module 40 | # is setup but it's urls are not added, which means API and social 41 | # views cannot be used, this is helpful if you have an external 42 | # radius instance. 43 | openwisp2_radius_urls: "{{ openwisp2_radius }}" 44 | openwisp2_path: /opt/openwisp2 45 | # It is recommended that you change the value of this variable if you intend to use 46 | # OpenWISP2 in production, as a misconfiguration may result in emails not being sent 47 | openwisp2_default_from_email: "openwisp2@yourhostname.com" 48 | # Email backend used by Django for sending emails. By default, the role 49 | # uses "CeleryEmailBackend" from django-celery-email. 50 | # (https://github.com/pmclanahan/django-celery-email) 51 | openwisp2_email_backend: "djcelery_email.backends.CeleryEmailBackend" 52 | # Email timeout in seconds used by Django for blocking operations 53 | # like connection attempts. For more info read the Django documentation, 54 | # https://docs.djangoproject.com/en/4.2/ref/settings/#email-timeout. 55 | # Defaults to 10 seconds. 56 | openwisp2_email_timeout: 5 57 | # edit database settings only if you are not using sqlite 58 | # eg, for deploying with PostgreSQL (recommended for production usage) 59 | # you will need the PostGIS spatial extension, find more info at: 60 | # https://docs.djangoproject.com/en/4.1/ref/contrib/gis/tutorial/ 61 | openwisp2_database: 62 | engine: django.contrib.gis.db.backends.postgis 63 | name: "{{ DB_NAME }}" 64 | user: "{{ DB_USER }}" 65 | host: "{{ DB_HOST }}" 66 | password: "{{ DB_PASSWORD }}" 67 | port: 5432 68 | # SPATIALITE_LIBRARY_PATH django setting 69 | # The role will attempt determining the right mod-spatialite path automatically 70 | # But you can use this variable to customize the path or fix future arising issues 71 | openwisp2_spatialite_path: "mod_spatialite.so" 72 | # customize other django settings: 73 | openwisp2_language_code: en-gb 74 | openwisp2_time_zone: UTC 75 | # openwisp-controller context 76 | openwisp2_context: {} 77 | # additional allowed hosts 78 | openwisp2_allowed_hosts: 79 | - myadditionalhost.openwisp.org 80 | # geographic map settings 81 | openwisp2_leaflet_config: 82 | DEFAULT_CENTER: [42.06775, 12.62011] 83 | DEFAULT_ZOOM: 6 84 | # enable/disable geocoding check 85 | openwisp2_geocoding_check: true 86 | # specify path to a valid SSL certificate and key 87 | # (a self-signed SSL cert will be generated if omitted) 88 | openwisp2_ssl_cert: "/etc/nginx/ssl/server.crt" 89 | openwisp2_ssl_key: "/etc/nginx/ssl/server.key" 90 | # customize the self-signed SSL certificate info if needed 91 | openwisp2_ssl_country: "US" 92 | openwisp2_ssl_state: "California" 93 | openwisp2_ssl_locality: "San Francisco" 94 | openwisp2_ssl_organization: "IT dep." 95 | # the following setting controls which ip address range 96 | # is allowed to access the controller via unencrypted HTTP 97 | # (this feature is disabled by default) 98 | openwisp2_http_allowed_ip: "10.8.0.0/16" 99 | # additional python packages that will be installed with pip 100 | openwisp2_extra_python_packages: 101 | - bpython 102 | - django-owm-legacy 103 | # additional django apps that will be added to settings.INSTALLED_APPS 104 | # (if the app needs to be installed, the name its python package 105 | # must be also added to the openwisp2_extra_python_packages var) 106 | openwisp2_extra_django_apps: 107 | - owm_legacy 108 | # additional django settings example 109 | openwisp2_extra_django_settings: 110 | CSRF_COOKIE_AGE: 2620800.0 111 | # in case you need to add python instructions to the django settings file 112 | openwisp2_extra_django_settings_instructions: 113 | - TEMPLATES[0]['OPTIONS']['loaders'].insert(0, 'apptemplates.Loader') 114 | # extra URL settings for django 115 | openwisp2_extra_urls: 116 | - "path(r'', include('my_custom_app.urls'))" 117 | # allows to specify imports that are used in the websocket routes, e.g.: 118 | openwisp2_websocket_extra_imports: 119 | - from my_custom_app.websockets.routing import get_routes as get_custom_app_routes 120 | # allows to specify extra websocket routes, e.g.: 121 | openwisp2_websocket_extra_routes: 122 | # Callable that returns a list of routes 123 | - get_custom_app_routes() 124 | # List of routes 125 | - "[path('ws/custom-app/', consumer.CustomAppConsumer.as_asgi())]" 126 | # controller URL are enabled by default 127 | # but can be disabled in multi-VM installations if needed 128 | openwisp2_controller_urls: true 129 | # The default retention policy that applies to the timeseries data 130 | # https://github.com/openwisp/openwisp-monitoring#openwisp-monitoring-default-retention-policy 131 | openwisp2_monitoring_default_retention_policy: "26280h0m0s" # 3 years 132 | # whether NGINX should be installed 133 | openwisp2_nginx_install: true 134 | # spdy protocol support (disabled by default) 135 | openwisp2_nginx_spdy: false 136 | # HTTP2 protocol support (disabled by default) 137 | openwisp2_nginx_http2: false 138 | # ipv6 must be enabled explicitly to avoid errors 139 | openwisp2_nginx_ipv6: false 140 | # nginx client_max_body_size setting 141 | openwisp2_nginx_client_max_body_size: 10M 142 | # list of upstream servers for OpenWISP 143 | openwisp2_nginx_openwisp_server: 144 | - "localhost:8000" 145 | # dictionary containing more nginx settings for 146 | # the 443 section of the openwisp2 nginx configuration 147 | # IMPORTANT: 1. you can add more nginx settings in this dictionary 148 | # 2. here we list the default values used 149 | openwisp2_nginx_ssl_config: 150 | gzip: "on" 151 | gzip_comp_level: "6" 152 | gzip_proxied: "any" 153 | gzip_min_length: "1000" 154 | gzip_types: 155 | - "text/plain" 156 | - "text/html" 157 | - "image/svg+xml" 158 | - "application/json" 159 | - "application/javascript" 160 | - "text/xml" 161 | - "text/css" 162 | - "application/xml" 163 | - "application/x-font-ttf" 164 | - "font/opentype" 165 | # nginx error log configuration 166 | openwisp2_nginx_access_log: "{{ openwisp2_path }}/log/nginx.access.log" 167 | openwisp2_nginx_error_log: "{{ openwisp2_path }}/log/nginx.error.log error" 168 | # nginx Content Security Policy header, customize if needed 169 | openwisp2_nginx_csp: > 170 | CUSTOM_NGINX_SECURITY_POLICY 171 | # uwsgi gid, omitted by default 172 | openwisp2_uwsgi_gid: null 173 | # number of uWSGI process to spawn. Default value is 1. 174 | openwisp2_uwsgi_processes: 1 175 | # number of threads each uWSGI process will have. Default value is 1. 176 | openwisp2_uwsgi_threads: 2 177 | # value of the listen queue of uWSGI 178 | openwisp2_uwsgi_listen: 100 179 | # socket on which uwsgi should listen. Defaults to UNIX socket 180 | # at "{{ openwisp2_path }}/uwsgi.sock" 181 | openwisp2_uwsgi_socket: 127.0.0.1:8000 182 | # extra uwsgi configuration parameters that cannot be 183 | # configured using dedicated ansible variables 184 | openwisp2_uwsgi_extra_conf: | 185 | single-interpreter=True 186 | log-4xx=True 187 | log-5xx=True 188 | disable-logging=True 189 | auto-procname=True 190 | # whether daphne should be installed 191 | # must be enabled for serving websocket requests 192 | openwisp2_daphne_install: true 193 | # number of daphne process to spawn. Default value is 1 194 | openwisp2_daphne_processes: 2 195 | # maximum time to allow a websocket to be connected (in seconds) 196 | openwisp2_daphne_websocket_timeout: 1800 197 | # the following setting controls which ip address range 198 | # is allowed to access the openwisp2 admin web interface 199 | # (by default any IP is allowed) 200 | openwisp2_admin_allowed_network: null 201 | # install ntp client (enabled by default) 202 | openwisp2_install_ntp: true 203 | # if you have any custom supervisor service, you can 204 | # configure it to restart along with other supervisor services 205 | openwisp2_extra_supervisor_restart: 206 | - name: my_custom_service 207 | when: my_custom_service_enabled 208 | # Disable usage metric collection. It is enabled by default. 209 | # Read more about it at 210 | # https://openwisp.io/docs/user/usage-metric-collection.html 211 | openwisp2_usage_metric_collection: false 212 | # enable sentry example 213 | openwisp2_sentry: 214 | dsn: "https://7d2e3cd61acc32eca1fb2a390f7b55e1:bf82aab5ddn4422688e34a486c7426e3@getsentry.com:443/12345" 215 | openwisp2_default_cert_validity: 1825 216 | openwisp2_default_ca_validity: 3650 217 | # the following options for redis allow to configure an external redis instance if needed 218 | openwisp2_redis_install: true 219 | openwisp2_redis_host: localhost 220 | openwisp2_redis_port: 6379 221 | openwisp2_redis_cache_url: "redis://{{ openwisp2_redis_host }}:{{ openwisp2_redis_port }}/1" 222 | # the following options are required to configure influxdb which is used in openwisp-monitoring 223 | openwisp2_influxdb_install: true 224 | openwisp2_timeseries_database: 225 | backend: "openwisp_monitoring.db.backends.influxdb" 226 | user: "openwisp" 227 | password: "openwisp" 228 | name: "openwisp2" 229 | host: "localhost" 230 | port: 8086 231 | # celery concurrency for the default queue, by default the number of CPUs is used 232 | # celery concurrency for the default queue, by default it is set to 1 233 | # Setting it to "null" will make concurrency equal to number of CPUs if autoscaling is not used 234 | openwisp2_celery_concurrency: null 235 | # alternative to the previous option, the celery autoscale option can be set if needed 236 | # for more info, consult the documentation of celery regarding "autoscaling" 237 | # by default it is set to "null" (no autoscaling) 238 | openwisp2_celery_autoscale: 4,1 239 | # prefetch multiplier for the default queue, 240 | # the default value is calculated automatically by celery 241 | openwisp2_celery_prefetch_multiplier: null 242 | # celery queuing mode for the default queue, 243 | # leaving the default will work for most cases 244 | openwisp2_celery_optimization: default 245 | # whether the dedicated celerybeat worker is enabled which is 246 | # responsible for triggering periodic tasks 247 | # must be turned on unless there's another server running celerybeat 248 | openwisp2_celerybeat: true 249 | # whether the dedicated worker for the celery "network" queue is enabled 250 | # must be turned on unless there's another server running a worker for this queue 251 | openwisp2_celery_network: true 252 | # concurrency option for the "network" queue (a worker is dedicated solely to network operations) 253 | # the default is 1. Setting it to "null" will make concurrency equal to number of CPUs if autoscaling is not used. 254 | openwisp2_celery_network_concurrency: null 255 | # alternative to the previous option, the celery autoscale option can be set if needed 256 | # for more info, consult the documentation of celery regarding "autoscaling" 257 | # by default it is set to "null" (no autoscaling) 258 | openwisp2_celery_network_autoscale: 8,4 259 | # prefetch multiplier for the "network" queue, 260 | # the default is 1, which mean no prefetching, 261 | # because the network tasks are long running and is better 262 | # to distribute the tasks to multiple processes 263 | openwisp2_celery_network_prefetch_multiplier: 1 264 | # celery queuing mode for the "network" queue, 265 | # fair mode is used in this case, which means 266 | # tasks will be equally distributed among workers 267 | openwisp2_celery_network_optimization: fair 268 | # whether the dedicated worker for the celery "firmware_upgrader" queue is enabled 269 | # must be turned on unless there's another server running a worker for this queue 270 | openwisp2_celery_firmware_upgrader: true 271 | # concurrency option for the "firmware_upgrader" queue (a worker is dedicated solely to firmware upgrade operations) 272 | # the default is 1. Setting it to "null" will make concurrency equal to number of CPUs if autoscaling is not used 273 | openwisp2_celery_firmware_upgrader_concurrency: null 274 | # alternative to the previous option, the celery autoscale option can be set if needed 275 | # for more info, consult the documentation of celery regarding "autoscaling" 276 | # by default it is set to "null" (no autoscaling) 277 | openwisp2_celery_firmware_upgrader_autoscale: 8,4 278 | # prefetch multiplier for the "firmware_upgrader" queue, 279 | # the default is 1, which mean no prefetching, 280 | # because the firmware upgrade tasks are long running and is better 281 | # to distribute the tasks to multiple processes 282 | openwisp2_celery_firmware_upgrader_prefetch_multiplier: 1 283 | # celery queuing mode for the "firmware_upgrader" queue, 284 | # fair mode is used in this case, which means 285 | # tasks will be equally distributed among workers 286 | openwisp2_celery_firmware_upgrader_optimization: fair 287 | # whether the dedicated worker for the celery "monitoring" queue is enabled 288 | # must be turned on unless there's another server running a worker for this queue 289 | openwisp2_celery_monitoring: true 290 | # concurrency option for the "monitoring" queue (a worker is dedicated solely to monitoring operations) 291 | # the default is 2. Setting it to "null" will make concurrency equal to number of CPUs 292 | # if autoscaling is not used. 293 | openwisp2_celery_monitoring_concurrency: null 294 | # alternative to the previous option, the celery autoscale option can be set if needed 295 | # for more info, consult the documentation of celery regarding "autoscaling" 296 | # by default it is set to "null" (no autoscaling) 297 | openwisp2_celery_monitoring_autoscale: 4,8 298 | # prefetch multiplier for the "monitoring" queue, 299 | # the default is 1, which mean no prefetching, 300 | # because the monitoring tasks can be long running and is better 301 | # to distribute the tasks to multiple processes 302 | openwisp2_celery_monitoring_prefetch_multiplier: 1 303 | # celery queuing mode for the "monitoring" queue, 304 | # fair mode is used in this case, which means 305 | # tasks will be equally distributed among workers 306 | openwisp2_celery_monitoring_optimization: fair 307 | # whether the default celery task routes should be written to the settings.py file 308 | # turn this off if you're defining custom task routing rules 309 | openwisp2_celery_task_routes_defaults: true 310 | # celery settings 311 | openwisp2_celery_broker_url: redis://{{ openwisp2_redis_host }}:{{ openwisp2_redis_port }}/3 312 | openwisp2_celery_task_acks_late: true 313 | # maximum number of retries by celery before giving up when broker is unreachable 314 | openwisp2_celery_broker_max_tries: 10 315 | # whether to activate the django logging configuration in celery 316 | # if set to true, will log all the celery events in the same log stream used by django 317 | # which will cause log lines to be written to "{{ openwisp2_path }}/log/openwisp2.log" 318 | # instead of "{{ openwisp2_path }}/log/celery.log" and "{{ openwisp2_path }}/log/celerybeat.log" 319 | openwisp2_django_celery_logging: false 320 | # postfix is installed by default, set to false if you don't need it 321 | openwisp2_postfix_install: true 322 | # allow overriding default `postfix_smtp_sasl_auth_enable` variable 323 | postfix_smtp_sasl_auth_enable_override: true 324 | # allow overriding postfix_smtpd_relay_restrictions 325 | postfix_smtpd_relay_restrictions_override: permit_mynetworks 326 | # allows overriding the default duration for keeping notifications 327 | openwisp2_notifications_delete_old_notifications: 10 328 | # Expiration time limit (in seconds) of magic sign-in links. 329 | # Magic sign-in links are used only when OpenWISP RADIUS is enabled. 330 | openwisp2_django_sesame_max_age: 1800 # 30 minutes 331 | # Maximum file size(in bytes) allowed to be uploaded as firmware image. 332 | # It overrides "openwisp2_nginx_client_max_body_size" setting 333 | # and updates nginx configuration accordingly. 334 | openwisp2_firmware_upgrader_max_file_size: 41943040 # 40MB 335 | # to add multi-language support 336 | openwisp2_internationalization: true 337 | openwisp2_users_auth_api: true 338 | # Allows setting OPENWISP_USERS_USER_PASSWORD_EXPIRATION setting. 339 | # Read https://github.com/openwisp/openwisp-users#openwisp_users_user_password_expiration 340 | openwisp2_users_user_password_expiration: 30 341 | # Allows setting OPENWISP_USERS_STAFF_USER_PASSWORD_EXPIRATION setting. 342 | # Read https://github.com/openwisp/openwisp-users#openwisp_users_staff_user_password_expiration 343 | openwisp2_users_staff_user_password_expiration: 30 344 | # used for SMS verification, the default is a dummy SMS backend 345 | # which prints to standard output and hence does nothing 346 | # one of the available providers from django-sendsms can be 347 | # used or alternatively, you can write a backend class for your 348 | # favorite SMS API gateway 349 | openwisp2_radius_sms_backend: "sendsms.backends.console.SmsBackend" 350 | openwisp2_radius_sms_token_max_ip_daily: 25 351 | openwisp2_radius_delete_old_radiusbatch_users: 365 352 | openwisp2_radius_cleanup_stale_radacct: 1 353 | openwisp2_radius_delete_old_postauth: 365 354 | # days for which the radius accounting sessions (radacct) are retained, 355 | # 0 means sessions are kept forever. 356 | # we highly suggest to set this number according 357 | # to the privacy regulation of your jurisdiction 358 | openwisp2_radius_delete_old_radacct: 365 359 | # days after which inactive users will flagged as unverified 360 | # Read https://openwisp.io/docs/stable/radius/user/settings.html#openwisp-radius-unverify-inactive-users 361 | openwisp2_radius_unverify_inactive_users: 540 362 | # days after which inactive users will be deleted 363 | # Read Read https://openwisp.io/docs/stable/radius/user/settings.html#openwisp-radius-delete-inactive-users 364 | openwisp2_radius_delete_inactive_users: 540 365 | openwisp2_radius_allowed_hosts: ["127.0.0.1"] 366 | # allow disabling celery beat tasks if needed 367 | openwisp2_monitoring_periodic_tasks: true 368 | openwisp2_radius_periodic_tasks: true 369 | openwisp2_usage_metric_collection_periodic_tasks: true 370 | # this role provides a default configuration of freeradius 371 | # if you manage freeradius on a different machine or you need different configurations 372 | # you can disable this default behavior 373 | openwisp2_freeradius_install: true 374 | # Set an account to expire T seconds after first login. 375 | # This variable sets the value of T. 376 | freeradius_expire_attr_after_seconds: 86400 377 | freeradius_dir: /etc/freeradius/3.0 378 | freeradius_mods_available_dir: "{{ freeradius_dir }}/mods-available" 379 | freeradius_mods_enabled_dir: "{{ freeradius_dir }}/mods-enabled" 380 | freeradius_sites_available_dir: "{{ freeradius_dir }}/sites-available" 381 | freeradius_sites_enabled_dir: "{{ freeradius_dir }}/sites-enabled" 382 | freeradius_rest: 383 | url: "https://{{ inventory_hostname }}/api/v1/freeradius" 384 | freeradius_safe_characters: "+@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" 385 | # Sets the source path of the template that contains freeradius site configuration. 386 | # Defaults to "templates/freeradius/openwisp_site.j2" shipped in the role. 387 | freeradius_openwisp_site_template_src: custom_freeradius_site.j2 388 | # Whether to deploy the default openwisp_site for FreeRADIUS. 389 | # Defaults to true. 390 | freeradius_deploy_openwisp_site: false 391 | # FreeRADIUS listen address for the openwisp_site. 392 | # Defaults to "*", i.e. listen on all interfaces. 393 | freeradius_openwisp_site_listen_ipaddr: "10.8.0.1" 394 | # A list of dict that includes organization's name, UUID, RADIUS token, 395 | # TLS configuration, and ports for authentication, accounting, and inner tunnel. 396 | # This list of dict is used to generate FreeRADIUS sites that support 397 | # WPA Enterprise (EAP-TTLS-PAP) authentication. 398 | # Defaults to an empty list. 399 | freeradius_eap_orgs: 400 | # The name should not contain spaces or special characters 401 | - name: openwisp 402 | # UUID of the organization can be retrieved from the OpenWISP admin 403 | uuid: 00000000-0000-0000-0000-000000000000 404 | # Radius token of the organization can be retrieved from the OpenWISP admin 405 | radius_token: secret-radius-token 406 | # Port used by the authentication service for this FreeRADIUS site 407 | auth_port: 1832 408 | # Port used by the accounting service for this FreeRADIUS site 409 | acct_port: 1833 410 | # Port used by the authentication service of inner tunnel for this FreeRADIUS site 411 | inner_tunnel_auth_port: 18330 412 | # CA certificate for the FreeRADIUS site 413 | ca: /etc/freeradius/certs/ca.crt 414 | # TLS certificate for the FreeRADIUS site 415 | cert: /etc/freeradius/certs/cert.pem 416 | # TLS private key for the FreeRADIUS site 417 | private_key: /etc/freeradius/certs/key.pem 418 | # Diffie-Hellman key for the FreeRADIUS site 419 | dh: /etc/freeradius/certs/dh 420 | # Extra instructions for the "tls-config" section of the EAP module 421 | # for the FreeRADIUS site 422 | tls_config_extra: | 423 | private_key_password = whatever 424 | ecdh_curve = "prime256v1" 425 | # Sets the source path of the template that contains freeradius site configuration 426 | # for WPA Enterprise (EAP-TTLS-PAP) authentication. 427 | # Defaults to "templates/freeradius/eap/openwisp_site.j2" shipped in the role. 428 | freeradius_eap_openwisp_site_template_src: custom_eap_openwisp_site.j2 429 | # Sets the source path of the template that contains freeradius inner tunnel 430 | # configuration for WPA Enterprise (EAP-TTLS-PAP) authentication. 431 | # Defaults to "templates/freeradius/eap/inner_tunnel.j2" shipped in the role. 432 | freeradius_eap_inner_tunnel_template_src: custom_eap_inner_tunnel.j2 433 | # Sets the source path of the template that contains freeradius EAP configuration 434 | # for WPA Enterprise (EAP-TTLS-PAP) authentication. 435 | # Defaults to "templates/freeradius/eap/eap.j2" shipped in the role. 436 | freeradius_eap_template_src: custom_eap.j2 437 | cron_delete_old_notifications: "'hour': 0, 'minute': 0" 438 | cron_deactivate_expired_users: "'hour': 0, 'minute': 5" 439 | cron_delete_old_radiusbatch_users: "'hour': 0, 'minute': 10" 440 | cron_cleanup_stale_radacct: "'hour': 0, 'minute': 20" 441 | cron_delete_old_postauth: "'hour': 0, 'minute': 30" 442 | cron_delete_old_radacct: "'hour': 1, 'minute': 30" 443 | cron_password_expiration_email: "'hour': 1, 'minute': 0" 444 | cron_unverify_inactive_users: "'hour': 1, 'minute': 45" 445 | cron_delete_inactive_users: "'hour': 1, 'minute': 55" 446 | # cross-origin resource sharing (CORS) settings 447 | # https://pypi.org/project/django-cors-headers/ 448 | openwisp2_django_cors: 449 | # Setting this to "true" will install the django-cors-headers package 450 | # and configure the Django middleware setting to support CORS. 451 | # By default, it is set to false. 452 | enabled: true 453 | # Configures "CORS_ALLOWED_ORIGINS" setting of the django-cors-headers 454 | # package. A list of origins that are authorized to make cross-site 455 | # HTTP requests. Read https://github.com/adamchainz/django-cors-headers#cors_allowed_origins-sequencestr 456 | # for detail. By default, it is set to an empty list. 457 | allowed_origins_list: ["https://log.openwisp.org"] 458 | 459 | .. note:: 460 | 461 | The default settings for controlling the number of processes and 462 | threads in uWSGI and Daphne are set conservatively. Users are 463 | encouraged to adjust these settings to match the scale of their 464 | project. The same applies to the concurrency and auto-scaling settings 465 | for Celery workers. 466 | -------------------------------------------------------------------------------- /docs/user/system-requirements.rst: -------------------------------------------------------------------------------- 1 | System Requirements 2 | =================== 3 | 4 | The following specifications will run a new, *empty* instance of OpenWISP. 5 | Please ensure you account for the amount of disk space your use case will 6 | require, e.g. allocate enough space for users to upload floor plan images. 7 | 8 | Hardware Requirements (Recommended) 9 | ----------------------------------- 10 | 11 | - 2 CPUs 12 | - 2 GB Memory 13 | - Disk space - depends on the projected size of your database and uploaded 14 | photo images 15 | 16 | Keep in mind that increasing the number of celery workers will require 17 | more memory and CPU. You will need to increase the amount of celery 18 | workers as the number of devices you manage grows. 19 | 20 | For more information about how to increase concurrency, look for the 21 | variables which end with ``_concurrency`` or ``_autoscale`` in the 22 | :doc:`role-variables` section. 23 | 24 | Software 25 | -------- 26 | 27 | A fresh installation of one of the supported operating systems is 28 | generally sufficient, with no preconfiguration required. The Ansible 29 | Playbook will handle the installation and configuration of all 30 | dependencies, providing you with a fully operational OpenWISP setup. 31 | 32 | .. important:: 33 | 34 | Ensure the hostname of your target machine matches what is in your 35 | Ansible configuration file. Also, please ensure that Ansible can 36 | access your target machine by SSH, be it either with a key or 37 | password. For more information see the `Ansible Getting Started 38 | Documentation 39 | `__. 40 | 41 | Supported Operating Systems 42 | --------------------------- 43 | 44 | - Debian 12 45 | - Debian 11 46 | - Ubuntu 24 LTS 47 | - Ubuntu 22 LTS 48 | -------------------------------------------------------------------------------- /docs/user/troubleshooting.rst: -------------------------------------------------------------------------------- 1 | Troubleshooting 2 | =============== 3 | 4 | OpenWISP is deployed using **uWSGI** and also uses **daphne** for 5 | WebSockets and **celery** as a task queue. 6 | 7 | All these services are run by **supervisor**. 8 | 9 | .. code-block:: shell 10 | 11 | sudo service supervisor start|stop|status 12 | 13 | You can view each individual process run by supervisor with the following 14 | command: 15 | 16 | .. code-block:: shell 17 | 18 | sudo supervisorctl status 19 | 20 | For more information about Supervisord, refer to `Running supervisorctl 21 | `__. 22 | 23 | The **nginx** web server sits in front of the **uWSGI** application 24 | server. You can control nginx with the following commands: 25 | 26 | .. code-block:: shell 27 | 28 | service nginx status start|stop|status 29 | 30 | OpenWISP is installed in ``/opt/openwisp2`` (unless you changed the 31 | ``openwisp2_path`` variable in the Ansible playbook configuration). These 32 | are some useful directories to check when experiencing issues. 33 | 34 | ========================= ============================== 35 | Location Description 36 | ========================= ============================== 37 | /opt/openwisp2 The OpenWISP 2 root directory. 38 | /opt/openwisp2/log Log files 39 | /opt/openwisp2/env Python virtual environment 40 | /opt/openwisp2/db.sqlite3 OpenWISP 2 SQLite database 41 | ========================= ============================== 42 | 43 | All processes are running as the ``www-data`` user. 44 | 45 | If you need to copy or edit files, you can switch to the ``www-data`` user 46 | with the following commands: 47 | 48 | .. code-block:: shell 49 | 50 | sudo su www-data -s /bin/bash 51 | cd /opt/openwisp2 52 | source env/bin/activate 53 | 54 | SSL Certificate Gotchas 55 | ----------------------- 56 | 57 | When you access the admin website, you will receive an SSL certificate 58 | warning because the playbook creates a self-signed (untrusted) SSL 59 | certificate. You can get rid of the warning by installing your own trusted 60 | certificate and setting the ``openwisp2_ssl_cert`` and 61 | ``openwisp2_ssl_key`` variables accordingly or by following the 62 | instructions explained in the section :doc:`certbot-ssl`. 63 | 64 | If you keep the untrusted certificate, you will also need to disable SSL 65 | verification on devices using :doc:`openwisp-config 66 | ` by setting ``verify_ssl`` to ``0``, 67 | although we advise against using this kind of setup in a production 68 | environment. 69 | -------------------------------------------------------------------------------- /files/generate_django_secret_key.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Pseudo-random django secret key generator""" 3 | from __future__ import print_function 4 | 5 | import random 6 | 7 | chars = ( 8 | 'abcdefghijklmnopqrstuvwxyz' 9 | 'ABCDEFGHIJKLMNOPQRSTUVXYZ' 10 | '0123456789' 11 | '#()^[]-_*%&=+/' 12 | ) 13 | 14 | SECRET_KEY = ''.join([random.SystemRandom().choice(chars) for i in range(50)]) 15 | 16 | print(SECRET_KEY) 17 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Update supervisor configuration 4 | command: supervisorctl update all 5 | register: supervisord_update_all 6 | 7 | - name: Reload application 8 | debug: 9 | msg: "Restarting application services" 10 | changed_when: true 11 | notify: 12 | - Reload uwsgi 13 | - Restart daphne 14 | - Restart other supervisor managed services 15 | - Remove celerybeat schedule 16 | - Migrate timeseries database 17 | 18 | - name: Reload uwsgi 19 | command: "{{ virtualenv_path }}/bin/uwsgi --reload {{ openwisp2_path }}/uwsgi.pid" 20 | # Skips reloading uwsgi if it's supervisor configuration has changed. 21 | # Otherwise, the process will be restarted twice. 22 | when: not (supervisord_update_all is defined and "openwisp2:" in supervisord_update_all.stdout) 23 | 24 | # Daphne does not support graceful reloading. 25 | # Read https://github.com/django/daphne/issues/177 26 | - name: Restart daphne 27 | command: "supervisorctl restart daphne:asgi0" 28 | # Skips restarting daphne if it's supervisor configuration has changed. 29 | # Otherwise, the process will be restarted twice. 30 | when: not (supervisord_update_all is defined and "daphne:" in supervisord_update_all.stdout) 31 | 32 | - name: Restart other supervisor managed services 33 | command: "supervisorctl restart {{ item.name }}" 34 | # Skips restarting service if it's supervisor configuration has changed. 35 | # Otherwise, the process will be restarted twice. 36 | when: "item.when and not (supervisord_update_all is defined and item.name ~ ':' in supervisord_update_all.stdout)" 37 | loop: "{{ openwisp2_default_supervisor_restart + openwisp2_extra_supervisor_restart }}" 38 | loop_control: 39 | loop_var: item 40 | 41 | # Clean up the schedule file because it may become corrupted after updates 42 | - name: Remove celerybeat schedule 43 | file: 44 | path: "{{ openwisp2_path }}/celerybeat-schedule.db" 45 | state: absent 46 | 47 | # NOTE: Related to https://github.com/openwisp/openwisp-monitoring/pull/368 48 | # TODO: Remove this when openwisp-monitoring 1.2.0 is released 49 | - name: Migrate timeseries database 50 | become: true 51 | become_user: "{{ www_user }}" 52 | django_manage: 53 | app_path: "{{ openwisp2_path }}" 54 | command: migrate_timeseries 55 | virtualenv: "{{ virtualenv_path }}" 56 | when: openwisp2_monitoring 57 | 58 | - name: Restart nginx 59 | service: 60 | name: nginx 61 | state: restarted 62 | when: openwisp2_nginx_install 63 | 64 | - name: Start redis 65 | when: openwisp2_redis_install 66 | service: 67 | name: redis 68 | state: started 69 | 70 | - name: Update-ca-certificates 71 | command: /usr/sbin/update-ca-certificates 72 | 73 | - name: Restart freeradius 74 | service: 75 | name: freeradius 76 | state: started 77 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | dependencies: 4 | - src: https://github.com/openwisp/Stouts.postfix 5 | version: origin/main 6 | name: Stouts.postfix 7 | when: openwisp2_postfix_install 8 | postfix_smtp_sasl_auth_enable: "{{ postfix_smtp_sasl_auth_enable_override | default(false) }}" 9 | postfix_smtpd_relay_restrictions: "{{ postfix_smtpd_relay_restrictions_override }}" 10 | 11 | - src: https://github.com/openwisp/ansible-ow-influxdb 12 | version: origin/master 13 | name: openwisp.influxdb 14 | when: openwisp2_monitoring and openwisp2_influxdb_install 15 | 16 | galaxy_info: 17 | role_name: openwisp2 18 | namespace: openwisp 19 | author: Federico Capoano 20 | company: OpenWISP 21 | description: Official role to install and upgrade openwisp2 controller 22 | license: BSD 23 | min_ansible_version: "2.13" 24 | issue_tracker_url: https://github.com/openwisp/ansible-openwisp2/issues 25 | platforms: 26 | - name: Debian 27 | versions: 28 | - bookworm 29 | - bullseye 30 | - name: Ubuntu 31 | versions: 32 | - noble 33 | - jammy 34 | galaxy_tags: 35 | - system 36 | - networking 37 | - openwisp 38 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | dependency: 4 | name: galaxy 5 | options: 6 | role-file: molecule/resources/requirements.yml 7 | driver: 8 | name: docker 9 | lint: | 10 | set -e 11 | yamllint . || true 12 | ansible-lint || true 13 | platforms: 14 | - name: "${ROLE_NAME:-instance}-${MOLECULE_DISTRO}" 15 | image: "geerlingguy/docker-${MOLECULE_DISTRO}-ansible:${tag:-latest}" 16 | command: ${MOLECULE_DOCKER_COMMAND:-""} 17 | volumes: 18 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 19 | cgroupns_mode: host 20 | privileged: true 21 | pre_build_image: true 22 | provisioner: 23 | name: ansible 24 | env: 25 | MOLECULE_NO_LOG: false 26 | playbooks: 27 | converge: ../resources/${MOLECULE_PLAYBOOK:-converge.yml} 28 | verify: ../resources/verify.yml 29 | config_options: 30 | defaults: 31 | stdout_callback: yaml 32 | bin_ansible_callbacks: true 33 | inventory: 34 | host_vars: 35 | openwisp2-debian11: 36 | openwisp2_django_version: "django~=4.2.0" 37 | 38 | verifier: 39 | name: ansible 40 | -------------------------------------------------------------------------------- /molecule/local/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | dependency: 4 | name: galaxy 5 | options: 6 | role-file: molecule/resources/requirements.yml 7 | driver: 8 | name: docker 9 | lint: | 10 | set -e 11 | yamllint . || true 12 | ansible-lint || true 13 | platforms: 14 | - name: "openwisp2-ubuntu2404" 15 | image: "geerlingguy/docker-ubuntu2404-ansible:latest" 16 | command: ${MOLECULE_DOCKER_COMMAND:-""} 17 | volumes: 18 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 19 | cgroupns_mode: host 20 | privileged: true 21 | pre_build_image: true 22 | - name: "openwisp2-ubuntu2204" 23 | image: "geerlingguy/docker-ubuntu2204-ansible:latest" 24 | command: ${MOLECULE_DOCKER_COMMAND:-""} 25 | volumes: 26 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 27 | cgroupns_mode: host 28 | privileged: true 29 | pre_build_image: true 30 | - name: "openwisp2-debian11" 31 | image: "geerlingguy/docker-debian11-ansible:latest" 32 | command: ${MOLECULE_DOCKER_COMMAND:-""} 33 | volumes: 34 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 35 | cgroupns_mode: host 36 | privileged: true 37 | pre_build_image: true 38 | - name: "openwisp2-debian12" 39 | image: "geerlingguy/docker-debian12-ansible:latest" 40 | command: ${MOLECULE_DOCKER_COMMAND:-""} 41 | volumes: 42 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 43 | cgroupns_mode: host 44 | privileged: true 45 | pre_build_image: true 46 | provisioner: 47 | name: ansible 48 | env: 49 | MOLECULE_NO_LOG: false 50 | playbooks: 51 | converge: ../resources/${MOLECULE_PLAYBOOK:-converge.yml} 52 | verify: ../resources/verify.yml 53 | config_options: 54 | defaults: 55 | stdout_callback: yaml 56 | bin_ansible_callbacks: true 57 | verifier: 58 | name: ansible 59 | -------------------------------------------------------------------------------- /molecule/resources/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Converge 4 | hosts: all 5 | become: true 6 | 7 | vars: 8 | openwisp2_network_topology: true 9 | openwisp2_firmware_upgrader: true 10 | openwisp2_radius: true 11 | openwisp2_controller_subnet_division: true 12 | openwisp2_uwsgi_extra_conf: | 13 | single-interpreter=True 14 | openwisp2_usage_metric_collection: false 15 | freeradius_eap_orgs: 16 | - name: openwisp 17 | uuid: 00000000-0000-0000-0000-000000000000 18 | radius_token: secret-radius-token 19 | auth_port: 1822 20 | acct_port: 1823 21 | inner_tunnel_auth_port: 18230 22 | 23 | pre_tasks: 24 | - name: Update apt cache 25 | apt: 26 | update_cache: true 27 | cache_valid_time: 600 28 | when: ansible_os_family == 'Debian' 29 | 30 | - name: Install net-tools 31 | apt: 32 | name: 33 | - net-tools 34 | 35 | - name: Remove the .dockerenv file 36 | file: 37 | path: /.dockerenv 38 | state: absent 39 | 40 | roles: 41 | - role: openwisp.openwisp2 42 | -------------------------------------------------------------------------------- /molecule/resources/requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - src: https://github.com/Stouts/Stouts.postfix 4 | version: origin/develop 5 | name: Stouts.postfix 6 | 7 | - src: https://github.com/openwisp/ansible-ow-influxdb 8 | version: origin/master 9 | name: openwisp.influxdb 10 | -------------------------------------------------------------------------------- /molecule/resources/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Verify 4 | hosts: all 5 | become: true 6 | gather_facts: false 7 | 8 | tasks: 9 | - name: Run a specific subset of tests 10 | command: > 11 | /opt/openwisp2/env/bin/python /opt/openwisp2/manage.py test --keepdb --exclude skip_prod \ 12 | openwisp_controller.pki.tests.test_admin \ 13 | openwisp_controller.pki.tests.test_models \ 14 | openwisp_controller.config.tests.test_tag \ 15 | openwisp_controller.config.tests.test_vpn \ 16 | openwisp_controller.config.tests.test_admin.TestAdmin.test_preview_device \ 17 | openwisp_controller.config.tests.test_admin.TestAdmin.test_device_preview_button \ 18 | openwisp_controller.config.tests.test_admin.TestAdmin.test_template_preview_button \ 19 | openwisp_controller.config.tests.test_admin.TestAdmin.test_vpn_preview_button \ 20 | openwisp_controller.config.tests.test_admin.TestAdmin.test_device_queryset \ 21 | openwisp_controller.config.tests.test_admin.TestAdmin.test_device_organization_fk_autocomplete_view \ 22 | openwisp_controller.config.tests.test_admin.TestAdmin.test_device_templates_m2m_queryset \ 23 | openwisp_controller.config.tests.test_admin.TestAdmin.test_template_queryset \ 24 | openwisp_controller.config.tests.test_admin.TestAdmin.test_template_organization_fk_autocomplete_view \ 25 | openwisp_controller.config.tests.test_admin.TestAdmin.test_template_vpn_fk_autocomplete_view \ 26 | openwisp_controller.config.tests.test_admin.TestAdmin.test_vpn_queryset \ 27 | openwisp_controller.config.tests.test_admin.TestAdmin.test_vpn_organization_fk_autocomplete_view \ 28 | openwisp_controller.config.tests.test_admin.TestAdmin.test_vpn_ca_fk_queryset \ 29 | openwisp_controller.config.tests.test_admin.TestAdmin.test_vpn_cert_fk_queryset \ 30 | openwisp_controller.config.tests.test_admin.TestAdmin.test_changelist_recover_deleted_button \ 31 | openwisp_controller.config.tests.test_admin.TestAdmin.test_recoverlist_operator_403 \ 32 | openwisp_controller.config.tests.test_admin.TestAdmin.test_device_template_filter \ 33 | openwisp_controller.config.tests.test_admin.TestAdmin.test_device_contains_default_templates_js \ 34 | openwisp_controller.config.tests.test_admin.TestAdmin.test_template_not_contains_default_templates_js \ 35 | openwisp_controller.config.tests.test_admin.TestAdmin.test_vpn_not_contains_default_templates_js \ 36 | openwisp_controller.config.tests.test_notifications \ 37 | openwisp_controller.subnet_division.tests.test_models \ 38 | openwisp_notifications.tests.test_admin.TestAdmin 39 | changed_when: false 40 | 41 | - name: Check if redis-server is running 42 | command: systemctl status redis-server 43 | changed_when: false 44 | 45 | - name: Chcke if redis is running 46 | command: systemctl status redis 47 | changed_when: false 48 | 49 | - name: Check Openwisp 50 | block: 51 | - name: Check if OpenWISP is running 52 | uri: 53 | url: "https://{{ inventory_hostname }}/admin/login/?next=/admin/" 54 | validate_certs: false 55 | rescue: 56 | - name: Get OpenWisp log 57 | command: "tail -n 500 {{ openwisp2_path }}/log/*.log" 58 | register: openwisp_log 59 | 60 | - name: Show OpenWisp log 61 | debug: 62 | var: openwisp_log 63 | 64 | - name: Check if FreeRADIUS is listening on WPA Enterprise site ports 65 | shell: "netstat -tuln | grep -Eq '1822|1823|18230'" 66 | register: freeradius_eap_ports # Register the output and return code 67 | failed_when: freeradius_eap_ports.rc 68 | changed_when: false 69 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.docstrfmt] 2 | extend_exclude = ["**/*.py"] 3 | -------------------------------------------------------------------------------- /run-qa-checks: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | openwisp-qa-check --skip-isort --skip-flake8 --skip-checkmigrations --skip-black 5 | -------------------------------------------------------------------------------- /tasks/apt.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Update APT package cache 4 | apt: 5 | update_cache: true 6 | changed_when: false 7 | retries: 5 8 | delay: 10 9 | register: result 10 | until: result is success 11 | tags: 12 | - molecule-idempotence-notest 13 | 14 | - name: Install system packages 15 | apt: 16 | name: 17 | - sudo 18 | - software-properties-common 19 | - build-essential 20 | - supervisor 21 | - openssl 22 | - libssl-dev 23 | - libffi-dev 24 | - python3 25 | - virtualenv 26 | - cron 27 | - curl 28 | # needed to generate SSH key for push updates 29 | - openssh-client 30 | - logrotate 31 | # required for celerybeat 32 | - python3-gdbm 33 | ignore_errors: true 34 | retries: 5 35 | delay: 10 36 | register: result 37 | until: result is success 38 | 39 | - name: Install nginx 40 | apt: 41 | name: nginx 42 | when: openwisp2_nginx_install 43 | 44 | - name: Install redis 45 | apt: 46 | name: redis-server 47 | notify: 48 | - Start redis 49 | when: openwisp2_redis_install 50 | 51 | # On the newer versions of redis, by default redis 52 | # binds to localhost on ipv6 address which wouldn't 53 | # let the service start if the server doesn't have 54 | # ipv6 enabled. Hence, we set redis to listen on ipv4 55 | - name: Set redis to listen on ipv4 56 | notify: Start redis 57 | when: openwisp2_redis_install 58 | lineinfile: 59 | path: /etc/redis/redis.conf 60 | regexp: '^bind 127\.0\.0\.1 ::1' 61 | line: 'bind 127.0.0.1' 62 | backrefs: true 63 | ignore_errors: true 64 | 65 | - name: Start redis 66 | when: openwisp2_redis_install 67 | service: 68 | name: redis 69 | state: started 70 | 71 | - name: Install geographic libraries 72 | apt: 73 | name: 74 | - gdal-bin 75 | - libproj-dev 76 | - libgeos-dev 77 | retries: 5 78 | delay: 10 79 | register: result 80 | until: result is success 81 | 82 | - name: Install spatialite 83 | when: '"spatialite" in openwisp2_database.engine' 84 | apt: 85 | name: 86 | - sqlite3 87 | - libspatialite-dev 88 | retries: 5 89 | delay: 10 90 | register: result 91 | until: result is success 92 | 93 | - name: Install postgresql client drivers 94 | when: openwisp2_database.engine == "django.contrib.gis.db.backends.postgis" 95 | apt: 96 | name: libpq-dev 97 | retries: 5 98 | delay: 10 99 | register: result 100 | until: result is success 101 | 102 | - name: Install cairo 103 | when: openwisp2_radius 104 | apt: 105 | name: 106 | - libcairo2 107 | - libpango-1.0-0 108 | - libpangocairo-1.0-0 109 | - libgdk-pixbuf2.0-0 110 | - shared-mime-info 111 | tags: [openwisp2, radius] 112 | 113 | - name: Install mod-spatialite (may fail on older linux distros) 114 | when: '"spatialite" in openwisp2_database.engine' 115 | apt: name=libsqlite3-mod-spatialite 116 | ignore_errors: true 117 | retries: 5 118 | delay: 10 119 | register: result 120 | until: result is success 121 | 122 | # fixes issue described in https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user 123 | - name: Install acl if acting as non-root user 124 | apt: name=acl 125 | when: ansible_user is not defined or ansible_user != 'root' 126 | retries: 5 127 | delay: 10 128 | register: result 129 | until: result is success 130 | ignore_errors: true 131 | 132 | - name: Ensure supervisor is started 133 | service: name=supervisor state=started 134 | 135 | - name: Install python3 packages 136 | apt: 137 | name: 138 | - python3-pip 139 | - python3-dev 140 | - python3-virtualenv 141 | retries: 5 142 | delay: 10 143 | register: result 144 | until: result is success 145 | 146 | - name: Install ntp 147 | when: openwisp2_install_ntp 148 | apt: name=ntp 149 | retries: 5 150 | delay: 10 151 | register: result 152 | until: result is success 153 | 154 | - name: Install gettext 155 | apt: 156 | name: gettext 157 | when: openwisp2_internationalization 158 | retries: 5 159 | delay: 10 160 | register: result 161 | until: result is success 162 | 163 | - name: Install fping 164 | when: openwisp2_monitoring 165 | apt: 166 | name: 167 | - fping 168 | retries: 5 169 | delay: 10 170 | register: result 171 | until: result is success 172 | -------------------------------------------------------------------------------- /tasks/complete.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Change superuser password hint 4 | when: '"created" in load_initial_data_result.stdout' 5 | debug: 6 | msg: "Change your admin password at https://{{ inventory_hostname }}/admin/password_change/" 7 | tags: 8 | - molecule-idempotence-notest 9 | 10 | - name: Update site object hint 11 | when: '"created" in load_initial_data_result.stdout' 12 | debug: 13 | msg: "Update the default site object at https://{{ inventory_hostname }}/admin/sites/site/1/change/" 14 | tags: 15 | - molecule-idempotence-notest 16 | -------------------------------------------------------------------------------- /tasks/consent.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Show usage metric collection disclaimer 4 | debug: 5 | msg: | 6 | By continuing with the ongoing installation process, 7 | you are giving your consent for the collection of usage metrics 8 | mentioned at https://openwisp.io/docs/user/usage-metric-collection.html 9 | 10 | If you prefer not to participate, you can opt-out by setting 11 | "openwisp2_usage_metric_collection: false" in your playbook. 12 | However, before making that decision, we encourage you to explore how this 13 | information contributes to the enhancement of OpenWISP at the following link: 14 | https://openwisp.io/docs/user/usage-metric-collection.html 15 | 16 | Should you wish to support us through data collection but prefer to disable 17 | this disclaimer, you can do so by setting 18 | "openwisp2_usage_metric_collection: true" in your playbook. 19 | when: "openwisp2_usage_metric_collection == None" 20 | 21 | - name: Ensure user has enough time to read 22 | pause: 23 | seconds: 13 24 | when: "openwisp2_usage_metric_collection == None" 25 | -------------------------------------------------------------------------------- /tasks/cron.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Install topology update cron 4 | when: openwisp2_network_topology 5 | become: true 6 | cron: 7 | name: "Update toplogies" 8 | day: "{{ openwisp2_topology_update_frequency.day }}" 9 | hour: "{{ openwisp2_topology_update_frequency.hour }}" 10 | minute: "{{ openwisp2_topology_update_frequency.minute }}" 11 | job: "{{ virtualenv_path }}/bin/python {{ openwisp2_path }}/manage.py update_topology" 12 | 13 | - name: Install topology snapshot save cron 14 | when: openwisp2_network_topology 15 | become: true 16 | cron: 17 | name: "Save snapshots of topologies" 18 | day: "{{ openwisp2_topology_save_snapshot_frequency.day }}" 19 | hour: "{{ openwisp2_topology_save_snapshot_frequency.hour }}" 20 | minute: "{{ openwisp2_topology_save_snapshot_frequency.minute }}" 21 | job: "{{ virtualenv_path }}/bin/python {{ openwisp2_path }}/manage.py save_snapshot" 22 | 23 | - name: Install clearsessions cronjob 24 | become: true 25 | cron: 26 | name: "clearsessions cronjob" 27 | day: "*" 28 | hour: "04" 29 | minute: "30" 30 | job: "{{ virtualenv_path }}/bin/python {{ openwisp2_path }}/manage.py clearsessions" 31 | -------------------------------------------------------------------------------- /tasks/django.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Create {{ openwisp2_path }} 4 | file: 5 | path: "{{ openwisp2_path }}" 6 | state: directory 7 | group: "{{ www_group }}" 8 | mode: 0770 9 | 10 | - name: Create "{{ openwisp2_path }}/openwisp2" 11 | file: 12 | path: "{{ openwisp2_path }}/openwisp2" 13 | state: directory 14 | group: "{{ www_group }}" 15 | mode: 0770 16 | 17 | - name: Create "{{ openwisp2_path }}/log" 18 | file: 19 | path: "{{ openwisp2_path }}/log" 20 | state: directory 21 | group: "{{ www_group }}" 22 | mode: 0770 23 | recurse: true 24 | tags: 25 | - molecule-idempotence-notest 26 | 27 | - name: Create "{{ openwisp2_path }}/static_custom" 28 | file: 29 | path: "{{ openwisp2_path }}/static_custom" 30 | state: directory 31 | group: "{{ www_group }}" 32 | mode: 0770 33 | tags: 34 | - openwisp2_theme 35 | 36 | - name: Template manage.py 37 | notify: Reload application 38 | template: 39 | src: manage.py 40 | dest: "{{ openwisp2_path }}/manage.py" 41 | group: "{{ www_group }}" 42 | mode: 0754 43 | 44 | - name: Template celery.py 45 | notify: Reload application 46 | template: 47 | src: openwisp2/celery.py 48 | dest: "{{ openwisp2_path }}/openwisp2/celery.py" 49 | group: "{{ www_group }}" 50 | mode: 0640 51 | tags: [django_init] 52 | 53 | - name: Template version.py 54 | notify: Reload application 55 | template: 56 | src: openwisp2/version.py 57 | dest: "{{ openwisp2_path }}/openwisp2/version.py" 58 | group: "{{ www_group }}" 59 | mode: 0640 60 | tags: [django_init] 61 | 62 | - name: Template __init__.py 63 | notify: Reload application 64 | template: 65 | src: openwisp2/__init__.py 66 | dest: "{{ openwisp2_path }}/openwisp2/__init__.py" 67 | group: "{{ www_group }}" 68 | mode: 0640 69 | tags: [django_init] 70 | 71 | - name: Template urls.py 72 | notify: Reload application 73 | template: 74 | src: openwisp2/urls.py 75 | dest: "{{ openwisp2_path }}/openwisp2/urls.py" 76 | group: "{{ www_group }}" 77 | mode: 0640 78 | 79 | - name: Template wsgi.py 80 | notify: Reload application 81 | template: 82 | src: openwisp2/wsgi.py 83 | dest: "{{ openwisp2_path }}/openwisp2/wsgi.py" 84 | group: "{{ www_group }}" 85 | mode: 0640 86 | 87 | - name: Template asgi.py 88 | notify: Reload application 89 | template: 90 | src: openwisp2/asgi.py 91 | dest: "{{ openwisp2_path }}/openwisp2/asgi.py" 92 | group: "{{ www_group }}" 93 | mode: 0640 94 | 95 | # set openwisp2_secret_key if not defined explicitly 96 | - import_tasks: django_secret_key.yml 97 | when: openwisp2_secret_key is not defined 98 | tags: [django_settings] 99 | 100 | - name: Template settings.py 101 | notify: Reload application 102 | template: 103 | src: openwisp2/settings.py 104 | dest: "{{ openwisp2_path }}/openwisp2/settings.py" 105 | group: "{{ www_group }}" 106 | mode: 0640 107 | tags: [django_settings] 108 | 109 | - name: Template routing.py 110 | notify: Reload application 111 | template: 112 | src: openwisp2/routing.py 113 | dest: "{{ openwisp2_path }}/openwisp2/routing.py" 114 | group: "{{ www_group }}" 115 | mode: 0640 116 | 117 | - name: Start redis for migration 118 | when: openwisp2_redis_install 119 | service: 120 | name: redis 121 | state: started 122 | 123 | - name: Run geocoding check 124 | become: true 125 | become_user: "{{ www_user }}" 126 | when: openwisp2_geocoding_check 127 | django_manage: 128 | app_path: "{{ openwisp2_path }}" 129 | command: "check --deploy --tag geocoding" 130 | virtualenv: "{{ virtualenv_path }}" 131 | 132 | - name: Migrate 133 | notify: Reload application 134 | become: true 135 | become_user: "{{ www_user }}" 136 | django_manage: 137 | app_path: "{{ openwisp2_path }}" 138 | command: migrate 139 | virtualenv: "{{ virtualenv_path }}" 140 | tags: [django_settings] 141 | 142 | - name: Set permissions to sqlite db 143 | when: openwisp2_database.engine == "django.db.backends.sqlite3" 144 | file: 145 | path: "{{ openwisp2_database.name }}" 146 | state: file 147 | group: "{{ www_group }}" 148 | mode: 0770 149 | 150 | - name: Set permissions to "{{ openwisp2_path }}/static" 151 | file: 152 | path: "{{ openwisp2_path }}/static" 153 | state: directory 154 | group: "{{ www_group }}" 155 | owner: "{{ www_user }}" 156 | # sets executable only to directories 157 | mode: u=rwX,g=rX,o=rX 158 | recurse: true 159 | tags: 160 | - molecule-idempotence-notest 161 | - openwisp2_theme 162 | 163 | - name: Copy static files 164 | become: true 165 | copy: 166 | src: ow2_static/ 167 | dest: "{{ openwisp2_path }}/static_custom" 168 | owner: "{{ www_user }}" 169 | group: "{{ www_group }}" 170 | mode: 0640 171 | failed_when: false 172 | tags: 173 | - openwisp2_theme 174 | 175 | - name: Collectstatic 176 | notify: Reload application 177 | become: true 178 | become_user: "{{ www_user }}" 179 | django_manage: 180 | app_path: "{{ openwisp2_path }}" 181 | command: "collectstatic --noinput" 182 | virtualenv: "{{ virtualenv_path }}" 183 | register: collectstatic_output 184 | changed_when: '"\n0 static files" not in collectstatic_output.out' 185 | tags: 186 | - molecule-idempotence-notest 187 | - openwisp2_theme 188 | 189 | # needed to run compilemessages 190 | # some .mo files are created in virtualenv_path 191 | - name: Set permissions to "{{ virtualenv_path }}" 192 | when: openwisp2_internationalization 193 | file: 194 | path: "{{ virtualenv_path }}" 195 | state: directory 196 | group: "{{ www_group }}" 197 | owner: "{{ www_user }}" 198 | # sets executable only to directories 199 | mode: u=rwX,g=rX,o=rX 200 | recurse: true 201 | tags: 202 | - molecule-idempotence-notest 203 | 204 | - name: Compilemessages 205 | notify: Reload application 206 | become: true 207 | become_user: "{{ www_user }}" 208 | when: openwisp2_internationalization 209 | django_manage: 210 | app_path: "{{ openwisp2_path }}" 211 | command: "compilemessages" 212 | virtualenv: "{{ virtualenv_path }}" 213 | 214 | - name: Create load_initial_data.py script 215 | template: 216 | src: load_initial_data.py 217 | dest: "{{ openwisp2_path }}/load_initial_data.py" 218 | mode: 0754 219 | 220 | - name: Load initial data 221 | environment: 222 | PRIVATE_KEY: "{{ default_private_ssh_key.stdout | default(None) }}" 223 | PUBLIC_KEY: "{{ default_public_ssh_key.stdout | default(None) }}" 224 | command: "env/bin/python load_initial_data.py" 225 | register: load_initial_data_result 226 | changed_when: > 227 | "created" in load_initial_data_result.stdout 228 | or "changed" in load_initial_data_result.stdout 229 | args: 230 | chdir: "{{ openwisp2_path }}" 231 | -------------------------------------------------------------------------------- /tasks/django_secret_key.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Upload generate_django_secret_key.py script 4 | copy: 5 | src: generate_django_secret_key.py 6 | dest: "{{ openwisp2_path }}/generate_django_secret_key.py" 7 | mode: 0754 8 | 9 | - name: Generate new django SECRET_KEY 10 | shell: "{{ virtualenv_path }}/bin/python ./generate_django_secret_key.py > .django-secret-key" 11 | args: 12 | chdir: "{{ openwisp2_path }}" 13 | creates: "{{ openwisp2_path }}/.django-secret-key" 14 | 15 | - name: Get django SECRET_KEY 16 | command: "cat .django-secret-key" 17 | register: secret_key 18 | changed_when: false 19 | args: 20 | chdir: "{{ openwisp2_path }}" 21 | 22 | - name: Set permission to secret key file 23 | file: 24 | dest: "{{ openwisp2_path }}/.django-secret-key" 25 | mode: 0600 26 | 27 | - name: Set secret_key fact 28 | set_fact: openwisp2_secret_key="{{ secret_key.stdout }}" 29 | -------------------------------------------------------------------------------- /tasks/freeradius.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add freeradius repository into Ubuntu jammy sources list 3 | # NOTE: The older version of openwisp.openwisp2 used to install 4 | # FreeRADIUS 3.0.x on Ubuntu 22.04. After the release of 5 | # Ubuntu 22.04.1, FreeRADIUS 3.0.x can no longer be installed. 6 | # This block contains tasks that configures FreeRADIUS server 7 | # on Ubuntu 22.04 in a backward compatible manner, i.e. if 8 | # FreeRADIUS 3.0.x is installed on the VM, then the role will continue 9 | # using FreeRADIUS 3.0.x. On new installations, FreeRADIUS 3.2.x 10 | # will be used. 11 | block: 12 | - name: Check installed FreeRADIUS version 13 | shell: > 14 | which freeradius > /dev/null && 15 | freeradius -v | sed -n 2p | awk {'print $3'} | 16 | awk -F '.' {'printf("%1s.%s", $1, $2)'} 17 | ignore_errors: true 18 | register: openwisp2_installed_freeradius_version 19 | - name: Add FreeRADIUS 3.0.x repository to sources list 20 | shell: > 21 | echo "deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/packages.networkradius.com.asc] http://packages.networkradius.com/freeradius-3.0/ubuntu/jammy focal main" | 22 | tee /etc/apt/sources.list.d/networkradius.list > /dev/null 23 | when: openwisp2_installed_freeradius_version.stdout == '3.0' 24 | - name: Add FreeRADIUS 3.2.x repository to sources list 25 | shell: > 26 | echo "deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/packages.networkradius.com.asc] http://packages.networkradius.com/freeradius-3.2/ubuntu/jammy jammy main" | 27 | tee /etc/apt/sources.list.d/networkradius.list > /dev/null 28 | when: openwisp2_installed_freeradius_version.stdout != '3.0' 29 | - name: Update freeradius_dir variable 30 | set_fact: 31 | freeradius_dir: '/etc/freeradius/3.0' 32 | when: openwisp2_installed_freeradius_version.stdout == '3.0' 33 | when: 34 | - ansible_distribution|string == 'Ubuntu' 35 | - ansible_distribution_release|string == 'jammy' 36 | tags: 37 | - molecule-idempotence-notest 38 | 39 | - name: Add freeradius repository into Ubuntu noble sources list 40 | shell: | 41 | echo "deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/packages.networkradius.com.asc] http://packages.networkradius.com/freeradius-3.2/ubuntu/noble noble main" | \ 42 | tee /etc/apt/sources.list.d/networkradius.list > /dev/null 43 | become: true 44 | when: 45 | - ansible_distribution|string == 'Ubuntu' 46 | - ansible_distribution_release|string == 'noble' 47 | tags: 48 | - molecule-idempotence-notest 49 | 50 | - name: Add freeradius repository into Debian bookworm sources list 51 | shell: | 52 | echo "deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/packages.networkradius.com.asc] http://packages.networkradius.com/freeradius-3.2/debian/bookworm bookworm main" | \ 53 | sudo tee /etc/apt/sources.list.d/networkradius.list > /dev/null 54 | become: true 55 | when: 56 | - ansible_distribution|string == 'Debian' 57 | - ansible_distribution_release|string == 'bookworm' 58 | tags: 59 | - molecule-idempotence-notest 60 | 61 | - name: Add freeradius repository into Debian bullseye sources list 62 | shell: | 63 | echo "deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/packages.networkradius.com.asc] http://packages.networkradius.com/freeradius-3.0/debian/bullseye bullseye main" | \ 64 | tee /etc/apt/sources.list.d/networkradius.list > /dev/null 65 | become: true 66 | when: 67 | - ansible_distribution|string == 'Debian' 68 | - ansible_distribution_release|string == 'bullseye' 69 | tags: 70 | - molecule-idempotence-notest 71 | 72 | - name: Add Network RADIUS PGP public key 73 | get_url: 74 | url: https://packages.networkradius.com/pgp/packages%40networkradius.com 75 | dest: /etc/apt/trusted.gpg.d/packages.networkradius.com.asc 76 | checksum: sha256:f9327befd8efb3fddd710ed8b4ed50d394cc57a598ca8c3ccc2dc3234a4d774a 77 | ignore_errors: true 78 | retries: 5 79 | delay: 10 80 | register: result 81 | until: result is success 82 | 83 | - name: Update apt packages 84 | apt: 85 | update_cache: true 86 | 87 | - name: Freeradius system packages 88 | apt: 89 | name: 90 | - freeradius-rest 91 | state: latest 92 | notify: Restart freeradius 93 | 94 | - name: Freeradius postgres packages 95 | when: freeradius_sql.dialect == "postgresql" 96 | apt: 97 | name: freeradius-postgresql 98 | state: latest 99 | notify: Restart freeradius 100 | 101 | - name: Freeradius mysql packages 102 | when: freeradius_sql.dialect == "mysql" 103 | apt: 104 | name: freeradius-mysql 105 | state: latest 106 | notify: Restart freeradius 107 | 108 | - name: SQL configuration 109 | template: 110 | src: freeradius/sql.j2 111 | dest: "{{ freeradius_mods_available_dir }}/sql" 112 | mode: 0640 113 | owner: freerad 114 | group: freerad 115 | notify: Restart freeradius 116 | 117 | - name: Enable SQL module 118 | file: 119 | src: "{{ freeradius_mods_available_dir }}/sql" 120 | dest: "{{ freeradius_mods_enabled_dir }}/sql" 121 | state: link 122 | mode: 0640 123 | owner: freerad 124 | group: freerad 125 | 126 | - name: Add plus sign to safe characters 127 | lineinfile: 128 | path: "{{ freeradius_mods_config_dir }}/sql/main/{{ freeradius_sql.dialect }}/queries.conf" 129 | regexp: "^(.*)safe_characters =(.*)$" 130 | line: 'safe_characters = "{{ freeradius_safe_characters }}"' 131 | state: present 132 | notify: Restart freeradius 133 | 134 | - name: Adding user 'freerad' to www-data group for database access 135 | when: freeradius_sql.dialect == "sqlite" 136 | user: 137 | name: freerad 138 | groups: www-data 139 | append: true 140 | 141 | - name: Add Attributes to freeradius dictionary 142 | lineinfile: 143 | path: "{{ freeradius_dir }}/dictionary" 144 | line: "ATTRIBUTE Expire-After {{ freeradius_expire_attr_after_seconds }} integer" 145 | 146 | - name: REST configuration 147 | template: 148 | src: freeradius/rest.j2 149 | dest: "{{ freeradius_mods_available_dir }}/rest" 150 | mode: 0640 151 | owner: freerad 152 | group: freerad 153 | notify: Restart freeradius 154 | 155 | - name: Enable REST module 156 | file: 157 | src: "{{ freeradius_mods_available_dir }}/rest" 158 | dest: "{{ freeradius_mods_enabled_dir }}/rest" 159 | state: link 160 | mode: 0640 161 | owner: freerad 162 | group: freerad 163 | 164 | - name: Remove default site 165 | # Leave empty to ensure upgrades to freeradius 166 | # will not overwrite it 167 | copy: 168 | content: "# Empty, Generated by Ansible OpenWISP\n\n" 169 | dest: "{{ item }}" 170 | with_items: 171 | - "{{ freeradius_sites_enabled_dir }}/default" 172 | 173 | - name: Site configuration 174 | template: 175 | src: "{{ freeradius_openwisp_site_template_src }}" 176 | dest: "{{ freeradius_sites_enabled_dir }}/openwisp_site" 177 | mode: 0640 178 | owner: freerad 179 | group: freerad 180 | when: freeradius_deploy_openwisp_site 181 | notify: Restart freeradius 182 | 183 | - name: Inner tunnel 184 | template: 185 | src: freeradius/inner_tunnel.j2 186 | dest: "{{ freeradius_sites_enabled_dir }}/inner-tunnel" 187 | mode: 0640 188 | owner: freerad 189 | group: freerad 190 | when: freeradius_deploy_openwisp_site 191 | notify: Restart freeradius 192 | 193 | - name: Copy configuration for WPA Enterprise TTLS 194 | include_tasks: tasks/freeradius_eap.yml 195 | loop: "{{ freeradius_eap_orgs }}" 196 | loop_control: 197 | loop_var: org 198 | when: freeradius_eap_orgs 199 | tags: [freeradius_eap] 200 | -------------------------------------------------------------------------------- /tasks/freeradius_eap.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Generate default DH key and certificates 4 | block: 5 | - name: "Check if {{ freeradius_dir }}/certs/dh exists" 6 | ansible.builtin.stat: 7 | path: "{{ freeradius_dir }}/certs/dh" 8 | register: cert_dh_exists 9 | - name: Generate DH key and certificates 10 | command: 11 | cmd: make destroycerts dh all 12 | chdir: "{{ freeradius_dir }}/certs" 13 | when: not cert_dh_exists.stat.exists 14 | notify: Restart freeradius 15 | tags: [freeradius_eap] 16 | 17 | - name: "Copy {{ org.name }} EAP openwisp_site" 18 | template: 19 | src: "{{ freeradius_eap_openwisp_site_template_src }}" 20 | dest: "{{ freeradius_dir }}/sites-available/{{ org.name }}_eap_openwisp_site" 21 | owner: freerad 22 | group: freerad 23 | mode: '0644' 24 | notify: Restart freeradius 25 | tags: [freeradius_eap] 26 | 27 | - name: "Create a symlink in sites-enabled for {{ org.name }} EAP openwisp_site" 28 | ansible.builtin.file: 29 | src: "{{ freeradius_dir }}/sites-available/{{ org.name }}_eap_openwisp_site" 30 | dest: "{{ freeradius_dir }}/sites-enabled/{{ org.name }}_eap_openwisp_site" 31 | state: link 32 | notify: Restart freeradius 33 | tags: [freeradius_eap] 34 | 35 | - name: "Copy {{ org.name }} eap_inner_tunnel" 36 | template: 37 | src: "{{ freeradius_eap_inner_tunnel_template_src }}" 38 | dest: "{{ freeradius_dir }}/sites-available/{{ org.name }}_eap_inner_tunnel" 39 | owner: freerad 40 | group: freerad 41 | mode: '0644' 42 | notify: Restart freeradius 43 | tags: [freeradius_eap] 44 | 45 | - name: "Create a symlink in sites-enabled for {{ org.name }} eap_inner_tunnel" 46 | ansible.builtin.file: 47 | src: "{{ freeradius_dir }}/sites-available/{{ org.name }}_eap_inner_tunnel" 48 | dest: "{{ freeradius_dir }}/sites-enabled/{{ org.name }}_eap_inner_tunnel" 49 | state: link 50 | notify: Restart freeradius 51 | tags: [freeradius_eap] 52 | 53 | - name: Copy {{ org.name }} custom EAP configuration in mods-available 54 | template: 55 | src: "{{ freeradius_eap_template_src }}" 56 | dest: "{{ freeradius_dir }}/mods-available/{{ org.name }}_eap" 57 | owner: freerad 58 | group: freerad 59 | mode: '0644' 60 | notify: Restart freeradius 61 | tags: [freeradius_eap] 62 | 63 | - name: Create a symlink in mods-enabled 64 | ansible.builtin.file: 65 | src: "{{ freeradius_dir }}/mods-available/{{ org.name }}_eap" 66 | dest: "{{ freeradius_dir }}/mods-enabled/{{ org.name }}_eap" 67 | state: link 68 | notify: Restart freeradius 69 | tags: [freeradius_eap] 70 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - import_tasks: consent.yml 4 | tags: [openwisp2, usage_collection] 5 | 6 | - import_tasks: apt.yml 7 | tags: [openwisp2, apt] 8 | 9 | - import_tasks: ssh.yml 10 | tags: [openwisp2, ssh] 11 | 12 | - name: Import system tasks 13 | import_tasks: system.yml 14 | tags: [openwisp2, system] 15 | 16 | - import_tasks: pip.yml 17 | tags: [openwisp2, pip] 18 | 19 | # comprises django_secret_key.yml 20 | - import_tasks: django.yml 21 | tags: [openwisp2, django] 22 | 23 | - import_tasks: freeradius.yml 24 | when: openwisp2_radius and openwisp2_freeradius_install 25 | tags: [openwisp2, freeradius] 26 | 27 | - import_tasks: supervisor.yml 28 | tags: [openwisp2, supervisor] 29 | 30 | - import_tasks: nginx.yml 31 | when: openwisp2_nginx_install 32 | tags: [openwisp2, nginx] 33 | 34 | - import_tasks: cron.yml 35 | tags: [openwisp2, cron] 36 | 37 | - import_tasks: complete.yml 38 | tags: [openwisp2] 39 | -------------------------------------------------------------------------------- /tasks/nginx.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set nginx max upload size 3 | # Required to allow uploading firmware images 4 | set_fact: 5 | openwisp2_nginx_client_max_body_size: "{{ openwisp2_firmware_upgrader_max_file_size }}" 6 | when: openwisp2_firmware_upgrader 7 | 8 | - name: Create "{{ openwisp2_path }}/public_html" 9 | file: 10 | path: "{{ openwisp2_path }}/public_html" 11 | state: directory 12 | mode: 0775 13 | 14 | - name: Create "{{ openwisp2_path }}/nginx-conf/openwisp2" 15 | file: 16 | path: "{{ openwisp2_path }}/nginx-conf/openwisp2" 17 | state: directory 18 | mode: 0770 19 | 20 | - name: Create "{{ openwisp2_path }}/ssl" 21 | file: 22 | path: "{{ openwisp2_path }}/ssl" 23 | state: directory 24 | mode: 0770 25 | 26 | - name: Create SSL cert if not exists yet 27 | command: > 28 | openssl req -new -nodes -x509 \ 29 | -subj "/C={{ openwisp2_ssl_country }}/ST={{ openwisp2_ssl_state }} \ 30 | /L={{ openwisp2_ssl_locality }}/O={{ openwisp2_ssl_organization }} \ 31 | /CN={{ openwisp2_ssl_common_name }}" \ 32 | -days 3650 \ 33 | -keyout {{ openwisp2_ssl_key }} \ 34 | -out {{ openwisp2_ssl_cert }} \ 35 | -extensions v3_ca creates={{ openwisp2_ssl_cert }} 36 | notify: Restart nginx 37 | 38 | - name: Copy SSL cert to be added to trusted Cert (for freeradius) 39 | copy: 40 | src: "{{ openwisp2_ssl_cert }}" 41 | dest: /usr/local/share/ca-certificates/openwisp-ssl-server.crt 42 | remote_src: true 43 | owner: "root" 44 | group: "root" 45 | mode: "0644" 46 | notify: Update-ca-certificates 47 | 48 | - name: Disable default nginx configuration 49 | file: 50 | path: "/etc/nginx/sites-enabled/default" 51 | state: absent 52 | 53 | - name: Nginx SSL configuration 54 | template: 55 | src: nginx/ssl-conf.j2 56 | dest: "{{ openwisp2_path }}/nginx-conf/openwisp2/ssl.conf" 57 | mode: 0644 58 | notify: Restart nginx 59 | 60 | - name: Nginx security configuration 61 | template: 62 | src: nginx/security-conf.j2 63 | dest: "{{ openwisp2_path }}/nginx-conf/openwisp2/security.conf" 64 | mode: 0644 65 | notify: Restart nginx 66 | tags: [nginx_security] 67 | 68 | - name: Nginx site available 69 | template: 70 | src: nginx/site-conf.j2 71 | dest: "/etc/nginx/sites-available/{{ inventory_hostname }}" 72 | mode: 0644 73 | notify: Restart nginx 74 | 75 | - name: Nginx site enabled 76 | file: 77 | src: "/etc/nginx/sites-available/{{ inventory_hostname }}" 78 | dest: "/etc/nginx/sites-enabled/{{ inventory_hostname }}" 79 | state: link 80 | notify: Restart nginx 81 | 82 | - name: Configure nginx log rotation 83 | template: 84 | src: logrotate.d/openwisp-nginx.j2 85 | dest: /etc/logrotate.d/openwisp-nginx 86 | mode: 0644 87 | 88 | - name: Disable nginx server tokens 89 | replace: 90 | path: /etc/nginx/nginx.conf 91 | regexp: '#(\s+)server_tokens off' 92 | replace: 'server_tokens off' 93 | backup: true 94 | notify: Restart nginx 95 | -------------------------------------------------------------------------------- /tasks/pip.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Update pip & related tools 4 | pip: 5 | name: 6 | - pip 7 | - setuptools 8 | - wheel 9 | - attrs 10 | state: latest 11 | virtualenv: "{{ virtualenv_path }}" 12 | virtualenv_python: "{{ openwisp2_python }}" 13 | retries: 5 14 | delay: 10 15 | register: result 16 | until: result is success 17 | 18 | - name: Remove jsonfield2 19 | pip: 20 | name: 21 | - jsonfield2 22 | state: absent 23 | virtualenv: "{{ virtualenv_path }}" 24 | virtualenv_python: "{{ openwisp2_python }}" 25 | retries: 2 26 | delay: 5 27 | register: result 28 | until: result is success 29 | notify: Reload application 30 | 31 | - name: Install openwisp2 controller and its dependencies 32 | pip: 33 | name: 34 | - "{{ openwisp2_controller_version }}" 35 | - service_identity 36 | state: latest 37 | virtualenv: "{{ virtualenv_path }}" 38 | virtualenv_python: "{{ openwisp2_python }}" 39 | environment: 40 | LC_CTYPE: "en_US.UTF-8" 41 | notify: Reload application 42 | retries: 5 43 | delay: 10 44 | register: result 45 | until: result is success 46 | tags: 47 | - molecule-idempotence-notest 48 | 49 | - name: Install channels_redis~=4.2.0 50 | pip: 51 | name: 52 | - channels_redis~=4.2.0 53 | state: latest 54 | virtualenv: "{{ virtualenv_path }}" 55 | virtualenv_python: "{{ openwisp2_python }}" 56 | retries: 1 57 | delay: 10 58 | register: result 59 | until: result is success 60 | 61 | - name: Install django-redis 62 | pip: 63 | name: "django-redis~=5.4.0" 64 | state: present 65 | virtualenv: "{{ virtualenv_path }}" 66 | virtualenv_python: "{{ openwisp2_python }}" 67 | notify: Reload application 68 | retries: 5 69 | delay: 10 70 | register: result 71 | until: result is success 72 | 73 | - name: Install openwisp2 network topology and its dependencies 74 | when: openwisp2_network_topology 75 | pip: 76 | name: "{{ openwisp2_network_topology_version }}" 77 | state: latest 78 | virtualenv: "{{ virtualenv_path }}" 79 | virtualenv_python: "{{ openwisp2_python }}" 80 | notify: Reload application 81 | retries: 5 82 | delay: 10 83 | register: result 84 | until: result is success 85 | tags: 86 | - molecule-idempotence-notest 87 | 88 | - name: Install openwisp firmware upgrader and its dependencies 89 | when: openwisp2_firmware_upgrader 90 | pip: 91 | name: "{{ openwisp2_firmware_upgrader_version }}" 92 | state: latest 93 | virtualenv: "{{ virtualenv_path }}" 94 | virtualenv_python: "{{ openwisp2_python }}" 95 | notify: Reload application 96 | retries: 5 97 | delay: 10 98 | register: result 99 | until: result is success 100 | tags: 101 | - molecule-idempotence-notest 102 | 103 | - name: Install openwisp monitoring and its dependencies 104 | when: openwisp2_monitoring 105 | pip: 106 | name: "{{ openwisp2_monitoring_version }}" 107 | state: latest 108 | virtualenv: "{{ virtualenv_path }}" 109 | virtualenv_python: "{{ openwisp2_python }}" 110 | notify: Reload application 111 | retries: 5 112 | delay: 10 113 | register: result 114 | until: result is success 115 | tags: 116 | - molecule-idempotence-notest 117 | 118 | - name: Install openwisp2_radius and its dependencies 119 | when: openwisp2_radius 120 | pip: 121 | name: "{{ openwisp2_radius_version }}" 122 | state: latest 123 | virtualenv: "{{ virtualenv_path }}" 124 | virtualenv_python: "{{ openwisp2_python }}" 125 | notify: Reload application 126 | retries: 5 127 | delay: 10 128 | register: result 129 | until: result is success 130 | tags: 131 | - molecule-idempotence-notest 132 | 133 | - name: Install django-cors-headers 134 | when: openwisp2_django_cors.get('enabled') 135 | pip: 136 | name: "django-cors-headers~=4.4.0" 137 | state: latest 138 | virtualenv: "{{ virtualenv_path }}" 139 | virtualenv_python: "{{ openwisp2_python }}" 140 | retries: 5 141 | delay: 10 142 | register: result 143 | until: result is success 144 | notify: Reload application 145 | 146 | - name: Install extra python packages 147 | pip: 148 | name: "{{ openwisp2_extra_python_packages }}" 149 | state: latest 150 | virtualenv: "{{ virtualenv_path }}" 151 | virtualenv_python: "{{ openwisp2_python }}" 152 | retries: 5 153 | delay: 10 154 | register: result 155 | until: result is success 156 | notify: Reload application 157 | tags: [extra_pip] 158 | 159 | - name: Install static minification dependencies 160 | pip: 161 | name: 162 | - django-pipeline~=3.1.0 163 | state: latest 164 | virtualenv: "{{ virtualenv_path }}" 165 | virtualenv_python: "{{ openwisp2_python }}" 166 | retries: 5 167 | delay: 10 168 | register: result 169 | until: result is success 170 | notify: Reload application 171 | 172 | - name: Install uwsgi 173 | pip: 174 | name: uwsgi 175 | state: latest 176 | virtualenv: "{{ virtualenv_path }}" 177 | virtualenv_python: "{{ openwisp2_python }}" 178 | retries: 5 179 | delay: 10 180 | register: result 181 | until: result is success 182 | notify: Reload application 183 | 184 | - name: Install psycopg2 185 | when: openwisp2_database.engine in ["django.db.backends.postgresql", "django.contrib.gis.db.backends.postgis"] 186 | pip: 187 | name: psycopg2 188 | state: latest 189 | virtualenv: "{{ virtualenv_path }}" 190 | virtualenv_python: "{{ openwisp2_python }}" 191 | retries: 5 192 | delay: 10 193 | register: result 194 | until: result is success 195 | notify: Reload application 196 | 197 | - name: Install MySQL-python 198 | when: openwisp2_database.engine in ["django.db.backends.mysql", "django.contrib.gis.db.backends.mysql"] 199 | pip: 200 | name: MySQL-python 201 | state: latest 202 | virtualenv: "{{ virtualenv_path }}" 203 | virtualenv_python: "{{ openwisp2_python }}" 204 | retries: 5 205 | delay: 10 206 | register: result 207 | until: result is success 208 | notify: Reload application 209 | 210 | - name: Install raven (sentry client) 211 | when: openwisp2_sentry.get('dsn') 212 | pip: 213 | name: raven 214 | state: latest 215 | virtualenv: "{{ virtualenv_path }}" 216 | virtualenv_python: "{{ openwisp2_python }}" 217 | retries: 5 218 | delay: 10 219 | register: result 220 | until: result is success 221 | notify: Reload application 222 | 223 | - name: Install django-celery-email 224 | pip: 225 | name: django-celery-email 226 | state: latest 227 | virtualenv: "{{ virtualenv_path }}" 228 | virtualenv_python: "{{ openwisp2_python }}" 229 | when: openwisp2_email_backend == "djcelery_email.backends.CeleryEmailBackend" 230 | retries: 5 231 | delay: 10 232 | register: result 233 | until: result is success 234 | notify: Reload application 235 | 236 | - name: Install django 237 | pip: 238 | name: "{{ openwisp2_django_version }}" 239 | state: present 240 | virtualenv: "{{ virtualenv_path }}" 241 | virtualenv_python: "{{ openwisp2_python }}" 242 | retries: 5 243 | delay: 10 244 | register: result 245 | until: result is success 246 | notify: Reload application 247 | tags: 248 | - molecule-idempotence-notest 249 | -------------------------------------------------------------------------------- /tasks/ssh.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Systems deployed with older version of ansible-openwisp2 use RSA keys. 4 | # We keep using the old RSA key and don't change the SSH keys inadvertently. 5 | - name: Check if /root/.ssh/id_rsa exists 6 | become: true 7 | stat: 8 | path: /root/.ssh/id_rsa 9 | register: rsa_key_file 10 | 11 | - name: Set SSH key file name 12 | set_fact: 13 | ssh_file_name: "{{ 'id_rsa' if rsa_key_file.stat.exists else 'id_ed25519' }}" 14 | 15 | - name: Create default SSH key pair 16 | become: true 17 | user: 18 | name: root 19 | generate_ssh_key: true 20 | ssh_key_type: ed25519 21 | ssh_key_file: ".ssh/{{ ssh_file_name }}" 22 | 23 | - name: Get default private SSH key 24 | become: true 25 | command: "cat /root/.ssh/{{ ssh_file_name }}" 26 | register: default_private_ssh_key 27 | changed_when: false 28 | 29 | - name: Get default public SSH key 30 | become: true 31 | command: " cat /root/.ssh/{{ ssh_file_name }}.pub" 32 | register: default_public_ssh_key 33 | changed_when: false 34 | -------------------------------------------------------------------------------- /tasks/supervisor.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Template for uwsgi.ini 4 | template: 5 | src: uwsgi.ini.j2 6 | dest: "{{ openwisp2_path }}/uwsgi.ini" 7 | mode: 0644 8 | notify: Update supervisor configuration 9 | 10 | - name: Template for supervisor uwsgi 11 | template: 12 | src: supervisor/openwisp2.j2 13 | dest: "{{ supervisor_path | format('openwisp2') }}" 14 | mode: 0644 15 | notify: Update supervisor configuration 16 | 17 | - name: Template for supervisor daphne 18 | template: 19 | src: supervisor/daphne.j2 20 | dest: "{{ supervisor_path | format('daphne') }}" 21 | mode: 0644 22 | when: openwisp2_daphne_install 23 | notify: Update supervisor configuration 24 | 25 | - name: Template for supervisor celery 26 | template: 27 | src: supervisor/celery.j2 28 | dest: "{{ supervisor_path | format('celery') }}" 29 | mode: 0644 30 | notify: Update supervisor configuration 31 | tags: [celery_conf] 32 | 33 | - name: Template for supervisor celery network 34 | template: 35 | src: supervisor/celery_network.j2 36 | dest: "{{ supervisor_path | format('celery_network') }}" 37 | mode: 0644 38 | when: openwisp2_celery_network 39 | notify: Update supervisor configuration 40 | tags: [celery_conf] 41 | 42 | - name: Template for supervisor celery firmware_upgrader 43 | template: 44 | src: supervisor/celery_firmware_upgrader.j2 45 | dest: "{{ supervisor_path | format('celery_firmware_upgrader') }}" 46 | mode: 0644 47 | when: openwisp2_firmware_upgrader and openwisp2_celery_firmware_upgrader 48 | notify: Update supervisor configuration 49 | tags: [celery_conf] 50 | 51 | - name: Template for supervisor celery monitoring 52 | template: 53 | src: supervisor/celery_monitoring.j2 54 | dest: "{{ supervisor_path | format('celery_monitoring') }}" 55 | mode: 0644 56 | when: openwisp2_monitoring and openwisp2_celery_monitoring 57 | notify: Update supervisor configuration 58 | tags: [celery_conf] 59 | 60 | - name: Template for supervisor celerybeat 61 | template: 62 | src: supervisor/celerybeat.j2 63 | dest: "{{ supervisor_path | format('celerybeat') }}" 64 | mode: 0644 65 | when: openwisp2_celerybeat 66 | notify: Update supervisor configuration 67 | tags: [celery_conf] 68 | 69 | - name: Remove supervisor runworker.conf (obsolete) 70 | file: 71 | dest: "{{ supervisor_path | format('runworker') }}" 72 | state: absent 73 | notify: Update supervisor configuration 74 | -------------------------------------------------------------------------------- /tasks/system.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Add inventory_hostname in /etc/hosts 4 | lineinfile: 5 | path: /etc/hosts 6 | regexp: "^127.0.0.1" 7 | line: "127.0.0.1 localhost {{ inventory_hostname }}" 8 | state: present 9 | tags: 10 | # Docker does not allow updating /etc/hosts file at 11 | # container level, hence this task would fail with molecule. 12 | # https://github.com/ansible-community/molecule/issues/959 13 | - molecule-notest 14 | -------------------------------------------------------------------------------- /templates/freeradius/eap/eap.j2: -------------------------------------------------------------------------------- 1 | eap {{ org.name }}_eap { 2 | default_eap_type = ttls 3 | timer_expire = 60 4 | ignore_unknown_eap_types = no 5 | cisco_accounting_username_bug = no 6 | max_sessions = ${max_requests} 7 | 8 | tls-config tls-common { 9 | # make sure to have a valid SSL certificate for production usage 10 | private_key_password = whatever 11 | private_key_file = {{ org.private_key | default('${certdir}/server.pem') }} 12 | certificate_file = {{ org.cert | default('${certdir}/server.pem') }} 13 | ca_file = {{ org.ca | default('${cadir}/ca.pem') }} 14 | dh_file = {{ org.dh | default('${certdir}/dh') }} 15 | ca_path = ${cadir} 16 | cipher_list = "DEFAULT" 17 | cipher_server_preference = no 18 | tls_min_version = "1.2" 19 | tls_max_version = "1.2" 20 | check_crl = no 21 | check_cert_issuer = no 22 | fragment_size = 2048 23 | auto_chain = yes 24 | 25 | {% if 'tls_config_extra' in org %} 26 | {{ org.tls_config_extra }} 27 | {% endif %} 28 | 29 | cache { 30 | enable = no 31 | } 32 | 33 | ocsp { 34 | enable = no 35 | override_cert_url = yes 36 | url = "http://127.0.0.1/ocsp/" 37 | } 38 | } 39 | 40 | ttls { 41 | tls = tls-common 42 | default_eap_type = pap 43 | copy_request_to_tunnel = yes 44 | use_tunneled_reply = yes 45 | virtual_server = "{{ org.name }}_eap_inner_tunnel" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /templates/freeradius/eap/inner_tunnel.j2: -------------------------------------------------------------------------------- 1 | server {{ org.name }}_eap_inner_tunnel { 2 | listen { 3 | ipaddr = 127.0.0.1 4 | port = {{ org.inner_tunnel_auth_port }} 5 | type = auth 6 | } 7 | 8 | api_token_header = "Authorization: Bearer {{ org.uuid }} {{ org.radius_token }}" 9 | authorize { 10 | filter_username 11 | update control { &REST-HTTP-Header += "${...api_token_header}" } 12 | rest 13 | {{ org.name }}_eap { 14 | ok = return 15 | } 16 | 17 | chap 18 | mschap 19 | suffix 20 | 21 | update control { 22 | &Proxy-To-Realm := LOCAL 23 | } 24 | 25 | eap { 26 | ok = return 27 | } 28 | 29 | -ldap 30 | 31 | pap 32 | 33 | expiration 34 | logintime 35 | } 36 | 37 | authenticate { 38 | Auth-Type PAP { 39 | pap 40 | } 41 | 42 | Auth-Type CHAP { 43 | chap 44 | } 45 | 46 | Auth-Type MS-CHAP { 47 | mschap 48 | } 49 | eap 50 | } 51 | 52 | session {} 53 | 54 | post-auth { 55 | if (0) { 56 | update reply { 57 | User-Name !* ANY 58 | Message-Authenticator !* ANY 59 | EAP-Message !* ANY 60 | Proxy-State !* ANY 61 | MS-MPPE-Encryption-Types !* ANY 62 | MS-MPPE-Encryption-Policy !* ANY 63 | MS-MPPE-Send-Key !* ANY 64 | MS-MPPE-Recv-Key !* ANY 65 | } 66 | update { 67 | &outer.session-state: += &reply: 68 | } 69 | } 70 | 71 | Post-Auth-Type REJECT { 72 | attr_filter.access_reject 73 | update outer.session-state { 74 | &Module-Failure-Message := &request:Module-Failure-Message 75 | } 76 | } 77 | } 78 | 79 | pre-proxy {} 80 | post-proxy { 81 | {{ org.name }}_eap 82 | eap 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /templates/freeradius/eap/openwisp_site.j2: -------------------------------------------------------------------------------- 1 | server {{ org.name }}_eap_openwisp_site { 2 | listen { 3 | type = auth 4 | ipaddr = {{ org.listen_ipaddr | default(freeradius_openwisp_site_listen_ipaddr) }} 5 | port = {{ org.auth_port }} 6 | limit { 7 | max_connections = 16 8 | lifetime = 0 9 | idle_timeout = 30 10 | } 11 | } 12 | 13 | listen { 14 | ipaddr = {{ org.listen_ipaddr | default(freeradius_openwisp_site_listen_ipaddr) }} 15 | port = {{ org.acct_port }} 16 | type = acct 17 | limit {} 18 | } 19 | 20 | api_token_header = "Authorization: Bearer {{ org.uuid }} {{ org.radius_token }}" 21 | authorize { 22 | {{ org.name }}_eap { 23 | ok = return 24 | } 25 | update control { &REST-HTTP-Header += "${...api_token_header}" } 26 | filter_username 27 | rest 28 | expiration 29 | logintime 30 | } 31 | 32 | authenticate { 33 | Auth-Type {{ org.name }}_eap { 34 | {{ org.name }}_eap 35 | } 36 | Auth-Type PAP { 37 | pap 38 | } 39 | 40 | Auth-Type CHAP { 41 | chap 42 | } 43 | 44 | Auth-Type MS-CHAP { 45 | mschap 46 | } 47 | 48 | Auth-Type EAP { 49 | eap 50 | } 51 | } 52 | 53 | preacct { 54 | preprocess 55 | acct_unique 56 | suffix 57 | files 58 | } 59 | 60 | accounting { 61 | update control { &REST-HTTP-Header += "${...api_token_header}" } 62 | rest 63 | } 64 | 65 | session {} 66 | 67 | post-auth { 68 | update control { &REST-HTTP-Header += "${...api_token_header}" } 69 | rest 70 | 71 | Post-Auth-Type REJECT { 72 | update control { &REST-HTTP-Header += "${....api_token_header}" } 73 | rest 74 | } 75 | } 76 | 77 | pre-proxy {} 78 | post-proxy {} 79 | } 80 | -------------------------------------------------------------------------------- /templates/freeradius/inner_tunnel.j2: -------------------------------------------------------------------------------- 1 | server inner_tunnel { 2 | listen { 3 | ipaddr = 127.0.0.1 4 | port = 18120 5 | type = auth 6 | } 7 | 8 | authorize { 9 | filter_username 10 | rest 11 | 12 | chap 13 | mschap 14 | suffix 15 | 16 | update control { 17 | &Proxy-To-Realm := LOCAL 18 | } 19 | 20 | eap { 21 | ok = return 22 | } 23 | 24 | -ldap 25 | 26 | pap 27 | 28 | expiration 29 | logintime 30 | } 31 | 32 | authenticate { 33 | Auth-Type PAP { 34 | pap 35 | } 36 | 37 | Auth-Type CHAP { 38 | chap 39 | } 40 | 41 | Auth-Type MS-CHAP { 42 | mschap 43 | } 44 | eap 45 | } 46 | 47 | session {} 48 | 49 | post-auth { 50 | if (0) { 51 | update reply { 52 | User-Name !* ANY 53 | Message-Authenticator !* ANY 54 | EAP-Message !* ANY 55 | Proxy-State !* ANY 56 | MS-MPPE-Encryption-Types !* ANY 57 | MS-MPPE-Encryption-Policy !* ANY 58 | MS-MPPE-Send-Key !* ANY 59 | MS-MPPE-Recv-Key !* ANY 60 | } 61 | update { 62 | &outer.session-state: += &reply: 63 | } 64 | } 65 | 66 | Post-Auth-Type REJECT { 67 | attr_filter.access_reject 68 | update outer.session-state { 69 | &Module-Failure-Message := &request:Module-Failure-Message 70 | } 71 | } 72 | } 73 | 74 | pre-proxy {} 75 | post-proxy { 76 | eap 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /templates/freeradius/openwisp_site.j2: -------------------------------------------------------------------------------- 1 | server default { 2 | listen { 3 | type = auth 4 | ipaddr = {{ freeradius_openwisp_site_listen_ipaddr }} 5 | port = 0 6 | limit { 7 | max_connections = 16 8 | lifetime = 0 9 | idle_timeout = 30 10 | } 11 | } 12 | 13 | listen { 14 | ipaddr = {{ freeradius_openwisp_site_listen_ipaddr }} 15 | port = 0 16 | type = acct 17 | limit {} 18 | } 19 | 20 | authorize { 21 | filter_username 22 | rest 23 | expiration 24 | logintime 25 | } 26 | 27 | authenticate { 28 | Auth-Type PAP { 29 | pap 30 | } 31 | 32 | Auth-Type CHAP { 33 | chap 34 | } 35 | 36 | Auth-Type MS-CHAP { 37 | mschap 38 | } 39 | 40 | Auth-Type EAP { 41 | eap 42 | } 43 | } 44 | 45 | preacct { 46 | preprocess 47 | acct_unique 48 | suffix 49 | files 50 | } 51 | 52 | accounting { 53 | rest 54 | } 55 | 56 | session {} 57 | 58 | post-auth { 59 | rest 60 | 61 | Post-Auth-Type REJECT { 62 | rest 63 | } 64 | } 65 | 66 | pre-proxy {} 67 | post-proxy {} 68 | } 69 | -------------------------------------------------------------------------------- /templates/freeradius/rest.j2: -------------------------------------------------------------------------------- 1 | rest { 2 | tls = {} 3 | connect_uri = "{{ freeradius_rest.url }}" 4 | 5 | authorize { 6 | uri = "${..connect_uri}/authorize/" 7 | method = 'post' 8 | body = 'json' 9 | data = '{"username": "%{User-Name}", "password": "%{User-Password}"}' 10 | tls = ${..tls} 11 | } 12 | 13 | # this section can be left empty 14 | authenticate {} 15 | 16 | post-auth { 17 | uri = "${..connect_uri}/postauth/" 18 | method = 'post' 19 | body = 'json' 20 | data = '{"username": "%{User-Name}", "password": "%{User-Password}", "reply": "%{reply:Packet-Type}", "called_station_id": "%{Called-Station-ID}", "calling_station_id": "%{Calling-Station-ID}"}' 21 | tls = ${..tls} 22 | } 23 | 24 | accounting { 25 | uri = "${..connect_uri}/accounting/" 26 | method = 'post' 27 | body = 'json' 28 | data = '{"status_type": "%{Acct-Status-Type}", "session_id": "%{Acct-Session-Id}", "unique_id": "%{Acct-Unique-Session-Id}", "username": "%{User-Name}", "realm": "%{Realm}", "nas_ip_address": "%{NAS-IP-Address}", "nas_port_id": "%{NAS-Port}", "nas_port_type": "%{NAS-Port-Type}", "session_time": "%{Acct-Session-Time}", "authentication": "%{Acct-Authentic}", "input_octets": "%{Acct-Input-Octets}", "output_octets": "%{Acct-Output-Octets}", "called_station_id": "%{Called-Station-Id}", "calling_station_id": "%{Calling-Station-Id}", "terminate_cause": "%{Acct-Terminate-Cause}", "service_type": "%{Service-Type}", "framed_protocol": "%{Framed-Protocol}", "framed_ip_address": "%{Framed-IP-Address}"}' 29 | tls = ${..tls} 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /templates/freeradius/sql.j2: -------------------------------------------------------------------------------- 1 | sql { 2 | driver = "{{ freeradius_sql.driver }}" 3 | dialect = "{{ freeradius_sql.dialect }}" 4 | {% if freeradius_sql.dialect in ['postgresql', 'mysql'] %} 5 | radius_db = "{{ openwisp2_database.name }}" 6 | server = "{{ openwisp2_database.host }}" 7 | port = "{{ openwisp2_database.port }}" 8 | login = "{{ openwisp2_database.user }}" 9 | password = "{{ openwisp2_database.password }}" 10 | 11 | {% elif freeradius_sql.dialect == 'sqlite' %} 12 | sqlite { 13 | filename = "{{ openwisp2_database.name }}" 14 | } 15 | 16 | {% endif %} 17 | acct_table1 = "radacct" 18 | acct_table2 = "radacct" 19 | postauth_table = "radpostauth" 20 | authcheck_table = "radcheck" 21 | groupcheck_table = "radgroupcheck" 22 | authreply_table = "radreply" 23 | groupreply_table = "radgroupreply" 24 | usergroup_table = "radusergroup" 25 | delete_stale_sessions = yes 26 | client_table = "nas" 27 | read_clients = yes 28 | group_attribute = "SQL-Group" 29 | 30 | $INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf 31 | 32 | pool { 33 | start = ${thread[pool].start_servers} 34 | min = ${thread[pool].min_spare_servers} 35 | max = ${thread[pool].max_servers} 36 | spare = ${thread[pool].max_spare_servers} 37 | uses = 0 38 | retry_delay = 30 39 | lifetime = 0 40 | idle_timeout = 60 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /templates/load_initial_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | - Creates the admin user when openwisp2 is installed 4 | - Additionally creates the default organization if no organization is present 5 | - Modifies default Site object 6 | - Adds a default access credential and updates or creates default 7 | template to use the same 8 | """ 9 | import os 10 | 11 | import django 12 | 13 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'openwisp2.settings') 14 | django.setup() 15 | 16 | from django.conf import settings 17 | from django.contrib.auth import get_user_model 18 | from django.db import IntegrityError 19 | from swapper import load_model 20 | 21 | Credentials = load_model('connection', 'Credentials') 22 | Template = load_model('config', 'Template') 23 | User = get_user_model() 24 | changed = False 25 | 26 | if User.objects.filter(is_superuser=True).count() < 1: 27 | admin = User.objects.create_superuser(username='admin', password='admin', email='') 28 | print('superuser created') 29 | 30 | if 'django.contrib.sites' in settings.INSTALLED_APPS: 31 | from django.contrib.sites.models import Site 32 | 33 | site = Site.objects.first() 34 | if site and 'example.com' in [site.name, site.domain]: 35 | site.name = '{{ inventory_hostname }}' 36 | site.domain = '{{ inventory_hostname }}' 37 | site.save() 38 | print('default site updated') 39 | 40 | # Get SSH key pair 41 | ssh_private_key = os.environ.get('PRIVATE_KEY') 42 | ssh_pub_key = os.environ.get('PUBLIC_KEY') 43 | 44 | # Create a default credentials object 45 | if ssh_private_key and Credentials.objects.count() == 0: 46 | Credentials.objects.create( 47 | connector='openwisp_controller.connection.connectors.ssh.Ssh', 48 | name='OpenWISP Default', 49 | auto_add=True, 50 | params={'username': 'root', 'key': ssh_private_key}, 51 | ) 52 | print('Credentials object created') 53 | 54 | # Update or create default template to add default credentials 55 | queryset = Template.objects.filter( 56 | default=True, config__contains='/etc/dropbear/authorized_keys' 57 | ) 58 | if ssh_pub_key and queryset.count() == 0: 59 | Template.objects.create( 60 | name='SSH Keys', 61 | default=True, 62 | backend='netjsonconfig.OpenWrt', 63 | config={ 64 | 'files': [ 65 | { 66 | 'path': '/etc/dropbear/authorized_keys', 67 | 'mode': '0644', 68 | 'contents': ssh_pub_key, 69 | }, 70 | ] 71 | }, 72 | ) 73 | print('created Default Credentials Template') 74 | else: 75 | for template_obj in queryset.iterator(): 76 | for config_file in template_obj.config['files']: 77 | if ( 78 | config_file['path'] == '/etc/dropbear/authorized_keys' 79 | and ssh_pub_key not in config_file['contents'] 80 | ): 81 | config_file['contents'] += '\n' + ssh_pub_key 82 | template_obj.save() 83 | print(f'changed {template_obj.name} to add default SSH credential') 84 | -------------------------------------------------------------------------------- /templates/logrotate.d/openwisp-nginx.j2: -------------------------------------------------------------------------------- 1 | {{ openwisp2_path }}/log/nginx*.log { 2 | maxsize 60M 3 | missingok 4 | rotate 5 5 | compress 6 | delaycompress 7 | notifempty 8 | create 0640 {{ www_user }} {{ www_group }} 9 | su root {{ www_user }} 10 | sharedscripts 11 | postrotate 12 | service nginx rotate >/dev/null 2>&1 13 | endscript 14 | } 15 | -------------------------------------------------------------------------------- /templates/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "openwisp2.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /templates/nginx/security-conf.j2: -------------------------------------------------------------------------------- 1 | # security headers adapted from https://www.digitalocean.com/community/tools/nginx 2 | add_header X-XSS-Protection "1; mode=block" always; 3 | add_header X-Content-Type-Options "nosniff" always; 4 | add_header Referrer-Policy "same-site" always; 5 | add_header Content-Security-Policy {{ openwisp2_nginx_csp }} 6 | add_header Permissions-Policy "interest-cohort=()" always; 7 | add_header Strict-Transport-Security "max-age=31536000" always; 8 | -------------------------------------------------------------------------------- /templates/nginx/site-conf.j2: -------------------------------------------------------------------------------- 1 | upstream channels-backend { 2 | server unix://{{ openwisp2_path }}/daphne0.sock fail_timeout=0; 3 | } 4 | 5 | upstream openwisp-server { 6 | {% for server in openwisp2_nginx_openwisp_server %} 7 | server {{ server }}; 8 | {% endfor %} 9 | } 10 | 11 | server { 12 | listen 443 ssl{% if openwisp2_nginx_spdy %} spdy{% endif %}{% if openwisp2_nginx_http2 %} http2{% endif %}; # ipv4 13 | {% if openwisp2_nginx_ipv6 %}listen [::]:443 ssl{% if openwisp2_nginx_spdy %} spdy{% endif %}{% if openwisp2_nginx_http2 %} http2{% endif %}; # ipv6{% endif %} 14 | 15 | # accepted hostnames on port 443 16 | server_name {{ inventory_hostname }}{% for host in openwisp2_allowed_hosts %} {{ host }}{% endfor %}; 17 | 18 | root {{ openwisp2_path }}/public_html; 19 | index index.html index.htm; 20 | 21 | # logging 22 | access_log {{ openwisp2_nginx_access_log }}; 23 | error_log {{ openwisp2_nginx_error_log }}; 24 | 25 | # set client body size # 26 | client_max_body_size {{ openwisp2_nginx_client_max_body_size }}; 27 | 28 | include {{ openwisp2_path }}/nginx-conf/openwisp2/ssl.conf; 29 | include {{ openwisp2_path }}/nginx-conf/openwisp2/security.conf; 30 | 31 | {% for key, value in openwisp2_nginx_ssl_config.items() %} 32 | {% if value is sequence and value is not string -%} 33 | {{ key }} {{ ' '.join(value) }}; 34 | {% else -%} 35 | {{ key }} {{ value }}; 36 | {%- endif %} 37 | {% endfor %} 38 | 39 | location @uwsgi { 40 | uwsgi_pass openwisp-server; 41 | include uwsgi_params; 42 | uwsgi_param HTTP_X_FORWARDED_PROTO https; 43 | } 44 | 45 | location / { 46 | try_files {{ openwisp2_path }}/public_html/maintenance.html $uri $uri/index.html @uwsgi; 47 | } 48 | 49 | # websockets 50 | location /ws/ { 51 | rewrite ^/(.*) /$1 break; 52 | proxy_pass http://channels-backend; 53 | 54 | proxy_http_version 1.1; 55 | proxy_set_header Upgrade $http_upgrade; 56 | proxy_set_header Connection "upgrade"; 57 | 58 | proxy_redirect off; 59 | proxy_set_header Host $http_host; 60 | proxy_set_header X-Real-IP $remote_addr; 61 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 62 | proxy_set_header X-Forwarded-Host $server_name; 63 | } 64 | 65 | {% if openwisp2_admin_allowed_network %} 66 | location /admin/ { 67 | try_files {{ openwisp2_path }}/public_html/maintenance.html $uri @uwsgi; 68 | allow {{ openwisp2_admin_allowed_network }}; 69 | deny all; 70 | } 71 | {% endif %} 72 | 73 | location /static/ { 74 | alias {{ openwisp2_path }}/static/; 75 | } 76 | 77 | location /media/ { 78 | alias {{ openwisp2_path }}/media/; 79 | } 80 | } 81 | 82 | server { 83 | listen 80; # ipv4 84 | {% if openwisp2_nginx_ipv6 %}listen [::]:80; # ipv6{% endif %} 85 | 86 | # accepted hostnames on port 80 87 | server_name {{ inventory_hostname }}{% for host in openwisp2_allowed_hosts %} {{ host }}{% endfor %}; 88 | 89 | root {{ openwisp2_path }}/public_html; 90 | 91 | # Necessary for Let's Encrypt Domain Name ownership validation 92 | location /.well-known/ { 93 | try_files $uri /dev/null =404; 94 | } 95 | 96 | {% if openwisp2_http_allowed_ip %} 97 | location @uwsgi { 98 | uwsgi_pass openwisp-server; 99 | include uwsgi_params; 100 | uwsgi_param HTTP_X_FORWARDED_PROTO http; 101 | } 102 | 103 | location / { 104 | error_page 403 = @deny; 105 | allow {{ openwisp2_http_allowed_ip }}; 106 | deny all; 107 | try_files $uri $uri/index.html @uwsgi; 108 | } 109 | 110 | location @deny { 111 | return 301 https://$host$request_uri; 112 | } 113 | 114 | location /static/ { 115 | alias {{ openwisp2_path }}/static/; 116 | } 117 | 118 | location /media/ { 119 | alias {{ openwisp2_path }}/media/; 120 | } 121 | {% else %} 122 | 123 | location / { 124 | try_files {{ openwisp2_path }}/public_html/maintenance.html $uri $uri/index.html @redirect; 125 | } 126 | 127 | # redirect all requests to https 128 | location @redirect { 129 | return 301 https://$host$request_uri; 130 | } 131 | {% endif %} 132 | 133 | } 134 | 135 | server { 136 | listen 80 default_server; 137 | {% if openwisp2_nginx_ipv6 %}listen [::]:80 default_server; # ipv6{% endif %} 138 | 139 | server_name _; 140 | 141 | return 404; 142 | } 143 | 144 | server { 145 | listen 443 ssl default_server; 146 | {% if openwisp2_nginx_ipv6 %}listen [::]:443 ssl default_server; # ipv6{% endif %} 147 | 148 | server_name _; 149 | 150 | include {{ openwisp2_path }}/nginx-conf/openwisp2/ssl.conf; 151 | include {{ openwisp2_path }}/nginx-conf/openwisp2/security.conf; 152 | 153 | return 404; 154 | } 155 | -------------------------------------------------------------------------------- /templates/nginx/ssl-conf.j2: -------------------------------------------------------------------------------- 1 | ssl_certificate {{ openwisp2_ssl_cert }}; 2 | ssl_certificate_key {{ openwisp2_ssl_key }}; 3 | ssl_protocols TLSv1.2 TLSv1.3; 4 | ssl_prefer_server_ciphers on; 5 | ssl_session_cache shared:SSL:20m; 6 | ssl_session_timeout 10m; 7 | # generated 2022-02-02, Mozilla Guideline v5.6, nginx 1.17.7, OpenSSL 1.1.1k, intermediate configuration 8 | # https://ssl-config.mozilla.org/#server=nginx&version=1.17.7&config=intermediate&openssl=1.1.1k&guideline=5.6 9 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; 10 | 11 | -------------------------------------------------------------------------------- /templates/openwisp2/__init__.py: -------------------------------------------------------------------------------- 1 | from .celery import app as celery_app 2 | from .version import __openwisp_version__, __openwisp_installation_method__ 3 | 4 | __all__ = [ 5 | 'celery_app', 6 | '__openwisp_version__', 7 | '__openwisp_installation_method__' 8 | ] 9 | -------------------------------------------------------------------------------- /templates/openwisp2/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI entrypoint. Configures Django and then runs the application 3 | defined in the ASGI_APPLICATION setting. 4 | """ 5 | 6 | import os 7 | 8 | import django 9 | from channels.routing import get_default_application 10 | 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "openwisp2.settings") 12 | django.setup() 13 | application = get_default_application() 14 | -------------------------------------------------------------------------------- /templates/openwisp2/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from celery import Celery 4 | from django.conf import settings 5 | 6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'openwisp2.settings') 7 | 8 | app = Celery('openwisp2') 9 | app.config_from_object('django.conf:settings', namespace='CELERY') 10 | app.autodiscover_tasks() 11 | 12 | {% if openwisp2_django_celery_logging %} 13 | from logging.config import dictConfig 14 | 15 | from celery.signals import setup_logging 16 | 17 | 18 | @setup_logging.connect 19 | def config_loggers(*args, **kwargs): 20 | dictConfig(settings.LOGGING) 21 | {% else %} 22 | if hasattr(settings, 'RAVEN_CONFIG'): 23 | from raven.contrib.celery import register_logger_signal, register_signal 24 | from raven.contrib.django.raven_compat.models import client 25 | register_logger_signal(client) 26 | register_signal(client, ignore_expected=True) 27 | {% endif %} 28 | -------------------------------------------------------------------------------- /templates/openwisp2/routing.py: -------------------------------------------------------------------------------- 1 | from channels.auth import AuthMiddlewareStack 2 | from channels.routing import ProtocolTypeRouter, URLRouter 3 | from channels.security.websocket import AllowedHostsOriginValidator 4 | from django.core.asgi import get_asgi_application 5 | 6 | {% for import in openwisp2_websocket_extra_imports %} 7 | {{ import }} 8 | {% endfor %} 9 | 10 | routes = [] 11 | 12 | {% if openwisp2_controller_urls %} 13 | from openwisp_controller.routing import get_routes as get_controller_routes 14 | 15 | routes.extend(get_controller_routes()) 16 | {% endif %} 17 | 18 | {% if openwisp2_network_topology %} 19 | from openwisp_network_topology.routing import \ 20 | websocket_urlpatterns as network_topology_routes 21 | 22 | routes.extend(network_topology_routes) 23 | {% endif %} 24 | 25 | {% for extra_routes in openwisp2_websocket_extra_routes %} 26 | routes.extend({{ extra_routes }}) 27 | {% endfor %} 28 | 29 | application = ProtocolTypeRouter( 30 | { 31 | 'websocket': AllowedHostsOriginValidator( 32 | AuthMiddlewareStack(URLRouter(routes)) 33 | ), 34 | 'http': get_asgi_application(), 35 | } 36 | ) 37 | -------------------------------------------------------------------------------- /templates/openwisp2/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from datetime import timedelta 4 | 5 | from celery.schedules import crontab 6 | 7 | TESTING = 'test' in sys.argv 8 | 9 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 10 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 11 | 12 | # SECURITY WARNING: keep the secret key used in production secret! 13 | SECRET_KEY = '{{ openwisp2_secret_key }}' 14 | 15 | # SECURITY WARNING: don't run with debug turned on in production! 16 | DEBUG = False 17 | 18 | ALLOWED_HOSTS = [ 19 | '{{ inventory_hostname }}', 20 | {% for host in openwisp2_allowed_hosts %} 21 | '{{ host }}', 22 | {% endfor %} 23 | ] 24 | 25 | # Application definition 26 | 27 | INSTALLED_APPS = [ 28 | 'django.contrib.auth', 29 | 'django.contrib.contenttypes', 30 | 'django.contrib.sessions', 31 | 'django.contrib.messages', 32 | 'django.contrib.staticfiles', 33 | 'django.contrib.humanize', 34 | 'django.contrib.gis', 35 | # all-auth 36 | 'django.contrib.sites', 37 | # overrides allauth templates 38 | # must precede allauth 39 | 'openwisp_users.accounts', 40 | 'allauth', 41 | 'allauth.account', 42 | 'allauth.socialaccount', 43 | 'django_extensions', 44 | # openwisp2 modules 45 | 'openwisp_users', 46 | 'openwisp_controller.pki', 47 | 'openwisp_controller.config', 48 | 'openwisp_controller.geo', 49 | 'openwisp_controller.connection', 50 | {% if openwisp2_controller_subnet_division %} 51 | 'openwisp_controller.subnet_division', 52 | {% endif %} 53 | {% if openwisp2_monitoring %} 54 | 'openwisp_monitoring.monitoring', 55 | 'openwisp_monitoring.device', 56 | 'openwisp_monitoring.check', 57 | 'nested_admin', 58 | {% endif %} 59 | 'openwisp_notifications', 60 | 'flat_json_widget', 61 | {% if openwisp2_network_topology %} 62 | 'openwisp_network_topology', 63 | {% endif %} 64 | {% if openwisp2_firmware_upgrader %} 65 | 'openwisp_firmware_upgrader', 66 | {% endif %} 67 | 'openwisp_ipam', 68 | {% if openwisp2_radius %} 69 | 'dj_rest_auth', 70 | 'dj_rest_auth.registration', 71 | 'openwisp_radius', 72 | {% endif %} 73 | # openwisp2 admin theme 74 | # (must be loaded here) 75 | 'openwisp_utils.admin_theme', 76 | {% if openwisp2_usage_metric_collection is not false %} 77 | 'openwisp_utils.metric_collection', 78 | {% endif %} 79 | 'admin_auto_filters', 80 | # admin 81 | 'django.contrib.admin', 82 | 'django.forms', 83 | # other dependencies 84 | 'sortedm2m', 85 | 'reversion', 86 | 'leaflet', 87 | 'rest_framework', 88 | 'rest_framework_gis', 89 | 'rest_framework.authtoken', 90 | 'django_filters', 91 | {% if openwisp2_firmware_upgrader or openwisp2_radius %} 92 | 'private_storage', 93 | {% endif %} 94 | 'drf_yasg', 95 | 'channels', 96 | 'pipeline', 97 | 'import_export', 98 | {% for app in openwisp2_extra_django_apps %} 99 | '{{ app }}', 100 | {% endfor %} 101 | {% if openwisp2_sentry.get('dsn') %} 102 | 'raven.contrib.django.raven_compat', 103 | {% endif %} 104 | {% if openwisp2_email_backend == "djcelery_email.backends.CeleryEmailBackend"%} 105 | 'djcelery_email', 106 | {% endif %} 107 | ] 108 | 109 | EXTENDED_APPS = [ 110 | 'django_x509', 111 | 'django_loci', 112 | ] 113 | 114 | {% if openwisp2_firmware_upgrader or openwisp2_radius %} 115 | PRIVATE_STORAGE_ROOT = os.path.join(BASE_DIR, 'private') 116 | {% endif %} 117 | 118 | {% if openwisp2_firmware_upgrader %} 119 | OPENWISP_FIRMWARE_UPGRADER_MAX_FILE_SIZE = {{ openwisp2_firmware_upgrader_max_file_size }} 120 | {% endif %} 121 | 122 | AUTH_USER_MODEL = 'openwisp_users.User' 123 | SITE_ID = 1 124 | LOGIN_REDIRECT_URL = 'admin:index' 125 | ACCOUNT_LOGOUT_REDIRECT_URL = LOGIN_REDIRECT_URL 126 | ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = 'email_confirmation_success' 127 | ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = 'email_confirmation_success' 128 | 129 | STATICFILES_FINDERS = [ 130 | 'django.contrib.staticfiles.finders.FileSystemFinder', 131 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 132 | 'openwisp_utils.staticfiles.DependencyFinder', 133 | ] 134 | 135 | MIDDLEWARE = [ 136 | 'django.middleware.security.SecurityMiddleware', 137 | 'django.contrib.sessions.middleware.SessionMiddleware', 138 | {% if openwisp2_internationalization %} 139 | 'django.middleware.locale.LocaleMiddleware', 140 | {% endif %} 141 | 'django.middleware.common.CommonMiddleware', 142 | 'django.middleware.csrf.CsrfViewMiddleware', 143 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 144 | {% if openwisp2_radius %} 145 | 'sesame.middleware.AuthenticationMiddleware', 146 | {% endif %} 147 | 'django.contrib.messages.middleware.MessageMiddleware', 148 | 'allauth.account.middleware.AccountMiddleware', 149 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 150 | {% if openwisp2_users_user_password_expiration or openwisp2_users_staff_user_password_expiration %} 151 | 'openwisp_users.middleware.PasswordExpirationMiddleware', 152 | {% endif %} 153 | 'pipeline.middleware.MinifyHTMLMiddleware' 154 | ] 155 | 156 | AUTHENTICATION_BACKENDS = [ 157 | 'openwisp_users.backends.UsersAuthenticationBackend', 158 | ] 159 | 160 | {% if openwisp2_radius %} 161 | OPENWISP_RADIUS_FREERADIUS_ALLOWED_HOSTS = {{ openwisp2_radius_allowed_hosts }} 162 | REST_AUTH = { 163 | 'SESSION_LOGIN': False, 164 | 'PASSWORD_RESET_SERIALIZER': 'openwisp_radius.api.serializers.PasswordResetSerializer', 165 | 'REGISTER_SERIALIZER': 'openwisp_radius.api.serializers.RegisterSerializer', 166 | } 167 | 168 | # dj-rest-auth 3.0 changed the configuration settings. 169 | # The below settings are kept for backward compatability with dj-rest-auth < 3.0 170 | # 171 | # Backward compatible settings begins 172 | REST_AUTH_SERIALIZERS = { 173 | 'PASSWORD_RESET_SERIALIZER': 'openwisp_radius.api.serializers.PasswordResetSerializer', 174 | } 175 | REST_AUTH_REGISTER_SERIALIZERS = { 176 | 'REGISTER_SERIALIZER': 'openwisp_radius.api.serializers.RegisterSerializer', 177 | } 178 | # Backward compatible settings ends 179 | 180 | # SMS settings 181 | OPENWISP_RADIUS_SMS_TOKEN_MAX_IP_DAILY = {{ openwisp2_radius_sms_token_max_ip_daily }} 182 | {% if openwisp2_radius_unverify_inactive_users %} 183 | OPENWISP_RADIUS_UNVERIFY_INACTIVE_USERS = {{ openwisp2_radius_unverify_inactive_users }} 184 | {% endif %} 185 | {% if openwisp2_radius_delete_inactive_users %} 186 | OPENWISP_RADIUS_DELETE_INACTIVE_USERS = {{ openwisp2_radius_delete_inactive_users }} 187 | {% endif %} 188 | SENDSMS_BACKEND = '{{ openwisp2_radius_sms_backend }}' 189 | 190 | # django-sesame configuration for magic sign-in links. 191 | # Refer https://github.com/aaugustin/django-sesame#django-sesame. 192 | AUTHENTICATION_BACKENDS += [ 193 | 'sesame.backends.ModelBackend', 194 | ] 195 | SESAME_MAX_AGE = {{ openwisp2_django_sesame_max_age }} 196 | {% endif %} 197 | 198 | ROOT_URLCONF = 'openwisp2.urls' 199 | OPENWISP_USERS_AUTH_API = {{ openwisp2_users_auth_api }} 200 | {% if openwisp2_users_user_password_expiration %} 201 | OPENWISP_USERS_USER_PASSWORD_EXPIRATION = {{ openwisp2_users_user_password_expiration }} 202 | {% endif %} 203 | {% if openwisp2_users_staff_user_password_expiration %} 204 | OPENWISP_USERS_STAFF_USER_PASSWORD_EXPIRATION = {{ openwisp2_users_staff_user_password_expiration }} 205 | {% endif %} 206 | 207 | 208 | CHANNEL_LAYERS = { 209 | 'default': { 210 | 'BACKEND': 'channels_redis.core.RedisChannelLayer', 211 | 'CONFIG': { 212 | 'hosts': [('{{ openwisp2_redis_host }}', {{ openwisp2_redis_port }})], 213 | 'group_expiry': {{ openwisp2_daphne_websocket_timeout }}, 214 | }, 215 | }, 216 | } 217 | ASGI_APPLICATION = 'openwisp2.routing.application' 218 | 219 | TEMPLATES = [ 220 | { 221 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 222 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 223 | 'OPTIONS': { 224 | 'loaders': [ 225 | ('django.template.loaders.cached.Loader', [ 226 | 'django.template.loaders.filesystem.Loader', 227 | 'django.template.loaders.app_directories.Loader', 228 | 'openwisp_utils.loaders.DependencyLoader' 229 | ]), 230 | ], 231 | 'context_processors': [ 232 | 'django.template.context_processors.debug', 233 | 'django.template.context_processors.request', 234 | 'django.contrib.auth.context_processors.auth', 235 | 'django.contrib.messages.context_processors.messages', 236 | 'openwisp_utils.admin_theme.context_processor.menu_items', 237 | 'openwisp_utils.admin_theme.context_processor.admin_theme_settings', 238 | 'openwisp_notifications.context_processors.notification_api_settings', 239 | ], 240 | }, 241 | }, 242 | ] 243 | 244 | # Run celery in eager mode using in-memory broker while running tests 245 | if not TESTING: 246 | CELERY_TASK_ACKS_LATE = {{ openwisp2_celery_task_acks_late }} 247 | CELERY_BROKER_URL = '{{ openwisp2_celery_broker_url }}' 248 | else: 249 | CELERY_TASK_ALWAYS_EAGER = True 250 | CELERY_TASK_EAGER_PROPAGATES = True 251 | CELERY_BROKER_URL = 'memory://' 252 | 253 | # Workaround for stalled migrate command 254 | CELERY_BROKER_TRANSPORT_OPTIONS = { 255 | 'max_retries': {{ openwisp2_celery_broker_max_tries }}, 256 | } 257 | 258 | CELERY_BEAT_SCHEDULE = { 259 | {% if openwisp2_users_user_password_expiration or openwisp2_users_staff_user_password_expiration %} 260 | 'password_expiry_email': { 261 | 'task': 'openwisp_users.tasks.password_expiration_email', 262 | 'schedule': crontab(**{ {{ cron_password_expiration_email }} }), 263 | }, 264 | {% endif %} 265 | {% if openwisp2_notifications_delete_old_notifications %} 266 | 'delete_old_notifications': { 267 | 'task': 'openwisp_notifications.tasks.delete_old_notifications', 268 | 'schedule': crontab(**{ {{ cron_delete_old_notifications }} }), 269 | 'args': ({{ openwisp2_notifications_delete_old_notifications }},), 270 | }, 271 | {% endif %} 272 | {% if openwisp2_monitoring and openwisp2_monitoring_periodic_tasks %} 273 | 'run_checks': { 274 | 'task': 'openwisp_monitoring.check.tasks.run_checks', 275 | 'schedule': timedelta(minutes=5), 276 | }, 277 | {% endif %} 278 | {% if openwisp2_radius and openwisp2_radius_periodic_tasks %} 279 | 'deactivate_expired_users': { 280 | 'task': 'openwisp_radius.tasks.deactivate_expired_users', 281 | 'schedule': crontab(**{ {{ cron_deactivate_expired_users }} }), 282 | 'args': None, 283 | 'relative': True, 284 | }, 285 | 'delete_old_radiusbatch_users': { 286 | 'task': 'openwisp_radius.tasks.delete_old_radiusbatch_users', 287 | 'schedule': crontab(**{ {{ cron_delete_old_radiusbatch_users }} }), 288 | 'kwargs': {'older_than_days': {{ openwisp2_radius_delete_old_radiusbatch_users }}}, 289 | 'relative': True, 290 | }, 291 | 'cleanup_stale_radacct': { 292 | 'task': 'openwisp_radius.tasks.cleanup_stale_radacct', 293 | 'schedule': crontab(**{ {{ cron_cleanup_stale_radacct }} }), 294 | 'args': [{{ openwisp2_radius_cleanup_stale_radacct }}], 295 | 'relative': True, 296 | }, 297 | 'delete_old_postauth': { 298 | 'task': 'openwisp_radius.tasks.delete_old_postauth', 299 | 'schedule': crontab(**{ {{ cron_delete_old_postauth }} }), 300 | 'args': [{{ openwisp2_radius_delete_old_postauth }}], 301 | 'relative': True, 302 | }, 303 | {% if openwisp2_radius_delete_old_radacct %} 304 | 'delete_old_radacct': { 305 | 'task': 'openwisp_radius.tasks.delete_old_radacct', 306 | 'schedule': crontab(**{ {{ cron_delete_old_radacct }} }), 307 | 'args': [{{ openwisp2_radius_delete_old_radacct }}], 308 | 'relative': True, 309 | }, 310 | {% endif %} 311 | {% if openwisp2_radius_unverify_inactive_users %} 312 | 'unverify_inactive_users': { 313 | 'task': 'openwisp_radius.tasks.unverify_inactive_users', 314 | 'schedule': crontab(**{ {{ cron_unverify_inactive_users }} }), 315 | 'relative': True, 316 | }, 317 | {% endif %} 318 | {% if openwisp2_radius_delete_inactive_users %} 319 | 'delete_inactive_users': { 320 | 'task': 'openwisp_radius.tasks.delete_inactive_users', 321 | 'schedule': crontab(**{ {{ cron_delete_inactive_users }} }), 322 | 'relative': True, 323 | }, 324 | {% endif %} 325 | {% endif %} 326 | {% if openwisp2_usage_metric_collection is not false and openwisp2_usage_metric_collection_periodic_tasks %} 327 | 'send_usage_metrics': { 328 | 'task': 'openwisp_utils.metric_collection.tasks.send_usage_metrics', 329 | 'schedule': timedelta(days=1), 330 | }, 331 | {% endif %} 332 | } 333 | 334 | {% if openwisp2_celery_task_routes_defaults %} 335 | CELERY_TASK_ROUTES = { 336 | {% if openwisp2_celery_network %} 337 | # network operations, executed in the "network" queue 338 | 'openwisp_controller.connection.tasks.*': {'queue': 'network'}, 339 | {% endif %} 340 | {% if openwisp2_monitoring and openwisp2_celery_monitoring %} 341 | # monitoring checks are executed in a dedicated "monitoring" queue 342 | 'openwisp_monitoring.check.tasks.perform_check': {'queue': 'monitoring'}, 343 | 'openwisp_monitoring.monitoring.tasks.migrate_timeseries_database': {'queue': 'monitoring'}, 344 | {% endif %} 345 | {% if openwisp2_firmware_upgrader and openwisp2_celery_firmware_upgrader %} 346 | # firmware upgrade operations, executed in the "firmware_upgrader" queue 347 | 'openwisp_firmware_upgrader.tasks.upgrade_firmware': {'queue': 'firmware_upgrader'}, 348 | 'openwisp_firmware_upgrader.tasks.batch_upgrade_operation': {'queue': 'firmware_upgrader'}, 349 | {% endif %} 350 | # all other tasks are routed to the default queue (named "celery") 351 | } 352 | {% endif %} 353 | 354 | # FOR DJANGO REDIS 355 | 356 | CACHES = { 357 | 'default': { 358 | 'BACKEND': 'django_redis.cache.RedisCache', 359 | 'LOCATION': '{{ openwisp2_redis_cache_url }}', 360 | 'OPTIONS': { 361 | 'CLIENT_CLASS': 'django_redis.client.DefaultClient', 362 | } 363 | } 364 | } 365 | 366 | SESSION_ENGINE = 'django.contrib.sessions.backends.cache' 367 | SESSION_CACHE_ALIAS = 'default' 368 | SESSION_COOKIE_SECURE = True 369 | CSRF_COOKIE_SECURE = True 370 | 371 | FORM_RENDERER = 'django.forms.renderers.TemplatesSetting' 372 | 373 | WSGI_APPLICATION = 'openwisp2.wsgi.application' 374 | 375 | # Database 376 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases 377 | 378 | DATABASES = { 379 | 'default': { 380 | 'ENGINE': '{{ openwisp2_database.engine }}', 381 | 'NAME': '{{ openwisp2_database.name }}', 382 | {% if openwisp2_database.user is defined and openwisp2_database.user%} 383 | 'USER': '{{ openwisp2_database.user }}', 384 | {% endif %} 385 | {% if openwisp2_database.password is defined and openwisp2_database.password %} 386 | 'PASSWORD': '{{ openwisp2_database.password }}', 387 | {% endif %} 388 | {% if openwisp2_database.host is defined and openwisp2_database.host %} 389 | 'HOST': '{{ openwisp2_database.host }}', 390 | {% endif %} 391 | {% if openwisp2_database.port is defined and openwisp2_database.port %} 392 | 'PORT': '{{ openwisp2_database.port }}', 393 | {% endif %} 394 | {% if openwisp2_database.options is defined and openwisp2_database.options %} 395 | 'OPTIONS': {{ openwisp2_database.options|to_nice_json }} 396 | {% endif %} 397 | } 398 | } 399 | 400 | {% if openwisp2_spatialite_path %} 401 | SPATIALITE_LIBRARY_PATH = '{{ openwisp2_spatialite_path }}' 402 | {% endif %} 403 | 404 | # Password validation 405 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators 406 | 407 | AUTH_PASSWORD_VALIDATORS = [ 408 | {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'}, 409 | {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'}, 410 | {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'}, 411 | {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'}, 412 | {'NAME': 'openwisp_users.password_validation.PasswordReuseValidator'} 413 | ] 414 | 415 | # Internationalization 416 | # https://docs.djangoproject.com/en/1.9/topics/i18n/ 417 | 418 | LANGUAGE_CODE = '{{ openwisp2_language_code }}' 419 | TIME_ZONE = '{{ openwisp2_time_zone }}' 420 | {% if openwisp2_internationalization %} 421 | USE_I18N = True 422 | {% endif %} 423 | USE_TZ = True 424 | 425 | # Static files (CSS, JavaScript, Images) 426 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 427 | 428 | STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static_custom')] 429 | STATIC_ROOT = os.path.join(BASE_DIR, 'static') 430 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 431 | STATIC_URL = '/static/' 432 | MEDIA_URL = '/media/' 433 | 434 | {% if openwisp2_context %} 435 | NETJSONCONFIG_CONTEXT = {{ openwisp2_context|to_nice_json }} 436 | {% endif %} 437 | 438 | # django x509 settings 439 | DJANGO_X509_DEFAULT_CERT_VALIDITY = {{ openwisp2_default_cert_validity }} 440 | DJANGO_X509_DEFAULT_CA_VALIDITY = {{ openwisp2_default_ca_validity }} 441 | 442 | {% if openwisp2_leaflet_config %} 443 | LEAFLET_CONFIG = {{ openwisp2_leaflet_config|to_nice_json }} 444 | {% else %} 445 | LEAFLET_CONFIG = {} 446 | {% endif %} 447 | # always disable RESET_VIEW button 448 | LEAFLET_CONFIG['RESET_VIEW'] = False 449 | 450 | # Set default email 451 | DEFAULT_FROM_EMAIL = '{{ openwisp2_default_from_email }}' 452 | EMAIL_BACKEND = '{{ openwisp2_email_backend }}' 453 | EMAIL_TIMEOUT = {{ openwisp2_email_timeout }} 454 | # See http://docs.djangoproject.com/en/dev/topics/logging for 455 | # more details on how to customize your logging configuration. 456 | LOGGING = { 457 | 'version': 1, 458 | 'disable_existing_loggers': False, 459 | 'filters': { 460 | 'require_debug_false': { 461 | '()': 'django.utils.log.RequireDebugFalse', 462 | }, 463 | 'require_debug_true': { 464 | '()': 'django.utils.log.RequireDebugTrue', 465 | }, 466 | }, 467 | 'formatters': { 468 | 'simple': { 469 | 'format': '[%(levelname)s] %(message)s' 470 | }, 471 | 'verbose': { 472 | 'format': '[%(levelname)s %(asctime)s] module: %(module)s, process: %(process)d, thread: %(thread)d\n%(message)s\n' 473 | }, 474 | }, 475 | 'handlers': { 476 | 'console': { 477 | 'level': 'DEBUG', 478 | 'class': 'logging.StreamHandler', 479 | 'filters': ['require_debug_true'], 480 | 'formatter': 'simple' 481 | }, 482 | 'mail_admins': { 483 | 'level': 'ERROR', 484 | 'filters': ['require_debug_false'], 485 | 'class': 'django.utils.log.AdminEmailHandler' 486 | }, 487 | 'main_log': { 488 | 'level': 'INFO', 489 | 'class': 'logging.handlers.RotatingFileHandler', 490 | 'filename': os.path.join(BASE_DIR, 'log/openwisp2.log'), 491 | 'maxBytes': 15728640, 492 | 'backupCount': 3, 493 | 'formatter': 'verbose' 494 | }, 495 | 'null': { 496 | 'level': 'DEBUG', 497 | 'class': 'logging.NullHandler', 498 | }, 499 | {% if openwisp2_sentry.get('dsn') %} 500 | 'sentry': { 501 | 'level': 'ERROR', 502 | 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', 503 | 'filters': ['require_debug_false'] 504 | }, 505 | {% endif %} 506 | }, 507 | 'root': { 508 | 'level': 'INFO', 509 | 'handlers': [ 510 | 'main_log', 511 | 'console', 512 | 'mail_admins', 513 | {% if openwisp2_sentry.get('dsn') %} 514 | 'sentry' 515 | {% endif %} 516 | ] 517 | }, 518 | 'loggers': { 519 | 'django.security.DisallowedHost': { 520 | 'handlers': ['main_log'], 521 | 'propagate': False, 522 | } 523 | } 524 | } 525 | 526 | # HTML minification with django pipeline 527 | PIPELINE = {'PIPELINE_ENABLED': True} 528 | # static files minification and invalidation with django-compress-staticfiles 529 | STORAGES = { 530 | 'staticfiles': { 531 | 'BACKEND': 'openwisp_utils.storage.CompressStaticFilesStorage', 532 | }, 533 | } 534 | # GZIP compression is handled by nginx 535 | BROTLI_STATIC_COMPRESSION = False 536 | GZIP_STATIC_COMPRESSION = False 537 | 538 | {% if openwisp2_sentry.get('dsn') %} 539 | RAVEN_CONFIG = {{ openwisp2_sentry|to_nice_json }} 540 | {% endif %} 541 | 542 | {% if openwisp2_monitoring %} 543 | TIMESERIES_DATABASE = { 544 | 'BACKEND': '{{ openwisp2_timeseries_database.backend }}', 545 | 'USER': '{{ openwisp2_timeseries_database.user }}', 546 | 'PASSWORD': '{{ openwisp2_timeseries_database.password }}', 547 | 'NAME': '{{ openwisp2_timeseries_database.name }}', 548 | 'HOST': '{{ openwisp2_timeseries_database.host }}', 549 | 'PORT': '{{ openwisp2_timeseries_database.port }}', 550 | } 551 | OPENWISP_MONITORING_DEFAULT_RETENTION_POLICY = '{{ openwisp2_monitoring_default_retention_policy }}' 552 | {% endif %} 553 | 554 | {% for setting, value in openwisp2_extra_django_settings.items() %} 555 | {{ setting }} = {% if value is string %}'{{ value }}'{% else %}{{ value }}{% endif %} 556 | 557 | {% endfor %} 558 | 559 | {% for instruction in openwisp2_extra_django_settings_instructions %} 560 | {{ instruction }} 561 | 562 | {% endfor %} 563 | 564 | {% if openwisp2_django_cors.enabled %} 565 | # CORS configuration 566 | INSTALLED_APPS.append('corsheaders') 567 | MIDDLEWARE.insert(MIDDLEWARE.index('django.middleware.common.CommonMiddleware'), 'corsheaders.middleware.CorsMiddleware') 568 | CORS_ALLOWED_ORIGINS = {{ openwisp2_django_cors.get('allowed_origins_list', []) }} 569 | {% endif %} 570 | 571 | TEST_RUNNER = 'openwisp_utils.metric_collection.tests.runner.MockRequestPostRunner' 572 | -------------------------------------------------------------------------------- /templates/openwisp2/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 3 | from django.urls import include, path, reverse_lazy 4 | from django.views.generic import RedirectView 5 | 6 | {% if openwisp2_firmware_upgrader %} 7 | # When using S3_REVERSE_PROXY feature of django-private-storage, 8 | # the storage backend reverse the "serve_private_file" URL 9 | # pattern in order to proxy the file with the correct URL. 10 | from openwisp_firmware_upgrader.private_storage.urls import \ 11 | urlpatterns as fw_private_storage_urls 12 | 13 | {% endif %} 14 | 15 | redirect_view = RedirectView.as_view(url=reverse_lazy('admin:index')) 16 | 17 | urlpatterns = [ 18 | path('admin/', admin.site.urls), 19 | {% if openwisp2_controller_urls %} 20 | path('', include('openwisp_controller.urls')), 21 | {% endif %} 22 | path('api/v1/', include('openwisp_utils.api.urls')), 23 | path('api/v1/', include('openwisp_users.api.urls')), 24 | {% if openwisp2_network_topology %} 25 | path('', include('openwisp_network_topology.urls')), 26 | {% endif %} 27 | {% if openwisp2_firmware_upgrader %} 28 | path('', include('openwisp_firmware_upgrader.urls')), 29 | path( 30 | '', 31 | include((fw_private_storage_urls, 'firmware'), namespace='firmware'), 32 | ), 33 | {% endif %} 34 | {% if openwisp2_monitoring %} 35 | path('', include('openwisp_monitoring.urls')), 36 | {% endif %} 37 | {% if openwisp2_radius and openwisp2_radius_urls %} 38 | path('', include('openwisp_radius.urls')), 39 | {% endif %} 40 | {% for extra_url in openwisp2_extra_urls %} 41 | {{ extra_url }}, 42 | {% endfor %} 43 | path('', redirect_view, name='index'), 44 | ] 45 | 46 | urlpatterns += staticfiles_urlpatterns() 47 | -------------------------------------------------------------------------------- /templates/openwisp2/version.py: -------------------------------------------------------------------------------- 1 | __openwisp_version__ = '25.07.0a' 2 | __openwisp_installation_method__ = ( 3 | '{{ openwisp2_installation_method | default("ansible-openwisp2") }}' 4 | ) 5 | -------------------------------------------------------------------------------- /templates/openwisp2/wsgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.wsgi import get_wsgi_application 4 | 5 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "openwisp2.settings") 6 | 7 | application = get_wsgi_application() 8 | -------------------------------------------------------------------------------- /templates/supervisor/celery.j2: -------------------------------------------------------------------------------- 1 | [program:celery] 2 | user={{ www_user }} 3 | directory={{ openwisp2_path }} 4 | command={{ openwisp2_path }}/env/bin/celery -A openwisp2 worker -l info --queues celery -O {{ openwisp2_celery_optimization }}{% if openwisp2_celery_concurrency %} --concurrency={{ openwisp2_celery_concurrency }}{% elif openwisp2_celery_autoscale %} --autoscale {{ openwisp2_celery_autoscale }}{% endif %}{% if openwisp2_celery_prefetch_multiplier %} --prefetch-multiplier {{ openwisp2_celery_prefetch_multiplier }}{% endif %} -n background@%%h 5 | autostart=true 6 | autorestart=true 7 | stopsignal=INT 8 | redirect_stderr=true 9 | stdout_logfile={{ openwisp2_path }}/log/celery.log 10 | stdout_logfile_maxbytes=30MB 11 | stdout_logfile_backups=5 12 | -------------------------------------------------------------------------------- /templates/supervisor/celery_firmware_upgrader.j2: -------------------------------------------------------------------------------- 1 | [program:celery_firmware_upgrader] 2 | user={{ www_user }} 3 | directory={{ openwisp2_path }} 4 | command={{ openwisp2_path }}/env/bin/celery -A openwisp2 worker -l info --queues firmware_upgrader -O {{ openwisp2_celery_firmware_upgrader_optimization }}{% if openwisp2_celery_firmware_upgrader_concurrency %} --concurrency={{ openwisp2_celery_firmware_upgrader_concurrency }}{% elif openwisp2_celery_firmware_upgrader_autoscale %} --autoscale {{ openwisp2_celery_firmware_upgrader_autoscale }}{% endif %}{% if openwisp2_celery_firmware_upgrader_prefetch_multiplier %} --prefetch-multiplier {{ openwisp2_celery_firmware_upgrader_prefetch_multiplier }}{% endif %} -n firmware_upgrader@%%h 5 | autostart=true 6 | autorestart=true 7 | stopsignal=INT 8 | redirect_stderr=true 9 | stdout_logfile={{ openwisp2_path }}/log/celery-firmware-upgrader.log 10 | stdout_logfile_maxbytes=30MB 11 | stdout_logfile_backups=5 12 | -------------------------------------------------------------------------------- /templates/supervisor/celery_monitoring.j2: -------------------------------------------------------------------------------- 1 | [program:celery_monitoring] 2 | user={{ www_user }} 3 | directory={{ openwisp2_path }} 4 | command={{ openwisp2_path }}/env/bin/celery -A openwisp2 worker -l info --queues monitoring -O {{ openwisp2_celery_monitoring_optimization }}{% if openwisp2_celery_monitoring_concurrency %} --concurrency={{ openwisp2_celery_monitoring_concurrency }}{% elif openwisp2_celery_monitoring_autoscale %} --autoscale {{ openwisp2_celery_monitoring_autoscale }}{% endif %}{% if openwisp2_celery_monitoring_prefetch_multiplier %} --prefetch-multiplier {{ openwisp2_celery_monitoring_prefetch_multiplier }}{% endif %} -n monitoring@%%h 5 | autostart=true 6 | autorestart=true 7 | stopsignal=INT 8 | redirect_stderr=true 9 | stdout_logfile={{ openwisp2_path }}/log/celery-monitoring.log 10 | stdout_logfile_maxbytes=30MB 11 | stdout_logfile_backups=5 12 | -------------------------------------------------------------------------------- /templates/supervisor/celery_network.j2: -------------------------------------------------------------------------------- 1 | [program:celery_network] 2 | user={{ www_user }} 3 | directory={{ openwisp2_path }} 4 | command={{ openwisp2_path }}/env/bin/celery -A openwisp2 worker -l info --queues network -O {{ openwisp2_celery_network_optimization }}{% if openwisp2_celery_network_concurrency %} --concurrency={{ openwisp2_celery_network_concurrency }}{% elif openwisp2_celery_network_autoscale %} --autoscale {{ openwisp2_celery_network_autoscale }}{% endif %}{% if openwisp2_celery_network_prefetch_multiplier %} --prefetch-multiplier {{ openwisp2_celery_network_prefetch_multiplier }}{% endif %} -n network@%%h 5 | autostart=true 6 | autorestart=true 7 | stopsignal=INT 8 | redirect_stderr=true 9 | stdout_logfile={{ openwisp2_path }}/log/celery-network.log 10 | stdout_logfile_maxbytes=30MB 11 | stdout_logfile_backups=5 12 | -------------------------------------------------------------------------------- /templates/supervisor/celerybeat.j2: -------------------------------------------------------------------------------- 1 | [program:celerybeat] 2 | user={{ www_user }} 3 | directory={{ openwisp2_path }} 4 | command={{ openwisp2_path }}/env/bin/celery -A openwisp2 beat -l info --schedule {{ openwisp2_path }}/celerybeat-schedule.db 5 | autostart=true 6 | autorestart=true 7 | stopsignal=INT 8 | redirect_stderr=true 9 | stdout_logfile={{ openwisp2_path }}/log/celerybeat.log 10 | stdout_logfile_maxbytes=30MB 11 | stdout_logfile_backups=5 12 | -------------------------------------------------------------------------------- /templates/supervisor/daphne.j2: -------------------------------------------------------------------------------- 1 | [fcgi-program:daphne] 2 | user={{ www_user }} 3 | socket=unix://{{ openwisp2_path }}/daphne0.sock 4 | directory={{ openwisp2_path }} 5 | command={{ openwisp2_path }}/env/bin/daphne --fd 0 -u {{ openwisp2_path }}/daphne%(process_num)d.sock --access-log - --websocket_timeout {{ openwisp2_daphne_websocket_timeout }} --proxy-headers openwisp2.asgi:application 6 | process_name=asgi%(process_num)d 7 | numprocs={{ openwisp2_daphne_processes }} 8 | autostart=true 9 | autorestart=true 10 | stopsignal=INT 11 | redirect_stderr=true 12 | stdout_logfile={{ openwisp2_path }}/log/daphne.log 13 | stdout_logfile_maxbytes=30MB 14 | stdout_logfile_backups=5 15 | -------------------------------------------------------------------------------- /templates/supervisor/openwisp2.j2: -------------------------------------------------------------------------------- 1 | [program:openwisp2] 2 | user={{ www_user }} 3 | directory={{ openwisp2_path }} 4 | command={{ openwisp2_path }}/env/bin/uwsgi --ini uwsgi.ini 5 | autostart=true 6 | autorestart=true 7 | stopsignal=INT 8 | redirect_stderr=true 9 | stdout_logfile={{ openwisp2_path }}/log/uwsgi.log 10 | stdout_logfile_maxbytes=30MB 11 | stdout_logfile_backups=5 12 | -------------------------------------------------------------------------------- /templates/uwsgi.ini.j2: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | chdir={{ openwisp2_path }} 3 | uid=www-data 4 | module=openwisp2.wsgi:application 5 | master=True 6 | pidfile={{ openwisp2_path }}/uwsgi.pid 7 | socket={{ openwisp2_uwsgi_socket }} 8 | processes={{ openwisp2_uwsgi_processes }} 9 | threads={{ openwisp2_uwsgi_threads }} 10 | listen={{ openwisp2_uwsgi_listen }} 11 | harakiri=20 12 | max-requests=5000 13 | vacuum=True 14 | enable-threads=True 15 | env=HTTPS=on 16 | buffer-size=8192 17 | {% if openwisp2_uwsgi_gid %} 18 | gid={{ openwisp2_uwsgi_gid }} 19 | {% endif %} 20 | ignore-sigpipe 21 | ignore-write-errors 22 | disable-write-exception 23 | {% if openwisp2_uwsgi_extra_conf %} 24 | {{ openwisp2_uwsgi_extra_conf }} 25 | {% endif %} 26 | -------------------------------------------------------------------------------- /vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | virtualenv_path: "{{ openwisp2_path }}/env" 4 | supervisor_path: "/etc/supervisor/conf.d/%s.conf" 5 | www_group: "www-data" 6 | www_user: "www-data" 7 | openwisp2_default_supervisor_restart: 8 | - name: celery 9 | when: true 10 | - name: celerybeat 11 | when: "{{ openwisp2_celerybeat }}" 12 | - name: celery_network 13 | when: "{{ openwisp2_celery_network }}" 14 | - name: celery_firmware_upgrader 15 | when: "{{ openwisp2_firmware_upgrader and openwisp2_celery_firmware_upgrader }}" 16 | - name: celery_monitoring 17 | when: "{{ openwisp2_monitoring and openwisp2_celery_monitoring }}" 18 | --------------------------------------------------------------------------------