NixOS sul Lenovo IdeaPad Slim 3 Chromebook 14M868

Guida completa — MediaTek Kompanio 520 (MT8186 / google-corsola-magneton)

⚠️ AVVERTENZE PRELIMINARI


Panoramica del flusso

[HOST LINUX]                        [CHROMEBOOK]
──────────────────────────────────────────────────────
1. Installa tool (vboot, mkimage)
2. Scarica kernel hexdump0815 stb-cbm
3. Crea FIT image + kpart firmato
4. Prepara USB installer             
   (GPT ChromeOS + kpart + rootfs Alpine)
                                    5. Attiva Developer Mode
                                    6. Disabilita WP hardware
                                    7. Imposta GBB Flags
                                    8. Avvia da USB (Ctrl+U)
                                    9. Dal live Alpine:
                                       - Partiziona eMMC con cgpt
                                       - Estrai NixOS tarball su eMMC
                                       - Scrivi kpart eMMC su KERN-A
                                    10. Riavvia da eMMC (Ctrl+D)
                                    11. nixos-install / configurazione

Indice

  1. Prerequisiti
  2. Preparazione host Linux — tool e kernel
  3. Creazione del kpart firmato
  4. Creazione dell'USB installer ChromeOS-compatibile
  5. Attivazione Developer Mode sul Chromebook
  6. Disabilitazione hardware write-protect (WP)
  7. Impostazione GBB Flags
  8. Avvio dal USB installer (Ctrl+U)
  9. Dal live Alpine — partizionamento eMMC e installazione NixOS
  10. Scrittura del kpart sull'eMMC
  11. Configurazione NixOS (configuration.nix)
  12. nixos-install e primo avvio
  13. Gestione degli aggiornamenti del kernel
  14. Stato dell'hardware
  15. Ripristino di ChromeOS

1. Prerequisiti

Hardware necessario:

Pacchetti sull'host Linux:

# Debian / Ubuntu
sudo apt install vboot-utils u-boot-tools device-tree-compiler \
                 xz-utils wget curl e2fsprogs

# Arch Linux
sudo pacman -S vboot-utils uboot-tools dtc xz wget

# NixOS host — ambiente temporaneo
nix-shell -p vboot_utils ubootTools dtc xz wget

Verifica che cgpt e futility siano disponibili:

which cgpt futility mkimage

2. Preparazione host Linux — tool e kernel

Crea una directory di lavoro sull'host:

mkdir ~/magneton-build
cd ~/magneton-build

Scarica il kernel precompilato per corsola/MT8186

Il progetto hexdump0815 fornisce kernel mainline patchati e testati esplicitamente su magneton. Controlla la pagina releases per la versione più recente; al momento della scrittura è 6.12.66-stb-cbm+.

KVER="6.12.66-stb-cbm+"
BASE="https://github.com/hexdump0815/linux-mainline-mediatek-mt81xx-kernel/releases/download/${KVER}"

wget "${BASE}/linux-${KVER}-aarch64.tar.gz"
wget "${BASE}/linux-${KVER}-aarch64-modules.tar.gz"

# Estrai
tar xzf linux-${KVER}-aarch64.tar.gz
tar xzf linux-${KVER}-aarch64-modules.tar.gz

# Verifica presenza DTB magneton
ls boot/dtb-${KVER}/mt8186-corsola-magneton-*.dtb
# Deve mostrare: sku393216, sku393217, sku393218

Scarica il mini-rootfs Alpine AArch64 (per il live USB)

ALPINE_VER="3.21.3"
wget "https://dl-cdn.alpinelinux.org/alpine/v3.21/releases/aarch64/alpine-minirootfs-${ALPINE_VER}-aarch64.tar.gz"

3. Creazione del kpart firmato

Il kpart è l'unico formato che Depthcharge accetta. È un FIT image (kernel + DTB) firmato con le vboot developer keys.

Servono due kpart distinti con command line diverse:

Passo 3a — File di command line

# Per il live USB (root su sda2)
cat > cmdline-usb.txt << 'EOF'
console=tty1 root=/dev/sda2 rootwait rw quiet
EOF

# Per l'installazione finale su eMMC (root su mmcblk0p2)
cat > cmdline-emmc.txt << 'EOF'
console=tty1 root=/dev/mmcblk0p2 rootwait rw quiet
EOF

Passo 3b — FIT Image (.itb)

Crea il file .its che descrive il FIT con kernel e tutti e tre i DTB magneton (Depthcharge sceglie automaticamente quello corretto in base al GPIO SKU strapping):

KVER="6.12.66-stb-cbm+"
KERNEL="boot/Image-${KVER}"
DTB="boot/dtb-${KVER}"

cat > magneton.its << EOF
/dts-v1/;

/ {
    description = "NixOS kernel FIT for MT8186 Magneton";
    #address-cells = <1>;

    images {
        kernel@1 {
            description = "Linux kernel ${KVER}";
            data = /incbin/("${KERNEL}");
            type = "kernel_noload";
            arch = "arm64";
            os = "linux";
            compression = "none";
            load = <0>;
            entry = <0>;
            hash@1 { algo = "sha256"; };
        };
        fdt@1 {
            description = "MT8186 Magneton SKU393216";
            data = /incbin/("${DTB}/mt8186-corsola-magneton-sku393216.dtb");
            type = "flat_dt";
            arch = "arm64";
            compression = "none";
            hash@1 { algo = "sha256"; };
        };
        fdt@2 {
            description = "MT8186 Magneton SKU393217";
            data = /incbin/("${DTB}/mt8186-corsola-magneton-sku393217.dtb");
            type = "flat_dt";
            arch = "arm64";
            compression = "none";
            hash@1 { algo = "sha256"; };
        };
        fdt@3 {
            description = "MT8186 Magneton SKU393218";
            data = /incbin/("${DTB}/mt8186-corsola-magneton-sku393218.dtb");
            type = "flat_dt";
            arch = "arm64";
            compression = "none";
            hash@1 { algo = "sha256"; };
        };
    };

    configurations {
        default = "conf@1";
        conf@1 {
            description = "Magneton SKU393216";
            kernel = "kernel@1";
            fdt = "fdt@1";
        };
        conf@2 {
            description = "Magneton SKU393217";
            kernel = "kernel@1";
            fdt = "fdt@2";
        };
        conf@3 {
            description = "Magneton SKU393218";
            kernel = "kernel@1";
            fdt = "fdt@3";
        };
    };
};
EOF

mkimage -f magneton.its magneton.itb

Passo 3c — Firma con futility (vboot dev keys)

DEVKEYS="/usr/share/vboot/devkeys"
# Su NixOS host: DEVKEYS="$(nix-build -A vboot_reference --no-out-link)/share/vboot/devkeys"

# kpart per USB installer
futility vbutil_kernel \
    --arch arm --version 1 \
    --keyblock    "${DEVKEYS}/kernel.keyblock" \
    --signprivate "${DEVKEYS}/kernel_data_key.vbprivk" \
    --bootloader  cmdline-usb.txt \
    --config      cmdline-usb.txt \
    --vmlinuz     magneton.itb \
    --pack        kpart-usb.bin

# kpart per installazione finale su eMMC
futility vbutil_kernel \
    --arch arm --version 1 \
    --keyblock    "${DEVKEYS}/kernel.keyblock" \
    --signprivate "${DEVKEYS}/kernel_data_key.vbprivk" \
    --bootloader  cmdline-emmc.txt \
    --config      cmdline-emmc.txt \
    --vmlinuz     magneton.itb \
    --pack        kpart-emmc.bin

# Verifica entrambi
futility vbutil_kernel --verify kpart-usb.bin
futility vbutil_kernel --verify kpart-emmc.bin
# Output atteso: "signatures VALID"

4. Creazione dell'USB installer ChromeOS-compatibile

Questo è il punto centrale: crei sull'host un USB con layout GPT ChromeOS, partizione KERN-A con il kpart-usb.bin, e partizione root con un mini-rootfs Alpine AArch64 + i moduli kernel + i tool necessari per l'installazione.

Sostituisci /dev/sdX con il device reale del tuo USB. Controlla con lsblk prima di procedere. Dati sul USB verranno cancellati.

USB=/dev/sdX   # <-- CAMBIA CON IL TUO DEVICE USB

# Smonta eventuali partizioni montate
umount ${USB}* 2>/dev/null || true

Passo 4a — Partizionamento GPT ChromeOS

# Crea tabella GPT pulita
sudo cgpt create ${USB}

# Partizione 1: KERN-A (ChromeOS kernel, 64 MiB)
# Settore 8192 = 4 MiB offset dall'inizio (spazio per GPT)
sudo cgpt add -i 1 \
    -b 8192 -s 131072 \
    -t kernel \
    -l "KERN-A" \
    -P 15 -T 1 -S 1 \
    ${USB}

# Partizione 2: ROOT (ext4, resto del disco)
sudo cgpt add -i 2 \
    -b 139264 -s $(( $(sudo blockdev --getsz ${USB}) - 139264 - 33 )) \
    -t data \
    -l "ROOT" \
    ${USB}

# Protective MBR
sudo cgpt boot -p ${USB}

# Verifica layout
sudo cgpt show ${USB}

Output atteso:

       start        size    part  contents
           0           1          PMBR
           1           1          Pri GPT header
           2          32          Pri GPT table
        8192      131072       1  Label: "KERN-A"
                                  Type: ChromeOS kernel
                                  Attr: priority=15 tries=1 successful=1
      139264    <N settori>   2  Label: "ROOT"
                                  Type: Linux data

Passo 4b — Scrivi il kpart USB sulla partizione KERN-A

sudo dd if=kpart-usb.bin of=${USB}p1 bs=4M status=progress
sudo sync

Passo 4c — Crea filesystem ext4 sulla partizione root

sudo mkfs.ext4 -L alpine-live ${USB}p2
sudo mkdir -p /mnt/usb-root
sudo mount ${USB}p2 /mnt/usb-root

Passo 4d — Estrai il mini-rootfs Alpine AArch64

ALPINE_VER="3.21.3"
sudo tar xzf alpine-minirootfs-${ALPINE_VER}-aarch64.tar.gz -C /mnt/usb-root

# Crea device nodes essenziali
sudo mkdir -p /mnt/usb-root/{dev,proc,sys,tmp,mnt,run}
sudo mknod -m 666 /mnt/usb-root/dev/null    c 1 3
sudo mknod -m 666 /mnt/usb-root/dev/urandom c 1 9
sudo mknod -m 666 /mnt/usb-root/dev/random  c 1 8
sudo mknod -m 600 /mnt/usb-root/dev/console c 5 1
sudo mknod -m 666 /mnt/usb-root/dev/tty     c 5 0
sudo mknod -m 666 /mnt/usb-root/dev/tty1    c 4 1

Passo 4e — Installa i moduli kernel

KVER="6.12.66-stb-cbm+"

# Estrai i moduli nel rootfs
sudo tar xzf linux-${KVER}-aarch64-modules.tar.gz \
    -C /mnt/usb-root \
    --strip-components=0

# Copia il kernel nel rootfs (per eventuale uso da live)
sudo mkdir -p /mnt/usb-root/boot
sudo cp boot/Image-${KVER} /mnt/usb-root/boot/

Passo 4f — Copia i file necessari per l'installazione

# Copia i kpart nel rootfs live (serviranno in Sezione 10)
sudo mkdir -p /mnt/usb-root/root/install
sudo cp kpart-emmc.bin         /mnt/usb-root/root/install/
sudo cp cmdline-emmc.txt       /mnt/usb-root/root/install/
sudo cp magneton.itb           /mnt/usb-root/root/install/
sudo cp magneton.its           /mnt/usb-root/root/install/

# Copia i moduli compressi per installarli sull'eMMC
sudo cp linux-${KVER}-aarch64-modules.tar.gz /mnt/usb-root/root/install/

Passo 4g — Configura Alpine per l'avvio automatico

# DNS resolver
echo "nameserver 8.8.8.8" | sudo tee /mnt/usb-root/etc/resolv.conf

# inittab: avvia getty su tty1 (tastiera del Chromebook)
sudo tee /mnt/usb-root/etc/inittab << 'EOF'
::sysinit:/sbin/openrc sysinit
::sysinit:/sbin/openrc boot
::wait:/sbin/openrc default
tty1::respawn:/sbin/getty 115200 tty1
tty2::respawn:/sbin/getty 115200 tty2
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/openrc shutdown
EOF

# /etc/fstab minimale
sudo tee /mnt/usb-root/etc/fstab << 'EOF'
/dev/sda2   /       ext4    defaults    0 1
proc        /proc   proc    defaults    0 0
sysfs       /sys    sysfs   defaults    0 0
devpts      /dev/pts devpts defaults    0 0
EOF

# Script di benvenuto da eseguire al login
sudo tee /mnt/usb-root/root/.profile << 'PROFILE'
echo ""
echo "=== MAGNETON NIXOS INSTALLER ==="
echo "eMMC interna:  /dev/mmcblk0"
echo "microSD:       /dev/mmcblk1"
echo "File install:  /root/install/"
echo ""
echo "Passo successivo: esegui le istruzioni della Sezione 9 della guida"
echo ""
PROFILE

# Smonta
sudo umount /mnt/usb-root
sudo sync

L'USB installer è pronto. Espellilo dal PC host.


5. Attivazione Developer Mode sul Chromebook

Attenzione: l'attivazione cancella tutti i dati utente ChromeOS (powerwash).

  1. Spegni completamente il Chromebook
  2. Tieni premuto Esc + Refresh (F3) + Power simultaneamente → Recovery Mode
  3. Sulla schermata di recovery, premi Ctrl+D
  4. Conferma con Invio
  5. Attendi il powerwash (2-5 minuti)
  6. Al riavvio, sulla schermata con punto esclamativo: NON premere barra spaziatrice
  7. Attendi 30 secondi o premi Ctrl+D per accelerare

Abilita il boot da USB (una sola volta, dentro ChromeOS)

Apri il terminale con Ctrl+Alt+T, digita shell, poi:

sudo su
enable_dev_usb_boot
crossystem dev_boot_usb=1
crossystem dev_boot_signed_only=0

6. Disabilitazione hardware write-protect (WP)

Sul magneton il chip di sicurezza è un GSC (Google Security Chip / CR50/TI50). Su questi dispositivi la write-protection hardware si disabilita scollegando la batteria mentre il sistema è alimentato via USB-C. Non c'è una vite WP separata come sui Chromebook più vecchi.

Procedura:

  1. Spegni il Chromebook
  2. Rimuovi le viti sul fondo del case (Phillips #0 o Torx T5)
  3. Rimuovi delicatamente il back cover facendo leva dal bordo posteriore
  4. Scollega il connettore flat della batteria dalla scheda madre
  5. Collega il caricabatterie USB-C
  6. Accendi: il dispositivo funzionerà solo con l'alimentatore esterno

Verifica in ChromeOS shell:

sudo flashrom --wp-status
# Output atteso: "WP: write protect is disabled"

Lascia la batteria scollegata fino a dopo la Sezione 7 (GBB Flags). Puoi ricollegarla e rimontare il case dopo.


7. Impostazione GBB Flags

Le GBB (Google Binary Block) Flags sono salvate nella flash ROM del firmware. Impostarle correttamente è essenziale: se la batteria si scarica completamente con ChromeOS rimosso e i flag non sono settati, il dispositivo non potrà più avviare da USB e sarà inutilizzabile senza un recovery.

Significato dei flag

Bit Valore Effetto
GBB_FLAG_DEV_SCREEN_SHORT_DELAY 0x01 Riduce countdown avvio da 30s a 2s
GBB_FLAG_FORCE_DEV_SWITCH_ON 0x08 Blocca Developer Mode permanentemente
GBB_FLAG_FORCE_DEV_BOOT_USB 0x10 Abilita boot USB permanentemente

Valore raccomandato: 0x19 (= 0x01 + 0x08 + 0x10)

Scrittura in ChromeOS shell (come root, con WP disabilitata)

sudo su

# Metodo 1 — futility diretto (prova prima questo)
futility gbb --set --flash --flags=0x19

# Se ottieni "ERROR: unrecognized option (possibly '--flash')"
# usa gli script wrapper Google:
/usr/share/vboot/bin/set_gbb_flags.sh 0x19

# Verifica
/usr/share/vboot/bin/get_gbb_flags.sh
# Deve riportare: 0x00000019

Dopo la verifica: ricollega la batteria e rimonta il back cover.


8. Avvio dal USB installer (Ctrl+U)

  1. Inserisci l'USB installer nel Chromebook
  2. Riavvia il Chromebook
  3. Alla schermata "OS verification is OFF": premi Ctrl+U
  4. Il Chromebook avvierà il live Alpine AArch64 dal USB

Il boot può richiedere 30-60 secondi. Comparirà un prompt di login.

Login: root (nessuna password)

Verifica che il kernel sia carico correttamente:

uname -r
# Output atteso: 6.12.66-stb-cbm+ (o versione scaricata)

lsblk
# Deve mostrare:
# sda       (USB drive)
# mmcblk0   (eMMC interna, ~64 GB)
# mmcblk1   (microSD, se inserita)

9. Dal live Alpine — partizionamento eMMC e installazione NixOS

Tutto quello che segue si esegue sul Chromebook, nel live Alpine.

Passo 9a — Connetti alla rete WiFi

# Carica il modulo WiFi
modprobe mt7921e

# Verifica che l'interfaccia sia rilevata
ip link show
# Deve mostrare un'interfaccia wlan0 o simile

# Connessione con wpa_supplicant
wpa_passphrase "NomeRete" "password" > /tmp/wpa.conf
wpa_supplicant -B -i wlan0 -c /tmp/wpa.conf
udhcpc -i wlan0

# Test connessione
ping -c 3 1.1.1.1

Passo 9b — Installa i tool necessari nel live Alpine

# Configura APK (gestore pacchetti Alpine)
echo "https://dl-cdn.alpinelinux.org/alpine/v3.21/main" > /etc/apk/repositories
echo "https://dl-cdn.alpinelinux.org/alpine/v3.21/community" >> /etc/apk/repositories

apk update
apk add vboot-utils e2fsprogs parted wget curl tar xz util-linux

Se non hai rete, cgpt potrebbe già essere nel rootfs. Verifica con which cgpt. Se mancano tool critici, puoi anche copiarli dall'USB stesso oppure dal tarball di ChromeOS già presente.

Passo 9c — Partizionamento eMMC con cgpt

EMMC=/dev/mmcblk0   # eMMC interna

# Attenzione: questo cancella TUTTO su mmcblk0, incluso ChromeOS
cgpt create ${EMMC}

# Partizione 1: KERN-A — 64 MiB per il kpart
cgpt add -i 1 \
    -b 8192 -s 131072 \
    -t kernel \
    -l "KERN-A" \
    -P 15 -T 1 -S 1 \
    ${EMMC}

# Partizione 2: ROOT — tutto il resto
ROOT_START=139264
DISK_SECTORS=$(blockdev --getsz ${EMMC})
ROOT_SIZE=$(( DISK_SECTORS - ROOT_START - 33 ))

cgpt add -i 2 \
    -b ${ROOT_START} \
    -s ${ROOT_SIZE} \
    -t data \
    -l "ROOT-A" \
    ${EMMC}

# Protective MBR
cgpt boot -p ${EMMC}

# Verifica
cgpt show ${EMMC}

Output atteso:

       start        size    part  contents
           0           1          PMBR
           1           1          Pri GPT header
           2          32          Pri GPT table
        8192      131072       1  Label: "KERN-A"
                                  Type: ChromeOS kernel
                                  Attr: priority=15 tries=1 successful=1
      139264   <N settori>    2  Label: "ROOT-A"
                                  Type: Linux data

Passo 9d — Formatta e monta la partizione root

# Forza il riconoscimento delle nuove partizioni
partprobe ${EMMC} 2>/dev/null || true
sleep 2

# Formatta come ext4
mkfs.ext4 -L nixos ${EMMC}p2

# Monta
mkdir -p /mnt/nixos
mount ${EMMC}p2 /mnt/nixos

Passo 9e — Scarica e installa NixOS AArch64

Hai due opzioni:

Opzione A — Tarball NixOS minimal (richiede rete)

# Scarica il tarball NixOS AArch64 minimal
NIXOS_VER="25.05"
wget "https://channels.nixos.org/nixos-${NIXOS_VER}/latest-nixos-minimal-aarch64-linux.tar.xz" \
    -O /tmp/nixos.tar.xz

tar xJf /tmp/nixos.tar.xz -C /mnt/nixos --strip-components=1

Opzione B — Installa Nix nel live Alpine e usa nixos-install

# Installa Nix nel live Alpine
apk add nix
# Poi nixos-generate-config e nixos-install come nella Sezione 12

Opzione A è più rapida. Continuiamo con quella.

Passo 9f — Monta i filesystem virtuali per il chroot

mount --bind /dev  /mnt/nixos/dev
mount --bind /proc /mnt/nixos/proc
mount --bind /sys  /mnt/nixos/sys
mount -t devpts devpts /mnt/nixos/dev/pts

Passo 9g — Installa i moduli kernel nell'eMMC

KVER="6.12.66-stb-cbm+"

# Copia dall'USB (già presente in /root/install)
cp /root/install/linux-${KVER}-aarch64-modules.tar.gz /tmp/

tar xzf /tmp/linux-${KVER}-aarch64-modules.tar.gz \
    -C /mnt/nixos \
    --strip-components=0

# Copia anche l'Image e i DTB (utili per ricreare il kpart dall'interno di NixOS)
mkdir -p /mnt/nixos/boot/dtb-${KVER}
cp /boot/Image-${KVER} /mnt/nixos/boot/
cp /root/install/magneton.its /mnt/nixos/boot/
cp /root/install/cmdline-emmc.txt /mnt/nixos/boot/

# Verifica
ls /mnt/nixos/lib/modules/

10. Scrittura del kpart sull'eMMC

Ancora dal live Alpine, scrivi il kpart-emmc.bin sulla partizione KERN-A:

# Il file è già nel rootfs USB live
dd if=/root/install/kpart-emmc.bin of=/dev/mmcblk0p1 bs=4M status=progress
sync

# Riconferma gli attributi CGpt (per sicurezza)
cgpt add -i 1 -P 15 -T 1 -S 1 /dev/mmcblk0

# Verifica
cgpt show /dev/mmcblk0 | grep -A4 "KERN-A"
# Deve mostrare: Attr: priority=15 tries=1 successful=1

11. Configurazione NixOS (configuration.nix)

La configurazione è suddivisa in tre file nella directory /etc/nixos/:

/etc/nixos/
├── configuration.nix        ← sistema base + Niri + servizi
├── hardware-configuration.nix  ← generato automaticamente
└── home.nix                 ← Home Manager: Waybar, Fuzzel, config utente

Entra nel chroot e genera la configurazione hardware:

chroot /mnt/nixos /bin/bash
nixos-generate-config --root /

File 1 — /etc/nixos/configuration.nix

{ config, lib, pkgs, ... }:

{
  imports = [
    ./hardware-configuration.nix
    ./home.nix
  ];

  # ============================================================
  # BOOTLOADER
  # Su Depthcharge NON si usa GRUB né systemd-boot.
  # Il kernel viene caricato direttamente dal kpart ChromeOS.
  # Il kpart va aggiornato manualmente dopo ogni cambio kernel
  # (vedi Sezione 13).
  # ============================================================
  boot.loader.grub.enable = false;
  boot.loader.generic-extlinux-compatible.enable = false;

  # Parametri kernel — devono corrispondere a cmdline-emmc.txt
  boot.kernelParams = [
    "console=tty1"
    "root=/dev/mmcblk0p2"
    "rootwait"
    "rw"
    "quiet"
  ];

  # ============================================================
  # KERNEL
  # linuxPackages_latest → 6.12+ con supporto corsola/magneton
  # ============================================================
  boot.kernelPackages = pkgs.linuxPackages_latest;

  boot.initrd.availableKernelModules = [ "mmc_block" "mmc_core" ];

  boot.kernelModules = [
    "mt7921e"   # WiFi MediaTek MT7921
    "panfrost"  # GPU Mali-G52 (open source)
  ];

  # ============================================================
  # FILESYSTEM
  # ============================================================
  fileSystems."/" = {
    device = "/dev/disk/by-label/nixos";
    fsType = "ext4";
    options = [ "defaults" "noatime" ];
  };

  swapDevices = [{ device = "/swapfile"; size = 2048; }];

  # ============================================================
  # HARDWARE
  # ============================================================
  hardware.enableRedistributableFirmware = true;
  hardware.firmware = [ pkgs.linux-firmware ];

  # GPU Panfrost — driver open source Mali
  hardware.opengl = {
    enable = true;
    driSupport = true;
  };

  # ============================================================
  # WAYLAND / NIRI
  # Niri è un compositor Wayland scrolling-tiled.
  # Non usa programs.sway né programs.hyprland.
  # ============================================================
  programs.niri = {
    enable = true;
    # Installa niri come pacchetto di sistema e abilita il
    # portale XDG necessario per screen capture e file picker
  };

  # Portale XDG — richiesto da molte app Wayland (Firefox, ecc.)
  xdg.portal = {
    enable = true;
    extraPortals = [ pkgs.xdg-desktop-portal-gnome ];
    config.common.default = "*";
  };

  # Variabili d'ambiente Wayland globali
  environment.sessionVariables = {
    NIXOS_OZONE_WL       = "1";   # Electron/Chromium su Wayland
    MOZ_ENABLE_WAYLAND   = "1";   # Firefox
    QT_QPA_PLATFORM      = "wayland";
    GDK_BACKEND          = "wayland";
    CLUTTER_BACKEND      = "wayland";
    XDG_SESSION_TYPE     = "wayland";
    XDG_CURRENT_DESKTOP  = "niri";
  };

  # ============================================================
  # DISPLAY MANAGER — greetd con autologin
  # (alternativa leggera a GDM/SDDM, adatta ad ARM)
  # ============================================================
  services.greetd = {
    enable = true;
    settings = {
      default_session = {
        command = "${pkgs.niri}/bin/niri-session";
        user    = "richard";
      };
    };
  };

  # ============================================================
  # AUDIO — PipeWire
  # ============================================================
  security.rtkit.enable = true;
  services.pipewire = {
    enable            = true;
    alsa.enable       = true;
    alsa.support32Bit = false;   # ARM64, no 32-bit
    pulse.enable      = true;
    jack.enable       = false;
  };

  # ============================================================
  # RETE
  # ============================================================
  networking.hostName = "magneton";
  networking.networkmanager.enable = true;

  # ============================================================
  # LOCALE
  # ============================================================
  time.timeZone = "Europe/Rome";
  i18n.defaultLocale = "it_IT.UTF-8";
  console.keyMap = "it";

  # ============================================================
  # FONT
  # ============================================================
  fonts = {
    enableDefaultPackages = true;
    packages = with pkgs; [
      noto-fonts
      noto-fonts-emoji
      nerd-fonts.jetbrains-mono   # font per Waybar e terminale
      nerd-fonts.iosevka
    ];
    fontconfig.defaultFonts = {
      monospace = [ "JetBrainsMono Nerd Font" ];
      sansSerif = [ "Noto Sans" ];
      serif     = [ "Noto Serif" ];
    };
  };

  # ============================================================
  # PACCHETTI DI SISTEMA
  # ============================================================
  environment.systemPackages = with pkgs; [
    # Wayland essentials
    wayland-utils
    wl-clipboard      # wl-copy / wl-paste
    wlr-randr         # gestione display Wayland
    grim              # screenshot Wayland
    slurp             # selezione area per grim

    # Terminale
    foot              # terminale Wayland nativo, leggero

    # Applicazioni base
    firefox-wayland
    nautilus          # file manager GTK

    # Tool di sistema
    wget curl git vim htop
    networkmanagerapplet  # nm-applet per tray WiFi

    # Tool per gestione kpart (necessari dopo ogni aggiornamento kernel)
    vboot_utils       # cgpt, futility
    ubootTools        # mkimage
    dtc               # device tree compiler

    pciutils usbutils
  ];

  # ============================================================
  # UTENTE
  # ============================================================
  users.users.richard = {
    isNormalUser = true;
    extraGroups  = [ "wheel" "networkmanager" "video" "audio" "input" ];
    initialPassword = "changeme";
    shell = pkgs.bash;
  };

  # ============================================================
  # HOME MANAGER — integrazione in-system
  # ============================================================
  home-manager.useGlobalPkgs   = true;
  home-manager.useUserPackages = true;

  # ============================================================
  # SSH
  # ============================================================
  services.openssh.enable = true;

  system.stateVersion = "25.05";
}

File 2 — /etc/nixos/home.nix

Questo file gestisce la configurazione utente tramite Home Manager: Waybar, Fuzzel e la config di Niri sono dichiarativi qui.

Nota: Home Manager deve essere aggiunto come input flake oppure come canale. Il modo più semplice senza flakes:

sudo nix-channel --add https://github.com/nix-community/home-manager/archive/release-25.05.tar.gz home-manager
sudo nix-channel --update

poi aggiungere imports = [ <home-manager/nixos> ] in configuration.nix (già incluso con ./home.nix come wrapper qui sotto).

# /etc/nixos/home.nix
{ config, pkgs, ... }:

{
  imports = [ <home-manager/nixos> ];

  home-manager.users.richard = { pkgs, config, lib, ... }: {

    home.stateVersion = "25.05";
    home.username     = "richard";
    home.homeDirectory = "/home/richard";

    # ============================================================
    # NIRI — configurazione compositor
    # File: ~/.config/niri/config.kdl
    # ============================================================
    programs.niri.settings = {
      # Layout scrolling tiles
      layout = {
        gaps = 8;
        center-focused-column = "never";
        preset-column-widths = [
          { proportion = 0.33333; }
          { proportion = 0.5; }
          { proportion = 0.66667; }
        ];
        default-column-width = { proportion = 0.5; };
        focus-ring = {
          enable = true;
          width  = 2;
          active-color   = "#7fc8ff";
          inactive-color = "#505050";
        };
        border = {
          enable = false;
        };
      };

      # Comportamento finestre
      prefer-no-csd = true;

      # Spawn al login
      spawn-at-startup = [
        { command = [ "waybar" ]; }
        { command = [ "nm-applet" "--indicator" ]; }
      ];

      # Input
      input = {
        keyboard = {
          xkb = {
            layout = "it";
            options = "caps:escape";  # CapsLock → Escape
          };
          repeat-delay    = 400;
          repeat-rate     = 30;
        };
        touchpad = {
          tap                    = true;
          dwt                    = true;   # disabilita durante digitazione
          natural-scroll         = true;
          scroll-method          = "two-finger";
          click-method           = "clickfinger";
          accel-speed            = 0.2;
        };
      };

      # Output (display interno magneton)
      outputs = {
        "eDP-1" = {
          scale  = 1.0;
          # transform = "normal";
        };
      };

      # Animazioni
      animations = {
        slowdown = 1.0;
        workspace-switch.spring = {
          damping-ratio = 1.0;
          stiffness     = 1000;
          epsilon       = 0.0001;
        };
        window-open.duration-ms   = 150;
        window-close.duration-ms  = 150;
      };

      # Keybindings
      binds = with config.lib.niri.actions; {
        # Launcher
        "Mod+Space".action   = spawn "fuzzel";
        # Terminale
        "Mod+Return".action  = spawn "foot";
        # Browser
        "Mod+B".action       = spawn "firefox";
        # File manager
        "Mod+E".action       = spawn "nautilus";
        # Screenshot schermo intero
        "Mod+Shift+S".action = spawn "sh" "-c" "grim ~/screenshot-$(date +%Y%m%d-%H%M%S).png";
        # Screenshot area selezionata
        "Mod+S".action       = spawn "sh" "-c" "grim -g \"$(slurp)\" ~/screenshot-$(date +%Y%m%d-%H%M%S).png";
        # Clipboard screenshot
        "Print".action       = spawn "sh" "-c" "grim - | wl-copy";

        # Gestione finestre
        "Mod+Q".action       = close-window;
        "Mod+H".action       = focus-column-left;
        "Mod+L".action       = focus-column-right;
        "Mod+J".action       = focus-window-down;
        "Mod+K".action       = focus-window-up;
        "Mod+Shift+H".action = move-column-left;
        "Mod+Shift+L".action = move-column-right;
        "Mod+Shift+J".action = move-window-down;
        "Mod+Shift+K".action = move-window-up;

        # Dimensioni colonne
        "Mod+R".action       = switch-preset-column-width;
        "Mod+F".action       = maximize-column;
        "Mod+Shift+F".action = fullscreen-window;

        # Workspace
        "Mod+1".action       = focus-workspace 1;
        "Mod+2".action       = focus-workspace 2;
        "Mod+3".action       = focus-workspace 3;
        "Mod+4".action       = focus-workspace 4;
        "Mod+5".action       = focus-workspace 5;
        "Mod+Shift+1".action = move-column-to-workspace 1;
        "Mod+Shift+2".action = move-column-to-workspace 2;
        "Mod+Shift+3".action = move-column-to-workspace 3;
        "Mod+Shift+4".action = move-column-to-workspace 4;
        "Mod+Shift+5".action = move-column-to-workspace 5;

        # Volume (tasti Fn Chromebook)
        "XF86AudioRaiseVolume".action  = spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "5%+";
        "XF86AudioLowerVolume".action  = spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "5%-";
        "XF86AudioMute".action         = spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle";

        # Luminosità (richiede brightnessctl)
        "XF86MonBrightnessUp".action   = spawn "brightnessctl" "set" "+10%";
        "XF86MonBrightnessDown".action = spawn "brightnessctl" "set" "10%-";

        # Esci da Niri
        "Mod+Shift+E".action = quit;
      };
    };

    # ============================================================
    # WAYBAR
    # ============================================================
    programs.waybar = {
      enable  = true;
      systemd.enable = false;  # avviato da niri spawn-at-startup

      style = ''
        * {
          font-family: "JetBrainsMono Nerd Font", monospace;
          font-size: 13px;
          border: none;
          border-radius: 0;
          min-height: 0;
        }

        window#waybar {
          background-color: rgba(20, 20, 28, 0.92);
          color: #cdd6f4;
        }

        .modules-left,
        .modules-center,
        .modules-right {
          padding: 2px 8px;
        }

        #workspaces button {
          padding: 2px 8px;
          color: #6c7086;
          background: transparent;
        }

        #workspaces button.active {
          color: #cdd6f4;
          background: rgba(137, 180, 250, 0.15);
          border-bottom: 2px solid #89b4fa;
        }

        #workspaces button:hover {
          color: #cdd6f4;
          background: rgba(255,255,255,0.07);
        }

        #clock {
          color: #cdd6f4;
          padding: 0 10px;
        }

        #battery {
          color: #a6e3a1;
          padding: 0 8px;
        }

        #battery.warning  { color: #f9e2af; }
        #battery.critical { color: #f38ba8; }

        #network {
          color: #89dceb;
          padding: 0 8px;
        }

        #network.disconnected { color: #6c7086; }

        #pulseaudio {
          color: #cba6f7;
          padding: 0 8px;
        }

        #pulseaudio.muted { color: #6c7086; }

        #cpu, #memory {
          color: #fab387;
          padding: 0 8px;
        }

        #temperature {
          color: #a6e3a1;
          padding: 0 8px;
        }

        #temperature.critical { color: #f38ba8; }

        #tray {
          padding: 0 8px;
        }
      '';

      settings = [{
        layer    = "top";
        position = "top";
        height   = 28;
        spacing  = 4;

        modules-left   = [ "niri/workspaces" "niri/window" ];
        modules-center = [ "clock" ];
        modules-right  = [
          "cpu" "memory" "temperature"
          "pulseaudio" "network" "battery"
          "tray"
        ];

        "niri/workspaces" = {
          format = "{name}";
        };

        "niri/window" = {
          max-length = 50;
          separate-outputs = true;
        };

        clock = {
          format          = " {:%H:%M}";
          format-alt      = " {:%A %d %B %Y}";
          tooltip-format  = "<tt>{calendar}</tt>";
          calendar = {
            mode        = "year";
            mode-mon-col = 3;
            on-scroll   = 1;
            format = {
              months    = "<span color='#cdd6f4'><b>{}</b></span>";
              days      = "<span color='#cdd6f4'><b>{}</b></span>";
              weekdays  = "<span color='#89b4fa'><b>{}</b></span>";
              today     = "<span color='#a6e3a1'><b><u>{}</u></b></span>";
            };
          };
        };

        battery = {
          states = {
            warning  = 30;
            critical = 15;
          };
          format            = "{icon} {capacity}%";
          format-charging   = "󰂄 {capacity}%";
          format-plugged    = "󰚥 {capacity}%";
          format-icons      = [ "󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰂂" "󰁹" ];
          tooltip-format    = "{timeTo} — {power:.1f}W";
        };

        network = {
          format-wifi         = "󰤨 {essid}";
          format-ethernet     = "󰈀 {ifname}";
          format-disconnected = "󰤭 Disconnesso";
          tooltip-format-wifi = "{signalStrength}% — {frequency}GHz";
          on-click            = "nm-connection-editor";
        };

        pulseaudio = {
          format        = "{icon} {volume}%";
          format-muted  = "󰝟 Muto";
          format-icons  = {
            default = [ "󰕿" "󰖀" "󰕾" ];
          };
          on-click      = "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle";
          on-click-right = "pavucontrol";
          scroll-step   = 5;
        };

        cpu = {
          format   = "󰻠 {usage}%";
          interval = 5;
          tooltip  = false;
        };

        memory = {
          format   = "󰍛 {used:.1f}G";
          interval = 10;
          tooltip-format = "Usata: {used:.1f}G / {total:.1f}G";
        };

        temperature = {
          thermal-zone    = 0;
          critical-threshold = 80;
          format          = "󰔏 {temperatureC}°C";
          format-critical = "󰸁 {temperatureC}°C";
        };

        tray = {
          icon-size   = 16;
          spacing     = 8;
          show-passive-items = true;
        };
      }];
    };

    # ============================================================
    # FUZZEL — launcher applicazioni Wayland
    # ============================================================
    programs.fuzzel = {
      enable = true;
      settings = {
        main = {
          font               = "JetBrainsMono Nerd Font:size=13";
          dpi-aware          = "auto";
          width              = 35;
          lines              = 10;
          tabs               = 8;
          horizontal-pad     = 16;
          vertical-pad       = 8;
          inner-pad          = 4;
          image-size-ratio   = 0.5;
          prompt             = "  ";
          terminal           = "foot -e";
          launch-prefix      = "";
          match-mode         = "fzf";
          show-actions       = true;
          icons-enabled      = true;
          icon-theme         = "Papirus-Dark";
        };
        colors = {
          # Schema Catppuccin Mocha
          background        = "1e1e2edd";
          text              = "cdd6f4ff";
          match             = "89b4faff";
          selection         = "313244ff";
          selection-text    = "cdd6f4ff";
          selection-match   = "89b4faff";
          border            = "89b4fa99";
        };
        border = {
          width  = 2;
          radius = 8;
        };
        dmenu = {
          exit-immediately-if-only-one-item = true;
        };
      };
    };

    # ============================================================
    # FOOT — terminale Wayland
    # ============================================================
    programs.foot = {
      enable = true;
      settings = {
        main = {
          font        = "JetBrainsMono Nerd Font:size=11";
          pad         = "8x8";
          shell       = "bash";
          term        = "foot";
        };
        colors = {
          # Catppuccin Mocha
          background  = "1e1e2e";
          foreground  = "cdd6f4";
          regular0    = "45475a";
          regular1    = "f38ba8";
          regular2    = "a6e3a1";
          regular3    = "f9e2af";
          regular4    = "89b4fa";
          regular5    = "f5c2e7";
          regular6    = "94e2d5";
          regular7    = "bac2de";
          bright0     = "585b70";
          bright1     = "f38ba8";
          bright2     = "a6e3a1";
          bright3     = "f9e2af";
          bright4     = "89b4fa";
          bright5     = "f5c2e7";
          bright6     = "94e2d5";
          bright7     = "a6adc8";
        };
        cursor = {
          style = "beam";
          blink = "yes";
        };
        scrollback = {
          lines = 5000;
        };
      };
    };

    # ============================================================
    # PACCHETTI UTENTE aggiuntivi
    # ============================================================
    home.packages = with pkgs; [
      brightnessctl    # controllo luminosità da tastiera
      pavucontrol      # mixer audio grafico
      papirus-icon-theme  # icone per Fuzzel
      playerctl        # controllo media player
    ];

    # ============================================================
    # VARIABILI D'AMBIENTE utente
    # ============================================================
    home.sessionVariables = {
      EDITOR  = "vim";
      BROWSER = "firefox";
      TERM    = "foot";
    };

  }; # fine home-manager.users.richard
}

---

## 12. nixos-install e primo avvio

Ancora nel chroot, o uscendo e usando nixos-install da fuori:

```bash
# Esci dal chroot se ci sei dentro
exit

# Esegui l'installazione
nixos-install --root /mnt/nixos
# Imposta la password di root quando richiesto

# Smonta tutto
umount -R /mnt/nixos
sync

Riavvio:

reboot

Al riavvio:

  1. Rimuovi l'USB mentre il Chromebook si spegne
  2. Alla schermata "OS verification is OFF": premi Ctrl+D (avvia da eMMC)
  3. NixOS si avvierà

Se il boot fallisce (kernel panic / no root):


13. Gestione degli aggiornamenti del kernel

Importante: Depthcharge non aggiorna il kpart automaticamente. Ogni volta che il kernel cambia (dopo nixos-rebuild switch), devi ricreare e riscrivere il kpart manualmente dall'interno di NixOS.

Workflow post-aggiornamento

# 1. Aggiorna il sistema
sudo nixos-rebuild switch

# 2. Identifica il nuovo kernel
NEW_KERNEL=$(readlink -f /run/current-system/kernel)
echo "Nuovo kernel: ${NEW_KERNEL}"

# 3. Vai nella directory di lavoro
cd /boot   # dove hai copiato magneton.its e cmdline-emmc.txt (Sezione 9g)

# 4. Aggiorna il percorso kernel nel .its
# (Sostituisci il percorso del kernel nel file magneton.its con quello nuovo)
sed -i "s|data = /incbin/(\"boot/Image.*\")|data = /incbin/(\"${NEW_KERNEL}\")|" magneton.its

# 5. Usa i DTB del nuovo kernel (se aggiornato)
# I DTB magneton si trovano in:
# /run/current-system/kernel-modules/lib/modules/$(uname -r)/dtb/mediatek/
# oppure in /boot/dtb-${KVER}/ se hai estratto il tarball hexdump0815

# 6. Ricrea il FIT image
mkimage -f magneton.its magneton.itb

# 7. Firma il nuovo kpart
DEVKEYS="/nix/var/nix/profiles/default/share/vboot/devkeys"
# oppure: find /nix/store -name "kernel.keyblock" 2>/dev/null | head -1 | xargs dirname

sudo futility vbutil_kernel \
    --arch arm --version 1 \
    --keyblock    "${DEVKEYS}/kernel.keyblock" \
    --signprivate "${DEVKEYS}/kernel_data_key.vbprivk" \
    --bootloader  cmdline-emmc.txt \
    --config      cmdline-emmc.txt \
    --vmlinuz     magneton.itb \
    --pack        kpart-emmc-new.bin

# 8. Scrivi il nuovo kpart
sudo dd if=kpart-emmc-new.bin of=/dev/mmcblk0p1 bs=4M status=progress
sudo sync
sudo cgpt add -i 1 -P 15 -T 1 -S 1 /dev/mmcblk0

echo "Kpart aggiornato. Riavvia."

Script helper /usr/local/bin/update-kpart

Salva questo script in NixOS per semplificare gli aggiornamenti:

#!/usr/bin/env bash
set -euo pipefail

EMMC="${1:-/dev/mmcblk0}"
WORKDIR="/boot"
DEVKEYS="$(find /nix/store -name 'kernel.keyblock' 2>/dev/null | head -1 | xargs dirname)"

if [[ -z "${DEVKEYS}" ]]; then
    echo "ERRORE: vboot dev keys non trovate. Installa vboot_utils."
    exit 1
fi

NEW_KERNEL=$(readlink -f /run/current-system/kernel)
echo "Kernel: ${NEW_KERNEL}"
echo "Dev keys: ${DEVKEYS}"

cd "${WORKDIR}"

# Aggiorna percorso kernel nel .its
sed -i "s|data = /incbin/(\"[^\"]*Image[^\"]*\")|data = /incbin/(\"${NEW_KERNEL}\")|" magneton.its

mkimage -f magneton.its magneton.itb

futility vbutil_kernel \
    --arch arm --version 1 \
    --keyblock    "${DEVKEYS}/kernel.keyblock" \
    --signprivate "${DEVKEYS}/kernel_data_key.vbprivk" \
    --bootloader  cmdline-emmc.txt \
    --config      cmdline-emmc.txt \
    --vmlinuz     magneton.itb \
    --pack        kpart-new.bin

echo "Scrittura kpart su ${EMMC}p1..."
dd if=kpart-new.bin of="${EMMC}p1" bs=4M status=progress
sync
cgpt add -i 1 -P 15 -T 1 -S 1 "${EMMC}"

echo "Fatto. Riavvia con: sudo reboot"
chmod +x /usr/local/bin/update-kpart

14. Stato dell'hardware

Componente Stato Note
CPU MT8186 (8 core) cpufreq e thermal OK
Display 14" FHD DRM/KMS attivo, visibile dal boot
GPU Mali-G52 (Panfrost) Driver open source, Mesa ≥ 23.x
WiFi MT7921 Modulo mt7921e, firmware in linux-firmware
Tastiera / Touchpad Input PS/2 standard
eMMC interna /dev/mmcblk0
microSD /dev/mmcblk1
USB-C (charging + data) OK
USB-C DisplayPort ✅ parziale Richiede patch extra (kernel ≥ 6.12.18)
Audio ⚠️ parziale Patch incluse da kernel 6.12.42+; UCM profile da configurare
Bluetooth ⚠️ parziale Firmware mt7921_bt* necessario
Touchscreen (SKU dipendente) ⚠️ Varia per SKU 393216/217/218
Suspend/Resume ⚠️ non testato Potenzialmente funzionante su 6.12+
Webcam ⚠️ non testato Driver MIPI CSI da verificare

Workaround audio in configuration.nix:

sound.enable = true;
hardware.alsa.enable = true;
environment.systemPackages = [ pkgs.alsa-ucm-conf pkgs.alsa-utils ];
boot.kernelModules = [ "snd_soc_mt8186_mt6366" ];

15. Ripristino di ChromeOS

Se vuoi tornare a ChromeOS:

  1. Scarica l'immagine di recovery ufficiale da chromeosdatarestore.withgoogle.com cercando STEELIX o MAGNETON
  2. Usa Chromebook Recovery Utility (estensione Chrome) su un altro PC per creare un USB di recovery
  3. Sul Chromebook: tieni premuto Esc + Refresh + Power → Recovery Mode
  4. Inserisci l'USB di recovery e segui le istruzioni

ChromeOS sovrascriverà l'intera eMMC e ripristinerà le GBB Flags ai valori di fabbrica.


Riferimenti


Guida scritta Giugno 2026 — Kernel 6.12.66-stb-cbm+ / NixOS 25.05