Capture this laptop's Omarchy customizations
Everything on top of a stock Omarchy 4.0.4-1 install, gathered so a second machine can be brought to the same state: the ~/.config files that actually differ from the shipped defaults, the three hand-built themes, the ten shell plugins pinned to commits, the two custom scripts, two user units, and the /etc drop-ins. install.sh applies it all idempotently, backing up anything it overwrites. capture.sh runs the other way so the repo stays the source of truth rather than a snapshot that rots. indie.herdr-hud's ten local-only commits ship as a git bundle plus a working-tree patch, since they stay off the upstream PR deliberately. Secrets, the dotfiles repo's territory (shell/git/mise), the remote-access stack and aether-managed themes are all scoped out; README says why for each. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Executable
+321
@@ -0,0 +1,321 @@
|
||||
#!/bin/bash
|
||||
# Apply this repo's Omarchy customizations to the machine it runs on.
|
||||
#
|
||||
# Safe to re-run: every step is a no-op when the target already matches, and any
|
||||
# file it would overwrite is backed up to <file>.bak.<epoch> first.
|
||||
#
|
||||
# ./install.sh # everything
|
||||
# ./install.sh config themes # only the named steps
|
||||
# ./install.sh --list # show the steps
|
||||
# ./install.sh --dry-run # print what would change, touch nothing
|
||||
#
|
||||
# Steps: packages config themes plugins bin services system
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
REPO="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
STAMP="$(date +%s)"
|
||||
DRY=0
|
||||
STEPS=(packages config themes plugins bin services system)
|
||||
|
||||
c_ok=$'\033[32m'; c_skip=$'\033[2m'; c_warn=$'\033[33m'; c_err=$'\033[31m'; c_hd=$'\033[1m'; c_off=$'\033[0m'
|
||||
[[ -t 1 ]] || { c_ok=; c_skip=; c_warn=; c_err=; c_hd=; c_off=; }
|
||||
|
||||
say() { printf '%s\n' "$*"; }
|
||||
head_() { printf '\n%s==> %s%s\n' "$c_hd" "$*" "$c_off"; }
|
||||
ok() { printf ' %s+%s %s\n' "$c_ok" "$c_off" "$*"; }
|
||||
skip() { printf ' %s= %s%s\n' "$c_skip" "$*" "$c_off"; }
|
||||
warn() { printf ' %s! %s%s\n' "$c_warn" "$*" "$c_off"; }
|
||||
err() { printf ' %sx %s%s\n' "$c_err" "$*" "$c_off"; }
|
||||
|
||||
run() { if (( DRY )); then printf ' %s? would: %s%s\n' "$c_skip" "$*" "$c_off"; else "$@"; fi; }
|
||||
|
||||
# install_file <src> <dest> [mode] -- copy only when content differs, backing up first
|
||||
install_file() {
|
||||
local src=$1 dest=$2 mode=${3:-}
|
||||
local rel=${dest/#"$HOME"/\~}
|
||||
if [[ -e $dest ]] && cmp -s "$src" "$dest"; then
|
||||
skip "$rel"
|
||||
return 0
|
||||
fi
|
||||
if (( DRY )); then
|
||||
printf ' %s? would write %s%s\n' "$c_skip" "$rel" "$c_off"
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "$(dirname -- "$dest")"
|
||||
if [[ -e $dest ]]; then
|
||||
cp -a "$dest" "$dest.bak.$STAMP"
|
||||
cp "$src" "$dest"
|
||||
[[ -n $mode ]] && chmod "$mode" "$dest"
|
||||
ok "$rel (previous saved as $(basename -- "$dest").bak.$STAMP)"
|
||||
else
|
||||
cp "$src" "$dest"
|
||||
[[ -n $mode ]] && chmod "$mode" "$dest"
|
||||
ok "$rel"
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- packages
|
||||
step_packages() {
|
||||
head_ "Packages"
|
||||
command -v omarchy >/dev/null || { err "omarchy not on PATH - is this an Omarchy system?"; return 1; }
|
||||
|
||||
local section="" repo=() aur=() optional=()
|
||||
while IFS= read -r line; do
|
||||
line=${line%%#*}; line=${line//[[:space:]]/}
|
||||
[[ -z $line ]] && continue
|
||||
case $line in
|
||||
'[repo]') section=repo; continue ;;
|
||||
'[aur]') section=aur; continue ;;
|
||||
'[optional]') section=optional; continue ;;
|
||||
esac
|
||||
case $section in
|
||||
repo) repo+=("$line") ;;
|
||||
aur) aur+=("$line") ;;
|
||||
optional) optional+=("$line") ;;
|
||||
esac
|
||||
done < "$REPO/packages.txt"
|
||||
|
||||
local missing=()
|
||||
for p in "${repo[@]}"; do pacman -Qq "$p" &>/dev/null || missing+=("$p"); done
|
||||
if (( ${#missing[@]} )); then
|
||||
say " installing from repos: ${missing[*]}"
|
||||
run omarchy pkg add "${missing[@]}" || err "some repo packages failed"
|
||||
else
|
||||
skip "all ${#repo[@]} repo packages present"
|
||||
fi
|
||||
|
||||
missing=()
|
||||
for p in "${aur[@]}"; do pacman -Qq "$p" &>/dev/null || missing+=("$p"); done
|
||||
if (( ${#missing[@]} )); then
|
||||
say " installing from AUR: ${missing[*]}"
|
||||
run omarchy pkg aur add "${missing[@]}" || err "some AUR packages failed"
|
||||
else
|
||||
skip "all ${#aur[@]} AUR packages present"
|
||||
fi
|
||||
|
||||
for p in "${optional[@]}"; do
|
||||
pacman -Qq "$p" &>/dev/null || warn "optional, not installed: $p (omarchy pkg add $p)"
|
||||
done
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- config
|
||||
step_config() {
|
||||
head_ "Config files -> ~/.config"
|
||||
local src rel
|
||||
while IFS= read -r -d '' src; do
|
||||
rel=${src#"$REPO/config/"}
|
||||
install_file "$src" "$HOME/.config/$rel"
|
||||
done < <(find "$REPO/config" -type f -print0 | sort -z)
|
||||
|
||||
if (( ! DRY )) && command -v hyprctl >/dev/null && [[ -n ${HYPRLAND_INSTANCE_SIGNATURE:-} ]]; then
|
||||
hyprctl reload >/dev/null 2>&1
|
||||
local errs; errs=$(hyprctl configerrors 2>/dev/null)
|
||||
if [[ -n $errs && $errs != "no errors"* ]]; then
|
||||
err "Hyprland reported config errors:"; printf '%s\n' "$errs" | sed 's/^/ /'
|
||||
else
|
||||
ok "hyprctl reload - no config errors"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- themes
|
||||
step_themes() {
|
||||
head_ "Themes -> ~/.config/omarchy/themes"
|
||||
local t name dest
|
||||
for t in "$REPO"/themes/*/; do
|
||||
[[ -d $t ]] || continue
|
||||
name=$(basename -- "$t")
|
||||
dest="$HOME/.config/omarchy/themes/$name"
|
||||
if [[ -d $dest ]] && diff -rq "$t" "$dest" >/dev/null 2>&1; then
|
||||
skip "$name"
|
||||
else
|
||||
run mkdir -p "$dest"
|
||||
run cp -r "$t." "$dest/"
|
||||
ok "$name"
|
||||
fi
|
||||
done
|
||||
say ""
|
||||
say " Themes managed by aether (not in this repo - reinstall with \`aether\`):"
|
||||
say " pink-gt3rs porsche-cayman porsche-rain"
|
||||
say " Apply one with: omarchy theme set <name>"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- plugins
|
||||
step_plugins() {
|
||||
head_ "Shell plugins -> ~/.config/omarchy/plugins"
|
||||
local dir="$HOME/.config/omarchy/plugins"
|
||||
[[ -d $dir ]] || run mkdir -p "$dir"
|
||||
|
||||
local id url sha p
|
||||
while IFS=$'\t' read -r id url sha; do
|
||||
[[ -z ${id:-} || $id == \#* ]] && continue
|
||||
p="$dir/$id"
|
||||
if [[ -d $p/.git ]]; then
|
||||
if [[ $(git -C "$p" rev-parse HEAD 2>/dev/null) == "$sha" ]]; then
|
||||
skip "$id @ ${sha:0:7}"
|
||||
continue
|
||||
fi
|
||||
if [[ -n $(git -C "$p" status --porcelain 2>/dev/null) ]]; then
|
||||
warn "$id has local changes - leaving it alone (wanted ${sha:0:7})"
|
||||
continue
|
||||
fi
|
||||
run git -C "$p" fetch --quiet origin
|
||||
run git -C "$p" checkout --quiet "$sha" && ok "$id -> ${sha:0:7}"
|
||||
else
|
||||
if (( DRY )); then
|
||||
printf ' %s? would clone %s -> %s%s\n' "$c_skip" "$id" "${sha:0:7}" "$c_off"
|
||||
continue
|
||||
fi
|
||||
git clone --quiet "$url" "$p" || { err "clone failed: $id ($url)"; continue; }
|
||||
git -C "$p" checkout --quiet "$sha" || warn "$id: sha $sha not found, left on default branch"
|
||||
ok "$id @ ${sha:0:7}"
|
||||
fi
|
||||
done < "$REPO/plugins.txt"
|
||||
|
||||
apply_herdr_hud
|
||||
apply_worktree_patch io.github.tallsam.navbar-cat
|
||||
|
||||
say ""
|
||||
say " Bar placement lives in config/omarchy/shell.json, already installed by the"
|
||||
say " config step. The shell hot-reloads it; \`omarchy restart shell\` if not."
|
||||
}
|
||||
|
||||
# indie.herdr-hud carries 10 commits that intentionally never went to the
|
||||
# upstream PR, plus an uncommitted working tree. Both are replayed here.
|
||||
apply_herdr_hud() {
|
||||
local p="$HOME/.config/omarchy/plugins/indie.herdr-hud"
|
||||
local bundle="$REPO/plugin-patches/indie.herdr-hud.bundle"
|
||||
[[ -d $p/.git && -f $bundle ]] || return 0
|
||||
|
||||
if git -C "$p" rev-parse --verify --quiet local >/dev/null; then
|
||||
skip "indie.herdr-hud: 'local' branch already present"
|
||||
elif (( DRY )); then
|
||||
printf ' %s? would replay the herdr-hud local branch%s\n' "$c_skip" "$c_off"
|
||||
return 0
|
||||
else
|
||||
git -C "$p" fetch --quiet "$bundle" 'refs/heads/local:refs/heads/local' \
|
||||
&& { git -C "$p" checkout --quiet local && ok "indie.herdr-hud: replayed 'local' branch"; } \
|
||||
|| warn "indie.herdr-hud: could not replay the local branch from the bundle"
|
||||
fi
|
||||
apply_worktree_patch indie.herdr-hud
|
||||
install_file "$REPO/plugin-patches/indie.herdr-hud--bin--herdr-agents" "$p/bin/herdr-agents" 755
|
||||
}
|
||||
|
||||
apply_worktree_patch() {
|
||||
local id=$1
|
||||
local p="$HOME/.config/omarchy/plugins/$id"
|
||||
local patch="$REPO/plugin-patches/$id-worktree.patch"
|
||||
[[ -d $p/.git && -s $patch ]] || return 0
|
||||
if git -C "$p" apply --reverse --check "$patch" &>/dev/null; then
|
||||
skip "$id: working-tree patch already applied"
|
||||
elif (( DRY )); then
|
||||
printf ' %s? would apply %s working-tree patch%s\n' "$c_skip" "$id" "$c_off"
|
||||
elif git -C "$p" apply --3way "$patch" &>/dev/null; then
|
||||
ok "$id: applied working-tree patch"
|
||||
else
|
||||
warn "$id: working-tree patch did not apply cleanly - see $patch"
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- bin
|
||||
step_bin() {
|
||||
head_ "Scripts -> ~/.local/bin"
|
||||
local f
|
||||
for f in "$REPO"/bin/*; do
|
||||
[[ -f $f ]] || continue
|
||||
install_file "$f" "$HOME/.local/bin/$(basename -- "$f")" 755
|
||||
done
|
||||
case ":$PATH:" in
|
||||
*":$HOME/.local/bin:"*) ;;
|
||||
*) warn "~/.local/bin is not on PATH" ;;
|
||||
esac
|
||||
say ""
|
||||
say " fix-stuck-displays needs passwordless sudo for 'tee /sys/class/drm/*/status'."
|
||||
say " omarchy-handoff needs a peer and a paired fingerprint before it does anything:"
|
||||
say " omarchy-handoff peer <hostname> <ip> then omarchy-handoff doctor"
|
||||
say " Its config (~/.config/lan-mouse/) holds a private key and per-machine"
|
||||
say " fingerprints, so it is not in this repo by design."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- services
|
||||
step_services() {
|
||||
head_ "systemd user units"
|
||||
local f name changed=0
|
||||
for f in "$REPO"/systemd/user/*.service; do
|
||||
[[ -f $f ]] || continue
|
||||
name=$(basename -- "$f")
|
||||
local dest="$HOME/.config/systemd/user/$name"
|
||||
[[ -e $dest ]] && cmp -s "$f" "$dest" || changed=1
|
||||
install_file "$f" "$dest"
|
||||
done
|
||||
(( changed )) && run systemctl --user daemon-reload
|
||||
for f in "$REPO"/systemd/user/*.service; do
|
||||
[[ -f $f ]] || continue
|
||||
name=$(basename -- "$f")
|
||||
if systemctl --user is-enabled --quiet "$name" 2>/dev/null; then
|
||||
skip "$name already enabled"
|
||||
else
|
||||
run systemctl --user enable "$name" && ok "enabled $name"
|
||||
fi
|
||||
done
|
||||
say ""
|
||||
say " Both are WantedBy=graphical-session.target, so they start with Hyprland"
|
||||
say " rather than now. 'systemctl --user start <unit>' to bring one up early."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- system
|
||||
step_system() {
|
||||
head_ "System drop-ins (needs sudo)"
|
||||
local src rel dest
|
||||
while IFS= read -r -d '' src; do
|
||||
rel=${src#"$REPO/system/"}
|
||||
dest="/etc/$rel"
|
||||
if [[ -e $dest ]] && cmp -s "$src" "$dest"; then
|
||||
skip "$dest"
|
||||
continue
|
||||
fi
|
||||
if (( DRY )); then
|
||||
printf ' %s? would write %s%s\n' "$c_skip" "$dest" "$c_off"
|
||||
continue
|
||||
fi
|
||||
sudo install -D -m 0644 "$src" "$dest" && ok "$dest"
|
||||
done < <(find "$REPO/system" -type f -print0 | sort -z)
|
||||
|
||||
if (( ! DRY )); then
|
||||
sudo sysctl --system >/dev/null 2>&1 && ok "sysctl reloaded"
|
||||
say ""
|
||||
say " hid_apple.conf (fnmode=2: F-keys act as F-keys) only takes effect once the"
|
||||
say " initramfs is rebuilt or the module reloads:"
|
||||
say " sudo mkinitcpio -P # or: sudo modprobe -r hid_apple && sudo modprobe hid_apple"
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- main
|
||||
main() {
|
||||
local want=()
|
||||
for a in "$@"; do
|
||||
case $a in
|
||||
--dry-run|-n) DRY=1 ;;
|
||||
--list|-l) printf '%s\n' "${STEPS[@]}"; exit 0 ;;
|
||||
-h|--help) sed -n '2,15p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
-*) err "unknown option: $a"; exit 2 ;;
|
||||
*)
|
||||
if [[ " ${STEPS[*]} " == *" $a "* ]]; then want+=("$a")
|
||||
else err "unknown step: $a (one of: ${STEPS[*]})"; exit 2; fi ;;
|
||||
esac
|
||||
done
|
||||
(( ${#want[@]} )) || want=("${STEPS[@]}")
|
||||
|
||||
say "${c_hd}omarchy-setup${c_off} repo=$REPO"
|
||||
(( DRY )) && warn "dry run - nothing will be changed"
|
||||
command -v omarchy >/dev/null || warn "omarchy not found; this looks like a non-Omarchy host"
|
||||
|
||||
for s in "${want[@]}"; do "step_$s"; done
|
||||
|
||||
head_ "Done"
|
||||
say " Re-running is safe. Backups from this run are suffixed .bak.$STAMP"
|
||||
say " Pull live changes back into the repo with ./capture.sh"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user