Installation

Disqu.es est encore très loin d’être diffusable comme une simple appli à installer sur son serveur... Les instructions visent une debian >= 9.

Dépendances

Utilisateur dédié

En root, on crée un utilisateur dédié disques :

adduser disques
su disques

Sources et Venv

Arborescence du projet :

cd ~
git clone git@gitlab.com:canarduck/disques.git
python3 -m venv venv
source venv/bin/activate
cd disques
pip install -r requirements.txt
pip install -r prod-requirements.txt  # si serveur de prod
pip install -r dev-requirements.txt  # si machine de dev ou test runner
pip install -r test-requirements.txt  # si test runner

Il est possible de créer un fichier .envrc pour se simplifier la vie en chargeant automatiquement le venv et les variables. Après avoir installé direnv :

export FLASK_CONFIG=prod
source ~/venv/bin/activate

Base de données

Après avoir spécifié l'environnement concerné (prod, dev, test) grace à FLASK_CONFIG

./manage.py initdb

secrets.py / variables d’environnement

Quelques variables secrètes doivent être renseignées avant le lancement de disqu.es, elles sont à mettre dans /home/disques/disques/disques/secrets.cfg (oui, 3x disques...) sous la forme VAR="value" (le fichier secrets.cfg.default peut servir de base) :

systemd

Deux services systemd pour lancer disqu.es :

/etc/systemd/system/disques-huey.service

[Unit]
Description=Huey pour disques
After=network.target

[Service]
User=disques
Environment=FLASK_CONFIG=prod
WorkingDirectory=/home/disques/disques
ExecStart=/home/disques/venv/bin/huey_consumer disques.tasks.huey

[Install]
WantedBy=multi-user.target

/etc/systemd/system/disques.service

[Unit]
Description=Daemon gunicorn pour disqu.es
Requires=disques-huey.service
After=network.target

[Service]
PIDFile=/run/disques/pid
Environment=FLASK_CONFIG=prod
User=disques
Group=disques
RuntimeDirectory=disques
WorkingDirectory=/home/disques/disques
ExecStart=/home/disques/venv/bin/gunicorn --pid /run/disques/pid  --bind 127.0.0.1:3345 wsgi:app
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID

[Install]
WantedBy=multi-user.target

Activation des services

# en root
systemctl enable disques.socket
systemctl start disques.socket
systemctl enable disques-huey.service
systemctl start disques-huey.service
systemctl enable disques.service
systemctl start disques.service

nginx

Nginx sert de proxy pour gunicorn et sert en direct les fichiers présents dans /static La configuration est à mettre dans /etc/nginx/sites-available/disques.conf

Pour letsencrypt j'utilise acmetool en mode stateless. ACCOUNT_THUMBPRINT est à remplacer par la bonne valeur.

upstream flask {
    server 127.0.0.1:3345;
}

server {
    listen 80;
    listen [::]:80;
    server_name disqu.es;
    location ~ "^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)$" {
        default_type text/plain;
        return 200 "$1.ACCOUNT_THUMBPRINT";
    }
    return 301 https://disqu.es$request_uri;
}

On active le site et recharge nginx.

# en root
cd /etc/nginx/sites-enabled
ln -s ../sites-available/disques.conf .
systemctl reload nginx

Génération du certificat letsencrypt avec acmetool.

# en root
acmetool want disqu.es www.disqu.es

Une fois le certificat généré on peut ajouter la conf https au fichier.

# upstream flask { ... }
# server { listen 80 ...}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name disqu.es;

    client_max_body_size 5M;

    # SSL config
    # https://mozilla.github.io/server-side-tls/ssl-config-generator/

    # certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
    ssl_certificate /var/lib/acme/live/disqu.es/fullchain;
    ssl_certificate_key /var/lib/acme/live/disqu.es/privkey;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;

    # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    # intermediate configuration. tweak to your needs.
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    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';
    ssl_prefer_server_ciphers on;

    # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
    add_header Strict-Transport-Security max-age=15768000;

    # OCSP Stapling ---
    # fetch OCSP records from URL in ssl_certificate and cache them
    ssl_stapling on;
    ssl_stapling_verify on;

    ## verify chain of trust of OCSP response using Root CA and Intermediate certs
    ssl_trusted_certificate /var/lib/acme/live/disqu.es/fullchain;

    resolver 208.67.222.222;

    error_log /var/log/nginx/disques-error.log error;
    access_log /var/log/nginx/disques-access.log;

    location / {
        try_files $uri @proxy_to_app;
    }

    location /static {
        alias /home/disques/disques/disques/static;
    }

    access_log /var/log/nginx/disques_access.log;
    error_log /var/log/nginx/disques_error.log;

    location @proxy_to_app {
        proxy_pass http://flask;
        proxy_redirect     off;

        proxy_set_header   Host                 $host;
        proxy_set_header   X-Real-IP            $remote_addr;
        proxy_set_header   X-Forwarded-For      $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto    $scheme;
    }
}