#!/bin/sh # bootstrap.sh — take a fresh minimal Arch install (only the 'base' # meta-package installed) to the point where `just init` has run, the # dotfiles are deployed, and recommended services are enabled. # # Prerequisites (from the Arch installation guide): # - A regular user already exists and is a member of the 'wheel' group. # - You are logged in as that user (paru/makepkg refuse to run as root). # # Usage: # curl -fsSL https://raw.githubusercontent.com/sommerfelddev/dotfiles/master/bootstrap.sh | sh # # Overrides: # DOTFILES_REPO (default: https://github.com/sommerfelddev/dotfiles.git) # DOTFILES_DIR (default: $HOME/dotfiles) set -eu log() { printf '\033[1;34m==>\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m==>\033[0m %s\n' "$*" >&2; } die() { printf '\033[1;31m==>\033[0m %s\n' "$*" >&2 exit 1 } # 0. refuse root — paru/makepkg won't run as root [ "$(id -u)" -ne 0 ] || die "run this as your regular user, not root" # 1. user must be in wheel (required so the sudoers rule we enable takes effect) id -nG "$USER" | tr ' ' '\n' | grep -qx wheel || die "user '$USER' must be in the 'wheel' group" # 2. install sudo + pacman prerequisites, enable wheel in sudoers. # If sudo is absent we do this in a single su -c so the root password # is entered only once. If sudo is already there, reuse it. PREREQS='sudo git base-devel chezmoi just efibootmgr nix' SUDOERS_SED='s/^# *\(%wheel ALL=(ALL:ALL\(:ALL\)*) ALL\)/\1/' if ! command -v sudo >/dev/null 2>&1; then log 'installing prerequisites (prompting for root password)' su -c "pacman -Syu --needed --noconfirm ${PREREQS} && \ sed -i '${SUDOERS_SED}' /etc/sudoers" else log 'installing prerequisites' # shellcheck disable=SC2086 # PREREQS is an intentional word list sudo pacman -Syu --needed --noconfirm ${PREREQS} sudo sed -i "${SUDOERS_SED}" /etc/sudoers fi # 3. bootstrap paru-bin from AUR if missing if ! command -v paru >/dev/null 2>&1; then log 'building paru-bin from AUR' tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT git clone --depth=1 https://aur.archlinux.org/paru-bin.git "$tmp/paru-bin" (cd "$tmp/paru-bin" && makepkg -si --noconfirm) fi # 4. clone dotfiles DOTFILES_DIR="${DOTFILES_DIR:-$HOME/dotfiles}" REPO_URL="${DOTFILES_REPO:-https://github.com/sommerfelddev/dotfiles.git}" if [ ! -d "$DOTFILES_DIR/.git" ]; then log "cloning $REPO_URL -> $DOTFILES_DIR" git clone "$REPO_URL" "$DOTFILES_DIR" else log "using existing clone at $DOTFILES_DIR" fi # 5. run just init — this deploys chezmoi, installs the 'base' meta list # (which pulls in sudo-rs), deploys /etc/sudoers-rs, /etc/pam.d/sudo, # creates /usr/local/bin/{sudo,su,visudo,sudoedit} symlinks pointing # at sudo-rs (PATH precedence shadows /usr/bin/sudo), and installs # git hooks. The classic 'sudo' package stays installed because # base-devel hard-depends on it; that's harmless — the binary is # never invoked once /usr/local/bin/sudo is in place. `just init` # also runs `just nix-switch` to apply the Home-Manager profile; # nix itself is part of PREREQS above (pacman package), and # nix-daemon.socket is enabled by `unit-apply` via # systemd-units/system.txt. cd "$DOTFILES_DIR" # Source the nix profile so `nix` is on PATH for the rest of this # script (pacman drops /etc/profile.d/nix.sh but the current shell # didn't read it). Multi-user (daemon mode) and per-user variants exist; # pacman ships the multi-user one. for f in /etc/profile.d/nix.sh /etc/profile.d/nix-daemon.sh; do if [ -r "$f" ]; then # shellcheck disable=SC1090 . "$f" break fi done log 'running just init' just init # 5b. chsh to nix-store zsh (provisioned by home-manager via nix/common.nix) NIX_ZSH="$HOME/.nix-profile/bin/zsh" if [ -x "$NIX_ZSH" ]; then if ! grep -qxF "$NIX_ZSH" /etc/shells 2>/dev/null; then log "appending $NIX_ZSH to /etc/shells" echo "$NIX_ZSH" | sudo tee -a /etc/shells >/dev/null fi current_shell="$(getent passwd "$USER" | cut -d: -f7)" if [ "$current_shell" != "$NIX_ZSH" ]; then log "changing login shell to $NIX_ZSH" sudo chsh -s "$NIX_ZSH" "$USER" fi fi # 6. refresh pacman mirrorlist once via reflector (config deployed by chezmoi) log 'refreshing pacman mirrorlist via reflector' sudo reflector @/etc/xdg/reflector/reflector.conf \ --save /etc/pacman.d/mirrorlist || warn 'reflector failed; keeping existing mirrorlist' # 7. create XDG user directories (~/Documents, ~/Downloads, etc.) if command -v xdg-user-dirs-update >/dev/null 2>&1; then log 'creating XDG user directories' xdg-user-dirs-update || warn 'xdg-user-dirs-update failed' fi # 8. optional: create an Arch EFI boot entry if none exists if [ -d /sys/firmware/efi ]; then if ! sudo efibootmgr 2>/dev/null | grep -iq arch; then warn 'no Arch Linux EFI boot entry found' warn 'after first kernel install, run: sudo mkinitcpio -P' warn 'then register the UKI with efibootmgr, for example:' # shellcheck disable=SC1003 # backslash is literal text shown to the user warn ' sudo efibootmgr --create --disk /dev/nvme0n1 --part 1 \' warn " --label 'Arch UKI' --loader '\\EFI\\Linux\\arch-linux.efi'" # shellcheck disable=SC1003 warn ' sudo efibootmgr --create --disk /dev/nvme0n1 --part 1 \' warn " --label 'Arch UKI Fallback' --loader '\\EFI\\Linux\\arch-linux-fallback.efi'" warn 'optionally, also register the linux-hardened UKIs (opt-in boot):' # shellcheck disable=SC1003 warn ' sudo efibootmgr --create --disk /dev/nvme0n1 --part 1 \' warn " --label 'Arch Hardened' --loader '\\EFI\\Linux\\arch-linux-hardened.efi'" # shellcheck disable=SC1003 warn ' sudo efibootmgr --create --disk /dev/nvme0n1 --part 1 \' warn " --label 'Arch Hardened Fallback' --loader '\\EFI\\Linux\\arch-linux-hardened-fallback.efi'" fi fi log 'done. Log out and back in (or reboot) to pick up shell and group changes.'