alpinux.site.2026/docs/technique/deploiement-dynamic.md
Cédrix 27847dfad3 feat: dynamic.alpinux.org — quiz interactifs Flask + AlpID OIDC
App Flask complète pour https://dynamic.alpinux.org :
- 10 quiz Linux, 5 niveaux (Découverte → Expert), 50+ questions
- Public : Découverte, Débutant, Intermédiaire (6 quiz)
- Membres AlpID : Avancé, Expert (4 quiz — Git, Admin, Sécurité, Bash)
- Navigation question par question avec avance automatique après choix
- Score calculé côté serveur, enregistré en SQLite si connecté
- Page profil : meilleurs scores par quiz + historique des tentatives

Authentification :
- OIDC via authlib + AlpID (Keycloak), SSO partagé avec Gitea/Nextcloud
- Décorateur @login_required, redirection post-login sur l'URL d'origine
- /auth/login, /auth/callback, /auth/logout

Structure :
- dynamic/app.py, db.py, quiz.py, auth_utils.py
- dynamic/routes/ (public.py, auth.py, protected.py)
- dynamic/templates/ (base, index, quiz/*, profil/)
- dynamic/static/ (style.css thème Alpinux, quiz.js vanilla)
- dynamic/data/quizzes.json (source de vérité des questions)
- dynamic/.env.example

Infrastructure :
- scripts/dynamic.alpinux.org.vhost.conf (Apache reverse proxy)
- scripts/dynamic.alpinux.org.service (systemd Gunicorn)
- docs/technique/deploiement-dynamic.md (procédure complète)
- mkdocs.yml : page de déploiement ajoutée à la nav Technique
- .gitignore : exclut venv/ et .env

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 12:09:37 +02:00

5.8 KiB

description
Déploiement de dynamic.alpinux.org — app Flask, Gunicorn, Apache reverse proxy, AlpID OIDC.

Déploiement de dynamic.alpinux.org

Application Flask de quiz interactifs, partiellement publique et partiellement réservée aux membres AlpID.

!!! note "Pour qui ?" Procédure pour les mainteneurs avec accès SSH au serveur.


Architecture

Navigateur
    │  HTTPS
    ▼
Apache (reverse proxy, SSL)
    │  HTTP 127.0.0.1:5001
    ▼
Gunicorn  (2 workers)
    │
    ▼
Flask app  (dynamic/)
    │                │
    ▼                ▼
SQLite            AlpID OIDC
(scores.db)    (alpid.alpinux.org)

Installation (première fois)

1. Cloner le dépôt sur le serveur

ssh alpinux@alpinux.org
git clone https://gitea.alpinux.org/alpinux.cedrica5l/alpinux.site.2026.git \
    /home/alpinux/site

2. Créer l'environnement Python

cd /home/alpinux/site/dynamic
python3 -m venv venv
venv/bin/pip install -r requirements.txt gunicorn

3. Configurer les variables d'environnement

sudo mkdir /etc/dynamic-alpinux
sudo cp /home/alpinux/site/dynamic/.env.example /etc/dynamic-alpinux/config.env
sudo nano /etc/dynamic-alpinux/config.env

Remplissez les valeurs :

SECRET_KEY=<générer avec : python3 -c "import secrets; print(secrets.token_hex(32))">
ALPID_CLIENT_ID=dynamic-alpinux
ALPID_CLIENT_SECRET=<obtenir depuis la console Keycloak AlpID>
ALPID_DISCOVERY_URL=https://alpid.alpinux.org/realms/alpinux/.well-known/openid-configuration
DATABASE=/var/lib/dynamic-alpinux/scores.db
sudo chmod 600 /etc/dynamic-alpinux/config.env

4. Créer les répertoires de données et de logs

sudo mkdir -p /var/lib/dynamic-alpinux
sudo mkdir -p /var/log/dynamic-alpinux
sudo chown alpinux:alpinux /var/lib/dynamic-alpinux /var/log/dynamic-alpinux

5. Configurer AlpID (Keycloak)

Dans la console d'administration Keycloak (https://alpid.alpinux.org) :

  1. ClientsCréer un client
  2. Client ID : dynamic-alpinux
  3. Client authentication : activé (pour obtenir un client_secret)
  4. Valid redirect URIs : https://dynamic.alpinux.org/auth/callback
  5. Web origins : https://dynamic.alpinux.org
  6. Notez le Client secret dans l'onglet Credentials

6. Installer le service systemd

sudo cp /home/alpinux/site/scripts/dynamic.alpinux.org.service \
        /etc/systemd/system/dynamic-alpinux.service
sudo systemctl daemon-reload
sudo systemctl enable --now dynamic-alpinux
sudo systemctl status dynamic-alpinux

7. Configurer Apache

# Activer les modules nécessaires
sudo a2enmod proxy proxy_http headers ssl

# Copier le vhost
sudo cp /home/alpinux/site/scripts/dynamic.alpinux.org.vhost.conf \
        /etc/apache2/sites-available/dynamic.alpinux.org.conf
sudo a2ensite dynamic.alpinux.org
sudo apachectl configtest
sudo systemctl reload apache2

8. Obtenir le certificat SSL

sudo certbot --apache -d dynamic.alpinux.org

Mise à jour

ssh alpinux@alpinux.org
cd /home/alpinux/site
git pull
cd dynamic
venv/bin/pip install -r requirements.txt  # si requirements.txt a changé
sudo systemctl restart dynamic-alpinux

Gestion du service

Action Commande
Démarrer sudo systemctl start dynamic-alpinux
Arrêter sudo systemctl stop dynamic-alpinux
Redémarrer sudo systemctl restart dynamic-alpinux
État sudo systemctl status dynamic-alpinux
Logs en direct sudo journalctl -u dynamic-alpinux -f
Logs accès tail -f /var/log/dynamic-alpinux/access.log
Logs erreurs tail -f /var/log/dynamic-alpinux/error.log

Structure de l'application

dynamic/
├── app.py              ← point d'entrée Flask
├── auth_utils.py       ← décorateur @login_required
├── db.py               ← SQLite (scores)
├── quiz.py             ← chargement du JSON
├── requirements.txt
├── .env.example
├── data/
│   └── quizzes.json    ← toutes les questions (source de vérité)
├── routes/
│   ├── public.py       ← accueil, liste, jeu, résultat
│   ├── auth.py         ← /auth/login, /auth/callback, /auth/logout
│   └── protected.py    ← /profil/
├── static/
│   ├── style.css
│   └── quiz.js
└── templates/
    ├── base.html
    ├── index.html
    ├── quiz/
    │   ├── intro.html
    │   ├── play.html
    │   └── result.html
    └── profil/
        └── index.html

Accès public vs membres

URL Accès
/ Public
/quiz/ Public (aperçu de tous les quiz)
/quiz/<id>/ Public (page intro)
/quiz/<id>/jouer Public si members_only: false, sinon AlpID requis
/profil/ AlpID requis
/auth/login Redirect → AlpID
/auth/callback Retour OIDC (interne)

Les quiz avancés (niveau 4) et experts (niveau 5) ont "members_only": true dans data/quizzes.json.


Ajouter un quiz

Éditez dynamic/data/quizzes.json et ajoutez un objet au tableau :

{
  "id": "mon-quiz",
  "title": "Titre du quiz",
  "description": "Description courte.",
  "level": "Intermédiaire",
  "level_id": 3,
  "members_only": false,
  "duration_min": 5,
  "icon": "🐧",
  "questions": [
    {
      "id": 1,
      "text": "Question ?",
      "choices": ["Réponse A", "Réponse B", "Réponse C", "Réponse D"],
      "answer": 0
    }
  ]
}
  • level_id : 1=Découverte, 2=Débutant, 3=Intermédiaire, 4=Avancé, 5=Expert
  • answer : index 0-based de la bonne réponse dans choices
  • members_only : true pour restreindre aux membres AlpID

Après modification, redémarrez le service pour recharger le JSON :

sudo systemctl restart dynamic-alpinux