Add an unattended-install ISO builder
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>
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
# Build artifacts: multi-GB ISOs and extracted payloads.
|
||||||
|
/build/
|
||||||
@@ -1,13 +1,18 @@
|
|||||||
# main-desktop
|
# main-desktop
|
||||||
|
|
||||||
**Ubuntu 26.04 LTS** on my desktop (Ryzen 7 9800X3D, RTX 5090, 49" 5120x1440), set up by one
|
**Ubuntu 26.04 LTS** on my desktop (Ryzen 7 9800X3D, RTX 5090, 49" 5120x1440), installed from a
|
||||||
script. This replaces the Bazzite/BlueBuild image that used to live here — that version is still
|
custom ISO that wipes the drive and sets the whole machine up without being watched. This replaces
|
||||||
in the git history.
|
the Bazzite/BlueBuild image that used to live here — that version is still in the git history.
|
||||||
|
|
||||||
[scripts/setup.sh](scripts/setup.sh) turns a fresh Ubuntu install into this machine: NVIDIA
|
| File | What it is |
|
||||||
driver, Docker with GPU access, Steam, the apps in [flatpaks.txt](flatpaks.txt), and the drive
|
|---|---|
|
||||||
mounts. It's safe to re-run. Copying files over and signing in to apps are deliberately **not**
|
| [scripts/build-iso.sh](scripts/build-iso.sh) | Builds the install ISO: stock Ubuntu desktop ISO + this repo + the autoinstall config |
|
||||||
its job — I do those by hand.
|
| [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
|
## Why leave Bazzite
|
||||||
|
|
||||||
@@ -15,39 +20,62 @@ The read-only base made ordinary things into projects. VS Code had to be layered
|
|||||||
Node and .NET came from Homebrew, and Claude desktop ran inside an Ubuntu distrobox because it
|
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`.
|
only ships for Ubuntu/Debian. On Ubuntu, each of those is an `apt install`.
|
||||||
|
|
||||||
## 1. Install Ubuntu 26.04 LTS
|
## 1. Build the ISO
|
||||||
|
|
||||||
> **⚠ Two identical 1 TB NVMe drives.** Install to the **Samsung SSD 990 PRO**. Never pick the
|
|
||||||
> **Samsung SSD 970 EVO Plus** — that's the "Data" drive holding the second Steam library and the
|
|
||||||
> emulator files, and it stays untouched. `nvme0`/`nvme1` can swap between boots, so go by the
|
|
||||||
> model name the installer shows.
|
|
||||||
|
|
||||||
- "Erase disk and install Ubuntu" on the 990 PRO is fine.
|
|
||||||
- Make the user **`ckoch`**. The first user gets UID 1000, which matches the file ownership
|
|
||||||
already on the Data drive.
|
|
||||||
- The "third-party drivers" checkbox doesn't matter; setup.sh installs the NVIDIA driver either
|
|
||||||
way.
|
|
||||||
- Prefer KDE? Install **Kubuntu 26.04** instead. The script doesn't care which desktop you run.
|
|
||||||
|
|
||||||
## 2. Run the script
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt install -y git
|
scripts/build-iso.sh
|
||||||
git clone https://git.lazypugs.com/ckoch/main-desktop.git ~/Documents/git/main-desktop
|
|
||||||
cd ~/Documents/git/main-desktop && scripts/setup.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
It asks for your sudo password once, then for the server-marvin SMB credentials — press Enter
|
It asks for the login password to bake in, downloads the Ubuntu 26.04.1 desktop ISO if `build/`
|
||||||
there to skip that mount. Flatpaks take most of the run. **Reboot** when it finishes.
|
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.
|
||||||
|
|
||||||
What it installs:
|
**The ISO holds your password hash, so treat the stick as private.**
|
||||||
|
|
||||||
- **NVIDIA driver:** Ubuntu's recommended **open-kernel** branch (595 today) with the prebuilt,
|
Write it to a USB stick — check the device name first, `lsblk` before you `dd`:
|
||||||
signed modules, so no DKMS. The script stops if it ends up with a non-open driver, since
|
|
||||||
Blackwell only runs on the open modules. Also 32-bit GL for Steam and Proton.
|
```bash
|
||||||
- **Gaming:** Steam (`steam-installer` plus `steam-devices` for controllers), Lutris, GameMode,
|
sudo dd if=build/main-desktop-<date>.iso of=/dev/sdX bs=4M status=progress oflag=direct conv=fsync
|
||||||
MangoHud, and ProtonUp-Qt for Proton-GE. The Xbox Bluetooth fix (`disable_ertm`) carries over
|
```
|
||||||
from Bazzite.
|
|
||||||
|
## 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
|
- **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`.
|
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
|
- **Apps:** the 27 Flatpaks in [flatpaks.txt](flatpaks.txt), picked from the 42 that were on
|
||||||
@@ -57,26 +85,19 @@ What it installs:
|
|||||||
cmake, and git credentials kept in the GNOME keyring. **No Node or .NET on the host** by
|
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:
|
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`.
|
`sudo add-apt-repository ppa:jdxcode/mise && sudo apt install mise dotnet-sdk-10.0`.
|
||||||
- **Mounts:** the Data drive at **`/mnt/data`**, and server-marvin at
|
- **Mounts:** the Data drive at **`/mnt/data`**. The server-marvin share is skipped on an
|
||||||
**`/mnt/server-marvin-personal`** if you gave it credentials.
|
unattended run; run setup.sh again by hand if you want it, and it'll ask for the credentials.
|
||||||
|
|
||||||
## 3. After the reboot
|
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.
|
||||||
|
|
||||||
```bash
|
## 4. Then, by hand
|
||||||
sudo flatpak update # fetches the NVIDIA GL runtime the Flatpak apps need
|
|
||||||
```
|
|
||||||
|
|
||||||
Flatpak can only match that runtime to the driver once the driver is actually loaded, which is why
|
|
||||||
it waits until after the reboot. Skip it and apps fall back to software rendering until the daily
|
|
||||||
timer catches up about 10 minutes after boot.
|
|
||||||
|
|
||||||
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
|
- **Steam:** sign in, then **Settings → Storage → Add drive → `/mnt/data/SteamLibrary`**. Those
|
||||||
games need no redownload.
|
games need no redownload.
|
||||||
- **Display:** Settings → Displays. The monitor does **5120x1440 @ 240 Hz**; Bazzite was running
|
- **Display:** Settings → Displays. The monitor does **5120x1440 @ 240 Hz**; Bazzite was running
|
||||||
it at 120. Turn on variable refresh rate while you're there. Ubuntu's built-in tiling earns its
|
it at 120. Turn on variable refresh rate while you're there.
|
||||||
keep at this width.
|
|
||||||
- **PIA VPN:** the official Linux app works on Ubuntu (it couldn't on Bazzite's read-only `/usr`).
|
- **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.
|
Download the installer from PIA and run it.
|
||||||
- **Gitea:** the first `git push` asks for your username and password once, then the GNOME keyring
|
- **Gitea:** the first `git push` asks for your username and password once, then the GNOME keyring
|
||||||
@@ -84,15 +105,11 @@ Then, by hand:
|
|||||||
|
|
||||||
### Moving files across by hand
|
### Moving files across by hand
|
||||||
|
|
||||||
Worth knowing while you copy things over from the old install:
|
|
||||||
|
|
||||||
- **Flatpak app data** lives in `~/.var/app/<app-id>`. Copying one app's folder across brings that
|
- **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 — as long
|
app's logins, profile and settings with it — Firefox and Thunderbird profiles included.
|
||||||
as the app ID matches.
|
- **Steam saves** are in `~/.local/share/Steam/userdata` (Proton prefixes in
|
||||||
- **Steam saves** are in `~/.local/share/Steam/userdata` (and Proton prefixes in
|
|
||||||
`steamapps/compatdata`). Game files themselves redownload.
|
`steamapps/compatdata`). Game files themselves redownload.
|
||||||
- **FFXIV** is `~/.xlcore`, ~124 GB. Worth copying rather than re-fetching from Square's patch
|
- **FFXIV** is `~/.xlcore`, ~124 GB. Worth copying rather than re-fetching from Square's servers.
|
||||||
servers.
|
|
||||||
- **ComfyUI:** copy the folder but not its `venv` — rebuild that with
|
- **ComfyUI:** copy the folder but not its `venv` — rebuild that with
|
||||||
`python3 -m venv venv && venv/bin/pip install -r requirements.txt`.
|
`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
|
- **Don't copy** Fedora's `.bashrc`/`.bash_profile`, `~/.local/bin` (pip shims built against
|
||||||
@@ -108,12 +125,29 @@ docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi
|
|||||||
flatpak list --app | wc -l # 27
|
flatpak list --app | wc -l # 27
|
||||||
findmnt /mnt/data
|
findmnt /mnt/data
|
||||||
claude --version && code --version
|
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
|
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
|
boot; if Podman says the device is unknown, run
|
||||||
`sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml`.
|
`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
|
## Coming from Bazzite: what moved
|
||||||
|
|
||||||
| Was | Now |
|
| Was | Now |
|
||||||
@@ -129,8 +163,13 @@ boot; if Podman says the device is unknown, run
|
|||||||
|
|
||||||
## Maintenance
|
## Maintenance
|
||||||
|
|
||||||
- **Add or remove an app:** edit [flatpaks.txt](flatpaks.txt) and re-run `scripts/setup.sh`.
|
- **Add or remove an app:** edit [flatpaks.txt](flatpaks.txt), then re-run `scripts/setup.sh` on
|
||||||
Removing a line doesn't uninstall anything; `flatpak uninstall <id>` once by hand.
|
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
|
- **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
|
upgrader disables third-party repos (Docker, NVIDIA toolkit, and so on) — turn them back on in
|
||||||
Software & Updates → Other Software afterwards.
|
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.
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=main-desktop first-boot setup (apps, Flatpaks, mounts)
|
||||||
|
Documentation=file:/opt/main-desktop/README.md
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
ConditionPathExists=!/var/lib/main-desktop-firstboot.done
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/opt/main-desktop/scripts/firstboot.sh
|
||||||
|
# Installing ~12 GB of Flatpaks takes as long as it takes; the default 90s would
|
||||||
|
# kill it mid-download.
|
||||||
|
TimeoutStartSec=infinity
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#cloud-config
|
||||||
|
# Unattended install for main-desktop. scripts/build-iso.sh fills in the placeholders
|
||||||
|
# and drops this on the ISO as /nocloud/user-data.
|
||||||
|
autoinstall:
|
||||||
|
version: 1
|
||||||
|
|
||||||
|
# Stock Ubuntu desktop, not the -minimal variant, so LibreOffice and the usual
|
||||||
|
# desktop apps come along as debs.
|
||||||
|
source:
|
||||||
|
id: ubuntu-desktop
|
||||||
|
|
||||||
|
# This is what installs the NVIDIA driver (the recommended open branch on this
|
||||||
|
# hardware), so the GPU works on first boot and Flatpak can match its GL runtime
|
||||||
|
# straight away. codecs pulls ubuntu-restricted-addons.
|
||||||
|
drivers:
|
||||||
|
install: true
|
||||||
|
codecs:
|
||||||
|
install: true
|
||||||
|
|
||||||
|
identity:
|
||||||
|
realname: '@@REALNAME@@'
|
||||||
|
username: '@@USERNAME@@'
|
||||||
|
hostname: '@@HOSTNAME@@'
|
||||||
|
password: '@@PASSWORD_HASH@@'
|
||||||
|
|
||||||
|
# ONE disk is touched, matched by serial. @@TARGET_MODEL@@ is the target; the other
|
||||||
|
# NVMe in this machine never matches, and if nothing matches, the install stops
|
||||||
|
# instead of guessing. That also means this ISO can't wipe a different machine.
|
||||||
|
storage:
|
||||||
|
layout:
|
||||||
|
name: direct
|
||||||
|
match:
|
||||||
|
serial: '@@TARGET_SERIAL@@'
|
||||||
|
|
||||||
|
packages:
|
||||||
|
- git
|
||||||
|
- curl
|
||||||
|
- ca-certificates
|
||||||
|
|
||||||
|
late-commands:
|
||||||
|
# Bake this repo into the installed system and arm the one-shot first-boot run
|
||||||
|
# of setup.sh, which installs everything in flatpaks.txt and the rest.
|
||||||
|
- cp -r /cdrom/main-desktop /target/opt/main-desktop
|
||||||
|
- chmod +x /target/opt/main-desktop/scripts/firstboot.sh /target/opt/main-desktop/scripts/setup.sh
|
||||||
|
- cp /cdrom/main-desktop/iso/main-desktop-firstboot.service /target/etc/systemd/system/
|
||||||
|
# Passwordless sudo so the first-boot run doesn't stall on a password prompt.
|
||||||
|
# firstboot.sh deletes this again on its way out, pass or fail.
|
||||||
|
- >-
|
||||||
|
printf '%s ALL=(ALL) NOPASSWD:ALL\n' '@@USERNAME@@'
|
||||||
|
> /target/etc/sudoers.d/99-main-desktop-firstboot
|
||||||
|
- chmod 440 /target/etc/sudoers.d/99-main-desktop-firstboot
|
||||||
|
- curtin in-target -- systemctl enable main-desktop-firstboot.service
|
||||||
|
|
||||||
|
shutdown: reboot
|
||||||
Executable
+164
@@ -0,0 +1,164 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build the unattended-install ISO: the stock Ubuntu desktop ISO, plus this repo, plus an
|
||||||
|
# autoinstall config that wipes ONE disk matched by serial. The image surgery runs in a
|
||||||
|
# container, so nothing needs installing on the machine you build from (Bazzite included).
|
||||||
|
#
|
||||||
|
# scripts/build-iso.sh # downloads the Ubuntu ISO if build/ lacks it
|
||||||
|
# ISO=/path/to/ubuntu-26.04.1-desktop-amd64.iso scripts/build-iso.sh
|
||||||
|
# PASSWORD_HASH='$6$...' scripts/build-iso.sh # skip the password prompt
|
||||||
|
#
|
||||||
|
# Output: build/main-desktop-<date>.iso
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
UBUNTU_RELEASE=26.04
|
||||||
|
UBUNTU_POINT=26.04.1
|
||||||
|
ISO_NAME="ubuntu-${UBUNTU_POINT}-desktop-amd64.iso"
|
||||||
|
BASE_URL="https://releases.ubuntu.com/${UBUNTU_RELEASE}"
|
||||||
|
BUILDER_IMAGE=docker.io/library/ubuntu:26.04
|
||||||
|
|
||||||
|
# --- the machine this ISO is built for -------------------------------------------------
|
||||||
|
# Serial of the ONLY disk the install may touch: the 1 TB Samsung 990 PRO. The 970 EVO Plus
|
||||||
|
# ("Data", second Steam library, serial S6P7NS0X656465Z) never matches this.
|
||||||
|
TARGET_SERIAL='*S73VNU0Y101555E*'
|
||||||
|
TARGET_MODEL='Samsung SSD 990 PRO 1TB'
|
||||||
|
USERNAME=ckoch
|
||||||
|
REALNAME='Christopher Koch'
|
||||||
|
HOSTNAME=main-desktop
|
||||||
|
|
||||||
|
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
BUILD="$REPO/build"
|
||||||
|
OUT="${OUT:-$BUILD/main-desktop-$(date +%Y%m%d).iso}"
|
||||||
|
|
||||||
|
step() { printf '\n\033[1;34m== %s ==\033[0m\n' "$*"; }
|
||||||
|
die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
for t in podman curl openssl; do command -v "$t" >/dev/null || die "need $t"; done
|
||||||
|
mkdir -p "$BUILD"
|
||||||
|
|
||||||
|
# --- 1. the login password (hashed into the ISO) ---------------------------------------
|
||||||
|
step "Login password for $USERNAME"
|
||||||
|
if [[ -z ${PASSWORD_HASH:-} ]]; then
|
||||||
|
echo "Hashed into the ISO, so keep the stick to yourself."
|
||||||
|
read -rsp " password: " p1; echo
|
||||||
|
read -rsp " again: " p2; echo
|
||||||
|
[[ -n $p1 && $p1 == "$p2" ]] || die "passwords empty or didn't match"
|
||||||
|
PASSWORD_HASH=$(openssl passwd -6 "$p1")
|
||||||
|
unset p1 p2
|
||||||
|
fi
|
||||||
|
[[ $PASSWORD_HASH == \$6\$* ]] || die "PASSWORD_HASH doesn't look like a SHA-512 crypt hash"
|
||||||
|
|
||||||
|
# --- 2. the Ubuntu ISO ------------------------------------------------------------------
|
||||||
|
step "Ubuntu $UBUNTU_POINT desktop ISO"
|
||||||
|
SRC_ISO="${ISO:-$BUILD/$ISO_NAME}"
|
||||||
|
if [[ ! -f $SRC_ISO ]]; then
|
||||||
|
echo "downloading $ISO_NAME (~6 GB, resumable)"
|
||||||
|
curl -fL -C - --retry 3 -o "$SRC_ISO" "$BASE_URL/$ISO_NAME"
|
||||||
|
fi
|
||||||
|
curl -fsSL -o "$BUILD/SHA256SUMS" "$BASE_URL/SHA256SUMS"
|
||||||
|
want=$(awk -v f="*$(basename "$SRC_ISO")" '$2 == f {print $1}' "$BUILD/SHA256SUMS")
|
||||||
|
if [[ -n $want ]]; then
|
||||||
|
echo "checking sha256…"
|
||||||
|
got=$(sha256sum "$SRC_ISO" | cut -d' ' -f1)
|
||||||
|
[[ $got == "$want" ]] || die "checksum mismatch on $SRC_ISO — delete it and rerun"
|
||||||
|
echo "ok: $got"
|
||||||
|
else
|
||||||
|
echo "warning: $(basename "$SRC_ISO") isn't listed in SHA256SUMS — skipping the check"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 3. what goes on the ISO ------------------------------------------------------------
|
||||||
|
step "Staging the payload"
|
||||||
|
PAYLOAD="$BUILD/payload"
|
||||||
|
rm -rf "$PAYLOAD"
|
||||||
|
mkdir -p "$PAYLOAD/nocloud" "$PAYLOAD/main-desktop/scripts" "$PAYLOAD/main-desktop/iso"
|
||||||
|
sed -e "s|@@PASSWORD_HASH@@|$PASSWORD_HASH|" \
|
||||||
|
-e "s|@@USERNAME@@|$USERNAME|g" \
|
||||||
|
-e "s|@@REALNAME@@|$REALNAME|" \
|
||||||
|
-e "s|@@HOSTNAME@@|$HOSTNAME|" \
|
||||||
|
-e "s|@@TARGET_SERIAL@@|$TARGET_SERIAL|" \
|
||||||
|
-e "s|@@TARGET_MODEL@@|$TARGET_MODEL|" \
|
||||||
|
"$REPO/iso/user-data.in" > "$PAYLOAD/nocloud/user-data"
|
||||||
|
: > "$PAYLOAD/nocloud/meta-data" # cloud-init wants this to exist, empty is fine
|
||||||
|
! grep -qE '@@[A-Z_]+@@' "$PAYLOAD/nocloud/user-data" \
|
||||||
|
|| die "a placeholder went unfilled in user-data: $(grep -oE '@@[A-Z_]+@@' "$PAYLOAD/nocloud/user-data" | sort -u | tr '\n' ' ')"
|
||||||
|
|
||||||
|
# Only the files the installed system needs — never build/ (it holds multi-GB ISOs).
|
||||||
|
cp "$REPO/README.md" "$REPO/flatpaks.txt" "$PAYLOAD/main-desktop/"
|
||||||
|
cp "$REPO/scripts/setup.sh" "$REPO/scripts/firstboot.sh" "$PAYLOAD/main-desktop/scripts/"
|
||||||
|
cp "$REPO/iso/main-desktop-firstboot.service" "$PAYLOAD/main-desktop/iso/"
|
||||||
|
du -sh "$PAYLOAD"
|
||||||
|
|
||||||
|
# --- 4. ISO surgery, in a container -----------------------------------------------------
|
||||||
|
step "Rebuilding the ISO"
|
||||||
|
cat > "$BUILD/_in-container.sh" <<'CONTAINER'
|
||||||
|
set -euo pipefail
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update -qq >/dev/null
|
||||||
|
apt-get install -y -qq xorriso >/dev/null
|
||||||
|
cd /work
|
||||||
|
SRC=$1 OUT=$2
|
||||||
|
|
||||||
|
# Reuse the original boot entry's kernel/initrd paths rather than hardcoding them.
|
||||||
|
rm -rf grub && mkdir grub
|
||||||
|
xorriso -osirrox on -indev "$SRC" -extract /boot/grub/grub.cfg grub/grub.cfg 2>/dev/null
|
||||||
|
kernel_line=$(grep -m1 -E '^[[:space:]]*linux[[:space:]]' grub/grub.cfg)
|
||||||
|
initrd_line=$(grep -m1 -E '^[[:space:]]*initrd[[:space:]]' grub/grub.cfg)
|
||||||
|
[ -n "$kernel_line" ] && [ -n "$initrd_line" ] || { echo "couldn't read boot entry from grub.cfg"; exit 1; }
|
||||||
|
|
||||||
|
# autoinstall = don't ask for confirmation; ds=nocloud points cloud-init at /nocloud.
|
||||||
|
# The ';' has to be escaped for GRUB's parser.
|
||||||
|
args='autoinstall ds=nocloud\;s=/cdrom/nocloud/'
|
||||||
|
|
||||||
|
# These args are for the INSTALLER, so they must go before the '---' separator — Ubuntu's
|
||||||
|
# stock line is "linux /casper/vmlinuz --- quiet splash", and anything after '---' is
|
||||||
|
# handed to the installed system instead, where autoinstall would do nothing.
|
||||||
|
# Done with string splitting, not sed, so the backslash GRUB needs survives.
|
||||||
|
if [ "${kernel_line#*---}" != "$kernel_line" ]; then
|
||||||
|
new_kernel="${kernel_line%%---*}$args ---${kernel_line#*---}"
|
||||||
|
else
|
||||||
|
new_kernel="$kernel_line $args"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp grub/grub.cfg grub/grub.cfg.new
|
||||||
|
cat >> grub/grub.cfg.new <<EOF
|
||||||
|
|
||||||
|
menuentry 'Install main-desktop UNATTENDED — ERASES the target disk' {
|
||||||
|
set gfxpayload=keep
|
||||||
|
$new_kernel
|
||||||
|
$initrd_line
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
chmod +w grub/grub.cfg.new
|
||||||
|
|
||||||
|
# 'replay' reproduces the original's boot equipment (El Torito, MBR, the appended EFI
|
||||||
|
# partition) exactly, instead of trying to rebuild it from scratch.
|
||||||
|
map_args=(-map /work/payload/nocloud /nocloud
|
||||||
|
-map /work/payload/main-desktop /main-desktop
|
||||||
|
-map /work/grub/grub.cfg.new /boot/grub/grub.cfg)
|
||||||
|
if xorriso -osirrox on -indev "$SRC" -lsl /boot/grub/loopback.cfg >/dev/null 2>&1; then
|
||||||
|
cp grub/grub.cfg.new grub/loopback.cfg.new
|
||||||
|
map_args+=(-map /work/grub/loopback.cfg.new /boot/grub/loopback.cfg)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The volume id is left alone on purpose: casper and GRUB can look the media up by label.
|
||||||
|
xorriso -indev "$SRC" -outdev "$OUT" \
|
||||||
|
-boot_image any replay \
|
||||||
|
-overwrite on \
|
||||||
|
"${map_args[@]}" \
|
||||||
|
-commit
|
||||||
|
CONTAINER
|
||||||
|
|
||||||
|
rm -f "$OUT" # xorriso won't write into an output image that already holds data
|
||||||
|
podman run --rm -v "$BUILD:/work:Z" -w /work "$BUILDER_IMAGE" \
|
||||||
|
bash /work/_in-container.sh "/work/$(basename "$SRC_ISO")" "/work/$(basename "$OUT")"
|
||||||
|
|
||||||
|
step "Done"
|
||||||
|
ls -lh "$OUT"
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
Write it to a USB stick (check the device name first with lsblk!):
|
||||||
|
sudo dd if=$OUT of=/dev/sdX bs=4M status=progress oflag=direct conv=fsync
|
||||||
|
|
||||||
|
Boot it, then pick the last GRUB entry:
|
||||||
|
"Install main-desktop UNATTENDED — ERASES the target disk"
|
||||||
|
It wipes only $TARGET_MODEL (serial $TARGET_SERIAL) and stops if that disk isn't there.
|
||||||
|
EOF
|
||||||
Executable
+38
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Runs once, on the first boot after an install from the ISO. Runs setup.sh as the
|
||||||
|
# desktop user, then takes back the passwordless sudo the installer granted for it.
|
||||||
|
# Log: /var/log/main-desktop-firstboot.log (and the journal).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
LOG=/var/log/main-desktop-firstboot.log
|
||||||
|
SUDOERS=/etc/sudoers.d/99-main-desktop-firstboot
|
||||||
|
STAMP=/var/lib/main-desktop-firstboot.done
|
||||||
|
|
||||||
|
# Whatever the installer called the account it made — uid 1000 is the desktop user.
|
||||||
|
USER_NAME=$(id -nu 1000 2>/dev/null || true)
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -f "$SUDOERS" # never leave passwordless sudo lying around, pass or fail
|
||||||
|
systemctl disable main-desktop-firstboot.service >/dev/null 2>&1 || true
|
||||||
|
: > "$STAMP"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
exec > >(tee -a "$LOG") 2>&1
|
||||||
|
echo "=== main-desktop first boot: $(date -Is) ==="
|
||||||
|
|
||||||
|
if [[ -z $USER_NAME ]]; then
|
||||||
|
echo "no uid 1000 user found — run /opt/main-desktop/scripts/setup.sh by hand"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "running setup.sh as $USER_NAME"
|
||||||
|
|
||||||
|
if runuser -u "$USER_NAME" -- env HOME="/home/$USER_NAME" UNATTENDED=1 \
|
||||||
|
bash /opt/main-desktop/scripts/setup.sh; then
|
||||||
|
echo "=== setup.sh finished $(date -Is) ==="
|
||||||
|
echo "Log out and back in to pick up the docker and kvm groups."
|
||||||
|
else
|
||||||
|
echo "=== setup.sh FAILED (see above) $(date -Is) ==="
|
||||||
|
echo "Fix whatever it complained about, then re-run it by hand (it's safe to re-run):"
|
||||||
|
echo " /opt/main-desktop/scripts/setup.sh"
|
||||||
|
fi
|
||||||
+44
-12
@@ -2,8 +2,14 @@
|
|||||||
# Fresh Ubuntu 26.04 LTS -> my desktop, in one pass. Run as your user; it sudos as needed.
|
# Fresh Ubuntu 26.04 LTS -> my desktop, in one pass. Run as your user; it sudos as needed.
|
||||||
# Safe to re-run: each step checks before it changes anything. Reboot when it finishes,
|
# Safe to re-run: each step checks before it changes anything. Reboot when it finishes,
|
||||||
# then run `sudo flatpak update` once so the Flatpak apps get the NVIDIA GL runtime.
|
# then run `sudo flatpak update` once so the Flatpak apps get the NVIDIA GL runtime.
|
||||||
|
#
|
||||||
|
# UNATTENDED=1 (or --unattended) asks nothing: used by the first-boot run after an
|
||||||
|
# install from the ISO, where the driver is already in place.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
UNATTENDED=${UNATTENDED:-}
|
||||||
|
[[ ${1:-} == --unattended ]] && UNATTENDED=1
|
||||||
|
|
||||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
# Second NVMe (970 EVO Plus, label "Data"): SteamLibrary + emulator files. Never formatted.
|
# Second NVMe (970 EVO Plus, label "Data"): SteamLibrary + emulator files. Never formatted.
|
||||||
@@ -17,13 +23,20 @@ MARVIN_CREDS=/etc/samba/credentials-marvin
|
|||||||
step() { printf '\n\033[1;34m== %s ==\033[0m\n' "$*"; }
|
step() { printf '\n\033[1;34m== %s ==\033[0m\n' "$*"; }
|
||||||
warn() { printf '\033[1;33mwarning:\033[0m %s\n' "$*" >&2; }
|
warn() { printf '\033[1;33mwarning:\033[0m %s\n' "$*" >&2; }
|
||||||
die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; }
|
die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; }
|
||||||
apt_install() { sudo DEBIAN_FRONTEND=noninteractive apt-get install -y "$@"; }
|
# Ubuntu's own unattended-upgrades run right after boot, so wait for the dpkg lock rather
|
||||||
|
# than failing the moment it's held — that's the difference between a first-boot run that
|
||||||
|
# works and one that dies in the first minute.
|
||||||
|
APT_OPTS=(-o DPkg::Lock::Timeout=600)
|
||||||
|
apt_install() { sudo DEBIAN_FRONTEND=noninteractive apt-get "${APT_OPTS[@]}" install -y "$@"; }
|
||||||
|
|
||||||
[[ $EUID -ne 0 ]] || die "run as your user, not with sudo"
|
[[ $EUID -ne 0 ]] || die "run as your user, not with sudo"
|
||||||
. /etc/os-release
|
. /etc/os-release
|
||||||
[[ $ID == ubuntu ]] || die "expected Ubuntu, found $ID"
|
[[ $ID == ubuntu ]] || die "expected Ubuntu, found $ID"
|
||||||
[[ $VERSION_ID == 26.04 ]] || warn "written for 26.04; this is $VERSION_ID — carrying on"
|
[[ $VERSION_ID == 26.04 ]] || warn "written for 26.04; this is $VERSION_ID — carrying on"
|
||||||
CODENAME=${UBUNTU_CODENAME:-$VERSION_CODENAME}
|
CODENAME=${UBUNTU_CODENAME:-$VERSION_CODENAME}
|
||||||
|
# Who we actually are. Not $USER: the first-boot service reaches this through runuser, which
|
||||||
|
# leaves $USER as root, and group changes would land on the wrong account.
|
||||||
|
ME=$(id -un)
|
||||||
|
|
||||||
# Ask for the password once and keep sudo alive for the whole run (Flatpaks take a while).
|
# Ask for the password once and keep sudo alive for the whole run (Flatpaks take a while).
|
||||||
sudo -v
|
sudo -v
|
||||||
@@ -33,7 +46,7 @@ trap 'kill "$keepalive" 2>/dev/null' EXIT
|
|||||||
|
|
||||||
# ---------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------
|
||||||
step "Apt repositories"
|
step "Apt repositories"
|
||||||
sudo apt-get update
|
sudo apt-get "${APT_OPTS[@]}" update
|
||||||
apt_install curl gpg ca-certificates software-properties-common
|
apt_install curl gpg ca-certificates software-properties-common
|
||||||
for c in universe multiverse restricted; do sudo add-apt-repository -y -n "$c"; done
|
for c in universe multiverse restricted; do sudo add-apt-repository -y -n "$c"; done
|
||||||
sudo dpkg --add-architecture i386 # Steam + 32-bit game libraries
|
sudo dpkg --add-architecture i386 # Steam + 32-bit game libraries
|
||||||
@@ -96,8 +109,8 @@ if [[ ! -f /etc/apt/sources.list.d/claude-desktop.list ]]; then
|
|||||||
| sudo tee /etc/apt/sources.list.d/claude-desktop.list >/dev/null
|
| sudo tee /etc/apt/sources.list.d/claude-desktop.list >/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo apt-get update
|
sudo apt-get "${APT_OPTS[@]}" update
|
||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get full-upgrade -y
|
sudo DEBIAN_FRONTEND=noninteractive apt-get "${APT_OPTS[@]}" full-upgrade -y
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------
|
||||||
step "Packages"
|
step "Packages"
|
||||||
@@ -115,11 +128,15 @@ apt_install \
|
|||||||
|
|
||||||
# ---------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------
|
||||||
step "NVIDIA driver (open kernel modules — the only kind the RTX 5090 supports)"
|
step "NVIDIA driver (open kernel modules — the only kind the RTX 5090 supports)"
|
||||||
apt_install ubuntu-drivers-common
|
|
||||||
installed_driver() {
|
installed_driver() {
|
||||||
{ dpkg-query -W -f='${Status} ${Package}\n' 'nvidia-driver-*' 2>/dev/null || true; } \
|
{ dpkg-query -W -f='${Status} ${Package}\n' 'nvidia-driver-*' 2>/dev/null || true; } \
|
||||||
| awk '/^install ok installed / {print $4}' | sort -V | tail -1
|
| awk '/^install ok installed / {print $4}' | sort -V | tail -1
|
||||||
}
|
}
|
||||||
|
if ! grep -qx '0x10de' /sys/bus/pci/devices/*/vendor 2>/dev/null; then
|
||||||
|
# No NVIDIA card here at all (a VM, say) — nothing to install, and nothing to fail over.
|
||||||
|
warn "no NVIDIA GPU on this machine — skipping the driver"
|
||||||
|
else
|
||||||
|
apt_install ubuntu-drivers-common
|
||||||
if [[ $(installed_driver) != *-open ]]; then
|
if [[ $(installed_driver) != *-open ]]; then
|
||||||
ubuntu-drivers devices 2>/dev/null | grep -E 'model|driver' || true
|
ubuntu-drivers devices 2>/dev/null | grep -E 'model|driver' || true
|
||||||
# Picks Ubuntu's recommended branch plus its prebuilt, signed kernel modules (no DKMS).
|
# Picks Ubuntu's recommended branch plus its prebuilt, signed kernel modules (no DKMS).
|
||||||
@@ -131,11 +148,12 @@ driver=$(installed_driver)
|
|||||||
branch=${driver#nvidia-driver-}; branch=${branch%-open}
|
branch=${driver#nvidia-driver-}; branch=${branch%-open}
|
||||||
apt_install "libnvidia-gl-$branch:i386" # 32-bit GL/Vulkan for Steam and Proton
|
apt_install "libnvidia-gl-$branch:i386" # 32-bit GL/Vulkan for Steam and Proton
|
||||||
echo "using $driver"
|
echo "using $driver"
|
||||||
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------
|
||||||
step "Docker, GPU containers, Claude's Cowork VM"
|
step "Docker, GPU containers, Claude's Cowork VM"
|
||||||
sudo usermod -aG docker "$USER" # docker without sudo (after reboot)
|
sudo usermod -aG docker "$ME" # docker without sudo (after reboot)
|
||||||
getent group kvm >/dev/null && sudo usermod -aG kvm "$USER" # Cowork needs /dev/kvm + vhost-vsock
|
getent group kvm >/dev/null && sudo usermod -aG kvm "$ME" # Cowork needs /dev/kvm + vhost-vsock
|
||||||
echo vhost_vsock | sudo tee /etc/modules-load.d/vhost_vsock.conf >/dev/null
|
echo vhost_vsock | sudo tee /etc/modules-load.d/vhost_vsock.conf >/dev/null
|
||||||
if ! grep -q nvidia /etc/docker/daemon.json 2>/dev/null; then
|
if ! grep -q nvidia /etc/docker/daemon.json 2>/dev/null; then
|
||||||
sudo nvidia-ctk runtime configure --runtime=docker # enables `docker run --gpus all`
|
sudo nvidia-ctk runtime configure --runtime=docker # enables `docker run --gpus all`
|
||||||
@@ -150,11 +168,18 @@ sudo flatpak install -y --noninteractive --system flathub "${apps[@]}"
|
|||||||
# Firefox's read-only peek at landingPage, as set by hand on Bazzite
|
# Firefox's read-only peek at landingPage, as set by hand on Bazzite
|
||||||
flatpak override --user org.mozilla.firefox --filesystem="$HOME/Documents/git/landingPage:ro"
|
flatpak override --user org.mozilla.firefox --filesystem="$HOME/Documents/git/landingPage:ro"
|
||||||
|
|
||||||
# Firefox/Thunderbird come from Flathub instead, so restoring ~/.var/app brings the old
|
# Firefox/Thunderbird come from Flathub instead, so copying ~/.var/app over brings the old
|
||||||
# profiles back untouched. Drop Ubuntu's snaps and the debs that pull them back in.
|
# profiles with them. Drop Ubuntu's snaps and the debs that pull them back in.
|
||||||
|
# On a first boot snapd may still be seeding, and a remove during that fails — wait it out,
|
||||||
|
# and treat a failure here as a warning rather than losing the rest of the run.
|
||||||
|
sudo snap wait system seed.loaded 2>/dev/null || true
|
||||||
for s in firefox thunderbird; do
|
for s in firefox thunderbird; do
|
||||||
if snap list "$s" >/dev/null 2>&1; then sudo snap remove --purge "$s"; fi
|
if snap list "$s" >/dev/null 2>&1; then
|
||||||
if dpkg -s "$s" >/dev/null 2>&1; then sudo apt-get remove -y "$s"; fi
|
sudo snap remove --purge "$s" || warn "couldn't remove the $s snap — remove it by hand later"
|
||||||
|
fi
|
||||||
|
if dpkg -s "$s" >/dev/null 2>&1; then
|
||||||
|
sudo apt-get "${APT_OPTS[@]}" remove -y "$s" || warn "couldn't remove the $s deb"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
xdg-settings set default-web-browser org.mozilla.firefox.desktop 2>/dev/null || true
|
xdg-settings set default-web-browser org.mozilla.firefox.desktop 2>/dev/null || true
|
||||||
|
|
||||||
@@ -228,7 +253,7 @@ if ! grep -q "$DATA_UUID" /etc/fstab; then
|
|||||||
fi
|
fi
|
||||||
sudo blkid -U "$DATA_UUID" >/dev/null || warn "Data drive ($DATA_UUID) not found — fstab entry is nofail, so boot is fine"
|
sudo blkid -U "$DATA_UUID" >/dev/null || warn "Data drive ($DATA_UUID) not found — fstab entry is nofail, so boot is fine"
|
||||||
|
|
||||||
if [[ ! -f $MARVIN_CREDS ]]; then
|
if [[ ! -f $MARVIN_CREDS && -z $UNATTENDED ]]; then
|
||||||
echo "server-marvin SMB share — press Enter to skip mounting it."
|
echo "server-marvin SMB share — press Enter to skip mounting it."
|
||||||
read -rp " username: " smb_user
|
read -rp " username: " smb_user
|
||||||
if [[ -n $smb_user ]]; then
|
if [[ -n $smb_user ]]; then
|
||||||
@@ -252,6 +277,12 @@ step "Small carry-overs from Bazzite"
|
|||||||
echo 'options bluetooth disable_ertm=1' | sudo tee /etc/modprobe.d/bluetooth-xbox.conf >/dev/null
|
echo 'options bluetooth disable_ertm=1' | sudo tee /etc/modprobe.d/bluetooth-xbox.conf >/dev/null
|
||||||
|
|
||||||
step "Done"
|
step "Done"
|
||||||
|
if [[ -n $UNATTENDED ]]; then
|
||||||
|
cat <<'EOF'
|
||||||
|
Log out and back in for the docker and kvm groups.
|
||||||
|
Then sign in to your apps, and add /mnt/data/SteamLibrary in Steam > Settings > Storage.
|
||||||
|
EOF
|
||||||
|
else
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Reboot now — the NVIDIA driver and your docker/kvm groups only take effect after that.
|
Reboot now — the NVIDIA driver and your docker/kvm groups only take effect after that.
|
||||||
|
|
||||||
@@ -262,3 +293,4 @@ Then, once:
|
|||||||
|
|
||||||
Then sign in to things, and add /mnt/data/SteamLibrary in Steam > Settings > Storage.
|
Then sign in to things, and add /mnt/data/SteamLibrary in Steam > Settings > Storage.
|
||||||
EOF
|
EOF
|
||||||
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user