alpinux-wiki/docs/technique/deploiement-dynamic.md
Alpinux 9d2c931a92 Déployer dynamic depuis alpinux-dynamic, plus depuis le monorepo
L'application a quitté alpinux.site.2026 pour son propre dépôt, et elle
est à la racine : plus de sous-dossier dynamic/ à traverser pour le
venv, le .env.example, la mise à jour ou le fichier des quiz.

Au passage, l'unité systemd est dans infra/services/, pas infra/dynamic/.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BE4rvHVETRoWnYNDTGssdo
2026-09-19 23:03:14 +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 (port local dédié)
    ▼
Gunicorn  (2 workers)
    │
    ▼
Flask app  (alpinux-dynamic)
    │                │
    ▼                ▼
SQLite            AlpID OIDC
(scores.db)    (alpid.alpinux.org)

Installation (première fois)

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

ssh <user>@alpinux.org
git clone https://gitea.alpinux.org/alpinux.cedrica5l/alpinux-dynamic.git \
    $APP_DIR

2. Créer l'environnement Python

cd $APP_DIR
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 $APP_DIR/.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=<client-id-configuré-dans-keycloak>
ALPID_CLIENT_SECRET=<obtenir depuis la console Keycloak AlpID>
ALPID_DISCOVERY_URL=https://alpid.alpinux.org/realms/alpinux/.well-known/openid-configuration
DATABASE=<chemin vers le fichier 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 <répertoire données>
sudo mkdir -p <répertoire logs>
sudo chown <user>:<group> <répertoire données> <répertoire logs>

5. Configurer AlpID (Keycloak)

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

  1. Clients → Créer un client
  2. Client ID : le client ID choisi pour cette app
  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

Les fichiers de configuration systemd et Apache sont conservés hors dépôt (infra/ local).

sudo cp infra/services/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 le site dans ISPConfig

Dans ISPConfig (https://owni.alpinux.org:8080) :

  1. Sites → Ajouter un site web — domaine dynamic.alpinux.org
  2. Activer Let's Encrypt SSL dans l'onglet SSL
  3. Dans Options → Directives Apache personnalisées (HTTPS), ajouter :
ProxyPreserveHost On
ProxyPass        / http://127.0.0.1:<port>/
ProxyPassReverse / http://127.0.0.1:<port>/
RequestHeader set X-Forwarded-Proto "https"

ISPConfig gère le VirtualHost et le renouvellement automatique du certificat.


Mise à jour

ssh <user>@alpinux.org
cd $APP_DIR
git pull
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

Structure de l'application

alpinux-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 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