Boot the stick, pick one GRUB entry, walk away: it wipes the target disk, installs Ubuntu 26.04.1 with the NVIDIA driver and codecs, and runs setup.sh on first boot to install the apps. build-iso.sh does the image surgery in a container, so the build machine needs nothing but podman. The wipe is pinned to one disk serial (the 990 PRO). The Data drive can't match it, and an unmatched disk stops the install rather than guessing — so the stick can't eat another machine either. The autoinstall entry is not the GRUB default, so an accidental boot lands in the ordinary Ubuntu installer. Verified by running the install in a VM with two virtual NVMe drives carrying the real serials: unattended start to finish, target partitioned and installed, Data disk byte-for-byte identical, and late-commands left /opt/main-desktop plus the enabled first-boot service in place. Testing caught the autoinstall args landing after '---' (where they reach the installed system instead of the installer, and do nothing), and the first-boot run adding root rather than ckoch to the docker group, since runuser leaves $USER alone. setup.sh gains an unattended mode for that first-boot run, waits out the dpkg lock that Ubuntu's own boot-time upgrades hold, waits for snapd to finish seeding before removing the Firefox snap, and skips the driver step on machines with no NVIDIA card. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
176 lines
9.0 KiB
Markdown
176 lines
9.0 KiB
Markdown
# main-desktop
|
|
|
|
**Ubuntu 26.04 LTS** on my desktop (Ryzen 7 9800X3D, RTX 5090, 49" 5120x1440), installed from a
|
|
custom ISO that wipes the drive and sets the whole machine up without being watched. This replaces
|
|
the Bazzite/BlueBuild image that used to live here — that version is still in the git history.
|
|
|
|
| File | What it is |
|
|
|---|---|
|
|
| [scripts/build-iso.sh](scripts/build-iso.sh) | Builds the install ISO: stock Ubuntu desktop ISO + this repo + the autoinstall config |
|
|
| [iso/user-data.in](iso/user-data.in) | The autoinstall config — which disk to wipe, the user, the driver and codec choices |
|
|
| [scripts/setup.sh](scripts/setup.sh) | Installs everything (drivers, Docker, Steam, Flatpaks, mounts). Runs itself on first boot; also runnable by hand on a stock Ubuntu install |
|
|
| [scripts/firstboot.sh](scripts/firstboot.sh) | The one-shot first-boot wrapper that calls setup.sh |
|
|
| [flatpaks.txt](flatpaks.txt) | The app list |
|
|
|
|
Copying files over and signing in to apps are deliberately **not** automated — I do those by hand.
|
|
|
|
## Why leave Bazzite
|
|
|
|
The read-only base made ordinary things into projects. VS Code had to be layered with rpm-ostree,
|
|
Node and .NET came from Homebrew, and Claude desktop ran inside an Ubuntu distrobox because it
|
|
only ships for Ubuntu/Debian. On Ubuntu, each of those is an `apt install`.
|
|
|
|
## 1. Build the ISO
|
|
|
|
```bash
|
|
scripts/build-iso.sh
|
|
```
|
|
|
|
It asks for the login password to bake in, downloads the Ubuntu 26.04.1 desktop ISO if `build/`
|
|
doesn't have it (~6 GB, resumable, checksum-verified), and writes
|
|
`build/main-desktop-<date>.iso`. The image surgery runs in a container, so the build machine needs
|
|
nothing but podman — it works from Bazzite as-is.
|
|
|
|
**The ISO holds your password hash, so treat the stick as private.**
|
|
|
|
Write it to a USB stick — check the device name first, `lsblk` before you `dd`:
|
|
|
|
```bash
|
|
sudo dd if=build/main-desktop-<date>.iso of=/dev/sdX bs=4M status=progress oflag=direct conv=fsync
|
|
```
|
|
|
|
## 2. Boot it and pick the last GRUB entry
|
|
|
|
> **Install main-desktop UNATTENDED — ERASES the target disk**
|
|
|
|
It is deliberately **not** the default entry: left alone, the stick boots into the ordinary Ubuntu
|
|
installer, so an accidental boot can't wipe anything. The other entries are untouched.
|
|
|
|
### What keeps this from eating the wrong disk
|
|
|
|
The autoinstall config matches **one disk by serial** — `S73VNU0Y101555E`, the 1 TB Samsung
|
|
990 PRO. The other NVMe in this machine (the 970 EVO Plus, "Data", serial `S6P7NS0X656465Z`,
|
|
holding the second Steam library and the emulator files) never matches, and if no disk matches
|
|
the installer stops instead of guessing. The same goes for any other machine you boot the stick
|
|
on: it refuses rather than picking something.
|
|
|
|
Verified by installing from this ISO in a VM with two virtual NVMe drives carrying exactly those
|
|
two serials: the 990 PRO one came out partitioned (1 GB ESP + root) with Ubuntu on it, and the
|
|
Data one was byte-for-byte identical afterwards, marker file and all. The whole install ran
|
|
without a keypress and rebooted on its own.
|
|
|
|
From there it runs on its own: partition, install Ubuntu desktop, install the NVIDIA driver and
|
|
media codecs, then reboot.
|
|
|
|
## 3. First boot installs the apps
|
|
|
|
A one-shot service runs [setup.sh](scripts/setup.sh) the first time the new system boots. It
|
|
needs the network, takes a while (roughly 12 GB of Flatpaks), and logs to
|
|
`/var/log/main-desktop-firstboot.log`:
|
|
|
|
```bash
|
|
tail -f /var/log/main-desktop-firstboot.log
|
|
```
|
|
|
|
You can log in and use the machine while it works. It installs:
|
|
|
|
- **Gaming:** Steam (plus `steam-devices` for controllers), Lutris, GameMode, MangoHud, and
|
|
ProtonUp-Qt for Proton-GE. The Xbox Bluetooth fix (`disable_ertm`) carries over from Bazzite.
|
|
- **Containers:** Docker CE from Docker's repo (not the snap), with `--gpus all` wired up through
|
|
nvidia-container-toolkit. You're added to `docker` and `kvm`.
|
|
- **Apps:** the 27 Flatpaks in [flatpaks.txt](flatpaks.txt), picked from the 42 that were on
|
|
Bazzite. The rest sit commented out at the bottom of that file. Ubuntu's Firefox snap is removed
|
|
in favour of the Flatpak. A systemd timer updates Flatpaks daily.
|
|
- **Dev:** VS Code, Claude desktop (Anthropic's apt repo), Claude Code CLI, `build-essential` and
|
|
cmake, and git credentials kept in the GNOME keyring. **No Node or .NET on the host** by
|
|
choice — LudosData and landingPage build in Docker. To add them:
|
|
`sudo add-apt-repository ppa:jdxcode/mise && sudo apt install mise dotnet-sdk-10.0`.
|
|
- **Mounts:** the Data drive at **`/mnt/data`**. The server-marvin share is skipped on an
|
|
unattended run; run setup.sh again by hand if you want it, and it'll ask for the credentials.
|
|
|
|
The installer grants passwordless sudo for that one run; firstboot.sh takes it away again on its
|
|
way out, whether setup.sh succeeded or not.
|
|
|
|
## 4. Then, by hand
|
|
|
|
- **Log out and back in** — the `docker` and `kvm` groups need a fresh session.
|
|
- **Steam:** sign in, then **Settings → Storage → Add drive → `/mnt/data/SteamLibrary`**. Those
|
|
games need no redownload.
|
|
- **Display:** Settings → Displays. The monitor does **5120x1440 @ 240 Hz**; Bazzite was running
|
|
it at 120. Turn on variable refresh rate while you're there.
|
|
- **PIA VPN:** the official Linux app works on Ubuntu (it couldn't on Bazzite's read-only `/usr`).
|
|
Download the installer from PIA and run it.
|
|
- **Gitea:** the first `git push` asks for your username and password once, then the GNOME keyring
|
|
remembers it. KDE Wallet was doing that here.
|
|
|
|
### Moving files across by hand
|
|
|
|
- **Flatpak app data** lives in `~/.var/app/<app-id>`. Copying one app's folder across brings that
|
|
app's logins, profile and settings with it — Firefox and Thunderbird profiles included.
|
|
- **Steam saves** are in `~/.local/share/Steam/userdata` (Proton prefixes in
|
|
`steamapps/compatdata`). Game files themselves redownload.
|
|
- **FFXIV** is `~/.xlcore`, ~124 GB. Worth copying rather than re-fetching from Square's servers.
|
|
- **ComfyUI:** copy the folder but not its `venv` — rebuild that with
|
|
`python3 -m venv venv && venv/bin/pip install -r requirements.txt`.
|
|
- **Don't copy** Fedora's `.bashrc`/`.bash_profile`, `~/.local/bin` (pip shims built against
|
|
Fedora's Python), `~/.config/dconf` (KDE's settings database, which would overwrite GNOME's), or
|
|
the KDE-generated `~/.config/gtk-3.0` and `gtk-4.0` themes.
|
|
|
|
## Check it worked
|
|
|
|
```bash
|
|
nvidia-smi # RTX 5090, driver 595.x
|
|
modinfo -F license nvidia # "Dual MIT/GPL" = open modules
|
|
docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi
|
|
flatpak list --app | wc -l # 27
|
|
findmnt /mnt/data
|
|
claude --version && code --version
|
|
systemctl status main-desktop-firstboot # should be a completed one-shot, now disabled
|
|
```
|
|
|
|
Podman's `--device nvidia.com/gpu=all` needs a CDI spec. The toolkit normally generates one at
|
|
boot; if Podman says the device is unknown, run
|
|
`sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml`.
|
|
|
|
## Without the ISO
|
|
|
|
Install Ubuntu 26.04 by hand (user **`ckoch`** — the first user gets UID 1000, which matches the
|
|
file ownership on the Data drive), then:
|
|
|
|
```bash
|
|
sudo apt install -y git
|
|
git clone https://git.lazypugs.com/ckoch/main-desktop.git ~/Documents/git/main-desktop
|
|
cd ~/Documents/git/main-desktop && scripts/setup.sh # safe to re-run
|
|
```
|
|
|
|
Reboot when it finishes, then `sudo flatpak update` once — Flatpak can only match its NVIDIA GL
|
|
runtime to a driver that's already loaded, so apps render in software until that happens (the
|
|
daily timer catches it about 10 minutes after boot anyway). The ISO route avoids this, because the
|
|
driver is already installed before the apps are.
|
|
|
|
## Coming from Bazzite: what moved
|
|
|
|
| Was | Now |
|
|
|---|---|
|
|
| `rpm-ostree` layering / `ujust` | `apt` |
|
|
| Homebrew `node@24`, `dotnet` | Nothing on the host — those projects build in Docker |
|
|
| Claude desktop in a distrobox | Native `claude-desktop` package |
|
|
| Git password from KDE Wallet | GNOME keyring, via git's libsecret helper |
|
|
| `/var/home/ckoch` | `/home/ckoch` |
|
|
| `/run/media/ckoch/Data` | `/mnt/data` |
|
|
| `/var/mnt/server-marvin-personal` | `/mnt/server-marvin-personal` |
|
|
| Image updates + rollback | Software Updater / `sudo apt update && sudo apt upgrade`; Flatpaks auto-update daily. No rollback by default; `sudo apt install timeshift` if you miss it |
|
|
|
|
## Maintenance
|
|
|
|
- **Add or remove an app:** edit [flatpaks.txt](flatpaks.txt), then re-run `scripts/setup.sh` on
|
|
the machine (or rebuild the ISO for the next install). Removing a line doesn't uninstall
|
|
anything; `flatpak uninstall <id>` once by hand.
|
|
- **New Ubuntu point release:** bump `UBUNTU_POINT` in
|
|
[scripts/build-iso.sh](scripts/build-iso.sh) and rebuild.
|
|
- **Next LTS (28.04):** Ubuntu offers the upgrade in Software Updater once 28.04.1 is out. The
|
|
upgrader disables third-party repos (Docker, NVIDIA toolkit, and so on) — turn them back on in
|
|
Software & Updates → Other Software afterwards.
|
|
- The ISO's own "Check disc for defects" option will report mismatches, since the added files
|
|
aren't in the stock `md5sum.txt`. That's expected and doesn't affect installing.
|