Merge pull request #121 from Southampton-RSG/dev

Dev
This commit is contained in:
James Graham
2022-03-31 19:51:39 +01:00
committed by GitHub
31 changed files with 472 additions and 522 deletions

18
.dockerignore Normal file
View File

@@ -0,0 +1,18 @@
.dbbackup/
.idea/
.mypy_cache/
.vagrant/
.venv/
venv/
.vscode/
Caddyfile
docker-compose.yml
.env
settings.ini
mail.log/
/roles/
/static/
*.sqlite3*
*.log*
deployment*

2
.gitignore vendored
View File

@@ -15,11 +15,13 @@ db.sqlite3
debug.log* debug.log*
# Configuration # Configuration
.env
settings.ini settings.ini
deployment-key* deployment-key*
# Deployment # Deployment
/.dbbackup/ /.dbbackup/
.vagrant/ .vagrant/
/custom
staging.yml staging.yml
production.yml production.yml

15
Caddyfile Normal file
View File

@@ -0,0 +1,15 @@
:80 :443 {
root * /srv
file_server
@proxy_paths {
not path /static/*
}
reverse_proxy @proxy_paths http://web:8000
log {
output stderr
format single_field common_log
}
}

17
Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
FROM python:3.8-slim
RUN groupadd -r mapper && useradd --no-log-init -r -g mapper mapper
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir -r /app/requirements.txt gunicorn
COPY . ./
# USER mapper
ENTRYPOINT [ "/app/entrypoint.sh" ]
CMD [ "gunicorn", "-w", "2", "-b", "0.0.0.0:8000", "breccia_mapper.wsgi" ]

View File

@@ -1,19 +1,28 @@
# BRECcIA-Mapper (provisional name) # BRECcIA Mapper
The canonical source for this project is hosted on [GitHub](https://github.com/Southampton-RSG/breccia-mapper), BRECcIA Mapper is a web app to collect and explore data about the relationships between researchers and their stakeholders on large-scale, multi-site research projects.
please log any issues there. This allows researchers to visually represent the relationships between project staff and stakeholders involved in the their project at different points in time.
Through this it is possible to explore the extent of networks and change over time, and identify where new relationships can be developed or existing ones strengthened.
BRECcIA-Mapper is a web app to collect and explore data about the relationships between researchers and their stakeholders on large-scale, multi-site research projects. This work was funded through the "Building REsearch Capacity for sustainable water and food security In drylands of sub-saharan Africa" (BRECcIA) project which is supported by UK Research and Innovation as part of the Global Challenges Research Fund, grant number NE/P021093/1.
TODO motivations, usage, license ## Deployment
## Technology
This project is written in Python using the popular [Django](https://www.djangoproject.com/) framework. This project is written in Python using the popular [Django](https://www.djangoproject.com/) framework.
Deployment is managed using [Ansible](https://www.ansible.com/) and Docker (https://www.docker.com/), see the `deploy/README.md` for details.
An [Ansible](https://www.ansible.com/) playbook is provided which is designed for deployment on RHEL7 or CentOS7 Linux systems. This installs and configures: ## Contributors
- MySQL
- Nginx
- Django + BRECcIA-Mapper
TODO deployment instructions - James Graham (@jag1g13) - developer
- Genevieve Agaba
- Sebastian Reichel
- Claire Bedelian
- Eunice Shame
- Fiona Ngarachu
- Gertrude Domfeh
- Henry Hunga
- Julie Reeves
## License
GPL-3.0 © University of Southampton

54
deploy/README.md Normal file
View File

@@ -0,0 +1,54 @@
# BRECcIA Mapper Deployment
BRECcIA Mapper is intended to be deployed using Ansible and Docker.
It has been tested on RHEL7 and RHEL8, though with minor modification to the Ansible playbook it is expected to deploy correctly on other Linux variants (e.g. Ubuntu).
## Development Deployment
Prerequisites:
- [Vagrant](https://www.vagrantup.com/)
- [Ansible](https://www.ansible.com/)
Using Vagrant, we can create a virtual machine and deploy BRECcIA Mapper using the same provisioning scripts as a production deployment.
To deploy a local development version of BRECcIA Mapper inside a virtual machine, use:
```
vagrant up
```
Once this virtual machine has been created, to redeploy use:
```
vagrant provision
```
And to stop the virtual machine use:
```
vagrant halt
```
For further commands see the [Vagrant documentation](https://www.vagrantup.com/docs/cli).
## Production Deployment
Prerequisites:
- [Ansible](https://www.ansible.com/)
To perform a production deployment of BRECcIA Mapper:
1. Copy the `inventory.example.yml` to `inventory.yml`
2. Edit this file:
- Use your server's hostname instead of `example.com`
- Disable debugging
- Replace the secret key with some text known only to you
3. Run the Ansible playbook with this inventory file using:
```
ansible-playbook playbook.yml -i inventory.yml -K -k -u <SSH username>
```
This will ask for your SSH and sudo passwords for the server, before deploying.
To redeploy updates, the same command can be run again - it's safe to redeploy on top of an existing deployment.

View File

@@ -8,17 +8,23 @@
Vagrant.configure("2") do |config| Vagrant.configure("2") do |config|
# Every Vagrant development environment requires a box. You can search for # Every Vagrant development environment requires a box. You can search for
# boxes at https://vagrantcloud.com/search. # boxes at https://vagrantcloud.com/search.
config.vm.box = "centos/7" config.vm.box = "generic/rocky8"
# Create a forwarded port mapping which allows access to a specific port # Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine and only allow access # within the machine from a port on the host machine and only allow access
# via 127.0.0.1 to disable public access # via 127.0.0.1 to disable public access
config.vm.network "forwarded_port", guest: 80, host: 8888, host_ip: "127.0.0.1" config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"
config.vm.network "forwarded_port", guest: 443, host: 8889, host_ip: "127.0.0.1"
# Provision VM using Ansible playbook # Provision VM using Ansible playbook
config.vm.provision "ansible" do |ansible| config.vm.provision "ansible" do |ansible|
ansible.verbose = "v" ansible.verbose = "v"
ansible.playbook = "playbook.yml" ansible.playbook = "playbook.yml"
ansible.host_vars = {
"default" => {
"deploy_environment" => "vagrant",
"django_debug" => 1,
"django_secret_key" => "debug_only_g62WlORMbo8iAcV7vKCKBQ=="
}
}
end end
end end

View File

@@ -0,0 +1,5 @@
all:
hosts:
example.com:
django_debug: 1
django_secret_key: debug_only_g62WlORMbo8iAcV7vKCKBQ==

86
deploy/playbook.yml Normal file
View File

@@ -0,0 +1,86 @@
---
- hosts: all
become_user: root
become_method: sudo
become: yes
pre_tasks:
- name: Check if running under Vagrant
stat:
path: /vagrant
register: vagrant_dir
vars:
project_name: mapper
project_dir: /srv/{{ project_name }}
project_src_dir: "{{ project_dir }}/src"
tasks:
- name: Vagrant specific tasks
block:
- name: Add Docker repository
get_url:
url: https://download.docker.com/linux/centos/docker-ce.repo
dest: '/etc/yum.repos.d/docker-ce.repo'
when: deploy_environment is defined and deploy_environment == "vagrant"
- name: Install system dependencies
ansible.builtin.yum:
name:
- git
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
# - name: Update system packages
# ansible.builtin.yum:
# name: '*'
# state: latest
- name: Clone / update from source repos
ansible.builtin.git:
repo: 'https://github.com/Southampton-RSG/breccia-mapper.git'
dest: '{{ project_src_dir }}'
version: docker
accept_hostkey: yes
- name: Copy template files
ansible.builtin.template:
src: '{{ item }}.j2'
dest: '{{ project_dir }}/{{ item }}'
mode: 0600
loop:
- Caddyfile
- docker-compose.yml
- name: Create database file
ansible.builtin.file:
path: "{{ project_dir }}/db.sqlite3"
state: touch
- name: Start Docker
ansible.builtin.systemd:
name: docker
state: started
enabled: yes
- name: Pull latest docker images
ansible.builtin.command:
chdir: "{{ project_dir }}"
cmd: docker compose pull {{ item }}
loop:
- caddy
- name: Build custom images
ansible.builtin.command:
chdir: "{{ project_dir }}"
cmd: docker compose build {{ item }}
loop:
- web
- name: Start containers
ansible.builtin.command:
chdir: "{{ project_dir }}"
cmd: docker compose up -d

View File

@@ -0,0 +1,15 @@
http://* {
root * /srv
file_server
@proxy_paths {
not path /static/*
}
reverse_proxy @proxy_paths http://web:8000
log {
output stderr
format single_field common_log
}
}

View File

@@ -0,0 +1,35 @@
version: '3.1'
services:
web:
image: breccia-mapper
build: {{ project_src_dir }}
ports:
- 8000:8000
environment:
DEBUG: {{ django_debug }}
DATABASE_URL: sqlite:////app/db.sqlite3
SECRET_KEY: {{ django_secret_key }}
volumes:
- {{ project_dir }}/db.sqlite3:/app/db.sqlite3:z
- static_files:/app/static
caddy:
image: caddy:2
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:z
# Caddy serves static files collected by Django
- static_files:/srv/static:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- web
volumes:
caddy_data:
caddy_config:
static_files:

35
docker-compose.yml Normal file
View File

@@ -0,0 +1,35 @@
version: '3.1'
services:
web:
image: breccia-mapper
build: .
ports:
- 8000:8000
environment:
DEBUG: ${DJANGO_DEBUG}
DATABASE_URL: sqlite:////app/db.sqlite3
SECRET_KEY: ${DJANGO_SECRET_KEY}
volumes:
- ./db.sqlite3:/app/db.sqlite3:z
- static_files:/app/static
caddy:
image: caddy:2
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:z
# Caddy serves static files collected by Django
- static_files:/srv/static:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- web
volumes:
caddy_data:
caddy_config:
static_files:

8
entrypoint.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
set -eo pipefail
python manage.py migrate
python manage.py collectstatic --no-input
exec "$@"

View File

@@ -47,6 +47,7 @@ class DynamicAnswerSetBase(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.negative_responses = {}
field_order = [] field_order = []
for question in self.question_model.objects.all(): for question in self.question_model.objects.all():
@@ -86,6 +87,13 @@ class DynamicAnswerSetBase(forms.Form):
self.fields[field_name] = field self.fields[field_name] = field
field_order.append(field_name) field_order.append(field_name)
try:
negative_response = question.answers.get(is_negative_response=True)
self.negative_responses[field_name] = negative_response.id
except (self.answer_model.DoesNotExist, self.answer_model.MultipleObjectsReturned):
pass
if question.allow_free_text and not self.as_filters: if question.allow_free_text and not self.as_filters:
free_field = forms.CharField(label=f'{question} free text', free_field = forms.CharField(label=f'{question} free text',
required=False) required=False)

View File

@@ -0,0 +1,33 @@
# Generated by Django 2.2.10 on 2022-03-31 17:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('people', '0053_organisation_order_name'),
]
operations = [
migrations.AddField(
model_name='organisationquestionchoice',
name='is_negative_response',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='organisationrelationshipquestionchoice',
name='is_negative_response',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='personquestionchoice',
name='is_negative_response',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='relationshipquestionchoice',
name='is_negative_response',
field=models.BooleanField(default=False),
),
]

View File

@@ -114,6 +114,9 @@ class QuestionChoice(models.Model):
#: Position of this answer in the list #: Position of this answer in the list
order = models.SmallIntegerField(default=0, blank=False, null=False) order = models.SmallIntegerField(default=0, blank=False, null=False)
#: Does this answer represent the negative response?
is_negative_response = models.BooleanField(default=False)
@property @property
def slug(self) -> str: def slug(self) -> str:
return slugify(self.text) return slugify(self.text)

View File

@@ -9,6 +9,10 @@ var organisation_edges;
var anonymise_people = false; var anonymise_people = false;
var anonymise_organisations = false; var anonymise_organisations = false;
function nodeSize (ele) {
return 100 + 20 * ele.connectedEdges().length;
}
var network_style = [ var network_style = [
{ {
selector: 'node[name]', selector: 'node[name]',
@@ -21,32 +25,42 @@ var network_style = [
return anonymise ? ele.data('id') : ele.data('name') return anonymise ? ele.data('id') : ele.data('name')
}, },
width: '100px', width: nodeSize,
height: '100px', height: nodeSize,
'text-halign': 'center', textHalign: 'center',
'text-valign': 'center', textValign: 'center',
'text-wrap': 'wrap', textWrap: 'wrap',
'text-max-width': '90px', textMaxWidth: function (ele) {
'font-size': '12rem', return 0.8 * nodeSize(ele);
'background-color': 'data(nodeColor)', },
'shape': 'data(nodeShape)' fontSize: function (ele) {
return (16 + ele.connectedEdges().length).toString() + 'rem';
},
backgroundColor: 'data(nodeColor)',
shape: 'data(nodeShape)',
opacity: 0.7
} }
}, },
{ {
selector: 'node:selected', selector: 'node:selected',
style: { style: {
'text-max-width': '300px', textMaxWidth: function (ele) {
'font-size': '40rem', return 0.8 * nodeSize(ele);
'z-index': 100, },
fontSize: function (ele) {
return (50 + ele.connectedEdges().length).toString() + 'rem';
},
zIndex: 100,
} }
}, },
{ {
selector: 'edge', selector: 'edge',
style: { style: {
'mid-target-arrow-shape': 'data(lineArrowShape)', midTargetArrowShape: 'data(lineArrowShape)',
'curve-style': 'straight', curveStyle: 'straight',
'width': 1, width: 4,
'line-color': 'data(lineColor)' lineColor: 'data(lineColor)',
opacity: 0.9
} }
} }
] ]
@@ -97,7 +111,8 @@ function get_network() {
// See https://js.cytoscape.org/ for documentation // See https://js.cytoscape.org/ for documentation
cy = cytoscape({ cy = cytoscape({
container: document.getElementById('cy'), container: document.getElementById('cy'),
style: network_style style: network_style,
wheelSensitivity: 0.2
}); });
// Add pan + zoom widget with cytoscape-panzoom // Add pan + zoom widget with cytoscape-panzoom
@@ -188,10 +203,12 @@ function get_network() {
// Optimise graph layout // Optimise graph layout
var layout = cy.layout({ var layout = cy.layout({
name: 'cose', name: 'cose',
randomize: true, randomize: false,
animate: false, animate: false,
idealEdgeLength: function (edge) { return 40; }, nodeRepulsion: function (node) {
nodeRepulsion: function (node) { return 1e7; } return 2 ** node.connectedEdges().length;
},
nodeOverlap: 80
}); });
layout.run(); layout.run();

View File

@@ -0,0 +1,67 @@
{% with config.RELATIONSHIP_FORM_HELP as help_text %}
{% if help_text %}
<div class="alert alert-info mt-3 pb-0">
{{ help_text|linebreaks }}
</div>
{% endif %}
{% endwith %}
<div class="alert alert-info mt-3">
If you do not know this person / organisation, you may use this button to autofill appropriate responses.
<button class="btn btn-warning" onclick="autofillNegative()">Autofill</button>
</div>
<hr>
<form class="form"
method="POST">
{% csrf_token %}
{% load bootstrap4 %}
{% bootstrap_form form %}
{% buttons %}
<button class="btn btn-success" type="submit">Submit</button>
{% endbuttons %}
</form>
{{ form.negative_responses|json_script:"negative-response-data" }}
<script type="application/javascript">
// Polyfill for `Object.entries` on IE
if (!Object.entries) {
Object.entries = function( obj ){
var ownProps = Object.keys( obj ),
i = ownProps.length,
resArray = new Array(i); // preallocate the Array
while (i--)
resArray[i] = [ownProps[i], obj[ownProps[i]]];
return resArray;
};
}
/**
* Autofill form with negative responses if no relationship exists.
*/
function autofillNegative() {
var data = JSON.parse(document.getElementById("negative-response-data").textContent);
var fields = Object.entries(data)
for (var i = 0, n = fields.length; i < n; i++) {
var field = fields[i]
var options = document.getElementById("id_" + field[0]).options
for (var j = 0, m = options.length; j < m; j++) {
var option = options[j]
if (option.value === field[1].toString()) {
option.selected = true
}
}
}
}
</script>
{{ form.negative_responses }}

View File

@@ -15,26 +15,6 @@
<h1>Add Relationship</h1> <h1>Add Relationship</h1>
{% with config.RELATIONSHIP_FORM_HELP as help_text %} {% include 'people/includes/relationship_form.html' %}
{% if help_text %}
<div class="alert alert-info mt-3 pb-0">
{{ help_text|linebreaks }}
</div>
{% endif %}
{% endwith %}
<hr>
<form class="form"
method="POST">
{% csrf_token %}
{% load bootstrap4 %}
{% bootstrap_form form %}
{% buttons %}
<button class="btn btn-success" type="submit">Submit</button>
{% endbuttons %}
</form>
{% endblock %} {% endblock %}

View File

@@ -18,27 +18,7 @@
<h1>Update Relationship</h1> <h1>Update Relationship</h1>
{% with config.RELATIONSHIP_FORM_HELP as help_text %} {% include 'people/includes/relationship_form.html' %}
{% if help_text %}
<div class="alert alert-info mt-3 pb-0">
{{ help_text|linebreaks }}
</div>
{% endif %}
{% endwith %}
<hr>
<form class="form"
method="POST">
{% csrf_token %}
{% load bootstrap4 %}
{% bootstrap_form form %}
{% buttons %}
<button class="btn btn-success" type="submit">Submit</button>
{% endbuttons %}
</form>
{% endblock %} {% endblock %}

View File

@@ -1,18 +0,0 @@
---
- hosts: all
become_user: root
become_method: sudo
become: yes
pre_tasks:
- name: Check if running under Vagrant
stat:
path: /vagrant
register: vagrant_dir
roles:
- database
- webserver
vars:
ansible_python_interpreter: python2

View File

@@ -21,7 +21,7 @@ isort==4.3.21
jsonfield==3.1.0 jsonfield==3.1.0
lazy-object-proxy==1.4.3 lazy-object-proxy==1.4.3
mccabe==0.6.1 mccabe==0.6.1
mysqlclient==1.4.6 # mysqlclient==1.4.6
pep8-naming==0.4.1 pep8-naming==0.4.1
prospector==1.2.0 prospector==1.2.0
pycodestyle==2.4.0 pycodestyle==2.4.0
@@ -42,5 +42,5 @@ six==1.14.0
snowballstemmer==2.0.0 snowballstemmer==2.0.0
soupsieve==1.9.5 soupsieve==1.9.5
sqlparse==0.3.0 sqlparse==0.3.0
typed-ast==1.4.1 typed-ast
wrapt==1.11.2 wrapt==1.11.2

View File

@@ -1,37 +0,0 @@
---
- name: Update system packages
yum:
name: '*'
state: latest
- name: Install system prerequisites
yum:
name: '{{ packages }}'
state: latest
vars:
packages:
- mariadb
- mariadb-devel
- mariadb-server
- python
# For Ansible - not used at runtime
- MySQL-python
- name: Restart database server
service:
name: mariadb
state: restarted
enabled: yes
daemon_reload: yes
- name: Create database
mysql_db:
name: '{{ db_name }}'
state: present
- name: Create database user
mysql_user:
name: '{{ db_user }}'
password: '{{ db_pass }}'
state: present
priv: '{{ db_name }}.*:ALL'

View File

@@ -1,4 +0,0 @@
---
db_name: 'breccia'
db_user: 'breccia'
db_pass: 'breccia'

View File

@@ -1,22 +0,0 @@
---
deploy_mode_dict:
1: Production
2: Staging
3: Development
deploy_mode: 3
secret_key: '{{ lookup("password", "/dev/null") }}'
parent_project_name: 'BRECcIA'
project_name: 'breccia-mapper'
project_full_name: 'breccia_mapper'
project_dir: '/var/www/{{ project_name }}'
venv_dir: '{{ project_dir }}/venv'
web_user: nginx
web_group: nginx
db_name: '{{ project_name }}'
db_user: 'breccia'
db_pass: 'breccia'
display_short_name: 'BRECcIA'
display_long_name: 'BRECcIA Mapper'

View File

@@ -1,255 +0,0 @@
---
- name: Test connection
ping:
- name: Enable EPEL
yum:
name: epel-release
state: latest
- name: Update system packages
yum:
name: '*'
state: latest
- name: Enable RedHat Software Collections - RHEL
rhsm_repository:
name: rhel-server-rhscl-7-rpms
when: ansible_distribution == "RedHat"
- name: Enable RedHat Software Collections - CentOS
yum:
name: centos-release-scl
state: latest
when: ansible_distribution == "CentOS"
- name: Install system prerequisites
yum:
name: '{{ packages }}'
state: latest
vars:
packages:
- gcc
- git
- rh-nginx114
- rh-python36
- policycoreutils-python
- python
- python-setuptools
- python2-cryptography
- name: (Vagrant only) Clone / update from local repo
git:
repo: '/vagrant'
dest: '{{ project_dir }}'
when: vagrant_dir.stat.exists == True
- name: (Vagrant only) Copy local settings file
copy:
src: '{{ settings_file | default("settings.ini") }}'
dest: '{{ project_dir }}/settings.ini'
owner: '{{ web_user }}'
group: '{{ web_group }}'
mode: 0600
when: vagrant_dir.stat.exists == True
- name: (Vagrant only) Add DB to settings file
ini_file:
path: '{{ project_dir }}/settings.ini'
section: settings
option: DATABASE_URL
value: 'mysql://{{ db_user }}:{{ db_pass }}@localhost:3306/{{ db_name }}'
when: vagrant_dir.stat.exists == True
- name: Copy deploy key
copy:
src: '{{ deployment_keyfile }}'
dest: '/tmp/deployment-key'
mode: 0600
when: vagrant_dir.stat.exists == False and deployment_keyfile is defined
- name: Clone / update from source repo
git:
repo: 'git@github.com:Southampton-RSG/breccia-mapper.git'
dest: '{{ project_dir }}'
key_file: '{{ "/tmp/deployment-key" if deployment_keyfile is defined else None }}'
version: '{{ branch | default ("master") }}'
accept_hostkey: yes
when: vagrant_dir.stat.exists == False
- name: Copy customisation deploy key
copy:
src: '{{ customisation_repo_keyfile }}'
dest: '/tmp/deployment-key-customisation'
mode: 0600
when: customisation_repo_keyfile is defined
- name: Clone / update from customisation repo
git:
repo: '{{ customisation_repo }}'
dest: '{{ project_dir }}/custom'
key_file: '{{ "/tmp/deployment-key-customisation" if customisation_repo_keyfile is defined else None }}'
version: '{{ branch | default ("master") }}'
accept_hostkey: yes
when: customisation_repo is defined
- name: Copy and populate settings template
template:
src: 'settings.j2'
dest: '{{ project_dir }}/settings.ini'
owner: '{{ web_user }}'
group: '{{ web_group }}'
mode: 0600
when: vagrant_dir.stat.exists == False
- name: Set ownership of source directory
file:
path: '{{ project_dir }}'
owner: '{{ web_user }}'
group: '{{ web_group }}'
recurse: yes
- name: Create venv
shell: |
source scl_source enable rh-python36
python3 -m venv {{ venv_dir }}
- name: Install pip requirements
pip:
requirements: '{{ project_dir }}/requirements.txt'
virtualenv: '{{ venv_dir }}'
- name: Create static directory
file:
path: '{{ project_dir }}/static'
state: directory
owner: '{{ web_user }}'
group: '{{ web_group }}'
mode: 0755
- name: Run Django setup stages
django_manage:
command: '{{ item }}'
app_path: '{{ project_dir }}'
virtualenv: '{{ venv_dir }}'
become_user: '{{ web_user }}'
with_items:
- dbbackup
- migrate
- collectstatic
- name: Apply SELinux type
file:
path: '{{ project_dir }}/static'
state: directory
setype: httpd_sys_content_t
- name: (Not production) Set SELinux permissive mode
selinux_permissive:
name: httpd_t
permissive: yes
when: deploy_mode > 1
- name: Install uWSGI
shell: |
source scl_source enable rh-python36
pip3 install uwsgi
- name: Setup uWSGI config
file:
path: /etc/uwsgi/sites
state: directory
mode: 0755
- name: Setup uWSGI service
template:
src: uwsgi-service.j2
dest: /etc/systemd/system/uwsgi.service
- name: Ensure uWSGI running
service:
name: uwsgi
state: started
enabled: yes
daemon_reload: yes
- name: Copy web config files
template:
src: uwsgi-site.j2
dest: '/etc/uwsgi/sites/{{ project_name }}.ini'
- name: Generate self-signed SSL certificate
block:
- name: Create directories
file:
path: "{{ item }}"
state: directory
with_items:
- /etc/ssl
- /etc/ssl/crt
- /etc/ssl/private
- /etc/ssl/csr
- name: Create keys
openssl_privatekey:
path: /etc/ssl/private/{{ inventory_hostname }}.pem
owner: '{{ web_user }}'
group: '{{ web_user }}'
- name: Create Certificate Signing Request (CSR)
openssl_csr:
path: /etc/ssl/csr/{{ inventory_hostname }}.csr
privatekey_path: /etc/ssl/private/{{ inventory_hostname }}.pem
common_name: "{{ inventory_hostname }}"
owner: '{{ web_user }}'
group: '{{ web_user }}'
- name: Generate certificate
openssl_certificate:
path: /etc/ssl/crt/{{ inventory_hostname }}.crt
privatekey_path: /etc/ssl/private/{{ inventory_hostname }}.pem
csr_path: /etc/ssl/csr/{{ inventory_hostname }}.csr
provider: selfsigned
owner: '{{ web_user }}'
group: '{{ web_user }}'
- name: Copy Nginx site
template:
src: nginx-site-ssl.j2
dest: '/etc/opt/rh/rh-nginx114/nginx/conf.d/{{ project_name }}-ssl.conf'
owner: '{{ web_user }}'
group: '{{ web_group }}'
when: deploy_mode > 1
- name: Copy Nginx site
template:
src: nginx-site.j2
dest: '/etc/opt/rh/rh-nginx114/nginx/conf.d/{{ project_name }}.conf'
owner: '{{ web_user }}'
group: '{{ web_group }}'
- name: Restart uWSGI and Nginx
service:
name: "{{ item }}"
state: restarted
enabled: yes
daemon_reload: yes
with_items:
- uwsgi
- rh-nginx114-nginx
- name: Populate service facts
service_facts:
- name: Open webserver ports on firewall
firewalld:
service: '{{ item }}'
state: enabled
permanent: yes
immediate: yes
loop:
- ssh
- http
- https
when: ansible_facts.services['firewalld.service'] is defined and ansible_facts.services['firewalld.service'].state == 'running'

View File

@@ -1,28 +0,0 @@
server {
# HTTP/2 allows requests to be pipelined within a single connection
listen 443 ssl http2;
server_name {{ inventory_hostname }} localhost 127.0.0.1;
ssl_certificate /etc/ssl/crt/{{ inventory_hostname }}.crt;
ssl_certificate_key /etc/ssl/private/{{ inventory_hostname }}.pem;
ssl_protocols TLSv1.2;
# Cache and tickets improve performance by ~10% on small requests
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 4h;
ssl_session_tickets on;
location /favicon.ico {
alias {{ project_dir }}/static/img/favicon.ico;
}
location /static/ {
alias {{ project_dir }}/static/;
}
location / {
include uwsgi_params;
uwsgi_pass unix:/run/uwsgi/{{ project_name }}.sock;
uwsgi_buffers 256 16k;
}
}

View File

@@ -1,17 +0,0 @@
server {
listen 80;
server_name {{ inventory_hostname }} localhost 127.0.0.1;
location /favicon.ico {
alias {{ project_dir }}/static/img/favicon.ico;
}
location /static/ {
alias {{ project_dir }}/static/;
}
location / {
include uwsgi_params;
uwsgi_pass unix:/run/uwsgi/{{ project_name }}.sock;
}
}

View File

@@ -1,30 +0,0 @@
# Template populated on {{ template_run_date }}
[settings]
SECRET_KEY={{ secret_key }}
DEBUG={{ "True" if deploy_mode > 1 else "False" }}
DATABASE_URL=mysql://{{ db_user }}:{{ db_pass }}@localhost:3306/{{ db_name }}
{% if allowed_hosts is defined %}
ALLOWED_HOSTS={% for h in allowed_hosts %}{{ h }},{% endfor %}
{% else %}
ALLOWED_HOSTS={{ inventory_hostname }},localhost,127.0.0.1
{% endif %}
PARENT_PROJECT_NAME={{ parent_project_name }}
PROJECT_SHORT_NAME={{ display_short_name }}
PROJECT_LONG_NAME={{ display_long_name }}
{% if email_host is defined %}
EMAIL_HOST={{ email_host }}
{% endif %}
{% if default_from_email is defined %}
DEFAULT_FROM_EMAIL={{ default_from_email }}
{% endif %}
{% if email_port is defined %}
EMAIL_PORT={{ email_port }}
{% endif %}
{% if google_maps_api_key is defined %}
GOOGLE_MAPS_API_KEY={{ google_maps_api_key }}
{% endif %}

View File

@@ -1,13 +0,0 @@
[Unit]
Description=uWSGI Emperor Service
[Service]
ExecStartPre=/bin/bash -c 'mkdir -p /run/uwsgi; chown {{ web_user }}:{{ web_group }} /run/uwsgi; source scl_source rh-python36'
ExecStart=/bin/scl enable rh-python36 "uwsgi --emperor /etc/uwsgi/sites"
Restart=always
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all
[Install]
WantedBy=multi-user.target

View File

@@ -1,19 +0,0 @@
[uwsgi]
project = {{ project_name }}
uid = {{ web_user }}
gid = {{ web_group }}
base = /var/www
chdir = %(base)/%(project)
home = {{ venv_dir }}
module = {{ project_full_name }}.wsgi:application
logto = %(chdir)/%(project).log
master = true
processes = 2
listen = 128
socket = /run/uwsgi/%(project).sock
chown-socket = %(uid):{{ web_group }}
chmod-socket = 660
vacuum = true