BlackSessionOS
Finalmente è uscito BlackSessionOS! Non è più un semplice browser o un'app Electron, ora è una distro Linux minimal basata su Debian 12 (Bookworm) a 64-bit, al momento pensata solo per utilizzo live, quindi progettata per avviarsi direttamente in RAM da chiavetta USB o VM in modalità zero trace (senza lasciare dati persistenti sul disco dell'host).
Questa distro è stata pensata come una Ephemeral Containerized Workstation, in pratica la shell dell'OS è integrata con l'interfaccia di BlackSession e ogni sessione viene isolata all'interno di container Linux dedicati. Invece "ephemeral" proprio perché allo spegnimento dell'ambiente i dati spariscono.

Architettura generale

Sequenza boot e avvio in utente Kiosk
L'intero flusso di boot è stato pensato per togliere qualsiasi desktop environment (KDE, GNOME, XFCE), riducendo al minimo l'overhead di RAM e CPU.
Il supporto (usb o iso) viene avviato tramite Syslinux (suite di bootloader leggeri) su macchine BIOS legacy o GRUB EFI su sistemi UEFI.
I paramentri passati al kernel sono: boot=live components quiet splash edd=off noedd.
L'immagine compressa filesystem.squashfs viene decompressa e montata come unione in sola lettura insieme a un layer scrivibile in RAM (tmpfs).
Non viene utilizzato alcuna grafica (come gdm o LightDM). Il login automatico è gestito da un override di Systemd, in grosso modo:
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin blacksession --noclear %I $TERM
Dopo il login su tty1, il file .profile dell'utente blacksession comunica con il terminale e lancia il server X:
if [ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
exec startx -- -nocursor
fi
C'è un file .xinitrc che avvia la sessione di openbox, un window manager molto minimal ( circa 10mb di RAM) configurato in autostart per lanciare lo script che poi andrà ad orchestare:
# /home/blacksession/.config/openbox/autostart
/usr/local/bin/blacksession-start &
Problema Docker su OverlayFS e il Loopback ext4
Uno dei problemi più complessi che abbia risolto nella creazione di BlackSessionOS è stato proprio l'unire Debian e Docker Engine.
Il vero problema è stato che nei sistemi operativi live il file system root / è montato tramite OverlayFS (squashfs + tmpfs). Quando Docker si avvia su un filesystem che è già un OverlayFS, il kernel Linux impedisce la creazione di un secondo layer OverlayFS annidato (overlay-on-overlay).
Questo porta a:
- Docker ricade in automatico sul driver di storage vfs.
- Il driver vfs non condivide i layer e copia l'intero filesystem per ogni container in RAM.
- Il caricamento delle immagini di BlackSession (800 MB compresse) portava quindi ad un'espansione di diversi gb nella ram, saturandola istantaneamente e causando il crash del sistema per out of memory con errore ENOSPC.
La soluzione è stata la creazione di blacksession-docker-storage!
Non è altro che un servizio Systemd dedicato che si attiva prima di Docker (Before=docker.service):
[Unit]
Description=BlackSession: prepare overlay2-capable storage for Docker
After=local-fs.target systemd-modules-load.service
Before=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/blacksession-docker-storage
Questo script esegue della logica a basso livello che consiste nel:
Rilevare la ram fisica disponibile da /proc/meminfo e calcolare un budget dedicato (fino a 8gb) lasciando 1,5gb per il sistema base.
RAM_MB=$(awk '/^MemTotal:/ {print int($2/1024)}' /proc/meminfo)
BUDGET_MB=$(( RAM_MB - 1536 ))
[ "$BUDGET_MB" -gt 8192 ] && BUDGET_MB=8192
Creare un punto di montaggio tmpfs isolato in /run/blacksession
Allocare docker-storage.img che consuma RAM solo per i blocchi effettivamente scritti:
truncate -s "${BUDGET_MB}M" /run/blacksession/docker-storage.img
Lo formatta come filesystem nativo ext4 senza root per massimizzare lo spazio:
mkfs.ext4 -F -q -m 0 /run/blacksession/docker-storage.img
Lo monta in loopback su /var/lib/docker:
mount -o loop,noatime /run/blacksession/docker-storage.img /var/lib/docker
Configura /etc/docker/daemon.json imponendo il driver ad alte prestazioni overlay2:
{
"storage-driver": "overlay2",
"data-root": "/var/lib/docker"
}
In questo modo il sistema si avvia senza problemi!
Avvio non bloccante e precaricamento delle immagini con flock
All'avvio dell'OS, il launcher blacksession-start gestisce l'inizializzazione (che in questo caso si svolge in parallelo):
# start Docker background non bloccante
sudo systemctl start --no-block docker
# start ui Electron
cd /opt/blacksession
ELECTRON_BIN="./node_modules/electron/dist/electron"
FLAGS="--no-sandbox --disable-gpu --disable-dev-shm-usage --disable-software-rasterizer"
exec "$ELECTRON_BIN" . $FLAGS >> "$LOG_FILE" 2>&1
Mentre l'interfaccia utente di BlackSession appare a schermo (evitando schermate nere o attese inutili) un sub-process in background monitora la disponibilità del socket (di Docker) e fa un "depacchettamento" delle immagini precompilate (browser.tar.gz e terminal.tar.gz).
Oltre a ciò, per impedire race condition (ad esempio l'utente che apre un tab prima che il preloader abbia terminato la decompressione), sia lo script di boot che il processo Electron usano un lock molto solido:
sudo flock -w 600 /var/lock/blacksession-image-load.lock -c "gunzip -c '$img' | docker load"
Questo significa che se il precaricatore ha già finito il caricamento, Electron rileva l'immagine all'istante senza decomprimerla una seconda volta.
Tab isolation e architettura container
Nell'OS ogni singola scheda non è un semplice renderer locale ma un intero ambiente Linux containerizzato e isolato via Docker (o gVisor runsc se disponibile nel kernel):

Container Browser
- Headless display: Xvfb crea il framebuffer grafico virtuale :99 con profondità colore 24bit.
- Window management trasparente: Fluxbox viene configurato tramite i file apps e init per rimuovere tutte le barre del titolo e forzare fullscreen.
- Patch NDI su noVNC: Il file /usr/share/novnc/vnc.html viene modificato via sed in fase di build del container per nascondere in modo nativo la barra di stato e gli elementi di controllo VNC:
RUN sed -i 's|</head>|<style>#noVNC_control_bar, #noVNC_status, #noVNC_status_bar { display: none !important; }</style></head>|' /usr/share/novnc/vnc.html
Controllo navigazione via CDP relay
Dato che Chromium gira all'interno del container isolato, Electron non ha accesso diretto al suo DOM o al ciclo di navigazione.
Ho fatto in modo che all'interno del container socat inoltra le connessioni esterne da :9223 alla porta interna :9222 (di Chromium):
socat TCP-LISTEN:9223,fork,reuseaddr TCP:127.0.0.1:9222 &
Electron interroga in modo periodico l'endpoint http://127.0.0.1:{debugPort}/json con una variabile in main.ts estraendo in tempo reale:
- L'url effettivo della pagina dopo redirect
- Il titolo del documento
- Lo stato di loading
Quando l'utente inserisce un nuovo URL nella barra degli indirizzi Electron esegue navigate.py che apre una sessione WebSocketverso il target CDP del container e invia il comando JSON-RPC:
{ "id": 1, "method": "Page.navigate", "params": { "url": "https://..." } }
Terminal mode e i suoi container

Le caratteristiche dei terminali sono:
- Debian Bookworm con suite di networking (nmap, netcat, curl, wget, proxychains4), compiler (build-essential, python3, nodejs 20), tools (tmux, htop, vim, nano) e utente blacksession abilitato a sudo senza password.
- Accesso web ad alte prestazioni: ttyd (compilato in C con supporto WebSocket e WebAssembly) espone la console su porta allocata dinamicamente sull'host.
- scambio file istantaneo: Il volume Docker
blacksession_vol_<profileId> viene montato assieme nel container browser e nel container terminal. La directory di download di Chromium è collegata via symlink a /shared/downloads quindi qualsiasi file scaricato dal browser è immediatamente accessibile dalla riga di comando del terminale.
- Toggle Dinamico CLI / GUI con un tasto si può passare al volo da terminale alla GUI grafica con finestra Xterm (noVNC su :6080).
Monitoraggio hardware a basso livello con systemStats.ts
Sulla barra in alto a destra integrata in BlackSessionOS ha la capacità di dialogare con i file virtuali del kernel Linux per visualizzare metriche (CPU e RAM) e gestire l'hardware:
// memoria reale ignorando la cache di pagina dello SquashFS
const txt = await fs.readFile('/proc/meminfo', 'utf8');
const total = extractKb(txt, 'MemTotal');
const avail = extractKb(txt, 'MemAvailable');
const usedPct = Math.round(((total - avail) / total) * 100);
In questa barra è presente il calcolo per la percentuale di carico della CPU, ottenuta analizzando il delta dei jiffies (user, nice, system, idle, irq) tra due chiamate successive a os.cpus().
Il Wi-Fi Manager con nmcli, che esegue scansioni e connessioni alle reti Wi-Fi richiamando nmcli tramite execFile con array di parametri (eliminando il rischio di command injection causato da caratteri speciali negli SSID o password).
Il controllo del menu spegnimento funziona richiamando i comandi ACPI di Systemd (systemctl poweroff e systemctl reboot) e per quanto riguarda l'audio, il volume master si regola tramite amixer (ALSA) o pactl (PulseAudio).
Toolchain dell'ISO (build-iso.sh)
La generazione dell'immagine bootable .iso sfrutta il framework ufficiale live-build di Debian configurato all'interno dello script build-iso.sh.
lb config \
--apt apt \
--apt-indices false \
--apt-recommends false \
--architectures amd64 \
--bootloader "syslinux,grub-efi" \
--bootappend-live "boot=live components quiet splash edd=off noedd" \
--binary-images iso-hybrid \
--distribution bookworm \
--parent-archive-areas "main contrib non-free non-free-firmware" \
--archive-areas "main contrib non-free non-free-firmware" \
--linux-packages "linux-image" \
--system live \
--win32-loader false
Le fasi di compilazione sono:
- Bootstrap del chroot: debootstrap scarica ed installa i pacchetti di Debian 12 Bookworm (Kernel, firmware Wi-Fi non-free Realtek/Intel, Xorg, Openbox, Docker.io, Node.js, librerie GTK3 e NetworkManager).
- Copia l'applicazione compilata (dist/ e dist-electron/), i manifest dei container (docker/) e installa le dipendenze di produzione Node.js (npm install --omit=dev).
- Esporta le immagini blacksession-browser:latest e blacksession-terminal:latest in archivi .tar.gz compressi in /opt/blacksession/preloaded-images/.
- mksquashfs comprime il chroot e xorriso genera il file immagine live-image-amd64.hybrid.iso compatibile sia con dischi ottici che con drive USB (dd/Ventoy/Rufus).
Tutorial installazione
Per installare BlackSessionOS, dopo aver scaricato l'ISO dobbiamo formattare una chiavetta USB in modo che sia avviabile. In questo caso ho utilizzato Rufus, selezionate lo schema di partizione su GPT e il sistema di destinazione su UEFI. In seguito clicca sul pulsante "Avvia", confermate le richieste a schermo e attendete. Una volta formattata l'unità, avviatela dal boot menu del PC e il sistema operativo si avvierà!
