Introduction
Dans des lieux tels que les bibliothèques ou les cafés Internet, on trouve souvent des ordinateurs mis à la disposition du public. Ces ordinateurs sont fournis avec un ensemble d’applications préinstallées, et les données des utilisateurs sont effacées après un redémarrage.
Les logiciels sur ces machines sont souvent propriétaires. Cependant, il existe aussi des solutions respectueuses de la liberté logicielle pour les ordinateurs publics. En tant qu’activistes du logiciel libre, nous devrions appliquer et promouvoir activement ces solutions dans les bibliothèques, les cafés Internet, les établissements scolaires, etc.
Dans cet article, je présenterai une solution qui installe Debian GNU/Linux sur un ordinateur et efface toutes les données après un redémarrage.
Licence
Tout le code de cet article est dédié au domaine public. Les autres parties de cet article sont sous licence CC BY-SA 4.0.
Préparation
Un ordinateur avec un disque dur (de préférence un HDD) d’au moins 50 GiB, et un média d’installation Debian GNU/Linux (pas Debian Live).
Étape 1 : Installer Debian GNU/Linux
Installez Debian GNU/Linux sur votre ordinateur avec la disposition de partitions suivante :
- 1 GiB
/dev/sda1avec un système de fichiers FAT, monté sur/boot/efi - 1 GiB
/dev/sda2avec un système de fichiers ext4, monté sur/boot - Au moins 40 GiB pour
/dev/sda3avec un système de fichiers ext4, monté sur/ /dev/sda4sans système de fichiers/dev/sda5comme partition swap
Après l’installation, installez les logiciels souhaités et configurez le système selon vos préférences. N’oubliez pas de définir un mot de passe root solide (ainsi qu’un mot de passe BIOS/UEFI robuste) ; laisser le compte root accessible au public n’est pas une bonne idée.
Étape 2 : Configuration
Formatez /dev/sda4 avec un système de fichiers ext4 et étiquetez-le OVERLAY_RW :
# mkfs.ext4 -L OVERLAY_RW /dev/sda4
Puis étiquetez /dev/sda3 comme ROOT_TEMPLATE :
# e2label /dev/sda3 ROOT_TEMPLATE
Créez les répertoires :
# mkdir -p /mnt/overlay_prepare
# mount /dev/disk/by-label/OVERLAY_RW /mnt/overlay_prepare
# mkdir -p /mnt/overlay_prepare/upper-root
# mkdir -p /mnt/overlay_prepare/work-root
# chmod 0700 /mnt/overlay_prepare
# sync
# umount /mnt/overlay_prepare
Commentez la ligne qui monte votre système racine dans /etc/fstab, puis ajoutez cette ligne :
LABEL=OVERLAY_RW /overlay_storage ext4 defaults,noatime 0 2
Actualisez le cache des paquets et installez ces paquets (cela peut supprimer votre busybox ; procédez avec prudence) :
# apt update
# apt install --no-install-recommends initramfs-tools busybox-static
Ajoutez ces lignes dans /etc/initramfs-tools/modules :
overlay
ext4
jbd2
crc32c
mbcache
Créez /etc/tmpfiles.d/overlay-runtime.conf avec ces lignes :
# /etc/tmpfiles.d/overlay-runtime.conf
d /run/dbus 0755 messagebus messagebus -
d /var/lib/dbus 0755 messagebus messagebus -
d /run/NetworkManager 0755 root root -
d /run/lock 0755 root root -
d /var/log 0755 root root -
d /tmp 1777 root root -
d /var/tmp 1777 root root -
Créez /etc/initramfs-tools/scripts/init-bottom/overlayroot et ajoutez le script suivant :
#!/bin/sh
PREREQ=""
prereqs() { echo "$PREREQ"; }
case "$1" in
prereqs) prereqs; exit 0;;
esac
set -eu
# If admin wants maintenance: skip overlay and let normal boot continue
if grep -q 'overlay=disabled' /proc/cmdline 2>/dev/null; then
echo "overlay disabled via kernel cmdline" >/dev/console 2>&1
exit 0
fi
# Try to load overlay module (best-effort)
modprobe overlay 2>/dev/null || true
# prepare mount points in initramfs
mkdir -p /overlay/lower /overlay/storage /overlay/overlay_root
# resolve device by label for the template root
DEV_LABEL=/dev/disk/by-label/ROOT_TEMPLATE
DEV_ROOT=$(readlink -f "$DEV_LABEL" 2>/dev/null || true)
if [ -z "$DEV_ROOT" ]; then
echo "overlayroot: ERROR: ROOT_TEMPLATE label not found" >/dev/console 2>&1
exit 1
fi
echo "overlayroot: resolved ROOT_TEMPLATE -> $DEV_ROOT" >/dev/console 2>&1
# find existing mount point for the device; fallback to using current root (/)
MOUNT_POINT=$(awk -v d="$DEV_ROOT" '($1==d){print $2; exit}' /proc/self/mounts || true)
if [ -z "$MOUNT_POINT" ]; then
B=$(basename "$DEV_ROOT" || true)
if [ -r "/sys/class/block/$B/dev" ]; then
DEVNUM=$(cat /sys/class/block/$B/dev)
MOUNT_POINT=$(awk -v num="$DEVNUM" '($3==num){print $5; exit}' /proc/self/mountinfo || true)
fi
fi
if [ -z "$MOUNT_POINT" ]; then
ROOT_SRC=$(awk '($2=="/"){print $1; exit}' /proc/self/mounts || true)
if [ -n "$ROOT_SRC" ]; then
echo "overlayroot: fall back to using current root mount ($ROOT_SRC) as lowerdir" >/dev/console 2>&1
mount --bind / /overlay/lower 2>/dev/null || true
MOUNT_POINT=/overlay/lower
fi
fi
if [ -n "$MOUNT_POINT" ] && [ "$MOUNT_POINT" != "/overlay/lower" ]; then
echo "overlayroot: bind existing mountpoint $MOUNT_POINT -> /overlay/lower" >/dev/console 2>&1
mount --bind "$MOUNT_POINT" /overlay/lower || true
fi
# if /overlay/lower still not a mountpoint, try direct ro mount (last resort)
if ! grep -q ' /overlay/lower ' /proc/self/mounts; then
echo "overlayroot: trying direct mount of $DEV_ROOT -> /overlay/lower" >/dev/console 2>&1
mount -o ro "$DEV_ROOT" /overlay/lower || {
echo "overlayroot: direct mount of $DEV_ROOT failed" >/dev/console 2>&1
exit 1
}
fi
# mount overlay storage partition (explicit ext4)
DEV_RW=$(readlink -f /dev/disk/by-label/OVERLAY_RW 2>/dev/null || true)
if [ -z "$DEV_RW" ]; then
echo "overlayroot: ERROR: OVERLAY_RW label not found" >/dev/console 2>&1
exit 1
fi
echo "overlayroot: mounting OVERLAY_RW ($DEV_RW) -> /overlay/storage" >/dev/console 2>&1
mount -t ext4 "$DEV_RW" /overlay/storage || {
echo "overlayroot: failed to mount OVERLAY_RW ($DEV_RW)" >/dev/console 2>&1
exit 1
}
# per-boot upper/work on overlay storage
BOOTID=$(cat /proc/sys/kernel/random/boot_id 2>/dev/null || date +%s)
UPPER="/overlay/storage/upper-${BOOTID}"
WORK="/overlay/storage/work-${BOOTID}"
mkdir -p "$UPPER" "$WORK"
# make sure upper/work are owned by root and inaccessible to others
chown -R root:root "$UPPER" "$WORK" 2>/dev/null || true
chmod 0700 "$UPPER" "$WORK" 2>/dev/null || true
# mount overlay onto the location that initramfs-tools expects for the real root (/root)
mkdir -p /root
echo "overlayroot: mounting overlay lower=/overlay/lower upper=$UPPER work=$WORK -> /root" >/dev/console 2>&1
mount -t overlay overlay -o lowerdir=/overlay/lower,upperdir="$UPPER",workdir="$WORK" /root || {
echo "overlayroot: overlay mount failed" >/dev/console 2>&1
exit 1
}
# move overlay storage under the real root so the booted system can access/clean it later
mkdir -p /root/overlay_storage
mount --move /overlay/storage /root/overlay_storage 2>/dev/null || mount --bind /overlay/storage /root/overlay_storage || true
# --- Post-mount safety & runtime skeleton preparation ---
# Ensure the merged root is traversable by non-root users to allow services to chdir/exec
chmod 0755 /root 2>/dev/null || true
# Create essential runtime and state directories inside the merged root.
# These make sure systemd and daemons (dbus, NetworkManager, etc.) can create sockets and pidfiles.
mkdir -p /root/run /root/run/dbus /root/run/NetworkManager /root/run/lock
mkdir -p /root/var/lib/dbus /root/var/log /root/var/tmp /root/tmp
# Set permissive permissions for tmp directories and standard perms for others
chmod 0755 /root/run /root/var /root/var/log 2>/dev/null || true
chmod 1777 /root/tmp /root/var/tmp 2>/dev/null || true
# Attempt to set dbus ownership for dbus runtime dirs; ignore errors if the user does not exist in initramfs.
chown -R messagebus:messagebus /root/run/dbus /root/var/lib/dbus 2>/dev/null || true
# Ensure root owns the primary runtime dirs
chown root:root /root /root/run /root/var 2>/dev/null || true
# Ensure upper/work are secure on the merged root as well (in case overlay moved them)
# (this is best-effort; ignore failures)
[ -d "$UPPER" ] && chown -R root:root "$UPPER" 2>/dev/null || true
[ -d "$WORK" ] && chown -R root:root "$WORK" 2>/dev/null || true
chmod 0700 "$UPPER" "$WORK" 2>/dev/null || true
# Move pseudo-filesystems into the new root so the real init finds them after switch_root.
# These moves are best-effort; if they fail, systemd may still handle necessary mounts.
for P in dev proc sys run; do
if mountpoint -q "/$P" 2>/dev/null; then
mkdir -p /root/$P 2>/dev/null || true
mount --move "/$P" "/root/$P" 2>/dev/null || true
fi
done
# Leave final switch_root to initramfs /init (do not exec switch_root here).
echo "overlayroot: overlay prepared at /root; returning to initramfs /init to perform switch_root" >/dev/console 2>&1
exit 0
Rendez /etc/initramfs-tools/scripts/init-bottom/overlayroot exécutable :
# chmod +x /etc/initramfs-tools/scripts/init-bottom/overlayroot
Actualisez votre initramfs :
# update-initramfs -u -k all
Créez /usr/local/sbin/overlay-prune.sh et ajoutez ces lignes :
#!/usr/bin/env bash
# overlay-prune.sh
# Prune unused overlay upper-*/work-* directories safely.
# Usage:
# /usr/local/sbin/overlay-prune.sh [--dry-run] [--age DAYS]
# Default AGE = 7 days
set -euo pipefail
DRY_RUN=0
AGE=7 # days
LOG="/var/log/overlay-prune.log"
while [ $# -gt 0 ]; do
case "$1" in
--dry-run) DRY_RUN=1; shift ;;
--age) AGE="$2"; shift 2 ;;
--help) echo "Usage: $0 [--dry-run] [--age DAYS]"; exit 0 ;;
*) echo "Unknown arg: $1"; exit 2 ;;
esac
done
now() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
log() {
printf '%s %s\n' "$(now)" "$*" >> "$LOG"
}
# Determine overlay storage mountpoint(s).
# Try common locations then fallback to finding mount points for device labelled OVERLAY_RW.
CANDIDATES=(
"/root/overlay_storage"
"/overlay_storage"
"/overlay/merged/overlay_storage"
"/overlay/storage"
)
STORAGE=""
for p in "${CANDIDATES[@]}"; do
if [ -d "$p" ]; then
# choose the first existing one that is on a separate filesystem or contains upper-* dirs
if find "$p" -maxdepth 1 -mindepth 1 -type d -name 'upper-*' -print -quit >/dev/null 2>&1; then
STORAGE="$p"
break
fi
fi
done
# fallback: try findmnt by device label
if [ -z "$STORAGE" ]; then
DEV=$(readlink -f /dev/disk/by-label/OVERLAY_RW 2>/dev/null || true)
if [ -n "$DEV" ]; then
STORAGE=$(findmnt -n -o TARGET -S "$DEV" 2>/dev/null || true)
fi
fi
if [ -z "$STORAGE" ]; then
echo "overlay-prune: no overlay storage found; exiting" >&2
log "No overlay storage found; abort."
exit 0
fi
log "Starting prune on storage: $STORAGE (age > ${AGE}d) DRY_RUN=${DRY_RUN}"
# Collect currently in-use upper/work directories by scanning /proc/mounts overlay options
mapfile -t INUSE < <(awk -F',' '/lowerdir=/{for(i=1;i<=NF;i++){if($i ~ /^upperdir=/) print substr($i,10); if($i ~ /^workdir=/) print substr($i,9)}}' /proc/mounts | sort -u)
# Helper: check if path is referenced in INUSE
is_inuse() {
local p="$1"
for u in "${INUSE[@]}"; do
if [ "$u" = "$p" ]; then
return 0
fi
done
return 1
}
# Find candidate dirs named upper-* or work-*
while IFS= read -r d; do
# normalize
dir="$d"
# guard: only operate under STORAGE
case "$dir" in
"$STORAGE"/*) ;;
*) continue ;;
esac
# skip if currently in use
if is_inuse "$dir"; then
log "SKIP in-use: $dir"
continue
fi
# skip if younger than AGE
if [ "$(find "$dir" -maxdepth 0 -mtime -"$AGE" -print -quit)" ]; then
log "SKIP recent: $dir"
continue
fi
if [ "$DRY_RUN" -eq 1 ]; then
echo "DRY-RUN would remove: $dir"
log "DRY-RUN would remove: $dir"
else
# double-check no mount points below it
if mountpoint -q "$dir"; then
log "SKIP mounted: $dir"
continue
fi
# safe remove
log "REMOVING: $dir"
rm -rf -- "$dir"
if [ $? -eq 0 ]; then
log "REMOVED: $dir"
else
log "FAILED_REMOVE: $dir"
fi
fi
done < <(find "$STORAGE" -maxdepth 1 -type d \( -name 'upper-*' -o -name 'work-*' \) -print | sort)
log "Prune finished."
exit 0
Rendez /usr/local/sbin/overlay-prune.sh exécutable :
# chmod +x /usr/local/sbin/overlay-prune.sh
Créez /etc/logrotate.d/overlay-prune avec ces lignes :
/var/log/overlay-prune.log {
rotate 7
daily
missingok
notifempty
compress
copytruncate
}
Créez le fichier de log et définissez ses propriétaires et permissions :
# touch /var/log/overlay-prune.log
# chown root:root /var/log/overlay-prune.log
# chmod 0640 /var/log/overlay-prune.log
Créez /etc/systemd/system/overlay-prune.service avec ces lignes :
[Unit]
Description=Prune unused overlay upper/work directories
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/overlay-prune.sh --age 7
Nice=10
# Run as root (needs to remove files)
Créez /etc/systemd/system/overlay-prune.timer avec ces lignes :
[Unit]
Description=Run overlay-prune daily (and shortly after boot)
[Timer]
OnBootSec=10min
OnUnitActiveSec=24h
Persistent=true
[Install]
WantedBy=timers.target
Activez le timer :
# systemctl enable overlay-prune.timer
Redémarrez maintenant votre système :
# reboot
Étape 3 : Testez votre installation
Après le redémarrage, exécutez :
$ findmnt /
Vous devriez voir que / est un système de fichiers overlay.
Créez plusieurs fichiers à différents emplacements, puis redémarrez le système ; ces fichiers devraient avoir disparu.
Si tout est correct, vous avez terminé.