The previous substring check (*"Maple Mono NF"* glob / grep -q substring)
matched 'Maple Mono NF CN' too, which silently skipped the Latin-only NF
install on boxes that had only the CJK variant installed (byte, kaiser —
they shipped with maple-font system package that defaults to NF CN).
Result: foot (and any other config asking for 'Maple Mono NF') silently
fell back to system sans on byte and kaiser, while looking like a
working terminal. Compounded by foot.ini asking for 'Maple Mono CN'
(see prior commit c6779c5), which masked the missing NF install on
miche (which has both CN and NF).
Fix: use fc-list : family | grep -qxF 'Maple Mono NF' which:
- prints one family per line (no style suffixes to confuse grep)
- -x anchors whole line
- -F fixed string (no regex)
- matches exactly 'Maple Mono NF' and nothing else
Why not fc-match? fc-match reports a substitute when the family isn't
installed (Noto/Liberation/DejaVu depending on distro), so it can't
distinguish 'NF installed' from 'NF not installed, fell back'.
Verified on byte: fc-list : family | grep -qxF 'Maple Mono NF' returns
1 (false) because byte has only 'Maple Mono NF CN', so the install will
now run on next chezmoi apply.
293 lines
No EOL
12 KiB
Bash
293 lines
No EOL
12 KiB
Bash
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# run_once_20-install-user-packages-gentoo.sh.tmpl (gentoo-only)
|
|
# Install the user package set on gentoo via emerge.
|
|
#
|
|
# Equivalent to run_once_20-install-user-packages.sh.tmpl's arch/debian
|
|
# branches, but using portage. Key differences:
|
|
# - USE flags matter (e.g. eza has git feature flag, fzf has shell-completion)
|
|
# - Some packages are only in GURU overlay (eza, lazygit, topgrade, etc.)
|
|
# - emerge is slow (compiles from source); distcc handles remote builds
|
|
# - Bun isn't in any gentoo overlay; use the official curl install
|
|
#
|
|
# Runs after 00-install-bootstrap-tools.sh and 10-add-gentoo-overlays.sh.
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
|
|
# Make user-local bins (bun, omp, cargo) visible to the script even when
|
|
# invoked from a non-interactive context (e.g. `chezmoi apply` over SSH).
|
|
export PATH="/usr/bin:/bin:$HOME/.local/bin:$HOME/.bun/bin:$HOME/.cargo/bin:$PATH"
|
|
|
|
log() { printf '\033[1;34m[packages]\033[0m %s\n' "$*"; }
|
|
die() { printf '\033[1;31m[packages ERROR]\033[0m %s\n' "$*" >&2; exit 1; }
|
|
|
|
USER_HOME="${HOME:-$(eval echo "~$(whoami)")}"
|
|
ZSH_CUSTOM="${ZSH_CUSTOM:-$USER_HOME/.oh-my-zsh/custom}"
|
|
|
|
# ----------------------------- GENTOO ---------------------------------------
|
|
# On gentoo, `emerge` is the only PM (plus the GURU overlay for some
|
|
# packages). Many of the user packages live in GURU, so we run emerge with
|
|
# GURU enabled. Distcc is recommended for this box (2-core Ivy Bridge +
|
|
# MAKEOPTS=-j50 means it's relying on remote build hosts via DISTCC_HOSTS).
|
|
#
|
|
# Package selection mirrors the arch/debian script. Where a package is
|
|
# only available with USE flags, we set them in /etc/portage/package.use/
|
|
# before emerging. Where a package is ~amd64-only, we accept_keywords.
|
|
|
|
log "checking user packages (gentoo)"
|
|
|
|
# Step 1: USE flags + accept_keywords for packages that need them
|
|
# Some packages need explicit USE flags or keyword unmask. Set them in
|
|
# package.use/package.accept_keywords so emerge sees them.
|
|
# eza is at sys-apps/eza (not app-misc/eza — that's a common typo).
|
|
USE_DIR="/etc/portage/package.use"
|
|
ACCEPT_DIR="/etc/portage/package.accept_keywords"
|
|
mkdir -p "$USE_DIR" "$ACCEPT_DIR" 2>/dev/null || true
|
|
sudo mkdir -p "$USE_DIR" "$ACCEPT_DIR"
|
|
|
|
# eza: USE=git enables git status column
|
|
# Skip if already set
|
|
if ! grep -q "^sys-apps/eza" "$USE_DIR/zz-gentoo-bootstrap" 2>/dev/null; then
|
|
log "writing USE flags: sys-apps/eza git"
|
|
echo "sys-apps/eza git" | sudo tee "$USE_DIR/zz-gentoo-bootstrap" >/dev/null
|
|
fi
|
|
|
|
# fzf-tab, zsh-autosuggestions: gentoo has them in app-shells/ but newer
|
|
# versions may need unmask. Use the ones in the tree first; if too old,
|
|
# we'll install via oh-my-zsh custom plugins (gentoo's ebuilds can lag).
|
|
|
|
# Step 2: Define the package set
|
|
# Note: on Gentoo there's no `base-devel` meta-package. The toolchain
|
|
# (gcc, binutils, glibc, make, patch, etc.) is part of the @system set
|
|
# which is always installed. We only need to list user-space packages.
|
|
GENTOO_PKGS=(
|
|
app-shells/zsh
|
|
app-shells/zsh-completions
|
|
app-shells/zsh-syntax-highlighting
|
|
app-admin/tmux
|
|
app-editors/neovim
|
|
dev-vcs/git
|
|
app-text/bat
|
|
sys-process/btop
|
|
sys-process/htop
|
|
app-text/fastfetch
|
|
sys-apps/eza # in main, sys-apps category (not app-misc!)
|
|
app-shells/fzf
|
|
sys-apps/fd # in main, sys-apps category (not app-misc!)
|
|
sys-apps/ripgrep
|
|
app-shells/zoxide
|
|
app-shells/starship
|
|
# lazygit is in GURU (app-misc/lazygit)
|
|
# topgrade is in GURU (app-misc/topgrade)
|
|
# media-video/yt-dlp is in main
|
|
app-text/jq
|
|
app-arch/unzip
|
|
app-arch/p7zip
|
|
net-misc/openssh
|
|
# bun: NOT in gentoo (main or GURU). Install via official curl.
|
|
)
|
|
|
|
# Step 3: Determine what's missing
|
|
# Many gentoo packages don't ship a binary of the same name as the
|
|
# package (zsh-completions, zsh-syntax-highlighting install files into
|
|
# /usr/share/zsh, not /usr/bin). The simplest "is it installed?"
|
|
# check for our purposes is `qlist -I <pkg>` — returns 0 if the
|
|
# package is in the installed-db, 1 if not. But qlist is from
|
|
# gentoolkit and may not be available. Fall back to `equery`.
|
|
#
|
|
# A package may also be installed OUTSIDE portage (e.g. cargo install
|
|
# or a tarball extract that landed a binary at /usr/bin/<name>). In
|
|
# that case qlist reports missing but the binary exists. To avoid
|
|
# re-installing these, also check for the binary on PATH and skip
|
|
# the package if it's there.
|
|
MISSING_PKGS=()
|
|
for p in "${GENTOO_PKGS[@]}"; do
|
|
bin_name=$(basename "$p")
|
|
bin_on_path=0
|
|
command -v "$bin_name" >/dev/null 2>&1 && bin_on_path=1
|
|
|
|
if command -v qlist >/dev/null 2>&1; then
|
|
if qlist -I "$p" >/dev/null 2>&1; then
|
|
: # installed via portage
|
|
elif (( bin_on_path )); then
|
|
log "$p: not in portage, but $bin_name found on PATH (skipped)"
|
|
else
|
|
MISSING_PKGS+=("$p")
|
|
fi
|
|
elif command -v equery >/dev/null 2>&1; then
|
|
if equery -q list "$p" 2>/dev/null | grep -q "^$p"; then
|
|
: # installed via portage
|
|
elif (( bin_on_path )); then
|
|
log "$p: not in portage, but $bin_name found on PATH (skipped)"
|
|
else
|
|
MISSING_PKGS+=("$p")
|
|
fi
|
|
else
|
|
# Last resort: just check the binary on PATH
|
|
if ! (( bin_on_path )); then
|
|
MISSING_PKGS+=("$p")
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Step 4: Install
|
|
if (( ${#MISSING_PKGS[@]} > 0 )); then
|
|
log "emerge --newuse user packages (missing: ${MISSING_PKGS[*]})"
|
|
log "this may take a while (gentoo compiles from source; distcc helps)"
|
|
# --ask=n: no prompts
|
|
# --nospinner: cleaner output
|
|
# --quiet-build: only show errors during compile
|
|
# --keep-going: don't abort if one package fails
|
|
# --with-bdeps=y: include build deps so we don't accidentally skip them
|
|
# --autounmask=y --autounmask-write=y: auto-write ~amd64 keyword
|
|
# unmask files (lazygit is ~amd64 only). Without --autounmask-write,
|
|
# emerge only DISPLAYS the needed changes and aborts.
|
|
# --autounmask-license=y: auto-accept license unmask
|
|
# --autounmask-continue=y: auto-apply ~amd64 keyword unmask AND
|
|
# continue (instead of aborting after the unmask). Needed for
|
|
# GURU packages like dev-vcs/lazygit which are ~amd64-only.
|
|
# Without this, --autounmask-write will write the unmask file
|
|
# but emerge aborts because CONFIG_PROTECT marks the file as
|
|
# needing manual review.
|
|
sudo emerge --ask=n --nospinner --quiet-build --keep-going \
|
|
--with-bdeps=y --autounmask-continue=y --autounmask-license=y \
|
|
"${MISSING_PKGS[@]}"
|
|
else
|
|
log "all user packages already installed; skipping emerge"
|
|
fi
|
|
|
|
# Step 5: GURU-only packages
|
|
# Note: topgrade is NOT in any gentoo overlay (not main, not GURU).
|
|
# It's installed via `cargo install topgrade --locked` (handled by
|
|
# run_onchange_30-ensure-cargo.sh). The cargo binary is at
|
|
# ~/.cargo/bin which we put on PATH at the top of this script.
|
|
# lazygit IS in GURU but at dev-vcs/lazygit (not app-misc/lazygit).
|
|
GURU_PKGS=(
|
|
dev-vcs/lazygit
|
|
)
|
|
|
|
GURU_MISSING=()
|
|
for p in "${GURU_PKGS[@]}"; do
|
|
bin_name=$(basename "$p")
|
|
bin_on_path=0
|
|
command -v "$bin_name" >/dev/null 2>&1 && bin_on_path=1
|
|
|
|
if command -v qlist >/dev/null 2>&1; then
|
|
if qlist -I "$p" >/dev/null 2>&1; then
|
|
: # already in portage
|
|
elif (( bin_on_path )); then
|
|
log "$p: not in portage, but $bin_name found on PATH (skipped)"
|
|
else
|
|
GURU_MISSING+=("$p")
|
|
fi
|
|
else
|
|
if ! (( bin_on_path )); then
|
|
GURU_MISSING+=("$p")
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if (( ${#GURU_MISSING[@]} > 0 )); then
|
|
log "emerge GURU packages (missing: ${GURU_MISSING[*]})"
|
|
sudo emerge --ask=n --nospinner --quiet-build --keep-going \
|
|
--with-bdeps=y --autounmask-continue=y --autounmask-license=y \
|
|
"${GURU_MISSING[@]}"
|
|
fi
|
|
|
|
# Step 6: bun (no gentoo package) — install via official curl, fallback path
|
|
if ! command -v bun >/dev/null 2>&1; then
|
|
log "installing bun via official curl installer (no gentoo package)"
|
|
curl -fsSL https://bun.sh/install | BUN_INSTALL="$HOME/.local" bash
|
|
fi
|
|
|
|
# Post-install verification: bun must be on PATH
|
|
if ! command -v bun >/dev/null 2>&1; then
|
|
log "ERROR: bun install succeeded but bun is not on PATH"
|
|
log " check \$BUN_INSTALL/bin/bun exists and is in PATH"
|
|
exit 1
|
|
fi
|
|
BUN_VER=$(bun --version 2>/dev/null)
|
|
log "bun: $BUN_VER (from official bun.sh installer; gentoo has no package)"
|
|
|
|
# Step 6.5: Pi coding agent (oh-my-pi) — installs omp via bun global.
|
|
# Gentoo: bun is in ~/.local/bin (from the curl install above), not
|
|
# /usr/bin, so the package-based install paths in the universal
|
|
# script don't apply. We do this here.
|
|
if command -v bun >/dev/null 2>&1; then
|
|
if ! command -v omp >/dev/null 2>&1; then
|
|
log "installing @oh-my-pi/pi-coding-agent via bun global"
|
|
# Wrap in subshell so a segfault/timeout (e.g. Pi undervoltage
|
|
# killing the install mid-run, see pitfall #30) doesn't abort
|
|
# the whole bootstrap. omp can be retried manually later.
|
|
if (bun add -g @oh-my-pi/pi-coding-agent 2>&1 | tail -10); then
|
|
log "omp installed: $(omp --version 2>&1 | head -1)"
|
|
else
|
|
log "WARNING: bun add -g failed; omp not installed. Retry manually."
|
|
fi
|
|
else
|
|
log "omp already installed: $(omp --version 2>&1 | head -1)"
|
|
fi
|
|
fi
|
|
|
|
# Step 7: oh-my-zsh + plugins
|
|
if [[ ! -d "$USER_HOME/.oh-my-zsh" ]]; then
|
|
log "installing oh-my-zsh"
|
|
RUNZSH=no KEEP_ZSHRC=yes \
|
|
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
|
|
else
|
|
log "oh-my-zsh already installed"
|
|
fi
|
|
|
|
# Plugins: zsh-autosuggestions, zsh-syntax-highlighting, zsh-history-substring-search, fzf-tab
|
|
declare -A PLUGINS=(
|
|
[zsh-autosuggestions]="https://github.com/zsh-users/zsh-autosuggestions"
|
|
[zsh-syntax-highlighting]="https://github.com/zsh-users/zsh-syntax-highlighting"
|
|
[zsh-history-substring-search]="https://github.com/zsh-users/zsh-history-substring-search"
|
|
[fzf-tab]="https://github.com/Aloxaf/fzf-tab"
|
|
)
|
|
for plugin in "${!PLUGINS[@]}"; do
|
|
if [[ -d "$ZSH_CUSTOM/plugins/$plugin" ]]; then
|
|
log "plugin already present: $ZSH_CUSTOM/plugins/$plugin"
|
|
else
|
|
log "cloning plugin: $plugin"
|
|
git clone --depth=1 "${PLUGINS[$plugin]}" "$ZSH_CUSTOM/plugins/$plugin"
|
|
fi
|
|
done
|
|
|
|
# Step 8: tpm (tmux plugin manager)
|
|
if [[ ! -d "$USER_HOME/.tmux/plugins/tpm" ]]; then
|
|
log "installing tpm"
|
|
git clone https://github.com/tmux-plugins/tpm "$USER_HOME/.tmux/plugins/tpm"
|
|
else
|
|
log "tpm already installed"
|
|
fi
|
|
|
|
# Step 9: Maple Mono NF font
|
|
# Match EXACT family "Maple Mono NF", not "Maple Mono NF CN" or other variants.
|
|
# See run_once_20-install-user-packages.sh.tmpl for the rationale.
|
|
if ! fc-list : family 2>/dev/null | grep -qxF 'Maple Mono NF'; then
|
|
log "installing Maple Mono NF font"
|
|
# Download the latest release zip
|
|
TMPFONT=$(mktemp -d)
|
|
curl -fsSL "https://github.com/subframe7536/Maple-font/releases/latest/download/MapleMono-NF.zip" \
|
|
-o "$TMPFONT/MapleMono-NF.zip"
|
|
mkdir -p "$USER_HOME/.local/share/fonts"
|
|
unzip -qo "$TMPFONT/MapleMono-NF.zip" -d "$USER_HOME/.local/share/fonts"
|
|
fc-cache -fv >/dev/null 2>&1
|
|
rm -rf "$TMPFONT"
|
|
else
|
|
log "Maple Mono NF already installed"
|
|
fi
|
|
|
|
# Step 10: zsh as login shell
|
|
if [[ "$(getent passwd rain | cut -d: -f7)" != "/usr/bin/zsh" ]]; then
|
|
log "changing default shell to /usr/bin/zsh"
|
|
sudo chsh -s /usr/bin/zsh rain
|
|
else
|
|
log "zsh already the login shell"
|
|
fi
|
|
|
|
log "all user packages installed"
|
|
zsh --version
|
|
nvim --version | head -1
|
|
tmux -V |