aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/bootstrap.sh
blob: 1d3783b99f06c225de42a8593419775be49e8a4d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/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'
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` (step 5b below); the nix
#    install needs to happen before that.
cd "$DOTFILES_DIR"

# 5a. install nix (Determinate Systems installer, multi-user) before
#     `just init`, so `just nix-switch` finds it.
if ! command -v nix >/dev/null 2>&1; then
  log 'installing nix (Determinate Systems multi-user installer)'
  curl --proto '=https' --tlsv1.2 -sSf -L \
    https://install.determinate.systems/nix |
    sh -s -- install linux --no-confirm
  # Source nix env for the rest of this script (installer writes
  # /etc/profile.d/nix.sh but the current shell hasn't sourced it).
  if [ -f /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh ]; then
    # shellcheck disable=SC1091
    . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
  fi
fi

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'"
  fi
fi

log 'done. Log out and back in (or reboot) to pick up shell and group changes.'