Installing Kubuntu 24.04 on a Chuwi MiniBook X N100 — entirely over SSH from a MacBook, with zero physical interaction beyond opening the lid and fixing the firewall.
Built through agentic coding with Claude Code. The AI operated the MiniBook X remotely via SSH to Windows, used a homelab Linux node to build the root filesystem, streamed a compressed disk image over HTTP, wrote it raw to an NVMe partition from Python on Windows, and booted into a working Kubuntu desktop — all without touching the machine.
For prior art on Linux compatibility for this device, see sonnyp/linux-minibook-x.
The Chuwi MiniBook X N100 ships with Windows 11 IoT Enterprise LTSC. The goal: dual-boot Kubuntu 24.04 LTS alongside Windows, with full remote SSH access to both OSes, and zero reliance on physical interaction after initial setup.
The constraints:
- No USB boot — the BIOS refused to boot from a USB SATA SSD
- No Ethernet — only WiFi, and the USB-Ethernet dongle was missing
- No IPKVM — no HDMI port, no USB-HDMI adapter available
- No WSL — Windows 11 IoT Enterprise LTSC has no Microsoft Store;
wsl --installfails even after enabling the features - Only SSH to Windows — that's all we had to work with
The question: how do you install Linux on a machine when your only interface is SSH to the existing Windows install?
| Spec | Detail |
|---|---|
| CPU | Intel N100 (4C/4T, 3.4GHz turbo) |
| RAM | 12GB LPDDR5 |
| Storage | 512GB NVMe (KPART512GBC2DVT) |
| Display | 10.51" DSI, 1200x1920 native (portrait), 50Hz |
| WiFi | AX (2.4GHz + 5GHz) |
| OS (original) | Windows 11 IoT Enterprise LTSC 2024 |
| Form factor | 360° YOGA 2-in-1, 920g |
The key insight: never leave Windows SSH. Build the entire Linux root filesystem on a remote machine, then write it directly to the disk from Windows.
┌──────────┐ SSH ┌──────────────┐ HTTP ┌──────────┐
│ MacBook │──────→│ MiniBook X │←────────│ .68 node │
│ (control) │ │ (Windows) │ gzip'd │ (deboot- │
│ │ │ Python→disk │ image │ strap) │
└──────────┘ └──────────────┘ └──────────┘
The first challenge was just getting SSH working. Windows had OpenSSH server installed but the firewall was blocking port 22, and the sshd service wasn't set to auto-start. After the user opened the firewall manually from the console:
# From SSH, immediately persist access
Set-Service -Name sshd -StartupType Automatic
New-NetFirewallRule -Name "OpenSSH-Server-Inbound" -DisplayName "OpenSSH Server (sshd) Inbound" `
-Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
# Set PowerShell as default SSH shell
New-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShell `
-Value 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe' -PropertyType String -ForceThis was the single most important step. Everything after this was fully remote.
From Windows SSH, used diskpart to carve 128GB from the unallocated space (user had pre-shrunk the Windows partition):
Disk 0 (512GB NVMe):
Partition 1: 100MB EFI System Partition (FAT32), UUID 24BF-0BF8
Partition 2: 100MB Microsoft Reserved
Partition 3: 347GB Windows C: (NTFS)
Partition 4: 127GB ← NEW: Kubuntu root (ext4), UUID d26b99a6-fa4b-473d-a5ee-051905693236
Partition 5: 1.9GB Windows Recovery
Windows can't format ext4, and WSL wouldn't install on IoT LTSC. So the root filesystem was built on .68 — a homelab Ubuntu node with 356GB free:
# On .68
sudo debootstrap --arch=amd64 noble /tmp/kubuntu-rootfs http://archive.ubuntu.com/ubuntuThen chrooted in to install everything needed for a bootable system with remote access:
sudo chroot /tmp/kubuntu-rootfs
apt install linux-image-generic linux-firmware openssh-server \
network-manager grub-efi-amd64-bin grub-efi-amd64-signed shim-signed sudoPre-configured before first boot:
- WiFi: NetworkManager
.nmconnectionfiles for both 2.4GHz (priority) and 5GHz (fallback) - SSH:
systemctl enable ssh, password auth on - User:
evnchnwith sudo NOPASSWD - GRUB:
video=DSI-1:panel_orientation=right_side_upkernel parameter - Hostname, timezone (Asia/Hong_Kong), locale (en_US.UTF-8)
The rootfs was written into a 127GB ext4 disk image on .68:
truncate -s 136365211648 /tmp/kubuntu.img # sparse, instant
mkfs.ext4 -L kubuntu /tmp/kubuntu.img
mount -o loop /tmp/kubuntu.img /tmp/kubuntu-mnt
cd /tmp/kubuntu-mnt && tar xzf /tmp/kubuntu-rootfs.tar.gzCompressed with parallel gzip (2.4GB compressed from 127GB sparse — mostly zeros):
pigz -1 -c /tmp/kubuntu.img > /tmp/kubuntu.img.gz # ~2 minutesServed via HTTP:
python3 -m http.server 9999 --directory /tmpOn the MiniBook X, a Python script downloaded and wrote the raw image directly to the partition:
# write_partition.py — downloads gzipped image, writes raw to physical disk
import gzip, urllib.request
disk = open(r"\\.\PhysicalDrive0", "r+b", buffering=0)
disk.seek(372667056128) # partition 4 offset
resp = urllib.request.urlopen("http://192.168.50.68:9999/kubuntu.img.gz")
decompressor = gzip.GzipFile(fileobj=resp)
while chunk := decompressor.read(4*1024*1024):
disk.write(chunk)127GB written over WiFi in ~20 minutes.
The EFI System Partition was mounted on Windows (mountvol S: /S) and GRUB files were downloaded from .68:
S:\EFI\ubuntu\shimx64.efi — Secure Boot shim
S:\EFI\ubuntu\grubx64.efi — GRUB EFI binary
S:\EFI\ubuntu\grub.cfg — Points GRUB to /boot/grub on the ext4 partition
A UEFI boot entry was created via bcdedit:
bcdedit /copy "{bootmgr}" /d "Ubuntu"
bcdedit /set {new-id} path \EFI\ubuntu\shimx64.efiThe fallback bootloader (\EFI\Boot\bootx64.efi) was also replaced with the GRUB shim as a safety net — some UEFI firmware ignores the boot order and always loads the fallback path.
bcdedit /set "{fwbootmgr}" bootsequence "{ubuntu-id}"
Restart-Computer -Force30 seconds later:
$ ssh evnchn@192.168.50.164
minibook-x
Linux minibook-x 6.8.0-106-generic #106-Ubuntu SMP x86_64
WiFi connected automatically. SSH was waiting. Zero physical interaction.
With SSH confirmed, the desktop was installed:
sudo apt install kubuntu-desktop # ~30 minutes on WiFi
sudo systemctl enable sddm
sudo systemctl set-default graphical.targetEight fixes were needed to go from "it boots" to "it works." This is the trial install — all fixes documented here for replay on a future fresh Kubuntu install.
Problem: The DSI panel is natively portrait (1200x1920). Boots sideways in the landscape-oriented chassis.
The kernel parameter video=DSI-1:panel_orientation=right_side_up is set but not sufficient for X11 — it helps the kernel console but X/KDE need their own rotation.
Files changed:
/etc/X11/xorg.conf.d/10-monitor.conf:
Section "Monitor"
Identifier "DSI-1"
Option "Rotate" "right"
EndSection
/etc/sddm.conf.d/rotate.conf:
[X11]
DisplayCommand=/etc/sddm/XsetupThe SDDM Xsetup script is shared with Fix 7 (touchscreen) — see below.
Problem: Stock Kubuntu doesn't run powertop auto-tune by default.
Measurements (idle, KDE desktop, WiFi, 50% backlight):
| Metric | Value |
|---|---|
| CPU package (RAPL) | 0.61W |
| Total system (battery) | 4.24W |
| Estimated battery life | ~6.3hr on 26.6Wh |
Governor is already powersave. KDE power profile is power-saver (EPP=power). Switching profiles changes CPU power (0.5W → 3W) but not total system draw at idle.
Files changed:
/etc/systemd/system/powertop-autotune.service:
[Unit]
Description=PowerTOP auto-tune
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/powertop --auto-tune
RemainAfterExit=true
[Install]
WantedBy=multi-user.targetsudo apt install powertop
sudo systemctl enable powertop-autotuneMarginal gains — most power is display/WiFi/platform, not tuneable software knobs.
Problem: UEFI firmware always boots Windows. The one-shot bootsequence was consumed after first boot, and subsequent reboots went straight to Windows.
Fix (two layers):
- Made GRUB first in UEFI boot order:
bcdedit /set "{fwbootmgr}" displayorder "{ubuntu-id}" "{bootmgr}"- Replaced the UEFI fallback bootloader with GRUB's shim:
Copy-Item S:\EFI\Boot\bootx64.efi S:\EFI\Boot\bootx64.efi.bak # backup
Copy-Item S:\EFI\ubuntu\shimx64.efi S:\EFI\Boot\bootx64.efi
Copy-Item S:\EFI\ubuntu\grubx64.efi S:\EFI\Boot\grubx64.efi
Copy-Item S:\EFI\ubuntu\grub.cfg S:\EFI\Boot\grub.cfgTo restore Windows-only boot: Copy-Item S:\EFI\Boot\bootx64.efi.bak S:\EFI\Boot\bootx64.efi
Problem: Debootstrap created a single combined sources stanza. Stock Kubuntu uses a separate security.ubuntu.com stanza and includes noble-backports.
Fix: Replaced /etc/apt/sources.list.d/ubuntu.sources to match stock Kubuntu Calamares output. Emptied /etc/apt/sources.list (debootstrap leftover).
/etc/apt/sources.list.d/ubuntu.sources:
Types: deb
URIs: http://archive.ubuntu.com/ubuntu
Suites: noble noble-updates noble-backports
Components: main universe restricted multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
Types: deb
URIs: http://security.ubuntu.com/ubuntu/
Suites: noble-security
Components: main universe restricted multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
Problem: Debootstrap installed the GA kernel (6.8.0). Stock Kubuntu 24.04.4 ships with the HWE kernel (6.17.x).
sudo apt install linux-generic-hwe-24.04Both kernels kept. GRUB menu:
- Kubuntu 24.04 (HWE 6.17) — default
- Kubuntu 24.04 (GA 6.8 fallback)
- Kubuntu 24.04 (HWE 6.17 recovery)
- Windows Boot Manager
Problem: KDE daemon reported incomplete language support.
sudo apt install language-pack-en language-pack-kde-en language-pack-gnome-enProblem: The SDDM greeter (first-boot login screen) has no touch coordinate rotation. KDE desktop handles its own touch mapping after login, so a global fix (e.g., udev rule) causes double-rotation in the desktop.
Key insight: The fix must apply ONLY during the SDDM greeter phase. KDE takes over touch mapping once the desktop session starts.
Gotcha: xinput list --id-only "device name" silently fails in the SDDM context. Must grep the xinput list output to extract the device ID.
File changed — /etc/sddm/Xsetup (shared with Fix 1):
#!/bin/sh
xrandr --output DSI-1 --rotate right
# Rotate touchscreen - only for SDDM greeter, KDE handles its own mapping
TOUCHID=$(xinput list | grep "Goodix Capacitive TouchScreen" | grep pointer | sed "s/.*id=\([0-9]*\).*/\1/")
if [ -n "$TOUCHID" ]; then
xinput set-prop "$TOUCHID" "Coordinate Transformation Matrix" 0 1 0 -1 0 1 0 0 1
logger "SDDM Xsetup: applied touch rotation to device $TOUCHID"
else
logger "SDDM Xsetup: Goodix touch device not found"
fiMatrix 0 1 0 -1 0 1 = 90° clockwise (matching xrandr --rotate right).
Problem: Selecting "Windows Boot Manager" from GRUB failed — the chainloader looked for bootmgfw.efi on the Kubuntu partition instead of the ESP.
Root cause: Missing search command to set the GRUB root to the EFI System Partition before chainloading.
Fix in /boot/grub/grub.cfg:
menuentry "Windows Boot Manager" {
search --no-floppy --fs-uuid --set=root 24BF-0BF8
chainloader /EFI/Microsoft/Boot/bootmgfw.efi
}
set default=0
set timeout=5
menuentry "Kubuntu 24.04 (HWE 6.17)" {
search --no-floppy --fs-uuid --set=root d26b99a6-fa4b-473d-a5ee-051905693236
linux /boot/vmlinuz-6.17.0-20-generic root=UUID=d26b99a6-fa4b-473d-a5ee-051905693236 ro quiet splash video=DSI-1:panel_orientation=right_side_up
initrd /boot/initrd.img-6.17.0-20-generic
}
menuentry "Kubuntu 24.04 (GA 6.8 fallback)" {
search --no-floppy --fs-uuid --set=root d26b99a6-fa4b-473d-a5ee-051905693236
linux /boot/vmlinuz-6.8.0-106-generic root=UUID=d26b99a6-fa4b-473d-a5ee-051905693236 ro quiet splash video=DSI-1:panel_orientation=right_side_up
initrd /boot/initrd.img-6.8.0-106-generic
}
menuentry "Kubuntu 24.04 (HWE 6.17 recovery)" {
search --no-floppy --fs-uuid --set=root d26b99a6-fa4b-473d-a5ee-051905693236
linux /boot/vmlinuz-6.17.0-20-generic root=UUID=d26b99a6-fa4b-473d-a5ee-051905693236 ro single
initrd /boot/initrd.img-6.17.0-20-generic
}
menuentry "Windows Boot Manager" {
search --no-floppy --fs-uuid --set=root 24BF-0BF8
chainloader /EFI/Microsoft/Boot/bootmgfw.efi
}
| Issue | Detail | Fixable? |
|---|---|---|
| GRUB menu sideways | DSI panel in native portrait; GRUB uses EFI GOP framebuffer with no rotation support. This is also the reason behind the BIOS logo being drawn twice during POST — the firmware first displays a pre-rotated logo bitmap via the display controller, then switches to the raw GOP framebuffer and redraws line-by-line in native portrait orientation. | No |
| Refresh rate 50Hz | 50Hz is the panel default and also the Windows default. Not a Linux regression. | Unknown — DSI panel timing investigation needed |
| Tablet mode switch | Physical switch exists but kernel doesn't support PNP0C60/INT33D3 | Possible — needs kernel patches |
SSH was the lifeline. The entire install — partitioning, filesystem creation, image transfer, bootloader setup, desktop install, every fix — was done over SSH. The safety model: Windows is always the default boot. If Linux fails, the next reboot returns to Windows and SSH is waiting.
The homelab was the workshop. Debootstrap needs a Linux environment. With WSL unavailable on IoT LTSC, the homelab node .68 served as the build machine. The root filesystem was built there, compressed, and streamed over HTTP to the MiniBook X. The 2.4GB compressed image transferred over WiFi in minutes.
Python on Windows was the bridge. Windows can't write ext4. But Python can open \\.\PhysicalDrive0 for raw I/O. A 15-line Python script replaced the need for dd, WSL, or any Linux tools on the Windows side.
| Component | Status |
|---|---|
| Display (DSI) | Works (needs rotation config) |
| Touchscreen (Goodix) | Works (needs calibration matrix for SDDM) |
| WiFi | Works |
| Bluetooth | Works |
| Audio | Works |
| Keyboard + backlight | Works |
| Touchpad | Works |
| USB-C | Works |
| Accelerometer | Reads (auto-rotation needs tablet switch) |
| Tablet mode switch | Not supported |
| 60Hz+ refresh | Stuck at 50Hz |