This tutorial describes how to fully configure the application on a Debian system.
(VPS stands for Virtual Private Server)
sudo apt-get update
sudo apt-get upgrade
sudo apt install python3-pip python3-venv nginx rsync git npm
- git & npm are only required if you want to compile the front on the server
- rsync is for the Github deploy action
Replace <user> with the name you want
# Create user
sudo useradd -m -N -g www-data <user> -s /bin/bash
# Create project directory
sudo mkdir /var/www/EventOrganizer
sudo chown <user>:www-data /var/www/EventOrganizer
2 choices here:
Take a look at .github\workflows\deploy.yml: it does everything to compile and deploy the app once you push a commit with a tag. You simply need to declare a few secrets.
Go to Github project > Settings > Secrets and add the following variables:
- HOST: your server DNS
- PORT: ssh port
- USERNAME: username
- SSHKEY: private key, generated like so:
sudo -u <user> -i
ssh-keygen -t rsa -b 4096
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
cat ~/.ssh/id_rsa
Then push a commit with a tag and the action will be executed: https://github.com/LyonParapente/EventOrganizer/actions
Do this only if you don't want to use Github action.
sudo -u <user> -i
git clone https://github.com/LyonParapente/EventOrganizer
Make sure you have a nodejs version >14 with node -v
Install a recent version by following: https://github.com/nodesource/distributions/blob/master/README.md
Compile the front:
cd EventOrganizer/front
npm install
npm run build # this compiles front
This will compile the front and put what's needed inside /back/static/, notably app.js and vendor.js.
Note: you might get an error on node-sass. Reinstall it with:
npm remove node-sass
npm install node-sass
Ok now you have the release files (the /back/ folder + what was generated by front).
# add content of "/back/" if you compile from sources
cp -R /<path-of-compile>/back/* /var/www/EventOrganizer/
We then need to configure python:
cd /var/www/EventOrganizer
python3 -m venv env
source env313/bin/activate
pip install wheel # to ensure that our packages will install even if they are missing wheel archives
pip install -r requirements.txt
cp app_secrets.sample.py app_secrets.py
nano app_secrets.py # make required changes
Also make sure settings are ok:
nano settings.py # make required changes
Especially the domain!! Note: Setting https will force cookies to be secure (https only) thus login won't fully work until https is set up properly.
Install locale if needed:
# sudo apt-get install language-pack-fr
sudo locale-gen fr_FR
sudo locale-gen fr_FR.UTF-8
sudo update-locale
# Directly with flask handling requests
python3 app_flask.py
# then test on port 5000 in your browser
# Then by using gunicorn
gunicorn --bind 0.0.0.0:8000 app_flask:app
# then test on port 8000 in your browser
# Must be root port ports<1024
sudo gunicorn3 --bind 0.0.0.0:80 -w 4 app_flask:app -u <user> -g <group> --pythonpath /var/www/EventOrganizer/env313/lib/python3.8/site-packages
# then test on port 80 in your browser
We're now done with our virtual environment, so we can deactivate it:
deactivate
Each admin should be created like a normal user (through gui or api), then manually update its role to 'admin'.
sqlite3 events.db "UPDATE users SET role='admin' WHERE id=101" (replace id accordingly)
(You can also use supervisor rather than systemd)
sudo nano /etc/systemd/system/eventorganizer.service
Fill this in, don't forget to replace <user>:
[Unit]
Description=EventOrganizer Gunicorn instance
After=network.target
[Service]
User=<user>
Group=www-data
WorkingDirectory=/var/www/EventOrganizer
Environment="PATH=/var/www/EventOrganizer/env313/bin"
Environment="PYTHONUNBUFFERED=TRUE"
ExecStart=/var/www/EventOrganizer/env313/bin/gunicorn --workers 4 --bind unix:eventorganizer.sock -m 007 app_flask:app --error-logfile /var/log/gunicorn/error.log --access-logfile /var/log/gunicorn/access.log --capture-output
Restart=always
[Install]
WantedBy=multi-user.target
Create the required folder:
sudo mkdir /var/log/gunicorn
sudo chown eventorganizer:www-data /var/log/gunicorn
Declare the logrotate: sudo nano /etc/logrotate.d/gunicorn
/var/log/gunicorn/access.log
{
daily
rotate 7
#compress
#delaycompress
missingok
notifempty
create 644 eventorganizer www-data
}
/var/log/gunicorn/error.log
{
daily
rotate 7
#compress
#delaycompress
missingok
notifempty
create 644 eventorganizer www-data
}
Then enable, start and check the service:
sudo systemctl enable eventorganizer
sudo systemctl start eventorganizer
sudo systemctl status eventorganizer
Give ability to restart gunicorn to eventorganizer passwordless user:
sudo nano /etc/sudoers.d/eventorganizer
eventorganizer ALL= NOPASSWD: /bin/systemctl restart eventorganizer
eventorganizer ALL= NOPASSWD: /bin/systemctl start eventorganizer
eventorganizer ALL= NOPASSWD: /bin/systemctl stop eventorganizer
eventorganizer ALL= NOPASSWD: /bin/systemctl status eventorganizer
sudo nano /etc/nginx/sites-available/eventorganizer
Copy this content:
server {
listen 80;
server_name <your_domain>;
location / {
include proxy_params;
proxy_pass http://unix:/var/www/EventOrganizer/eventorganizer.sock;
}
}
<your_domain> = calendrier.lyonparapente.fr in our case
Note: we have a DNS entry type CNAME to link this to the hosting server
Enable the configuration:
sudo ln -s /etc/nginx/sites-available/eventorganizer /etc/nginx/sites-enabled
Test for syntax errors:
sudo nginx -t
Restart nginx:
sudo systemctl restart nginx
sudo apt install python3-certbot-nginx
sudo certbot --nginx -d <your_domain>
Activate HTTP2 by editing:
sudo nano /etc/nginx/sites-enabled/eventorganizer
Add "http2" in the following line:
listen 443 ssl http2; # managed by Certbot
sudo apt install fail2ban
nano /etc/ssh/sshd_config # Set Port to something else than 22
/etc/init.d/ssh restart
sudo nano /etc/nginx/conf.d/security.conf
Add this inside (following https://gist.github.com/plentz/6737338):
# don't send the nginx version number in error pages and Server header
server_tokens off;
# config to don't allow the browser to render the page inside an frame or iframe
add_header X-Frame-Options SAMEORIGIN;
# enables server-side protection from BEAST attacks
# http://blog.ivanristic.com/2013/09/is-beast-still-a-threat.html
# ssl_prefer_server_ciphers on;
# disable SSLv3(enabled by default since nginx 0.8.19) since it's less secure then TLS http://en.wikipedia.org/wiki/Secure_Sockets_Layer#SSL_3.0
ssl_protocols TLSv1.2 TLSv1.3;
# ciphers chosen for forward secrecy and compatibility
# http://blog.ivanristic.com/2013/08/configuring-apache-nginx-and-openssl-for-forward-secrecy.html
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
# enable ocsp stapling (mechanism by which a site can convey certificate revocation information to visitors in a privacy-preserving, scalable manner)
# http://blog.mozilla.org/security/2013/07/29/ocsp-stapling-in-firefox/
resolver 1.1.1.1 8.8.8.8;
ssl_stapling on;
ssl_stapling_verify on;
Then edit again the website file:
sudo nano /etc/nginx/sites-enabled/eventorganizer
Comment the following line:
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
and add this in the section, :
include /etc/nginx/conf.d/security.conf;
Test: sudo nginx -t
Apply changes: sudo systemctl restart nginx
Overall file with maintenance page:
server {
server_name calendrier.lyonparapente.fr;
location / {
if (-f /var/www/EventOrganizer/under_maintenance.html) {
return 503;
}
include proxy_params;
proxy_pass http://unix:/var/www/EventOrganizer/eventorganizer.sock;
}
error_page 503 /under_maintenance.html;
location = /under_maintenance.html {
root /var/www/EventOrganizer/;
internal;
}
listen 443 ssl http2; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/calendrier.lyonparapente.fr/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/calendrier.lyonparapente.fr/privkey.pem; # managed by Certbot
#include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
include /etc/nginx/conf.d/security.conf;
}
server {
if ($host = calendrier.lyonparapente.fr) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name calendrier.lyonparapente.fr;
return 404; # managed by Certbot
}
nano ~/tomorrow_events.sh
Replace <token> with the value of DAILY_CHECK from secrets.py:
#!/bin/sh
curl "https://calendrier.lyonparapente.fr/tomorrow_events?token=<token>" >~/curl_res.txt
Recommended: encodeURIComponent(DAILY_CHECK)
Then: chmod u+x ~/tomorrow_events.sh
And schedule it with: crontab -e
0 17 * * * /bin/sh /var/www/EventOrganizer/tomorrow_events.sh
Store public key in ~/.ssh/authorized_keys of the remote system user.
nano ~/rsync_backup.sh and put this inside:
#!/bin/sh
rsync -arz --include="events.db" --include="avatars/***" --exclude="*" --delete -e "ssh -p <port>" /var/www/EventOrganizer/ <user>@<host>:~/backups/
Replace <user>, <host> and <port> with the appropriate values.
Then: chmod u+x ~/rsync_backup.sh
And schedule it with: crontab -e
0 */12 * * * /bin/sh /var/www/EventOrganizer/rsync_backup.sh
Than haven't been accessed in a year.
Schedule it with: crontab -e
0 0 1 * * find /var/www/EventOrganizer/backgrounds/ -type f -atime +365 -delete
A few helpful commands:
source /var/www/EventOrganizer/back/env313/bin/activate
less /var/log/nginx/error.log
less /var/log/nginx/access.log
journalctl -u nginx
journalctl -u eventorganizer
echo "alias o='ls -AlFh --time-style=long-iso --color=auto'" >~/.bash_aliases
# delete cache files for backgrounds than haven't been accessed in a year
du -sh /var/www/EventOrganizer/backgrounds/
find /var/www/EventOrganizer/backgrounds/ -type f -atime +365 -delete
du -sh /var/www/EventOrganizer/backgrounds/