🚀 NixOS su Lenovo IdeaPad Slim 3 Chromebook 14M868
Versione HTML Premium • Tema Catppuccin Mocha • Syntax Highlighting • Navigazione laterale
NixOS sul Lenovo IdeaPad Slim 3 Chromebook 14M868
Guida completa — MediaTek Kompanio 520 (MT8186 / google-corsola-magneton)
**⚠️ AVVERTENZE PRELIMINARI**
- Questa procedura **cancella ChromeOS** dalla eMMC interna in modo definitivo
- Il Depthcharge bootloader di ChromeOS **non avvia ISO standard** — richiede un kpart firmato con vboot keys
- Per questo motivo serve un **USB installer personalizzato** (costruito sull'host), non una ISO NixOS standard
- Il processo richiede di aprire fisicamente il case per disabilitare la write-protection hardware
- Board codename: **magneton** — tre varianti hardware (SKU 393216 / 393217 / 393218)
- Kernel mainline 6.12+ supporta corsola/magneton; GPU via driver **Panfrost**; audio parziale
---
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](#1-prerequisiti)
2. [Preparazione host Linux — tool e kernel](#2-preparazione-host-linux--tool-e-kernel)
3. [Creazione del kpart firmato](#3-creazione-del-kpart-firmato)
4. [Creazione dell'USB installer ChromeOS-compatibile](#4-creazione-dellUSB-installer-chromeos-compatibile)
5. [Attivazione Developer Mode sul Chromebook](#5-attivazione-developer-mode-sul-chromebook)
6. [Disabilitazione hardware write-protect (WP)](#6-disabilitazione-hardware-write-protect-wp)
7. [Impostazione GBB Flags](#7-impostazione-gbb-flags)
8. [Avvio dal USB installer (Ctrl+U)](#8-avvio-dal-usb-installer-ctrlu)
9. [Dal live Alpine — partizionamento eMMC e installazione NixOS](#9-dal-live-alpine--partizionamento-emmc-e-installazione-nixos)
10. [Scrittura del kpart sull'eMMC](#10-scrittura-del-kpart-sullemmc)
11. [Configurazione NixOS (configuration.nix)](#11-configurazione-nixos-configurationnix)
12. [nixos-install e primo avvio](#12-nixos-install-e-primo-avvio)
13. [Gestione degli aggiornamenti del kernel](#13-gestione-degli-aggiornamenti-del-kernel)
14. [Stato dell'hardware](#14-stato-dellhardware)
15. [Ripristino di ChromeOS](#15-ripristino-di-chromeos)
---
1. Prerequisiti
**Hardware necessario:**
- Lenovo IdeaPad Slim 3 Chromebook 14M868
- Cacciavite Phillips #0 (o Torx T5 a seconda del lotto di produzione)
- USB drive ≥ 4 GB (per l'installer)
- PC Linux (host) per costruire l'installer — qualsiasi distro x86_64 va bene
**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](https://github.com/hexdump0815/linux-mainline-mediatek-mt81xx-kernel)
fornisce kernel mainline patchati e testati esplicitamente su magneton.
Controlla la [pagina releases](https://github.com/hexdump0815/linux-mainline-mediatek-mt81xx-kernel/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:
- `kpart-usb.bin` → `root=/dev/sda2` (per avviare dal live USB)
- `kpart-emmc.bin` → `root=/dev/mmcblk0p2` (per il sistema finale su eMMC)
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:
```bash
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:
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):
- Reinsersci l'USB, riavvia con Ctrl+U → torna nel live Alpine
- Controlla che `cgpt show /dev/mmcblk0` mostri `priority=15 successful=1` su KERN-A
- Verifica che `root=` in `cmdline-emmc.txt` sia `/dev/mmcblk0p2`
- Riscrivi il kpart con `dd` (Sezione 10)
---
## 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](https://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
- [hexdump0815/linux-mainline-mediatek-mt81xx-kernel](https://github.com/hexdump0815/linux-mainline-mediatek-mt81xx-kernel) — Kernel precompilati per corsola/MT8186, testati su magneton
- [postmarketOS Wiki — ChromeOS devices](https://wiki.postmarketos.org/wiki/ChromeOS_devices) — GBB flags, Developer Mode, WP
- [Chromebook Boot Flow — kernel.org](https://docs.kernel.org/arch/arm/google/chromebook-boot-flow.html) — Come Depthcharge seleziona il DTB dal FIT
- [Gentoo wiki — depthcharge bootable media](https://wiki.gentoo.org/wiki/Creating_bootable_media_for_depthcharge_based_devices) — Creazione FIT + vbutil_kernel
- [thefloweringash/kevin-nix](https://github.com/thefloweringash/kevin-nix) — NixOS su Chromebook ARM con modulo depthcharge
- [Mobile NixOS — lenovo-krane](https://mobile.nixos.org/devices/lenovo-krane.html) — kpart/depthcharge su MT8183 (stesso approccio)
- [alpernebbi/depthcharge-tools](https://github.com/alpernebbi/depthcharge-tools) — Tool alternativo `mkdepthcharge`
---
*Guida scritta Giugno 2026 — Kernel 6.12.66-stb-cbm+ / NixOS 25.05*