summaryrefslogtreecommitdiff
path: root/install.sh
blob: 435135751b1ab7169d9d464b7ddf226b156a3cb8 (plain)
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/bin/bash

# Set up this account on a machine: the tools, checkouts and nvim plugins that
# the config link.sh links expects. Run it by hand, after `./link.sh yes`, once
# per machine and again whenever the Brewfile changes; every step only adds
# what is missing. Unlike link.sh it is slow and may stop to ask for a password,
# so nothing runs it on a timer.
#
#   ~/src/soarez/dot/install.sh
#
# On a new Mac, also run ./macos-defaults.sh, once -- see its header. A private
# layer's bootstrap may call both for you.
#
# Runs under macOS's /bin/bash 3.2.

set -euo pipefail

dotfiles_dir="$(cd "$(dirname "$0")" && pwd)"

# 1. Homebrew packages, on macOS. Linux machines get their packages from
#    whatever provisions them, not from here.
if [[ "$(uname)" == "Darwin" ]]; then
  if ! command -v brew >/dev/null 2>&1; then
    for b in /opt/homebrew/bin/brew /usr/local/bin/brew; do
      if [[ -x "${b}" ]]; then
        eval "$("${b}" shellenv)"
        break
      fi
    done
  fi
  # Install Homebrew rather than print the command and stop: on a machine whose
  # whole setup is this repo there is nothing else to run it, and everything
  # below depends on it. NONINTERACTIVE so it does not wait on a RETURN it will
  # never see; it still asks for the sudo password, which is why this script is
  # run by hand and not on a timer.
  if ! command -v brew >/dev/null 2>&1; then
    echo "Homebrew is not installed; installing it now"
    NONINTERACTIVE=1 /bin/bash -c \
      "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    for b in /opt/homebrew/bin/brew /usr/local/bin/brew; do
      if [[ -x "${b}" ]]; then
        eval "$("${b}" shellenv)"
        break
      fi
    done
  fi
  if ! command -v brew >/dev/null 2>&1; then
    >&2 echo "ERROR: Homebrew still not on PATH after installing it. Install it"
    >&2 echo "by hand, then re-run this script:"
    # shellcheck disable=SC2016  # printed for a human to paste, not expanded
    >&2 echo '  /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
    exit 1
  fi
  # Two casks in the Brewfile come from their authors' own taps. Homebrew gates
  # unofficial taps behind `brew trust`, recorded per machine in
  # $XDG_CONFIG_HOME/homebrew/trust.json, so it cannot live in the Brewfile.
  # Measured on 2026-10-01 rather than assumed: the gate refused an untrusted
  # *formula* outright, while these two casks installed through brew bundle
  # without it and only quickmd ended up recorded. So this is a precaution that
  # keeps working if the gate tightens, not a step the install depends on
  # today. Naming the casks rather than the taps keeps the trust to these two
  # files and not whatever the taps add later. Older Homebrews have no `trust`
  # subcommand and need nothing, hence the guard.
  if brew help trust >/dev/null 2>&1; then
    for untrusted_cask in satrik/togglemute/togglemute b451c/quickmd/quickmd; do
      brew trust --cask "${untrusted_cask}" >/dev/null || \
        >&2 echo "WARNING: could not trust ${untrusted_cask}; brew bundle will skip it"
    done
  fi

  # --no-upgrade: install what is missing and leave what is there alone. Plain
  # `brew bundle` also upgrades every listed package that is out of date.
  #
  # Not fatal: brew bundle fails the whole run if a single entry will not
  # install, and on a managed machine that is routine -- a cask can be blocked
  # by policy ("has been disabled because it does not pass the macOS Gatekeeper
  # check"). One unavailable app is no reason to leave the checkouts, nvim
  # plugins and colours undone, so this warns and carries on. brew has already
  # said which entry failed, right above.
  if ! brew bundle --no-upgrade --file="${dotfiles_dir}/Brewfile"; then
    >&2 echo
    >&2 echo "WARNING: some Brewfile entries did not install (see brew's output"
    >&2 echo "above). Continuing with the rest of the setup."
    >&2 echo
  fi

  # toggleMute will not open while the download carries com.apple.quarantine,
  # and the author's install instructions say to clear it. What that costs is
  # worth stating exactly, because it is worse than "not notarized" (checked
  # 2026-10-01): the app is ad-hoc signed, TeamIdentifier is not set, and
  # `spctl -a` rejects it with "invalid Info.plist (plist or signature have
  # been modified)". There is no verifiable publisher, and macOS considers the
  # bundle altered since it was signed. Clearing quarantine is the only way to
  # run it, so this is a deliberate exception for one app -- hence the explicit
  # path rather than a loop over /Applications, and only that one attribute
  # rather than the `xattr -cr` the README asks for, which would strip every
  # extended attribute the bundle has.
  #
  # QuickMD needs none of this: it is signed with a Developer ID and notarized,
  # so its quarantine flag costs one dialog and nothing more.
  togglemute_app="/Applications/toggleMute.app"
  if [[ -d "${togglemute_app}" ]] &&
     xattr "${togglemute_app}" 2>/dev/null | grep -q com.apple.quarantine
  then
    echo "INFO: clearing the quarantine flag on ${togglemute_app}"
    xattr -dr com.apple.quarantine "${togglemute_app}"
  fi
fi

# 2. Checkouts the config reads: the pure prompt (config/zshrc falls back to a
#    plain one without it) and the extra colour schemes bin/theme lists. Over
#    https: both are public, so a fresh machine needs no ssh key for them.
#    Existing checkouts are left alone.
clone() {
  local url="$1" dest="$2"
  if [[ -d "${dest}/.git" ]]; then
    return
  fi
  mkdir -p "$(dirname "${dest}")"
  git clone --quiet "${url}" "${dest}"
  echo "cloned ${dest}"
}
clone https://github.com/sindresorhus/pure.git         "${HOME}/src/sindresorhus/pure"
clone https://github.com/alacritty/alacritty-theme.git "${HOME}/src/alacritty/alacritty-theme"

# 3. nvim's plugins, so the first real launch does not stop to install them.
#    `install`, not `sync`: install only fetches plugins that are missing, and
#    leaves installed ones where this host's lock file (~/.config/nvim/
#    lazy-lock.json, not in the repo) has them. sync would also update every
#    plugin to its latest version and rewrite that lock file.
if command -v nvim >/dev/null 2>&1; then
  nvim --headless "+Lazy! install" +qa || true
fi

# 4. The colours the terminals, tmux, nvim and bat read. bin/theme writes those
#    files under $XDG_DATA_HOME; until it has run once they are absent, which
#    every config tolerates -- nothing is themed, that is all. Seeded here only
#    when this machine has none, with whatever mode and colourscheme `theme`
#    reports (its own defaults on a fresh machine), so re-running this script
#    never disturbs a machine whose theme is already set.
theme_bin="${dotfiles_dir}/bin/theme"
if [[ ! -e "${XDG_DATA_HOME:-${HOME}/.local/share}/tmux/color.conf" && -x "${theme_bin}" ]]; then
  "${theme_bin}" --set "$("${theme_bin}" --mode)" "$("${theme_bin}" --colors)" || true
fi

if [[ "$(uname)" == "Darwin" ]]; then
  echo
  echo "Setting up a new Mac? Also run this, once:"
  echo "  ${dotfiles_dir}/macos-defaults.sh"
  echo "It applies the baseline Finder, Dock and keyboard settings, and resets the Dock."
fi