Bazzite answered git's password prompts from KDE Wallet via ksshaskpass, which Ubuntu won't have. Build git's libsecret helper (Ubuntu ships it as source only) and point credential.helper at it, falling back to a week-long credential cache if the build isn't possible. Note in the README that the first Gitea push has to re-enter the password, since keyrings aren't in the backup. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
256 lines
12 KiB
Bash
Executable File
256 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 scripts/restore.sh.
|
|
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 — the backup lives here. \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
|
|
read -rp "server-marvin SMB username: " smb_user
|
|
read -rsp "server-marvin SMB 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
|
|
if ! 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 (NVIDIA driver + docker/kvm groups take effect), then:
|
|
scripts/restore.sh
|
|
EOF
|