security: retire les configs serveur du dépôt public

Supprime tous les fichiers *.vhost.conf et *.service du suivi git.
Ils révèlent l'architecture interne (ports, chemins, utilisateurs système)
et sont désormais conservés localement dans infra/ (hors dépôt).

Sanitise les docs de déploiement et les .env.example :
- chemins système remplacés par des variables génériques
- ports internes retirés
- client IDs Keycloak remplacés par des placeholders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Cédrix 2026-05-03 17:25:55 +02:00
parent 58c8b2ea7c
commit bc7e9f601b
13 changed files with 50 additions and 349 deletions

5
.gitignore vendored
View file

@ -16,5 +16,10 @@ admin/__pycache__/
admin/venv/
admin/.env
# Configuration serveur (Apache vhosts, systemd) — local uniquement, ne pas versionner
infra/
*.vhost.conf
*.service
# Obsidian (local uniquement)
.obsidian/

View file

@ -20,16 +20,17 @@ org.alpinux.owni/
└── feedback/ → feedback.alpinux.org Formulaire de retours (à construire)
```
Les configurations serveur (Apache, systemd) sont gérées séparément et ne sont **pas versionnées**.
---
## Projets indépendants, liés par l'infrastructure
Chaque projet :
- a sa propre configuration Apache (`*.vhost.conf`) et son unit systemd (`*.service`) dans son dossier
- utilise **AlpID** (SSO Keycloak — https://alpid.alpinux.org) pour l'authentification
- utilise **AlpID** (SSO Keycloak) pour l'authentification quand nécessaire
- a son propre environnement Python (`venv/`) et son fichier `.env` (non versionés)
Les projets sont **liés** par le SSO partagé, le serveur commun (ISPConfig), et l'identité visuelle (logo depuis `static.alpinux.org`).
Les projets sont liés par le SSO partagé, le serveur commun, et l'identité visuelle (logo depuis `static.alpinux.org`).
---
@ -60,7 +61,7 @@ cd ~/Projects/org.alpinux.owni/admin && claude
cd wiki
python3 scripts/build-assets.py # génère docs/assets/alpinux-logo.png
pip install mkdocs-material
mkdocs serve # dev local sur http://127.0.0.1:8000
mkdocs serve # dev local
mkdocs build --strict # build de prod dans site/
```
@ -70,7 +71,7 @@ cd dynamic
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env && nano .env # renseigner les clés AlpID
flask run --port 5001
flask run
```
### admin (admin.alpinux.org)
@ -79,12 +80,12 @@ cd admin
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env && nano .env # renseigner les clés AlpID
flask run --port 5002
flask run
```
---
## Déploiement serveur
Voir les fichiers `docs/technique/` dans `wiki/` pour la documentation complète.
Chaque projet contient son propre `*.vhost.conf` (Apache ISPConfig) et `*.service` (systemd).
Voir `wiki/docs/technique/` pour la documentation.
Les fichiers de configuration serveur sont conservés localement hors dépôt.

View file

@ -1,6 +1,6 @@
SECRET_KEY=changez-moi-avec-une-valeur-aleatoire-longue
ALPID_CLIENT_ID=admin-alpinux
ALPID_CLIENT_ID=<client-id-configuré-dans-keycloak>
ALPID_CLIENT_SECRET=
ALPID_DISCOVERY_URL=https://alpid.alpinux.org/realms/alpinux/.well-known/openid-configuration
@ -8,4 +8,4 @@ ALPID_DISCOVERY_URL=https://alpid.alpinux.org/realms/alpinux/.well-known/openid-
ADMIN_GROUPS=admins
# Chemin du script de déploiement (défaut dans builds.py si non défini)
# DEPLOY_SCRIPT=/home/alpinux/site/scripts/deploy-wiki.sh
# DEPLOY_SCRIPT=<chemin absolu vers le script de déploiement>

View file

@ -1,36 +0,0 @@
# Apache vhost pour admin.alpinux.org
# À créer via ISPConfig : Sites > Ajouter un site web
# Domaine : admin.alpinux.org
# Activer SSL Let's Encrypt dans ISPConfig
#
# L'app admin Flask tourne derrière Gunicorn sur 127.0.0.1:5002
<VirtualHost *:80>
ServerName admin.alpinux.org
Redirect permanent / https://admin.alpinux.org/
</VirtualHost>
<VirtualHost *:443>
ServerName admin.alpinux.org
# ── Proxy vers Gunicorn ──────────────────────────────────────
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:5002/
ProxyPassReverse / http://127.0.0.1:5002/
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"
# ── Sécurité ─────────────────────────────────────────────────
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# ── Logs ─────────────────────────────────────────────────────
ErrorLog /var/log/apache2/admin.alpinux.org-error.log
CustomLog /var/log/apache2/admin.alpinux.org-access.log combined
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/admin.alpinux.org/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/admin.alpinux.org/privkey.pem
</VirtualHost>

View file

@ -1,24 +0,0 @@
# Systemd unit pour l'app d'administration Alpinux
# Copier dans /etc/systemd/system/alpinux-admin.service
# puis : sudo systemctl enable --now alpinux-admin
[Unit]
Description=Alpinux Admin — admin.alpinux.org (Flask + Gunicorn)
After=network.target
[Service]
User=alpinux
Group=alpinux
WorkingDirectory=/home/alpinux/site/admin
EnvironmentFile=/etc/alpinux-admin/config.env
ExecStart=/home/alpinux/site/admin/venv/bin/gunicorn \
--workers 1 \
--bind 127.0.0.1:5002 \
--access-logfile /var/log/alpinux-admin/access.log \
--error-logfile /var/log/alpinux-admin/error.log \
app:app
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target

View file

@ -4,12 +4,12 @@
SECRET_KEY=changez-moi-avec-une-valeur-aleatoire-longue
# AlpID / OIDC (obtenir les valeurs dans la console Keycloak)
ALPID_CLIENT_ID=dynamic-alpinux
ALPID_CLIENT_ID=<client-id-configuré-dans-keycloak>
ALPID_CLIENT_SECRET=
ALPID_DISCOVERY_URL=https://alpid.alpinux.org/realms/alpinux/.well-known/openid-configuration
# Base de données SQLite
DATABASE=/var/lib/dynamic-alpinux/scores.db
DATABASE=<chemin absolu vers scores.db>
# Mode debug (mettre à 0 en production)
FLASK_DEBUG=0

View file

@ -1,24 +0,0 @@
# Systemd unit pour l'app Flask dynamic.alpinux.org
# Copier dans /etc/systemd/system/dynamic-alpinux.service
# puis : sudo systemctl enable --now dynamic-alpinux
[Unit]
Description=Alpinux Dynamic — Quiz interactifs (Flask + Gunicorn)
After=network.target
[Service]
User=alpinux
Group=alpinux
WorkingDirectory=/home/alpinux/dynamic
EnvironmentFile=/etc/dynamic-alpinux/config.env
ExecStart=/home/alpinux/dynamic/venv/bin/gunicorn \
--workers 2 \
--bind 127.0.0.1:5001 \
--access-logfile /var/log/dynamic-alpinux/access.log \
--error-logfile /var/log/dynamic-alpinux/error.log \
app:app
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target

View file

@ -1,33 +0,0 @@
# Apache vhost pour dynamic.alpinux.org
# L'app Flask tourne derrière Gunicorn sur 127.0.0.1:5001
<VirtualHost *:80>
ServerName dynamic.alpinux.org
Redirect permanent / https://dynamic.alpinux.org/
</VirtualHost>
<VirtualHost *:443>
ServerName dynamic.alpinux.org
# ── Proxy vers Gunicorn ──────────────────────────────────────
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:5001/
ProxyPassReverse / http://127.0.0.1:5001/
# En-têtes transmis à Flask
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"
# ── Sécurité ─────────────────────────────────────────────────
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# ── Logs ─────────────────────────────────────────────────────
ErrorLog /var/log/apache2/dynamic.alpinux.org-error.log
CustomLog /var/log/apache2/dynamic.alpinux.org-access.log combined
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/dynamic.alpinux.org/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/dynamic.alpinux.org/privkey.pem
</VirtualHost>

View file

@ -1,67 +0,0 @@
# Apache vhost pour alpinux.org (page d'accueil)
# À créer via ISPConfig : Sites > Ajouter un site web
# Domaine : alpinux.org + www.alpinux.org | DocumentRoot : /var/www/clients/client1/web1/web
#
# Ce vhost gère également la migration SEO depuis l'ancienne infra (DokuWiki)
# vers la nouvelle (wiki.alpinux.org + alpinux.org)
<VirtualHost *:80>
ServerName alpinux.org
ServerAlias www.alpinux.org
Redirect permanent / https://alpinux.org/
</VirtualHost>
<VirtualHost *:443>
ServerName alpinux.org
ServerAlias www.alpinux.org
DocumentRoot /var/www/clients/client1/web1/web
# ── Redirections www → sans-www ─────────────────────────────────
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.alpinux\.org$ [NC]
RewriteRule ^ https://alpinux.org%{REQUEST_URI} [R=301,L]
# ── Migration SEO : anciennes URLs DokuWiki ──────────────────────
# L'ancien wiki tournait sur DokuWiki avec des URLs de type :
# /doku.php?id=namespace:page
# /wiki/doku.php?id=namespace:page
#
# Les deux-points (:) sont encodés %3A dans les query strings.
# On redirige vers wiki.alpinux.org avec des URLs propres.
# /doku.php?id=start → wiki.alpinux.org/
RewriteCond %{QUERY_STRING} ^id=start$ [NC]
RewriteRule ^/doku\.php$ https://wiki.alpinux.org/ [R=301,L]
# /doku.php?id=alpinux:start → wiki.alpinux.org/alpinux/
RewriteCond %{QUERY_STRING} ^id=alpinux(%3A|:)start$ [NC]
RewriteRule ^/doku\.php$ https://wiki.alpinux.org/alpinux/ [R=301,L]
# /doku.php?id=namespace:page → wiki.alpinux.org/namespace/page/
# Capture générique : transforme les ":" en "/" dans le chemin
RewriteCond %{QUERY_STRING} ^id=([a-z0-9_-]+)(%3A|:)([a-z0-9_-]+)$ [NC]
RewriteRule ^/doku\.php$ https://wiki.alpinux.org/%1/%3/ [R=301,L,NE]
# /doku.php?id=page (namespace racine) → wiki.alpinux.org/page/
RewriteCond %{QUERY_STRING} ^id=([a-z0-9_-]+)$ [NC]
RewriteRule ^/doku\.php$ https://wiki.alpinux.org/%1/ [R=301,L,NE]
# /wiki/* → wiki.alpinux.org/* (si l'ancien wiki était monté en sous-répertoire)
RewriteRule ^/wiki/(.*)$ https://wiki.alpinux.org/$1 [R=301,L]
# ── Fichiers statiques ───────────────────────────────────────────
<Directory /var/www/clients/client1/web1/web>
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
DirectoryIndex index.html
</Directory>
# Logs
ErrorLog /var/log/apache2/alpinux.org-error.log
CustomLog /var/log/apache2/alpinux.org-access.log combined
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/alpinux.org/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/alpinux.org/privkey.pem
</VirtualHost>

View file

@ -1,37 +0,0 @@
# Apache vhost pour static.alpinux.org
# À créer via ISPConfig : Sites > Ajouter un site web
# Domaine : static.alpinux.org | DocumentRoot : /var/www/clients/clientX/webY/web
# Activer SSL Let's Encrypt dans ISPConfig
#
# Ou, si créé manuellement, coller ce fichier dans /etc/apache2/sites-enabled/
<VirtualHost *:80>
ServerName static.alpinux.org
Redirect permanent / https://static.alpinux.org/
</VirtualHost>
<VirtualHost *:443>
ServerName static.alpinux.org
DocumentRoot /var/www/clients/client1/web-static/web
# En-têtes CORS — permet au wiki et à la page d'accueil de charger les assets
Header always set Access-Control-Allow-Origin "*"
Header always set Cache-Control "public, max-age=31536000, immutable"
# Pas d'exécution PHP
php_admin_flag engine Off
<Directory /var/www/clients/client1/web-static/web>
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
</Directory>
# Logs
ErrorLog /var/log/apache2/static.alpinux.org-error.log
CustomLog /var/log/apache2/static.alpinux.org-access.log combined
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/static.alpinux.org/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/static.alpinux.org/privkey.pem
</VirtualHost>

View file

@ -18,7 +18,7 @@ Navigateur
│ HTTPS
Apache (reverse proxy, SSL)
│ HTTP 127.0.0.1:5001
│ HTTP (port local dédié)
Gunicorn (2 workers)
@ -37,15 +37,15 @@ SQLite AlpID OIDC
### 1. Cloner le dépôt sur le serveur
```bash
ssh alpinux@alpinux.org
ssh <user>@alpinux.org
git clone https://gitea.alpinux.org/alpinux.cedrica5l/alpinux.site.2026.git \
/home/alpinux/site
$APP_DIR
```
### 2. Créer l'environnement Python
```bash
cd /home/alpinux/site/dynamic
cd $APP_DIR/dynamic
python3 -m venv venv
venv/bin/pip install -r requirements.txt gunicorn
```
@ -54,7 +54,7 @@ venv/bin/pip install -r requirements.txt gunicorn
```bash
sudo mkdir /etc/dynamic-alpinux
sudo cp /home/alpinux/site/dynamic/.env.example /etc/dynamic-alpinux/config.env
sudo cp $APP_DIR/dynamic/.env.example /etc/dynamic-alpinux/config.env
sudo nano /etc/dynamic-alpinux/config.env
```
@ -62,10 +62,10 @@ Remplissez les valeurs :
```bash
SECRET_KEY=<générer avec : python3 -c "import secrets; print(secrets.token_hex(32))">
ALPID_CLIENT_ID=dynamic-alpinux
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=/var/lib/dynamic-alpinux/scores.db
DATABASE=<chemin vers le fichier scores.db>
```
```bash
@ -75,9 +75,9 @@ sudo chmod 600 /etc/dynamic-alpinux/config.env
### 4. Créer les répertoires de données et de logs
```bash
sudo mkdir -p /var/lib/dynamic-alpinux
sudo mkdir -p /var/log/dynamic-alpinux
sudo chown alpinux:alpinux /var/lib/dynamic-alpinux /var/log/dynamic-alpinux
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)
@ -85,7 +85,7 @@ sudo chown alpinux:alpinux /var/lib/dynamic-alpinux /var/log/dynamic-alpinux
Dans la console d'administration Keycloak (`https://alpid.alpinux.org`) :
1. **Clients** → **Créer un client**
2. **Client ID** : `dynamic-alpinux`
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`
@ -93,8 +93,10 @@ Dans la console d'administration Keycloak (`https://alpid.alpinux.org`) :
### 6. Installer le service systemd
Les fichiers de configuration systemd et Apache sont conservés **hors dépôt** (`infra/` local).
```bash
sudo cp /home/alpinux/site/scripts/dynamic.alpinux.org.service \
sudo cp infra/dynamic/dynamic.alpinux.org.service \
/etc/systemd/system/dynamic-alpinux.service
sudo systemctl daemon-reload
sudo systemctl enable --now dynamic-alpinux
@ -104,11 +106,9 @@ sudo systemctl status dynamic-alpinux
### 7. Configurer Apache
```bash
# Activer les modules nécessaires
sudo a2enmod proxy proxy_http headers ssl
# Copier le vhost
sudo cp /home/alpinux/site/scripts/dynamic.alpinux.org.vhost.conf \
sudo cp infra/dynamic/dynamic.alpinux.org.vhost.conf \
/etc/apache2/sites-available/dynamic.alpinux.org.conf
sudo a2ensite dynamic.alpinux.org
sudo apachectl configtest
@ -126,8 +126,8 @@ sudo certbot --apache -d dynamic.alpinux.org
## Mise à jour
```bash
ssh alpinux@alpinux.org
cd /home/alpinux/site
ssh <user>@alpinux.org
cd $APP_DIR
git pull
cd dynamic
venv/bin/pip install -r requirements.txt # si requirements.txt a changé
@ -145,8 +145,6 @@ sudo systemctl restart dynamic-alpinux
| Redémarrer | `sudo systemctl restart dynamic-alpinux` |
| État | `sudo systemctl status dynamic-alpinux` |
| Logs en direct | `sudo journalctl -u dynamic-alpinux -f` |
| Logs accès | `tail -f /var/log/dynamic-alpinux/access.log` |
| Logs erreurs | `tail -f /var/log/dynamic-alpinux/error.log` |
---

View file

@ -29,7 +29,7 @@ Dépôt local serveur
│ mkdocs build --strict
/var/www/clients/client1/web2/web/wiki-static/
DocumentRoot Apache (wiki.alpinux.org)
│ Apache
@ -62,14 +62,10 @@ sudo apt install chromium
```bash
git clone https://gitea.alpinux.org/alpinux.cedrica5l/alpinux.site.2026.git \
/home/alpinux/wiki
$WIKI_DIR
```
- Le `site_dir` dans `mkdocs.yml` pointe vers le bon DocumentRoot Apache :
```yaml
site_dir: /var/www/clients/client1/web2/web/wiki-static
```
- Le `site_dir` dans `wiki/mkdocs.yml` pointe vers le DocumentRoot Apache configuré dans ISPConfig.
---
@ -78,13 +74,13 @@ site_dir: /var/www/clients/client1/web2/web/wiki-static
### 1. Se connecter au serveur
```bash
ssh alpinux@alpinux.org
ssh <user>@alpinux.org
```
### 2. Récupérer les dernières modifications
```bash
cd /home/alpinux/wiki
cd $WIKI_DIR
git pull
```
@ -95,6 +91,7 @@ Vérifiez que la commande affiche bien les fichiers modifiés. Si elle affiche `
Le logo PNG n'est pas dans git — il est généré depuis le SVG source :
```bash
cd $WIKI_DIR/wiki
python3 scripts/build-assets.py
```
@ -110,6 +107,7 @@ Cette commande produit :
### 4. Lancer le build MkDocs
```bash
cd $WIKI_DIR/wiki
mkdocs build --strict
```
@ -123,7 +121,7 @@ INFO - Cleaning site directory
INFO - Documentation built in X.XX seconds
```
Le dossier `site_dir` est maintenant mis à jour. Apache sert immédiatement les nouveaux fichiers — **pas besoin de redémarrer Apache**.
Le DocumentRoot Apache est maintenant mis à jour. **Pas besoin de redémarrer Apache**.
### 5. Vérifier en ligne
@ -133,19 +131,20 @@ Ouvrez [https://wiki.alpinux.org](https://wiki.alpinux.org) et vérifiez que la
## Automatiser avec un script
Pour éviter d'oublier une étape, créez un script `/home/alpinux/deploy-wiki.sh` :
Pour éviter d'oublier une étape, créez un script de déploiement sur le serveur :
```bash
#!/bin/bash
set -e
WIKI_DIR="/home/alpinux/wiki"
WIKI_DIR="<chemin vers le dépôt sur le serveur>/wiki"
echo "==> Récupération des modifications..."
cd "$WIKI_DIR"
cd "$WIKI_DIR/.."
git pull
echo "==> Génération du logo..."
cd "$WIKI_DIR"
python3 scripts/build-assets.py
echo "==> Build MkDocs..."
@ -154,18 +153,6 @@ mkdocs build --strict
echo "==> Déployé avec succès sur https://wiki.alpinux.org"
```
Rendez-le exécutable :
```bash
chmod +x /home/alpinux/deploy-wiki.sh
```
Utilisation :
```bash
/home/alpinux/deploy-wiki.sh
```
---
## Automatiser avec un hook Gitea (optionnel)
@ -186,8 +173,8 @@ Créez `/etc/webhook/hooks.json` :
[
{
"id": "deploy-wiki",
"execute-command": "/home/alpinux/deploy-wiki.sh",
"command-working-directory": "/home/alpinux/wiki",
"execute-command": "<chemin du script de déploiement>",
"command-working-directory": "<chemin du dépôt>",
"response-message": "Déploiement lancé"
}
]
@ -199,12 +186,10 @@ Démarrez le service :
sudo systemctl enable --now webhook
```
Le webhook écoute par défaut sur le port `9000`.
### Côté Gitea : configurer le webhook
1. Allez dans le dépôt sur Gitea → **Paramètres****Webhooks****Ajouter un webhook**.
2. **URL** : `http://alpinux.org:9000/hooks/deploy-wiki`
2. **URL** : `http://<serveur>:<port>/hooks/deploy-wiki`
3. **Type de déclencheur** : *Push* (ou *Pull Request merging*)
4. Cliquez sur **Ajouter le webhook**.
@ -245,4 +230,3 @@ Ouvrez [http://localhost:8000](http://localhost:8000) — MkDocs recharge automa
| Générer le logo PNG (si SVG modifié) | `python3 scripts/build-assets.py` |
| Construire et déployer | `mkdocs build --strict` |
| Tester en local | `mkdocs serve` |
| Déployer via script (tout en un) | `/home/alpinux/deploy-wiki.sh` |

View file

@ -1,66 +0,0 @@
# Apache vhost pour wiki.alpinux.org
# À créer via ISPConfig : Sites > Ajouter un site web
# Domaine : wiki.alpinux.org | DocumentRoot : /var/www/clients/client1/web2/web/wiki-static
#
# Ce vhost sert le wiki MkDocs (statique) et gère la migration SEO
# depuis l'éventuelle ancienne structure DokuWiki sur ce sous-domaine.
<VirtualHost *:80>
ServerName wiki.alpinux.org
Redirect permanent / https://wiki.alpinux.org/
</VirtualHost>
<VirtualHost *:443>
ServerName wiki.alpinux.org
DocumentRoot /var/www/clients/client1/web2/web/wiki-static
RewriteEngine On
# ── Migration SEO : anciennes URLs DokuWiki sur ce sous-domaine ──
# Si l'ancien DokuWiki était hébergé ici avant la migration MkDocs
# /doku.php?id=start → /
RewriteCond %{QUERY_STRING} ^id=start$ [NC]
RewriteRule ^/doku\.php$ https://wiki.alpinux.org/ [R=301,L]
# /doku.php?id=alpinux:start → /alpinux/
RewriteCond %{QUERY_STRING} ^id=alpinux(%3A|:)start$ [NC]
RewriteRule ^/doku\.php$ https://wiki.alpinux.org/alpinux/ [R=301,L]
# /doku.php?id=namespace:page → /namespace/page/
RewriteCond %{QUERY_STRING} ^id=([a-z0-9_-]+)(%3A|:)([a-z0-9_-]+)$ [NC]
RewriteRule ^/doku\.php$ https://wiki.alpinux.org/%1/%3/ [R=301,L,NE]
# /doku.php?id=page → /page/
RewriteCond %{QUERY_STRING} ^id=([a-z0-9_-]+)$ [NC]
RewriteRule ^/doku\.php$ https://wiki.alpinux.org/%1/ [R=301,L,NE]
# URLs sans slash final → avec slash (cohérence MkDocs)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !/$
RewriteRule ^(.+)$ $1/ [R=301,L]
# ── Fichiers statiques MkDocs ────────────────────────────────────
<Directory /var/www/clients/client1/web2/web/wiki-static>
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
DirectoryIndex index.html
# Cache long pour les assets versionnés MkDocs
<FilesMatch "\.(css|js|woff2?|png|svg|ico)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
# Pas de cache sur le HTML (contenu mis à jour)
<FilesMatch "\.html$">
Header set Cache-Control "public, max-age=3600"
</FilesMatch>
</Directory>
# Logs
ErrorLog /var/log/apache2/wiki.alpinux.org-error.log
CustomLog /var/log/apache2/wiki.alpinux.org-access.log combined
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/wiki.alpinux.org/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/wiki.alpinux.org/privkey.pem
</VirtualHost>