Ce dépôt d'infrastructure décrit la machine ; le déploiement de ce site-ci le regarde lui, et le tenir ailleurs garantissait qu'il divergerait du code qu'il décrit. Repris tel quel depuis alpinux-owni/docs/, où il est supprimé en retour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SFbwnJurBwTs7x7t93ecku
5.8 KiB
static.alpinux.org — infrastructure
Configuration de référence pour static.alpinux.org (CDN assets Alpinux + tableau de bord).
Fichiers
| Fichier | Description |
|---|---|
static.alpinux.org.vhost.conf |
Configuration Apache — référence audit, non déployé manuellement |
static-cdn.service |
Systemd unit pour l'app Flask (tableau de bord) |
Gestion via ISPConfig
Le VirtualHost est créé et géré par ISPConfig (https://owni.alpinux.org:8080).
Pour recréer ou reconfigurer le site :
- Sites → Ajouter un site web — domaine
static.alpinux.org - Onglet SSL → activer Let's Encrypt
- PHP désactivé (onglet Options avancées)
Tableau de bord Flask (app/)
L'app Flask (static/app/) sert la page d'accueil de static.alpinux.org après authentification AlpID.
Déploiement
Depuis le poste de développement :
cd org.alpinux.owni/static
scripts/deploy-app.sh
Le script :
- rsync
app/→/opt/static-cdn/sur le serveur (hors web root) - crée le venv Python et installe les dépendances
- installe et démarre
static-cdn.servicevia systemd
Créer le fichier /opt/static-cdn/.env sur le serveur après le premier déploiement :
ssh alpinux.org
nano /opt/static-cdn/.env
sudo systemctl restart static-cdn
Apache — directives proxy
Ajouter dans ISPConfig → Sites → static.alpinux.org → onglet Options → champ
Directives Apache personnalisées SSL :
# Fichiers CDN publics — Apache sert directement depuis DocumentRoot
ProxyPass /logo/ !
ProxyPass /wiki/ !
ProxyPass /error/ !
ProxyPass /favicon.ico !
ProxyPass /robots.txt !
# Tableau de bord → Flask sur 127.0.0.1:5003
RequestHeader set X-Forwarded-Proto "https"
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:5003/
ProxyPassReverse / http://127.0.0.1:5003/
Note : /stats/ n'est pas exclu — il est servi par Flask avec authentification AlpID.
RequestHeader set X-Forwarded-Proto "https" est indispensable : sans lui, Flask génère
le redirect_uri en HTTP et Keycloak refuse le callback.
Variables d'environnement (/opt/static-cdn/.env)
| Variable | Obligatoire | Valeur / Description |
|---|---|---|
SECRET_KEY |
oui | Chaîne aléatoire longue (python3 -c "import secrets; print(secrets.token_hex(32))") |
ALPID_CLIENT_ID |
oui | static-cdn (client créé dans Keycloak) |
ALPID_CLIENT_SECRET |
oui | Secret généré par Keycloak |
ALPID_DISCOVERY_URL |
oui | https://alpid.alpinux.org/realms/master/.well-known/openid-configuration |
ADMIN_GROUPS |
non | Groupes Keycloak autorisés (défaut : admins) — nécessite le claim groups activé côté Keycloak |
ADMIN_EMAILS |
non | Emails autorisés séparés par virgule — fallback si groups n'est pas dans le token |
ASSETS_ROOT |
oui | /var/www/clients/client1/web17/web |
STATS_FILE |
non | /opt/static-cdn/goaccess.html — rapport HTML GoAccess |
STATS_JSON |
non | /opt/static-cdn/goaccess.json — statistiques par fichier (badges "vues" dans browse) |
Keycloak — realm et client
- Realm :
master(seul realm actif sur cette instance AlpID) - Client ID :
static-cdn - Redirect URI :
https://static.alpinux.org/auth/callback - Scopes demandés :
openid profile email— le scopegroupsn'est pas activé sur ce client ; l'autorisation se fait viaADMIN_EMAILSou, si les deux sont absents, tous les utilisateurs AlpID sont acceptés
Logique d'autorisation (priorité décroissante)
- Claim
groupsprésent dans le token → vérification contreADMIN_GROUPS ADMIN_EMAILSdéfini dans.env→ l'email du compte doit figurer dans la liste- Ni l'un ni l'autre → tout utilisateur authentifié via AlpID est accepté
Pour restreindre à un seul administrateur :
ADMIN_EMAILS=cedric.alpinux@acemail.fr
Chemin web root
ISPConfig attribue /var/www/clients/client1/web17/web au site static.alpinux.org.
Appartient à web17:client1 (drwx--x---) — sudo rsync requis pour y écrire.
ACL pour l'app Flask
Le service tourne en tant qu'abonnelc qui n'est ni web17 ni dans client1.
Une ACL POSIX lui donne les droits de lecture nécessaires :
sudo setfacl -R -m u:abonnelc:rx /var/www/clients/client1/web17/web
sudo setfacl -d -m u:abonnelc:rx /var/www/clients/client1/web17/web
ISPConfig peut réinitialiser les permissions du web root lors d'une modification du site —
relancer ces deux commandes si le tableau de bord retourne une PermissionError.
Statistiques GoAccess
Génération manuelle
sudo goaccess /var/log/ispconfig/httpd/static.alpinux.org/access.log \
--log-format=COMBINED --no-global-config \
--output=/opt/static-cdn/goaccess.html
sudo goaccess /var/log/ispconfig/httpd/static.alpinux.org/access.log \
--log-format=COMBINED --no-global-config \
--output=/opt/static-cdn/goaccess.json
sudo chown abonnelc: /opt/static-cdn/goaccess.html /opt/static-cdn/goaccess.json
Cron quotidien (root)
0 4 * * * goaccess /var/log/ispconfig/httpd/static.alpinux.org/access.log \
--log-format=COMBINED --no-global-config \
--output=/opt/static-cdn/goaccess.html && \
goaccess /var/log/ispconfig/httpd/static.alpinux.org/access.log \
--log-format=COMBINED --no-global-config \
--output=/opt/static-cdn/goaccess.json && \
chown abonnelc: /opt/static-cdn/goaccess.html /opt/static-cdn/goaccess.json
Le rapport JSON alimente les badges "Vues" dans le navigateur de fichiers (browse). Sans ce fichier, les badges sont masqués.
Accès rsync (assets CDN)
Les assets sont synchronisés via static/scripts/push-assets.sh / pull-assets.sh.
Le dossier app/ est exclu du rsync (déployé séparément, non servi depuis le web root).