🎙️ ANNU KUTE CED — Hub multimédia du podcast
- 🇫🇷 Version française
- 📖 Description
- 🌳 Origine du projet
- 🔗 Sources et instances utilisées
- ✨ Fonctionnalités
- 🛠️ Technologies utilisées
- 📁 Structure du projet
- 📋 Prérequis
- 🚀 Installation
- ⚙️ Configuration
- 🎨 Personnalisation de l’apparence
- 🧭 Ordre des sections sur la page d’accueil
- 🛡️ Sécurité
- 📱 Progressive Web App (PWA)
- 📊 Analytics (Plausible)
- 🚀 Déploiement sur serveur mutualisé
- 👨💻 Développement et contribution
- 📜 Licence
- 📞 Contact
- 🇬🇧 English version
- 📖 Description
- 🌳 Project origin
- 🔗 Aggregated sources and instances
- ✨ Features
- 🛠️ Tech stack
- 📁 Project structure
- 📋 Requirements
- 🚀 Installation
- ⚙️ Configuration
- 🎨 Appearance customization
- 🧭 Homepage section order
- 🛡️ Security
- 📱 Progressive Web App (PWA)
- 📊 Analytics (Plausible)
- 🚀 Deploying to shared hosting
- 👨💻 Development and contributing
- 📜 License
- 📞 Contact
🌍 Hub multimédia du podcast ANNU KUTE CED
🌍 Multimedia hub for the ANNU KUTE CED podcast
Choisissez votre langue / Choose your language :
🇫🇷 Version française · 🇬🇧 English version
🇫🇷 Version française
📖 Description
ANNU KUTE CED est une interface web responsive qui fait office de hub multimédia pour le podcast du même nom. Elle regroupe en un seul lieu :
-
🎥 la chaîne PeerTube du podcast (vidéos, shorts, directs) ;
-
🎙️ son compte Castopod (derniers épisodes, lecture audio intégrée) ;
-
📡 sa timeline Mastodon (actualités et annonces).
La plateforme est légère, déployable sur un serveur mutualisé (PHP + serveur web, sans base de données) et optimisée pour mobile et desktop. C’est aussi une Progressive Web App installable, avec mode hors ligne.
🎯 Mission : offrir un point d’entrée unique, libre et décentralisé, pour découvrir, écouter et suivre le podcast ANNU KUTE CED sans dépendre des grandes plateformes propriétaires.
🌳 Origine du projet
Cette application est un fork de FEDIVERSE OKI, développée par l’ORGANISATION KA INTERNATIONALE (OKI), elle-même issue du projet kaubuntu.re du mouvement Ka-Ubuntu. La licence d’origine (GNU AGPL v3) est conservée et respectée.
| Élément | Détail |
|---|---|
Dépôt upstream |
https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI |
Dépôt de ce fork |
|
Domaine (exemple) |
|
Gouvernance |
Application maintenue par OKI ; fork porté par le propriétaire du dépôt cedric (Cédric Famibelle-Pronzola) |
Licence |
GNU Affero General Public License v3 (AGPL-V3) ou ultérieure |
🔗 Sources et instances utilisées
Le hub est configuré par défaut pour agréger les sources du podcast ANNU KUTE CED :
| Service | Instance | Compte / chaîne |
|---|---|---|
🎥 PeerTube (vidéos, lives) |
GADE — |
|
🎙️ Castopod (épisodes audio) |
KUTE — |
|
📡 Mastodon (timeline) |
BOKANTE — |
|
🎵 Funkwhale (musique, optionnel, désactivé par défaut) |
MIZIK — |
morceaux aléatoires de l’instance |
Toutes ces sources sont modifiables dans includes/config.local.php (voir Configuration).
✨ Fonctionnalités
🎥 Vidéos PeerTube
-
Vidéos récentes, tendances et par catégories (configurables)
-
Shorts : carrousel dédié aux vidéos portrait de moins de 3 minutes (défilement tactile)
-
Page vidéo complète : lecteur PeerTube embarqué, description Markdown, badge de licence Creative Commons, commentaires (lecture seule), vidéos suggérées
-
Téléchargement : fichiers directs et playlists HLS, avec résolution et taille
-
Partage : copie de lien, code d’intégration, e-mail, Facebook, X, WhatsApp, LinkedIn, Telegram
-
Recherche plein texte et recherche par hashtag (préfixe
#) -
Pagination AJAX « Voir plus » (protégée par jeton CSRF et vérification d’origine)
📺 Directs et annonces
-
Page Direct (
/direct) : embed du live PeerTube s’il est en cours, avec autoplay -
Annonce du prochain live : date et heure converties automatiquement pour 5 territoires (Ma’ohi Nui, Martinique/Guadeloupe, Guyane, France, Kanaky), image personnalisable (4:5, 1:1, 16:9)
-
Section hero d’accueil configurable : live, vidéo unique, playlist (PeerTube / Funkwhale / Castopod) ou masquée
🎙️ Podcast et audio
-
Castopod : derniers épisodes via flux RSS, avec lecteur audio intégré (lecture/pause, avance automatique à l’épisode suivant, un seul flux à la fois)
-
Funkwhale (optionnel) : sélection aléatoire de morceaux de l’instance, avec le même lecteur intégré
📡 Réseaux sociaux et contenus externes
-
Timeline Mastodon intégrée (bibliothèque vendored mastodon-embed-timeline v4.7.0, AGPLv3)
-
Compatibilité Pleroma : adaptateur qui convertit les réponses de l’API Pleroma au format Mastodon attendu par la timeline
-
WordPress (optionnel) : derniers articles via l’API REST, avec image à la une et auteur
📱 PWA et confort d’utilisation
-
Progressive Web App installable (bouton d’installation automatique)
-
Mode hors ligne : pages et ressources statiques en cache via Service Worker
-
Indicateur visuel de perte de connexion
-
Mode sombre (basé sur
prefers-color-scheme, mémorisé enlocalStorage) -
Interface entièrement responsive (mobile, tablette, desktop)
💝 Dons, compte à rebours et divers
-
Page de dons : LiberaPay, Ko-fi et Stripe (dons ponctuels et mensuels, montants suggérés)
-
Compte à rebours : page de lancement/maintenance multi-fuseaux qui verrouille le site jusqu’à la date cible
-
Bloc « À propos » configurable (titre, deux paragraphes, image légendée)
🔍 SEO et données structurées
-
JSON-LD généré automatiquement :
WebSite(avecSearchAction),PodcastSeriesetPodcastEpisode(avecAudioObject) pour le podcast Castopod,VideoObject(avecembedUrl),CollectionPage,BreadcrumbList,Organization -
Meta descriptions sur toutes les pages et URL canoniques (
link rel="canonical") ; pages de résultats de recherche ennoindex, follow -
Balises Open Graph et Twitter Cards
-
sitemap.xmletrobots.txtfournis en exemples -
Prêt pour Plausible Analytics (voir Analytics) : désactivé par défaut, sans cookies et conforme RGPD
🛡️ Sécurité
-
En-têtes de sécurité complets : CSP dynamique avec nonce par requête, HSTS,
X-Frame-Options,nosniff,Referrer-Policy,Permissions-Policy -
Protection CSRF sur les requêtes AJAX, vérification de l’en-tête
Origin -
Validation anti-SSRF des URLs d’instances et liste blanche des endpoints de l’API PeerTube
-
Échappement systématique des sorties (anti-XSS)
-
Exemples de configuration sécurisée pour Apache et Nginx dans
conf/
🛠️ Technologies utilisées
-
📄 HTML5, 🎨 CSS3 (Media Queries), ⚡ JavaScript vanilla (sans framework, sans jQuery)
-
🐘 PHP 7.4+ (aucune base de données requise)
-
🔧 Service Worker (cache offline) et 📋 Web App Manifest (installation)
-
📦 Bibliothèques :
-
🎯 Font Awesome 7 (icônes, via CDN cdnjs)
-
📡 mastodon-embed-timeline v4.7.0 (vendored dans
js/, AGPLv3) -
📊 Plausible Analytics (pré-configuré, à activer manuellement)
-
📁 Structure du projet
├── .gitea/
│ └── workflows/ # CI/CD Gitea Actions (check-pr, deploy-prod)
├── ajax/
│ └── load-more-videos.php # Endpoint AJAX « Voir plus » (CSRF + Origin)
├── conf/
│ ├── .htaccess.sample # Configuration Apache sécurisée
│ └── nginx.conf.sample # Configuration Nginx sécurisée
├── css/ # Feuilles de style (styles, vidéo, catégories,
│ │ # recherche, dons, countdown, mastodon,
│ ├── castopod-podcasts.css # castopod, funkwhale, wordpress, grille…)
│ └── …
├── docs/
│ ├── wireframes/ # Maquettes d'origine (PNG desktop + mobile)
│ ├── docinfo.html # Styles d'impression (fallback emoji) pour l'export
│ └── generate-readme-pdf.sh # Export de la doc : README.html / README.pdf
├── img/ # Logo, favicons, icône de lecture
├── includes/
│ ├── config.php # Bootstrap + fonctions API (PeerTube, Castopod,
│ │ # Funkwhale) et formatage
│ ├── config.default.php # Valeurs par défaut de toutes les constantes
│ ├── config.local.php.sample # Modèle de configuration locale (à copier)
│ ├── security.php # CSRF, CSP + nonce, en-têtes, validateurs
│ ├── simple-cache.php # Cache fichier de l'API (cache/api/)
│ ├── wordpress.php # Intégration API REST WordPress
│ ├── structured-data.php # Générateurs JSON-LD
│ ├── header.php # Barre supérieure (recherche, réseaux, thème…)
│ ├── footer.php # Pied de page
│ ├── sidebar.php # Navigation latérale
│ ├── mobile-menu.php # Menu mobile coulissant
│ ├── hero-section.php # Bannière d'accueil (live/vidéo/playlist)
│ ├── featured-videos.php # ⚠️ legacy, non utilisé par les pages actuelles
│ ├── recent-videos.php # ⚠️ legacy, non utilisé par les pages actuelles
│ ├── pwa-init.php # ⚠️ legacy, PWA désormais inline dans index.php
│ └── lib/
│ └── markdown.php # Rendu Markdown (sous-ensemble PeerTube)
├── js/
│ ├── main.js # UI globale (thème, carrousel, « Voir plus »…)
│ ├── audio-player.js # Lecteur audio Castopod / Funkwhale
│ ├── countdown.js # Compte à rebours
│ ├── search.js # Cartes cliquables de la recherche
│ ├── mastodon-config.php # Configuration JS de la timeline (servie en JS)
│ ├── mastodon-timeline.umd.js # mastodon-embed-timeline v4.7.0 (vendored)
│ ├── pleroma-adapter.js # Adaptateur Pleroma → API Mastodon
│ └── pwa-update.js # Enregistrement SW + modal de mise à jour
├── scripts/
│ ├── check.sh # Vérifications qualité en local (avant push)
│ └── warm-cache.php # Préchauffage du cache API (cron)
├── uploads/ # Images d'annonces (non versionné, hors .gitkeep)
├── index.php # Page d'accueil (agrégateur)
├── video.php # Page de lecture d'une vidéo
├── categories.php # Page d'une catégorie
├── recherche.php # Page de recherche
├── direct.php # Page du direct / annonce du prochain live
├── countdown.php # Page de compte à rebours (maintenance)
├── sw.js # Service Worker (PWA)
├── site.webmanifest.sample # Exemple de manifest PWA
├── browserconfig.xml # Tuiles Windows
├── sitemap.xml.sample # Exemple de sitemap
├── robots.txt.sample # Exemple de robots.txt
├── mentions-legales.php.sample # Exemple de mentions légales
├── dons.php.sample # Exemple de page de dons
├── LICENSE # GNU AGPL v3
├── DEPLOY.adoc # Guide de déploiement serveur + CI/CD
└── README.adoc
|
Note
|
Les fichiers marqués « legacy » sont conservés pour référence mais ne sont inclus par aucune page ; la page d’accueil rend ces sections en interne. |
📋 Prérequis
-
🐘 PHP 7.4+ (recommandé : PHP 8.0+)
-
📦 Extensions PHP requises :
-
curl— appels API (PeerTube, Castopod, Funkwhale, WordPress) -
json— traitement des réponses -
intl— dates internationales et fuseaux horaires -
mbstring— chaînes multi-octets -
xml(SimpleXML) — recommandée pour Castopod (un fallback regex existe)
-
-
🌐 Serveur web : Nginx (recommandé) ou Apache
-
🔒 HTTPS : requis pour les fonctionnalités PWA
intl# Ubuntu / Debian
sudo apt-get install php-intl
# Fedora / RHEL / CentOS
sudo dnf install php-intl
# Redémarrer le serveur web
sudo systemctl restart apache2 # Apache
sudo systemctl restart php8.3-fpm # Nginx + php-fpm
# Vérifier
php -m | grep intl
🚀 Installation
-
📥 Clonez le dépôt :
git clone git@labola.o-k-i.net:cedric/annu-kute-ced.git cd annu-kute-ced -
📦 Vérifiez les prérequis PHP (voir ci-dessus).
-
🔧 Créez votre configuration locale et les fichiers d’instance :
cp includes/config.local.php.sample includes/config.local.php cp site.webmanifest.sample site.webmanifest cp robots.txt.sample robots.txt cp sitemap.xml.sample sitemap.xml cp mentions-legales.php.sample mentions-legales.php cp dons.php.sample dons.php -
🛡️ Installez la configuration serveur sécurisée depuis
conf/(voir Sécurité). -
✍️ Rendez le dossier
cache/accessible en écriture par le serveur web (créé automatiquement au premier appel API). -
🔒 Assurez-vous que HTTPS est actif (obligatoire pour la PWA).
-
🌐 Faites pointer votre domaine (ex.
example.com) vers la racine du projet.
⚙️ Configuration
Le système en trois fichiers
| Fichier | Rôle | Versionné ? |
|---|---|---|
|
Bootstrap : charge les autres fichiers et contient toutes les fonctions métier |
✅ Oui |
|
Valeurs par défaut de chaque constante (définies seulement si absentes) |
✅ Oui |
|
Vos surcharges locales — prioritaires car chargées en premier |
❌ Non ( |
Pour personnaliser l’instance, ne modifiez jamais config.default.php : définissez la constante dans config.local.php. Comme ce dernier est chargé avant, vos valeurs ont toujours la priorité.
cp includes/config.local.php.sample includes/config.local.php
Référence des constantes
Valeurs par défaut issues de includes/config.default.php.
| Constante | Défaut | Description |
|---|---|---|
|
|
Nom de domaine affiché (mentions légales…) |
|
|
Nom court de l’organisation |
|
|
Nom complet de l’organisation |
| Constante | Défaut | Description |
|---|---|---|
|
|
URL de l’instance PeerTube (obligatoire) |
|
|
Nom affiché de l’instance |
|
|
Clé API optionnelle ( |
|
|
Afficher le nombre de vues sur les cartes vidéo |
|
|
Catégories mises en avant : |
|
|
Fuseau horaire d’affichage (liste PHP) |
| Constante | Défaut | Description |
|---|---|---|
|
|
|
|
|
Compte PeerTube surveillé pour les directs (hero + page |
|
|
UUID de la vidéo (mode |
|
|
Titre (accessibilité) |
|
|
|
|
|
URL de base de la plateforme de playlist |
|
|
ID de la playlist ; pour Castopod : |
|
|
Textes optionnels (sinon récupérés via API) |
| Constante | Défaut | Description |
|---|---|---|
|
|
Activer la section podcast de l’accueil |
|
|
URL de l’instance Castopod |
|
|
Slug(s) de podcast ; flux RSS = |
|
|
Nombre total d’épisodes affichés |
|
Warning
|
Castopod applique un rate limit strict (HTTP 429) sur les flux RSS. Le code applique un backoff exponentiel (5 s, 10 s, 15 s) uniquement en cas de 429. Activez le cache (CACHE_ENABLED) et préchauffez-le avec scripts/warm-cache.php pour éviter ces délais à chaque page.
|
| Constante | Défaut | Description |
|---|---|---|
|
|
Activer la section musique |
|
|
URL de l’instance Funkwhale |
|
|
Morceaux aléatoires locaux affichés (50 mis en cache) |
| Constante | Défaut | Description |
|---|---|---|
|
|
Instance Mastodon (ou Pleroma, via l’adaptateur) |
|
|
URL publique du compte (liens « Voir plus », footer) |
|
|
Locale de date des posts |
|
|
Libellés des boutons |
|
|
Posts récupérés / affichés |
|
(non défini) |
Origine S3 des médias (ajoutée à la CSP |
|
Note
|
La timeline n’a pas de flag d’activation : elle est toujours affichée sur l’accueil. |
| Constante | Défaut | Description |
|---|---|---|
|
|
Activer la section articles |
|
|
URL du site WordPress (sans |
|
|
Nombre d’articles (API REST |
| Constante | Défaut | Description |
|---|---|---|
|
|
Activer le système (icône cœur, lien « Soutenir », page |
|
|
URLs des plateformes externes. Par défaut le lien Liberapay pointe vers OKI, mainteneur de l’application. |
|
|
Activer l’interface Stripe (onglets ponctuel / mensuel) |
|
|
Liens de paiement Stripe par montant |
|
|
Liens d’abonnement mensuel Stripe |
|
|
Montants suggérés |
|
|
Devise |
|
Texte explicite |
Message affiché sur |
|
Note
|
Au moins une plateforme (LiberaPay, Ko-fi ou Stripe) doit être configurée pour que dons.php s’affiche, sinon la page renvoie une erreur 500. Par défaut, seul le compte Liberapay de OKI est renseigné.
|
|
Tip
|
Si vous déployez une instance indépendante, remplacez LIBERAPAY_URL par votre propre lien et adaptez ou videz DONATIONS_OKI_DISCLAIMER.
|
| Constante | Défaut | Description |
|---|---|---|
|
|
⚠️ Si |
|
|
Date cible ( |
|
5 territoires |
|
| Constante | Défaut | Description |
|---|---|---|
|
|
Afficher l’annonce quand aucun direct n’est en cours |
|
|
Textes (date et heure ajoutées automatiquement) |
|
|
Date du live ( |
|
|
Image d’annonce (utilisée seulement si le fichier existe) |
| Constante | Défaut | Description |
|---|---|---|
|
|
Nom du site (titres, meta) |
|
|
Description SEO |
|
|
Chemins des images |
|
|
|
|
E-mail de contact (footer) |
| Constante | Défaut | Description |
|---|---|---|
|
|
Titulaire du copyright |
|
|
Webmaster |
|
valeurs o2Switch |
Coordonnées de l’hébergeur |
|
Contact juridique |
|
|
AGPL-V3 + URL GNU |
Licence du code |
|
|
Dépôt du code source (exigé par l’AGPL pour un service réseau) |
|
|
Phrase de description du service |
| Constante | Défaut | Description |
|---|---|---|
|
|
Tag de la fonction |
|
|
Durée max d’un short (s) ; ratio portrait ( |
|
|
Hashtags de la sidebar, du footer et du menu mobile |
|
|
Hashtags affichés sur l’accueil |
| Constante | Défaut | Description |
|---|---|---|
|
|
Vidéos par page (recherche) |
|
|
Résultats récupérés par recherche |
|
|
Taille des sections d’accueil |
|
|
Shorts affichés / pool de recherche |
|
|
Vidéos chargées par clic sur « Voir plus » |
|
|
Réservés (sections non affichées actuellement) |
| Constante | Défaut | Description |
|---|---|---|
|
|
Active le cache pour Castopod et Funkwhale. Indispensable contre le rate limit RSS de Castopod ; les résultats vides (erreur, 429) ne sont jamais mis en cache |
|
|
Durée de vie du cache (s) |
|
Note
|
Le cache de l’API PeerTube et de WordPress (includes/simple-cache.php) est toujours actif, indépendamment de CACHE_ENABLED, avec des TTL par endpoint (catégories 1 h, vidéos/recherche 10 min, articles WP 15 min, comptes 5 min). Les fichiers sont stockés dans cache/api/.
|
|
Tip
|
Pour éviter que le premier visiteur ne paye le coût des appels API sur cache froid, préchauffez le cache via cron : php /var/www/annu-kute-ced/scripts/warm-cache.php (toutes les 5 minutes par exemple).
|
| Constante | Défaut | Description |
|---|---|---|
|
|
Clé secrète utilisée pour signer les tokens CSRF stateless. À remplacer impérativement dans |
| Constante | Description |
|---|---|
|
Titre du bloc (défaut template : « À propos ») |
|
Premier paragraphe ; commenter cette ligne masque tout le bloc |
|
Second paragraphe (optionnel) |
|
Image ( |
Exemple complet de config.local.php
<?php
define('APP_HOST_NAME', 'example.com');
// PeerTube (chaîne GADE du podcast)
define('PEERTUBE_URL', 'https://gade.o-k-i.net');
define('PEERTUBE_DISPLAY_NAME', 'gade.o-k-i.net');
// Hero : direct de la chaîne, sinon annonce du prochain live
define('HERO_TYPE', 'live');
define('LIVE_ACCOUNT_NAME', 'annu_kute_ced');
define('NEXT_LIVE_ENABLED', true);
define('NEXT_LIVE_TITLE', 'Prochain live');
define('NEXT_LIVE_DESCRIPTION', 'Enregistrement du prochain épisode !');
define('NEXT_LIVE_DATE', '2025-10-11 10:00:00');
// Castopod (compte KUTE du podcast)
define('CASTOPOD_ENABLED', true);
define('CASTOPOD_URL', 'https://kute.o-k-i.net');
define('CASTOPOD_PODCAST_SLUGS', ['annu_kute_cedric']);
define('CASTOPOD_EPISODES_COUNT', 5);
// Mastodon (compte BOKANTE)
define('MASTODON_INSTANCE_URL', 'https://bokante.o-k-i.net');
define('MASTODON_URL', 'https://bokante.o-k-i.net/@cedric');
// Cache : indispensable pour Castopod (rate limit RSS)
define('CACHE_ENABLED', true);
define('CACHE_DURATION', 3600);
🎨 Personnalisation de l’apparence
-
🖼️ Remplacez
img/logo.pngpar votre logo (et les favicons du dossierimg/) -
🎨 Modifiez les couleurs dans
css/styles.css -
📝 Textes : la plupart passent par les constantes de
config.local.php(bloc « À propos », libellés Mastodon, etc.)
Fichiers d’instance à personnaliser
Les fichiers sitemap.xml, robots.txt, site.webmanifest, mentions-legales.php et dons.php sont ignorés par Git : copiez les .sample puis adaptez-les. Les exemples fournis utilisent le domaine générique example.com : remplacez-le par votre domaine réel. Dans mentions-legales.php, remplacez aussi le placeholder VOTRE-DATE-MAJ.
🧭 Ordre des sections sur la page d’accueil
La page d’accueil assemble les sections dans cet ordre, chacune conditionnée par sa configuration :
-
Hero — si
HERO_TYPE≠'none'(live, vidéo ou playlist) -
Podcasts Castopod — si
CASTOPOD_ENABLEDetCASTOPOD_URLnon vide -
Morceaux Funkwhale — si
FUNKWHALE_ENABLEDetFUNKWHALE_URLnon vide -
Timeline Mastodon — toujours affichée
-
Articles WordPress — si
WORDPRESS_ENABLEDetWORDPRESS_URLnon vide -
Hashtags populaires (
POPULAR_TAGS) -
Shorts — carrousel
-
Dernières vidéos + bouton « Voir plus »
-
Tendances + « Voir plus »
-
Une section par catégorie prioritaire (
PRIORITY_CATEGORIES, seulement si elle contient des vidéos) + « Voir plus » -
Bloc « À propos » (si
MOVEMENT_DESCRIPTIONdéfini) + aside hashtags
Mise en page desktop : hero/Castopod/Funkwhale + Mastodon + WordPress s’organisent en colonnes (1fr 2fr, ou 1fr 2fr 1fr avec WordPress) ; empilés verticalement sur mobile.
🛡️ Sécurité
Ce que l’application applique déjà
-
CSP dynamique :
default-src 'self'avec nonce unique par requête pour les scripts/styles inline ; origines PeerTube/Mastodon/CDN ajoutées automatiquement ; assouplissementslocalhostpour le développement -
En-têtes :
X-Frame-Options: SAMEORIGIN,X-Content-Type-Options: nosniff,X-XSS-Protection,Referrer-Policy: strict-origin-when-cross-origin,Permissions-Policyrestrictive, HSTS (HTTPS uniquement), CORP/COOPsame-originNoteL’en-tête COEP a été volontairement retiré (commit 62d4d99) :require-corpbloquait les embeds cross-origin de PeerTube, Mastodon, Castopod et Funkwhale. -
CSRF : jeton stateless HMAC-SHA256 (timestamp + signature, validité 1 h) injecté en
<meta>, vérifié parhash_equalssur l’endpoint AJAX ; vérification de l’en-têteOriginou duReferer -
Anti-SSRF : validation des URLs d’instances (schéma http/https, blocage IP privées et
localhost), liste blanche des endpoints de l’API PeerTube, cURL sans redirections -
Anti-XSS : échappement systématique des sorties ; validation des entrées (UUID vidéo, requête de recherche ≤ 200 car., numéros de page, ID de catégorie 1–20)
Configuration serveur (Nginx, recommandé)
Le fichier conf/nginx.conf.sample est l’équivalent Nginx complet du .htaccess : mêmes protections (fichiers de configuration et .sample, dossiers /includes/, /cache/, /docs/, /conf/, fichiers cachés, pas de listing), masquage de l’extension .php (URLs propres /video au lieu de /video.php), no-cache pour sw.js et site.webmanifest, plus le cache des assets et gzip. Le bloc :80 sert le site immédiatement (aucun certificat requis) ; le bloc 443 et la redirection HTTPS sont générés par certbot --nginx (voir DEPLOY.adoc).
# Adaptez les chemins dans conf/nginx.conf.sample puis :
sudo cp conf/nginx.conf.sample /etc/nginx/sites-available/example.com
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Configuration serveur (Apache, alternative)
cp conf/.htaccess.sample .htaccess
Protections incluses : blocage des fichiers de configuration et .sample, des dossiers /includes/, /cache/, /docs/, /conf/ ; pas de listing de répertoires ; fichiers cachés bloqués ; HTTPS forcé ; masquage de l’extension .php.
|
Warning
|
Cette configuration est essentielle : sans elle, vos fichiers de configuration sont exposés aux visiteurs. |
📱 Progressive Web App (PWA)
Fonctionnalités
-
📲 Installation native : bouton « Installer » dans le header à la première visite
-
🌐 Mode hors ligne : cache des pages visitées et des ressources statiques
-
📡 Détection de connexion : indicateur visuel en cas de perte réseau
-
🔄 Mise à jour sans friction : quand une nouvelle version est déployée, un modal stylé la propose ; en acceptant, l’utilisateur la reçoit immédiatement — aucune purge manuelle du cache navigateur nécessaire
Stratégies de cache du Service Worker (sw.js)
| Type de requête | Stratégie | Détail |
|---|---|---|
Ressources statiques ( |
Cache First |
Mise en cache à l’installation et au premier accès ; repli sur |
Pages ( |
Network First |
Réseau d’abord, cache en secours hors ligne |
API et AJAX ( |
Network Only |
Jamais de cache ; réponse JSON 503 synthétique hors ligne |
Cycle de mise à jour automatique
Le mécanisme (js/pwa-update.js + sw.js) garantit que les visiteurs reçoivent les nouvelles versions sans purger manuellement le cache de leur navigateur :
-
Au déploiement, bumpez le suffixe de version des noms de cache dans
sw.js(formatJJMMAAAA-HHMM, ex.24072026-0612) -
Au chargement d’une page, le navigateur détecte le nouveau
sw.jset installe le nouveau Service Worker en arrière-plan ; il reste en attente (pas deskipWaiting()automatique) -
Un modal propose la mise à jour à l’utilisateur :
-
« Mettre à jour » → le SW en attente reçoit
SKIP_WAITING, purge les anciens caches à l’activation, prend le contrôle (controllerchange) et la page se recharge sur la nouvelle version -
« Plus tard » → le modal se ferme ; la mise à jour est reproposée au prochain chargement
-
-
Une mise à jour déjà installée lors d’une visite précédente mais jamais activée est également signalée
|
Warning
|
Pour que la détection fonctionne, sw.js ne doit pas être caché longtemps côté serveur : les configurations fournies (conf/nginx.conf.sample, conf/.htaccess.sample) le servent en no-cache (ainsi que site.webmanifest). Si vous utilisez votre propre configuration serveur, reproduisez cette règle.
|
Installation par les visiteurs
-
🌐 Chrome/Edge : menu → « Installer example.com »
-
🍎 Safari iOS : Partager → « Ajouter à l’écran d’accueil »
-
🦊 Firefox Android : menu → « Installer »
📊 Analytics (Plausible)
Le site est pré-configuré pour Plausible Analytics (sans cookies, conforme RGPD, données anonymes), mais le script est désactivé par défaut.
Pour l’activer :
-
Ouvrez
index.phpet décommentez le snippet Plausible (balise<script defer data-domain=… src="https://plausible.io/js/script…js">et son shimwindow.plausible). -
C’est tout : la CSP autorise déjà
https://plausible.ioenscript-srcetconnect-src, et ledata-domainutilise automatiquement le domaine courant.
Données alors collectées (anonymes) : pages visitées, pays d’origine (IP non stockée), type d’appareil, navigateur, temps passé.
🚀 Déploiement sur serveur mutualisé
-
🐘 Vérifiez que l’hébergeur fournit PHP 7.4+ et les extensions requises
-
🔒 Activez HTTPS (obligatoire pour la PWA)
-
📤 Transférez les fichiers via FTP à la racine du site
-
🔧 Permissions :
644pour les fichiers,755pour les dossiers ;cache/accessible en écriture -
📄 Créez les fichiers d’instance depuis les
.sample(config, manifest, robots, sitemap, mentions légales, dons) -
🛡️ Copiez
conf/.htaccess.sampleen.htaccess(Apache) -
🌐 Faites pointer votre domaine (ex.
example.com) vers le dossier d’installation -
🧪 Testez la PWA via les outils de développement du navigateur (onglet Application)
👨💻 Développement et contribution
-
🌿 Créez une branche :
git checkout -b ma-fonctionnalite -
💾 Committez avec des messages atomiques en anglais, au format conventionnel (< 70 caractères) :
-
feat: add …,fix: prevent …,docs: update …,refactor: …,typo: …
-
-
📤 Poussez :
git push origin ma-fonctionnalite -
🔀 Ouvrez une pull request
Bonnes pratiques du dépôt :
-
Les remontées de bugs et propositions sont les bienvenues sur le dépôt du fork ; les améliorations génériques peuvent être proposées en upstream (FEDIVERSE OKI)
-
Ne versionnez jamais
config.local.phpni les fichiers dérivés des.sample -
Quelques constantes sont définies mais non utilisées par le code actuel (
ENABLE_SEARCH,ENABLE_COMMENTS,ENABLE_USER_ACCOUNTS,TAG_SHORT) : ne vous en servez pas comme points d’extension
📄 Exporter la documentation (HTML / PDF)
Ce README peut être exporté en HTML et en PDF en une seule commande (tout fichier .adoc peut être passé en argument) :
docs/generate-readme-pdf.sh # README.html + README.pdf
docs/generate-readme-pdf.sh --html # HTML seulement
docs/generate-readme-pdf.sh --pdf # PDF seulement
docs/generate-readme-pdf.sh DEPLOY.adoc # DEPLOY.html + DEPLOY.pdf
docs/generate-readme-pdf.sh DEPLOY.adoc --pdf # DEPLOY.pdf seulement
Les fichiers sont produits à la racine du projet (nommés d’après la source) et ignorés par Git (.gitignore).
Prérequis : asciidoctor, chromium (rendu headless) et le paquet fonts-noto-color-emoji (émojis couleur dans le PDF). La feuille de style docs/docinfo.html (fallback emoji + règles d’impression) est injectée automatiquement via le mécanisme docinfo d’Asciidoctor.
|
Note
|
asciidoctor-pdf (utilisé par l’extension AsciiDoc de VSCodium) n’est pas utilisable ici : son moteur Prawn ne sait pas embarquer les polices emoji couleur (CBDT/COLR), les émojis disparaissent du PDF. Chromium les gère nativement.
|
✅ Vérifications locales et CI/CD
Avant de pousser, exécutez les vérifications locales complètes :
scripts/check.sh
Ce script lance : lint PHP (php -l sur tous les fichiers, samples inclus), lint JS (node --check), rendu AsciiDoc, validation JSON (site.webmanifest.sample) et XML (sitemap.xml.sample, browserconfig.xml), shellcheck. Un outil manquant est signalé et le check correspondant ignoré.
Pipelines Gitea Actions (.gitea/workflows/) :
-
Vérification PR (
check-pr.yml) : lint PHP et JS, tests unitaires PHP et JS bloquants sur toute pull request versmain -
Déploiement PROD (
deploy-prod.yml) : lint PHP et JS, tests unitaires PHP et JS sur push surmain, puis déploiement en SSH sur le serveur (git pull --ff-only+ bump de version du Service Worker, qui déclenche le modal de mise à jour chez les visiteurs)
Les validations AsciiDoc, JSON, XML et shellcheck ne sont pas exécutées dans le CI : elles doivent être passées en local avec scripts/check.sh.
La mise en place complète du serveur de production (clone, fichiers d’instance, clés SSH, secrets Gitea) est documentée dans DEPLOY.adoc.
🧪 Tests
Le projet dispose de trois niveaux de tests, tous sans framework lourd (aucune dépendance commitée dans le repo) :
-
Tests unitaires PHP (
tests/php/) : script PHP natif (php tests/php/run.php). Couvre les validateurs (security.php), le cache (simple-cache.php), les formateurs (config.php) et le rendu Markdown. -
Tests unitaires JS (
tests/js/) :node:testnatif (node tests/js/run.js). CouvreCountdownTimeret l’adaptateur Pleroma → Mastodon. -
Tests E2E (
tests/e2e/) : Playwright (Python), installé en local uniquement. Couvre la homepage, la page vidéo et l’endpoint AJAX « Voir plus ».
# Tests unitaires PHP et JS (aussi lancés par scripts/check.sh)
php tests/php/run.php
node tests/js/run.js
# Tests E2E (nécessite Playwright installé hors repo)
python3 -m venv /tmp/test-venv
/tmp/test-venv/bin/pip install playwright pytest
/tmp/test-venv/bin/playwright install chromium
/tmp/test-venv/bin/python -m pytest tests/e2e/
|
Note
|
Les tests E2E démarrent un serveur PHP local (php -S) et s’appuient sur la configuration par défaut. Les appels réseau vers PeerTube sont sautés proprement si l’instance est injoignable.
|
📜 Licence
Copyright © 2025 Cédric Famibelle-Pronzola & ORGANISATION KA INTERNATIONALE
Ce programme est un logiciel libre : vous pouvez le redistribuer et/ou le modifier selon les termes de la licence publique générale GNU Affero publiée par la Free Software Foundation, soit la version 3 de la licence, soit (à votre choix) toute version ultérieure.
Ce programme est distribué dans l’espoir qu’il sera utile, mais SANS AUCUNE GARANTIE ; sans même la garantie implicite de COMMERCIALISATION ou d’ADAPTATION À UN USAGE PARTICULIER. Voir la licence publique générale GNU Affero pour plus de détails (texte intégral dans LICENSE).
🇬🇧 English version
📖 Description
ANNU KUTE CED is a responsive web interface acting as a multimedia hub for the podcast of the same name. It brings together in a single place:
-
🎥 the podcast’s PeerTube channel (videos, shorts, live streams);
-
🎙️ its Castopod account (latest episodes, built-in audio playback);
-
📡 its Mastodon timeline (news and announcements).
The platform is lightweight, deployable on shared hosting (PHP + web server, no database), and optimized for both mobile and desktop. It is also an installable Progressive Web App with offline support.
🎯 Mission: provide a single, free and decentralized entry point to discover, listen to and follow the ANNU KUTE CED podcast without relying on Big Tech platforms.
🌳 Project origin
This application is a fork of FEDIVERSE OKI, developed by the ORGANISATION KA INTERNATIONALE (OKI), itself derived from the kaubuntu.re project by the Ka-Ubuntu movement. The original license (GNU AGPL v3) is preserved and respected.
| Item | Detail |
|---|---|
Upstream repository |
https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI |
This fork’s repository |
|
Domain (example) |
|
Governance |
Application maintained by OKI; fork carried by the repository owner cedric (Cédric Famibelle-Pronzola) |
License |
GNU Affero General Public License v3 (AGPL-V3) or later |
🔗 Aggregated sources and instances
The hub is configured by default to aggregate the ANNU KUTE CED podcast’s sources:
| Service | Instance | Account / channel |
|---|---|---|
🎥 PeerTube (videos, lives) |
GADE — |
|
🎙️ Castopod (audio episodes) |
KUTE — |
|
📡 Mastodon (timeline) |
BOKANTE — |
|
🎵 Funkwhale (music, optional, disabled by default) |
MIZIK — |
random tracks from the instance |
All of these sources can be changed in includes/config.local.php (see Configuration).
✨ Features
🎥 PeerTube videos
-
Recent, trending and per-category videos (configurable)
-
Shorts: dedicated carousel for portrait videos under 3 minutes (touch scrolling)
-
Full video page: embedded PeerTube player, Markdown description, Creative Commons licence badge, comments (read-only), suggested videos
-
Downloads: direct files and HLS playlists, with resolution and size
-
Sharing: copy link, embed code, e-mail, Facebook, X, WhatsApp, LinkedIn, Telegram
-
Full-text search and hashtag search (
#prefix) -
AJAX "Load more" pagination (protected by CSRF token and Origin check)
📺 Live streams and announcements
-
Live page (
/direct): embeds the PeerTube live stream when one is running, with autoplay -
Next live announcement: date and time automatically converted for 5 territories (Ma’ohi Nui, Martinique/Guadeloupe, French Guiana, France, Kanaky), custom image (4:5, 1:1, 16:9)
-
Configurable homepage hero section: live, single video, playlist (PeerTube / Funkwhale / Castopod) or hidden
🎙️ Podcast and audio
-
Castopod: latest episodes via RSS feed, with a built-in audio player (play/pause, auto-advance to the next episode, one stream at a time)
-
Funkwhale (optional): random track selection from the instance, using the same built-in player
📡 Social networks and external content
-
Embedded Mastodon timeline (vendored mastodon-embed-timeline v4.7.0 library, AGPLv3)
-
Pleroma compatibility: adapter converting Pleroma API responses to the Mastodon format expected by the timeline
-
WordPress (optional): latest posts via the REST API, with featured image and author
📱 PWA and user experience
-
Installable Progressive Web App (automatic install button)
-
Offline mode: visited pages and static assets cached via Service Worker
-
Visual indicator when the connection is lost
-
Dark mode (based on
prefers-color-scheme, persisted inlocalStorage) -
Fully responsive interface (mobile, tablet, desktop)
💝 Donations, countdown and more
-
Donation page: LiberaPay, Ko-fi and Stripe (one-time and monthly donations, suggested amounts)
-
Countdown: multi-timezone launch/maintenance page that locks the whole site until the target date
-
Configurable "About" block (title, two paragraphs, captioned image)
🔍 SEO and structured data
-
Automatically generated JSON-LD:
WebSite(withSearchAction),PodcastSeriesandPodcastEpisode(withAudioObject) for the Castopod podcast,VideoObject(withembedUrl),CollectionPage,BreadcrumbList,Organization -
Meta descriptions on every page and canonical URLs (
link rel="canonical"); search result pages set tonoindex, follow -
Open Graph and Twitter Cards meta tags
-
sitemap.xmlandrobots.txtprovided as samples -
Ready for Plausible Analytics (see Analytics): disabled by default, cookie-free and GDPR-compliant
🛡️ Security
-
Full security headers: dynamic CSP with a per-request nonce, HSTS,
X-Frame-Options,nosniff,Referrer-Policy,Permissions-Policy -
CSRF protection on AJAX requests,
Originheader verification -
Anti-SSRF validation of instance URLs and an allowlist of PeerTube API endpoints
-
Systematic output escaping (anti-XSS)
-
Hardened Apache and Nginx configuration samples in
conf/
🛠️ Tech stack
-
📄 HTML5, 🎨 CSS3 (Media Queries), ⚡ vanilla JavaScript (no framework, no jQuery)
-
🐘 PHP 7.4+ (no database required)
-
🔧 Service Worker (offline cache) and 📋 Web App Manifest (installation)
-
📦 Libraries:
-
🎯 Font Awesome 7 (icons, via cdnjs CDN)
-
📡 mastodon-embed-timeline v4.7.0 (vendored in
js/, AGPLv3) -
📊 Plausible Analytics (pre-configured, to be enabled manually)
-
📁 Project structure
├── .gitea/
│ └── workflows/ # Gitea Actions CI/CD (check-pr, deploy-prod)
├── ajax/
│ └── load-more-videos.php # "Load more" AJAX endpoint (CSRF + Origin)
├── conf/
│ ├── .htaccess.sample # Hardened Apache configuration
│ └── nginx.conf.sample # Hardened Nginx configuration
├── css/ # Stylesheets (styles, video, categories,
│ │ # search, donations, countdown, mastodon,
│ ├── castopod-podcasts.css # castopod, funkwhale, wordpress, grid…)
│ └── …
├── docs/
│ ├── wireframes/ # Original mockups (desktop + mobile PNGs)
│ ├── docinfo.html # Print styles (emoji fallback) for the export
│ └── generate-readme-pdf.sh # Docs export: README.html / README.pdf
├── img/ # Logo, favicons, play icon
├── includes/
│ ├── config.php # Bootstrap + API functions (PeerTube, Castopod,
│ │ # Funkwhale) and formatting helpers
│ ├── config.default.php # Default values for every constant
│ ├── config.local.php.sample # Local configuration template (to copy)
│ ├── security.php # CSRF, CSP + nonce, headers, validators
│ ├── simple-cache.php # File-based API cache (cache/api/)
│ ├── wordpress.php # WordPress REST API integration
│ ├── structured-data.php # JSON-LD generators
│ ├── header.php # Top bar (search, socials, theme…)
│ ├── footer.php # Footer
│ ├── sidebar.php # Side navigation
│ ├── mobile-menu.php # Slide-in mobile menu
│ ├── hero-section.php # Homepage banner (live/video/playlist)
│ ├── featured-videos.php # ⚠️ legacy, unused by current pages
│ ├── recent-videos.php # ⚠️ legacy, unused by current pages
│ ├── pwa-init.php # ⚠️ legacy, PWA now inlined in index.php
│ └── lib/
│ └── markdown.php # Markdown renderer (PeerTube-flavored subset)
├── js/
│ ├── main.js # Global UI (theme, carousel, "Load more"…)
│ ├── audio-player.js # Castopod / Funkwhale audio player
│ ├── countdown.js # Countdown timer
│ ├── search.js # Clickable search result cards
│ ├── mastodon-config.php # Timeline JS config (served as JavaScript)
│ ├── mastodon-timeline.umd.js # mastodon-embed-timeline v4.7.0 (vendored)
│ ├── pleroma-adapter.js # Pleroma → Mastodon API adapter
│ └── pwa-update.js # SW registration + update modal
├── scripts/
│ ├── check.sh # Local quality checks (before pushing)
│ └── warm-cache.php # API cache warm-up (cron)
├── uploads/ # Announcement images (not versioned, except .gitkeep)
├── index.php # Homepage (aggregator)
├── video.php # Video playback page
├── categories.php # Category page
├── recherche.php # Search page
├── direct.php # Live stream / next-live announcement page
├── countdown.php # Countdown page (maintenance)
├── sw.js # Service Worker (PWA)
├── site.webmanifest.sample # PWA manifest sample
├── browserconfig.xml # Windows tiles
├── sitemap.xml.sample # Sitemap sample
├── robots.txt.sample # robots.txt sample
├── mentions-legales.php.sample # Legal notice sample
├── dons.php.sample # Donation page sample
├── LICENSE # GNU AGPL v3
├── DEPLOY.adoc # Server deployment + CI/CD guide
└── README.adoc
|
Note
|
Files marked "legacy" are kept for reference but are not included by any page; the homepage renders those sections internally. |
📋 Requirements
-
🐘 PHP 7.4+ (recommended: PHP 8.0+)
-
📦 Required PHP extensions:
-
curl— API calls (PeerTube, Castopod, Funkwhale, WordPress) -
json— response handling -
intl— international dates and timezones -
mbstring— multi-byte strings -
xml(SimpleXML) — recommended for Castopod (a regex fallback exists)
-
-
🌐 Web server: Nginx (recommended) or Apache
-
🔒 HTTPS: required for PWA features
intl extension# Ubuntu / Debian
sudo apt-get install php-intl
# Fedora / RHEL / CentOS
sudo dnf install php-intl
# Restart the web server
sudo systemctl restart apache2 # Apache
sudo systemctl restart php8.3-fpm # Nginx + php-fpm
# Check
php -m | grep intl
🚀 Installation
-
📥 Clone the repository:
git clone git@labola.o-k-i.net:cedric/annu-kute-ced.git cd annu-kute-ced -
📦 Check the PHP requirements (see above).
-
🔧 Create your local configuration and instance files:
cp includes/config.local.php.sample includes/config.local.php cp site.webmanifest.sample site.webmanifest cp robots.txt.sample robots.txt cp sitemap.xml.sample sitemap.xml cp mentions-legales.php.sample mentions-legales.php cp dons.php.sample dons.php -
🛡️ Install the hardened server configuration from
conf/(see Security). -
✍️ Make the
cache/directory writable by the web server (created automatically on the first API call). -
🔒 Make sure HTTPS is enabled (mandatory for the PWA).
-
🌐 Point your domain (e.g.
example.com) to the project root.
⚙️ Configuration
The three-file system
| File | Role | Versioned? |
|---|---|---|
|
Bootstrap: loads the other files and holds all business functions |
✅ Yes |
|
Default values for every constant (defined only if missing) |
✅ Yes |
|
Your local overrides — take precedence as they load first |
❌ No ( |
To customize the instance, never edit config.default.php: define the constant in config.local.php. Since it loads first, your values always win.
cp includes/config.local.php.sample includes/config.local.php
Constants reference
Default values from includes/config.default.php.
| Constant | Default | Description |
|---|---|---|
|
|
Displayed domain name (legal notice…) |
|
|
Short organization name |
|
|
Full organization name |
| Constant | Default | Description |
|---|---|---|
|
|
PeerTube instance URL (required) |
|
|
Displayed instance name |
|
|
Optional API key ( |
|
|
Show view counts on video cards |
|
|
Featured categories: |
|
|
Display timezone (PHP list) |
| Constant | Default | Description |
|---|---|---|
|
|
|
|
|
PeerTube account watched for live streams (hero + |
|
|
Video UUID ( |
|
|
Title (accessibility) |
|
|
|
|
|
Base URL of the playlist platform |
|
|
Playlist ID; for Castopod: |
|
|
Optional texts (otherwise fetched via API) |
| Constant | Default | Description |
|---|---|---|
|
|
Enable the homepage podcast section |
|
|
Castopod instance URL |
|
|
Podcast slug(s); RSS feed = |
|
|
Total number of episodes displayed |
|
Warning
|
Castopod enforces a strict rate limit (HTTP 429) on RSS feeds. The code applies exponential backoff (5 s, 10 s, 15 s) only on HTTP 429. Enable the cache (CACHE_ENABLED) and warm it with scripts/warm-cache.php to avoid these delays on every page.
|
| Constant | Default | Description |
|---|---|---|
|
|
Enable the music section |
|
|
Funkwhale instance URL |
|
|
Random local tracks displayed (50 cached) |
| Constant | Default | Description |
|---|---|---|
|
|
Mastodon instance (or Pleroma, via the adapter) |
|
|
Public account URL ("See more" links, footer) |
|
|
Post date locale |
|
|
Button labels |
|
|
Posts fetched / displayed |
|
(undefined) |
S3 media origin (added to the CSP |
|
Note
|
The timeline has no enable flag: it is always displayed on the homepage. |
| Constant | Default | Description |
|---|---|---|
|
|
Enable the posts section |
|
|
WordPress site URL (no trailing |
|
|
Number of posts (REST API |
| Constant | Default | Description |
|---|---|---|
|
|
Enable the system (heart icon, "Support" link, |
|
|
External platform URLs. By default the LiberaPay link points to OKI, the application maintainer. |
|
|
Enable the Stripe interface (one-time / monthly tabs) |
|
|
Stripe payment links per amount |
|
|
Stripe monthly subscription links |
|
|
Suggested amounts |
|
|
Currency |
|
Explanatory text |
Message shown on |
|
Note
|
At least one platform (LiberaPay, Ko-fi or Stripe) must be configured for dons.php to render, otherwise the page returns a 500 error. By default only OKI’s LiberaPay account is set.
|
|
Tip
|
If you deploy an independent instance, replace LIBERAPAY_URL with your own link and update or clear DONATIONS_OKI_DISCLAIMER.
|
| Constant | Default | Description |
|---|---|---|
|
|
⚠️ When |
|
|
Target date ( |
|
5 territories |
|
| Constant | Default | Description |
|---|---|---|
|
|
Show the announcement when no live stream is running |
|
|
Texts (date and time appended automatically) |
|
|
Live date ( |
|
|
Announcement image (used only if the file exists) |
| Constant | Default | Description |
|---|---|---|
|
|
Site name (titles, meta) |
|
|
SEO description |
|
|
Image paths |
|
|
|
|
Contact e-mail (footer) |
| Constant | Default | Description |
|---|---|---|
|
|
Copyright holder |
|
|
Webmaster |
|
o2Switch values |
Hosting provider details |
|
Legal contact |
|
|
AGPL-V3 + GNU URL |
Code license |
|
|
Source code repository (required by the AGPL for a network service) |
|
|
Service description sentence |
| Constant | Default | Description |
|---|---|---|
|
|
Tag used by |
|
|
Max short duration (s); portrait ratio ( |
|
|
Hashtags in the sidebar, footer and mobile menu |
|
|
Hashtags displayed on the homepage |
| Constant | Default | Description |
|---|---|---|
|
|
Videos per page (search) |
|
|
Results fetched per search |
|
|
Homepage section sizes |
|
|
Shorts displayed / search pool |
|
|
Videos loaded per "Load more" click |
|
|
Reserved (sections not currently displayed) |
| Constant | Default | Description |
|---|---|---|
|
|
Enable caching for Castopod and Funkwhale. Essential against the Castopod RSS rate limit; empty results (error, 429) are never cached |
|
|
Cache lifetime (s) |
|
Note
|
The PeerTube API and WordPress cache (includes/simple-cache.php) is always on, regardless of CACHE_ENABLED, with per-endpoint TTLs (categories 1 h, videos/search 10 min, WP posts 15 min, accounts 5 min). Files are stored in cache/api/.
|
|
Tip
|
To prevent the first visitor from paying the cost of API calls on a cold cache, warm the cache via cron: php /var/www/annu-kute-ced/scripts/warm-cache.php (every 5 minutes for example).
|
| Constant | Default | Description |
|---|---|---|
|
|
Secret key used to sign stateless CSRF tokens. Must be replaced in |
| Constant | Description |
|---|---|
|
Block title (template fallback: "À propos") |
|
First paragraph; commenting this line hides the whole block |
|
Second paragraph (optional) |
|
Image ( |
Complete config.local.php example
<?php
define('APP_HOST_NAME', 'example.com');
// PeerTube (the podcast's GADE channel)
define('PEERTUBE_URL', 'https://gade.o-k-i.net');
define('PEERTUBE_DISPLAY_NAME', 'gade.o-k-i.net');
// Hero: channel live stream, otherwise next-live announcement
define('HERO_TYPE', 'live');
define('LIVE_ACCOUNT_NAME', 'annu_kute_ced');
define('NEXT_LIVE_ENABLED', true);
define('NEXT_LIVE_TITLE', 'Prochain live');
define('NEXT_LIVE_DESCRIPTION', 'Recording the next episode!');
define('NEXT_LIVE_DATE', '2025-10-11 10:00:00');
// Castopod (the podcast's KUTE account)
define('CASTOPOD_ENABLED', true);
define('CASTOPOD_URL', 'https://kute.o-k-i.net');
define('CASTOPOD_PODCAST_SLUGS', ['annu_kute_cedric']);
define('CASTOPOD_EPISODES_COUNT', 5);
// Mastodon (BOKANTE account)
define('MASTODON_INSTANCE_URL', 'https://bokante.o-k-i.net');
define('MASTODON_URL', 'https://bokante.o-k-i.net/@cedric');
// Cache: essential for Castopod (RSS rate limit)
define('CACHE_ENABLED', true);
define('CACHE_DURATION', 3600);
🎨 Appearance customization
-
🖼️ Replace
img/logo.pngwith your logo (and the favicons inimg/) -
🎨 Edit the colors in
css/styles.css -
📝 Most texts are set through
config.local.phpconstants ("About" block, Mastodon labels, etc.)
Instance files to customize
The sitemap.xml, robots.txt, site.webmanifest, mentions-legales.php and dons.php files are Git-ignored: copy the .sample files then adapt them. The provided samples use the generic example.com domain: replace it with your real domain. In mentions-legales.php, also replace the VOTRE-DATE-MAJ placeholder.
🧭 Homepage section order
The homepage assembles sections in this order, each gated by its configuration:
-
Hero — if
HERO_TYPE≠'none'(live, video or playlist) -
Castopod podcasts — if
CASTOPOD_ENABLEDandCASTOPOD_URLis not empty -
Funkwhale tracks — if
FUNKWHALE_ENABLEDandFUNKWHALE_URLis not empty -
Mastodon timeline — always displayed
-
WordPress posts — if
WORDPRESS_ENABLEDandWORDPRESS_URLis not empty -
Popular hashtags (
POPULAR_TAGS) -
Shorts — carousel
-
Latest videos + "Load more" button
-
Trending + "Load more"
-
One section per priority category (
PRIORITY_CATEGORIES, only when it has videos) + "Load more" -
"About" block (if
MOVEMENT_DESCRIPTIONis defined) + hashtags aside
Desktop layout: hero/Castopod/Funkwhale + Mastodon + WordPress are arranged in columns (1fr 2fr, or 1fr 2fr 1fr with WordPress); stacked vertically on mobile.
🛡️ Security
What the application already enforces
-
Dynamic CSP:
default-src 'self'with a unique per-request nonce for inline scripts/styles; PeerTube/Mastodon/CDN origins added automatically;localhostrelaxations for development -
Headers:
X-Frame-Options: SAMEORIGIN,X-Content-Type-Options: nosniff,X-XSS-Protection,Referrer-Policy: strict-origin-when-cross-origin, restrictivePermissions-Policy, HSTS (HTTPS only), CORP/COOPsame-originNoteThe COEP header was deliberately removed (commit 62d4d99):require-corpbroke cross-origin embeds from PeerTube, Mastodon, Castopod and Funkwhale. -
CSRF: stateless HMAC-SHA256 token (timestamp + signature, 1 h validity) injected as a
<meta>tag, verified withhash_equalson the AJAX endpoint;OriginorRefererheader verification -
Anti-SSRF: instance URL validation (http/https scheme, blocking private IPs and
localhost), allowlist of PeerTube API endpoints, cURL without redirects -
Anti-XSS: systematic output escaping; input validation (video UUID, search query ≤ 200 chars, page numbers, category ID 1–20)
Server configuration (Nginx, recommended)
The conf/nginx.conf.sample file is the complete Nginx equivalent of the .htaccess: same protections (configuration and .sample files, /includes/, /cache/, /docs/, /conf/ directories, hidden files, no directory listing), .php extension masking (clean URLs /video instead of /video.php), no-cache for sw.js and site.webmanifest, plus asset caching and gzip. The :80 block serves the site immediately (no certificate required); the 443 block and the HTTPS redirect are generated by certbot --nginx (see DEPLOY.adoc).
# Adjust the paths in conf/nginx.conf.sample, then:
sudo cp conf/nginx.conf.sample /etc/nginx/sites-available/example.com
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Server configuration (Apache, alternative)
cp conf/.htaccess.sample .htaccess
Included protections: blocking of configuration and .sample files, of the /includes/, /cache/, /docs/, /conf/ directories; no directory listing; hidden files blocked; HTTPS enforced; .php extension masking.
|
Warning
|
This configuration is essential: without it, your configuration files are exposed to visitors. |
📱 Progressive Web App (PWA)
Features
-
📲 Native installation: an "Install" button in the header on first visit
-
🌐 Offline mode: visited pages and static assets cached
-
📡 Connection detection: visual indicator when the network is lost
-
🔄 Frictionless updates: when a new version is deployed, a styled modal offers it; on accept, the user gets it immediately — no manual browser-cache purge needed
Service Worker caching strategies (sw.js)
| Request type | Strategy | Detail |
|---|---|---|
Static assets ( |
Cache First |
Cached at install time and on first access; falls back to |
Pages ( |
Network First |
Network first, cache fallback when offline |
API and AJAX ( |
Network Only |
Never cached; synthetic 503 JSON response when offline |
Automatic update cycle
This mechanism (js/pwa-update.js + sw.js) ensures visitors receive new versions without manually purging their browser cache:
-
When deploying, bump the version suffix of the cache names in
sw.js(formatDDMMYYYY-HHMM, e.g.24072026-0612) -
On page load, the browser detects the new
sw.jsand installs the new Service Worker in the background; it stays waiting (no automaticskipWaiting()) -
A modal offers the update to the user:
-
"Mettre à jour" → the waiting SW receives
SKIP_WAITING, purges old caches on activation, takes control (controllerchange) and the page reloads onto the new version -
"Plus tard" → the modal closes; the update is offered again on the next page load
-
-
An update already installed during a previous visit but never activated is also reported
|
Warning
|
For detection to work, sw.js must not be cached for long by the server: the provided configurations (conf/nginx.conf.sample, conf/.htaccess.sample) serve it with no-cache (as well as site.webmanifest). If you use your own server configuration, reproduce this rule.
|
Installation by visitors
-
🌐 Chrome/Edge: menu → "Install example.com"
-
🍎 Safari iOS: Share → "Add to Home Screen"
-
🦊 Firefox Android: menu → "Install"
📊 Analytics (Plausible)
The site is pre-configured for Plausible Analytics (cookie-free, GDPR-compliant, anonymous data), but the script is disabled by default.
To enable it:
-
Open
index.phpand uncomment the Plausible snippet (the<script defer data-domain=… src="https://plausible.io/js/script…js">tag and itswindow.plausibleshim). -
That’s it: the CSP already allows
https://plausible.ioinscript-srcandconnect-src, and thedata-domainautomatically uses the current domain.
Data then collected (anonymous): visited pages, country of origin (IP not stored), device type, browser, time spent.
🚀 Deploying to shared hosting
-
🐘 Check that the host provides PHP 7.4+ and the required extensions
-
🔒 Enable HTTPS (mandatory for the PWA)
-
📤 Upload the files via FTP to the site root
-
🔧 Permissions:
644for files,755for directories;cache/must be writable -
📄 Create the instance files from the
.samplefiles (config, manifest, robots, sitemap, legal notice, donations) -
🛡️ Copy
conf/.htaccess.sampleto.htaccess(Apache) -
🌐 Point your domain (e.g.
example.com) to the installation directory -
🧪 Test the PWA via the browser developer tools (Application tab)
👨💻 Development and contributing
-
🌿 Create a branch:
git checkout -b my-feature -
💾 Commit with atomic, English messages, in conventional format (< 70 characters):
-
feat: add …,fix: prevent …,docs: update …,refactor: …,typo: …
-
-
📤 Push:
git push origin my-feature -
🔀 Open a pull request
Repository best practices:
-
Bug reports and suggestions are welcome on the fork’s repository; generic improvements can be proposed upstream (FEDIVERSE OKI)
-
Never version
config.local.phpor files derived from the.samplefiles -
A few constants are defined but unused by the current code (
ENABLE_SEARCH,ENABLE_COMMENTS,ENABLE_USER_ACCOUNTS,TAG_SHORT): do not rely on them as extension points
📄 Exporting the documentation (HTML / PDF)
This README can be exported to HTML and PDF with a single command (any .adoc file can be passed as argument):
docs/generate-readme-pdf.sh # README.html + README.pdf
docs/generate-readme-pdf.sh --html # HTML only
docs/generate-readme-pdf.sh --pdf # PDF only
docs/generate-readme-pdf.sh DEPLOY.adoc # DEPLOY.html + DEPLOY.pdf
docs/generate-readme-pdf.sh DEPLOY.adoc --pdf # DEPLOY.pdf only
Files are produced in the project root (named after the source) and are Git-ignored (.gitignore).
Requirements: asciidoctor, chromium (headless rendering) and the fonts-noto-color-emoji package (color emoji in the PDF). The docs/docinfo.html stylesheet (emoji fallback + print rules) is automatically injected through Asciidoctor’s docinfo mechanism.
|
Note
|
asciidoctor-pdf (used by VSCodium’s AsciiDoc extension) cannot be used here: its Prawn engine cannot embed color-emoji fonts (CBDT/COLR), so emoji disappear from the PDF. Chromium handles them natively.
|
✅ Local checks and CI/CD
Before pushing, run the full local checks:
scripts/check.sh
This script runs: PHP lint (php -l on every file, samples included), JS lint (node --check), AsciiDoc rendering, JSON (site.webmanifest.sample) and XML (sitemap.xml.sample, browserconfig.xml) validation, shellcheck. A missing tool is reported and its check skipped.
Gitea Actions pipelines (.gitea/workflows/):
-
PR check (
check-pr.yml): blocking PHP and JS lint plus PHP and JS unit tests on every pull request tomain -
PROD deployment (
deploy-prod.yml): PHP and JS lint plus PHP and JS unit tests on push tomain, then SSH deployment to the server (git pull --ff-only+ Service Worker version bump, which triggers the update modal for visitors)
AsciDoc, JSON, XML and shellcheck validation are not run in CI: they must be run locally with scripts/check.sh.
The full production server setup (clone, instance files, SSH keys, Gitea secrets) is documented in DEPLOY.adoc.
🧪 Tests
The project has three levels of tests, all without heavy frameworks (no dependencies committed to the repo):
-
PHP unit tests (
tests/php/): native PHP script (php tests/php/run.php). Covers validators (security.php), cache (simple-cache.php), formatters (config.php) and Markdown rendering. -
JS unit tests (
tests/js/): nativenode:test(node tests/js/run.js). CoversCountdownTimerand the Pleroma → Mastodon adapter. -
E2E tests (
tests/e2e/): Playwright (Python), installed locally only. Covers the homepage, video page and the "Load more" AJAX endpoint.
# PHP and JS unit tests (also run by scripts/check.sh)
php tests/php/run.php
node tests/js/run.js
# E2E tests (requires Playwright installed outside the repo)
python3 -m venv /tmp/test-venv
/tmp/test-venv/bin/pip install playwright pytest
/tmp/test-venv/bin/playwright install chromium
/tmp/test-venv/bin/python -m pytest tests/e2e/
|
Note
|
E2E tests start a local PHP server (php -S) and rely on the default configuration. Network calls to PeerTube are skipped gracefully if the instance is unreachable.
|
📜 License
Copyright © 2025 Cédric Famibelle-Pronzola & ORGANISATION KA INTERNATIONALE
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details (full text in LICENSE).