Files and app logins move across by hand, so archiving and unpacking a home directory isn't this repo's job any more. setup.sh installs the machine and stops there. Two things restore.sh used to cover, kept where they still matter: the post-reboot `sudo flatpak update` that fetches the NVIDIA GL runtime (Flatpak can only match it to a loaded driver, so it can't happen during setup), and the notes on which dotfiles not to copy over from Fedora. The server-marvin mount is now optional — press Enter at the prompt to skip it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
265 lines
12 KiB
Bash
Executable File
265 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# 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,
|
|
# then run `sudo flatpak update` once so the Flatpak apps get the NVIDIA GL runtime.
|
|
set -euo pipefail
|
|
|
|
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
|
# Second NVMe (970 EVO Plus, label "Data"): SteamLibrary + emulator files. Never formatted.
|
|
DATA_UUID=29db4bd3-b743-417d-aa39-4da4acc9df3f
|
|
DATA_MNT=/mnt/data
|
|
# server-marvin SMB share, mounted if you give it credentials. \040 is fstab's escaped space.
|
|
MARVIN_SHARE='//192.168.1.193/Personal\040Files\040-\040ckoch'
|
|
MARVIN_MNT=/mnt/server-marvin-personal
|
|
MARVIN_CREDS=/etc/samba/credentials-marvin
|
|
|
|
step() { printf '\n\033[1;34m== %s ==\033[0m\n' "$*"; }
|
|
warn() { printf '\033[1;33mwarning:\033[0m %s\n' "$*" >&2; }
|
|
die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; }
|
|
apt_install() { sudo DEBIAN_FRONTEND=noninteractive apt-get install -y "$@"; }
|
|
|
|
[[ $EUID -ne 0 ]] || die "run as your user, not with sudo"
|
|
. /etc/os-release
|
|
[[ $ID == ubuntu ]] || die "expected Ubuntu, found $ID"
|
|
[[ $VERSION_ID == 26.04 ]] || warn "written for 26.04; this is $VERSION_ID — carrying on"
|
|
CODENAME=${UBUNTU_CODENAME:-$VERSION_CODENAME}
|
|
|
|
# Ask for the password once and keep sudo alive for the whole run (Flatpaks take a while).
|
|
sudo -v
|
|
while true; do sudo -n true; sleep 50; kill -0 "$$" 2>/dev/null || exit; done 2>/dev/null &
|
|
keepalive=$!
|
|
trap 'kill "$keepalive" 2>/dev/null' EXIT
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
step "Apt repositories"
|
|
sudo apt-get update
|
|
apt_install curl gpg ca-certificates software-properties-common
|
|
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 install -d -m 0755 /etc/apt/keyrings
|
|
|
|
# Docker CE — Docker's own repo, not the snap or Ubuntu's docker.io
|
|
if [[ ! -f /etc/apt/sources.list.d/docker.sources ]]; then
|
|
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
|
sudo chmod a+r /etc/apt/keyrings/docker.asc
|
|
sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null <<EOF
|
|
Types: deb
|
|
URIs: https://download.docker.com/linux/ubuntu
|
|
Suites: $CODENAME
|
|
Components: stable
|
|
Signed-By: /etc/apt/keyrings/docker.asc
|
|
EOF
|
|
fi
|
|
|
|
# NVIDIA Container Toolkit — GPU access inside Docker/Podman
|
|
if [[ ! -f /etc/apt/sources.list.d/nvidia-container-toolkit.list ]]; then
|
|
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \
|
|
| sudo gpg --dearmor --yes -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
|
|
curl -fsSL https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \
|
|
| sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \
|
|
| sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list >/dev/null
|
|
fi
|
|
|
|
# VS Code — Microsoft's repo (was rpm-ostree-layered on Bazzite)
|
|
if [[ ! -f /etc/apt/sources.list.d/vscode.sources ]]; then
|
|
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
|
|
| sudo gpg --dearmor --yes -o /usr/share/keyrings/microsoft.gpg
|
|
sudo tee /etc/apt/sources.list.d/vscode.sources >/dev/null <<EOF
|
|
Types: deb
|
|
URIs: https://packages.microsoft.com/repos/code
|
|
Suites: stable
|
|
Components: main
|
|
Architectures: amd64
|
|
Signed-By: /usr/share/keyrings/microsoft.gpg
|
|
EOF
|
|
fi
|
|
|
|
# GitHub CLI
|
|
if [[ ! -f /etc/apt/sources.list.d/github-cli.list ]]; then
|
|
sudo curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
|
-o /etc/apt/keyrings/githubcli-archive-keyring.gpg
|
|
sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg
|
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|
|
| sudo tee /etc/apt/sources.list.d/github-cli.list >/dev/null
|
|
fi
|
|
|
|
# Claude desktop — Anthropic's repo (ran inside an Ubuntu distrobox on Bazzite)
|
|
if [[ ! -f /etc/apt/sources.list.d/claude-desktop.list ]]; then
|
|
key=/usr/share/keyrings/claude-desktop-archive-keyring.asc
|
|
sudo curl -fsSLo "$key" https://downloads.claude.ai/claude-desktop/key.asc
|
|
if ! gpg --show-keys --with-colons "$key" 2>/dev/null \
|
|
| grep -q '^fpr:::::::::31DDDE24DDFAB679F42D7BD2BAA929FF1A7ECACE:'; then
|
|
sudo rm -f "$key"; die "Claude desktop signing key has the wrong fingerprint — not adding the repo"
|
|
fi
|
|
echo "deb [arch=amd64,arm64 signed-by=$key] https://downloads.claude.ai/claude-desktop/apt/stable stable main" \
|
|
| sudo tee /etc/apt/sources.list.d/claude-desktop.list >/dev/null
|
|
fi
|
|
|
|
sudo apt-get update
|
|
sudo DEBIAN_FRONTEND=noninteractive apt-get full-upgrade -y
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
step "Packages"
|
|
# steam-installer refuses to install non-interactively until its license is accepted.
|
|
echo 'steam-installer steam/question select I AGREE' | sudo debconf-set-selections
|
|
echo 'steam-installer steam/license note ' | sudo debconf-set-selections
|
|
|
|
apt_install \
|
|
build-essential cmake pkg-config git git-lfs wget jq zstd unzip p7zip-full rsync htop \
|
|
python3-venv python3-pip pipx ffmpeg cifs-utils flatpak \
|
|
steam-installer steam-devices gamemode mangohud lutris vulkan-tools \
|
|
docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin \
|
|
nvidia-container-toolkit \
|
|
code claude-desktop
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
step "NVIDIA driver (open kernel modules — the only kind the RTX 5090 supports)"
|
|
apt_install ubuntu-drivers-common
|
|
installed_driver() {
|
|
{ dpkg-query -W -f='${Status} ${Package}\n' 'nvidia-driver-*' 2>/dev/null || true; } \
|
|
| awk '/^install ok installed / {print $4}' | sort -V | tail -1
|
|
}
|
|
if [[ $(installed_driver) != *-open ]]; then
|
|
ubuntu-drivers devices 2>/dev/null | grep -E 'model|driver' || true
|
|
# Picks Ubuntu's recommended branch plus its prebuilt, signed kernel modules (no DKMS).
|
|
sudo ubuntu-drivers install
|
|
fi
|
|
driver=$(installed_driver)
|
|
[[ $driver == nvidia-driver-*-open ]] \
|
|
|| die "got '${driver:-no driver}', but the 5090 needs an -open driver — check 'ubuntu-drivers devices'"
|
|
branch=${driver#nvidia-driver-}; branch=${branch%-open}
|
|
apt_install "libnvidia-gl-$branch:i386" # 32-bit GL/Vulkan for Steam and Proton
|
|
echo "using $driver"
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
step "Docker, GPU containers, Claude's Cowork VM"
|
|
sudo usermod -aG docker "$USER" # docker without sudo (after reboot)
|
|
getent group kvm >/dev/null && sudo usermod -aG kvm "$USER" # Cowork needs /dev/kvm + vhost-vsock
|
|
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
|
|
sudo nvidia-ctk runtime configure --runtime=docker # enables `docker run --gpus all`
|
|
sudo systemctl restart docker
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
step "Flatpak apps"
|
|
sudo flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
|
mapfile -t apps < <(sed -e 's/#.*//' -e 's/[[:space:]]//g' -e '/^$/d' "$REPO/flatpaks.txt")
|
|
sudo flatpak install -y --noninteractive --system flathub "${apps[@]}"
|
|
# 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"
|
|
|
|
# Firefox/Thunderbird come from Flathub instead, so restoring ~/.var/app brings the old
|
|
# profiles back untouched. Drop Ubuntu's snaps and the debs that pull them back in.
|
|
for s in firefox thunderbird; do
|
|
if snap list "$s" >/dev/null 2>&1; then sudo snap remove --purge "$s"; fi
|
|
if dpkg -s "$s" >/dev/null 2>&1; then sudo apt-get remove -y "$s"; fi
|
|
done
|
|
xdg-settings set default-web-browser org.mozilla.firefox.desktop 2>/dev/null || true
|
|
|
|
# Keep Flatpaks updated daily, like Bazzite did. Starts next boot.
|
|
sudo tee /etc/systemd/system/flatpak-update.service >/dev/null <<'EOF'
|
|
[Unit]
|
|
Description=Update system Flatpaks
|
|
Wants=network-online.target
|
|
After=network-online.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/bin/flatpak update --system --noninteractive --assumeyes
|
|
EOF
|
|
sudo tee /etc/systemd/system/flatpak-update.timer >/dev/null <<'EOF'
|
|
[Unit]
|
|
Description=Update system Flatpaks daily
|
|
|
|
[Timer]
|
|
OnBootSec=10min
|
|
OnUnitActiveSec=1d
|
|
Persistent=true
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
EOF
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable flatpak-update.timer
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
step "Git credentials in the keyring"
|
|
# On Bazzite, KDE Wallet fed git your Gitea password through ksshaskpass. GNOME's keyring does
|
|
# the same job via git's libsecret helper, which Ubuntu ships as source only — so build it.
|
|
if ! git config --global --get credential.helper >/dev/null 2>&1; then
|
|
helper="" src=/usr/share/doc/git/contrib/credential/libsecret
|
|
if [[ -e $src/git-credential-libsecret.c || -e $src/git-credential-libsecret.c.gz ]]; then
|
|
apt_install libsecret-1-dev libglib2.0-dev
|
|
tmp=$(mktemp -d)
|
|
if [[ -e $src/git-credential-libsecret.c.gz ]]; then
|
|
zcat "$src/git-credential-libsecret.c.gz" > "$tmp/h.c"
|
|
else
|
|
cp "$src/git-credential-libsecret.c" "$tmp/h.c"
|
|
fi
|
|
read -ra cf < <(pkg-config --cflags libsecret-1 glib-2.0)
|
|
read -ra lf < <(pkg-config --libs libsecret-1 glib-2.0)
|
|
if cc -O2 "${cf[@]}" "$tmp/h.c" -o "$tmp/git-credential-libsecret" "${lf[@]}" 2>/dev/null; then
|
|
sudo install -m 755 "$tmp/git-credential-libsecret" /usr/local/bin/
|
|
helper=/usr/local/bin/git-credential-libsecret
|
|
fi
|
|
rm -rf "$tmp"
|
|
fi
|
|
if [[ -n $helper ]]; then
|
|
git config --global credential.helper "$helper"
|
|
else
|
|
git config --global credential.helper 'cache --timeout=604800'
|
|
warn "no keyring helper — git will hold your Gitea password for a week at a time instead"
|
|
fi
|
|
fi
|
|
|
|
step "Claude Code CLI"
|
|
# No Node or .NET on the host by choice — LudosData and landingPage build in Docker.
|
|
# If you want them back: sudo apt install mise dotnet-sdk-10.0 (mise needs ppa:jdxcode/mise).
|
|
command -v claude >/dev/null || [[ -x ~/.local/bin/claude ]] || curl -fsSL https://claude.ai/install.sh | bash
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
step "Data drive + server-marvin share"
|
|
sudo mkdir -p "$DATA_MNT" "$MARVIN_MNT"
|
|
if ! grep -q "$DATA_UUID" /etc/fstab; then
|
|
echo "UUID=$DATA_UUID $DATA_MNT ext4 defaults,nofail,x-gvfs-show,x-gvfs-name=Data 0 2" \
|
|
| sudo tee -a /etc/fstab >/dev/null
|
|
fi
|
|
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
|
|
echo "server-marvin SMB share — press Enter to skip mounting it."
|
|
read -rp " username: " smb_user
|
|
if [[ -n $smb_user ]]; then
|
|
read -rsp " password: " smb_pass; echo
|
|
sudo install -d -m 0755 /etc/samba
|
|
printf 'username=%s\npassword=%s\n' "$smb_user" "$smb_pass" \
|
|
| sudo install -m 600 /dev/stdin "$MARVIN_CREDS"
|
|
unset smb_pass
|
|
fi
|
|
fi
|
|
if [[ -f $MARVIN_CREDS ]] && ! grep -q "$MARVIN_MNT" /etc/fstab; then
|
|
echo "$MARVIN_SHARE $MARVIN_MNT cifs credentials=$MARVIN_CREDS,uid=$(id -u),gid=$(id -g),iocharset=utf8,vers=3.0,x-systemd.automount,_netdev,nofail 0 0" \
|
|
| sudo tee -a /etc/fstab >/dev/null
|
|
fi
|
|
sudo systemctl daemon-reload
|
|
sudo mount -a || warn "a mount failed — check 'sudo mount -a' (is server-marvin up? right password?)"
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
step "Small carry-overs from Bazzite"
|
|
# Xbox controllers over Bluetooth (Bazzite set this as a kernel argument)
|
|
echo 'options bluetooth disable_ertm=1' | sudo tee /etc/modprobe.d/bluetooth-xbox.conf >/dev/null
|
|
|
|
step "Done"
|
|
cat <<'EOF'
|
|
Reboot now — the NVIDIA driver and your docker/kvm groups only take effect after that.
|
|
|
|
Then, once:
|
|
sudo flatpak update # pulls the NVIDIA GL runtime the Flatpak apps need.
|
|
# Skip it and apps render in software until the daily
|
|
# timer catches up ~10 minutes after boot.
|
|
|
|
Then sign in to things, and add /mnt/data/SteamLibrary in Steam > Settings > Storage.
|
|
EOF
|