\chapter{Debian}

La première étape pour la configuration de notre hôte consiste à définir les utilisateurs et groupes de droits. Il est faut absolument éviter de faire tourner une application en tant qu'utilisateur \textbf{root}, car la moindre faille pourrait avoir des conséquences catastrophiques.

Une fois que ces utilisateurs seront configurés, nous pourrons passer à l'étape de configuration, qui consistera à:

\begin{enumerate}
\item
  Déployer les sources
\item
  Démarrer un serveur implémentant une interface WSGI (\textbf{Web Server Gateway Interface}), qui sera chargé de créer autant de petits lutins travailleurs que nous le désirerons.
\item
  Démarrer un superviseur, qui se chargera de veiller à la bonne santé de nos petits travailleurs, et en créer de nouveaux s'il le juge nécessaire
\item
  Configurer un proxy inverse, qui s'occupera d'envoyer les requêtes d'un utilisateur externe à la machine hôte vers notre serveur applicatif, qui la communiquera à l'un des travailleurs.
\end{enumerate}

La machine hôte peut être louée chez Digital Ocean, Scaleway, OVH, Vultr, ... Il existe des dizaines d'hébergements typés VPS (\textbf{Virtual Private Server}). A vous de choisir celui qui vous convient \footnote{Personnellement, j'ai un petit faible pour Hetzner Cloud}.

\begin{verbatim}
  apt update
  groupadd --system webapps
  groupadd --system gunicorn_sockets
  useradd --system --gid webapps --shell /bin/bash --home /home/gwift gwift
  mkdir -p /home/gwift
  chown gwift:webapps /home/gwift
\end{verbatim}


\begin{itemize}
  \item
    On ajoute un groupe intitulé \texttt{webapps}
  \item
    On crée un groupe pour les communications via sockets
  \item
    On crée notre utilisateur applicatif; ses applications seront placées
    dans le répertoire \texttt{/home/gwift}
  \item
    On crée le répertoire home/gwift
  \item
    On donne les droits sur le répertoire /home/gwift
\end{itemize}

\section{Dépendances systèmes}


La version 3.6 de Python se trouve dans les dépôts officiels de CentOS.
Si vous souhaitez utiliser une version ultérieure, il suffit de
l'installer en parallèle de la version officiellement supportée par
votre distribution.

Pour CentOS, vous avez donc deux possibilités :

\begin{verbatim}
  yum install python36 -y
\end{verbatim}

Ou passer par une installation alternative:

\begin{verbatim}
  sudo yum -y groupinstall "Development Tools"
  sudo yum -y install openssl-devel bzip2-devel libffi-devel
  wget https://www.python.org/ftp/python/3.8.2/Python-3.8.2.tgz
  cd Python-3.8*/
  ./configure --enable-optimizations
  sudo make altinstall
\end{verbatim}

\begin{itemize}
  \item
    \textbf{Attention !} Le paramètre \texttt{altinstall} est primordial.
    Sans lui, vous écraserez l'interpréteur initialement supporté par la
    distribution, et cela pourrait avoir des effets de bord non souhaités.
\end{itemize}

\section{Base de données}


On l'a déjà vu, Django se base sur un pattern type
\href{https://www.martinfowler.com/eaaCatalog/activeRecord.html}{ActiveRecords}
pour la gestion de la persistance des données et supporte les principaux
moteurs de bases de données connus:

\begin{itemize}
\item
  SQLite (en natif, mais Django 3.0 exige une version du moteur
  supérieure ou égale à la 3.8)
\item
  MariaDB (en natif depuis Django 3.0),
\item
  PostgreSQL au travers de psycopg2 (en natif aussi),
\item
  Microsoft SQLServer grâce aux drivers {[}\ldots\hspace{0pt}à
  compléter{]}
\item
  Oracle via
  \href{https://oracle.github.io/python-cx_Oracle/}{cx\_Oracle}.
\end{itemize}

Chaque pilote doit être utilisé précautionneusement ! Chaque version de Django n'est pas toujours compatible avec chacune des versions des pilotes, et chaque moteur de base de données nécessite parfois une
version spécifique du pilote. Par ce fait, vous serez parfois bloqué sur une version de Django, simplement parce que votre serveur de base de données se trouvera dans une version spécifique (eg. Django 2.3 à cause
d'un Oracle 12.1).

Ci-dessous, quelques procédures d'installation pour mettre un serveur à disposition. Les deux plus simples seront MariaDB et PostgreSQL, qu'on couvrira ci-dessous. Oracle et Microsoft SQLServer se trouveront en
annexes.

\subsection{PostgreSQL}

On commence par installer PostgreSQL.

Dans le cas de debian, on exécute la commande suivante:

\begin{verbatim}
    # aptitude install postgresql postgresql-contrib
\end{verbatim}

Ensuite, on crée un utilisateur pour la DB:

\begin{verbatim}
    # su - postgres
    postgres@gwift:~$ createuser --interactive -P
    Enter name of role to add: gwift_user
    Enter password for new role:
    Enter it again:
    Shall the new role be a superuser? (y/n) n
    Shall the new role be allowed to create databases? (y/n) n
    Shall the new role be allowed to create more new roles? (y/n) n
    postgres@gwift:~$
\end{verbatim}

Finalement, on peut créer la DB:

\begin{verbatim}
    postgres@gwift:~$ createdb --owner gwift_user gwift
    postgres@gwift:~$ exit
    logout
\end{verbatim}

\subsection{MariaDB}

Idem, installation, configuration, backup, tout ça. A copier de grimboite, je suis sûr d’avoir
des notes là-dessus.


\section{Préparation de l'environment utilisateur}

\begin{verbatim}
    su - gwift
    cp /etc/skel/.bashrc .
    cp /etc/skel/.bash_profile .
    ssh-keygen
    mkdir bin
    mkdir .venvs
    mkdir webapps
    python3.6 -m venv .venvs/gwift
    source .venvs/gwift/bin/activate
    cd /home/gwift/webapps
    git clone ...
\end{verbatim}

La clé SSH doit ensuite être renseignée au niveau du dépôt, afin de pouvoir y accéder.
A ce stade, on devrait déjà avoir quelque chose de fonctionnel en démarrant les commandes
suivantes:

\begin{verbatim}
    # en tant qu'utilisateur 'gwift'
    source .venvs/gwift/bin/activate
    pip install -U pip
    pip install -r requirements/base.txt
    pip install gunicorn
    cd webapps/gwift
    gunicorn config.wsgi:application --bind localhost:3000 --settings
    =config.settings_production
\end{verbatim}

\section{Configuration de l'application}

\begin{verbatim}
  SECRET_KEY=<set your secret key here>
  ALLOWED_HOSTS=*
  STATIC_ROOT=/var/www/gwift/static
  DATABASE=
\end{verbatim}

\begin{itemize}
  \item
    La variable \texttt{SECRET\_KEY} est notamment utilisée pour le
    chiffrement des sessions.
  \item
    On fait confiance à django\_environ pour traduire la chaîne de
    connexion à la base de données.
  \end{itemize}
  
\section{Création des répertoires de logs}


\begin{verbatim}
  mkdir -p /var/www/gwift/static
\end{verbatim}

\section{Socket}  

Dans le fichier \texttt{/etc/tmpfiles.d/gwift.conf}:
  
\begin{verbatim}
  D /var/run/webapps 0775 gwift gunicorn_sockets -
\end{verbatim}
  
Suivi de la création par systemd :
  
\begin{verbatim}
  systemd-tmpfiles --create
\end{verbatim}

\section{Gunicorn}

\begin{verbatim}
  #!/bin/bash
  # defines settings for gunicorn
  NAME="gwift"
  DJANGODIR=/home/gwift/webapps/gwift
  SOCKFILE=/var/run/webapps/gunicorn_gwift.sock
  USER=gwift
  GROUP=gunicorn_sockets
  NUM_WORKERS=5
  DJANGO_SETTINGS_MODULE=config.settings_production
  DJANGO_WSGI_MODULE=config.wsgi
  echo "Starting $NAME as `whoami`"
  source /home/gwift/.venvs/gwift/bin/activate
  cd $DJANGODIR
  export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
  export PYTHONPATH=$DJANGODIR:$PYTHONPATH
  exec gunicorn ${DJANGO_WSGI_MODULE}:application \
    --name $NAME \
    --workers $NUM_WORKERS \
    --user $USER \
    --bind=unix:$SOCKFILE \
    --log-level=debug \
    --log-file=-
\end{verbatim}

\section{Supervsion, keepalive et autoreload}

Pour la supervision, on passe par Supervisor. Il existe d'autres
superviseurs,

\begin{verbatim}
  yum install supervisor -y
\end{verbatim}

On crée ensuite le fichier \texttt{/etc/supervisord.d/gwift.ini}:

\begin{verbatim}
  [program:gwift]
  command=/home/gwift/bin/start_gunicorn.sh
  user=gwift
  stdout_logfile=/var/log/gwift/gwift.log
  autostart=true
  autorestart=unexpected
  redirect_stdout=true
  redirect_stderr=true
\end{verbatim}

Et on crée les répertoires de logs, on démarre supervisord et on vérifie
qu'il tourne correctement:

\begin{verbatim}
  $ mkdir /var/log/gwift
  $ chown gwift:nagios /var/log/gwift
  $ systemctl enable supervisord
  $ systemctl start supervisord.service
  $ systemctl status supervisord.service
  supervisord.service - Process Monitoring and Control Daemon
  Loaded: loaded (/usr/lib/systemd/system/supervisord.service; enabled;
  vendor preset: disabled)
  Active: active (running) since Tue 2019-12-24 10:08:09 CET; 10s ago
  Process: 2304 ExecStart=/usr/bin/supervisord -c /etc/supervisord.conf (code
  =exited, status=0/SUCCESS)
  Main PID: 2310 (supervisord)
  CGroup: /system.slice/supervisord.service
  - 2310 /usr/bin/python /usr/bin/supervisord -c
  /etc/supervisord.conf
  - 2313 /home/gwift/.venvs/gwift/bin/python3
  /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
  - 2317 /home/gwift/.venvs/gwift/bin/python3
  /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
  - 2318 /home/gwift/.venvs/gwift/bin/python3
  /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
  - 2321 /home/gwift/.venvs/gwift/bin/python3
  /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
  - 2322 /home/gwift/.venvs/gwift/bin/python3
  /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
  - 2323 /home/gwift/.venvs/gwift/bin/python3
  /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
  ls /var/run/webapps
\end{verbatim}


On peut aussi vérifier que l'application est en train de tourner, à
l'aide de la commande \texttt{supervisorctl}:

\begin{verbatim}
  supervisorctl status gwift
  gwift RUNNING pid 31983, uptime 0:01:00
  supervisorctl stop gwift
  gwift: stopped
  root@ks3353535:/etc/supervisor/conf.d# supervisorctl start gwift
  gwift: started
  root@ks3353535:/etc/supervisor/conf.d# supervisorctl restart gwift
  gwift: stopped
  gwift: started
\end{verbatim}

\section{Firewall}

\begin{verbatim}
  et 443 (HTTPS).
\end{verbatim}
  
\begin{verbatim}
  firewall-cmd --permanent --zone=public --add-service=http 
  firewall-cmd --permanent --zone=public --add-service=https 
  firewall-cmd --reload
\end{verbatim}
  
\begin{itemize}
  \item
    On ouvre le port 80, uniquement pour autoriser une connexion HTTP,
    mais qui sera immédiatement redirigée vers HTTPS
  \item
    Et le port 443 (forcément).
\end{itemize}

\section{Reverse proxy}


\begin{verbatim}
  yum install nginx -y
  usermod -a -G gunicorn_sockets nginx
  \end{verbatim}
  
  On configure ensuite le fichier \texttt{/etc/nginx/conf.d/gwift.conf}:
  
  \begin{verbatim}
  upstream gwift_app {
    server unix:/var/run/webapps/gunicorn_gwift.sock fail_timeout=0;
  }
  
  server {
    listen 80;
    server_name <server_name>;
    root /var/www/gwift;
    error_log /var/log/nginx/gwift_error.log;
    access_log /var/log/nginx/gwift_access.log;

    client_max_body_size 4G;
    keepalive_timeout 5;

    gzip on;
    gzip_comp_level 7;
    gzip_proxied any;
    gzip_types gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;


    location /static/ { 
      access_log off;
      expires 30d;
      add_header Pragma public;
      add_header Cache-Control "public";
      add_header Vary "Accept-Encoding";
      try_files $uri $uri/ =404;
    }

    location / {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
      proxy_set_header Host $http_host;
      proxy_redirect off;

      proxy_pass http://gwift_app;
    }
  }
  \end{verbatim}
  
  \begin{itemize}
  \item
    Ce répertoire sera complété par la commande \texttt{collectstatic} que
    l'on verra plus tard. L'objectif est que les fichiers ne demandant
    aucune intelligence soit directement servis par Nginx. Cela évite
    d'avoir un processus Python (relativement lent) qui doive être
    instancié pour servir un simple fichier statique.
  \item
    Afin d'éviter que Django ne reçoive uniquement des requêtes provenant
    de 127.0.0.1
  \end{itemize}

\section{Mise à jour}

\begin{verbatim}
  u - <user>
  source ~/.venvs/<app>/bin/activate
  cd ~/webapps/<app>
  git fetch
  git checkout vX.Y.Z
  pip install -U requirements/prod.txt
  python manage.py migrate
  python manage.py collectstatic
  kill -HUP `ps -C gunicorn fch -o pid | head -n 1`
\end{verbatim}


\begin{itemize}
  \item
    \url{https://stackoverflow.com/questions/26902930/how-do-i-restart-gunicorn-hup-i-dont-know-masterpid-or-location-of-pid-file}
\end{itemize}

\section{Logrotate}

\begin{verbatim}
  /var/log/gwift/* {
    weekly
    rotate 3
    size 10M
    compress
    delaycompress
  }
\end{verbatim}

Puis on démarre logrotate avec \# logrotate -d /etc/logrotate.d/gwift
pour vérifier que cela fonctionne correctement.

\section{Sauvegardes}

Les sauvegardes ont été configurées avec borg:
\texttt{yum\ install\ borgbackup}.

C'est l'utilisateur gwift qui s'en occupe.

\begin{verbatim}
mkdir -p /home/gwift/borg-backups/
cd /home/gwift/borg-backups/
borg init gwift.borg -e=none
borg create gwift.borg::{now} ~/bin ~/webapps
\end{verbatim}

Et dans le fichier crontab :

\begin{verbatim}
0 23 * * * /home/gwift/bin/backup.sh
\end{verbatim}

\section{Ansible}

