Il y avait deux dépôts d'infrastructure et aucune frontière entre eux. La seule distinction défendable — « documentation » d'un côté, « fichiers de configuration » de l'autre — ne tenait plus : infra avait fini par contenir plus de documentation qu'owni, pendant qu'owni décrivait du concret (IP, bases, comptes). Tout vient donc ici : conf/ (vhosts de référence), dns/ (export de zone), services/ (units systemd), scripts/ (sauvegarde) et docs/ (courrier sortant, certificats, sauvegardes, et le déploiement par service). Dans ce sens plutôt que l'inverse parce qu'« owni » nomme la machine, là où « infra » ne dit pas de quoi il s'agit — et c'est le nom que Cédric emploie spontanément, y compris pour le dossier de secrets. Les renvois croisés entre les deux dépôts deviennent des liens internes : un lien vers un dépôt qu'on s'apprête à archiver aurait pourri en silence. Reste une incohérence, signalée plutôt que corrigée à la hâte : docs/admin.md, static.md, wiki.md, dynamic.md et proxy-calendar.md décrivent le déploiement de sites, pas la machine. Ils ont la même place ici que celle que la messagerie n'avait pas — ils devraient rejoindre leurs dépôts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SFbwnJurBwTs7x7t93ecku
5.8 KiB
infra/static — static.alpinux.org
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).