#!/bin/bash set -euo pipefail # Debian Trixie -> Niri Bootstrap Script # https://bootstrap.mbriggs.dev # # Compiled: 2026-01-21 (aa09ea9) # # Run with: curl -fsSL https://bootstrap.mbriggs.dev | sh # Dry run: curl -fsSL https://bootstrap.mbriggs.dev | sh -s -- --dry-run # --- BEGIN BOOTSTRAP --- # Parse arguments DRY_RUN=false # First pass: extract flags for arg in "$@"; do case "$arg" in --dry-run|-n) DRY_RUN=true ;; esac done # Second pass: collect positional args (non-flags) args=() for arg in "$@"; do case "$arg" in --dry-run|-n) ;; *) args+=("$arg") ;; esac done # Set defaults, then override from positional args USERNAME="$(whoami)" GRUB_RESOLUTION="1024x768" if [[ ${#args[@]} -ge 1 ]]; then USERNAME="${args[0]}" fi if [[ ${#args[@]} -ge 2 ]]; then GRUB_RESOLUTION="${args[1]}" fi # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log() { echo -e "${BLUE}==>${NC} $*"; } log_ok() { echo -e "${GREEN}==>${NC} $*"; } log_warn() { echo -e "${YELLOW}==>${NC} $*"; } log_error() { echo -e "${RED}==>${NC} $*" >&2; } # Run command wrapper - respects DRY_RUN run() { if $DRY_RUN; then echo " [dry-run] $*" else "$@" fi } # Sudo wrapper - respects DRY_RUN run_sudo() { if $DRY_RUN; then echo " [dry-run] sudo $*" else sudo "$@" fi } # Error trap for debugging (only in real mode) if ! $DRY_RUN; then trap 'echo ""; echo "ERROR: Bootstrap failed at line $LINENO"; exit 1' ERR fi # niri-update URL NIRI_UPDATE_URL="https://bootstrap.mbriggs.dev/niri-update" echo "" if $DRY_RUN; then log_warn "DRY RUN MODE - No changes will be made" echo "" fi log "Setting up niri for user: $USERNAME" log "GRUB resolution: $GRUB_RESOLUTION" echo "" # --- Brave Browser --- if [ -f /etc/apt/sources.list.d/brave-browser-release.list ]; then log "Brave repository already configured, skipping..." else log "Adding Brave repository..." if $DRY_RUN; then echo " [dry-run] Download brave-browser-archive-keyring.gpg" echo " [dry-run] Create /etc/apt/sources.list.d/brave-browser-release.list" else curl -fsSL https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg | \ sudo tee /usr/share/keyrings/brave-browser-archive-keyring.gpg > /dev/null echo "deb [arch=amd64 signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg] https://brave-browser-apt-release.s3.brave.com/ stable main" | \ sudo tee /etc/apt/sources.list.d/brave-browser-release.list > /dev/null fi fi # --- 1Password --- if [ -f /etc/apt/sources.list.d/1password.list ]; then log "1Password repository already configured, skipping..." else log "Adding 1Password repository..." if $DRY_RUN; then echo " [dry-run] Download 1password GPG keys" echo " [dry-run] Create /etc/apt/sources.list.d/1password.list" echo " [dry-run] Create debsig policies" else curl -fsSL https://downloads.1password.com/linux/keys/1password.asc | \ sudo gpg --batch --yes --dearmor -o /usr/share/keyrings/1password-archive-keyring.gpg echo "deb [arch=amd64 signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/amd64 stable main" | \ sudo tee /etc/apt/sources.list.d/1password.list > /dev/null sudo mkdir -p /etc/debsig/policies/AC2D62742012EA22/ curl -fsSL https://downloads.1password.com/linux/debian/debsig/1password.pol | \ sudo tee /etc/debsig/policies/AC2D62742012EA22/1password.pol > /dev/null sudo mkdir -p /usr/share/debsig/keyrings/AC2D62742012EA22 curl -fsSL https://downloads.1password.com/linux/keys/1password.asc | \ sudo gpg --batch --yes --dearmor -o /usr/share/debsig/keyrings/AC2D62742012EA22/debsig.gpg fi fi # --- System packages --- log "Installing system packages..." run_sudo apt update run_sudo apt install -y \ brave-browser \ 1password \ rustup \ gcc clang pkg-config \ libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libxcb-cursor-dev \ libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev \ libseat-dev libpipewire-0.3-dev libpango1.0-dev libdisplay-info-dev \ alacritty fuzzel pipewire wireplumber \ xdg-desktop-portal-gtk fonts-noto polkitd \ git nautilus \ iwd resolvconf # --- Rust setup --- log "Setting up Rust..." export PATH="$HOME/.cargo/bin:$PATH" run rustup default stable run cargo install --locked cargo-deb # --- Build and install niri --- if command -v niri &>/dev/null && ! $DRY_RUN; then log "niri already installed, skipping..." else log "Downloading niri-update..." if $DRY_RUN; then echo " [dry-run] curl -fsSL $NIRI_UPDATE_URL -o /tmp/niri-update" echo " [dry-run] /tmp/niri-update install -y" echo " [dry-run] -> Clone/fetch niri repository" echo " [dry-run] -> Checkout latest tag" echo " [dry-run] -> Build .deb package" echo " [dry-run] -> Install via dpkg" else curl -fsSL "$NIRI_UPDATE_URL" -o /tmp/niri-update chmod +x /tmp/niri-update log "Building and installing niri..." /tmp/niri-update install -y rm -f /tmp/niri-update fi fi # --- GRUB configuration --- log "Configuring GRUB resolution..." if $DRY_RUN; then echo " [dry-run] Set GRUB_GFXMODE=$GRUB_RESOLUTION" echo " [dry-run] update-grub" else if grep -q "^GRUB_GFXMODE=" /etc/default/grub; then sudo sed -i "s/^GRUB_GFXMODE=.*/GRUB_GFXMODE=$GRUB_RESOLUTION/" /etc/default/grub else echo "GRUB_GFXMODE=$GRUB_RESOLUTION" | sudo tee -a /etc/default/grub > /dev/null fi sudo update-grub fi # --- Auto-login on tty1 --- log "Setting up auto-login for $USERNAME on tty1..." if $DRY_RUN; then echo " [dry-run] Create /etc/systemd/system/getty@tty1.service.d/autologin.conf" else sudo mkdir -p /etc/systemd/system/getty@tty1.service.d sudo tee /etc/systemd/system/getty@tty1.service.d/autologin.conf > /dev/null << EOF [Service] ExecStart= ExecStart=-/sbin/agetty --autologin $USERNAME --noclear %I \$TERM EOF fi # --- Add user to required groups --- log "Adding $USERNAME to video, input, render groups..." run_sudo usermod -aG video,input,render "$USERNAME" # --- Set systemd default target --- log "Setting default target to multi-user..." run_sudo systemctl set-default multi-user.target # --- WiFi with iwd --- log "Configuring iwd for WiFi..." if $DRY_RUN; then echo " [dry-run] Create /etc/iwd/main.conf" echo " [dry-run] Create /etc/NetworkManager/conf.d/iwd.conf" else sudo mkdir -p /etc/iwd sudo tee /etc/iwd/main.conf > /dev/null << 'EOF' [General] EnableNetworkConfiguration=true [Network] NameResolvingService=resolvconf EOF sudo mkdir -p /etc/NetworkManager/conf.d sudo tee /etc/NetworkManager/conf.d/iwd.conf > /dev/null << 'EOF' [device] wifi.backend=iwd EOF fi # Prompt for WiFi credentials (skip in dry-run) if $DRY_RUN; then echo "" log "WiFi Setup (skipped in dry-run)" echo " [dry-run] Would prompt for SSID and password" echo " [dry-run] Would create /var/lib/iwd/SSID.psk" echo " [dry-run] Would switch from wpa_supplicant to iwd" else echo "" log "WiFi Setup" read -rp " WiFi network name (SSID): " WIFI_SSID read -rp " WiFi password: " WIFI_PASSWORD if [ -n "$WIFI_SSID" ] && [ -n "$WIFI_PASSWORD" ]; then sudo mkdir -p /var/lib/iwd sudo tee "/var/lib/iwd/${WIFI_SSID}.psk" > /dev/null << EOF [Security] Passphrase=${WIFI_PASSWORD} EOF sudo chmod 600 "/var/lib/iwd/${WIFI_SSID}.psk" log "Cleaning up /etc/network/interfaces..." sudo cp /etc/network/interfaces /etc/network/interfaces.bak 2>/dev/null || true sudo tee /etc/network/interfaces > /dev/null << 'INTERFACES' # This file describes the network interfaces available on your system # and how to activate them. For more information, see interfaces(5). source /etc/network/interfaces.d/* # The loopback network interface auto lo iface lo inet loopback INTERFACES log "Switching to iwd..." sudo systemctl disable --now wpa_supplicant 2>/dev/null || true sudo systemctl enable --now iwd log "Waiting for WiFi connection..." sleep 3 if ping -c 1 -W 5 8.8.8.8 &>/dev/null; then log_ok "WiFi connected successfully!" else log_warn "WiFi may not be connected. Check with 'iwctl station wlan0 show'" fi echo "" echo " WiFi config stored in: /var/lib/iwd/${WIFI_SSID}.psk" echo " To fix SSID/password: sudo vi \"/var/lib/iwd/${WIFI_SSID}.psk\"" echo " To connect to a different network: iwctl" else log "Skipping WiFi setup (no credentials provided)" echo " You can configure WiFi later with: iwctl" fi fi echo "" if $DRY_RUN; then log_ok "Dry run complete - no changes were made" else log_ok "Setup complete!" fi echo "" echo "To start niri manually after reboot:" echo " niri-session" echo "" echo "To manage niri updates (after installing dotfiles):" echo " niri-update check # Check for updates" echo " niri-update install # Install latest" echo " niri-update rollback # Rollback to previous" echo "" if ! $DRY_RUN; then echo "Reboot to apply changes." fi