cedric 98c2818c3f
Déploiement PROD / check (push) Successful in 3m24s
Déploiement PROD / deploy (push) Successful in 8s
fix: change cache name
2026-07-26 19:47:15 +04:00
2025-10-17 12:29:10 +04:00
2025-04-07 13:09:47 +04:00
2026-07-26 19:47:15 +04:00

🎙️ ANNU KUTE CED — Hub multimédia du podcast

Sommaire / Contents

🌍 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. Cest aussi une Progressive Web App installable, avec mode hors ligne.

🎯 Mission : offrir un point dentré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 lORGANISATION KA INTERNATIONALE (OKI), elle-même issue du projet kaubuntu.re du mouvement Ka-Ubuntu. La licence dorigine (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

git@labola.o-k-i.net:cedric/annu-kute-ced.git

Domaine (exemple)

https://example.com

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 — https://gade.o-k-i.net

annu_kute_ced

🎙️ Castopod (épisodes audio)

KUTE — https://kute.o-k-i.net

@annu_kute_cedric

📡 Mastodon (timeline)

BOKANTE — https://bokante.o-k-i.net

@cedric

🎵 Funkwhale (musique, optionnel, désactivé par défaut)

MIZIK — https://mizik.o-k-i.net

morceaux aléatoires de linstance

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 dinté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 dorigine)

📺 Directs et annonces

  • Page Direct (/direct) : embed du live PeerTube sil est en cours, avec autoplay

  • Annonce du prochain live : date et heure converties automatiquement pour 5 territoires (Maohi Nui, Martinique/Guadeloupe, Guyane, France, Kanaky), image personnalisable (4:5, 1:1, 16:9)

  • Section hero daccueil 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 linstance, 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 lAPI Pleroma au format Mastodon attendu par la timeline

  • WordPress (optionnel) : derniers articles via lAPI REST, avec image à la une et auteur

📱 PWA et confort dutilisation

  • Progressive Web App installable (bouton dinstallation 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é en localStorage)

  • 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 (avec SearchAction), PodcastSeries et PodcastEpisode (avec AudioObject) pour le podcast Castopod, VideoObject (avec embedUrl), CollectionPage, BreadcrumbList, Organization

  • Meta descriptions sur toutes les pages et URL canoniques (link rel="canonical") ; pages de résultats de recherche en noindex, follow

  • Balises Open Graph et Twitter Cards

  • sitemap.xml et robots.txt fournis 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 len-tête Origin

  • Validation anti-SSRF des URLs dinstances et liste blanche des endpoints de lAPI 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)
├── 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 daccueil 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

Installation de lextension 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

  1. 📥 Clonez le dépôt :

    git clone git@labola.o-k-i.net:cedric/annu-kute-ced.git
    cd annu-kute-ced
  2. 📦 Vérifiez les prérequis PHP (voir ci-dessus).

  3. 🔧 Créez votre configuration locale et les fichiers dinstance :

    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
  4. 🛡️ Installez la configuration serveur sécurisée depuis conf/ (voir Sécurité).

  5. ✍️ Rendez le dossier cache/ accessible en écriture par le serveur web (créé automatiquement au premier appel API).

  6. 🔒 Assurez-vous que HTTPS est actif (obligatoire pour la PWA).

  7. 🌐 Faites pointer votre domaine (ex. example.com) vers la racine du projet.

⚙️ Configuration

Le système en trois fichiers

Fichier Rôle Versionné ?

includes/config.php

Bootstrap : charge les autres fichiers et contient toutes les fonctions métier

Oui

includes/config.default.php

Valeurs par défaut de chaque constante (définies seulement si absentes)

Oui

includes/config.local.php

Vos surcharges locales — prioritaires car chargées en premier

Non (.gitignore)

Pour personnaliser linstance, 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.

Table 1. Application et organisation
Constante Défaut Description

APP_HOST_NAME

'example.com'

Nom de domaine affiché (mentions légales…)

ORGANIZATION_SHORT_NAME

'ANNU KUTE CED'

Nom court de lorganisation

ORGANIZATION_NAME

'ANNU KUTE CED'

Nom complet de lorganisation

Table 2. PeerTube
Constante Défaut Description

PEERTUBE_URL

'https://gade.o-k-i.net'

URL de linstance PeerTube (obligatoire)

PEERTUBE_DISPLAY_NAME

'gade.o-k-i.net'

Nom affiché de linstance

API_KEY

''

Clé API optionnelle (Authorization: ApiKey …)

SHOW_VIDEO_VIEWS

false

Afficher le nombre de vues sur les cartes vidéo

PRIORITY_CATEGORIES

[10, 15]

Catégories mises en avant : [ID ⇒ 'Nom'], ordre = ordre daffichage (accueil, sidebar, footer, menu mobile)

DEFAULT_TIMEZONE

'America/Guadeloupe'

Fuseau horaire daffichage (liste PHP)

Table 3. Section hero (bannière daccueil)
Constante Défaut Description

HERO_TYPE

'live'

'live' · 'video' · 'playlist' · 'none'

LIVE_ACCOUNT_NAME

'annu_kute_ced'

Compte PeerTube surveillé pour les directs (hero + page /direct)

HERO_VIDEO_ID

'fDE6tvQMXRuBpE4eDDS7kj'

UUID de la vidéo (mode video), extrait de lURL /w/<uuid>

HERO_VIDEO_TITLE

'Vidéo de présentation'

Titre (accessibilité)

PLAYLIST_PLATFORM

'peertube'

'peertube' · 'funkwhale' · 'castopod'

PLAYLIST_INSTANCE_URL

''

URL de base de la plateforme de playlist

PLAYLIST_ID

''

ID de la playlist ; pour Castopod : '@slug'

PLAYLIST_TITLE / PLAYLIST_DESCRIPTION

'' / ''

Textes optionnels (sinon récupérés via API)

Table 4. Castopod (podcast)
Constante Défaut Description

CASTOPOD_ENABLED

true

Activer la section podcast de laccueil

CASTOPOD_URL

'https://kute.o-k-i.net'

URL de linstance Castopod

CASTOPOD_PODCAST_SLUGS

['annu_kute_cedric']

Slug(s) de podcast ; flux RSS = {URL}/@{slug}/feed. Plusieurs podcasts : épisodes fusionnés et triés par date

CASTOPOD_EPISODES_COUNT

5

Nombre total d’épisodes affichés

Warning
Castopod applique un rate limit strict (HTTP 429) sur les flux RSS. Le code patiente 5 s entre chaque podcast. Activez le cache (CACHE_ENABLED) pour éviter ces délais à chaque page.
Table 5. Funkwhale (musique, optionnel)
Constante Défaut Description

FUNKWHALE_ENABLED

false

Activer la section musique

FUNKWHALE_URL

'https://mizik.o-k-i.net'

URL de linstance Funkwhale

FUNKWHALE_TRACKS_COUNT

10

Morceaux aléatoires locaux affichés (50 mis en cache)

Table 6. Mastodon (timeline)
Constante Défaut Description

MASTODON_INSTANCE_URL

'https://bokante.o-k-i.net'

Instance Mastodon (ou Pleroma, via ladaptateur)

MASTODON_URL

'https://bokante.o-k-i.net/@cedric'

URL publique du compte (liens « Voir plus », footer)

MASTODON_DATE_FORMAT

'fr-FR'

Locale de date des posts

MASTODON_BTN_SEE_MORE / MASTODON_BTN_RELOAD

'Voir plus de post' / 'Rafraichir'

Libellés des boutons

MASTODON_MAX_POST_FETCH / MASTODON_MAX_POST_SHOW

'10' / '10'

Posts récupérés / affichés

MASTODON_S3_MEDIA_URL

(non défini)

Origine S3 des médias (ajoutée à la CSP media-src)

Note
La timeline na pas de flag dactivation : elle est toujours affichée sur laccueil.
Table 7. WordPress (optionnel)
Constante Défaut Description

WORDPRESS_ENABLED

false

Activer la section articles

WORDPRESS_URL

''

URL du site WordPress (sans / final)

WORDPRESS_POSTS_COUNT

6

Nombre darticles (API REST /wp-json/wp/v2/)

Table 8. Dons
Constante Défaut Description

DONATIONS_ENABLED

true

Activer le système (icône cœur, lien « Soutenir », page /dons.php)

LIBERAPAY_URL / KOFI_URL

'https://liberapay.com/OKI/donate' / ''

URLs des plateformes externes. Par défaut le lien Liberapay pointe vers OKI, mainteneur de lapplication.

STRIPE_ENABLED

false

Activer linterface Stripe (onglets ponctuel / mensuel)

STRIPE_ONE_TIME_LINKS

[1, 5, 10, 20, 50, 'custom'] ⇒ ''

Liens de paiement Stripe par montant

STRIPE_MONTHLY_LINKS

[1, 5, 10, 20, 50] ⇒ ''

Liens dabonnement mensuel Stripe

DONATION_AMOUNTS

[1, 5, 10, 20, 50]

Montants suggérés

DONATION_CURRENCY

'EUR'

Devise

DONATIONS_OKI_DISCLAIMER

Texte explicite

Message affiché sur /dons.php lorsque les liens pointent vers les comptes dune organisation tierce (OKI par défaut). Laissez vide pour masquer.

Note
Au moins une plateforme (LiberaPay, Ko-fi ou Stripe) doit être configurée pour que dons.php saffiche, 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.
Table 9. Compte à rebours (maintenance / lancement)
Constante Défaut Description

COUNTDOWN_ENABLED

false

⚠️ Si true, toutes les pages redirigent vers countdown.php

COUNTDOWN_TARGET_DATE

'2025-10-11 00:00:00'

Date cible (Y-m-d H:i:s) ; à la fin, redirection vers /

COUNTDOWN_TIMEZONES

5 territoires

[Libellé ⇒ fuseau PHP] affichés sur la page

Table 10. Annonce du prochain live
Constante Défaut Description

NEXT_LIVE_ENABLED

false

Afficher lannonce quand aucun direct nest en cours

NEXT_LIVE_TITLE / NEXT_LIVE_DESCRIPTION

'Prochain live' / 'Rejoignez-nous…'

Textes (date et heure ajoutées automatiquement)

NEXT_LIVE_DATE

''

Date du live (Y-m-d H:i:s)

NEXT_LIVE_IMAGE

'uploads/next-live.jpg'

Image dannonce (utilisée seulement si le fichier existe)

Table 11. Site, réseaux sociaux et contact
Constante Défaut Description

SITE_NAME

'ANNU KUTE CED'

Nom du site (titres, meta)

SITE_DESCRIPTION

'Hub multimédia du podcast ANNU KUTE CED'

Description SEO

SITE_LOGO / SITE_FAVICON

'img/logo.png' / 'img/favicon.png'

Chemins des images

FACEBOOK_URL, X_URL, INSTAGRAM_URL, YOUTUBE_URL, TIKTOK_URL

'#'

'#' ou vide = icône masquée

CONTACT_EMAIL

'kontak@o-k-i.net'

E-mail de contact (footer)

Table 12. Mentions légales
Constante Défaut Description

LEGAL_COPYRIGHT

'ANNU KUTE CED'

Titulaire du copyright

LEGAL_WEBMASTER_NAME / LEGAL_WEBMASTER_EMAIL

'Cédric Famibelle-Pronzola' / 'contact@cedric-pronzola.dev'

Webmaster

LEGAL_HOST_NAME, LEGAL_HOST_COMPANY, LEGAL_HOST_RCS, LEGAL_HOST_ADDRESS

valeurs o2Switch

Coordonnées de lhébergeur

LEGAL_CONTACT_EMAIL

'kontak@o-k-i.net'

Contact juridique

LEGAL_LICENSE / LEGAL_LICENSE_URL

AGPL-V3 + URL GNU

Licence du code

LEGAL_SOURCE_CODE_URL

'https://labola.o-k-i.net/cedric/annu-kute-ced'

Dépôt du code source (exigé par lAGPL pour un service réseau)

LEGAL_SERVICE_DESCRIPTION

'est le hub multimédia du podcast ANNU KUTE CED.'

Phrase de description du service

Table 13. Tags et filtres
Constante Défaut Description

TAG_INDEPENDENCE

'indépendance'

Tag de la fonction getIndependenceVideos() (pas de section daccueil associée actuellement)

SHORTS_MAX_DURATION

180

Durée max dun short (s) ; ratio portrait (aspectRatio ≤ 1) aussi requis

IMPORTANT_TAGS

['ANNUKUTECED']

Hashtags de la sidebar, du footer et du menu mobile

POPULAR_TAGS

['ANNUKUTECED']

Hashtags affichés sur laccueil

Table 14. Pagination et volumes daffichage
Constante Défaut Description

VIDEOS_PER_PAGE

12

Vidéos par page (recherche)

COUNT_VIDEO_SEARCH

20

Résultats récupérés par recherche

RECENT_VIDEOS_COUNT / TRENDING_VIDEOS_COUNT / CATEGORY_VIDEOS_COUNT

6

Taille des sections daccueil

SHORTS_COUNT / SHORTS_COUNT_SEARCH

6 / 100

Shorts affichés / pool de recherche

LOAD_MORE_COUNT

6

Vidéos chargées par clic sur « Voir plus »

FEATURED_VIDEOS_COUNT / INDEPENDENCE_VIDEOS_COUNT

6

Réservés (sections non affichées actuellement)

Table 15. Cache
Constante Défaut Description

CACHE_ENABLED

true

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

CACHE_DURATION

3600

Durée de vie du cache (s)

Note
Le cache de lAPI 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/.
Table 16. Sécurité
Constante Défaut Description

CSRF_SECRET

'change-me-in-config-local-php'

Clé secrète utilisée pour signer les tokens CSRF stateless. À remplacer impérativement dans config.local.php par une valeur aléatoire (php -r "echo bin2hex(random_bytes(32)) . PHP_EOL;")

Table 17. Bloc « À propos » (pas de valeur par défaut — bloc masqué tant que non défini)
Constante Description

MOVEMENT_TITLE

Titre du bloc (défaut template : « À propos »)

MOVEMENT_DESCRIPTION

Premier paragraphe ; commenter cette ligne masque tout le bloc

MOVEMENT_DESCRIPTION_2

Second paragraphe (optionnel)

MOVEMENT_IMAGE / MOVEMENT_IMAGE_ALT / MOVEMENT_CAPTION

Image (img/movement_presentation.png, non versionnée), texte alternatif et légende (HTML simple accepté)

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 lapparence

  1. 🖼️ Remplacez img/logo.png par votre logo (et les favicons du dossier img/)

  2. 🎨 Modifiez les couleurs dans css/styles.css

  3. 📝 Textes : la plupart passent par les constantes de config.local.php (bloc « À propos », libellés Mastodon, etc.)

Fichiers dinstance à 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 daccueil

La page daccueil assemble les sections dans cet ordre, chacune conditionnée par sa configuration :

  1. Hero — si HERO_TYPE'none' (live, vidéo ou playlist)

  2. Podcasts Castopod — si CASTOPOD_ENABLED et CASTOPOD_URL non vide

  3. Morceaux Funkwhale — si FUNKWHALE_ENABLED et FUNKWHALE_URL non vide

  4. Timeline Mastodon — toujours affichée

  5. Articles WordPress — si WORDPRESS_ENABLED et WORDPRESS_URL non vide

  6. Hashtags populaires (POPULAR_TAGS)

  7. Shorts — carrousel

  8. Dernières vidéos + bouton « Voir plus »

  9. Tendances + « Voir plus »

  10. Une section par catégorie prioritaire (PRIORITY_CATEGORIES, seulement si elle contient des vidéos) + « Voir plus »

  11. Bloc « À propos » (si MOVEMENT_DESCRIPTION défini) + aside hashtags

Mise en page desktop : hero/Castopod/Funkwhale + Mastodon + WordPress sorganisent en colonnes (1fr 2fr, ou 1fr 2fr 1fr avec WordPress) ; empilés verticalement sur mobile.

🛡️ Sécurité

Ce que lapplication 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 ; assouplissements localhost pour 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-Policy restrictive, HSTS (HTTPS uniquement), CORP/COOP same-origin

    Note
    Len-tête COEP a été volontairement retiré (commit 62d4d99) : require-corp bloquait 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é par hash_equals sur lendpoint AJAX ; vérification de len-tête Origin ou du Referer

  • Anti-SSRF : validation des URLs dinstances (schéma http/https, blocage IP privées et localhost), liste blanche des endpoints de lAPI 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 120)

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 lextension .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 lextension .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, lutilisateur 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 (/css/, /js/, /img/, CDN cdnjs)

Cache First

Mise en cache à linstallation et au premier accès ; repli sur img/logo.png si une image échoue

Pages (/, *.php…)

Network First

Réseau dabord, cache en secours hors ligne

API et AJAX (/ajax/, mastodon-config.php…)

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 :

  1. Au déploiement, bumpez le suffixe de version des noms de cache dans sw.js (format JJMMAAAA-HHMM, ex. 24072026-0612)

  2. Au chargement dune page, le navigateur détecte le nouveau sw.js et installe le nouveau Service Worker en arrière-plan ; il reste en attente (pas de skipWaiting() automatique)

  3. Un modal propose la mise à jour à lutilisateur :

    • « Mettre à jour » → le SW en attente reçoit SKIP_WAITING, purge les anciens caches à lactivation, 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

  4. Une mise à jour déjà installée lors dune 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 daccueil »

  • 🦊 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 lactiver :

  1. Ouvrez index.php et décommentez le snippet Plausible (balise <script defer data-domain=… src="https://plausible.io/js/script…js"> et son shim window.plausible).

  2. Cest tout : la CSP autorise déjà https://plausible.io en script-src et connect-src, et le data-domain utilise automatiquement le domaine courant.

Données alors collectées (anonymes) : pages visitées, pays dorigine (IP non stockée), type dappareil, navigateur, temps passé.

🚀 Déploiement sur serveur mutualisé

  1. 🐘 Vérifiez que lhébergeur fournit PHP 7.4+ et les extensions requises

  2. 🔒 Activez HTTPS (obligatoire pour la PWA)

  3. 📤 Transférez les fichiers via FTP à la racine du site

  4. 🔧 Permissions : 644 pour les fichiers, 755 pour les dossiers ; cache/ accessible en écriture

  5. 📄 Créez les fichiers dinstance depuis les .sample (config, manifest, robots, sitemap, mentions légales, dons)

  6. 🛡️ Copiez conf/.htaccess.sample en .htaccess (Apache)

  7. 🌐 Faites pointer votre domaine (ex. example.com) vers le dossier dinstallation

  8. 🧪 Testez la PWA via les outils de développement du navigateur (onglet Application)

👨‍💻 Développement et contribution

  1. 🌿 Créez une branche : git checkout -b ma-fonctionnalite

  2. 💾 Committez avec des messages atomiques en anglais, au format conventionnel (< 70 caractères) :

    • feat: add …, fix: prevent …, docs: update …, refactor: …, typo: …

  3. 📤 Poussez : git push origin ma-fonctionnalite

  4. 🔀 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.php ni 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 dextension

📄 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 daprè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 dimpression) est injectée automatiquement via le mécanisme docinfo dAsciidoctor.

Note
asciidoctor-pdf (utilisé par lextension AsciiDoc de VSCodium) nest 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 bloquants sur toute pull request vers main

  • Déploiement PROD (deploy-prod.yml) : lint PHP et JS sur push sur main, 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 plus 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 dinstance, clés SSH, secrets Gitea) est documentée dans DEPLOY.adoc.

📜 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 lespoir quil sera utile, mais SANS AUCUNE GARANTIE ; sans même la garantie implicite de COMMERCIALISATION ou dADAPTATION À UN USAGE PARTICULIER. Voir la licence publique générale GNU Affero pour plus de détails (texte intégral dans LICENSE).

📞 Contact

Pour toute question ou suggestion concernant cette plateforme : kontak@o-k-i.net


🇬🇧 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 podcasts 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 forks repository

git@labola.o-k-i.net:cedric/annu-kute-ced.git

Domain (example)

https://example.com

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

Service Instance Account / channel

🎥 PeerTube (videos, lives)

GADE — https://gade.o-k-i.net

annu_kute_ced

🎙️ Castopod (audio episodes)

KUTE — https://kute.o-k-i.net

@annu_kute_cedric

📡 Mastodon (timeline)

BOKANTE — https://bokante.o-k-i.net

@cedric

🎵 Funkwhale (music, optional, disabled by default)

MIZIK — https://mizik.o-k-i.net

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 (Maohi 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 in localStorage)

  • 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 (with SearchAction), PodcastSeries and PodcastEpisode (with AudioObject) for the Castopod podcast, VideoObject (with embedUrl), CollectionPage, BreadcrumbList, Organization

  • Meta descriptions on every page and canonical URLs (link rel="canonical"); search result pages set to noindex, follow

  • Open Graph and Twitter Cards meta tags

  • sitemap.xml and robots.txt provided 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, Origin header 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)
├── 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

Installing the 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

  1. 📥 Clone the repository:

    git clone git@labola.o-k-i.net:cedric/annu-kute-ced.git
    cd annu-kute-ced
  2. 📦 Check the PHP requirements (see above).

  3. 🔧 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
  4. 🛡️ Install the hardened server configuration from conf/ (see Security).

  5. ✍️ Make the cache/ directory writable by the web server (created automatically on the first API call).

  6. 🔒 Make sure HTTPS is enabled (mandatory for the PWA).

  7. 🌐 Point your domain (e.g. example.com) to the project root.

⚙️ Configuration

The three-file system

File Role Versioned?

includes/config.php

Bootstrap: loads the other files and holds all business functions

Yes

includes/config.default.php

Default values for every constant (defined only if missing)

Yes

includes/config.local.php

Your local overrides — take precedence as they load first

No (.gitignore)

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.

Table 18. Application and organization
Constant Default Description

APP_HOST_NAME

'example.com'

Displayed domain name (legal notice…)

ORGANIZATION_SHORT_NAME

'ANNU KUTE CED'

Short organization name

ORGANIZATION_NAME

'ANNU KUTE CED'

Full organization name

Table 19. PeerTube
Constant Default Description

PEERTUBE_URL

'https://gade.o-k-i.net'

PeerTube instance URL (required)

PEERTUBE_DISPLAY_NAME

'gade.o-k-i.net'

Displayed instance name

API_KEY

''

Optional API key (Authorization: ApiKey …)

SHOW_VIDEO_VIEWS

false

Show view counts on video cards

PRIORITY_CATEGORIES

[10, 15]

Featured categories: [ID ⇒ 'Name'], order = display order (homepage, sidebar, footer, mobile menu)

DEFAULT_TIMEZONE

'America/Guadeloupe'

Display timezone (PHP list)

Table 20. Hero section (homepage banner)
Constant Default Description

HERO_TYPE

'live'

'live' · 'video' · 'playlist' · 'none'

LIVE_ACCOUNT_NAME

'annu_kute_ced'

PeerTube account watched for live streams (hero + /direct page)

HERO_VIDEO_ID

'fDE6tvQMXRuBpE4eDDS7kj'

Video UUID (video mode), extracted from the /w/<uuid> URL

HERO_VIDEO_TITLE

'Vidéo de présentation'

Title (accessibility)

PLAYLIST_PLATFORM

'peertube'

'peertube' · 'funkwhale' · 'castopod'

PLAYLIST_INSTANCE_URL

''

Base URL of the playlist platform

PLAYLIST_ID

''

Playlist ID; for Castopod: '@slug'

PLAYLIST_TITLE / PLAYLIST_DESCRIPTION

'' / ''

Optional texts (otherwise fetched via API)

Table 21. Castopod (podcast)
Constant Default Description

CASTOPOD_ENABLED

true

Enable the homepage podcast section

CASTOPOD_URL

'https://kute.o-k-i.net'

Castopod instance URL

CASTOPOD_PODCAST_SLUGS

['annu_kute_cedric']

Podcast slug(s); RSS feed = {URL}/@{slug}/feed. Multiple podcasts: episodes merged and date-sorted

CASTOPOD_EPISODES_COUNT

5

Total number of episodes displayed

Warning
Castopod enforces a strict rate limit (HTTP 429) on RSS feeds. The code waits 5 s between podcasts. Enable the cache (CACHE_ENABLED) to avoid these delays on every page.
Table 22. Funkwhale (music, optional)
Constant Default Description

FUNKWHALE_ENABLED

false

Enable the music section

FUNKWHALE_URL

'https://mizik.o-k-i.net'

Funkwhale instance URL

FUNKWHALE_TRACKS_COUNT

10

Random local tracks displayed (50 cached)

Table 23. Mastodon (timeline)
Constant Default Description

MASTODON_INSTANCE_URL

'https://bokante.o-k-i.net'

Mastodon instance (or Pleroma, via the adapter)

MASTODON_URL

'https://bokante.o-k-i.net/@cedric'

Public account URL ("See more" links, footer)

MASTODON_DATE_FORMAT

'fr-FR'

Post date locale

MASTODON_BTN_SEE_MORE / MASTODON_BTN_RELOAD

'Voir plus de post' / 'Rafraichir'

Button labels

MASTODON_MAX_POST_FETCH / MASTODON_MAX_POST_SHOW

'10' / '10'

Posts fetched / displayed

MASTODON_S3_MEDIA_URL

(undefined)

S3 media origin (added to the CSP media-src)

Note
The timeline has no enable flag: it is always displayed on the homepage.
Table 24. WordPress (optional)
Constant Default Description

WORDPRESS_ENABLED

false

Enable the posts section

WORDPRESS_URL

''

WordPress site URL (no trailing /)

WORDPRESS_POSTS_COUNT

6

Number of posts (REST API /wp-json/wp/v2/)

Table 25. Donations
Constant Default Description

DONATIONS_ENABLED

true

Enable the system (heart icon, "Support" link, /dons.php page)

LIBERAPAY_URL / KOFI_URL

'https://liberapay.com/OKI/donate' / ''

External platform URLs. By default the LiberaPay link points to OKI, the application maintainer.

STRIPE_ENABLED

false

Enable the Stripe interface (one-time / monthly tabs)

STRIPE_ONE_TIME_LINKS

[1, 5, 10, 20, 50, 'custom'] ⇒ ''

Stripe payment links per amount

STRIPE_MONTHLY_LINKS

[1, 5, 10, 20, 50] ⇒ ''

Stripe monthly subscription links

DONATION_AMOUNTS

[1, 5, 10, 20, 50]

Suggested amounts

DONATION_CURRENCY

'EUR'

Currency

DONATIONS_OKI_DISCLAIMER

Explanatory text

Message shown on /dons.php when donation links point to a third-party organization (OKI by default). Leave empty to hide.

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 OKIs 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.
Table 26. Countdown (maintenance / launch)
Constant Default Description

COUNTDOWN_ENABLED

false

⚠️ When true, every page redirects to countdown.php

COUNTDOWN_TARGET_DATE

'2025-10-11 00:00:00'

Target date (Y-m-d H:i:s); on completion, redirects to /

COUNTDOWN_TIMEZONES

5 territories

[Label ⇒ PHP timezone] shown on the page

Table 27. Next-live announcement
Constant Default Description

NEXT_LIVE_ENABLED

false

Show the announcement when no live stream is running

NEXT_LIVE_TITLE / NEXT_LIVE_DESCRIPTION

'Prochain live' / 'Rejoignez-nous…'

Texts (date and time appended automatically)

NEXT_LIVE_DATE

''

Live date (Y-m-d H:i:s)

NEXT_LIVE_IMAGE

'uploads/next-live.jpg'

Announcement image (used only if the file exists)

Table 28. Site, social networks and contact
Constant Default Description

SITE_NAME

'ANNU KUTE CED'

Site name (titles, meta)

SITE_DESCRIPTION

'Hub multimédia du podcast ANNU KUTE CED'

SEO description

SITE_LOGO / SITE_FAVICON

'img/logo.png' / 'img/favicon.png'

Image paths

FACEBOOK_URL, X_URL, INSTAGRAM_URL, YOUTUBE_URL, TIKTOK_URL

'#'

'#' or empty = icon hidden

CONTACT_EMAIL

'kontak@o-k-i.net'

Contact e-mail (footer)

Table 29. Legal notice
Constant Default Description

LEGAL_COPYRIGHT

'ANNU KUTE CED'

Copyright holder

LEGAL_WEBMASTER_NAME / LEGAL_WEBMASTER_EMAIL

'Cédric Famibelle-Pronzola' / 'contact@cedric-pronzola.dev'

Webmaster

LEGAL_HOST_NAME, LEGAL_HOST_COMPANY, LEGAL_HOST_RCS, LEGAL_HOST_ADDRESS

o2Switch values

Hosting provider details

LEGAL_CONTACT_EMAIL

'kontak@o-k-i.net'

Legal contact

LEGAL_LICENSE / LEGAL_LICENSE_URL

AGPL-V3 + GNU URL

Code license

LEGAL_SOURCE_CODE_URL

'https://labola.o-k-i.net/cedric/annu-kute-ced'

Source code repository (required by the AGPL for a network service)

LEGAL_SERVICE_DESCRIPTION

'est le hub multimédia du podcast ANNU KUTE CED.'

Service description sentence

Table 30. Tags and filters
Constant Default Description

TAG_INDEPENDENCE

'indépendance'

Tag used by getIndependenceVideos() (no associated homepage section currently)

SHORTS_MAX_DURATION

180

Max short duration (s); portrait ratio (aspectRatio ≤ 1) also required

IMPORTANT_TAGS

['ANNUKUTECED']

Hashtags in the sidebar, footer and mobile menu

POPULAR_TAGS

['ANNUKUTECED']

Hashtags displayed on the homepage

Table 31. Pagination and display volumes
Constant Default Description

VIDEOS_PER_PAGE

12

Videos per page (search)

COUNT_VIDEO_SEARCH

20

Results fetched per search

RECENT_VIDEOS_COUNT / TRENDING_VIDEOS_COUNT / CATEGORY_VIDEOS_COUNT

6

Homepage section sizes

SHORTS_COUNT / SHORTS_COUNT_SEARCH

6 / 100

Shorts displayed / search pool

LOAD_MORE_COUNT

6

Videos loaded per "Load more" click

FEATURED_VIDEOS_COUNT / INDEPENDENCE_VIDEOS_COUNT

6

Reserved (sections not currently displayed)

Table 32. Cache
Constant Default Description

CACHE_ENABLED

true

Enable caching for Castopod and Funkwhale. Essential against the Castopod RSS rate limit; empty results (error, 429) are never cached

CACHE_DURATION

3600

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/.
Table 33. Security
Constant Default Description

CSRF_SECRET

'change-me-in-config-local-php'

Secret key used to sign stateless CSRF tokens. Must be replaced in config.local.php with a random value (php -r "echo bin2hex(random_bytes(32)) . PHP_EOL;")

Table 34. "About" block (no default values — block hidden until defined)
Constant Description

MOVEMENT_TITLE

Block title (template fallback: "À propos")

MOVEMENT_DESCRIPTION

First paragraph; commenting this line hides the whole block

MOVEMENT_DESCRIPTION_2

Second paragraph (optional)

MOVEMENT_IMAGE / MOVEMENT_IMAGE_ALT / MOVEMENT_CAPTION

Image (img/movement_presentation.png, not versioned), alt text and caption (simple HTML allowed)

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

  1. 🖼️ Replace img/logo.png with your logo (and the favicons in img/)

  2. 🎨 Edit the colors in css/styles.css

  3. 📝 Most texts are set through config.local.php constants ("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:

  1. Hero — if HERO_TYPE'none' (live, video or playlist)

  2. Castopod podcasts — if CASTOPOD_ENABLED and CASTOPOD_URL is not empty

  3. Funkwhale tracks — if FUNKWHALE_ENABLED and FUNKWHALE_URL is not empty

  4. Mastodon timeline — always displayed

  5. WordPress posts — if WORDPRESS_ENABLED and WORDPRESS_URL is not empty

  6. Popular hashtags (POPULAR_TAGS)

  7. Shorts — carousel

  8. Latest videos + "Load more" button

  9. Trending + "Load more"

  10. One section per priority category (PRIORITY_CATEGORIES, only when it has videos) + "Load more"

  11. "About" block (if MOVEMENT_DESCRIPTION is 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; localhost relaxations for development

  • Headers: X-Frame-Options: SAMEORIGIN, X-Content-Type-Options: nosniff, X-XSS-Protection, Referrer-Policy: strict-origin-when-cross-origin, restrictive Permissions-Policy, HSTS (HTTPS only), CORP/COOP same-origin

    Note
    The COEP header was deliberately removed (commit 62d4d99): require-corp broke 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 with hash_equals on the AJAX endpoint; Origin or Referer header 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 120)

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 (/css/, /js/, /img/, cdnjs CDN)

Cache First

Cached at install time and on first access; falls back to img/logo.png if an image fails

Pages (/, *.php…)

Network First

Network first, cache fallback when offline

API and AJAX (/ajax/, mastodon-config.php…)

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:

  1. When deploying, bump the version suffix of the cache names in sw.js (format DDMMYYYY-HHMM, e.g. 24072026-0612)

  2. On page load, the browser detects the new sw.js and installs the new Service Worker in the background; it stays waiting (no automatic skipWaiting())

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

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

  1. Open index.php and uncomment the Plausible snippet (the <script defer data-domain=… src="https://plausible.io/js/script…js"> tag and its window.plausible shim).

  2. Thats it: the CSP already allows https://plausible.io in script-src and connect-src, and the data-domain automatically 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

  1. 🐘 Check that the host provides PHP 7.4+ and the required extensions

  2. 🔒 Enable HTTPS (mandatory for the PWA)

  3. 📤 Upload the files via FTP to the site root

  4. 🔧 Permissions: 644 for files, 755 for directories; cache/ must be writable

  5. 📄 Create the instance files from the .sample files (config, manifest, robots, sitemap, legal notice, donations)

  6. 🛡️ Copy conf/.htaccess.sample to .htaccess (Apache)

  7. 🌐 Point your domain (e.g. example.com) to the installation directory

  8. 🧪 Test the PWA via the browser developer tools (Application tab)

👨‍💻 Development and contributing

  1. 🌿 Create a branch: git checkout -b my-feature

  2. 💾 Commit with atomic, English messages, in conventional format (< 70 characters):

    • feat: add …, fix: prevent …, docs: update …, refactor: …, typo: …

  3. 📤 Push: git push origin my-feature

  4. 🔀 Open a pull request

Repository best practices:

  • Bug reports and suggestions are welcome on the forks repository; generic improvements can be proposed upstream (FEDIVERSE OKI)

  • Never version config.local.php or files derived from the .sample files

  • 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 Asciidoctors docinfo mechanism.

Note
asciidoctor-pdf (used by VSCodiums 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 on every pull request to main

  • PROD deployment (deploy-prod.yml): PHP and JS lint on push to main, 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 no longer 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.

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

📞 Contact

For any question or suggestion about this platform: kontak@o-k-i.net

S
Description
Interface web responsive pour plateforme multimédia PeerTube, intégrant une timeline Mastodon.
https://annukuteced.buzz
Readme AGPL-3.0
12 MiB
Languages
PHP 58.9%
CSS 23.7%
JavaScript 12.2%
Python 4.6%
Shell 0.6%