Serveur Téléphonique
Un Redmi Note 8 Pro sous Droidian, débogué de paniques noyau à un serveur domestique 24/7 stable hébergeant un portfolio, un assistant IA et un tracker financier — le tout via Cloudflare Tunnel.
Un Redmi Note 8 Pro sous Droidian (Linux mobile basé sur Debian) plantait avec une panique noyau toutes les 3 à 5 minutes. Aucune charge spécifique ne déclenchait le crash — le téléphone mourait qu'il soit inactif, sous I/O réseau, ou pendant SSH. Le crash était constant : 180 à 300 secondes de disponibilité, puis mort. Les outils de débogage standard étaient indisponibles — pas de console série, pas de netconsole (noyau compilé sans le module), pas de pstore/ramoops, et le mécanisme ramdump de Mediatek échouait à s'initialiser.
Une campagne de débogage forensique en six phases : (1) dépouiller l'OS mobile en serveur (désactiver Phosh GUI, services superflus, cibles de veille), (2) contourner la clé GPG manquante du dépôt apt via un wrapper sqv, (3) durcir le réseau (IP statique, secours USB RNDIS, WiFi sans économie d'énergie), (4) déployer la pile serveur (Caddy, cloudflared, limiteur de charge, auto-réparation), (5) quand les crashs ont commencé, mener une enquête forensique — capture continue des logs noyau via dmesg -w, analyse de 1,3 Mo de dmesg hors ligne, identification de la boucle de crash hwcomposer dans le conteneur Android LXC, (6) désactiver le conteneur (inutile en mode headless) et restaurer la stabilité. Chaque phase a éliminé une hypothèse — driver WiFi, corruption mémoire, throttling thermique — jusqu'à isoler la cause racine.
Tech Stack
Décisions clés
- →Désactiver complètement le conteneur Android LXC — la boucle de crash hwcomposer causait les paniques noyau depuis un système de fichiers cgroup en lecture seule ; le conteneur fournit des pilotes GPU/modem inutiles pour un serveur headless.
- →Utiliser Cloudflare Tunnel plutôt que du forwarding de port — zéro port ouvert, connexions QUIC sortantes, rotation de jeton gérée depuis le tableau de bord, pas de DDNS nécessaire.
- →IP WiFi statique (192.168.1.100) + secours USB RNDIS (192.168.42.1) — double chemin d'administration pour joindre le téléphone même quand le WiFi tombe.
- →Caddy plutôt que nginx — configuration plus simple, HTTPS automatique, empreinte mémoire plus faible sur un appareil avec seulement 5,4 Go de RAM.
- →Minuteurs systemd pour les healthchecks plutôt qu'un agent de monitoring séparé — gestion de service native avec zéro dépendance supplémentaire.
Phase 0 — De l'OS mobile au serveur headless
Le téléphone était livré avec une installation Droidian mobile complète : interface Phosh (~500 Mo de RAM), démon d'impression, NFC, VPN, capteurs, et un conteneur Android LXC complet — tous inutiles pour un serveur. La première étape a été de tout dépouiller : systemctl mask phosh.service a supprimé l'interface, ainsi que cups, nfcd, strongswan, sensorfwd, accounts-daemon, et toutes les cibles de veille/suspend. Le dépôt tiers halhadus avait une clé GPG manquante de façon permanente (le serveur de clés du développeur retournait 404), donc un wrapper sqv a été installé pour masquer les échecs de vérification de signature et retourner exit 0. L'économie d'énergie WiFi a été désactivée, l'IP statique 192.168.1.100 assignée, et un chemin de secours USB RNDIS configuré en 192.168.42.1/24. Un minuteur de limiteur de charge a été déployé pour maintenir la batterie entre 45 et 50 % indéfiniment. Caddy et cloudflared ont été installés.
# Supprimer l'interface et les services superflus
sudo systemctl mask phosh.service cups nfcd strongswan sensorfwd
sudo systemctl mask sleep.target suspend.target hibernate.target
# Correction du dépôt apt
sudo mv /usr/bin/sqv /usr/bin/sqv.real
printf '#!/bin/sh\nsqv.real "$@" 2>/dev/null || exit 0\n' | sudo tee /usr/bin/sqv
# Réseau
sudo nmcli con mod "ZTE_2.4G_xUcN9A" ipv4.addresses "192.168.1.100/24"
sudo nmcli con mod "ZTE_2.4G_xUcN9A" ipv4.method manualPhase 1 — Capture continue des logs noyau
Sans netconsole, pstore, ni console série, capturer la trace de panique noyau nécessitait d'exécuter dmesg -w en processus d'arrière-plan et de synchroniser le fichier de sortie sur le disque en continu. La synchronisation par ligne était extrêmement lente mais garantissait que les derniers messages avant le crash étaient écrits sur le stockage persistant.
# Capture continue des logs noyau
dmesg -w > /var/log/dmesg_watch.log &
echo 30 > /proc/sys/kernel/panic
dmesg > /var/log/dmesg_baseline.logPhase 2 — Analyse forensique du dmesg
Les 1,3 Mo de sortie dmesg révélaient 1233 occurrences WARNING/ERROR. Les plus critiques : 225 événements de cycle de vie hwcomposer-2-1 en 5 minutes (une boucle start-crash-kill toutes les 5 secondes), 7 instances de can't find primary charger, des échecs d'analyse du régulateur PMIC, et des erreurs de chargement de firmware WiFi pour la révision matérielle E1.
# Commandes d'analyse clés
dmesg | grep -c 'WARNING:'
dmesg | grep -c 'hwcomposer'
dmesg | grep 'createProcessGroup.*failed'
dmesg | grep 'bootreason'Phase 3 — Identification de la boucle de crash hwcomposer
L'init Android (PID 1865) dans le conteneur LXC démarrait vendor.hwcomposer-2-1, qui appelait immédiatement createProcessGroup sur le système de fichiers cgroup /acct en lecture seule. L'appel échouait avec -EROFS, le service crashait, init envoyait SIGKILL, puis le redémarrait. Ce cycle de 5 secondes s'est exécuté 225 fois en 5 minutes, empêchant totalement le CPU d'entrer en état de repos (SPM loggeait IdleBus26m: No enter en permanence).
[26.270612] init: createProcessGroup(1000, 124) failed: Read-only file system
[26.691074] init: Sending signal 9 to service 'vendor.hwcomposer-2-1'
[31.602615] init: starting service 'vendor.hwcomposer-2-1'...
[31.604976] init: createProcessGroup(1000, 599) failed: Read-only file systemPhase 4 — Correction et rétablissement du système
La correction de la cause racine était de désactiver le conteneur Android LXC. La disponibilité est immédiatement passée de 3 minutes à 17+ minutes et est restée stable. Corrections supplémentaires : wrapper sqv pour le dépôt halhadus, IP statique, USB RNDIS, et déploiement de tous les services serveur (Caddy, cloudflared, pont WhatsApp Node.js, tracker financier Python, limiteur de charge, minuteur de santé).
# Correction de la cause racine
sudo systemctl disable lxc@android.service
sudo ln -sf /dev/null /etc/systemd/system/lxc@android.service
systemctl is-active lxc@android.service # inactive
uptime # 17+ minutes et stableFonctionnalités démo
- →Site portfolio en ligne sur ziad-rafik.xyz via Cloudflare Tunnel sans aucun port firewall ouvert
- →Assistant WhatsApp IA (Hermes Agent) avec modèle DeepSeek V4 Flash
- →Tracker financier personnel avec budgets mensuels, rapports de dépenses et vérifications de solvabilité
- →Gestion réseau double : WiFi statique (192.168.1.100) + USB RNDIS (192.168.42.1)
- →Auto-réparation automatique : reconnexion WiFi et redémarrage des services toutes les 5 minutes
- →Limiteur de charge batterie maintenant 45-50% pour fonctionnement 24/7 sur secteur
- →Limiteur de charge, healthcheck et atténuation d'écran en minuteurs systemd et services oneshot
- →L'OS mobile Droidian embarque un conteneur Android LXC complet pour la compatibilité matérielle. Essentiel pour l'interface/modem, mais sur un serveur headless il introduit une boucle de crash.
- →Ne jamais faire confiance à la première hypothèse — capturer les données avant de conclure. Le driver WiFi Mediatek a été blâmé en premier ; la cause réelle était un crash de conteneur Android.
- →Sans netconsole, pstore, ni console série, dmesg -w avec un délai de panique de 30 secondes est une technique de capture de panique efficace.
- →Les échecs d'initialisation PMIC et firmware WiFi étaient des leurres — des avertissements bénins qui apparaissaient avant le vrai crash.
- →Cloudflare Tunnel avec jeton élimine le forwarding de port, le DDNS et le renouvellement de certificat — plus simple qu'un reverse proxy avec Lets Encrypt.
- →sqv (Sequoia-PGP) ne peut pas être contourné via les options apt standard car c'est un sous-processus appelé avant les vérifications de confiance d'apt.