Lee Hardy Dydo

The Chromebook Kiosk

October 18, 2026 · Chromebook Kiosk, part 1 of 2

A Lenovo Chromebook showing the developer mode setup screen next to a MacBook Air, a rubber duck, a microSD card and its case, and a USB-C hub on a wooden table
The starting point: the Chromebook wiping itself into developer mode.
DeviceLenovo IdeaPad Slim 3 Chromebook 14M868 (board "magneton", MAGNETON-LCKC), built June 2025
ChipMediaTek Kompanio 520 (MT8186): 2 Cortex-A76 cores at 2.05 GHz, 6 Cortex-A55 at 2.0 GHz
GraphicsArm Mali-G52 MC2 (open-source panfrost driver on Linux)
Memory, storage4 GB LPDDR4X, 64 GB eMMC, microSD slot
Screen14-inch, 1920 by 1080
Wireless, powerWi-Fi 6, Bluetooth; charges over USB-C (asks for 15 V)
PriceAbout $200 new, around $130 used (October 2026)

1A Chromebook with no job

I bought this Chromebook for an idea that didn't take off: work more like a thin client, so I could hand an employee or a contractor a laptop and they'd have everything ready to go. Since then it has mostly sat around. It's the Lenovo in the table above: a $200 laptop.

The new job is smaller: a screen I can set on a countertop anywhere in the house, showing one web page. Mostly things I'd look at in the morning, dashboards I'm trying out, served from my own machines.

2Kiosk mode costs a subscription

ChromeOS has a true kiosk mode: boot straight into one page, no login, no browser controls. But it only works on a managed device. That means a Google admin account on a domain you own, a license of $25 per device per year, enrolling the Chromebook in Google's console, and setting the page from there. Enrollment also sticks: a factory reset doesn't undo it.

Everything else on this project runs on machines in the house, so paying Google yearly for a console that controls what my kitchen screen shows was the wrong direction. Google is also folding ChromeOS into Android. So the question became whether it could run plain Linux instead, with a browser opened full screen at startup.

3ARM changes the route

Most guides for putting Linux on a Chromebook start by replacing its firmware, which only works on Intel and AMD models. This one has a MediaTek Kompanio 520, an ARM chip, and keeps Google's own bootloader, which only starts kernels packaged in its required format.

The good news is that the board, codenamed Magneton, has been in the mainline Linux kernel since early 2024, added by Google and Collabora engineers. postmarketOS, a Linux distribution for phones and small devices, builds images for this family of Chromebooks that the stock bootloader can start from a USB stick or a microSD card. Partway through this, I found out postmarketOS had renamed itself Nura three weeks earlier.

4Developer mode

Booting anything that isn't ChromeOS starts with developer mode, which wipes the Chromebook. No firmware gets touched at this stage, so if Linux doesn't work out, a ChromeOS recovery puts it back the way it was.

There was nothing on it worth keeping: when I signed in one last time, ChromeOS couldn't even sync because my sign-in had gone stale, so I signed out. Then, with the Chromebook on, I held Esc + Refresh (the circular arrow) and pressed Power. It restarted into a recovery screen, and Ctrl + D started the switch. A message said it was transitioning to developer mode and that local data had been cleared, then it sat on "Preparing system for developer mode" for several minutes before restarting on its own.

The ChromeOS firmware screen reading You are in developer mode, with buttons for Return to secure mode, Boot from internal disk and Boot from external disk, and the model MAGNETON-LCKC
The firmware's developer mode screen, from a photo: straightened, and the glare and moiré taken out.

From then on, every boot stops at this screen first. The model line confirms the board: Magneton. "Boot from internal disk," or waiting out the timer, starts ChromeOS as usual.

5Letting it boot from the card

The screen has a "Boot from external disk" button, but it won't work until a setting allows it, and that setting is changed from inside ChromeOS. Developer mode adds a text console. The usual instructions say Ctrl + Alt + the forward-arrow key, but this keyboard has no forward arrow; Refresh sits in that spot and does the job. My first tries did nothing because I was pressing it at the firmware's developer mode screen: the console only exists once ChromeOS is running. At ChromeOS's welcome screen, without signing in, Ctrl + Alt + Refresh worked, and the console opens with its own instructions:

Developer Console

To return to the browser, press:

  [ Ctrl ] and [ Alt ] and [ <- ]  (F1)

To use this console, developer mode must be enabled.
Doing so will destroy any saved data on the system.

In developer mode, it is possible to
- login and sudo as user 'chronos'
- require a password for sudo and login(*)
- disable power management behavior (screen dimming):
  sudo initctl stop powerd
- install your own operating system image!

* To set a password for 'chronos', run the following as root:

chromeos-setdevpasswd

If you are having trouble booting a self-signed kernel, you may need to
enable USB booting.  To do so, run the following as root:

enable_dev_usb_boot

Have fun and send patches!

localhost login: root

Logged in as root, the one command it lists does the job:

magneton-rev3 ~ # enable_dev_usb_boot

    SUCCESS: Booting any self-signed kernel from SSD/USB/SDCard slot is enabled.

    Insert bootable media into USB / SDCard slot and press Ctrl-U in developer
    screen to boot your self-signed image.

magneton-rev3 ~ # crossystem dev_boot_usb
1

The 1 means booting from the card is on. I left the console there until the card was ready, then typed reboot.

6A card with a past

The spare microSD card in the drawer wasn't blank. It came out of an old Android phone and still held 9 GB: photos from 2015, Mandelbrot renders from a fractal zoom app, and, in the folder of an app called Lapse It, ten time-lapses I'd shot on that phone in March 2015, eleven years before the Raspberry Pi time-lapse camera. Most of the 9 GB turned out to be a podcast app's downloaded episodes, which didn't need saving. The other 2 GB did.

The plan was to put it on the backup drive, along with an earlier card's 60 GB that had been waiting on my Mac since the start of the month. The backup drive had other ideas. Partway through checking the copy, it stopped answering reads: folder listings still worked, but reading a file just hung. It froze on two different USB hubs, so the drive itself goes on the list to look at. The card's files went to the Mac's internal drive instead, and every one was checked against the card before anything else happened to it: 4,192 files, none missing, none different.

7Writing the image

Nura publishes ready-made images for this family of Chromebooks, Google's "corsola" boards, at images.nura.eco/bpo/: pick the release (v26.06), google-corsola, a desktop, and the newest dated build. I took the GNOME build, 670 MB, because a full desktop is the quickest way to see what works: screen, keyboard, touchpad, Wi-Fi. Each image comes with a checksum file, and the download matched it.

Then the card, on the Mac: find its disk number, unmount it, and write the image straight onto the raw card. xz came from Homebrew.

$ shasum -a 256 -c 20261016-0243-postmarketOS-v26.06-gnome-4-google-corsola.img.xz.sha256
20261016-0243-postmarketOS-v26.06-gnome-4-google-corsola.img.xz: OK
$ diskutil list external physical
/dev/disk4 (external, physical):
   0:     FDisk_partition_scheme                        *32.0 GB    disk4
$ diskutil unmountDisk /dev/disk4
$ xz -dc 20261016-0243-postmarketOS-v26.06-gnome-4-google-corsola.img.xz | sudo dd of=/dev/rdisk4 bs=4m && sync && diskutil eject disk4

The one thing to get right is /dev/rdisk4: dd writes to whatever disk you name, so it's worth checking twice that it's the card and not something that matters. It prompted for my Mac password, printed nothing for a few minutes, and ejected the card.

8It boots

Card in the Chromebook's slot, reboot in the console, and "Boot from external disk" at the developer screen (Ctrl + U does the same). A green triangle and "Loading," then "Starting," then a GNOME login screen with one user, named user, and the image's default password, 147147. Then a full Linux desktop: Firefox in the dock, the keyboard and touchpad working, the screen at its full resolution. The welcome card still says postmarketOS. The rename hasn't reached everything yet.

A GNOME desktop with a welcome card reading Welcome to postmarketOS v26.06, a hot air balloon illustration, and a dock with Firefox, Calendar, Files, Software, Text Editor and Calculator
First boot from the card, photographed and straightened.

I skipped the tour and joined Wi-Fi from the quick settings in the top-right corner. The battery read 91%, on the Raspberry Pi's 5 V power supply; the label asks for 15 V, but USB-C chargers agree on a voltage before sending one, so a weaker charger only charges slowly. (I left the charger from a mini PC in the drawer: some of those put a fixed voltage on USB-C without negotiating.) The clock said 3:24 PM on the wrong day, and once online it jumped to the right day, still four hours ahead: the image runs on GMT.

9Letting the Mac in

From here I wanted to work from the Mac, which means SSH. In the Console app, first the Chromebook's address on the network:

~ $ ip -4 addr | grep inet
    inet 127.0.0.1/8 scope host lo
    inet 192.168.1.201/24 brd 192.168.1.255 scope global dynamic noprefixroute wlan0

The image doesn't come with an SSH server, so I installed one and started it (the sudo password is the same 147147):

~ $ sudo apk add openssh-server
~ $ sudo systemctl enable --now sshd

Then my Mac's public key, so it can log in without a password. The Mac served a folder holding only that key with python3 -m http.server 8656, and the Chromebook fetched it. The image has no curl, which cost one try, but BusyBox's wget does the same thing:

~ $ mkdir -p ~/.ssh && wget -qO- http://192.168.1.196:8656/key.pub >> ~/.ssh/authorized_keys

The Mac stopped serving the key as soon as it was in. That was the last thing I typed on the Chromebook.

10Finishing from the Mac

Everything after that went over SSH. The Chromebook's shell is BusyBox ash, which is stricter than the Mac's, so commands stay plain sh.

$ ssh user@192.168.1.201
~ $ sudo timedatectl set-timezone America/New_York
~ $ date
Sun Oct 18 11:37:43 EDT 2026

Instead of a password I'd have to remember, the LLM coding tool generated a one-line shell command that drew 24 random letters and digits from the Mac's /dev/urandom and saved them straight into the new project's .env file (which never goes into git), so the password never went into the model's input. A second command set it on the Chromebook with chpasswd, passing it over SSH's input so it never appeared on a command line. The default 147147 stopped working.

Then a name. There may be more kiosks, so this one is kiosk- plus four random letters and digits: kiosk-q0m7. Setting the hostname wasn't quite enough; the service that answers .local names had to restart to pick it up.

~ $ sudo hostnamectl set-hostname kiosk-q0m7
~ $ sudo systemctl restart avahi-daemon
$ ssh user@kiosk-q0m7.local hostname
kiosk-q0m7

For the record: 3.8 GB of memory with 2.7 GB free with the desktop running, 2.1 GB used of the card's 28 GB, and Linux 6.18, the mainline kernel.

11Where my hands stop

Until SSH answered, everything on the Chromebook happened with my fingers on its keyboard, because nothing else could reach it. Looking back, the manual part is a short, specific list:

Everything else I did from an LLM coding tool in a terminal on the Mac, giving the instructions myself. With it I looked up the board's name, searched the kernel patches, checked the image server after the rename, backed up the card, and generated the commands I typed; once SSH answered, the commands ran over SSH. From here on I run the whole project by voice: I say what I want, and the tool runs the commands on the Mac and on the kiosk.

That held for about an hour. The first reboot test sent me back to the keyboard: the developer screen's timer booted ChromeOS instead of the card, and the fix, one crossystem setting, can only be made from ChromeOS's own console. So the manual list grew by one: Ctrl + Alt + Refresh, root, crossystem dev_default_boot=usb, reboot. It's the same kind of step as the rest of the list, keystrokes on a screen nothing else can reach, which is the case for the USB keyboard idea below.

The manual list is short enough to automate too. Every item on it is keystrokes, plus one card swap. A small board that plugs in over USB and presents itself as a keyboard (a Raspberry Pi Pico or a Pi Zero can do this) could type them, driven from the Mac. That's for a future kiosk: the second one should need my hands for even less.

A chevron crocheted blanket in brown, coral, mustard and cream piled in a lump on a chrome chair, with a dog sleeping underneath
Meanwhile, Freddie got cold, scratched at my leg until I got her a blanket, and is now the lump in the chair next to me, asleep.

12Booting straight to a page

The kiosk's pages will be Next.js, and they have to work offline: the device serves its own page, fetches live data when it can, and falls back to the last copy it saved when it can't. So the first page is built on the Mac into plain files and copied to the Chromebook, where BusyBox's small web server serves it on localhost. No Mac, no internet needed to show it.

For the display, the GNOME desktop is switched off at boot. In its place, cage, a display server that runs exactly one app full screen, runs Firefox in kiosk mode, with a profile that skips every first-run screen and prompt. Both are ordinary system services, so they start at boot and restart if they fall over. All of it lives in the project, and one script on the Mac drives it: setup installs it on the device, deploy builds the page and copies it over, and dev points the kiosk at the Mac's live development server, so an edit shows up on the counter as I save it. desktop brings GNOME back.

The first reboot came up in ChromeOS: the developer screen's timer boots the internal disk unless set otherwise, and it beeps at you while it waits. The setting lives in the firmware and has to be changed from ChromeOS's console, so one more trip there:

magneton-rev3 ~ # crossystem dev_default_boot=usb
magneton-rev3 ~ # crossystem dev_default_boot
usb
magneton-rev3 ~ # reboot

Now the developer screen highlights "Boot from external disk," and when its timer runs out, the Chromebook boots the card and lands on the page by itself, about half a minute after power-on. The page is a success screen, with a little game-style juice: letters that pop in and overshoot, a glow that pulses, fireworks that shake the screen and punch the title when they burst. This is it, running the same code as the kiosk:

The first page the kiosk boots into. On the Chromebook it's choppy for now.

13Next

Part 2: making it smooth.