# Xubuntu Setup — Asus T101HA (`ears-tablet`)

Working notes for setting up an Asus T101HA (Atom x5-Z8350, 2 GB RAM / ~1.8 GB
usable, eMMC) as a Mono + GTK#3 development and runtime machine.

User: `ears` · Hostname: `ears-tablet`

---

## Contents

- [0. Recommended baseline](#0-recommended-baseline)
- [1. Installing Xubuntu](#1-installing-xubuntu)
  - [1.1 Check firmware bitness first](#11-check-firmware-bitness-first--this-determines-everything)
  - [1.2 Build the USB](#12-build-the-usb)
  - [1.3 BIOS](#13-bios)
  - [1.4 Install](#14-install)
  - [1.5 Post-install GRUB fix (IA32 only)](#15-post-install-grub-fix-ia32-machines-only)
  - [1.6 Expect to fix afterwards](#16-expect-to-fix-afterwards)
- [2. First boot / recovery mode](#2-first-boot--recovery-mode)
- [3. Low-RAM tuning](#3-low-ram-tuning)
  - [Option A — `zram-tools`](#option-a--zram-tools-simple)
  - [Option B — `systemd-zram-generator`](#option-b--systemd-zram-generator-current-preferred-on-2404)
  - [Swappiness](#swappiness)
- [4. apt: repair and sources](#4-apt-repair-and-sources)
  - [4.1 The modern layout](#41-the-modern-layout)
  - [4.2 Correct stock sources](#42-correct-stock-sources-2510-example--substitute-your-codename)
  - [4.3 Which host? archive vs old-releases](#43-which-host-archive-vs-old-releases)
  - [4.4 Check for genuinely broken packages](#44-check-for-genuinely-broken-packages)
  - [4.5 Don't do these](#45-dont-do-these)
  - [4.6 `full-upgrade` checklist](#46-full-upgrade-checklist)
  - [4.7 Reboot needed?](#47-reboot-needed)
- [5. Mono + GTK#3](#5-mono--gtk3)
  - [Install](#install)
  - [Optional](#optional)
  - [Verify](#verify)
  - [Smoke test](#smoke-test)
  - [Notes vs. the WSL guide](#notes-vs-the-wsl-guide)
- [6. Display rotation + touchscreen](#6-display-rotation--touchscreen)
  - [Identify devices](#identify-devices)
  - [Matrices](#matrices)
  - [Persistence script](#persistence-script)
  - [Login screen (LightDM)](#login-screen-lightdm--separate-from-your-session)
  - [Better: rotate before the session starts](#better-rotate-before-the-session-starts)
  - [Wallpaper breaks after rotating](#wallpaper-breaks-after-rotating)
  - [Auto-rotation (optional)](#auto-rotation-optional)
- [7. Audio](#7-audio)
  - [Install and test](#install-and-test)
  - [Choosing the right card](#choosing-the-right-card)
  - [Renumbering at the kernel level](#renumbering-at-the-kernel-level)
  - [Caveats](#caveats)
- [8. Running a script at boot](#8-running-a-script-at-boot)
- [9. File transfer to/from Windows](#9-file-transfer-tofrom-windows)
- [10. Quick reference](#10-quick-reference)

---

## 0. Recommended baseline

**Target: Xubuntu 24.04 LTS (Minimal ISO).**

Reasons, from the apt/Mono investigation:
- The machine was found running **Ubuntu 25.10 "questing"**, which hit end of
  life on **9 July 2026**. No further updates will ever be published for it.
- `gtk-sharp3` is **not packaged on 25.10**, and upgrading to 26.04 would not
  bring it back.
- `gtk-sharp3` **is** packaged on 24.04 (noble), supported to 2029.
- Xfce on 24.04 idles around 500–600 MB, which matters at 1.8 GB RAM.

Mono + `mcs` + GTK# is the correct toolchain for this hardware — the .NET SDK
(~800 MB) and Avalonia are far heavier.

Before reinstalling, back up: `*.cs`, `*.glade`, SSH keys, browser profile.

```bash
lsblk            # is /home a separate partition?
df -h /
free -h
mkdir -p ~/backup && cp ~/*.cs ~/*.glade ~/backup/
```

If `/home` is its own partition, it can be preserved during install (assign
`/home` **without** format). Use the same username to keep permissions clean.

---

## 1. Installing Xubuntu

### 1.1 Check firmware bitness first — this determines everything

In Windows: **Settings → System → About → System type**

- *32-bit operating system, x64-based processor* → **IA32 UEFI**, needs the
  bootia32 workaround below.
- *64-bit* → skip the GRUB steps.

### 1.2 Build the USB

Ventoy includes IA32 UEFI support, so try it first. If it won't boot, fall back
to Rufus and copy a 32-bit `bootia32.efi` (GRUB) into `/EFI/BOOT` on the stick.

From Linux, writing directly:

```bash
sudo dd if=xubuntu-24.04-minimal-amd64.iso of=/dev/sdX bs=4M status=progress conv=fsync
```

> Verify the target with `lsblk` first. `dd` to the wrong disk destroys it
> silently.

Verify the ISO: `sha256sum` against the checksum on the download page.

### 1.3 BIOS

- F2 at power-on
- **Disable Secure Boot** (the ia32 GRUB is unsigned)
- Boot menu is usually F12 / Esc

### 1.4 Install

Choose **Install**, not *Try* — the live session eats RAM you don't have.
Don't open Firefox or anything else during install. Let it create swap.

### 1.5 Post-install GRUB fix (IA32 machines only)

The installer writes a 64-bit `grubx64.efi` the firmware can't read. Boot the
live session, chroot in, and:

```bash
grub-install --target=i386-efi --efi-directory=/boot/efi
```

This is **not optional** on IA32 firmware.

### 1.6 Expect to fix afterwards

| Component | Notes |
|---|---|
| Wi-Fi | RTL8723BS SDIO — finicky |
| Audio | ESS ES8316 codec, needs UCM configs |
| Touchscreen | Works, but rotation needs manual matrix (§6) |
| Auto-rotation | Needs `iio-sensor-proxy` + custom handler |

A recent 6.x kernel fixes most of these.

---

## 2. First boot / recovery mode

If the user account lacks admin rights:

```bash
usermod -aG sudo ears
groups ears                       # confirm 'sudo' appears
hostnamectl set-hostname ears-tablet
localectl set-locale LANG=en_US.UTF-8   # optional, cosmetic
exit                              # then 'resume'
```

`usermod -aG sudo` is the one that isn't optional — without it you can log in
but can't run any admin command.

---

## 3. Low-RAM tuning

### Option A — `zram-tools` (simple)

```bash
sudo apt install zram-tools
```

Edit `/etc/default/zramswap`:
```
PERCENT=60
ALGO=zstd
```

### Option B — `systemd-zram-generator` (current, preferred on 24.04+)

```bash
sudo apt install systemd-zram-generator

sudo tee /etc/systemd/zram-generator.conf > /dev/null <<'EOF'
[zram0]
zram-size = min(ram / 2, 4096)
compression-algorithm = zstd
swap-priority = 100
EOF

sudo systemctl daemon-reload
sudo systemctl start systemd-zram-setup@zram0.service
swapon --show     # expect /dev/zram0
zramctl
```

The package does nothing without a config file containing at least one section.

### Swappiness

Swapping to zram is cheap, so raise it so the kernel actually uses it:

```bash
echo 'vm.swappiness=100' | sudo tee /etc/sysctl.d/99-zram.conf
```

Reboot. Verdict: worth it at 2–4 GB, marginal at 8 GB, skip above 16 GB.
zram compresses **RAM** — it does nothing for low disk space.

---

## 4. apt: repair and sources

### 4.1 The modern layout

Since 24.04, archive config lives in **`/etc/apt/sources.list.d/ubuntu.sources`**
(deb822 format). `/etc/apt/sources.list` being empty is normal, not a symptom.
Most apt advice online predates this and edits a file that does nothing.

### 4.2 Correct stock sources (25.10 example — substitute your codename)

```bash
sudo tee /etc/apt/sources.list.d/ubuntu.sources > /dev/null <<'EOF'
Types: deb
URIs: http://archive.ubuntu.com/ubuntu/
Suites: questing questing-updates questing-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg

Types: deb
URIs: http://security.ubuntu.com/ubuntu/
Suites: questing-security
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
EOF

sudo apt update
```

Do not drop the `Signed-By:` line — without it apt falls back to the legacy
trusted-keyring and you get confusing "not signed" errors later.

`ubuntu.sources.curtin.orig` is the installer's pristine backup. apt ignores
`.orig` files. Useful as a reference, don't copy blindly.

### 4.3 Which host? archive vs old-releases

An EOL release only moves to `old-releases.ubuntu.com` when the migration
actually runs — which can lag the EOL date by weeks. Pointing there too early
gives four clean 404s. Test before switching:

```bash
for p in questing questing-updates questing-backports questing-security; do
  echo -n "$p: "
  curl -sI "http://old-releases.ubuntu.com/ubuntu/dists/$p/Release" | head -1
done

curl -sI http://archive.ubuntu.com/ubuntu/dists/questing/Release | head -1
```

`200` = pocket exists. `404` = drop it or use the other host.

### 4.4 Check for genuinely broken packages

All read-only, cheapest first:

```bash
sudo dpkg --audit          # clean = no output at all
sudo apt-get check         # verifies the dependency tree
dpkg -l | grep -v '^ii' | grep -v '^rc'
apt-mark showhold          # held packages are silently skipped on upgrade
```

dpkg status codes (desired state, then actual):

| Code | Meaning |
|---|---|
| `ii` | Healthy |
| `rc` | Removed, config left — harmless |
| `iU` | Unpacked, never configured |
| `iF` | Half-configured |
| `iH` | Half-installed |
| `iW` / `it` | Waiting on / pending triggers |
| `*R*` | Reinstall required → `apt install --reinstall <name>` |

Then repair:

```bash
sudo dpkg --configure -a
sudo apt --fix-broken install
sudo apt full-upgrade
```

### 4.5 Don't do these

```bash
# WRONG — killall receives 'sudo', 'rm', paths etc. as process names
sudo killall apt apt-get dpkg 2>/dev/null sudo rm /var/lib/apt/lists/lock ...

# DANGEROUS — rm -rf also targets files named sudo, apt, clean, update in $PWD
sudo rm -rf /var/lib/apt/lists/* sudo apt clean sudo apt update
```

These must be separate lines. Also: killing `dpkg` mid-transaction is what
*creates* the broken state. Check first with `ps aux | grep -E 'apt|dpkg'`.

Don't remove `universe` — it's an Ubuntu component, not a PPA, and
`gtk-sharp3` lives there. And never run
`add-apt-repository --remove ppa:repository-name/ppa` literally; that's a
placeholder from a copied guide.

### 4.6 `full-upgrade` checklist

- Use `full-upgrade`, not `upgrade` — it permits removals where deps changed
- Read the summary line; mass removals of desktop/kernel packages = stop
- Plug the tablet in; power loss mid-`dpkg` recreates the broken state
- On config prompts, keeping the local version (the default) is safe unless
  you know you edited it
- Then `sudo apt autoremove` — but answer `n` if anything with `efi` appears
- Reboot before `do-release-upgrade` so the newest kernel is running

### 4.7 Reboot needed?

```bash
[ -f /var/run/reboot-required ] && cat /var/run/reboot-required.pkgs
sudo needrestart     # interactive; shows what's running old libs
```

Kernel / initramfs / systemd / glibc → reboot. Everything else → restart the
service.

---

## 5. Mono + GTK#3

### Install

```bash
sudo add-apt-repository universe
sudo apt update
sudo apt install mono-complete gtk-sharp3
```

| Package | Contents | Size |
|---|---|---|
| `mono-runtime` | JIT + `mscorlib` only | ~15 MB |
| `mono-devel` | Compiler + the BCL you need | ~200–400 MB |
| `mono-complete` | Runtime + `mcs` + entire class library + F#/VB | ~300–500 MB |

**Avoid `mono-runtime`.** The BCL is split across dozens of
`libmono-system-*-cil` packages; hello-world runs, but anything touching LINQ,
XML, HTTP, or `System.IO.Ports` (the COM-port combo box in `homepage.glade`)
throws `Could not load file or assembly`. On a tight disk, `mono-devel` saves
~100 MB over `mono-complete` and still covers everything this project needs.

### Optional

```bash
sudo apt install glade         # visual designer — skip on this machine
sudo apt install libgdiplus    # only if using System.Drawing
sudo apt install libcanberra-gtk3-module   # silences a harmless console warning
```

### Verify

```bash
mono --version
mcs --version
pkg-config --list-all | grep gtk-sharp     # want gtk-sharp-3.0
apt policy gtk-sharp3
```

### Smoke test

```bash
cat > gtkcheck.cs <<'EOF'
using System;
using Gtk;

class GtkCheck {
    static void Main() {
        Application.Init();
        var win = new Window("Hello GTK#3");
        win.SetDefaultSize(300, 200);
        win.DeleteEvent += (o, args) => Application.Quit();

        var button = new Button("Click me");
        button.Clicked += (o, args) => Console.WriteLine("Clicked!");
        win.Add(button);

        win.ShowAll();
        Application.Run();
    }
}
EOF

mcs -pkg:gtk-sharp-3.0 gtkcheck.cs
mono gtkcheck.exe
```

Do the throwaway test first — it isolates "is gtk-sharp3 working" from "does my
app compile."

Then the real thing:

```bash
mcs -pkg:gtk-sharp-3.0 program.cs MainWindow.cs ArduinoConnection.cs -out:test.exe
mono test.exe
```

(`ArduinoConnection.cs` is needed because `MainWindow` calls
`_arduino.Disconnect()`; omitting it gives `CS0246`.)

### Notes vs. the WSL guide

- No WSLg, no `$DISPLAY` fiddling — Xfce is itself GTK3, so the app picks up the
  system theme and the window just appears.
- Only exception: over SSH you need `ssh -X`.
- Companion files (`-r:` DLLs, `homepage.glade`) must sit next to the `.exe`, or
  you get `FileNotFoundException`.
- `homepage.glade` targets GTK 3.24, which 22.04 and 24.04 both ship.
- `gtk-sharp2` is gone from 24.04 onward — use `gtk-sharp3`.

---

## 6. Display rotation + touchscreen

X11 does **not** rotate touch input when you rotate the display. The touch
device needs its own coordinate transformation matrix.

### Identify devices

```bash
xrandr                          # output name — DSI-1, or None-1 on this unit
xinput list --name-only         # look for Silead / GSL / SIS, NOT 'Touchpad'
```

> On this tablet the output reported as **`None-1`** (xrandr's fallback when the
> connector type isn't identified — it can change between kernels) and the
> touchscreen as **`SIS0457:00 0457:11ED`**.

### Matrices

| Rotation | Matrix |
|---|---|
| normal | `1 0 0 0 1 0 0 0 1` |
| right (90° CW) | `0 1 0 -1 0 1 0 0 1` |
| left (90° CCW) | `0 -1 1 1 0 0 0 0 1` |
| inverted (180°) | `-1 0 1 0 -1 1 0 0 1` |

```bash
xrandr --output None-1 --rotate right
xinput set-prop "SIS0457:00 0457:11ED" "Coordinate Transformation Matrix" 0 1 0 -1 0 1 0 0 1
```

### Persistence script

```bash
nano ~/rotate.sh
```

```bash
#!/bin/bash
sleep 3
OUT=$(xrandr | awk '/ connected/{print $1; exit}')
xrandr --output "$OUT" --rotate right
TS=$(xinput list --name-only | grep -iE 'silead|gsl|touchscreen|SIS' | grep -vi 'touchpad' | head -1)
[ -n "$TS" ] && xinput set-prop "$TS" "Coordinate Transformation Matrix" 0 1 0 -1 0 1 0 0 1
xset s off
xset -dpms
```

```bash
chmod +x ~/rotate.sh
~/rotate.sh          # test before automating
```

Then **Settings → Session and Startup → Application Autostart → Add**, command
`/home/ears/rotate.sh`.

> **Gotcha:** grepping bare `touch` matches the **trackpad** and rotates the
> pointer instead. Use `touchscreen` and exclude `touchpad`. To undo:
> ```bash
> TP=$(xinput list --name-only | grep -i -m1 touchpad)
> xinput set-prop "$TP" "Coordinate Transformation Matrix" 1 0 0 0 1 0 0 0 1
> ```
> Or just log out — the matrix isn't saved anywhere.

The `sleep 3` matters; the touchscreen isn't registered the instant the session
starts. Raise to 5 if touch still comes out wrong.

**Try Settings → Display first.** Its rotation dropdown often persists on its
own, leaving the script to handle only the `xinput` part.

### Login screen (LightDM — separate from your session)

```bash
sudo nano /etc/lightdm/lightdm.conf
```
```ini
[Seat:*]
display-setup-script=/usr/bin/xrandr --output None-1 --rotate right
```

### Better: rotate before the session starts

```bash
sudo nano /etc/X11/xorg.conf.d/10-monitor.conf
```
```
Section "Monitor"
    Identifier "DSI-1"
    Option "Rotate" "right"
EndSection
```

Whole machine including the text console — add to `/etc/default/grub`:
`fbcon=rotate:1` plus `video=DSI-1:panel_orientation=right_side_up`, then
`sudo update-grub`.

### Wallpaper breaks after rotating

The desktop paints the wallpaper onto a surface sized for the old geometry and
doesn't repaint after `xrandr`. On Xfce:

```bash
xfdesktop --reload
```

Also set the image style to **scaled** — `spanned` and `zoom` misbehave badly
when the aspect ratio flips. Rotating before the session starts avoids this
entirely.

### Auto-rotation (optional)

Xfce has no built-in handler. Confirm the sensor works before writing anything:

```bash
sudo apt install -y iio-sensor-proxy
monitor-sensor        # tilt the tablet; orientation should print
```

If nothing prints, this unit needs a kernel quirk — not worth chasing.

```bash
monitor-sensor | while read -r line; do
  case "$line" in
    *normal*)    R=normal;   M="1 0 0 0 1 0 0 0 1" ;;
    *right-up*)  R=right;    M="0 1 0 -1 0 1 0 0 1" ;;
    *left-up*)   R=left;     M="0 -1 1 1 0 0 0 0 1" ;;
    *bottom-up*) R=inverted; M="-1 0 1 0 -1 1 0 0 1" ;;
    *) continue ;;
  esac
  xrandr --output DSI-1 --rotate "$R"
  xinput set-prop "SIS0457:00 0457:11ED" "Coordinate Transformation Matrix" $M
done
```

> Most community auto-rotate scripts only call `xrandr` and leave touch behind.
> If a script doesn't contain `xinput set-prop`, it won't rotate touch.

---

## 7. Audio

### Install and test

```bash
sudo apt install alsa-utils      # this is what provides aplay

aplay --version
aplay -l                          # list playback devices
aplay /usr/share/sounds/alsa/Front_Center.wav
speaker-test -c 2 -t wav -l 1
```

PipeWire/PulseAudio layer:

```bash
pactl info                  # "Server Name" tells you which
pactl list short sinks
paplay /usr/share/sounds/alsa/Front_Center.wav
```

### Choosing the right card

Neither config file exists by default — create whichever you prefer:

```bash
nano ~/.asoundrc              # per-user; wins over the system file
# or
sudo nano /etc/asound.conf    # system-wide, incl. root and systemd services
```

```
defaults.pcm.card 1
defaults.ctl.card 1
```

`ctl` is what `alsamixer` reads, `pcm` is what `aplay` reads — set both. No
reload needed; it's read per-process.

With `plughw` conversion behaviour as the default:

```
pcm.!default {
    type plug
    slave.pcm "hw:1,0"
}
ctl.!default {
    type hw
    card 1
}
```

Verify — `-v` prints the resolved device:

```bash
aplay -D default -v /usr/share/sounds/alsa/Front_Center.wav 2>&1 | head -20
```

### Renumbering at the kernel level

```bash
cat /proc/asound/modules
sudo nano /etc/modprobe.d/alsa-card-order.conf
```
```
options snd_soc_sst_bytcr_rt5640 index=0
options snd_hda_intel index=1
```
```bash
sudo update-initramfs -u && sudo reboot
```

### Caveats

- **PipeWire ignores all of the above.** `~/.asoundrc` only governs programs
  talking to ALSA directly (`aplay`, `alsamixer`). For desktop apps use
  `pactl set-default-sink` or `pavucontrol`.
- Save your mixer levels or they come back muted: `sudo alsactl store 1`
- Device busy → use `aplay -D default file.wav`, not `-D hw:0,0`
- Permission errors → `sudo usermod -aG audio $USER`, then re-login
- `aplay` handles WAV/AU/RAW only — use `mpg123` or `ffplay` for MP3/FLAC

---

## 8. Running a script at boot

```bash
sudo nano /usr/local/bin/myscript.sh
sudo chmod +x /usr/local/bin/myscript.sh
```

Start with `#!/bin/bash` and use **absolute paths** — there's almost no
environment at boot.

```bash
sudo nano /etc/systemd/system/myscript.service
```

```ini
[Unit]
Description=My boot script
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/myscript.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
```

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now myscript.service
systemctl status myscript.service
journalctl -u myscript.service
```

Variations:

- Long-running daemon → `Type=simple`, drop `RemainAfterExit`
- Before the login screen → `Before=display-manager.service` in `[Unit]`,
  `WantedBy=display-manager.service` in `[Install]`
- Quick and dirty → `sudo crontab -e`, then
  `@reboot /usr/local/bin/myscript.sh` (no logging, no ordering)
- Drop `After=`/`Wants=` if networking isn't needed — it'll run earlier

> **A root systemd service cannot rotate the display** — no `DISPLAY`, no
> `XAUTHORITY`. Use the config-level methods in §6 instead. Same for audio: a
> root service has no audio session, so use `systemctl --user` or target the
> hardware device directly.

---

## 9. File transfer to/from Windows

**Tailscale is not required.** It creates a private network *across the
internet* — only needed to reach the tablet from outside your home. For a
tablet and PC on the same Wi-Fi it's just another background daemon on a
2 GB machine.

Lightest option — SSH:

```bash
sudo apt install -y openssh-server
```

Connect from Windows with WinSCP or `scp` in PowerShell. No configuration
beyond the above.

Samba is only worth it if you want the tablet's folders to appear in Windows
Explorer as a normal network drive.

---

## 10. Quick reference

```bash
# state
free -h; df -h /; lsblk; swapon --show
cat /etc/os-release
. /etc/os-release; echo $VERSION_CODENAME

# apt health
sudo dpkg --audit; sudo apt-get check; apt-mark showhold
ls /etc/apt/sources.list.d/
cat /etc/apt/sources.list.d/ubuntu.sources

# display
xrandr; xinput list --name-only
~/rotate.sh; xfdesktop --reload

# audio
aplay -l; pactl list short sinks
aplay /usr/share/sounds/alsa/Front_Center.wav
sudo alsactl store 1

# build
mcs -pkg:gtk-sharp-3.0 program.cs MainWindow.cs ArduinoConnection.cs -out:test.exe
mono test.exe
```