Interface d'administration légère pour Radicale — gestion des utilisateurs (htpasswd/bcrypt) et des droits d'accès (from_file) via une interface web.
Stack : Python 3.12 + bcrypt uniquement. Zéro framework, zéro dépendance JS externe.
RAdmin lit et écrit directement les fichiers users et rights de Radicale. Il faut que Radicale soit configuré pour utiliser ces fichiers.
Dans /srv/Files/Radicale/config/config (ou le chemin équivalent sur votre serveur), la section [auth] doit pointer vers le fichier htpasswd :
[auth]
type = htpasswd
htpasswd_filename = /config/users
htpasswd_encryption = bcryptEt la section [rights] doit utiliser from_file :
[rights]
type = from_file
file = /config/rightsChemin hôte : /srv/Files/Radicale/config/users
Chemin conteneur Radicale : /config/users
Format htpasswd, un utilisateur par ligne :
alice:$2y$05$...hash_bcrypt...
bob:$2y$05$...hash_bcrypt...
RAdmin gère ce fichier automatiquement (ajout, suppression, changement de mot de passe avec rehash bcrypt).
Chemin hôte : /srv/Files/Radicale/config/rights
Chemin conteneur Radicale : /config/rights
Format INI, une section par règle. Exemple minimal :
[admin-root]
user = admin
collection =
permissions = R
[admin-full]
user = admin
collection = admin/.*
permissions = RrWw
[alice-own]
user = alice
collection = alice/.*
permissions = RrWwRAdmin gère ce fichier automatiquement (ajout, édition, suppression de règles).
Radicale distingue deux niveaux d'objets : les collections (un calendrier ou un carnet d'adresses) et les objets (un événement, un contact). Les permissions s'appliquent séparément à chaque niveau :
| Permission | Niveau | Effet |
|---|---|---|
R |
Collection | Lister les collections (nécessaire pour la découverte CalDAV) |
r |
Objet | Lire les objets dans une collection (événements, contacts) |
W |
Collection | Créer ou supprimer une collection |
w |
Objet | Créer, modifier ou supprimer des objets dans une collection |
Combinaisons courantes :
Rseul : accès racine en lecture, permet la découverte — sansr, le contenu n'est pas lisibleRr: lecture seule complète (découverte + lecture du contenu)RrWw: accès complet en lecture et écritureWwseul : écriture sans lecture — cas rare, peu utile en pratique
Les noms de règles sont libres mais une convention lisible aide à maintenir le fichier. Le pattern habituel est utilisateur-type :
-root : règle sur la collection racine (champ collection vide). Indispensable pour que le client CalDAV puisse découvrir les collections via PROPFIND. Sans cette règle, un client ne voit rien même si les sous-collections sont accessibles.
[admin-root]
user = admin
collection =
permissions = R-principal : règle sur le dossier principal de l'utilisateur (ex: collection = admin). Permet de lister les collections de premier niveau de cet utilisateur.
[admin-principal]
user = admin
collection = admin
permissions = RrWw-full : règle sur toutes les sous-collections de l'utilisateur via regex (ex: collection = admin/.*). Donne accès au contenu de tous ses calendriers.
[admin-full]
user = admin
collection = admin/.*
permissions = RrWwLes trois règles
-root,-principalet-fullforment le jeu minimal pour qu'un utilisateur ait un accès complet à ses propres données. Omettre-rootou-principalcasse la découverte automatique dans la plupart des clients (DAVx⁵, Luna, Thunderbird).
-own : variante compacte qui combine -principal et -full en une seule règle regex :
[alice-own]
user = alice
collection = alice/.*
permissions = RrWwFonctionne si le client n'a pas besoin d'accéder au dossier principal lui-même, ce qui dépend du client utilisé.
RAdmin détecte automatiquement les règles dont la suppression ou modification casserait l'accès fondamental d'un utilisateur. Ces règles sont affichées avec un badge 🔒 protégée dans l'interface et ne peuvent ni être éditées ni supprimées — la vérification est faite côté serveur, pas seulement dans le frontend.
Trois critères déclenchent la protection :
Collection vide + permission R : règle racine. Sans elle, le client CalDAV ne peut pas faire de PROPFIND sur la racine et ne découvre aucune collection.
[admin-root]
user = admin
collection =
permissions = RCollection = nom d'utilisateur exact + permission R : règle principal. Permet de lister les collections de premier niveau de l'utilisateur.
[admin-principal]
user = admin
collection = admin
permissions = RrWwCollection = user/.* + permissions RrWw complètes : règle full/own. Accès complet de l'utilisateur à ses propres données. Sans elle, l'utilisateur ne peut plus écrire dans ses calendriers.
[admin-full]
user = admin
collection = admin/.*
permissions = RrWw
[coraline-own]
user = coraline
collection = coraline/.*
permissions = RrWwLes règles de partage croisé (ex: coraline-famille, admin-lit-coraline-job) ne sont pas critiques et restent éditables.
mkdir -p /srv/Files/Radmin
scp app.py Dockerfile /srv/Files/Radmin/cd /srv/Files/Radmin
docker build -t radmin .Ajouter le service radmin au compose existant de Radicale.
Remplacer YOUR_SERVER_IP par l'IP du serveur (ou 0.0.0.0 pour toutes les interfaces) :
services:
docker-radicale:
container_name: radicale
ports:
- YOUR_SERVER_IP:5232:5232
init: true
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
- KILL
deploy:
resources:
limits:
pids: 50
memory: 256M
healthcheck:
test: curl --fail http://localhost:5232 || exit 1
interval: 30s
retries: "3"
volumes:
- /srv/Files/Radicale/data:/data
- /srv/Files/Radicale/config:/config:ro
image: tomsquest/docker-radicale
radmin:
container_name: radmin
image: radmin
pull_policy: never
restart: unless-stopped
volumes:
- /srv/Files/Radicale/config:/config
- /srv/Files/Radicale/data:/data:ro
environment:
- RADICALE_USERS_FILE=/config/users
- RADICALE_RIGHTS_FILE=/config/rights
- RADICALE_STORAGE=/data/collections/collection-root
- RADMIN_PASSWORD=YOUR_ADMIN_PASSWORD
- RADMIN_SECRET=YOUR_LONG_RANDOM_SECRET
- RADMIN_PORT=5765
ports:
- YOUR_SERVER_IP:5765:5765
networks: {}Important : Radicale monte
/configen:ro(lecture seule), mais RAdmin a besoin d'y écrire. Les deux volumes sont indépendants — c'est correct.
| Variable | Description | Exemple |
|---|---|---|
RADICALE_USERS_FILE |
Chemin vers le fichier htpasswd | /config/users |
RADICALE_RIGHTS_FILE |
Chemin vers le fichier rights | /config/rights |
RADICALE_STORAGE |
Racine du stockage Radicale | /data/collections/collection-root |
RADMIN_PASSWORD |
Mot de passe de l'interface | YOUR_ADMIN_PASSWORD |
RADMIN_SECRET |
Clé HMAC pour les sessions (chaîne aléatoire longue) | YOUR_LONG_RANDOM_SECRET |
RADMIN_PORT |
Port d'écoute | 5765 |
RADMIN_SESSION_TTL |
Durée de session en secondes (défaut : 3600) | 3600 |
Pour générer un secret aléatoire :
openssl rand -hex 32RAdmin écoute sur YOUR_SERVER_IP:5765. Il n'y a pas de HTTPS natif — à exposer derrière un reverse proxy (NPM, Caddy) si accès public, ou accessible directement si le firewall est restreint à vos IPs.
http://YOUR_SERVER_IP:5765
Onglet Utilisateurs
- Lister les comptes htpasswd
- Ajouter un utilisateur (hash bcrypt automatique)
- Changer le mot de passe (rehash bcrypt)
- Supprimer un utilisateur
Onglet Droits
- Lister toutes les règles du fichier
rights - Ajouter une règle (nom, utilisateur, collection, permissions R/r/W/w)
- Éditer une règle existante (pré-remplissage du formulaire)
- Supprimer une règle
Onglet Collections
- Vue lecture seule de l'arborescence du stockage Radicale
- Comptage des fichiers
.icspar calendrier
- Authentification par token HMAC-SHA256 signé, cookie
HttpOnly; SameSite=Strict - Toutes les écritures fichier sont atomiques (fichier temporaire + rename)
- Validation des inputs par regex avant toute écriture
- Le stockage Radicale est monté en
:rodans RAdmin - Aucune dépendance réseau au runtime (pas de CDN, pas d'appels externes)
Après modification de app.py :
cd /srv/Files/Radmin
docker build -t radmin .
docker restart radmin