Déploiement de dynamic.alpinux.org : décrire ce qui tourne réellement

Installation faite le 22/10/2026 sur owni : /opt/dynamic-alpinux, service
dynamic-alpinux sur 127.0.0.1:5001, configuration dans .env, mise en ligne
par scripts/deploy.sh (dépôt privé, pas de git pull sur le serveur).
Ajoute l'état de la connexion AlpID (client Keycloak à créer) et renvoie au
DEPLOIEMENT.md du dépôt pour le détail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019V4mhmHoJD3b8YfQy249dC
This commit is contained in:
Alpinux 2026-10-22 08:54:28 +02:00
parent f0884d9a21
commit 4ff7390d6b

View file

@ -1,13 +1,16 @@
--- ---
description: Déploiement de dynamic.alpinux.org — app Flask, Gunicorn, Apache reverse proxy, AlpID OIDC. description: Déploiement de dynamic.alpinux.org — application Flask derrière Apache (ISPConfig), Gunicorn, SQLite, AlpID.
--- ---
# Déploiement de dynamic.alpinux.org # Déploiement de dynamic.alpinux.org
Application Flask de quiz interactifs, partiellement publique et partiellement réservée aux membres AlpID. Le site « apprendre Linux en jouant » : cours, jeux et quiz. Application Flask, publique,
avec une connexion AlpID pour enregistrer ses scores.
!!! note "Pour qui ?" !!! note "Pour qui ?"
Procédure pour les mainteneurs avec accès SSH au serveur. Les mainteneurs qui ont un accès SSH au serveur. La référence détaillée est le fichier
`DEPLOIEMENT.md` du dépôt [alpinux-dynamic](https://gitea.alpinux.org/alpinux.cedrica5l/alpinux-dynamic),
tenu à jour avec le code ; cette page en donne l'essentiel.
--- ---
@ -17,214 +20,122 @@ Application Flask de quiz interactifs, partiellement publique et partiellement r
Navigateur Navigateur
│ HTTPS │ HTTPS
▼ ▼
Apache (reverse proxy, SSL) Apache (ISPConfig, proxy, Let's Encrypt)
│ HTTP (port local dédié) │ HTTP, 127.0.0.1:5001
▼ ▼
Gunicorn (2 workers) Gunicorn (2 workers) — service dynamic-alpinux
│ │
▼ ▼
Flask app (alpinux-dynamic) Flask (/opt/dynamic-alpinux)
│ │ │ │
▼ ▼ ▼ ▼
SQLite AlpID OIDC SQLite AlpID (OIDC)
(scores.db) (alpid.alpinux.org) /var/lib/dynamic-alpinux/scores.db
``` ```
| | |
|---|---|
| Code | `/opt/dynamic-alpinux`, utilisateur système `dynamic-alpinux` |
| Service | `dynamic-alpinux`, Gunicorn sur `127.0.0.1:5001` |
| Configuration | `/opt/dynamic-alpinux/.env` (droits `600`) |
| Base de données | `/var/lib/dynamic-alpinux/scores.db` |
| Journaux | `sudo journalctl -u dynamic-alpinux` |
--- ---
## Installation (première fois) ## Mettre à jour le site
### 1. Cloner le dépôt sur le serveur Toute modification passe par une pull request fusionnée dans `main`. Ensuite, depuis sa
copie locale du dépôt :
```bash ```bash
ssh <user>@alpinux.org scripts/deploy.sh -n # quel commit de main va partir
git clone https://gitea.alpinux.org/alpinux.cedrica5l/alpinux-dynamic.git \ scripts/deploy.sh # mise en ligne
$APP_DIR
``` ```
### 2. Créer l'environnement Python Le script envoie `origin/main` sur le serveur, installe les dépendances, redémarre le
service et vérifie que les pages répondent.
```bash !!! info "Pourquoi pas un `git pull` sur le serveur ?"
cd $APP_DIR Le dépôt est privé et le serveur n'a pas d'identifiant pour la forge. Le code part donc
python3 -m venv venv du poste du mainteneur, comme pour static.alpinux.org.
venv/bin/pip install -r requirements.txt gunicorn
```
### 3. Configurer les variables d'environnement Les jeux, les cours et les quiz sont lus au démarrage : modifier le contenu demande, comme
le code, une mise en ligne.
```bash
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 :
```bash
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>
```
```bash
sudo chmod 600 /etc/dynamic-alpinux/config.env
```
### 4. Créer les répertoires de données et de logs
```bash
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).
```bash
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 :
```apache
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 ## Gérer le service
```bash
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 | | Action | Commande |
|---|---| |---|---|
| Démarrer | `sudo systemctl start dynamic-alpinux` | | État | `systemctl status dynamic-alpinux` |
| Arrêter | `sudo systemctl stop dynamic-alpinux` |
| Redémarrer | `sudo systemctl restart dynamic-alpinux` | | Redémarrer | `sudo systemctl restart dynamic-alpinux` |
| État | `sudo systemctl status dynamic-alpinux` | | Journal en direct | `sudo journalctl -u dynamic-alpinux -f` |
| Logs en direct | `sudo journalctl -u dynamic-alpinux -f` | | Tester sans Apache | `curl -sI http://127.0.0.1:5001/jeux/` (sur le serveur) |
--- ---
## Structure de l'application ## Le site dans ISPConfig
Le proxy se déclare dans *Sites → dynamic.alpinux.org → Options → Directives Apache*, et
pas dans le fichier `.vhost`, qu'ISPConfig régénère :
```apache
ProxyPass /.well-known/ !
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:5001/
ProxyPassReverse / http://127.0.0.1:5001/
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"
``` ```
alpinux-dynamic/
├── app.py ← point d'entrée Flask La première ligne laisse passer le renouvellement du certificat Let's Encrypt.
├── 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 ## La connexion AlpID
| URL | Accès | L'application utilise le realm `master` d'AlpID, comme admin.alpinux.org. Son client
Keycloak, `dynamic`, se crée dans la console d'AlpID :
1. *Clients → Create client*, **Client ID** `dynamic` ;
2. **Client authentication** activé ;
3. **Valid redirect URIs** : `https://dynamic.alpinux.org/auth/callback` ;
4. **Web origins** : `https://dynamic.alpinux.org` ;
5. le secret de l'onglet *Credentials* va dans `ALPID_CLIENT_SECRET`, dans
`/opt/dynamic-alpinux/.env`, puis on redémarre le service.
!!! warning "État au 22/10/2026"
Le client n'est pas encore créé : les cours, les jeux et les quiz publics fonctionnent,
mais pas la connexion. Il faut aussi que l'application tienne compte du proxy HTTPS
(`ProxyFix`) avant de l'ouvrir, sinon AlpID refuse l'adresse de retour.
---
## Ajouter du contenu
Les procédures pour ajouter un jeu, un cours ou un quiz sont dans le `README.md` du dépôt.
En résumé :
| Contenu | Fichiers |
|---|---| |---|---|
| `/` | Public | | Quiz | `data/quizzes.json` |
| `/quiz/` | Public (aperçu de tous les quiz) | | Jeu | une entrée dans `data/jeux.json`, et `data/jeux/<id>.json` |
| `/quiz/<id>/` | Public (page intro) | | Cours | `data/cours/<id>.md` |
| `/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`. Puis pull request, fusion, et `scripts/deploy.sh`.
--- ---
## Ajouter un quiz ## Réinstaller de zéro
Éditez `data/quizzes.json` et ajoutez un objet au tableau : Le détail, commande par commande, est dans `DEPLOIEMENT.md`. Dans l'ordre :
```json 1. créer l'utilisateur système `dynamic-alpinux`, les dossiers `/opt/dynamic-alpinux` et
{ `/var/lib/dynamic-alpinux`, et le `venv` ;
"id": "mon-quiz", 2. écrire `/opt/dynamic-alpinux/.env` (droits `600`) ;
"title": "Titre du quiz", 3. installer l'unité systemd, dont une copie de référence est dans `deploiement/` ;
"description": "Description courte.", 4. lancer `scripts/deploy.sh` depuis sa copie locale ;
"level": "Intermédiaire", 5. créer le site dans ISPConfig, avec Let's Encrypt et les directives ci-dessus.
"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 :
```bash
sudo systemctl restart dynamic-alpinux
```