Skip to content

Latest commit

 

History

History
396 lines (307 loc) · 11.4 KB

File metadata and controls

396 lines (307 loc) · 11.4 KB

This tutorial describes how to fully configure the application on a Debian system.

(VPS stands for Virtual Private Server)

Install required packages

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

Create dedicated user

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

Deploy code

2 choices here:

Via a Github action

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

Manually compile from sources

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

Install python requirements

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

Setup application

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

Manual tests to run application

# 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

Create admins

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)

Create a systemd service

(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

Configuring Nginx to Proxy Requests

HTTP listener

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

Activate HTTPS

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

Secure your server

sudo apt install fail2ban
nano /etc/ssh/sshd_config  # Set Port to something else than 22
/etc/init.d/ssh restart

Hardening nginx

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
}

Scheduled task for tomorrow Events

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

Automatic remote backup

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

Delete old cache file for backgrounds

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

Useful

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/

Links

https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-gunicorn-and-nginx-on-ubuntu-20-04