alpinux-static/INFRASTRUCTURE.md
Alpinux ab0cc146ea Reprendre la documentation de déploiement, qui vivait dans alpinux-owni
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
2026-09-27 13:00:19 +02:00

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 :

  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 :

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 :

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

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).