initial commit
This commit is contained in:
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stage 00 — sanity checks, mirror refresh, pacman quality-of-life.
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
||||
|
||||
log "Preflight"
|
||||
|
||||
require_arch
|
||||
require_not_root
|
||||
require_sudo
|
||||
|
||||
step "checking network connectivity"
|
||||
if ! ping -c1 -W3 archlinux.org >/dev/null 2>&1; then
|
||||
warn "cannot reach archlinux.org — package installation will fail"
|
||||
confirm "continue anyway?" || die "aborted"
|
||||
else
|
||||
ok "network reachable"
|
||||
fi
|
||||
|
||||
# --- pacman.conf niceties --------------------------------------------------
|
||||
step "enabling pacman colour output + parallel downloads"
|
||||
if [[ "$DRY_RUN" != "1" ]]; then
|
||||
sudo sed -i \
|
||||
-e 's/^#\(Color\)$/\1/' \
|
||||
-e 's/^#\(VerbosePkgLists\)$/\1/' \
|
||||
-e 's/^#\?ParallelDownloads.*/ParallelDownloads = 8/' \
|
||||
/etc/pacman.conf
|
||||
grep -q '^ParallelDownloads' /etc/pacman.conf || \
|
||||
sudo sed -i '/^\[options\]/a ParallelDownloads = 8' /etc/pacman.conf
|
||||
fi
|
||||
ok "pacman.conf tuned"
|
||||
|
||||
# --- multilib (optional, off by default) ----------------------------------
|
||||
if [[ "${ENABLE_MULTILIB:-0}" == "1" ]]; then
|
||||
step "enabling [multilib]"
|
||||
run sudo sed -i '/^#\[multilib\]$/,+1 s/^#//' /etc/pacman.conf
|
||||
fi
|
||||
|
||||
# --- mirrors ---------------------------------------------------------------
|
||||
if have reflector; then
|
||||
step "refreshing mirrorlist with reflector (this can take a minute)"
|
||||
run sudo reflector --protocol https --latest 20 --sort rate \
|
||||
--save /etc/pacman.d/mirrorlist || warn "reflector failed; keeping existing mirrorlist"
|
||||
else
|
||||
skip "reflector not installed yet — mirrors will be refreshed on the next run"
|
||||
fi
|
||||
|
||||
step "synchronising package databases"
|
||||
run sudo pacman -Syu --noconfirm
|
||||
|
||||
ok "preflight complete"
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stage 10 — install everything available from the official repositories.
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
||||
|
||||
log "Installing official packages"
|
||||
|
||||
require_arch
|
||||
require_not_root
|
||||
require_sudo
|
||||
|
||||
mapfile -t PKGS < <(read_pkg_list "$PACKAGES_DIR/pacman.txt")
|
||||
[[ ${#PKGS[@]} -gt 0 ]] || die "pacman.txt yielded no packages"
|
||||
|
||||
# Split into "already installed" and "to install" so re-runs are quiet.
|
||||
TO_INSTALL=()
|
||||
for p in "${PKGS[@]}"; do
|
||||
if pacman -Qq "$p" >/dev/null 2>&1; then
|
||||
continue
|
||||
fi
|
||||
TO_INSTALL+=("$p")
|
||||
done
|
||||
|
||||
if [[ ${#TO_INSTALL[@]} -eq 0 ]]; then
|
||||
ok "all ${#PKGS[@]} packages already installed"
|
||||
else
|
||||
step "installing ${#TO_INSTALL[@]} of ${#PKGS[@]} packages"
|
||||
printf '%s %s%s\n' "$C_DIM" "${TO_INSTALL[*]}" "$C_RESET"
|
||||
# --needed keeps reinstalls off the table; failures on a single package
|
||||
# should not nuke the whole run, so retry one-by-one if the batch fails.
|
||||
if ! run sudo pacman -S --needed --noconfirm "${TO_INSTALL[@]}"; then
|
||||
warn "batch install failed — retrying package by package"
|
||||
for p in "${TO_INSTALL[@]}"; do
|
||||
run sudo pacman -S --needed --noconfirm "$p" || warn "could not install: $p"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- GPU driver hint -------------------------------------------------------
|
||||
if lspci 2>/dev/null | grep -qi 'vga.*intel'; then
|
||||
step "Intel GPU detected — installing vulkan-intel"
|
||||
run sudo pacman -S --needed --noconfirm vulkan-intel intel-media-driver || true
|
||||
elif lspci 2>/dev/null | grep -qi 'vga.*amd\|vga.*ati'; then
|
||||
step "AMD GPU detected — installing vulkan-radeon"
|
||||
run sudo pacman -S --needed --noconfirm vulkan-radeon libva-mesa-driver || true
|
||||
elif lspci 2>/dev/null | grep -qi 'vga.*nvidia'; then
|
||||
warn "NVIDIA GPU detected — install 'nvidia' or 'nvidia-open' yourself; see README"
|
||||
fi
|
||||
|
||||
ok "official packages done"
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stage 20 — bootstrap the yay AUR helper and install AUR packages.
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
||||
|
||||
log "AUR packages"
|
||||
|
||||
require_arch
|
||||
require_not_root
|
||||
require_sudo
|
||||
|
||||
# --- yay -------------------------------------------------------------------
|
||||
if have yay; then
|
||||
ok "yay already installed"
|
||||
else
|
||||
step "building yay-bin from the AUR"
|
||||
build_dir="$(mktemp -d)"
|
||||
if [[ "$DRY_RUN" == "1" ]]; then
|
||||
skip "would clone + makepkg yay-bin in $build_dir"
|
||||
else
|
||||
git clone --depth 1 https://aur.archlinux.org/yay-bin.git "$build_dir/yay-bin"
|
||||
( cd "$build_dir/yay-bin" && makepkg -si --noconfirm )
|
||||
rm -rf "$build_dir"
|
||||
fi
|
||||
ok "yay installed"
|
||||
fi
|
||||
|
||||
# --- i3lock -> i3lock-color swap ------------------------------------------
|
||||
# betterlockscreen needs i3lock-color; it conflicts with plain i3lock.
|
||||
if pacman -Qq i3lock >/dev/null 2>&1 && ! pacman -Qq i3lock-color >/dev/null 2>&1; then
|
||||
step "removing repo i3lock so i3lock-color can take over"
|
||||
run sudo pacman -Rdd --noconfirm i3lock || warn "could not remove i3lock; yay will prompt about the conflict"
|
||||
fi
|
||||
|
||||
mapfile -t AUR_PKGS < <(read_pkg_list "$PACKAGES_DIR/aur.txt")
|
||||
|
||||
TO_INSTALL=()
|
||||
for p in "${AUR_PKGS[@]}"; do
|
||||
pacman -Qq "$p" >/dev/null 2>&1 || TO_INSTALL+=("$p")
|
||||
done
|
||||
|
||||
if [[ ${#TO_INSTALL[@]} -eq 0 ]]; then
|
||||
ok "all AUR packages already installed"
|
||||
else
|
||||
step "installing: ${TO_INSTALL[*]}"
|
||||
for p in "${TO_INSTALL[@]}"; do
|
||||
run yay -S --needed --noconfirm --answerclean None --answerdiff None "$p" \
|
||||
|| warn "AUR package failed to build: $p (continuing)"
|
||||
done
|
||||
fi
|
||||
|
||||
ok "AUR stage done"
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stage 30 — deploy dotfiles into $HOME (backing up whatever was there).
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
||||
|
||||
log "Deploying dotfiles"
|
||||
|
||||
require_not_root
|
||||
|
||||
CFG="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
|
||||
# --- home-level files ------------------------------------------------------
|
||||
install_file "$DOTFILES_DIR/xinitrc" "$HOME/.xinitrc" 755
|
||||
install_file "$DOTFILES_DIR/xprofile" "$HOME/.xprofile" 644
|
||||
install_file "$DOTFILES_DIR/Xresources" "$HOME/.Xresources" 644
|
||||
install_file "$DOTFILES_DIR/gtkrc-2.0" "$HOME/.gtkrc-2.0" 644
|
||||
|
||||
# --- ~/.config/* -----------------------------------------------------------
|
||||
# Copy each tracked file individually so untracked files the user added
|
||||
# (e.g. their own polybar module) survive a re-run.
|
||||
while IFS= read -r -d '' src; do
|
||||
rel="${src#"$DOTFILES_DIR"/config/}"
|
||||
install_file "$src" "$CFG/$rel" 644
|
||||
done < <(find "$DOTFILES_DIR/config" -type f -print0)
|
||||
|
||||
# --- executables -----------------------------------------------------------
|
||||
step "installing helper scripts to ~/.local/bin"
|
||||
run mkdir -p "$HOME/.local/bin"
|
||||
while IFS= read -r -d '' src; do
|
||||
install_file "$src" "$HOME/.local/bin/$(basename "$src")" 755
|
||||
done < <(find "$DOTFILES_DIR/local/bin" -type f -print0)
|
||||
|
||||
# Scripts inside ~/.config need the exec bit too
|
||||
for s in "$CFG/polybar/launch.sh" "$CFG/rofi/scripts/powermenu.sh"; do
|
||||
[[ -f "$s" ]] && run chmod 755 "$s"
|
||||
done
|
||||
if [[ -d "$CFG/i3/scripts" ]]; then
|
||||
run find "$CFG/i3/scripts" -type f -name '*.sh' -exec chmod 755 {} +
|
||||
fi
|
||||
|
||||
# --- user-owned override directories (never overwritten) -------------------
|
||||
run mkdir -p "$CFG/i3/config.d"
|
||||
# The include glob in the main config must match at least one file, otherwise
|
||||
# i3 complains on every reload — so ship a comment-only placeholder.
|
||||
if [[ ! -f "$CFG/i3/config.d/00-local.conf" && "$DRY_RUN" != "1" ]]; then
|
||||
cat > "$CFG/i3/config.d/00-local.conf" <<'TXT'
|
||||
# Personal i3 overrides.
|
||||
#
|
||||
# Every *.conf in this directory is included at the very END of
|
||||
# ~/.config/i3/config, so anything you put here wins over the defaults:
|
||||
# re-binding a key here silently replaces the earlier bindsym.
|
||||
#
|
||||
# This directory is never overwritten by the arch-i3 installer.
|
||||
#
|
||||
# Examples:
|
||||
# bindsym $mod+Shift+b exec --no-startup-id firefox
|
||||
# gaps inner 12
|
||||
# for_window [class="Spotify"] move to workspace $ws9
|
||||
TXT
|
||||
fi
|
||||
|
||||
# --- wallpapers ------------------------------------------------------------
|
||||
step "generating wallpapers"
|
||||
if [[ "$DRY_RUN" == "1" ]]; then
|
||||
skip "would generate wallpapers into $WALLPAPER_DIR"
|
||||
else
|
||||
mkdir -p "$WALLPAPER_DIR"
|
||||
# Prefer any images the user dropped into the repo's wallpapers/ dir
|
||||
shopt -s nullglob
|
||||
repo_walls=("$REPO_ROOT"/wallpapers/*.{png,jpg,jpeg,webp})
|
||||
shopt -u nullglob
|
||||
if [[ ${#repo_walls[@]} -gt 0 ]]; then
|
||||
cp -n "${repo_walls[@]}" "$WALLPAPER_DIR/" || true
|
||||
ok "copied ${#repo_walls[@]} wallpaper(s) from the repo"
|
||||
fi
|
||||
# Generate the Catppuccin set if the directory is still (nearly) empty
|
||||
if [[ $(find "$WALLPAPER_DIR" -maxdepth 1 -type f | wc -l) -lt 2 ]]; then
|
||||
# pipefail would turn a missing/failing xrandr into a stage abort
|
||||
geom=""
|
||||
if have xrandr; then
|
||||
geom="$(xrandr --query 2>/dev/null | awk '/\*/{print $1; exit}' || true)"
|
||||
fi
|
||||
"$REPO_ROOT/scripts/make-wallpaper.sh" "$WALLPAPER_DIR" "${geom:-2560x1440}" \
|
||||
|| warn "wallpaper generation failed (ImageMagick missing?)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- default applications --------------------------------------------------
|
||||
step "setting default applications"
|
||||
if [[ "$DRY_RUN" != "1" ]] && have xdg-mime; then
|
||||
xdg-mime default thunar.desktop inode/directory 2>/dev/null || true
|
||||
xdg-settings set default-web-browser firefox.desktop 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [[ -d "$BACKUP_DIR" ]]; then
|
||||
warn "previous configs were moved to: $(tildify "$BACKUP_DIR")"
|
||||
fi
|
||||
|
||||
ok "dotfiles deployed"
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stage 40 — enable system services, user services and groups.
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
||||
|
||||
log "Services & system integration"
|
||||
|
||||
require_arch
|
||||
require_not_root
|
||||
require_sudo
|
||||
|
||||
enable_system() {
|
||||
local unit="$1"
|
||||
if ! systemctl list-unit-files --no-legend | grep -q "^${unit}"; then
|
||||
skip "$unit not present"
|
||||
return 0
|
||||
fi
|
||||
if systemctl is-enabled --quiet "$unit" 2>/dev/null; then
|
||||
skip "$unit already enabled"
|
||||
else
|
||||
step "enabling $unit"
|
||||
run sudo systemctl enable --now "$unit"
|
||||
fi
|
||||
}
|
||||
|
||||
enable_user() {
|
||||
local unit="$1"
|
||||
if ! systemctl --user list-unit-files | grep -q "^${unit}"; then
|
||||
skip "user unit $unit not present"
|
||||
return 0
|
||||
fi
|
||||
if systemctl --user is-enabled --quiet "$unit" 2>/dev/null; then
|
||||
skip "user unit $unit already enabled"
|
||||
else
|
||||
step "enabling user unit $unit"
|
||||
run systemctl --user enable "$unit"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- system services -------------------------------------------------------
|
||||
enable_system NetworkManager.service
|
||||
enable_system bluetooth.service
|
||||
enable_system systemd-timesyncd.service
|
||||
|
||||
# --- user services ---------------------------------------------------------
|
||||
# pipewire is socket-activated; enabling the sockets is enough.
|
||||
enable_user pipewire.socket
|
||||
enable_user pipewire-pulse.socket
|
||||
enable_user wireplumber.service
|
||||
|
||||
# --- betterlockscreen on suspend ------------------------------------------
|
||||
# xss-lock (started from the i3 config) already handles lid-close and suspend.
|
||||
# The systemd unit below is the belt-and-braces version for logind-initiated
|
||||
# sleeps that happen outside the X session.
|
||||
if have betterlockscreen; then
|
||||
step "installing betterlockscreen@.service hook"
|
||||
if [[ "$DRY_RUN" != "1" ]]; then
|
||||
sudo tee /etc/systemd/system/betterlockscreen@.service >/dev/null <<UNIT
|
||||
[Unit]
|
||||
Description=Lock the screen with betterlockscreen before sleep
|
||||
Before=sleep.target
|
||||
[Service]
|
||||
User=%i
|
||||
Type=forking
|
||||
Environment=DISPLAY=:0
|
||||
ExecStart=/usr/bin/betterlockscreen --lock dimblur
|
||||
TimeoutSec=infinity
|
||||
[Install]
|
||||
WantedBy=sleep.target
|
||||
UNIT
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable "betterlockscreen@${USER}.service"
|
||||
fi
|
||||
ok "betterlockscreen sleep hook enabled"
|
||||
fi
|
||||
|
||||
# --- groups ----------------------------------------------------------------
|
||||
for grp in video input audio storage; do
|
||||
if getent group "$grp" >/dev/null && ! id -nG "$USER" | tr ' ' '\n' | grep -qx "$grp"; then
|
||||
step "adding $USER to group '$grp'"
|
||||
run sudo usermod -aG "$grp" "$USER"
|
||||
fi
|
||||
done
|
||||
|
||||
# --- font cache ------------------------------------------------------------
|
||||
step "rebuilding the font cache"
|
||||
run fc-cache -f >/dev/null
|
||||
if fc-list | grep -qi 'inconsolata.*nerd'; then
|
||||
ok "Inconsolata Nerd Font is available to X clients"
|
||||
else
|
||||
warn "Inconsolata Nerd Font not found by fontconfig — check that ttf-inconsolata-nerd installed"
|
||||
fi
|
||||
|
||||
# --- lock screen cache -----------------------------------------------------
|
||||
if have betterlockscreen; then
|
||||
wall="$(cat "${XDG_STATE_HOME:-$HOME/.local/state}/current-wallpaper" 2>/dev/null || true)"
|
||||
[[ -z "$wall" ]] && wall="$(find "$WALLPAPER_DIR" -maxdepth 1 -type f 2>/dev/null | sort | head -n1 || true)"
|
||||
if [[ -n "$wall" && -f "$wall" ]]; then
|
||||
step "building the betterlockscreen image cache (this takes a moment)"
|
||||
run betterlockscreen --update "$wall" --blur 0.5 >/dev/null 2>&1 \
|
||||
|| warn "betterlockscreen cache build failed; run 'betterlockscreen -u <image>' by hand"
|
||||
else
|
||||
skip "no wallpaper available yet — run 'betterlockscreen -u <image>' after first login"
|
||||
fi
|
||||
fi
|
||||
|
||||
ok "services configured"
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stage 50 — apply the Catppuccin Macchiato theme to everything that needs
|
||||
# a runtime nudge rather than a config file.
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
||||
|
||||
log "Theming"
|
||||
|
||||
require_not_root
|
||||
|
||||
GTK_THEME_NAME="catppuccin-macchiato-mauve-standard+default"
|
||||
ICON_THEME="Papirus-Dark"
|
||||
CURSOR_THEME="catppuccin-macchiato-dark-cursors"
|
||||
|
||||
# --- verify the themes actually landed ------------------------------------
|
||||
theme_exists() {
|
||||
local name="$1" kind="$2" # kind: themes | icons
|
||||
[[ -d "/usr/share/$kind/$name" || -d "$HOME/.local/share/$kind/$name" || -d "$HOME/.$kind/$name" ]]
|
||||
}
|
||||
|
||||
if theme_exists "$GTK_THEME_NAME" themes; then
|
||||
ok "GTK theme found: $GTK_THEME_NAME"
|
||||
else
|
||||
warn "GTK theme '$GTK_THEME_NAME' not installed"
|
||||
# The AUR package name for the accent may differ; fall back to whatever
|
||||
# catppuccin-macchiato theme is present so apps are not left on Adwaita.
|
||||
found="$(find /usr/share/themes "$HOME/.local/share/themes" -maxdepth 1 \
|
||||
-iname 'catppuccin-macchiato*' -printf '%f\n' 2>/dev/null | sort | head -n1 || true)"
|
||||
if [[ -n "$found" ]]; then
|
||||
warn "using '$found' instead — updating the GTK settings files"
|
||||
GTK_THEME_NAME="$found"
|
||||
if [[ "$DRY_RUN" != "1" ]]; then
|
||||
sed -i "s/^gtk-theme-name=.*/gtk-theme-name=$GTK_THEME_NAME/" \
|
||||
"$HOME/.config/gtk-3.0/settings.ini" "$HOME/.config/gtk-4.0/settings.ini" 2>/dev/null || true
|
||||
sed -i "s/^gtk-theme-name=.*/gtk-theme-name=\"$GTK_THEME_NAME\"/" \
|
||||
"$HOME/.gtkrc-2.0" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
theme_exists "$ICON_THEME" icons || warn "icon theme '$ICON_THEME' not installed"
|
||||
theme_exists "$CURSOR_THEME" icons || warn "cursor theme '$CURSOR_THEME' not installed"
|
||||
|
||||
# --- Papirus folders in Catppuccin colours --------------------------------
|
||||
if have papirus-folders; then
|
||||
step "recolouring Papirus folders (mauve)"
|
||||
run sudo papirus-folders -C cat-macchiato-mauve --theme Papirus-Dark >/dev/null 2>&1 \
|
||||
|| warn "papirus-folders failed; folders keep the stock colour"
|
||||
fi
|
||||
|
||||
# --- default cursor theme (used by the root window and non-GTK apps) ------
|
||||
step "setting the default X cursor theme"
|
||||
if [[ "$DRY_RUN" != "1" ]]; then
|
||||
mkdir -p "$HOME/.icons/default"
|
||||
cat > "$HOME/.icons/default/index.theme" <<INI
|
||||
[Icon Theme]
|
||||
Name=Default
|
||||
Comment=Default cursor theme
|
||||
Inherits=$CURSOR_THEME
|
||||
INI
|
||||
fi
|
||||
ok "cursor theme set to $CURSOR_THEME"
|
||||
|
||||
# --- dconf/gsettings, for the handful of apps that ignore settings.ini ----
|
||||
if have gsettings; then
|
||||
step "applying gsettings desktop preferences"
|
||||
run gsettings set org.gnome.desktop.interface gtk-theme "$GTK_THEME_NAME" 2>/dev/null || true
|
||||
run gsettings set org.gnome.desktop.interface icon-theme "$ICON_THEME" 2>/dev/null || true
|
||||
run gsettings set org.gnome.desktop.interface cursor-theme "$CURSOR_THEME" 2>/dev/null || true
|
||||
run gsettings set org.gnome.desktop.interface font-name "Inconsolata Nerd Font 11" 2>/dev/null || true
|
||||
run gsettings set org.gnome.desktop.interface monospace-font-name "Inconsolata Nerd Font 11" 2>/dev/null || true
|
||||
run gsettings set org.gnome.desktop.interface color-scheme "prefer-dark" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# --- reload anything already running --------------------------------------
|
||||
if [[ -n "${DISPLAY:-}" && "$DRY_RUN" != "1" ]]; then
|
||||
step "reloading the live session"
|
||||
xrdb -merge "$HOME/.Xresources" 2>/dev/null || true
|
||||
have i3-msg && i3-msg reload >/dev/null 2>&1 || true
|
||||
have dunstctl && dunstctl reload >/dev/null 2>&1 || true
|
||||
[[ -x "$HOME/.config/polybar/launch.sh" ]] && "$HOME/.config/polybar/launch.sh" || true
|
||||
fi
|
||||
|
||||
ok "theming applied"
|
||||
Executable
+117
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
# Shared helpers for the arch-i3 bootstrap scripts.
|
||||
# Sourced, never executed directly.
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
# --- paths -----------------------------------------------------------------
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
DOTFILES_DIR="$REPO_ROOT/dotfiles"
|
||||
PACKAGES_DIR="$REPO_ROOT/packages"
|
||||
WALLPAPER_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/wallpapers"
|
||||
BACKUP_DIR="$HOME/.config-backup-$(date +%Y%m%d-%H%M%S)"
|
||||
|
||||
# --- flags (exported by install.sh) ---------------------------------------
|
||||
: "${DRY_RUN:=0}"
|
||||
: "${ASSUME_YES:=0}"
|
||||
|
||||
# --- colours ---------------------------------------------------------------
|
||||
if [[ -t 1 ]]; then
|
||||
C_RESET=$'\e[0m'; C_BOLD=$'\e[1m'; C_DIM=$'\e[2m'
|
||||
C_BLUE=$'\e[38;5;111m'; C_GREEN=$'\e[38;5;115m'
|
||||
C_YELLOW=$'\e[38;5;222m'; C_RED=$'\e[38;5;210m'; C_MAUVE=$'\e[38;5;140m'
|
||||
else
|
||||
C_RESET=''; C_BOLD=''; C_DIM=''
|
||||
C_BLUE=''; C_GREEN=''; C_YELLOW=''; C_RED=''; C_MAUVE=''
|
||||
fi
|
||||
|
||||
log() { printf '%s==>%s %s\n' "$C_BLUE$C_BOLD" "$C_RESET" "$*"; }
|
||||
step() { printf '%s ->%s %s\n' "$C_MAUVE" "$C_RESET" "$*"; }
|
||||
ok() { printf '%s ok%s %s\n' "$C_GREEN" "$C_RESET" "$*"; }
|
||||
warn() { printf '%s !!%s %s\n' "$C_YELLOW" "$C_RESET" "$*" >&2; }
|
||||
die() { printf '%serror%s %s\n' "$C_RED$C_BOLD" "$C_RESET" "$*" >&2; exit 1; }
|
||||
skip() { printf '%s --%s %s\n' "$C_DIM" "$C_RESET" "$*"; }
|
||||
|
||||
# Run a command, honouring DRY_RUN.
|
||||
run() {
|
||||
if [[ "$DRY_RUN" == "1" ]]; then
|
||||
printf '%s dry%s %s\n' "$C_DIM" "$C_RESET" "$*"
|
||||
return 0
|
||||
fi
|
||||
"$@"
|
||||
}
|
||||
|
||||
confirm() {
|
||||
[[ "$ASSUME_YES" == "1" ]] && return 0
|
||||
[[ "$DRY_RUN" == "1" ]] && return 0
|
||||
local reply
|
||||
read -rp "$(printf '%s ??%s %s [y/N] ' "$C_YELLOW" "$C_RESET" "$1")" reply
|
||||
[[ "$reply" =~ ^[Yy]$ ]]
|
||||
}
|
||||
|
||||
have() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
# Print a path with $HOME collapsed to '~'. Done as a function because the
|
||||
# obvious ${path/#$HOME/~} keeps a literal backslash in the replacement.
|
||||
tildify() {
|
||||
local p="$1"
|
||||
if [[ "$p" == "$HOME"/* ]]; then
|
||||
printf '~/%s' "${p#"$HOME"/}"
|
||||
elif [[ "$p" == "$HOME" ]]; then
|
||||
printf '~'
|
||||
else
|
||||
printf '%s' "$p"
|
||||
fi
|
||||
}
|
||||
|
||||
# Read a package list file, dropping comments and blanks.
|
||||
read_pkg_list() {
|
||||
local file="$1"
|
||||
[[ -f "$file" ]] || die "package list not found: $file"
|
||||
sed -e 's/#.*$//' -e 's/[[:space:]]\+$//' "$file" | grep -v '^[[:space:]]*$' || true
|
||||
}
|
||||
|
||||
# Guard rails common to every stage.
|
||||
require_arch() {
|
||||
[[ -f /etc/arch-release ]] || die "this bootstrap targets Arch Linux (no /etc/arch-release found)"
|
||||
}
|
||||
|
||||
require_not_root() {
|
||||
[[ "$(id -u)" -ne 0 ]] || die "run this as your normal user, not root — sudo is invoked where needed"
|
||||
}
|
||||
|
||||
require_sudo() {
|
||||
have sudo || die "sudo is not installed; install it and add your user to the wheel group first"
|
||||
if [[ "$DRY_RUN" != "1" ]]; then
|
||||
sudo -v || die "sudo authentication failed"
|
||||
# Keep the sudo timestamp alive for the length of the run.
|
||||
( while true; do sudo -n true; sleep 50; kill -0 "$$" 2>/dev/null || exit; done ) &
|
||||
SUDO_KEEPALIVE_PID=$!
|
||||
trap 'kill "$SUDO_KEEPALIVE_PID" 2>/dev/null || true' EXIT
|
||||
fi
|
||||
}
|
||||
|
||||
# Back up a path (file, dir or symlink) into $BACKUP_DIR, preserving structure.
|
||||
backup_path() {
|
||||
local target="$1"
|
||||
[[ -e "$target" || -L "$target" ]] || return 0
|
||||
local rel="${target#"$HOME"/}"
|
||||
local dest="$BACKUP_DIR/$rel"
|
||||
step "backing up $(tildify "$target") -> $(tildify "$dest")"
|
||||
run mkdir -p "$(dirname "$dest")"
|
||||
run mv "$target" "$dest"
|
||||
}
|
||||
|
||||
# Copy a file from the repo into place, backing up whatever was there.
|
||||
install_file() {
|
||||
local src="$1" dest="$2" mode="${3:-644}"
|
||||
[[ -f "$src" ]] || die "missing source file: $src"
|
||||
if [[ -f "$dest" ]] && cmp -s "$src" "$dest"; then
|
||||
skip "$(tildify "$dest") already up to date"
|
||||
return 0
|
||||
fi
|
||||
backup_path "$dest"
|
||||
run mkdir -p "$(dirname "$dest")"
|
||||
run install -m "$mode" "$src" "$dest"
|
||||
ok "installed $(tildify "$dest")"
|
||||
}
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
# ---------------------------------------------------------------------------
|
||||
# Generate Catppuccin Macchiato wallpapers with ImageMagick.
|
||||
# Shipping binary images in a config repo is rude, so we synthesise them.
|
||||
# usage: make-wallpaper.sh [output-dir] [WIDTHxHEIGHT]
|
||||
# ---------------------------------------------------------------------------
|
||||
set -euo pipefail
|
||||
|
||||
OUT_DIR="${1:-${XDG_DATA_HOME:-$HOME/.local/share}/wallpapers}"
|
||||
GEOM="${2:-3840x2160}"
|
||||
W="${GEOM%x*}"
|
||||
H="${GEOM#*x}"
|
||||
|
||||
# ImageMagick 7 uses `magick`; 6 uses `convert`.
|
||||
if command -v magick >/dev/null 2>&1; then IM=(magick)
|
||||
elif command -v convert >/dev/null 2>&1; then IM=(convert)
|
||||
else
|
||||
echo "make-wallpaper: ImageMagick not found — skipping wallpaper generation" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
base="#24273a"
|
||||
mantle="#1e2030"
|
||||
crust="#181926"
|
||||
mauve="#c6a0f6"
|
||||
blue="#8aadf4"
|
||||
teal="#8bd5ca"
|
||||
pink="#f5bde6"
|
||||
|
||||
# --- 1. soft diagonal gradient --------------------------------------------
|
||||
"${IM[@]}" -size "${W}x${H}" \
|
||||
"gradient:${mantle}-${crust}" \
|
||||
-rotate 45 -gravity center -extent "${W}x${H}" \
|
||||
-blur 0x24 \
|
||||
"$OUT_DIR/macchiato-gradient.png"
|
||||
|
||||
# --- 2. radial glow over the base colour ----------------------------------
|
||||
"${IM[@]}" -size "${W}x${H}" "xc:${base}" \
|
||||
\( -size "${W}x${H}" "radial-gradient:${mauve}-${base}" -alpha set -channel A -evaluate multiply 0.22 +channel \) \
|
||||
-compose over -composite \
|
||||
\( -size "${W}x${H}" "radial-gradient:${teal}-${base}" -alpha set -channel A -evaluate multiply 0.12 +channel -roll +$((W/3))+$((H/3)) \) \
|
||||
-compose over -composite \
|
||||
-blur 0x40 \
|
||||
"$OUT_DIR/macchiato-glow.png"
|
||||
|
||||
# --- 3. subtle triangle/mosaic texture ------------------------------------
|
||||
"${IM[@]}" -size "$((W/12))x$((H/12))" "xc:${base}" \
|
||||
+noise Gaussian -blur 0x2 -normalize \
|
||||
-level 40%,60% \
|
||||
-fill "${base}" -colorize 82% \
|
||||
-resize "${W}x${H}!" -blur 0x3 \
|
||||
"$OUT_DIR/macchiato-texture.png"
|
||||
|
||||
# --- 4. flat base, for people who want zero distraction -------------------
|
||||
"${IM[@]}" -size "${W}x${H}" "xc:${base}" "$OUT_DIR/macchiato-flat.png"
|
||||
|
||||
# --- 5. logo-ish centred mark on the gradient -----------------------------
|
||||
"${IM[@]}" "$OUT_DIR/macchiato-gradient.png" \
|
||||
-gravity center \
|
||||
-fill "${mauve}" -stroke none \
|
||||
-draw "circle $((W/2)),$((H/2)) $((W/2)),$((H/2 - H/9))" \
|
||||
-fill "${base}" \
|
||||
-draw "circle $((W/2)),$((H/2)) $((W/2)),$((H/2 - H/9 + H/90))" \
|
||||
-blur 0x1 \
|
||||
"$OUT_DIR/macchiato-ring.png"
|
||||
|
||||
echo "wallpapers written to $OUT_DIR:"
|
||||
ls -1 "$OUT_DIR"
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
# ---------------------------------------------------------------------------
|
||||
# Put back the configs that install.sh moved aside.
|
||||
# scripts/restore-backup.sh # restore the most recent backup
|
||||
# scripts/restore-backup.sh <dir> # restore a specific backup
|
||||
# scripts/restore-backup.sh --list # show available backups
|
||||
# ---------------------------------------------------------------------------
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
||||
|
||||
mapfile -t BACKUPS < <(find "$HOME" -maxdepth 1 -type d -name '.config-backup-*' | sort -r)
|
||||
|
||||
if [[ "${1:-}" == "--list" ]]; then
|
||||
if [[ ${#BACKUPS[@]} -eq 0 ]]; then
|
||||
echo "no backups found in $HOME"
|
||||
else
|
||||
printf '%s\n' "${BACKUPS[@]}"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SRC="${1:-${BACKUPS[0]:-}}"
|
||||
[[ -n "$SRC" ]] || die "no backup directories found (looked for ~/.config-backup-*)"
|
||||
[[ -d "$SRC" ]] || die "not a directory: $SRC"
|
||||
|
||||
log "Restoring from ${SRC/#$HOME/\~}"
|
||||
step "the following files will be copied back over your current configs:"
|
||||
(cd "$SRC" && find . -type f | sed 's|^\./| ~/|')
|
||||
|
||||
confirm "Restore these files?" || die "aborted"
|
||||
|
||||
# -a preserves modes; the trailing /. copies the contents, not the directory.
|
||||
run cp -a "$SRC/." "$HOME/"
|
||||
ok "restored — restart i3 (Super+Shift+R) or log out and back in"
|
||||
Reference in New Issue
Block a user