# 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 : 1. *Sites → Ajouter un site web* — domaine `static.alpinux.org` 2. Onglet *SSL* → activer **Let's Encrypt** 3. 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 : ```bash 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.service` via systemd Créer le fichier `/opt/static-cdn/.env` sur le serveur après le premier déploiement : ```bash 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** : ```apache # 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 scope `groups` n'est **pas** activé sur ce client ; l'autorisation se fait via `ADMIN_EMAILS` ou, si les deux sont absents, tous les utilisateurs AlpID sont acceptés ### Logique d'autorisation (priorité décroissante) 1. Claim `groups` présent dans le token → vérification contre `ADMIN_GROUPS` 2. `ADMIN_EMAILS` défini dans `.env` → l'email du compte doit figurer dans la liste 3. 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 : ```bash 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 ```bash 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) ```cron 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).