initial commit

This commit is contained in:
2026-09-14 15:31:20 -05:00
commit 438546d84f
44 changed files with 3292 additions and 0 deletions
+50
View File
@@ -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"
+49
View File
@@ -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"
+51
View File
@@ -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"
+98
View File
@@ -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"
+106
View File
@@ -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"
+83
View File
@@ -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
View File
@@ -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")"
}
+70
View File
@@ -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"
+33
View File
@@ -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"